Java基础教程
JAVA基础知识
JAVA语言平台
- J2SE:(Java 2 Platform Standard Edition)标准版,为开发普通桌面和商务应用程序,可以完成一些桌面型应用程序的开发。
- J2ME:(Java 2 Platform Micro Edition)小型版,为开发电子消费产品和嵌入式设备。
- J2EE:(Java 2 Platform Enterprise Edition)企业版,为开发企业环境下的应用程序,该体系中包含的技术入Servlet、Jsp等,主要针对Web应用程序开发。
- JavaEE和JavaSE的区别:
- JavaEE:Java Enterprise Edition,Java企业版,多用于企业级开发,包括web开发等等。企业版本帮助开发和部署可移植、健壮、可伸缩切安全的服务端Java应用。Java EE是在JavaSE的基础上构建的他提供Web 服务、组建模型、管理和通信API.可以用来实现企业级的面向服务体系结构(service-oriented architecture,SOA)和web2.0应用程序。
- JavaSE:通常是指Java Standard Edition,Java标准版,就是一般Java程序的开发就可以(如桌面程序),可以看作是JavaEE的子集。它允许开发和部署在桌面、服务器、嵌入式环境和实施环境中使用的Java应用程序。JavaSE 包括支持Java Web服务开发的类,并为Java Platform,Enterprise Edition(Java EE)提供基础。
JAVA属于跨平台语言
- 什么是跨平台?
- JVM:JVM是java虚拟机(Java Virtual Machine),java程序需要运行在虚拟机上,不同平台有自己的虚拟机,因此java语言可以跨平台。
- JRE:包括Java虚拟机(Java Virtual Machine)和Java程序所需的核心类库等。如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。
JRE=JVM+核心类库。 - JDK:JDK是提供给Java开发人员使用的,其中包含了java的开发工具,也包括了JRE。所以安装了JDK,就不用在单独安装JRE了。
其中的开发工具:编译工具(javac.exe)、打包工具(jar.exe)等。
JDK=JRE+JAVA的开发工具。运行代码
- 运行代码步骤:
- Java语言是强类型语言,对于每一种数据都定义了明确的具体数据类型,在内存中分配了不同大小的内存空间。
- Java中数据类型的分类:
- 基本数据类型(8种):byte,short,int,long,float,double,char,boolean
- 引用数据类型(3种):类(class),接口(interface),数组([])
标识符
- 作用:给包,类,方法,变量等起名字
- 组成规则:
- 由字符,下划线_,美元符$组成(这里的字符采用的是unicode字符集,所以包括英文大小写字母,中文字符,数字字符等)
- 注意事项:
- 不能以数字开头
- 不能是Java中的关键字
- 命名原则:见名知意
- 隐式数据类型转换:
- 取值范围小的数据类型与取值范围大的数据类型进行运算,会先将小的数据类型提升为大的,再进行运算。
- 强制类型数据转换
- 格式:
1
2//目标类型 变量名 = (目标类型) (被转换的数据);
b = (byte)(a); - 注意事项:如果超出了被赋值的数据类型的取值范围得到的结果会与你期望的结果不同。
- 格式:
集成开发环境(IDE)
- 集成开发环境IDE(Integrated Development Environment)
运算符与表达式
- 运算符:对常量和变量进行操作的符号。
- 表达式:用运算符把常量或者变量连接起来的符合Java语法的式子。
不同运算符连接的式子体现的是不同类型的表达式。 - 常用运算符
- 算术运算符:+,-,*,/(取整),%(取余),++,–
- 赋值运算符:
- 基本的赋值运算符:=
- 扩展的赋值运算符:+=,-=,*=,/=,%=
- 关系运算符:
- ==,!=,>,>=,<,<=
- 关系运算符的结果都是boolean型,也就是要么是true,要么是false。
- 逻辑运算符:
- &,|,^,!,&&,||
- &逻辑与:有false则false。
- |逻辑或:有true则true。
- ^逻辑异或:相同为false,不同为true。
- !逻辑非:非false则true,非true则false。
- 逻辑运算符&&与&的区别?
- 最终结果一样。
- &&具有短路效果。左边是false,右边不执行。
- &是无论左边是false还是true,右边都会执行。
- 逻辑运算符||与|的区别?
- 最终结果一样
- ||具有短路效果。左边是true,右边不执行。
- |是无论左边是false还是true,右边都会执行。
- 三元运算符:
- 概述:把数据改进为键盘录入,提高程序的灵活性。
- 步骤:
- 导包:import java.util.Scanner;
- 创建对象:Scanner sc = new Scanner(System.in);
- 接收数据:int x = sc.nextInt();
选择流程控制语句
- 顺序结构:按照代码的先后顺序,依次执行。
- if语句:
- switch语句:
1
2
3
4
5
6
7
8
9
10
11
12switch(表达式) {
case 值1:
语句体1;
break;
case 值2:
语句体2;
break;
...
default:
语句体n+1;
break;
}循环流程控制语句
- for循环:
1
2
3for(初始化语句;判断条件语句;控制条件语句) {
循环体语句;
} - while循环:
1
2
3while(判断条件语句) {
循环体语句;
} - do…while循环:(while为true时继续执行)
1
2
3do{
循环体语句;
}while(判断条件语句);控制循环语句
- break:退出当前的循环。
- continue:退出本次循环。
产生随机数(Random)
- 使用步骤:
- 数组是存储同一种数据类型多个元素的容器。
- 数组既可以存储基本数据类型,也可以存储引用数据类型。
- 定义格式:
- 格式1:数据类型[] 数组名;
- 格式2:数据类型 数组名[];
- 数组初始化:Java中的数组必须先初始化,然后才能使用。所谓初始化:就是为数组中的数组元素分配内存空间,并为每个数组元素赋值。
- JVM内存划分
- 定义格式:
1
2
3数据类型[][] 数组名;
数据类型 数组名[][]; 不推荐
数据类型[] 数组名[]; 不推荐 - 初始化方式:
1
2
3
4数据类型[][] 变量名 = new 数据类型[m][n];
数据类型[][] 变量名 = new 数据类型[][]{{元素…},{元素…},{元素…}};
简化版格式:
数据类型[][] 变量名 = {{元素…},{元素…},{元素…}};
方法
- 方法就是完成特定功能的代码块。
- 格式:
1
2
3
4修饰符 返回值类型 方法名(参数类型 参数名1,参数类型 参数名2…) {
函数体;
return 返回值;
}方法的重载及参数传递
- 方法的重载:
- 概述:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
- 特点:与返回值类型无关,只看方法名和参数列表。在调用时,虚拟机通过参数列表的不同来区分同名方法。
- 参数传递:
- 方法的形式参数为基本数据类型:
- 形式参数的改变不影响实际参数。
- 形式参数:用于接收实际数据的变量
- 实际参数:实际参与运算的变量
- 方法的形式参数为引用数据类型:
- 形式参数的改变直接影响实际参数。
- 方法的形式参数为基本数据类型:
面向过程和面向对象
- 面向过程:
- 概述:其实就是面向着具体的每一个步骤和过程,把每一个步骤和过程完成,然后由这些功能方法相互调用,完成需求。
- 特点:强调的是过程,所有事情都需要自己完成。
- 面向对象:
- 概述:
- 我们学习编程是为了什么?
- 是为了把我们日常生活中实物用学习语言描述出来。
- 我们如何描述现实世界事物?
- 属性:就是该事物的描述信息(事物身上的名词)
- 行为:就是该事物能够做什么(事物身上的动词)
- Java中最基本的单位是类,Java中用class描述事物也是如此
- 成员变量:就是事物的属性
- 成员方法:就是事物的行为
- 定义类其实就是定义类的成员(成员变量和成员方法)
- 成员变量:和以前定义变量是一样的,只不过位置发生了改变。在类中,方法外。
- 成员方法:和以前定义方法是一样的,只不过把static去掉,后面在详细讲解static的作用。
- 类和对象的概念
- 我们学习编程是为了什么?
- 在类中的位置不同
- 成员变量:类中,方法外。
- 局部变量:方法中或者方法声明上(形式参数)。
- 在内存中的位置不同
- 成员变量:堆内存。
- 局部变量:栈内存。
- 生命周期不同
- 成员变量:随着对象的创建而存在,随着对象的消失而消失。
- 局部变量:随着方法的调用而存在,随着方法的调用完毕而消失。
- 初始化值的问题
- private关键字:
- 是一个权限修饰符。
- 可以修饰成员(成员变量和成员方法)
- 被private修饰的成员只在本类中才能访问。
- 封装的概述:
- 是面向对象三大特征之一(三大特征为:封装、继承、多态)。
- 是面向对象编程语言对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界无法直接操作和修改。
- 封装的原则:
- 将不需要对外提供的内容都隐藏起来。
- 把属性隐藏,提供公共方法对其访问。
- 将成员变量设置为private,提供对应的getXxx()/setXxx()方法。
- 封装的好处:
- 通过方法来控制成员变量的操作,提高了代码的安全性。
- 把代码用方法进行封装,提高了代码的复用性。
- this关键字:
- 主要用来给对象的数据进行初始化。
- 构造方法格式:
- 方法名与类名相同。
- 没有返回值类型,连void都没有。
- 没有具体的返回值。
- 构造方法注意事项与重载
- 如果我们没有给出构造方法,系统将会提供一个默认的无参构造方法供我们使用。
- 如果我们给出了构造方法,系统将不在提供默认的无参构造方法供我们使用。
这个时候,如果我们想使用无参构造方法,就必须自己提供。
推荐:自己给无参构造方法。 - 构造方法也是可以重载的,重载条件和普通方法相同。
JAVA常用API
API概述
- API(Application Programming Interface):应用程序编程接口。
- String类:
- 通过构造方法创建的字符串对象和直接赋值方式创建的字符串对象有什么区别呢?
- 通过构造方法创建字符串对象是在堆内存。
- 直接赋值方式创建对象是在方法区的常量池。
- 通过构造方法创建的字符串对象和直接赋值方式创建的字符串对象有什么区别呢?
- “==”比较符:
- 比较基本数据类型:比较的是基本数据类型的值是否相同。
- 比较引用数据类型:比较的是引用数据类型的地址值是否相同。
- StringBuilder类
- StringBuilder:是一个可变的字符串。字符串缓冲区类。
- String和StringBuilder的区别:
- String的内容是固定的,String拼接的时候会开辟一个新的内存空间。
- StringBuilder的内容是可变的,拼接的时候不会开辟新的内存空间。
对象数组
- 概述
- 特点:长度可变。
- ArrayList
集合: - 取长度:size();
IO流及FileWriter类、FileReader类(基本流)使用
- IO概述及分类:
- IO流用来处理设备之间的数据传输。
- Java对数据的操作是通过流的方式。
- Java用于操作流的类都在IO包中。
- 流按流向分为两种:输入流、输出流。
- FileWriter类使用:
- FileWriter向文件中写数据
- 步骤:
- 使用FileWriter流关联文件
- 利用FileWriter的写方法写数据
- 利用FileWriter的刷新方法将数据从内存刷到硬盘上
- 利用FileWriter的关流方法将释放占用的系统底层资源
- FileWriter方法:
- 构造方法
- FileWriter(String fileName)//传入一个文件的路径
- 成员方法
- void write(String str)//向文件中写str
- void flush()//将内存中的数据刷新到文件中(刷新缓冲区。流对象还可以继续使用)
- void close()//关流释放系统底层资源(先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31public class FileWriterDemo {
public static void main(String[] args) throws IOException {
//创建输出流对象
FileWriter fw = new FileWriter("e:\\a.txt");
//FileWriter fw = new FileWriter(new File("e:\\a.txt"), true); //设置成true就是追加
/*
* 创建输出流对象做了哪些事情:
* A:调用系统资源创建了一个文件
* B:创建输出流对象
* C:把输出流对象指向文件
*/
//调用输出流对象的写数据的方法
//写一个字符串数据
fw.write("第一行");
/*如何实现数据的换行?
'\n'可以实现换行,但是windows系统自带的记事本打开并没有换行,这是为什么呢?
因为windows识别的换行不是\n,而是\r\n
windows:\r\n
linux:\n
mac:\r
*/
fw.write("\r\n");
fw.write("第二行");
//数据没有直接写到文件,其实是写到了内存缓冲区
fw.flush();//刷新缓冲区。流对象还可以继续使用。
//释放资源
//通知系统释放和该文件相关的资源
fw.close();//先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
}
}
- 构造方法
- 步骤:
- FileWriter向文件中写数据
- FileReader类使用:
- FileReader读数据一次读取一个字符
- 输入流读文件的步骤:
- 创建输入流对象。
- 调用输入流对象的读数据方法。
- 释放资源。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class FileReaderDemo {
public static void main(String[] args) throws IOException {
//创建输入流对象
FileReader fr = new FileReader("f://a.txt");
//调用输入流对象的读数据方法
//int read():一次读取一个字符
int ch;
while ((ch = fr.read()) != -1) {
System.out.print((char) ch);
}
//释放资源
fr.close();
}
}
- 输入流读文件的步骤:
- FileReader读数据一次读取一个字符
- 利用FileReader和FileWriter完成文件复制
- 读一次写一次:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class CopyFileDemo {//a.txt->b.txt
public static void main(String[] args) throws IOException {
//创建输入流对象
FileReader fr = new FileReader("f://a.txt");
//创建输出流对象
FileWriter fw = new FileWriter("f://b.txt");
//读写数据
int ch;
while((ch=fr.read())!=-1) {
fw.write(ch);
}
//释放资源
fw.close();
fr.close();
}
} - 利用字符数组拷贝文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class CopyFileDemo2 {//a.txt->b.txt
public static void main(String[] args) throws IOException {
//创建输入流对象
FileReader fr = new FileReader("f://a.txt");
//创建输出流对象
FileWriter fw = new FileWriter("f://b.txt");
//读写数据
char[] chs = new char[1024];
int len;
while((len=fr.read(chs))!=-1) {
fw.write(chs, 0, len);
}
//释放资源
fw.close();
fr.close();
}
}缓冲流介绍和使用
- 读一次写一次:
- BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
1
2
3
4
5
6
7
8
9
10public class BufferedWriterDemo {
public static void main(String[] args) throws IOException {
//创建输出缓冲流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("f://a.txt"));
bw.write("hello");
bw.newLine();//写入一个换行符,这个换行符由系统而定
//bw.flush();
bw.close();
}
} - BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class BufferedReaderDemo {
public static void main(String[] args) throws IOException {
//创建输入缓冲流对象
BufferedReader br = new BufferedReader(new FileReader("f://a.txt"));
//一次读写一个字符数组
/*char[] chs = new char[1024];
int len;
while((len=br.read(chs))!=-1) {
System.out.print(new String(chs,0,len));
}*/
//一次读取一行,但不会读取换行符
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
//释放资源
br.close();
}
} - 缓冲流复制文本文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class BufferedCopyFileDemo {//a.txt->b.txt
public static void main(String[] args) throws IOException {
//创建输入缓冲流对象
BufferedReader br = new BufferedReader(new FileReader("f://a.txt"));
//创建输出缓冲流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("f://b.txt"));
//读写数据
//一次读写一个字符数组
char[] chs = new char[1024];
int len;
while ((len = br.read(chs)) != -1) {
bw.write(chs, 0, len);
}
//释放资源
bw.close();
br.close();
}
} - 缓冲流的特有方法使用:
- BufferedWriter:
- void newLine():写一个换行符,这个换行符由系统决定,不同的操作系统newLine()方法使用的换行符不同
windows:\r\n
linux:\n
mac:\r
- void newLine():写一个换行符,这个换行符由系统决定,不同的操作系统newLine()方法使用的换行符不同
- BufferedReader
- String readLine():一次读取一行数据,但是不读取换行符
- BufferedWriter:
- 缓冲流的特有方法复制文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class BufferedCopyFileDemo2 {//a.txt->b.txt
public static void main(String[] args) throws IOException {
//创建输入缓冲流对象
BufferedReader br = new BufferedReader(new FileReader("f://a.txt"));
//创建输出缓冲流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("f://b.txt"));
//读写数据
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
//释放资源
bw.close();
br.close();
}
}
static静态关键字
- 静态概述:
- 当在定义类的时候,类中都会有相应的属性和方法。而属性和方法都是通过创建本类对象调用的。当在调用对象的某个方法时,这个方法没有访问到对象的特有数据时,方法创建这个对象有些多余。可是不创建对象,方法又调用不了,这时就会想,那么我们能不能不创建对象,就可以调用方法呢?
答案是可以的,我们可以通过static关键字来实现。static它是静态修饰符,一般用来修饰类中的成员。
- 当在定义类的时候,类中都会有相应的属性和方法。而属性和方法都是通过创建本类对象调用的。当在调用对象的某个方法时,这个方法没有访问到对象的特有数据时,方法创建这个对象有些多余。可是不创建对象,方法又调用不了,这时就会想,那么我们能不能不创建对象,就可以调用方法呢?
- 静态的特点:
- 被static修饰的成员变量属于类,不属于这个类的某个对象,被所有的对象所共享。
(也就是说,多个对象在访问或修改static修饰的成员变量时,其中一个对象将static成员变量值进行了修改,其他对象中的static成员变量值跟着改变,即多个对象共享同一个static成员变量) - 被static修饰的成员可以并且建议通过类名直接访问
访问静态成员的格式:- 类名.静态成员变量名
- 类名.静态成员方法名(参数)
- 静态的加载优先于对象。
- 随着类的加载而加载。
- 被static修饰的成员变量属于类,不属于这个类的某个对象,被所有的对象所共享。
- 静态的注意事项:
- 静态成员只能直接访问静态成员。
- 非静态成员既可以访问非静态成员也可以访问静态成员。
- 即:
- 静态方法:
- 可以调用静态的成员变量
- 可以调用静态的成员方法
- 不可以调用非静态成员变量
- 不可以调用非静态成员方法
- 静态方法只能调用静态的成员
- 非静态方法:
- 可以调用静态的成员变量
- 可以调用静态的成员方法
- 可以调用非静态的成员变量
- 可以调用非静态的成员方法
- 可以调用静态的成员方法
- 静态的方法中没有this这个对象。
- 静态方法:
- 静态的优缺点:
- Math.abs(double a)//返回绝对值
- Math.ceil(double a)//天花板 向上取整
- Math.floor(double a)//地板 向下取整
- Math.round(double a)//四舍五入
- Math.pow(double a, double b)//返回第一个参数的第二个参数次幂(a的b次方)
- Math.random()//返回一个随机数,大于零且小于一
类变量与实例变量辨析
- 类变量:其实就是静态变量
- 定义位置:定义在类中方法外
- 所在内存区域:方法区
- 生命周期:随着类的加载而加载
- 特点:无论创建多少对象,类变量仅在方法区中,并且只有一份
- 实例变量:其实就是非静态变量
- 局部代码块:定义在方法或语句中。
- 以”{}”划定的代码区域,此时只需要关注作用域的不同即可
- 方法和类都是以代码块的方式划定边界的
- 构造代码块:定义在类中成员位置的代码块。
- 优先于构造方法执行,构造代码块用于执行所有对象均需要的初始化动作
- 每创建一个对象均会执行一次构造代码块。
- 静态代码块:定义在成员位置,使用static修饰的代码块。
- 随着类的加载而加载,只加载一次,加载类时需要做的一些初始化,比如加载驱动等。
- 它优先于主方法执行、优先于构造代码块执行,当以任意形式第一次使用到该类时执行。
- 该类不管创建多少对象,静态代码块只执行一次。
- 可用于给静态变量赋值,用来给类进行初始化。
继承(面向对象三大特性之一)
继承的概述:
- 在现实生活中,继承一般指的是子女继承父辈的财产。在程序中,继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关系体系。
- 多个类有共同的成员变量和成员方法,抽取到另外一个类中(父类),在让多个类去继承这个父类,我们的多个类就可以获取到父类中的成员了。
继承的格式及使用:
- 在程序中,如果想声明一个类继承另一个类,需要使用extends关键字。
- 格式:
class 子类 extends 父类 {}
。继承的特点:
- 在Java中,类只支持单继承,不允许多继承,也就是说一个类只能有一个直接父类。
- 多个类可以继承一个父类。
- 在Java中,多层继承是可以的,即一个类的父类可以再去继承另外的父类。
- 在Java中,子类和父类是一种相对概念,也就是说一个类是某个类父类的同时,也可以是另一个类的子类。
继承中成员变量的特点:
- 子类只能获取父类非私有成员
- 子父类中成员变量的名字不一样直接获取父类的成员变量。
- 子父类中成员变量名字是一样的获取的是子类的成员变量。
- 就近原则:谁离我近我就用谁
- 如果有局部变量就使用局部变量。
- 如果没有局部变量,有子类的成员变量就使用子类的成员变量。
- 如果没有局部变量和子类的成员变量,有父类的成员变量就使用父类的成员变量。
- super:可以获取父类的成员变量和成员方法,用法和this是相似的。
继承中成员方法的特点及方法重写
- 子类中没有这个方法,则调用父类的。
- 子类中重写了这个方法,则调用子类的。
- 应用场景:当父类的方法不能完全满足子类使用的时候,既可以保留父类的功能(沿袭、传承),还可以有自己特有的功能
- 注意事项:
super(实参列表);
:在子类的构造方法中使用,用来调用父类中的构造方法(具体哪一个由传递的参数决定),并且只能在构造方法第一行使用this(实参列表);
:在类的构造方法中使用,用来调用本类中的其它构造方法(具体哪一个由传递的参数决定),并且只能在构造方法的第一行使用- 在子类的构造方法的第一行代码如果没有调用父类的构造或者没有调用子类的其他构造,则默认调用父类无参构造。
- 为什么要调用父类构造?
- 因为需要给父类的成员变量初始化。
- 肯定会先把父类的构造执行完毕,在去执行子类构造中的其他代码。
this和super的区别
- this:当前对象的引用
- 调用子类的成员变量。
- 调用子类的成员方法。
- 在子类的构造方法第一行调用子类其他构造方法。
- super:子类对象的父类引用
- 优点
- 提高了代码的复用性
- 提高了代码的可维护性
- 缺点:
- 匿名对象即无名对象,直接使用new关键字来创建对象。
- 应用场景:
- 当方法只调用一次的时候可以使用匿名对象。
- 可以当作参数进行传递,但是无法在传参之前做其他的事情。
- 注意:匿名对象可以调用成员变量并赋值,但是赋值并没有意义。
final关键字
- final:修饰符,可以用于修饰类、成员方法和成员变量
- 概述:
- 当编写一个类时,我们往往会为该类定义一些方法,这些方法是用来描述该类的功能具体实现方式,那么这些方法都有具体的方法体。
- 但是有的时候,某个父类只是知道子类应该包含怎么样的方法,但是无法准确知道子类如何实现这些方法。比如一个图形类应该有一个求周长的方法,但是不同的图形求周长的算法不一样。那该怎么办呢?
- 分析事物时,发现了共性内容,就出现向上抽取。会有这样一种特殊情况,就是方法功能声明相同,但方法功能主体不同。那么这时也可以抽取,但只抽取方法声明,不抽取方法主体。那么此方法就是一个抽象方法。
- abstract:关键字,用于修饰方法和类
- 抽象方法:不同类的方法是相似,但是具体内容又不太一样,所以我们只能抽取他的声明,没有具体的方法体。
- 抽象类:有抽象方法的类必须是抽象类
- 抽象类的特点
- 抽象方法只能在抽象类里面
- 抽象类和抽象方法必须被abstract修饰
- 抽象类不能创建对象(不能实例化)
- 抽象类中可以有非抽象的方法
- 抽象类和类的关系也是继承
- 一个类继承了抽象类要么重写所有的抽象方法,要么他自己是抽象类
- 抽象方法只能在抽象类里面
- 抽象类的成员的特点:
- 成员变量
- 可以有成员变量
- 可以有常量
- 成员方法
- 可以有抽象方法
- 可以有非抽象方法
- 构造方法
- 可以有构造方法的,需要对抽象类的成员变量进行初始化
- 成员变量
- 抽象类的细节:
- 抽象类关键字abstract不可以和哪些关键字共存?
- private:私有的方法子类是无法继承到的,也不存在覆盖,而abstract和private一起使用修饰方法,abstract既要子类去实现这个方法,而private修饰子类根本无法得到父类这个方法。互相矛盾。
- final:
- 抽象类不能和final共存,因为抽象类自身无法创建对象,我们需要通过子类创建对象,一旦抽象类使用final关键字,那么抽象类就没有子类
- 抽象方法不能和final共存,因为抽象方法后期需要被子类重写,一旦加final无法重写
- static:抽象方法不能和static关键字共存,因为一旦加static我们就可以通过类名直接访问抽象方法,由于抽象方法没有方法体,没有任何意义,也不允许这样做
- 抽象类中是否可以不定义抽象方法?
- 是可以的,那这个抽象类的存在到底有什么意义呢?不让该类创建对象,方法可以直接让子类去使用
- 抽象类是否有构造函数?
- 有,抽象类的构造函数,是由子类的super语句来调用,用于给抽象类中的成员初始化
- 抽象类关键字abstract不可以和哪些关键字共存?
接口
- 概述:
- 接口是功能的集合,同样可看做是一种数据类型,是比抽象类更为抽象的”类”。
- 接口只描述所应该具备的方法,并没有具体实现,具体的实现由接口的实现类(相当于接口的子类)来完成。这样将功能的定义与实现分离,优化了程序设计。
- 格式:
- 与定义类的class不同,接口定义时需要使用interface关键字。
- 定义接口所在的仍为.java文件,虽然声明时使用的为interface关键字的编译后仍然会产生.class文件。这点可以让我们将接口看做是一种只包含了功能声明的特殊类。
- 定义格式:
1
2
3
4
5public interface 接口名 {
抽象方法1;
抽象方法2;
抽象方法3;
}
- 接口的使用:
- 接口中的方法全是抽象方法,直接new接口来调用方法没有意义,Java也不允许这样干
- 类与接口的关系为实现关系,即类实现接口。实现的动作类似继承,只是关键字不同,实现使用implements
- 其他类(实现类)实现接口后,就相当于声明:”我应该具备这个接口中的功能”。实现类仍然必须要重写方法以实现具体的功能。
- 格式:
1
2
3class 类 implements 接口 {
重写接口中方法
} - 在类实现接口后,该类就会将接口中的抽象方法继承过来,此时该类必须重写该抽象方法,完成具体的逻辑。
- 接口中成员的特点:
- 接口中可以定义变量,但是变量必须有固定的修饰符修饰,public static final 所以接口中的变量也称之为常量,其值不能改变。
- 接口中可以定义方法,方法也有固定的修饰符,public abstract
- 接口不可以创建对象。
- 子类必须覆盖掉接口中所有的抽象方法后,子类才可以实例化。否则子类是一个抽象类。
- 接口和类的关系:
- 类与类之间:继承关系,一个类只能直接继承一个父类,但是支持多层继承
- 类与接口之间:只有实现关系,一个类可以实现多个接口
- 接口与接口之间:只有继承关系,一个接口可以继承多个接口
- 接口的思想:
- 举例:我们都知道电脑上留有很多个插口,而这些插口可以插入相应的设备,这些设备为什么能插在上面呢?主要原因是这些设备在生产的时候符合了这个插口的使用规则,否则将无法插入接口中,更无法使用。发现这个插口的出现让我们使用更多的设备。
- 接口的出现方便后期使用和维护,一方是在使用接口(如电脑),一方在实现接口(插在插口上的设备)。例如:笔记本使用这个规则(接口),电脑外围设备实现这个规则(接口)。
- 集合体系中大量使用接口
- Collection接口
- List接口
- ArrayList实现类
- LinkedList实现类
- Set接口
- List接口
- Collection接口
- 接口优点:
- 类与接口的关系,实现关系,而且是多实现,一个类可以实现多个接口,类与类之间是继承关系,java中的继承是单一继承,一个类只能有一个父类,打破了继承的局限性。
- 对外提供规则(USB接口)
- 降低了程序的耦合性(可以实现模块化开发,定义好规则,每个人实现自己的模块,提高了开发的效率)
- 接口和抽象类的区别:
- 概述:
- 现实事物经常会体现出多种形态,如学生,学生是人的一种,则一个具体的同学张三既是学生也是人,即出现两种形态。
- Java作为面向对象的语言,同样可以描述一个事物的多种形态。如Student类继承了Person类,一个Student的对象便既是Student,又是Person
- 多态的定义格式:
就是父类的引用变量指向子类对象1
2父类类型 变量名 = new 子类类型();
变量名.方法名(); - 普通类多态定义的格式: 如:
1
父类 变量名 = new 子类();
1
2
3
4class Fu {}
class Zi extends Fu {}
//类的多态使用
Fu f = new Zi(); - 抽象类多态定义的格式: 如:
1
抽象类 变量名 = new 抽象类子类();
1
2
3
4
5
6
7
8
9
10abstract class Fu {
public abstract void method();
}
class Zi extends Fu {
public void method(){
System.out.println(“重写父类抽象方法”);
}
}
//类的多态使用
Fu fu= new Zi(); - 接口多态定义的格式: 如:
1
接口 变量名 = new 接口实现类();
1
2
3
4
5
6
7
8
9
10interface Fu {
public abstract void method();
}
class Zi implements Fu {
public void method(){
System.out.println(“重写接口抽象方法”);
}
}
//接口的多态使用
Fu fu = new Zi(); - 多态的前提:
- 子父类的继承关系
- 方法的重写
- 父类引用指向子类对象
- 动态绑定:运行期间调用的方法,是根据其具体的类型
- 多态成员的特点:
- 多态成员变量
- 当子父类中出现同名的成员变量时,多态调用该变量时:
- 编译时期:参考的是引用型变量所属的类中是否有被调用的成员变量。没有,编译失败。
- 运行时期:也是调用引用型变量所属的类中的成员变量。
- 简单记:编译和运行都参考等号的左边。编译运行看左边。
- 当子父类中出现同名的成员变量时,多态调用该变量时:
- 多态成员方法
- 当子父类中出现同名的成员方法时,多态调用该方法时:
- 编译时期:参考引用变量所属的类,如果没有类中没有调用的方法,编译失败。
- 运行时期:参考引用变量所指的对象所属的类,并运行对象所属类中的成员方法。
- 简而言之:编译看左边,运行看右边
- 当子父类中出现同名的成员方法时,多态调用该方法时:
- 多态静态方法
- 简而言之:编译时看的是左边,运行时看的也是左边
- 总结:编译时看的都是左边,运行时成员方法看的是右边,其他(成员变量和静态的方法)看的都是左边
- 多态成员变量
- 多态中向上转型与向下转型:
- 多态的转型分为向上转型与向下转型两种:
- 向上转型(由小到大,子类型转换成父类型):当有子类对象赋值给一个父类引用时,便是向上转型,多态本身就是向上转型的过程。
使用格式:父类类型 变量名 = new 子类类型();
如:Person p = new Student();
- 向下转型(由大到小):一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用转为子类引用,这个过程是向下转型。如果是直接创建父类对象,是无法向下转型的。
使用格式:子类类型 变量名 = (子类类型) 父类类型的变量;
如:Student stu = (Student) p; //变量p 实际上指向Student对象
- 引用类型之间的转换:
- 向上转型:由小到大(子类型转换成父类型)
- 向下转型:由大到小
- 基本数据类型的转换:
- 自动类型转换:由小到大
byte short char —> int —> long —> float —> double - 强制类型转换:由大到小
- 自动类型转换:由小到大
- 多态的优缺点:
- 优点:可以提高可维护性(多态前提所保证的),提高代码的可扩展性。
- 缺点:无法直接访问子类特有的成员。
包和权限修饰符
包的概述:
- java的包,其实就是我们电脑系统中的文件夹,包里存放的是类文件。
- 当类文件很多的时候,通常我们会采用多个包进行存放管理他们,这种方式称为分包管理。
- 在项目中,我们将相同功能的类放到一个包中,方便管理。并且日常项目的分工也是以包作为边界。
包的声明格式:
- 通常使用公司网址反写,可以有多层包,包名采用全部小写字母,多层包之间用”.”连接,不同包下的文件名可以重复。
- 类中包的声明格式:
package 包名.包名.包名…;
- 注意:声明包的语句,必须写在程序有效代码的第一行(注释不算)
包之间互相访问:
- 在访问类时,为了能够找到该类,必须使用含有包名的类全名(包名.类名)。
1
2
3
4
5
6
7包名.包名….类名
如:java.util.Scanner
java.util.Random
cn.lxy.Demo
带有包的类,创建对象格式:
包名.类名 变量名 = new包名.类名();
如:cn.lxy.Demo d = new cn.lxy.Demo(); - 前提:包的访问与访问权限密切相关,这里以一般情况来说,即类用public修饰的情况。
- 类的简化访问:
- 当我们要使用一个类时,这个类与当前程序在同一个包中(即同一个文件夹中),或者这个类是java.lang包中的类时通常可以省略掉包名,直接使用该类。
- 我们每次使用类时,都需要写很长的包名。很麻烦,我们可以通过import导包的方式来简化。
- 可以通过导包的方式使用该类,可以避免使用全类名编写(即,包类.类名)。
- 导包的格式:
import 包名.类名;
- 注意:’*’代表的是通配符,代表导入了这个包下所有的类,并没有导入子包下的类
- 在访问类时,为了能够找到该类,必须使用含有包名的类全名(包名.类名)。
权限修饰符:
在Java中提供了四种访问权限,使用不同的访问权限时,被修饰的内容会有不同的访问权限,以下表来说明不同权限的访问能力:public protected default private 同一类中 √ √ √ √ 同一包中(子类与无关类) √ √ √ 不同包的子类 √ √ 不同包中的无关类 √ 归纳一下:在日常开发过程中,编写的类、方法、成员变量的访问 A:要想仅能在本类中访问使用private修饰 B:要想本包中的类都可以访问除了private修饰符,其它都可以 C:要想本包中的类与其他包中的子类可以访问使用protected修饰 D:要想所有包中的所有类都可以访问使用public修饰。 注意:如果类用public修饰,则类名必须与文件名相同。一个文件中只能有一个public修饰的类。 权限修饰符:
- public:当前类,相同包下不同的类,不同包下的类
- default:当前类,相同包下不同的类(当前包下使用)
- private:当前类
- protected:当前类,相同包下不同的类(让子类对象使用)
修饰符总结:
修饰符 类 成员变量 成员方法 构造方法 public Y Y Y Y default Y Y Y Y protected Y Y Y private Y Y Y abstract Y Y static Y Y final Y Y Y 内部类
概述:
- 什么是内部类?
- 将类写在其他类的内部,可以写在其他类的成员位置和局部位置,这时写在其他类内部的类就称为内部类。其他类也称为外部类。
- 什么时候使用内部类?
- 在描述事物时,若一个事物内部还包含其他可能包含的事物,比如在描述汽车时,汽车中还包含这发动机,这时发动机就可以使用内部类来描述。
1
2
3
4class 汽车 { //外部类
class 发动机 { //内部类
}
}
- 在描述事物时,若一个事物内部还包含其他可能包含的事物,比如在描述汽车时,汽车中还包含这发动机,这时发动机就可以使用内部类来描述。
- 什么是内部类?
成员内部类:
- 定义在外部类中的成员位置。与类中的成员变量相似,可通过外部类对象进行访问
- 在类的成员位置,和成员变量以及成员方法所在的位置是一样的
- 在内部类当中,可以直接访问外部类的成员,包括私有成员
- 定义格式
1
2
3
4
5class 外部类 {
修饰符 class 内部类 {
//其他代码
}
} - 访问方式:
1
外部类名.内部类名 变量名 = new 外部类名().new 内部类名();
- 成员内部类可以使用的修饰符:private,public,procted,final,static,abstract
局部内部类:
- 定义在外部类方法中的局部位置。与访问方法中的局部变量相似,可通过调用方法进行访问.
- 在方法内,出了方法之后就无法使用
- 定义格式
1
2
3
4
5
6
7class 外部类 {
修饰符 返回值类型 方法名(参数) {
class 内部类 {
//其他代码
}
}
} - 访问方式:在外部类方法中,创建内部类对象,进行访问
匿名内部类:
- 作用:匿名内部类是创建某个类型子类对象的快捷方式。
- 格式:
1
2
3
4new 父类或接口(){
//如果是创建了继承这个类的子类对象,我们可以重写父类的方法
//如果是创建了实现这个接口的子类对象,我们必须要实现该接口的所有方法
}; - 可以把匿名内部类看成是一个没有名字的局部内部类
- 定义在方法当中
- 必须在定义匿名内部类的时候创建他的对象
- 原理:创建了继承这个类的子类对象或者是创建了实现这个接口的子类对象
Object类
- 概述:Object类是Java语言中的根类,即所有类的父类。它中描述的所有方法子类都可以使用。所有类在创建对象的时候,最终找的父类就是Object。
- 获取字节码对象的方式:
- 通过Object类的getClass()方法获取。
- 通过类名调用属性class来获取。
- 通过Class类的静态方法forName()来获取。
1
2
3
4
5
6
7
8
9
10
11
12
13public class ClassDemo {
public static void main(String[] args) throws ClassNotFoundException {
//方式1 通过Object类的getClass()方法获取
Teacher t = new Teacher();
Class clazz = t.getClass();//返回一个字节码对象
//方式2 通过类名调用属性class来获取
Class clazz2 = Teacher.class;
//方式3 通过Class类的静态方法forName()来获取
Class clazz3 = Class.forName("com.lxy.Teacher");
}
}
- toString()方法
- 由于toString方法返回的结果是内存地址,而在开发中,经常需要按照对象的属性得到相应的字符串表现形式,因此也需要重写它。
- equals()方法
- 成员方法
- Date:表示特定的瞬间,精确到毫秒,他可以通过方法来设定自己所表示的时间,可以表示任意的时间
- Date类的构造方法
Date()
:创建的是一个表示当前系统时间的Date对象Date(long date)
:根据”指定时间”创建Date对象
- Date类常用方法
- DateFormat:是日期/时间格式化子类的抽象类,它以与语言无关的方式格式化并解析日期或时间。日期/时间格式化子类(如 SimpleDateFormat类)允许进行格式化(也就是日期->文本)、解析(文本->日期)和标准化。
- 我们通过这个类可以帮我们完成日期和文本之间的转换。
- DateFormat&SimpleDateFormat的常用方法
- 概述:
- Calendar是日历类,在Date后出现,替换掉了许多Date的方法。该类将所有可能用到的时间信息封装为静态成员变量,方便获取。
- Calendar为抽象类,由于语言敏感性,Calendar类在创建对象时并非直接创建,而是通过静态方法创建,将语言敏感内容处理好,再返回子类对象,如下:
- Calendar类静态方法:
static Calendar getInstance()
:使用默认时区和语言环境获得一个日历Calendar c = Calendar.getInstance()
:返回当前时间
- Calendar类静态方法:
- Calendar类常用方法:
abstract void add(int field,int amount)
:根据日历规则,为给定的日历字段添加或减去制定的时间量。int get(int field)
:返回给定日历字段的值。Date getTime()
:返回一个表示此Calendar时间值(从历元至现在的毫秒偏移量)的Date对象。void set(int field,int value)
:将给定的日历字段设置为给定值。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class CalendarDemo {
public static void main(String[] args) {
//static Calendar getInstance()
Calendar c = Calendar.getInstance();//返回当前时间
//void set(int field, int value) :把指定的字段修改成指定的值
c.set(Calendar.DAY_OF_MONTH, 20);
//void add(int field, int amount): 在指定的字段上加上指定的值
c.add(Calendar.DAY_OF_MONTH, -1);
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH) + 1;
int day = c.get(Calendar.DAY_OF_MONTH);
System.out.println(year + "年" + month + "月" + day + "日");
}
}包装类&正则表达式
包装类
- 在实际程序使用中,程序界面上用户输入的数据都是以字符串类型进行存储的。而程序开发中,我们需要把字符串数据,根据需求转换成指定的基本数据类型,如年龄需要转换成int类型,考试成绩需要转换成double类型等。那么,想实现字符串与基本数据之间转换怎么办呢?
- Java中提供了相应的对象来解决该问题,基本数据类型对象包装类:java将基本数据类型值封装成了对象。封装成对象有什么好处?可以提供更多的操作基本数值的功能。
- 8种基本类型对应的包装类如下:
字节型 短整型 整型 长整型 字符型 布尔型 浮点型 浮点型 byte short int long char boolean float double Byte Short Integer Long Character Boolean Float Double - 包装类的常用方法
- 构造方法:
Integer(int value)
:构造一个新分配的Integer对象,它表示制定的int值。Integer(String s)
:构造一个新分配的Integer对象,它表示String参数所指示的int值。
- 成员方法:
int intValue()
:以int类型返回该Integer的值。static int parseInt(String s)
:将字符串参数作为有符号的十进制整数进行解析。String toString()
:返回一个表示该Integer值的String对象。
- 构造方法:
- 包装类的自动装箱与自动拆箱
- 正则表达式
集合&迭代器
集合体系结构图:
在最顶层的父接口Collection中定义了所有子类集合的共同属性和方法,因此我们首先需要学习Collection中共性方法,然后再去针对每个子类集合学习它的特有方法。
- 集合的体系结构:
- 由于不同的数据结构(数据的组织,存储方式),所以Java为我们提供了不同的集合,但是不同的集合他们的功能都是相似,不断的向上提取,将共性抽取出来,这就是集合体系结构形成的原因。
- Collection中的常用功能:
boolean add(Object e)
: 向集合中添加元素void clear()
:清空集合中所有元素boolean contains(Object o)
:判断集合中是否包含某个元素boolean isEmpty()
:判断集合中的元素是否为空boolean remove(Object o)
:根据元素的内容来删除某个元素int size()
:获取集合的长度Object[] toArray()
:能够将集合转换成数组并把集合中的元素存储到数组中
- 迭代器:
- java中提供了很多个集合,它们在存储元素时,采用的存储方式不同。我们要取出这些集合中的元素,可通过一种通用的获取方式来完成。
- Collection集合元素的通用获取方式:
- 在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续再判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
- 集合中把这种取元素的方式描述在Iterator接口中。
- Iterator接口的常用方法如下
boolean hasNext()
方法:判断集合中是否有元素可以迭代E next()
方法:用来返回迭代的下一个元素,并把指针向后移动一位。
- 注意:在使用迭代器遍历集合时,不允许对元素进行增加或删除操作。
增强for&泛型
- 增强for:
- 增强for循环是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的。
- 它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。
- 格式:
1
2for(元素的数据类型 变量 : Collection集合or数组){
} - 注意:它用于遍历Collection和数组。通常只进行遍历元素,不要在遍历的过程中对集合元素进行增删操作。
- 泛型:
- 数组:
- 数组,采用该结构的集合,对元素的存取有如下的特点:
- 数组的长度一旦定义则不可改变。
- 数组中的元素都有整数索引。
- 数组只能存储同一类型的元素。
- 数组既可以存储基本数据类型,也可以存储引用数据类型。
- 查找元素快:通过索引,可以快速访问指定位置的元素。
- 增删元素慢:每次添加元素需要移动大量元素或者创建新的数组。
- 数组,采用该结构的集合,对元素的存取有如下的特点:
- 链表:
- 链表,采用该结构的集合,对元素的存取有如下的特点:
- 多个节点之间,通过地址进行连接。例如,多个人手拉手,每个人使用自己的右手拉住下个人的左手,依次类推,这样多个人就连在一起了。
- 查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素。
- 增删元素快。
- 增加元素:只需要修改连接下个元素的地址即可。
- 删除元素:只需要修改连接下个元素的地址即可。
- 链表,采用该结构的集合,对元素的存取有如下的特点:
- 栈:
- 堆栈,采用该结构的集合,对元素的存取有如下的特点:
- 先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。例如,子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹。
- 堆栈,采用该结构的集合,对元素的存取有如下的特点:
- 队列:
- List子体系特点
- 有序的(存储和读取的顺序是一致的)
- 有整数索引
- 允许重复的
- List的特有功能:
void add(int index, E element)
:将元素添加到index索引位置上E get(int index)
:根据index索引获取元素E remove(int index)
:根据index索引删除元素E set(int index, E element)
:将index索引位置的的元素设置为element
- LinkedList特有功能:
- LinkedList底层使用的是链表结构,因此增删快,查询相对ArrayList较慢
void addFirst(E e)
:向链表的头部添加元素void addLast(E e)
:向链表的尾部添加元素E getFirst()
:获取链头的元素,不删除元素E getLast()
:获取链尾的元素,不删除元素E removeFirst()
:返回链头的元素并删除链头的元素E removeLast()
:返回链尾的元素并删除链尾的元素
- 如何选择使用不同的集合?
- 如果查询多,增删少,则使用ArrayList
- 如果查询少,增删多,则使用LinkedList
- 如果你不知道使用什么,则使用ArrayList
HashSet集合
- Set接口的特点:
- 存入集合的顺序和取出集合的顺序不一致
- 没有索引
- 不可重复
- HashSet唯一性原理:
- 规则:新添加到HashSet集合的元素都会与集合中已有的元素一一比较
- 首先比较哈希值(每个元素都会调用hashCode()产生一个哈希值)
- 如果新添加的元素与集合中已有的元素的哈希值都不同,新添加的元素存入集合
- 如果新添加的元素与集合中已有的某个元素哈希值相同,此时还需要调用equals(Object obj)比较
- 如果equals(Object obj)方法返回true,说明新添加的元素与集合中已有的某个元素的属性值相同,那么新添加的元素不存入集合。
- 如果equals(Object obj)方法返回false, 说明新添加的元素与集合中已有的元素的属性值都不同, 那么新添加的元素存入集合。
- hashCode方法优化:
- 规则:新添加到HashSet集合的元素都会与集合中已有的元素一一比较
- 面试题:Collection和Collections有什么区别?
- Collection是集合体系的最顶层,包含了集合体系的共性
- Collections是一个工具类,方法都是用于操作Collection
static void swap(List list, int i, int j);
:将指定列表中的两个索引进行位置互换static void sort(List<T> list);
:按照列表中元素的自然顺序进行排序static void shuffle(List list);
:将元素的顺序随机置换static void reverse(List list);
:将元素的顺序反转static void fill(List list, Object obj);
:使用指定的对象填充指定列表的所有元素static void copy(List dest, List src);
:是把源列表中的数据覆盖到目标列表(注意:目标列表的长度至少等于源列表的长度)static int binarySearch(List list, Object key);
:使用二分查找法查找指定元素在指定列表的索引位置HashMap集合
- Map接口概述:
- 我们通过查看Map接口描述,发现Map接口下的集合与Collection接口下的集合,它们存储数据的形式不同:
- Collection中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储
- Map中的集合,元素是成对存在的(理解为夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值。
- Collection中的集合称为单列集合,Map中的集合称为双列集合。
- 需要注意的是,Map中的集合不能包含重复的键,值可以重复;每个键只能对应一个值。
- 我们通过查看Map接口描述,发现Map接口下的集合与Collection接口下的集合,它们存储数据的形式不同:
- Map常用功能:
- 映射功能:
V put(K key, V value);
:以键=值的方式存入Map集合(如果key存在,则覆盖value,并将原来的value返回)
- 获取功能:
V get(Object key);
:根据键获取值int size();
:返回Map中键值对的个数
- 判断功能:
boolean containsKey(Object key);
:判断Map集合中是否包含键为key的键值对boolean containsValue(Object value);
:判断Map集合中是否包含值为value键值对boolean isEmpty();
:判断Map集合中是否没有任何键值对
- 删除功能:
void clear();
:清空Map集合中所有的键值对V remove(Object key);
:根据键值删除Map中键值对,并返回key所对应的值,如果没有删除成功则返回null
- 遍历功能:
Set<Map.Entry<K,V>> entrySet()
:将每个键值对封装到一个个Entry对象中,再把所有Entry的对象封装到Set集合中返回Set<K> keySet();
:将Map中所有的键装到Set集合中返回Collection<V> values();
:返回集合中所有的value的值的集合
- 映射功能:
- Map的两种遍历方式:
- 利用keySet()方法遍历:
- 首先召集所有的key
- 遍历所有的key
- 获取每一个key
- 让每一个key去找对应的value
- 利用entrySet()方法遍历:
- 利用keySet()方法遍历:
- 概述:
- 当参数不确定的时候, 类型要明确
- Java可以把多个参数直接帮我们转成数组
- 理解: 可变参数本质就是一个长度可变的数组.
- 格式:
- 实参: 一个参数一个参数的传递
- 形参:
类型...变量名
例如String... strs
。
- 注意:
- 在可变参数之后不可以再追加参数
- 参数的数量定义, 可以给多个甚至也可以一个都不给
异常产生&异常处理
- 异常概述:
- 什么是异常?Java代码在运行时期发生的问题就是异常。
- 在Java中,把异常信息封装成了一个类。当出现了问题时,就会创建异常类对象并抛出异常相关的信息(如异常出现的位置、原因等)。
- 在Java中使用Exception类来描述异常。
- API中Exception的描述:“Exception类及其子类是Throwable的一种形式,它用来表示java程序中可能会产生的异常,并要求对产生的异常进行合理的异常处理。”
- Exception有继承关系,它的父类是Throwable。Throwable是Java语言中所有错误或异常的超类,即祖宗类。
- 另外,在异常Exception类中,有一个子类要特殊说明一下,RuntimeException子类,RuntimeException及其它的子类只能在Java程序运行过程中出现。
- 我们再来观察Throwable类,能够发现与异常Exception平级的有一个Error,它是Throwable的子类,它用来表示java程序中可能会产生的严重错误。解决办法只有一个,修改代码避免Error错误的产生。
- 异常的体系结构:
- Throwable(最顶层)
- Error:出现的不能够处理的严重问题
- Exception:可以处理的问题
- Throwable(最顶层)
- 异常处理:
- 1.JVM默认处理方式:如果出现异常我们没有处理,jvm会帮我们进行处理,他会把异常的类型,原因还有位置显示在命令行并且还终止了程序,异常后面的代码将不再执行。
- 2.try…catch方式处理异常:
- 捕获:Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理捕获异常格式:
1
2
3
4
5
6
7
8
9try {
//需要被检测的语句。
}
catch(异常类 变量) { //参数。
//异常的处理语句。
}
finally {
//一定会被执行的语句。
} - try:该代码块中编写可能产生异常的代码。
- catch:用来进行某种异常的捕获,实现对捕获到的异常进行处理。
- finally:无论try…catch语句如何执行,finally的代码一定会执行。
- 捕获:Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理捕获异常格式:
- 3.throws方式处理异常
- throws使用:使用关键字throws在方法的声明出抛出异常
1
2权限修饰符 返回值类型 方法名(形参列表) throws 异常类型1,异常类型2...{
}
- throws使用:使用关键字throws在方法的声明出抛出异常
- 4.多异常处理
- 对代码进行异常检测,并对检测的异常传递给catch处理。对每种异常信息进行不同的捕获处理。
1
2
3
4
5
6
7
8
9
10
11void show(){//不用throws
try{
throw new Exception();//产生异常,直接捕获处理
}catch(XxxException e){
//处理方式
}catch(YyyException e){
//处理方式
}catch(ZzzException e){
//处理方式
}
} - 注意:这种异常处理方式,要求多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。
Throwable常用方法&自定义异常
- 对代码进行异常检测,并对检测的异常传递给catch处理。对每种异常信息进行不同的捕获处理。
- Throwable常用方法:
String getMessage();
:返回此throwable的详细消息字符串String toString();
:返回此throwable的简短描述void printStackTrace();
:打印异常的堆栈的跟踪信息
- finally的概述和应用场景
1
2
3
4
5try{
}catch(异常类型 异常变量){
}finally{
//释放资源的代码
}- 无论try…catch语句如何执行,finally的代码一定会执行。
- 编译时异常&运行时异常
- 递归,指在当前方法内调用自己的这种现象
1
2
3
4
5public void method(){
System.out.println(“递归的演示”);
//在当前方法内调用自己
method();
} - 递归注意事项:
- 递归一定要有出口,否则会内存溢出。
- 递归次数不宜过多,否则会内存溢出。
##File类
- File概述:
- File文件和目录路径名的抽象表示形式。即,Java中把文件或者目录(文件夹)都封装成File对象。也就是说如果我们要去操作硬盘上的文件,或者文件夹只要找到File这个类即可,那么我们就要研究研究File这个类中都有那些功能可以操作文件或者文件夹呢。
- File类的构造函数:
File(String pathname);
:将一个字符串路径封装成File对象File(String parent,String child);
:传入一个父级路径和子级路径File(File parent,String child);
:传入一个File类型的父级路径和子级路径
- File类创建和删除功能:
boolean createNewFile();
:指定路径不存在该文件时时创建文件,返回true,否则返回falseboolean mkdir();
:当指定的单级文件夹不存在时创建文件夹,并返回true,否则返回falseboolean mkdirs();
:当指定的多级文件夹某一级文件夹**不存在时,创建多级文件夹,并返回true,否则返回falseboolean delete();
:删除文件或者删除单级文件夹(注意:删除一个文件夹,这个文件夹下面不能有其他的文件和文件夹)
- File类的判断功能:
boolean exists();
:判断指定路径的文件或文件夹是否存在boolean isAbsolute();
:判断当前路径是否是绝对路径boolean isDirectory();
:判断当前的目录是否存在boolean isFile();
:判断当前路径是否是一个文件boolean isHidden();
:判断当前路径是否是隐藏文件
- File类的获取功能和修改名字功能
File getAbsoluteFile();
:获取文件的绝对路径,返回File对象String getAbsolutePath();
:获取文件的绝对路径,返回路径的字符串String getParent();
:获取当前路径的父级路径,以字符串形式返回该父级路径File getParentFile();
:获取当前路径的父级路径,以字File对象形式返回该父级路径String getName();
:获取文件或文件夹的名称String getPath();
:获取File对象中封装的路径long lastModified();
:以毫秒值返回最后修改时间long length();
:返回文件的字节数boolean renameTo(File dest);
: 将当前File对象所指向的路径 修改为 指定File所指向的路径
- File类的其它获取功能
String[] list();
:以字符串数组的形式返回当前路径下所有的文件和文件夹的名称(注意:只有指向文件夹的File对象才可以调用该方法)File[] listFiles();
:以File对象的形式返回当前路径下所有的文件和文件夹的名称(注意:只有指向文件夹的File对象才可以调用该方法)static File[] listRoots();
:获取计算机中所有的盘符
- File类的两个案例:
- 一、列出当前路径下(包含子目录)的所有以’.java’结尾的文件名称
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28public class FileDemo1 {
public static void main(String[] args) {
File f = new File("src");
listAllFileName(f);
}
/**
* 列出当前路径下(包含子目录)的所有以'.java'结尾的文件名称
*
* @param file 文件夹
*/
public static void listAllFileName(File file) {
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
//判断是否是文件对象
if (f.isFile()) {
if (f.getName().endsWith(".java")) {
System.out.println(f.getName());
}
} else if (f.isDirectory()) {
//是一个目录对象
listAllFileName(f);//递归调用
}
}
}
}
} - 二、删除指定目录下所有文件和目录(包含子目录)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33public class FileDemo2 {
public static void main(String[] args) {
File f = new File("e:\\threadFile");
delAllFiles(f);
}
/**
* 删除指定目录下所有文件和目录(包含子目录)
* 注意:如果要删除一个目录,则需要先删除这个目录下的所有子文件和子目录
* @param file 文件夹
*/
public static void delAllFiles(File file) {
if(file.isDirectory()) {
//干掉自己所有的子文件和子目录
//获取所有的子文件和子目录
File[] files = file.listFiles();
for (File f : files) {
if(f.isFile()) {
//直接干掉他
System.out.println(f.getName());
f.delete();
}
else if(f.isDirectory()) {
//继续查看是否还有文件和子目录
delAllFiles(f);
}
}
//干掉自己
System.out.println(file.getName());
file.delete();
}
}
}字符流与字节流
- 一、列出当前路径下(包含子目录)的所有以’.java’结尾的文件名称
- IO流分类:
- 按流向分:
- 输入流–读取数据–FileReader–Reader
- 输出流–写出数据–FileWriter–Writer
- 按数据类型分:
- 字节流
- 字节输入流–读取数据–InputStream
- 字节输出流–写出数据–OutputStream
- 字符流
- 字符输入流–读取数据–Reader
- 字符输出流–写出数据–Writer
- 字节流
- 按流向分:
- 字符流与字节流的区别:
- 字节流可以操作所有类型的文件,因为所有的文件在计算机中都是以字节形式存储。
- 而字符流只能用来操作纯文本类型的文件,不能操作字节类型的文件。
- 二进制文件只能使用字节流进行复制(使用windows自带记事本打开读不懂的)
- 文本文件的复制即可使用字符流,也可以使用字节流
标准输入流 & 转换流 & 打印流
- 标准输入输出流:
public static final InputStream in;
:标准输入流–字节输入流,用来读取键盘录入的数据.public static final PrintStream out;
:标准输出流–字节输出流,将数据输出到命令行.
- 转换流
OutputStreamWriter
:将字节输出流转换为字符输出流InputStreamReader
:将字节输入流转换为字符输入流
- 打印流
- 概述:
- 用于从流中读取对象。
ObjectInputStream
称为反序列化流,利用输入流从文件中读取对象。ObjectOutputStream
称为序列化流,利用输出流向文件中写入对象。- 特点:
- 用于操作对象。可以将对象写入到文件中,也可以从文件中读取对象。
- 可以用于读写任意类型的对象.
- 使用对象输出流写出对象,只能使用对象输入流来读取对象.
- 只能将支持 java.io.Serializable 接口的对象写入流中.
- ObjectOutputStream
writeObject
ObjectOutputStream(OutputStream out)
- ObjectInputStream
- Serializable:
- 序列号,是一个标识接口,只起标识作用,没有方法
- 当一个类的对象需要IO流进行读写的时候,这个类必须实现该接口
1
2
3
4
5
6
public class Student implements Serializable {
String name;
int age;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40public class ObjectOutputStreamDemo {
public static void main(String[] args) throws Exception {
//对象输出到文件
method();
//读取对象文件
//method2();
System.out.println("finish");
}
private static void method() throws Exception {
//创建对象输出流的对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E://b.txt"));
//创建学生对象
Student s = new Student("zhangsan", 18);
Student s2 = new Student("lisi", 19);
//写出学生对象
oos.writeObject(s);
oos.writeObject(s2);
//释放资源
oos.close();
}
private static void method2() throws Exception {
//创建对象输入流的对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E://b.txt"));
//读取对象
try {
while (true) {
Object obj = ois.readObject();
System.out.println(obj);
}
} catch (EOFException e) {//读取到末尾会报异常
System.out.println("读到了文件的末尾");
}
//释放资源
ois.close();
}
}解决对象输入流读取对象出现异常的问题
- 用集合解决
1
2
3
4
5
6
public class Student implements Serializable {
String name;
int age;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36public class ObjectOutputStreamDemo2 {
public static void main(String[] args) throws Exception {
//对象输出到文件
method();
//读取对象文件
//method2();
System.out.println("finish");
}
private static void method() throws Exception {
//创建对象输出流的对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E://b.txt"));
//创建集合对象
List<Student> list = new ArrayList<>();
//添加学生对象
list.add(new Student("wangwu", 30));
list.add(new Student("zhaoliu", 28));
//写出集合对象
oos.writeObject(list);
//释放资源
oos.close();
}
private static void method2() throws Exception {
//创建对象输入流的对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E://b.txt"));
//读取数据
Object obj = ois.readObject();
List<Student> list = (List) obj;
for (Student student : list) {
System.out.println(student);
}
//释放资源
ois.close();
}
}解决读写对象版本不一致问题(解决对实现序列化接口出现的黄色警告问题)
1
2
3
4
5
6
7
public class Student implements Serializable {
private static final long serialVersionUID = 6361890890437825953L;//显式声明序列化ID
String name;
int age;
}Properties集合
- Properties介绍:
- Properties类表示了一个持久的属性集。
- Properties可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。
- 特点:
- Hashtable的子类,map集合中的方法都可以用。
- 该集合没有泛型。键值都是字符串。
- 它是一个可以持久化的属性集。键值可以存储到集合中,也可以存储到持久化的设备(硬盘、U盘、光盘)上。键值的来源也可以是持久化的设备。
- 有和流技术相结合的方法。
void load(InputStream instream)
:从输入流中读取属性列表(键和元素对)void load(Reader reader)
:按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)void store(OutputStream outputStream,String comments)
:以适合使用load(InputStream)方法加载到Properties表中的格式,将此Properties表中的属性列表(键和元素对)写入输出流void store(Writer writer,String comments)
:以适合使用load(Reader)方法的格式,将此Properties表中的属性列表(键和元素对)写入输出字符
- 利用Properties存储键值对
1
2
3
4
5
6
7
8
9
10
11
12
13
14public class PropertiesDemo {
public static void main(String[] args) {
//创建属性列表对象
Properties prop = new Properties();
//添加映射关系
prop.put("CZBK001", "zhangsan");
prop.put("CZBK002", "lisi");
prop.put("CZBK003", "wangwu");
//遍历属性列表
for (Map.Entry<Object, Object> entry : prop.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
} - Properties与流结合使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54public class PropertiesDemo2 {
public static void main(String[] args) throws Exception {
//将属性列表写入文件
method();
//将属性列表写入文件(并加入注释)
//method3();
//从文件读取写入列表
//method2();
}
private static void method() throws Exception {
//创建属性列表对象
Properties prop = new Properties();
//添加映射关系
prop.setProperty("CZBK001", "zhangsan");
prop.setProperty("CZBK002", "lisi");
prop.setProperty("CZBK003", "wangwu");
//创建打印流对象
PrintWriter out = new PrintWriter("E://d.txt");
//void list(PrintWriter out)
prop.list(out);
//释放资源
out.close();
}
private static void method2() throws Exception {
//创建属性列表对象
Properties prop = new Properties();
//创建一个输入流对象
FileReader fr = new FileReader("E://d.txt");
//void load(Reader reader)
prop.load(fr);//不会读取注释信息
//释放资源
fr.close();
System.out.println(prop);
}
public static void method3() throws Exception {
//创建属性列表对象
Properties prop = new Properties();
//添加映射关系
prop.setProperty("CZBK001", "zhangsan");
prop.setProperty("CZBK002", "lisi");
prop.setProperty("CZBK003", "wangwu");
//创建输出流对象
FileWriter fw = new FileWriter("E://d.txt");
//void store(Writer writer, String comments)
prop.store(fw, "hello world");//会加入注释和日期
//释放资源
fw.close();
}
}编码表
- 概述:
- 编码表:把计算机底层的二进制数据转换成我们能看到的字符
- ASCII
- GB2312:GBK
- Unicode:所有的字符都占2个字节
- UTF-8 长度可变的码表
- ANSI:本地编码表(gbk)
- Java中的字符串默认使用的ANSI(gbk)
- 乱码:编码保持前后一致即可解决
- 编码表:把计算机底层的二进制数据转换成我们能看到的字符
- Java中字符串的编码
- 常用方法
- 构造方法(字节数组转字符串):
String()
:初始化一个新创建的String对象,使其表示一个空字符序列。String(byte[] bytes)
:使用平台的默认字符集解码指定的byte数组,构造一个新的String。String(byte[] bytes, Charset charset)
:通过使用指定的charset解码指定的byte数组,构造一个新的String。
- 成员方法(字符串转字节数组)
getBytes()
:使用平台的默认字符集将此String编码为byte序列,并将结果存储到一个新的byte数组中。getBytes(Charset charset)
:使用给定的charset将此String编码到byte序列,并将结果存储到新的byte数组。
- 构造方法(字节数组转字符串):
- 常用方法
- 字符流中的编码
- 常见对象
InputStreamReader(InputStream in,CharsetDecoder dec)
:创建使用给定字符集解码器的InputStreamReaderOutputStreamWriter(OutputStream out,CharsetEncoder enc)
:创建使用给定字符集编码器的OutputStreamWriter
- 字符流 = 字节流 + 编码
- 常见对象
多线程
- 概述:
- 学习多线程之前,我们先要了解几个关于多线程有关的概念。
- 进程:指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。
- 线程:是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
- 简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程。
- 什么是多线程呢?
- 即一个程序中有多个线程在同时执行。
- 线程实现:
- 实现线程一:继承Thread类:
- Thread是程序中的执行线程。Java虚拟机允许应用程序并发地运行多个执行线程。
- CPU执行程序的随机性。
- 创建线程的步骤:
- 1.定义一个类继承Thread。
- 2.重写run方法。
- 3.创建子类对象,就是创建线程对象。
- 4.调用start方法,开启线程并让线程执行,同时还会告诉jvm去调用run方法
- Thread
String getName()
:返回该线程的名称。void setName(String name)
:改变线程名称,使之与参数name相同。1
2
3
4
5
6
7
8public class MyThread extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class ThreadDemo {
public static void main(String[] args) {
//创建线程实例
MyThread mt = new MyThread();
//修改线程名字
mt.setName("张三");
//启动线程
mt.start();
//创建线程实例
MyThread mt2 = new MyThread();
mt2.setName("老王");
//启动线程
mt2.start();
}
}
- 实现线程二:实现Runnable接口:
- Runnable接口用来指定每个线程要执行的任务。包含了一个run的无参数抽象方法,需要由接口实现类重写该方法。
- 创建线程的步骤。
- 1、定义类实现Runnable接口。
- 2、覆盖接口中的run方法。
- 3、创建Thread类的对象。
- 4、将Runnable接口的子类对象作为参数传递给Thread类的构造函数。
- 5、调用Thread类的start方法开启线程。
1
2
3
4
5
6
7
8
9public class MyThread2 implements Runnable {
public void run() {
for (int i = 0; i < 100; i++) {
//链式编程
System.out.println(Thread.currentThread().getName() + ":" + i );
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class ThreadDemo2 {
public static void main(String[] args) {
//创建线程实例
Thread mt = new Thread(new MyThread2());
//修改线程名字
mt.setName("张三");
//启动线程
mt.start();
//创建线程实例
Thread mt2 = new Thread(new MyThread2());
mt2.setName("老王");
//启动线程
mt2.start();
}
}
- 实现线程一:继承Thread类:
- 多线程安全问题产生&解决方案
- 问题出现的原因:
- 要有多个线程
- 要有被多个线程所共享的数据
- 多个线程并发的访问共享的数据
- 使用同步代码块解决:
- synchronized:同步(锁),可以修饰代码块和方法,被修饰的代码块和方法一旦被某个线程访问,则直接锁住,其他的线程将无法访问
- 注意:锁对象需要被所有的线程所共享
- 同步:安全性高,效率低
- 非同步:效率高,但是安全性低
1
2
3
4//格式:
synchronized(锁对象){
//需要同步的代码
}
- 使用同步方法解决:
- 同步方法:使用关键字synchronized修饰的方法,一旦被一个线程访问,则整个方法全部锁住,其他线程则无法访问
- 注意:
- 非静态同步方法的锁对象是this
- 静态的同步方法的锁对象是当前类的字节码对象
1
2
3
4//格式:
修饰符 synchronized 返回值 方法名(){
//需要同步的代码
}
- 问题出现的原因:
面向网络编程
网络编程概述
- 网络协议:
- 通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。
- 网络通信协议有很多种,目前应用最广泛的是TCP/IP协议(Transmission Control Protocal/Internet Protoal传输控制协议/英特网互联协议),它是一个包括TCP协议和IP协议,UDP(User Datagram Protocol)协议和其它一些协议的协议组,在学习具体协议之前首先了解一下TCP/IP协议组的层次结构。
- 在进行数据传输时,要求发送的数据与收到的数据完全一样,这时,就需要在原有的数据上添加很多信息,以保证数据在传输过程中数据格式完全一致。TCP/IP协议的层次结构比较简单,共分为四层,如图所示。
- 上图中,TCP/IP协议中的四层分别是应用层、传输层、网络层和链路层,每层分别负责不同的通信功能,接下来针对这四层进行详细地讲解。
- 链路层:用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。
- 网络层:是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。
- 传输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。
- 应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等。
- IP地址和端口号:
- 要想使网络中的计算机能够进行通信,必须为每台计算机指定一个标识号,通过这个标识号来指定接受数据的计算机或者发送数据的计算机。
- 在TCP/IP协议中,这个标识号就是IP地址,它可以唯一标识一台计算机,目前,IP地址广泛使用的版本是IPv4,它是由4个字节大小的二进制数来表示,如:00001010000000000000000000000001。由于二进制形式表示的IP地址非常不便记忆和处理,因此通常会将IP地址写成十进制的形式,每个字节用一个十进制数字(0-255)表示,数字间用符号“.”分开,如 “192.168.1.100”。
- 随着计算机网络规模的不断扩大,对IP地址的需求也越来越多,IPV4这种用4个字节表示的IP地址面临枯竭,因此IPv6便应运而生了,IPv6使用16个字节表示IP地址,它所拥有的地址容量约是IPv4的8×1028倍,达到2128个(算上全零的),这样就解决了网络地址资源数量不够的问题。
- 通过IP地址可以连接到指定计算机,但如果想访问目标计算机中的某个应用程序,还需要指定端口号。在计算机中,不同的应用程序是通过端口号区分的。端口号是用两个字节(16位的二进制数)表示的,它的取值范围是0
65535,其中,01023之间的端口号用于一些知名的网络服务和应用,用户的普通应用程序需要使用1024以上的端口号,从而避免端口号被另外一个应用或服务所占用。 - 接下来通过一个图例来描述IP地址和端口号的作用,如下图所示。
- 从上图中可以清楚地看到,位于网络中一台计算机可以通过IP地址去访问另一台计算机,并通过端口号访问目标计算机中的某个应用程序。
- InetAddress:
- 了解了IP地址的作用,我们看学习下JDK中提供了一个InetAdderss类,该类用于封装一个IP地址,并提供了一系列与IP地址相关的方法,下面列出了InetAddress类的一些常用方法。
static InetAddress getByName(String host)
:在给定主机名的情况下确定主机的IP地址static InetAddress getLocalHost()
:返回本地主机String getHostName()
:获取此IP地址的主机名String getHostAddress()
:返回IP地址字符串(以文本表现形式)1
2
3
4
5
6
7
8
9public class InetAddressDemo {
public static void main(String[] args) throws UnknownHostException {
// InetAddress address = InetAddress.getByName("DESKTOP-IHI76E3");
// InetAddress address = InetAddress.getByName("172.16.132.177");
InetAddress address = InetAddress.getLocalHost();
System.out.println(address.getHostAddress());//172.16.132.177返回IP地址
System.out.println(address.getHostName());//DESKTOP-IHI76E3返回主机名
}
}UDP协议
- 了解了IP地址的作用,我们看学习下JDK中提供了一个InetAdderss类,该类用于封装一个IP地址,并提供了一系列与IP地址相关的方法,下面列出了InetAddress类的一些常用方法。
- 概述:
- UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
- 由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
- 但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。UDP的交换过程如下图所示。
- DatagramPacket类
- 前面介绍了UDP是一种面向无连接的协议,因此,在通信时发送端和接收端不用建立连接。UDP通信的过程就像是货运公司在两个码头间发送货物一样。在码头发送和接收货物时都需要使用集装箱来装载货物,UDP通信也是一样,发送和接收的数据也需要使用“集装箱”进行打包,为此JDK中提供了一个DatagramPacket类,该类的实例对象就相当于一个集装箱,用于封装UDP通信中发送或者接收的数据。
- 想要创建一个DatagramPacket对象,首先需要了解一下它的构造方法。在创建发送端和接收端的DatagramPacket对象时,使用的构造方法有所不同,接收端的构造方法只需要接收一个字节数组来存放接收到的数据,而发送端的构造方法不但要接收存放了发送数据的字节数组,还需要指定发送端IP地址和端口号。
- 接下来根据API文档的内容,对DatagramPacket的构造方法进行逐一详细地讲解。
DatagramPacket(byte[] buf,int length)
:构造DatagramPacket,用来接收长度为length的数据包。- 使用该构造方法在创建DatagramPacket对象时,指定了封装数据的字节数组和数据的大小,没有指定IP地址和端口号。很明显,这样的对象只能用于接收端,不能用于发送端。因为发送端一定要明确指出数据的目的地(ip地址和端口号),而接收端不需要明确知道数据的来源,只需要接收到数据即可。
DatagramPacket(byte[] buf,int length,InetAddress address,int port)
:构造数据报包,用来将长度为length的包发送到指定主机的指定端口号。- 使用该构造方法在创建DatagramPacket对象时,不仅指定了封装数据的字节数组和数据的大小,还指定了数据包的目标IP地址(address)和端口号(port)。该对象通常用于发送端,因为在发送数据时必须指定接收端的IP地址和端口号,就好像发送货物的集装箱上面必须标明接收人的地址一样。
- 上面我们讲解了DatagramPacket的构造方法,接下来对DatagramPacket类中的常用方法进行详细地讲解,如下所示。
InetAddress getAddress()
:返回某台机器的IP地址,此数据报将要发往该机器或者是从该机器接收到的int getPort()
:返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的byte[] getData()
:返回数据缓存区int getLength()
:返回将要发送或者接收的数据长度
- DatagramSocket类
- DatagramPacket数据包的作用就如同是“集装箱”,可以将发送端或者接收端的数据封装起来。然而运输货物只有“集装箱”是不够的,还需要有码头。在程序中需要实现通信只有DatagramPacket数据包也同样不行,为此JDK中提供的一个DatagramSocket类。DatagramSocket类的作用就类似于码头,使用这个类的实例对象就可以发送和接收DatagramPacket数据包,发送数据的过程如下图所示。
- 在创建发送端和接收端的DatagramSocket对象时,使用的构造方法也有所不同,下面对DatagramSocket类中常用的构造方法进行讲解。
DatagramSocket()
:构造数据报套接字并将其绑定到本机上任何可用的端口。- 该构造方法用于创建发送端的DatagramSocket对象,在创建DatagramSocket对象时,并没有指定端口号,此时,系统会分配一个没有被其它网络程序所使用的端口号。
DatagramSocket(int port)
:构造数据报套接字并将其绑定到本机上的指定端口。- 该构造方法既可用于创建接收端的DatagramSocket对象,又可以创建发送端的DatagramSocket对象,在创建接收端的DatagramSocket对象时,必须要指定一个端口号,这样就可以监听指定的端口。
- 上面我们讲解了DatagramSocket的构造方法,接下来对DatagramSocket类中的常用方法进行详细地讲解。
void receive(DatagramPacket p)
:从此套接字接收数据报包void send(DatagramPacket p)
:从此套接字发送数据报包
- DatagramPacket数据包的作用就如同是“集装箱”,可以将发送端或者接收端的数据封装起来。然而运输货物只有“集装箱”是不够的,还需要有码头。在程序中需要实现通信只有DatagramPacket数据包也同样不行,为此JDK中提供的一个DatagramSocket类。DatagramSocket类的作用就类似于码头,使用这个类的实例对象就可以发送和接收DatagramPacket数据包,发送数据的过程如下图所示。
- UDP实现
先运行ReceiveDemo,试其进入到阻塞模式,以等待SendDemo发送数据报包。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建接收端Socket对象
DatagramSocket ds = new DatagramSocket(8888);
//接收数据
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
System.out.println("等待接收数据");
ds.receive(dp);//阻塞
System.out.println("接收到数据了");
//解析数据
InetAddress address = dp.getAddress();//获取发送端的IP对象
byte[] data = dp.getData();//获取接收到的数据,也可以直接使用创建包对象时的数组
int length = dp.getLength();//获取具体收到数据的长度
//输出数据
System.out.println("sender ---> " + address.getHostAddress());
//System.out.println(new String(data,0,length));
System.out.println(new String(bys, 0, length));
//释放资源
ds.close();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class SendDemo {
public static void main(String[] args) throws IOException {
//创建发送端Socket对象
DatagramSocket ds = new DatagramSocket();
//创建数据
String s = "hello udp,i'm coming!";
byte[] bys = s.getBytes();
int length = bys.length;
InetAddress address = InetAddress.getLocalHost();//发送给当前设备
int port = 8888;
//打包(数据内容,数据长度,ip,端口)
DatagramPacket dp = new DatagramPacket(bys, length, address, port);
//发送数据
ds.send(dp);
//释放资源
ds.close();
}
}TCP协议
- 概述:
- TCP通信同UDP通信一样,都能实现两台计算机之间的通信,通信的两端都需要创建socket对象。
- 区别在于,UDP中只有发送端和接收端,不区分客户端与服务器端,计算机之间可以任意地发送数据。
- 而TCP通信是严格区分客户端与服务器端的,在通信时,必须先由客户端去连接服务器端才能实现通信,服务器端不可以主动连接客户端,并且服务器端程序需要事先启动,等待客户端的连接。
- 在JDK中提供了两个类用于实现TCP程序,一个是ServerSocket类,用于表示服务器端,一个是Socket类,用于表示客户端。
- 通信时,首先创建代表服务器端的ServerSocket对象,该对象相当于开启一个服务,并等待客户端的连接,然后创建代表客户端的Socket对象向服务器端发出连接请求,服务器端响应请求,两者建立连接开始通信。
- ServerSocket类
- 在开发TCP程序时,首先需要创建服务器端程序。JDK的java.net包中提供了一个ServerSocket类,该类的实例对象可以实现一个服务器段的程序。
- 通过查阅API文档可知,ServerSocket类提供了多种构造方法,接下来就对ServerSocket的构造方法进行逐一地讲解。
ServerSocket(int port)
:创建绑定到特定端口的服务器套接字。- 使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上(参数port就是端口号)。
- 接下来学习一下ServerSocket的常用方法,如下所示。
Socket accept()
:侦听并接受到此套接字的连接。InetAddress getInetAddress()
:返回此服务器套接字的本地地址。- ServerSocket对象负责监听某台计算机的某个端口号,在创建ServerSocket对象后,需要继续调用该对象的accept()方法,接收来自客户端的请求。当执行了accept()方法之后,服务器端程序会发生阻塞,直到客户端发出连接请求,accept()方法才会返回一个Scoket对象用于和客户端实现通信,程序才能继续向下执行。
- Socket类
- 讲解了ServerSocket对象可以实现服务端程序,但只实现服务器端程序还不能完成通信,此时还需要一个客户端程序与之交互,为此JDK提供了一个Socket类,用于实现TCP客户端程序。
- 通过查阅API文档可知Socket类同样提供了多种构造方法,接下来就对Socket的常用构造方法进行详细讲解。
Socket(String host,int port)
:创建一个流套接字并将其连接到指定主机上的指定端口号。- 使用该构造方法在创建Socket对象时,会根据参数去连接在指定地址和端口上运行的服务器程序,其中参数host接收的是一个字符串类型的IP地址。
Socket(InetAddress address,int port)
:创建一个流套接字并将其连接到指定IP地址的指定端口号。- 该方法在使用上与第一个构造方法类似,参数address用于接收一个InetAddress类型的对象,该对象用于封装一个IP地址。
- 在以上Socket的构造方法中,最常用的是第一个构造方法。
- 接下来学习一下Socket的常用方法,如下所示。
int getPort()
:返回一个int类型对象,该对象是Socket对象与服务器端连接的端口号InetAddress getLocalAddress()
:用于获取Socket对象绑定的本地IP地址,并将IP地址封装成InetAddress类型的对象返回void close()
:用于关闭Socket连接,结束本次通信。在关闭socket之前,应将与socket相关的所有的输入/输出流全部关闭,这是因为一个良好的程序应该在执行完毕时释放所有的资源InputStream getInputStream()
:返回一个InputStream类型的输入流对象,如果该对象是由服务器端的Socket返回,就用于读取客户端发送的数据,反之,用于读取服务器端发送的数据OutputStream getOutputStream()
:返回一个OutputStream类型的输出流对象,如果该对象是由服务器端的Socket返回,就用于向客户端发送数据,反之,用于向服务器端发送数据- 在Socket类的常用方法中,getInputStream()和getOutStream()方法分别用于获取输入流和输出流。当客户端和服务端建立连接后,数据是以IO流的形式进行交互的,从而实现通信。
- 接下来通过一张图来描述服务器端和客户端的数据传输,如下图所示。
- TCP协议实现
先运行服务端(ServerDemo),试其进入到阻塞模式,以等待客户端(ClientDemo)发送数据报包。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class ServerDemo {
public static void main(String[] args) throws Exception {
//创建接收端Socket对象
ServerSocket ss = new ServerSocket(10086);
System.out.println("等待接收数据");
//监听(阻塞)
Socket s = ss.accept();
System.out.println("接收到数据了");
//获取输入流对象
InputStream is = s.getInputStream();
//获取数据
byte[] bys = new byte[1024];
int len;//用于存储读到的字节个数
len = is.read(bys);
//输出数据
InetAddress address = s.getInetAddress();
System.out.println("client ---> " + address.getHostName());
System.out.println(new String(bys, 0, len));
//释放资源
s.close();
//ss.close();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14public class ClientDemo {
public static void main(String[] args) throws Exception {
//创建发送端Socket对象(创建连接)
Socket s = new Socket(InetAddress.getLocalHost(), 10086);
//获取输出流对象
OutputStream os = s.getOutputStream();
//发送数据
String str = "hello tcp,i'm coming!!!";
os.write(str.getBytes());
//释放资源
//os.close();
s.close();
}
} - TCP相关案例(见提高篇day12Word文档)
反射机制概述、字节码对象的获取方式、反射操作构造方法、成员方法、成员属性
反射机制的概述和字节码对象的获取方式
- 反射介绍:
- JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法
- 对于任意一个对象,都能够调用它的任意一个方法
- 这种动态获取的以及动态调用对象的方法的功能称为java语言的反射机制.
- 简单来说, 就可以把.class文件比做动物的尸体, 而反射技术就是对尸体的一种解剖.
- 通过反射技术, 我们可以拿到该字节码文件中所有的东西, 例如成员变量, 成员方法, 构造方法, 而且还包括私有。
- JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法
- 字节码对象获取的三种方式
- 1.
对象名.getCalss();
//此方法来自于Object—对象已经存在的情况下, 可以使用这种方式 - 2.
类名.class;
//类名.class这是一个静态的属性,只要知道类名, 就可以获取 - 3.
Class.forName("com.lxy.Student");
//通过Class类中的静态方法, 指定字符串, 该字符串是类的全类名(包名+类名),此处将会抛出异常都系ClassNotFoundException防止传入错误的类名
- 1.
- 反射:
- 在运行时,我们可以获取任意一个类的所有方法和属性
- 在运行时,让我们调用任意一个对象的所有方法和属性
- 反射的前提:
- 要获取类的对象(Class对象)
1
2
3
4
5
public class Student {
private String name;
private Integer age;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
// 1.通过Object的getClass()方法获取,必须要有对象
Student s = new Student();
Class clazz = s.getClass();
// 2.通过类名获取字节码对象
Class clazz2 = Student.class;
// 3. 通过全类名获取字节码对象
Class clazz3 = Class.forName("reflect.Student");
System.out.println(clazz == clazz2);
System.out.println(clazz == clazz3);
System.out.println(clazz);
}
}
- 要获取类的对象(Class对象)
- 字节码对象是用来描述什么的?
- 通过获取的构造创建对象
- 步骤:
- 1.获得Class对象
- 2.获得构造
- 3.通过构造对象获得实例化对象
1
2
3
4
5
6
7
public class Student {
private String name;
private Integer age;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37public class ReflectDemo2 {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("reflect.Student");
// method(clazz);
// method2(clazz);
// method3(clazz);
Object obj = clazz.newInstance();
System.out.println(obj);
}
//获取所有public修饰的构造方法
private static void method(Class clazz) {
//Constructor<?>[] getConstructors() :
Constructor[] cs = clazz.getConstructors();
for (int i = 0; i < cs.length; i++) {
System.out.println(cs[i]);
}
}
//获取无参构造
private static void method2(Class clazz) throws Exception {
//Constructor<T> getConstructor(Class<?>... parameterTypes)
Constructor c = clazz.getConstructor();//获取无参构造
System.out.println(c);
Object obj = c.newInstance();
System.out.println(obj);
}
//获取有参构造,其参数1类型为String,参数2类型为Integer
private static void method3(Class clazz) throws Exception {
//Constructor<T> getConstructor(Class<?>... parameterTypes)
Constructor c = clazz.getConstructor(String.class, Integer.class);
System.out.println(c);
Object obj = c.newInstance("lisi", 30);
System.out.println(obj);
}
}
- 步骤:
- 直接通过Class类中的newInstance()和获取getConstructor()有什么区别?
newInstance()
方法, 只能通过无参的构造方法创建对象getConstructor(Class<T>… parameterTypes)
方法, 方法接受一个可变参数, 可以根据传入的类型来匹配对应的构造方法
- 总结
- 反射public成员变量(字段)
- 通过反射运行public变量流程
- 通过反射获取该类的字节码对象:
Class clazz = Class.forName("com.lxy.Person");
- 通过反射获取该类的字节码对象:
- 创建该类对象:
Object p = clazz.newInstance();
- 创建该类对象:
- 获取该类中需要操作的字段(成员变量):
getField(String name)
–> 方法传入字段的名称.
- 注意: 此方法只能获取公共的字段
Field f = clazz.getField("age");
- 获取该类中需要操作的字段(成员变量):
- 通过字段对象中的方法修改属性值:
void set(Object obj, Object value)
- 参数1: 要修改的对象。
- 参数2: 将字段修改为什么值。
f.set(p, 23);
1
2
3
4
5
6
public class Student {
private String name;
public Integer age;
public String address;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39public class ReflectDemo3 {
public static void main(String[] args) throws Exception {
// method();
//获取学生类的字节码对象
Class clazz = Class.forName("reflect.Student");
method2(clazz);
}
private static void method() throws Exception {
//获取学生类的字节码对象
Class clazz = Class.forName("reflect.Student");
//获取学生类的对象
Object stu = clazz.newInstance();
//Field getField(String name) :根据字段名称获取公共的字段对象
Field f = clazz.getField("age");//获取成员变量对象(成员变量age需要是public的)
//void set(Object obj, Object value)
f.set(stu, 28);//通过成员变量对象,修改指定对为指定的值
//Object get(Object obj)
Object age = f.get(stu);//通过对象获取成员变量的值
System.out.println(age);
System.out.println(stu);
}
private static void method2(Class clazz) {
//Field[] getFields() :获取公共的成员变量
Field[] fs = clazz.getFields();
for (int i = 0; i < fs.length; i++) {
System.out.println(fs[i]);
}
System.out.println("---------------------------------");
//getDeclaredFields() :获取所有的成员变量(包含私有的)
Field[] fs2 = clazz.getDeclaredFields();
for (int i = 0; i < fs2.length; i++) {
System.out.println(fs2[i]);
}
}
}
- 通过字段对象中的方法修改属性值:
- 通过反射运行public变量流程
- 总结:
- 通过反射获取成员变量并使用:
Field[] getFields();
//返回该类所有(公共)的字段Field getField(String name);
//返回指定名称字段Field[] getDeclaredFields();
//暴力反射获取所有字段(包括私有)Field getDeclaredField(String name);
//暴力反射获取指定名称字段- Field:
- 通过反射获取成员变量并使用:
- 反射private成员变量(字段)
- 执行流程
- 获取学生类字节码对象
- 获取学生对象
- 通过
Field[] getDeclaredFields();
方法获取私有字段
- 通过
- 通过
void setAccessible(boolean flag)
让jvm不检查权限
- 通过
- 通过set方法设置对象为具体的值
1
2
3
4
5
6
public class Student {
private String name;
public Integer age;
public String address;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class ReflectDemo4 {
public static void main(String[] args) throws ReflectiveOperationException {
//获取学生类的字节码对象
Class clazz = Class.forName("reflect.Student");
//获取学生对象
Object stu = clazz.newInstance();
//获取私有的字段对象
Field f = clazz.getDeclaredField("name");
f.setAccessible(true);//设置反射时取消Java的访问检查,暴力访问
//设置具体值
f.set(stu, "lisi");
Object name = f.get(stu);
System.out.println(name);
}
}通过反射获取成员方法并使用
- 通过set方法设置对象为具体的值
- 执行流程
- 反射获取普通成员方法
- 执行流程:
- 获取学生类字节码对象
- 反射手段创建学生对象
- 调用getMethod方法获取Method对象, 方法形参接受方法的名字
- 调用Method方法中的invoke()将方法运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Student {
private String name;
public Integer age;
public String address;
public void method(){
System.out.println("Student.method");
}
private void privateMethod(){
System.out.println("Student.privateMethod");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47public class ReflectDemo5 {
public static void main(String[] args) throws Exception {
//获取学生类的字节码对象
Class clazz = Class.forName("reflect.Student");
//获取学生类的对象
Object stu = clazz.newInstance();
System.out.println("获取无参无返回值的方法(public)");
method(clazz,stu);
System.out.println("获取有参无返回值的方法");
method2(clazz,stu);
System.out.println("获取无参有返回值的方法");
method3(clazz,stu);
System.out.println("获取无参无返回值的方法(private)");
method4(clazz,stu);
}
private static void method(Class clazz, Object stu) throws Exception {
//获取无参无返回值的方法
Method m = clazz.getMethod("method");
m.invoke(stu);//并执行方法
}
private static void method4(Class clazz, Object stu) throws Exception {
//获取无参无返回值的方法
// 暴力反射获取方法
Method method = clazz.getDeclaredMethod("privateMethod");
// 让jvm不检查权限
method.setAccessible(true);
// 执行方法
method.invoke(stu);//并执行方法
}
private static void method2(Class clazz, Object stu) throws Exception {
//获取有参无返回值的方法
Method m = clazz.getMethod("setName", String.class);
m.invoke(stu, "lisi");//并执行方法
System.out.println(stu);
}
private static void method3(Class clazz, Object stu) throws Exception {
//获取无参有返回值的方法
Method m = clazz.getMethod("getName");
Object obj = m.invoke(stu);//并执行方法
System.out.println(obj);
}
- 调用Method方法中的invoke()将方法运行
- 执行流程:
- 方法总结:
- Class:
1
2
3
4Method getMethod(String name, Class<?>... parameterTypes);
// 此方法由字节码对象调用
// 参数1: 要反射的方法名称
// 参数2: 此方法需要接受的参数类型(注意,传入的都是字节码) - Method:
1
2
3
4Object invoke(Object obj, Object... args);
// 方法由Method对象调用
// 参数1: 要由那个对象调用方法
// 参数2: 方法需要的具体实参(实际参数)
- Class:
- 私有的成员方法怎么玩?
1
2
3
4
5
6
7
8
9
10// 获取字节码对象
Class clazz = Class.forName("com.lxy.Student");
// 创建学生对象
Object stu = clazz.newInstance();
// 暴力反射获取方法
Method method = clazz.getDeclaredMethod("method");
// 让jvm不检查权限
method.setAccessible(true);
// 执行方法
method.invoke(stu);JavaBean的概述、BeanUtils的使用
JavaBean的概述和规范
- JavaBean的概述:
- 将需要操作的多个属性封装成JavaBean, 简单来说就是用于封装数据的.
- 规范:
- BeanUtils的由来:
- Apache commons提供的一个组件,主要功能就是为了简化JavaBean封装数据的操作
- 之前我们使用的类都是来自Java编写好的源代码
- 而这个BeanUtils却是一个叫做Apache的组织编写.
- 那么这个组织编写的代码当中, 有一个系列可以很方便的提高我们今后的开发效率.
- 这个系列为Commons, BeanUtils就是其中之一
- BeanUtils的常用方法
static void setProperty(Object bean, String name, Object value)
- 用来给对象中的属性赋值(了解)
- 参数1: 需要设置属性的对象
- 参数2: 需要修改的属性名称
- 参数3: 需要修改的具体元素
static String getProperty(Object bean, String name)
- 用来获取对象中的属性(了解)
- 参数1: 要获取的javaBean对象
- 参数2: 对象中的哪个属性
- 注意:BeanUtils的setProperty和getProperty方法底层并不是直接操作成员变量,而是操作和成员变量名有关的get和set方法
static void populate(Object bean, Map properties)
- 用来给对象中的属性赋值(掌握)
- 参数1: 要设置属性的对象
- 参数2: 将属性以Map集合的形式传入
- Key : 属性的名称
- Value: 属性具体的值
1
2
3
4
5
6
public class Student {
private String name;
private Integer age;
private String gender;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36public class BeanUtilsDemo {
public static void main(String[] args) throws Exception {
//通过static void setProperty(Object bean, String name, Object value)
//和static String getProperty(Object bean, String name)
//进行设置属性及获取属性
method();
//通过static void populate(Object bean, Map properties)
method2();
}
private static void method() throws Exception {
Student stu = new Student();
//static void setProperty(Object bean, String name, Object value) :给JavaBean对象的成员变量进行赋值
BeanUtils.setProperty(stu, "name", "zhangsan");
BeanUtils.setProperty(stu, "age", 18);
System.out.println(stu);
//static String getProperty(Object bean, String name)
String name = BeanUtils.getProperty(stu, "name");
System.out.println(name);
}
private static void method2() throws Exception {
Student stu = new Student();
//static void populate(Object bean, Map properties)
Map<String, Object> map = new HashMap<>();
map.put("name", "lisi");
map.put("age", 18);
map.put("gender", "male");
BeanUtils.populate(stu, map);
System.out.println(stu);
}
}
- 方法总结:
- 三个方法底层是通过反射实现, 而且反射操作的是setXxx方法和getXxx方法.
- 所以编写JavaBean的时候一定要注意格式
xml的概述与如何编写xml文件
xml语言的概述
- xml语言是具有结构性的标记语言,可以灵活的存储一对多的数据关系.
- xml的使用场景:用来存储一对多的数据
xml是怎样存储数据的?
- 以标签的形式存储。(例:
Jack )
- 以标签的形式存储。(例:
Xml文件的组成部分:
- 文档声明
- 元素
- 元素的属性
- 注释
- CDATA区:了解
- 特殊字符:了解
- 处理指令(PI:Processing Instruction):了解
xml的文档声明
- 什么是文档声明?
- 在编写XML文档时,需要先使用文档声明来声明XML文档。且必须出现在文档的第一行,这就好比我们在写java文件的时候需要声明class一样, 就是个硬性的规定.
- 如何编写文档声明?
1
2
3
4
5`
//xml表示标签的名字
//version表示当前文件的版本号
//encoding表示当前编码, 需要跟文件的编码产生对应关系
//ps: standalone表示标记此文档是否独立
- 什么是文档声明?
xml的元素
- 什么是元素?
- xml中的元素其实就是一个个的标签
- 标签分为两种:
- 包含标签体
- 尖括号全部成对儿出现, 所有的数据都用一对儿尖括号存储。
例如:1
2
3
4<student>
<name>zhangsan</name>
<age>18</age>
</student>
- 尖括号全部成对儿出现, 所有的数据都用一对儿尖括号存储。
- 不包含标签体
- 只有最外层的一个尖括号,括号用/标识结束, 内部的数据都用属性来编写.
例如:1
2
3
4<student
name="zhangsan"
age="18"
/>
- 只有最外层的一个尖括号,括号用/标识结束, 内部的数据都用属性来编写.
- 两种方式都需要掌握, 但是第二种编写起来会更加方便.
- 包含标签体
- 标签(元素的书写规范)
- 严格区分大小写;
- 只能以字母或下划线开头;abc _abc
- 不能以xml(或XML、Xml等)开头—-W3C保留日后使用;
- 名称字符之间不能有空格或制表符;
- 名称字符之间不能使用冒号 : (有特殊用途)
- 元素中属性的注意事项
- 一个元素可以有多个属性,每个属性都有它自己的名称和取值。
- 属性值一定要用引号(单引号或双引号)引起来。
- 属性名称的命名规范与元素的命名规范相同
- 元素中的属性是不允许重复的
- 在XML技术中,标签属性所代表的信息也可以被改成用子元素的形式来描述
例如:1
2
3
4
5
6
7
8
9
<students>
<student name="zhangsan" age="18" />
<student>
<name>zhangsan</name>
<age>18</age>
</student>
</students>
- 什么是元素?
xml的注释
- 格式编写:
<!--被注释的内容 -->
- 注意事项:注释不能嵌套定义
- 格式编写:
xml的其他组成部分
引入CDATA区
- 为什么要使用CDATA区域?
- 如果我们在标签中写入的内容, 想要带有标签的标记符号的话, 就需要对这段内容进行转义,就好比java中的打印语句, 想要打印出”这个字符就必须用\进行转义.标签也是一样, 想要将
当做内容存储的话, 就需要对他进行转义.
- 如果我们在标签中写入的内容, 想要带有标签的标记符号的话, 就需要对这段内容进行转义,就好比java中的打印语句, 想要打印出”这个字符就必须用\进行转义.标签也是一样, 想要将
- 为什么要使用CDATA区域?
如何转义?
特殊符号 替代符号 & & < < > > “ " ‘ ' 注意:这种转义可以达到效果,但是如果操作的数据过多,编写起来会非常痛苦,所以,可以使用CDATA区来解决此问题
实例代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!--
如果有一个包含标签体的标签,
他的标签体是一个普通文本,不是子标签,
而普通文本中包含了一个标签,那这样可以吗?
-->
<students>
<student>
<name>zhangsan</name>
<url>
<![CDATA[
<lxy>www.lxy.com</lxy>
<xy>www.xy.cn</xy>
]]>
</url>
</student>
<student>
<name>zhangsan</name>
<url>
<lxy>www.lxy.com</lxy>
</url>
</student>
</students>
DTD
- 为什么要有约束 (DTD)?
- XML都是用户自定义的标签,若出现小小的错误,软件程序将不能正确地获取文件中的内容而报错。(如:Tomcat)
- XML技术中,可以编写一个文档来约束一个XML的书写规范,这个文档称之为约束.
- 如何使用DTD约束文件?
- 1.编写DTD文件
1
2
3
4
5
6
- 2.在xml文件中引入DTD文件:
- 引入了写好的DTD文件后, 格式就必须跟DTD文件保持一致
1
- 引入了写好的DTD文件后, 格式就必须跟DTD文件保持一致
- 1.编写DTD文件
- 为什么要有约束 (DTD)?
DTD的细节
- 语法细节
- 在DTD文档中使用ELEMENT关键字来声明一个XML元素。
- 语法:
<!ELEMENT 元素名称 使用规则>
- 使用规则:
- (#PCDATA):指示元素的主体内容只能是普通的文本.(Parsed Character Data)
- EMPTY:用于指示元素的主体为空。比如
- ANY:用于指示元素的主体内容为任意类型。
- (子元素):指示元素中包含的子元素
- 定义子元素及描述它们的关系:
- 如果子元素用逗号分开,说明必须按照声明顺序去编写XML文档。
- 如:
<!ELEMENT FILE (TITLE,AUTHOR,EMAIL)
- 如:
- 如果子元素用”|”分开,说明任选其一。
- 如:
<!ELEMENT FILE (TITLE|AUTHOR|EMAIL)
- 如:
- 用+、*、?来表示元素出现的次数
- 如果元素后面没有+*?:表示必须且只能出现一次
- +:表示至少出现一次,一次或多次
- *:表示可有可无,零次、一次或多次
- ?:表示可以有也可以无,有的话只能有一次。零次或一次
- 如果子元素用逗号分开,说明必须按照声明顺序去编写XML文档。
- 语法:
- 在DTD文档中使用ELEMENT关键字来声明一个XML元素。
- 定义属性
- 在DTD文档中使用ATTLIST关键字来为一个元素声明属性。
- 语法:
1
2
3
4
5 - 属性值类型:
- CDATA:表示属性的取值为普通的文本字符串
- ENUMERATED (DTD没有此关键字):表示枚举,只能从枚举列表中任选其一,如(鸡肉|牛肉|猪肉|鱼肉)
- ID:表示属性的取值不能重复
- 设置说明
- #REQUIRED:表示该属性必须出现
- #IMPLIED:表示该属性可有可无
- #FIXED:表示属性的取值为一个固定值。语法:#FIXED “固定值”
- 直接值:表示属性的取值为该默认值
- 实例代码1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<商品 类别="服装"颜色="黄色" />
```
- 实例代码2:
```xml
<购物篮>
<肉 品种="鱼肉"/>
<肉 品种="牛肉"/>
<肉/>
</购物篮>
- 语法细节
Schema
- 概述:
- Schema约束自身就是一个XML文件,但它的扩展名通常为.xsd
- 一个XML Schema文档通常称之为模式文档(约束文档),遵循这个文档书写的xml文件称之为实例文档。
- XML Schema对名称空间支持得非常好
- 理解:
- 名称空间: 相当于package
- 约束文档: 编写好的Person类
- 实例文档: 通过Person类创建对象
- Schema入门案例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40约束文档:
<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'
//标准的名称空间
targetNamespace='http://www.itheima.com'
//将该schema文档绑定到http://www.itheima.com名称空间
>
<xs:element name='书架' >
<xs:complexType>
<xs:sequence maxOccurs='unbounded' >
<xs:element name='书' >
<xs:complexType>
<xs:sequence>
<xs:element name='书名' type='xs:string' />
<xs:element name='作者' type='xs:string' />
<xs:element name='售价' type='xs:string' />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
实例文档:
<itheima:书架 xmlns:itheima="http://www.itheima.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.itheima.com book.xsd">
<itheima:书>
<itheima:书名>JavaScript网页开发</itheima:书名>
<itheima:作者>张孝祥</itheima:作者>
<itheima:售价>28.00元</itheima:售价>
</itheima:书>
</itheima:书架>
名称空间:
<itheima:书架 xmlns:itheima="http://www.itheima.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.itheima.com book.xsd">
- 概述:
XML解析的两种方式:
- DOM方式:Document Object Model,文档对象模型。这种方式是W3C推荐的处理XML的一种方式。
- SAX方式:Simple API for XML。这种方式不是官方标准,属于开源社区XML-DEV,几乎所有的XML解析器都支持它。
XML解析开发包
- JAXP:是SUN公司推出的解析标准实现。
- Dom4J:是开源组织推出的解析开发包。(牛,大家都在用,包括SUN公司的一些技术的实现都在用。)
- Dom for java four
- Log4j
总结:
- DOM: 将整棵树一口气全部加载到内存当中, 我们可以非常方便的操作任意的标签和属性。但是, 如果整棵树特别大的时候, 会出现内存溢出的问题。
- 节点: 标签、属性、文本、甚至是换行都称之为节点
- SAX: 一个节点一个节点的进行解析(暂不掌握)
Dom4J的方法
- Dom4J的常用方法:
- Document:
Element getRootElement()
:获取根元素对象(根标签)
- Element:
List elements()
:获取所有的子元素List elements(String name)
:根据指定的元素名称来获取相应的所有的子元素Element element(String name)
:根据指定的元素名称来获取子元素对象,如果元素名称重复,则获取第一个元素String elementText(String name)
:根据指定的子元素名称,来获取子元素中的文本String getText()
:获取当前元素对象的文本void setText(String text)
:设置当前元素对象的文本String attributeValue(String name)
:根据指定的属性名称获取其对应的值public Element addAttribute(String name,String value)
:根据指定的属性名称和值进行添加或者修改BeanUtils的常用方法
- Document:
- Dom4J的案例—见提高篇Day14
- Dom4J的常用方法:
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 LXY's blog!