JAVA教程
JAVA教程
NuyoahJAVA笔记
注释需要注意的地方:
/** */这是文档注释,会生成一个html文件来对这个文档进行说明
多行注释中,注释不能嵌套
编译需要注意的
一个java文件中可以生命多个class但是最多只能有一个class被public修饰而且要求声明为public的类的类名必须与源文件相同
程序入口是main()方法,格式是固定的
输出语句
System.out.println(): 输出完之后有换行
System.out.print(): 输出完之后不换行
编译之后会生成一个或多个字节码文件,字节码文件的文件名与java文件中类名相同
JDK = JRE + Java的开发工具(javac.exe.java.exe. javadoc.exe)
JRD = JVM + JAVA 核心类库
配置path环境变量目的是为了在cmd运行框中,任何盘之下都可以进行java语法的调用
命名规范
包名:多单词组成时所有字母都小写:xxxyyyzzz
类名,接口名:多单词组成时所有单词的首字母大写:XxxYyyZzz
变量名,方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写
常量名:所有字母都大写,多单词时每个单词用下划线链接
变量分类
基本数据类型:int char bool short long byte float double
引用数据类型:class(类) interface(接口) array(数组)
byte (-128-127); (一个字节) short(2字节)int(4个字节)long(8个字节)
byte b1 = 128;这样会导致编译不通过
声明long型变量的时候必须以“l”或“L”结尾
long a = 134564L
声明float型变量的时候必须以“f”或“F”结尾
使用小数的时候我们通常使用double类型的变量,因为double空间更大,而且不用再最后写一个f或者更F
基本数据类型之间的运算规则:
前提:不包含boolean
\1. 自动类型提升:
当容量小的数据类型的变量与容量打的数据类型的变量做运算的时候
byte,char,short,int,long,float,doouble
short和char类型相加的话为int
2.当使用long命名变量的时候如果后面不加L的话就相当于使用int型赋值给long型,当让如果你后面赋值的数字超过int类型的大小的话,那么会报错
Long l1 = 241654654654564;这样就会报错
如果定义float类型的变量的时候后面不加F的话而且后面数字还为小数,那么会报错,因为小数默认为double型,double型赋值给int型明显是错误的
String类型
是一个字符串使用双引号
可以和八种数据类型变量做运算,且运算只能是链接运算 +
运算完结果是String类型的
equals方法(用来比较两个字符串所包含的内容是否相同)
这种方法s1.equals(s2) 相同为True 不同为false
进制转换
二级制0b开头
八进制以 0 开头
十六进制以 0x开头
源码 反码 补码
正数的三个码都和其本身相同,
负数:
源码:该负数的本身
反码:处符号位,剩下的都取反
补码:反码加一
计算机的底层都已补码的方式来存储数据
逻辑运算符:(操作对象都是boolean类型的)
逻辑是从左到右全部判断完之后结束
短路是能判断结果之后就停止
& 逻辑与 && 短路与
| 逻辑或 || 短路或
!逻辑非
^ 逻辑异或 // a^b 当a和b相同的时候为flase 当ab不同的时候为True
位运算符 需要为int型
<< 左移运算符(单目运算符:左移一位 相当于整体乘2, 被移除的高位丢弃,空缺位补0)
>> 右移运算符(单目运算符:右移一位 相当于整体除以2(最小为0),右移之后,最高位为1,空缺补1,最高位为0,空缺位补零)
>>>被移位二进制最高位无论是0或者1,空缺位都用0补
& 使用二进制一对一,都为1的时候生成数对应的位置才为1,其余为零
| 使用二进制一对一,都为0的时候生成数对应位置才为0,其余为1
^ 使用二进制一对一,对应位置相同的时候才为0,否则为1
## **三元运算符**
(条件表达式)?表达式1:表达式2; 其中表达式1和表达式2数据类型必须相同
如果条件表达式为真的话,运行表达式1,为假的话运行表达式2
从键盘获取数据
这里涉及到导包操作(import)使用Scanner类
步骤
-
导包:import java.util.Scanner;
-
Scanner的实例化,Scanner = new Scanner(System.in);
-
调用Scanner类的相关方法,来获取指定类型的变量
Scanner scan = new Scanner(System.in);
Int num = scan.nextInt()
-
除了获取字符串的方法是scan.next(),剩下的全部都是scan,nextXxx()来获取数据
Scanner类型中没有获取单个字符的方法,如果想要获取单个字符串的话可以先使用scan.next()获取完字符串之后,在使用charAt(0)来获取单个字符
String gender = scan.next();
Char genderChar = gender.charAt(0); charAt()方法的参数是字符串的第几个位置的索引
如果用户输入的数据类型和源文件中要求输入的数据类型不同的话,那就要做比较,如果用户输入的数据类型所占字节数比源文件中要求的数据类型多,则会报错
Math类中的方法
\1. double data = Math.pow(2,3) ==== 将2的3次方运算完之后的数字赋值给data
\2. double data = Math.sqrt(5)= Math.pow(5,0.5); 将5开方之后的值赋值给data
\3. double data = Math.random()*90+10;
random()的范围是[0.0, 1.0)左开右闭,取该范围里面的随机数,返回值是double,所以如果你想要得到整数的话,你可以使用类型强制转换成int类型.
random()*90; 获取的是[0, 90)之间的随机数
random()*90+10;获取的是[10, 100)之间的随机数
try catch 用法
try{逻辑代码块一}catch{处理代码块一}
如果逻辑代码块一出现问题的时候,则执行catch中处理代码块一,如果没有出现问题的话,则执行完逻辑代码块一之后,跳过catch中的代码块,执行下一条语句
数组的使用
-
一维数组的声明和初始化
-
静态初始化
- Int[] ids = new int[]{1001,1002,1003}
-
动态初始化
String[] names = new String[4];
数组一旦初始化完成其长度就已经确定了
-
-
数组元素默认初始化值
-
整形的统统为0
-
浮点型的统统为0.0
-
Char类型的统统为Ascii码中的0 (NULL)
-
Boolean类型的统统为false
-
二维数组
-
Int[][] arr = new int[5][4]
二维数组在动态初始化的时候列数必须填,行数不一定要填写
-
二维数组求长度的时候 用上面的例子 arr.length = 5; 只要是数组就有length方法,arr[0].length = 4;
-
二维数组初始值对于 int[ ] [ ] arr = new int[3] [4] 这样都分配好空间的数组来说 arr[1]代表是地址.arr[1] [2]的初始值和一维数组类型初始值一样,对于 int[ ] [ ] arr = new int[3] [] 只给出列,没有给出行的数组来说,arr[1]代表的是null, 内层元素不能调用否则报错
-
-
数组的方法(引用java.util.Arrays)
1 boolean equals(int[] a, int[] b) 判断两个数组是否相同 2 String toString(int[] ) 输出数组信息 3 void fill(int[] a, int val) 将指定值填充到数组之中 4 void sort(int[] a) 对数组进行排序 5 int binarySearch(int[] a, int key ) 对排序后的数组进行二分查找指定的值key -
数组异常
- 数组角标越界异常:ArrayIndexOutOfBoundsException
- 空指针异常:NullPointerException 声明了但是没有复制 int[] a; System.out.print(a[0]) 这样就会报错
面向对象
面向过程:强调的是功能行为,以函数为最小单位,考虑怎么做,
面向对象:将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做
Java类及类的成员(属性,方法,构造器;代码块,内部类)
两个要素:
类:是对一类事物的描述,是抽象的,概念上的定义
对象:是实际存在的该类事物的每个个体,因而也称为实例。
面向对象的重点就是类的设计,设计类就是设计类的成员,
类:含有 **属性**和**方法**
属性 = 成员变量 = field = 域,字段
方法 = 成员方法 = 函数 = method
创建类的对象 = 类的实例化 = 实例化类
1 | // 创建类 |
-
类和对象的使用(面向对象思想落地)
- 创建类,设计类的成员
- 创建类的对象
- 通过“对象.属性” 或“对象.方法”调用对象的结构
-
属性(成员变量) VS 局部变量
-
相同点:
- 定义变量的格式 :数据类型 变量名 = 变量值
- 先声明,后使用
- 变量都有其对应的作用域
-
不同点
-
在类中声明的位置不同
属性:直接定义在类的一对{}内
局部变量:声明在方法内,方法形参,代码块之内,构造器形参,构造器内部的变量
-
权限修饰符的不同
属性:可以在声明属性的时候,指明其权限,使用权限修饰符
常用的权限修饰符有:private,public,缺省(不加声明就是默认缺省),protected -->封装性
局部变量:不可以使用权限修饰符
-
默认初始化值的情况
属性:类的属性,根据其类型,都有默认初始化值
整形(byte, short, int , long); 0 浮点型(float,double); 0.0 字符型(char); 0(或‘\u000’) 布尔型(boolean); false;
局部变量:没有初始化值
意味着:我们再调用局部变量之前,一定要先是赋值 特别地:在形参调用时,我们赋值即可
-
在加载位置不同
属性:加载到堆空间中(非static)
局部变量:加载到栈空间中
1
2
3
4
5
6
7
8class User{
String name; // 成员变量
public void talk(String language) // 这里language就是局部变量
{
String age; // 这里age也是局部变量
System.out.println("我是中国人,我说"+language);
}
} -
-
方法的使用(method)
-
举例
1
2
3
4public void eat(){};
public void food(){};
public String Getname(){};
public String GetNation(String nation){}; -
方法的声明
权限修饰符 返回值类型 方法名(形参列表){
方法体;
}
-
说明:
-
关于权限修饰符,默认方法的权限是先使用public
Java规定的4种权限修饰符:private, public, 缺省, protected — >封装性
-
返回值类型: 有返回值 VS 没有返回值
如果方法有返回值,则必须在方法声明时,指定返回的类型,同时,方法中(必须)需要使用return关键字来返回指定类型的常量或变量
如果方法没有返回值,则方法声明时,使用void来表示。通常,没有返回值的方法中,就不使用return,但是如果使用的话只能用”return;“表示结束此方法的意思
-
方法名:需要符合方法名的规则和规范
-
形参列表:可以有一个,两个,或多个
数据类型1 形参1 ; 数据类型2 形参2;
-
-
return 关键字的使用:
- 适用范围:使用在方法体中
- 作用:
- 结束方法
- 针对有返回值类型的方法,使用return返回数据
- return 关键字后面不可以使用语句
-
方法的使用中,可以调用当前类的属性或方法
-
编译完源程序之后,生成一个或多个字节码文件
编译器使用JVM中的类的加载器和解释器对生成的字节码文件进行解释运行,意味着,需要将字节对应的类加载到内存中,涉及到内存解析
所以说,编译的时候不会讲字节加载到内存中,只有在运行的时候才会设计到内存
-
理解“万事万物皆对象”
- 在Java语言范畴中,我们都将功能,结构等封装到类中,通过类的实例化,来调用功能结构
- Scanner,String等
- 文件 File
- 网络资源
- 涉及到Java语言与前端Html,后端数据库交互时,前后端的结构在Java层面都体现为类,对象
- 在Java语言范畴中,我们都将功能,结构等封装到类中,通过类的实例化,来调用功能结构
-
-
-
内存解析的说明:
- 引用类型的变量,只可能存储两类值,null,或地址值(含变量的类型)
-
匿名对象的使用
- 理解:我们创建对象,没有显示的赋给一个变量名,即为匿名对象
- 特征:匿名对象只能调用一次
- 使用:
1
2
3
4
5
6
7
8
9
10class Person()
{
String name;
int age;
}
public class Java(){
public static void main(String[] args){
new Person().name; // 这里就是匿名对象, 调用完之后就无法再调用这个对象了
}
} -
方法:
-
方法的重载(overload)
-
定义:在同一个类中,允许存在一个以上的同名的方法,只要他们的参数个数或者参数类型不同即可。
两同一不同:相同类,相同方法名,参数列表不同/参数个数不同/参数类型不同
-
举例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class Text(){
public static void mian(String[] args){
Person p1 = new Person();
p1.say("zhang");
p1.say("zhanng", n);
}
}
class Person()
{
public void say(String name)
{
System.out.println("我的名字是:"+name);
}
public void say(String name, int n)
{
for(int i = 0; i < n; i++)
{
System.out.println("我的名字是:"+name);
}
}
}这样的话,当你传入的参数不同的时候,调用的say方法也不同
-
-
可变个数的形参
-
定义:可变形参格式:数据类型 … 变量名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class Text{
public static void main(){
Person p1 = new Person();
p1.say("Mr","zhang");
}
}
class Person{
/**
*运行结果为
* [Ljava.lang.String;@4eec7777
* zhang
*当使用可变个数形参的时候,传入多个参数的的时候形参就会变成对应的数组
*/
public void say(String ... strs){
System.out.println(strs);
System.out.println(strs[1]);
}
} -
具体使用
- 可变个数形参的格式:数据类型 … 变量名
- 当调用可变个数形参方法时,传入的参数个数可以是 0个, 1个, 2个…
- 可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载
- 可变个数的形参变量必须声明在方法的最后一个
- 可变个数的形参在同一个方法中最多声明一个
-
-
关于变量的赋值:
- 如果变量是基本数据类型,此时赋值的时变量所保存的数据值
- 如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值
-
关于程序的退出:
- 使用System.exit(0); 会直接结束程序
-
方法的重写
-
定义:子类可以根据需要对父类中继承来的方法进行改造,也称为方法的重置或者覆盖,在执行程序的时候,子类的方法将覆盖父类的方法。
-
要求:
- 子类重写的方法必须和父类被重写的方法具有相同的方法名称,参数列表
- 子列重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
- 父类中返回值类型时void的时候,则子类重写方法的返回值类型只能是void
- 父类被重写的方法的返回值类型时A类型,则子类重写的方法的返回值类型可以是A类或者A的子类
- 父类中被重写的方法的返回值类型是基本数据类型,则子类重写方法的返回值类型必须是相同的基本数据类型
- 子类重写的方法使用权限不能小于父类中被重写的方法的访问权限
- 子列不能重写父类中声明为private权限的方法
1
// 方法的声明:权限修饰符 返回值类型 方法名(形参列表) throws 异常类型()
- 子类方法抛出的异常,不能大于父类中被重写方法的异常
- protected 权限修饰符 在子类中可以用,但是在不同包下不可以用,如果想要在不同包下调用protected权限的变量,我们可以使用extends来继承相应的类
-
注意:
- 子类与父类中同名同参数的方法必须同时声明为static(即为重写),或者,同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法
-
应用:重写之后,当创建子类对象以后,通过子类对象调用父类中同名的方法时,实际执行的时子类重写父类的方法
重载和重写的区别
-
概念:
重载:在同一个类中可以声明多个方法名相同形参列表不同的方法
重写:子类继承父类之后,可以对父类中同名同参数的方法进行覆盖操作
-
编译和运行
对于重载而言,在方法调用的之前,编译器已经确定了索要调用的方法,这称为“早绑定”,或“静态绑定”
对于多态,只有等到方法调用的时候,编译器才会确定索要调用的具体方法,这成为“完绑定”或“动态绑定”
-
子类对象实例化的过程
-
从结果上看:
子类继承父类之后,就获取了父类中声明的属性或方法
创建子类对象,在对空间中,就会加载所有父类中声明的属性
-
从过程上来看,当我们通过子类的构造器创建子类对象上来看,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,直到调用了。java.lang.Object类中空参构造器为止,正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用
-
明确,虽然创建子类对象时,调用了父类的构造器,但自始至终就创建过一个对象,即为new的子类对象
代码块
-
代码块的作用,用来初始化类和对象
-
代码块如果有修饰的话,只能使用static
-
分类:静态代码块 VS非静态代码块
-
静态代码块:
- 内部可以有输出语句
- 随着类的加载而运行,而且只执行一次
- 作用初始化类的信息
- 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
- 静态代码块的执行要优先于非静态代码块
- 静态代码块内只能调用静态的属性,静态的方法,不能调用非静态的结构
-
非静态代码块:
- 内部可以有输出语句
- 随着对象的创建而运行,创建几个对象执行几次
- 作用初始化类的信息
- 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
- 非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
-
使用方法:
-
我们想要给一个属性的赋值的之前需要做一下其他的工作,这时候不能够进行如下方法赋值:
1
2
3
4
5
6
7
8
9
10
11class Person{
int a;
// 这样的赋值是错误的
// a = 0
// 我们想要在这个赋值之前需要做一下别的工作,这时候我们需要代码块
static{
// 作别的工作
...;
a = 0;
}
}
-
-
执行的先后顺序:
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// 由父即子 静态线性:
class Root{
static{
System.out.println("Root静态初始化模块");
}
{
System.out.println("Root普通初始化模块");
}
public Root(){
System.out.println("Root无参构造器");
}
}
class Mid extends Root{
static{
System.out.println("Mid静态初始化模块");
}
{
System.out.println("Mid普通初始化模块");
}
public Mid(){
System.out.println("Mid无参构造器");
}
}
class Left extends Mid{
static{
System.out.println("Left静态初始化模块");
}
{
System.out.println("Left普通初始化模块");
}
public Left(){
System.out.println("Left无参构造器");
}
}
public class SingletonTest1 {
public static void main(String[] args) {
Left l = new Left();
}
}最后结果为:
Root静态初始化模块
Mid静态初始化模块
Left静态初始化模块
Root普通初始化模块
Root无参构造器
Mid普通初始化模块
Mid无参构造器
Left普通初始化模块
Left无参构造器如果我们在一个子类的对象中调用static模块的话
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// 由父即子 静态线性:
class Root{
static{
System.out.println("Root静态初始化模块");
}
{
System.out.println("Root普通初始化模块");
}
public Root(){
System.out.println("Root无参构造器");
}
}
class Mid extends Root{
static{
System.out.println("Mid静态初始化模块");
}
{
System.out.println("Mid普通初始化模块");
}
public Mid(){
System.out.println("Mid无参构造器");
}
}
class Left extends Mid{
static{
System.out.println("Left静态初始化模块");
}
{
System.out.println("Left普通初始化模块");
}
public Left(){
System.out.println("Left无参构造器");
}
public static void main(String[] args) {
System.out.println("这是初始化函数的部分");
Left l = new Left();
}
}最后结果为:
Root静态初始化模块
Mid静态初始化模块
Left静态初始化模块
这是初始化函数的部分
Root普通初始化模块
Root无参构造器
Mid普通初始化模块
Mid无参构造器
Left普通初始化模块
Left无参构造器
内部类:
使用方法:inner class一般在定义的他的 类或语句块之间,在外部引用他的时候必须给出完整名称。inner class类名不能和外部类相同
-
在JAVA中我们允许,一个类定义于另一个类的内部,前者成为内部类 ,后者称为外部类 。
-
分类:
- 成员内部类(static成员内部类,和非static成员内部类)
- 局部内部类(不谈修饰符),匿名内部类
-
成员内部类
- 一方面,可以作为外部类的成员:
- 调用外部类的结构
- 可以被static修饰
- 可以被四种不同的权限修饰符
- 另一方面作为一个类
- 类可以定义属性,方法,构造器等
- 可以被final修饰,表示此类不能够别继承。言外之意,不适用final,就可以被继承
- 可以使用abstract修饰
- 一方面,可以作为外部类的成员:
1 | class Person{ |
-
关注问题
-
如何实现内部类的对象
-
如何在成员内部类中区分调用外部类的结构
1
2
3
4
5public void display(String name){
System.out.println(name); // 方法的形参
System.out.println(this.name); // 内部类的属性
System.out.println(Person.this.name); // 外部类的属性
} -
开发中局部内部类的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14public class InnerClassTest {
public static void main(String[] args){
// 创建Dog实例(静态成员内部类)
Person.Dog dog = new Person.Dog();
dog.show();
// 创建Bird实例(非静态的成员内部类):
// Person.Bird bird = new Person.Bird(); //这样是错误的,因为Bird内部类不是static不能通过Person直接调用,需要实例化Person的对象,然后通过实例化的Person的对象来调用这个内部类
Person p = new Person();
Person.Brid bird = p.new Bird;
bird.sing();
}
} -
-
总结:
成员内部类和局部内部类,在编译之后都会出现字节码文件
格式:
成员内部类:外部类$内部类名.class
局部内部类:外部类$数字 内部类名.class
-
注意:
- 在局部内部类的方法中(比如说show)如果调用局部内部类所声明的方法(比如method)中的局部变量(比如说num)要求此局部变量声明为final
- jdk7 及以前的版本,要求此局部变量声明为final的
- jdk8 及以后的版本,可以省略final的声明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class Person{
public void method(){
// 局部变量
int num = 10;
class AA{
public void show(){
// 这样是错误的
// num = 20
// 这样是正确的
System.out.println(num);
}
}
}
}
Object类的使用
-
object类是所有Java类的根父类
-
如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
-
Object类中的功能(属性,方法)就具有通用性
- 属性:无
- 方法:euqals() / toString() / getClass() / hashCode() / clone() / finalize() / wait() / notify() / notifyAll()
-
equals() 方法的使用:
-
回顾: == 运算符:
-
可以使用在基本数据类型变量和引用数据类型变量中
-
如果可以比较的是基本数据类型变量,比较两个变量保存是数据是否相等。(不一定要类型相同)
如果比较的是引用数据类型变量,则比较的是两个数据地址值是否相等,即两个引用是否指向一个对象实体
-
-
equals()方法的使用:
-
是一个方法,而并非一个运算符
-
只能适用于引用数据类型
-
Object类中euqals()的定义:
1
2
3public boolean euqals(Object obj):{
return (this == obj)
}这就说明Object类中定义的equals()方法和 == 是相同的。比较两个对象的地址值是否相等,即两个引用是否指向同一个数据对象
-
像String,Data,File,包装类都重写的Object中的euqals的方法,重写后,比较的不是两个引用的地址值是否相同,而是比较两个对象的实体内容是否相同
-
通常情况下,我们自定义类如果使用equals()的话,也通常是比较两个对象的实体内容是否相同,那么,我们就需要对Object类中euqals()进行重写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// Customer 类中有两个属性,1.name, 2.age
// 重写Customer中的equals()方法
// 比较两个对象的属性是否相同
public boolean equals(Object obj){
if(this == obj){
return true;
}
if(obj instanceof Customer){
Customer cus = (Customer)obj;
if((this.age == obj.age) && (this.name.equals(cust.name))){
return true;
}
else{
return false;
}
}
return false;
}
-
-
-
Object类中toString()的使用
- 当我们输出一个对象的引用的时候,默认调用的是他的的toString()方法
- Object中toString()方法
- hashCode()计算该类在堆空间的值, 使用Integer类的toHexString转换成16进制
1
2
3public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}- 像String,Data,File,包装类等都重写了Object类中toSting()方法,使得在调用对象的toString()时,返回“实体内容”信息
- 自定义类也可以重写toString()方法,当调用此方法的时候,返回对象的“实体内容”
-
Java中的Junit单元测试:
步骤:
-
选中当前工程 - 右键选择,build path - add libraries - JUit 4 - 下一步
-
创建Java类,进行单元测试
此时Java类要求,①此类时public的,②此类提供公共的无参构造器
-
此类中声明单元测试方法
此时的单元测试方法,防范权限是public,没有返回值,没有形参
-
此时单元测试方法以后,就可以在方法体内测试相关的代码
-
此单元测试方法上需要声明注解@Test,在单元测试类中导入,import org.junit.Test
-
写完代码以后,就可以对单元格进行测试了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package ObjectTest;
import org.junit.Test;
public class GeometricTest {
public static void main(String[] args){
Circle circle1 = new Circle(2.3);
Circle circle2 = new Circle(2.3,"white",2.0);
System.out.println("颜色是否相等:"+circle1.getColor().equals(circle2.getColor()));
System.out.println(circle1.equals(circle2));
System.out.println(circle1);
}
public void testEuqals() {
Circle circle1 = new Circle(2.3);
Circle circle2 = new Circle(2.3, "white", 2.0);
System.out.println(circle1.equals(circle2));
}
}
-
-
包装类的使用:
针对八种基本类型定义相应的引用类型 — 包装类(封装类)
有了类的特点,就可以调用类中的方法,Java才是真正的面向对象
Java提供了8中基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
前六个是数值型的,由共同的父类Number
基本数据类型 包装类 byte Byte short Short int Integer long Long float Float double Double boolean Boolean char Character 包装类中核心部分
-
基本数据类型,包装类,与String类之间的转换
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
32public class WrapperTest {
// 基本数据类型的转换 -----> 包装类,调用包装类的对象
public void test1(){
int num = 10;
// 下面这种方法就会报错,因为基本数据类型没有方法可言
// System.out.println(num1.toString())
// 转换成引用数据类型
Integer in1 = new Integer(num1);
System.out.println("in1.toString"); // ----> 10
// Integer in2 = new Integer("123abc") 这样就会报错
Float f1 = new Float(12.3f);
Float f2 = new Float("12.3");
boolean b1;
Boolean b2;
System.out.println(b1); // false b1是基础类型
System.out.println(b2); // null b2是一个类
}
// 包装类 -----> 基本数据类型 ,调用包装类的xxxValue()
public void test2(){
Integer in1 = new Integer(13);
// 调用包装类.xxxValue就可以将包装类转换成对应的数据类型
int i1 = in1.intValue();
System.out.println(i1+1);
}
}新特性:不强制要求上述转换方法了,有自动转换的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// JDK 5.0 新特性,自动装箱与拆箱:
public void test1(){
int num1 = 0;
// 理论上说,method的参数是对象,但是你直接填入基本数据类型也不会报错
// 基本数据类型 ----- > 包装类的对象
method(num1);
// 自动装箱: 基本数据类型 --- > 包装类
int num2 = 10;
Integer in1 = num2;
boolean b1 = true;
Boolean b2 = b1;
// 自动拆箱: 包装类 --- > 基本数据类型
int num3 = in1;
}
public void method(Object obj){
pass;
}基本数据类型,包装类 ----> String类型
1
2
3
4
5
6
7
8
9
10
11// 基本数据类型,包装类 ---- > String类型, 调用String重载的valueOf(Xxx xxx)
public void test1(){
int num1 = 10;
// 方式一:连接运算
String s1 = num1+"";
// 方式二:调用String的valueOf(Xxx xxx)方法
float f1 = 12.3f;
String str2 = String.valueOf(f1);
}String类型 ---- > 基本数据类型,包装类,调用parsexxx,可以将对应的String转换为对应的属性。
1
2
3
4
5
6
7
8
9
10
11// 调用包装类的parsexxx()
public void test1(){
String str1 = "123";
int num1 = Integer.parseint(str1);
System.out.println(num1); // ===》 123
String str2 = "true";
boolean b1 = Boolean.parseBoolean(str2);
System.out.println(b1); // ====> true
}
-
-
常用方法:
- clone() :创建并复制该对象, p2 = p1.clone()
- equals(), 比较两个对象是否相等
- finalize(), 垃圾回收,当该对象没有变量指向它的时候,就会自动调用这个方法
-
注意:
-
自动类型提升:
1
2
3Object o1 = true ? Integer(1) : Double(2.0);
// 这里面就存在着自动类型提升的一部分,因为是要给o1赋值的,所以编译器需要事先确定好赋值的类型,一个是Integer 一个是Double 所以需要自动类型提升
System.out.println(o1); // =======》 1.0 -
Integer内部的IntegerCache结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public void test3() {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j); // ====== false
// Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],保存了-128~127范围内整数,如果我们使用自动装箱的方式,给Integer赋值的范围在-182~127内时,可以直接使用数组中的元素,不用再去new了
// 提高效率
Integer m = 1;
Integer n = 1;
System.out.println(m == n); // ====== true
Integer x = 128;
Integer y = 128; // 相当于new了一个Integer对象
System.out.println(x == y); // ==== false
}
-
Vector可扩展数组
Vector在使用的时候可以给定数组长度,达到预期长度,减少扩展次数
1 | Vector v = new Vector(); |
面向对象的三大特称(封装性,继承性,多态性(抽象性))
面向对象的特征一:封装和隐藏
高内聚 低耦合
高内聚:类的内部数据操作细节自己完成,不允许外部干涉
低耦合:仅对外暴露少量的方法用于自己使用
隐藏对象内部的复杂性,之对外公开简单的接口。便于外界调用,从而提高系统的可扩展性,可维护性。通俗的说,把该隐藏的隐藏,该暴露的暴露出来,这就是封装性的设计思想
-
问题的引入:
- 当我们创建一个类的对象之后,我们可以通过“对象.属性”的方式。对对象属性进行赋值,这里赋值操作要受到属性的数据类型和存储范围的制约,但是除此之外,没有其他制约条件。但是在实际问题中,我们往往需要给属性赋值加入额外的限制条件,这个条件就不能再属性声明时体现,我们只能通过方法来进行限制条件的添加,同时我们需要避免用户在使用“对象.属性”的方式,对属性进行赋值,则需要将属性,声明为私有的private —> 此时,针对属性就体现了封装
-
封装性的体现:
- 我们将类的属性xxx私有化(private),同时提供公共的(public)方法来获取(getXxx)和设置(setXxx),该变量的值
- 拓展:封装性的体现①如上②不对外暴露私有的方法③单例模式……
-
封装性的体现,需要权限修饰符来配合
-
java规定的4种权限修饰符(从小到大)private, 缺省, protected, publi
-
修饰符 类内部 同一个包 不同包的子类 同一个工程 private √ (缺省) √ √ protected √ √ √ public √ √ √ √ -
四种权限可以用来修饰类及类的内部结构:属性,方法,构造器,内部类
-
具体的,四种权限都可以用来修饰类的内部结构,属性,方法,构造器,内部类
修饰类的话只能使用:缺省,public
-
总结:Java提供了4中权限修饰符来修饰类的内部结构,体现类及类的内部结构在被调用时的可见性大小
-
-
类的结构三:构造器(或构造方法,constructor)的使用
construct:建设,建造。construction
- 构造器的作用:创建对象,初始化对象信息
- 说明
- 如果没有显式的定义类的构造器的话,则系统默认提供一个空参的构造器
- 定义构造器的格式:权限修饰符 类名(形参列表){}
- 一个类中定义的多个构造器,彼此构成重载
- 一旦我们显示的定义类的构造器后,系统不再提供默认的空参构造器
- 一个类中至少有一个构造器
-
总结:属性赋值的先后顺序
- 默认初始化
- 显式初始化
- 构造器中赋值
- 通过“对象.方法” 或“对象.属性”赋值
- 以上操作先后顺序 1,2,3,4
继承性
- 继承性的好处
- 减少了代码的冗余。提高了代码的复用性
- 便于功能的扩展
- 为了以后多态性的使用,提供了前提
- 继承性的格式:class A extends B{}
- A:子类,派生类,subclass
- B:父类,超类,基类,superclass
- 体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。特别的,父类中声明为private的属性和方法,子类继承父类之后,仍然认为获取了父类中私有的结构。只是应为封装性的影响,使得子类不能调用父类中的结构而已
- 子类继承父类之后,还可以声明自己特有的属性和方法,实现功能的扩展
- 子类和父类的关系不同于,子集和集合的关系
- extends 扩展,延展
- Java中关于继承性的规定
- 一个类中可以背多个子类继承
- Java中类的单继承性,一个类只能有一个父类
- 子父类是相对的概念
- 子类解释继承的父类,称为直接父类,间接继承的父类称为间接父类
- 子类继承父类之后,就获取了直接父类以及所有间接父类中声明的属性和方法
- 如果我们没有显式的声明一个类的父类的话,则此继承于java.lang.Object类
- 所有的Java类除(java.lang.Object类之外)都直接或间接的继承于java.lang.Object类
- 意味着,所有的java类具有java.lang.Object类中所有声明的功能
多态性
-
理解多态性:
如果想要调用子类种的方法的话,你需要在父类中也有这个方法,在子类重写之后你调用的这个方法在父类种也可以使用
1
2// 如果Man 和 Woman 是Person的子类
Person p = new Man() // 这就是多态性 -
何为多态性
对象的多态性,父类的引用指向子类的对象(或子类的对象赋给父类引用)
-
多态性的使用:虚拟方法调用
有了对象的多态性以后,我们在编译期只能调用父类中声明的方法,但在运行期,我们执行的是子类重写父类的方法
总结:编译,看左边,运行,看右边
-
多态性使用前提
- 类的继承关系
- 方法的重写
-
对象的多态性,只适用于方法,不适用与属性
-
虚拟方法的调用
-
正常方法调用:
1
2
3
4Person e = new Person();
e.getinfo();
Student e = new Student();
e.getinfo(); -
虚拟方法调用(多态情况下)
1
2
3// 子类中定义了与父类同名同参数的方法,在多态情况下,此时父类方法称为虚拟方法,父类根据赋给它不同子类的对象,动态调用属于子类的该方法。这样方法的调用在编译期是无法确定的,是运行时行为
Person e = new Student();
e.getinfo(); // 调用Student类的getinfo()方法
-
-
多态性使用的时候不能调用子类特有的方法和属性
有了对象的多态性之后内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法,子类特有的属性和方法不能够调用
-
如何才能调用子类特有的属性和方法?
向下转型,使用强制类型转换符
Man m1 = (Man)p2;
但是在使用强制类型转换的时候,可能会出现ClassCastException的异常,这时候我们可以使用instanceof关键字来规避这个错误
其他关键字this,super, static ,final,abstract,interface
this关键字的使用:
-
this可以用来修饰:属性,方法,构造器
-
this修饰属性和方法:
this理解为当前对象
在类方法中,我们可以使用“this.属性”或者“this.方法”的方式,调用当前对象属性或方法。但是通常情况下,我们选择省略"this.",特殊情况下,如果方法的形参和类的属性同名这时候我们就需要使用this关键字,表明此变量是属性,而非形参
1
2
3
4
5
6
7
8
9class text{
int age;
String name;
public setAge(int age)
{
// 前面使用this来表示前面的age是属性,后面的age是形参
this.age = age;
}
} -
this调用构造器
- 我们在类的构造器中,可以显式的使用“this(形参列表)”方式,调用本类中其他的构造器
- 构造器中不能通过"this(形参列表)"方式调用自己
- 如果一个类中有n个构造器,则最多有n-1个构造器啊使用“this(形参列表)”
- 规定:“this(形参列表)“必须声明在当前构造器的首行
- 构造器内部,最多只能声明一个”this(形参列表)“,用来调用其他的构造器
package关键字的使用
-
为了更好的实现项目中类的管理,提供包的概念
-
使用package声明类或接口所属的包,声明在源文件首行
-
包,属于标识符,遵循标识符的命名规则。规范(xxxyyyzzz),“见名知意”
-
每“.”一次,就代表一层文件目录
-
补充,
- 同一个包下,不能命名同名的接口,类;
- 不同包下,可以命名同命的接口,类
java.lang | 包含一些java语言常用的核心类,如String,Math,Integer,System和Thread,提供常用功能 |
---|---|
java.net | 包含执行与网络相关的操作的类和接口 |
java.io | 提供多种输入输出功能的类 |
Java.util | 包含一些实用的工具类,如 定义系统特征,接口集合框架类,使用与日期日历相关的函数 |
java.text | 包含了一些java格式化相关的类 |
java.sql | 包含了java进行jdbc数据库编程的相关类/接口 |
java.awt | 包含了构成了抽象窗口工具集(abstract window toolkits)的多个类,这些类用来构建和管理应用程序的图像化界面(GUI) |
import关键字的使用
import导入:
-
在源文件中显示的使用import结构导入指定包下面的类,接口
-
声明在包的声明和类的声明之间
-
如果需要导入多个结构,则并列写出即可
-
可使用“xxx.*”的方法,表示可以导入xxx包下的所有结构
-
如果使用的类或接口是java.lang包下定义的,则可以省略import结构
-
如果使用的类或接口是本包下定义的,则可以省略import结构
-
如果在源文件中,使用了不同包下的同名的类,则必须至少有一个类需要以全类名的方式显示
1
2
3
4// 其中import 是关键字,project1是包名,"."后面的是包中的类名
import project1.First;
// 全类名的显示方式
project1.First f1 = new project1.First(); -
使用“xxx.*”方式表明可以调用xxx包下面的所有结构。但如果使用的是xxx包下面的子结构,则仍需要显示导入
-
import static:导入的指定类或接口中的静态结构(不是类):属性,或方法
super关键字的使用
super关键字:
- super理解为:父类的
- super可以用来调用:属性,方法,构造器
- super的使用:调用属性或方法
- 我们可以在子类的方法或构造器中,通过使用“super.属性”或“super.方法”的方式,显示调用父类中声明的属性或方法,但是通常情况下,我们习惯省略super
- 特殊情况:当子类和父类中定义了同名的属性的时候,我们想要在子类中调用父类声明的属性的时候,则必须显式的使用“super.属性”的方式,表明调用的是父类中声明的属性
- 特殊情况:当子类重写了父类中的方法之后,我们想要在子类的方法中调用父类的中被重写的方法的时候,则必须显式的使用“super.方式”的方法,表明调用的是父类中被重写的方法
- super调用构造器
- 我们可以在子类的构造器中显示的使用“super(形参列表)”的方式,调用父类中声明的指定的构造器
- “super(形参列表)”的使用,必须声明在子类构造器的首行!!!!
- 我们在类的构造器中,针对与“this(形参列表)”或“super(形参列表)”,只能二选一,不能同时出现
- 在构造器的首行,没有显式的声明“this(形参列表)”或“super(形参列表)”,则默认调用的是父类中空参的构造器
- 在类的多个构造器中,至少有一个类的构造器中使用了“super(形参列表)”,调用父类中的构造器
instanceof关键字的使用
-
用法: a instanceof A:判断对象a是否是类A的实例,如果是,返回True,如果不是返回False,
要求a 所属类与A必须是子类或者父类的关系,否则编译会出现错误
-
目的:为了在多态的时候使用类型强制转换的时候不出现错误:
1
2
3
4
5if(p2 instanceof Woman){ // 这样的话我们就不会出现多态中类型转换出现的错误了
Woman w1 = (Woman)p2;
p2.goShopping();
System.out.println();
}如果类B是类A的父类:
a instanceof A 返回true,则a instanceof B 返回true
练习
1
2
3
4
5
6
7
8
9
10
11
12
13// 问题一:编译时通过,运行时不通过
// 举例一: 同子类不能互相转换
Person p3 = new Woman();
Man m3 = (Man)p3;
// 举例二: 要想使用类的强制类型转换必须是多态之间的
Person p4 = new Person();
Man m4 = (Man)p4;
// 问题二:编译时通过,运行时也通过:
Object obj = new Woman();
Person p = (Person)obj;
// 问题三:编译不通过:
Man m5 = new Woman(); // 逻辑语法都不符合, 直接报错
static关键字的使用:
目的:为了减少统一变量的空间,例如所有中国人的国籍都是“中国”,这时候我们建立一个中国人的类,这个国籍变量就可以设置为static,就是所有的中国人的实例都共用这一个变量
1 | public class StaticTest { |
- static:静态的,随着类的加载而加载
- static用来修饰。属性,方法,代码块,内部类。
- 使用static修饰属性:静态变量
- 属性,按是否使用static修饰,分为 静态属性 VS 非静态属性(实例变量)
- 实例变量:我们创建了类的多个对象,每个对象都有一套属于自己的非静态属性。修改其中一个的非静态属性,不会影响其它对象里面的属性
- 静态变量:我们创建了类的多个对象,多个对象共享一套非静态属性。当通过某一个对象修改非静态属性,会导致其他对象调用此静态对象的时候,是修改过的
- static修饰属性的其他说明:
- 静态变量随着类的加载而加载,可以通过“类.静态变量”的方式进行调用
- 静态变量的加载要早于对象的创建。
- 由于类只会加载一次,则静态变量在内存中也只会存在一份
- 属性,按是否使用static修饰,分为 静态属性 VS 非静态属性(实例变量)
- 使用static修饰方法:静态方法
- 随着类的加载而加载,可以通过“类.静态方法”的方式调用
- 静态方法种只能调用静态的方法或属性
- 非静态方法种既可以调用静态方法也可以调用非静态方法或属性
- static注意点
- 在静态方法种不能使用this关键字,super关键字
- 关于静态属性和静态方法的使用。可以从生命周期的角度去理解
- 开发中,如何确定一个属性是否要声明为static的?
- 属性可以被多个方法共享,不会随着对象的的不同而不同
- 操作静态属性的方法,通常设置为static
- 工具类种的方法习惯上声明为static, 这样就可以直接通过类名来调用,不需要在实例化了
final关键字的使用:
Final:最终的
-
final可以用来修饰的结构:类,方法,变量
-
final 用来修饰一个类:此类不能被其他的类所继承。不想在扩充功能了
比如说:String类,System类,StringBuffer类
-
final 用来修饰方法:表明此方法不能够被重写
比如:Object类中的getClass()
-
final用来修饰变量,此时的“变量”就称为常量
抽象类与抽象方法(abstract关键字)
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例 ,这样的类叫做抽象类。
abstract关键字的使用
abstract可以用来修饰的结构:类,方法;
abstract修饰类:
- 使用abstract修饰的类,不能够创建实例
- 但是抽象类中有构造器,虽然不能够实例化,但是实例化子类的时候还是可以调用的
- 在开发中,都会提供抽象类的子类,让子类对象实例化,完成相关操作,如果没有子类的话abstract修饰类就没有意义了
abstract修饰方法:
- 抽象方法只有声明,没有方法体
- 包含抽象方法的类,一定是一个抽象类,因为抽象方法没有方法体,如果包含抽象方法的类不是抽象类的话,那么就可以通过类的实例化来调用抽象方法了,这样做显然是不合适的,所以包含抽象方法的类一定是抽象类 ,反之抽象类中不一定包含抽象方法
- 如果子类重写了父类中所有的抽象方法后,此类方法可以实例化
- 如果子类没有重写父类的所有抽象方法,那么此子类也是一个抽象类,需要使用abstract修饰
1 | public class AbstractTest { |
abstract使用上的注意点:
- abstract不能用来修饰属性,构造器等结构
- abstract不能用来修饰私有方法 和静态方法 和final的方法 ,因为使用abstract修饰的方法必定会被子类重写,但是私有化方法子类又不能重写,这样会导致矛盾,静态方法在子类中虽然能够构造同名同参数的方法,但是不认为这两个方法是同一个,final修饰的方法,不能够被重写
匿名类:
1 | public class PersonTest { |
最后运行结果为:
工人想要发表声明
工人想要发表声明
我是抽象类
我是匿名子类匿名对象
多态的应用
模版方法设计模式:
应用场景:当我们在构造一些功能的时候,功能内部一部分是确定的,一部分是不确定的,这时候我们可以把不确定的哪一部分暴露出去,让子类去实现。
换句话说:我们在设计一个算法的时候,整体步骤很固定,通用,这些步骤已经在父类中写好了。但是某些部分易变,易变的部分,可以抽象出来,供不同的子类实现,这就是一种模板形式
1 | public class TemplateTest { |
接口(interface)
再生产模式中,我们通常需要从几个类中派生出一个子类,继承他们所有的属性和方法,但是JAVA不支持多重继承。有了接口,就可以 得到多重继承的效果
另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接。
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的关系,而接口实现则是"能不能"的关系。
接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
接口的使用:
- 接口使用interface来定义
- Java中,接口和类是并行的两个结构
- 如何定义接口,定义接口中的成员:
- JDK7及以前只能:定义全局常量和抽象方法
- 全局常量:public static final的,但是可以省略不写
- 抽象方法:public abstract的
- JDK8:除了可以定义全局常量和抽象方法之外还可以定义静态方法(public static void 方法名),默认方法(public default void 方法名)
- 接口中定义的静态方法只能接口自己调用
- 实现类中可以调用接口中的默认方法,如果实现类重写了接口中的方法的话,调用的时候仍会调用重写的方法
- 如果子类(或实现类)继承父类和实现的接口中同名同参数的默认方法,那么子类在没有重写此方法的时候,默认调用的是父类中的同名同参数的方法 —> 类优先原则
- 如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在类没有重写这个方法的时候,报错:—>接口冲突
- 解决方法:我们需要在类中重写此方法
- 调用接口中的默认方法:
- 如果要调用接口中的静态方法直接通过 接口.静态方法 这种方式来进行调用
- 如果想要调用接口中的默认方法的话,需要通过接口.super.默认方法 这种方法来进行调用
- JDK7及以前只能:定义全局常量和抽象方法
- 接口中不能定义构造器! 意味着接口不能够实例化
- 在JAVA中,接口通过让类实现(implements)的方式来使用
- 如果实现类覆盖了接口 中的所有抽象方法,则此实现类可以实例化
- 如果实现类没有覆盖接口中的所有抽象方法,则次实现类仍未一个抽象类
- Java类可以实现多个接口 —>弥补了Java单继承的局限性
- 格式:class AA extends BB implements CC,DD,EE…
- 接口和接口之间可以继承,也可以多继承
- 接口的具体使用,体现多态性
- 接口实际上可以看做一种规范
接口的使用:
- 接口的使用上也满足多态性
1 | public class InterfaceTest { |
接口应用的代理模式 :
代理模式:是JAVA开发中使用较多的一种设计模式,代理设计就是为了其他对象提供一种代理以控制这个对象的访问
接口报错:
1 | interface A{ |
接口排错:
1 | interface P1ayable{ |
设计模式:
设计模式:设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模免去我们自己再思考和摸索。式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,”套路”
-
单例模式:所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法,如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private ,这样,就不能用new操作符在类的外部产生类的对象了 ,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21/**
*单例设计模式:
* 1. 单例设计模式,就是采用一定的方法保证在整个软件系统中该类只有一个实例对象
*
* 2. 实现方法:
* 饿汉式 VS 懒汉式
* 饿汉式
* 1. 私有化类的构造器,目的就是为了防止在 该类外面创建这个类的实例
* 2. 在该类内部创建类的对象(静态)
* 3. 提供公共的方法(静态)来调用这个类中创建的对象
* 懒汉式
* 1. 私有化类的构造器,目的就是为了防止在 该类外面创建这个类的实例
* 2. 声明当前类的对象,没有初始化, 设置为null
* 3. 声明public static的返回当前类的方法
* 3.1 在该方法中进行 类的声明
* 3.2 在声明前需要 判断一下该对象是否为null,如果是的话在进行实例化,如果不是的话直接返回
* 饿汉式:坏处:对象一开始就存在,存在时间过长
* 懒汉式:好处:什么时候使用是么时候在开始创建,
*
*
*/1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 饿汉式
public class SingletonTest1 {
public static void main(String[] args) {
Bank instance1 = Bank.getInstance();
Bank instance2 = Bank.getInstance();
System.out.println(instance1 == instance2);
}
}
class Bank {
// 1. 私有化类的构造器
private Bank() {
}
// 2. 在该类内部创建类的对象
private static Bank instance = new Bank();
// 3. 提供公共的方法返回类的对象
public static Bank getInstance() {
return instance;
}
}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 SingletonTest2 {
public static void main(String[] args) {
Order order1 = Order.getInstance();
Order order2 = Order.getInstance();
System.out.println(order1 == order2);
}
}
class Order {
// 1.私有化类的构造器
private Order(){
}
// 2. 声明当前类的对象,没有初始化
// 4. 此对象需要声明为static
private static Order instance = null;
// 3. 声明public static的返回当前类的方法
public static Order getInstance() {
if (instance == null) {
instance = new Order();
}
return instance;
}
}-
单例模式的应用场景:
网站的计数器,一般也是单例模式实现,否则难以同步。
应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
Application也是单例的典型应用
Windows的Task Manager(任务管理器)就是很典型的单例模式
Windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
-
-
创建型模式:共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
-
结构型模式,共7种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
-
行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式
状态模式、访问者模式、中介者模式、解释器模式。
main函数的使用方发
main() 方法的使用说明:
public static void main(String[] args)
-
mian()方法作为程序入口
-
mian()方法也是一个普通的静态方法
-
mian()方法也可以作为我们与控制台 的交互方法。传入参数为args数组中的元素
操作方法:运行时:java “TOM” “123” “ture” “false”
这样的操作 就是把后面这四个元素放到String类型的数组 args中
赋值的先后顺序
对象赋值可使用的位置,
- 默认初始化
- 显示初始化
- 在代码中赋值
- 在构造器中初始化
- 有对象之后可以通过“对象.属性”或“对象.方法”的方式,进行赋值
执行先后顺序为 1-> 2 /3(不分先后谁在前谁先赋值) ->4 -> 5
- 默认初始化: 在你创建变量的时候就会初始化赋值,例如在创建变量的时候会默认赋值,例如,int型默认值为0,浮点型默认型为0.0,bool型默认为true
- 显示初始化:我们手动的给他赋值,例如a = 10
- 在代码块中赋值 字面意思
每天一考
-
如何实现向下转型?需要注意什么?如何解决此问题
向下转型的目的就是使父类对象能够调用子类的方法
使用强转符:()
Person p = new Man()
Man m = (Man)p
如果转换失败的话会报ClassCastException异常,为了避免这个错误,我们在进行强制类型转换之前,先进行instanceof 判断一下被转换目标
if (p instanceof Man){
Man m = (Man)p;
}
-
和equals() :
可用于基础数据类型,也可用于引用数据类型,用于基础数据类型的时候比较的是两个数据的值相等就返回true,如果是引用数据类型的话,就是要比较这两个引用数据类型地址是否相等
equals:是一个方法,只能比较引用数据类型,在没有重写的时候,和==的用法是相同的,都是比较地址,如果重写之后就能够比较引用数据变量内的具体的值
-
8种数据类型及其对应的包装类:
int Integer
char Character
-
基本数据类型,包装类,String三者之间如何转换
基本数据类型,和包装类,使用自动装箱和拆箱
前两者和String之间的
基本数据类型, 包装类–>String:使用String中的方法 valueOf(xxx)
String --> 基本数据类型,包装类:使用包装类种的方法 parseXxx(String s)
-
对多态性的理解:
实现代码的通用性
抽象类和接口肯定体现了多态性。(抽象类和接口不能实例化)
多态性是运行时行为
-
static修饰的属性,相较于实例变量,有哪些特别之处:
- 随着类的加载而加载
- 早于对象的创建
- 只要权限允许,可以通过“对象.static 属性”的方式进行调用
- 存在于方法区的静态域中
-
final可以用来修饰那些结构,分别表示什么意思:
-
final可以用来修饰的结构:类,方法,变量
-
final 用来修饰一个类:此类不能被其他的类所继承。不想在扩充功能了
比如说:String类,System类,StringBuffer类
-
final 用来修饰方法:表明此方法不能够被重写
比如:Object类中的getClass()
-
final用来修饰变量,此时的“变量”就称为常量
-
-
类的属性赋值位置有哪些?先后顺序为什么
- 默认初始化
- 显示赋值
- 代码块赋值
- 构造器赋值
- “对象.属性”或者 “对象.方法” 赋值
-
小结:一叶知秋
publicstaticvoidmain(String[] args){//方法体}
权限修饰符: private缺省protected pubilc ---->封装性
修饰符: static \ final \ abstract \native可以用来修饰方法返回值类型:无返回值/有返回值–>return
方法名:需要满足标识符命名的规则、规范;“见名知意”
形参列表:重载vs重写;参数的值传递机制;体现对象的多态性方法体:来体现方法的功能方法体:来体现方法的功能
异常处理
异常体系结构:
java.lang.Throwable:
- java.lang.Error:重大错误异常,一般不编写针对性的代码进行处理
- java.lang.Exception:可以进行异常处理
- 编译时异常(checked)
- IOException
- FileNotFoundException
- ClassNotFoundException
- IOException
- 运行时异常(unchecked)
- NullPointerException
- ArrayIndexOutOfBoundsException
- ClassCastException
- NumberFormatException
- InputMismatException
- ArithmeticException
- 编译时异常(checked)
异常实例:
1 |
|
异常处理机制
JAVA处理异常的情况,是将异常处理的程序代码集中在一起,与正常的程序代码分开,是程序简洁,优雅,并易于维护
异常的处理:抓抛模型
过程一:“抛”:程序在正常执行的时候,一旦出现异常,就会在异常代码处生成一个对应的异常类的对象,并将这个对象抛出。一旦抛出对象之后,其后代吗就不再执行
关于异常对象的产生:
- 系统自动生成的异常对象
- 手动生成一个异常对象,并抛出(throw)
1 | public class ExceptionTest3 { |
过称二:“抓”:可以理解为异常的处理方式① try - catch - finally,②throws
方式一:try - catch - finally
try{
// 可能出现异常的代码
}catch(异常类型1,变量名1){
// 处理方法1
}catch(异常类型2,变量名2){
// 处理方法2
}catch(异常类型3,变量名3){
// 处理方法3
}
…
finally{
// 一定会执行的代码
}
说明:
- finally是可选的
- 使用try将可能的出现错误的代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应的异常类,根据此对象的类型,去catch中匹配
- 一旦try中异常对象匹配到某一个catch时,就进入catch中进行异常处理,一旦处理完成就跳出当前的try - catch结构(在没有写finally的情况下),继续执行其后面的代码
- catch中异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓,如果有子父类的关系则要求子类一定要声明在父类上面,否则报错
- catch中常用的异常处理方式①String getMessage() ②printStackTrace()
- 在try结构中声明的变量,再出了try结构以后就不能够再调用了
- try - catch - finally结构可以相互嵌套
体会1:使用try - catch - finally处理编译时异常的时候,使得在编译时不报错,但是运行的时候可能会报错。相当于我们使用try-catch - finally将一个编译时可能出现的异常,延迟到运行时出现
体会2:开发中,由于运行时异常比较常见,所以我们通常不针对运行时异常编写try - catch - finally了,针对编译时异常,我们一定要考虑异常的处理。
1 | public void test1(){ |
- finally是可选的
- finally中声明的是一定会执行的代码,即使catch中又出现异常了,try中有return语句,catch中有return语句的情况,如果出现以上几种情况的话,try - catch 结构后面的语句就不会运行了,如果有finally语句的话,即使出现上面的情况的话,finally里面的语句也会被执行
- 像数据库,输入输出流,网络编程Socket等资源中,JVM是不能够自动回收的,我们需要自己手动的进行资源的释放。此时资源的释放,就需要声明在finally中
方式二:throws + 异常类型
-
"throws + 异常类型"写在方法的声明处,指明此方法执行时,可能会抛出的异常类型,一旦方法体执行的时,出现异常,仍会在异常代码处生成一个异常的对象,此对象满足throws后异常类型时,就会被抛出,异常后面的代码不会再执行!!!
-
体会:try - catch - finally真正的将异常给处理掉了。
throws的方式只是将异常抛给了方法的调用者。并没有真正的将异常处理掉
-
开发中如何选择try - catch - finally 还是throws 方式来处理异常
- 如果父类中的方法没有进行throws方式来进行处理异常, 那么子类重写该方法的时候也不能使用throws方式来处理异常,但是如果子类中重写的方法真的有异常的话,只能使用try-catch-finally的方式来进行处理
- 在执行方法a的时候,又先后调用了另外几个方法,这几个方法是递进关系执行的,我们建议这几个方法使用throws方式来进行处理,而执行方法a可以使用try- catch-finally来进行处理
1 | public class ExceptionTest2 { |
方法重写规则之一:
子类重写的方法抛出的异常类型不能大于父类被重写的方法抛出的异常类型
自定义异常类
-
继承与现有的异常结构:RuntimeException, Exception
-
提供全局常量:serialVersionUID
-
提供重载的构造器
1
2
3
4
5
6
7
8
9
10
11public class MyException extends RuntimeException{
static final long serialVersionUID = -4648468498468487L;
public MyException(){
}
public MyException(String msg){
super(msg);f
}
}
异常的测试
1 | public class EcmDef { |
throw 和 throws的区别:
- throw是生成一个异常的方式
- throws是异常处理的一个方式
世界上最遥远的距离,是我在if里你在else里,似乎一直相伴又永远分离;
世界上最痴心的等待,是我当case你是switch,或许永远都选不上自己;
世界上最真情的相依,是你在try我在catch。无论你发神马脾气,我都默默承受,静静处理。到那时,再来期待我们的finally。
面试题
final、 finally、finalize三者的区别?
类似 :throw和throws
Collection和Collections
String . stringBuffer、StringBuilder
ArrayList . LinkedList
HashMap . LinkedHashMap
重写、重载
结构不相似的 :抽象类、接口
== 、 equals()
sleep(). wait()
多线程
程序,进程,线程
- 程序:
- 是为了完成特定的任务,用某种语言编写的一组指令的集合。即一段静态的代码,静态对象
- 进程:
- 是进程的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身产生,存在和消亡的过程。–生命周期
- 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
- 线程:
- 进程可以进一步细化为线程,是一个程序内部的一条执行路径
- 如果一个进程同一时间并行 执行多个线程,就是支持多线程的
并行和并发:
并行:多个cpu同时执行多个任务, 多个人做不同的事情
并发:一个cpu同时执行多个任务,多个人做同一件事情
使用多线程的优点:
背景: 以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?
多线程程序的优点:
1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
2. 提高计算机系统CPU的利用率
3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
创建多线程的方法:
-
通过实现Thread的子类来进行创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class myThread extend Thread{
public void run(){
System.out.println("啊");
}
}
class myThreadTest(){
public static void main(String[] args){
myThread mythrad = new myThread();
// 创建一个线程
mythread.start();
mythread.getName();
print("b");
}
}
-
创建一个继承与Thread类的子类
-
重写Thread中的run方法 – >将此线程中需要执行的操作放在run方法中
-
创建Thread类的子类对象
-
通过此对象调用start()方法来创建新线程
-
启动当前线程
-
调用当前线程中的run方法
-
问题1:我们不能够直接通过对象.run 的方法来启用多线程
- 问题2:如果我们想要在启用一个线程的话,不能够通过已经start后的线程来开启,会报错:
- 我们需要从新创建一个线程对象来进行下一个线程的调用
- 问题2:如果我们想要在启用一个线程的话,不能够通过已经start后的线程来开启,会报错:
-
-
如果一个线程中的东西我们只想执行一次的话,我们可以创建一个匿名对象:
1
2
3
4
5
6
7
8
9
10
11
12
13public static void main(String[] args){
new Trhead(){
public void run(){
// 需要执行的代码:
for (int i = 0; i < 100 ; i++){
if(i % 2 == 0){
System.out.println(i)
}
}
System.out.pirntln(Thread.currentThread.getName())
}
}
}1
2
3
4
5public static void main(String[] args){
MThread mThread = new MThread();
Thread t1 = new Thread(mThread);
t1.start()
}-
通过实现Runable这个接口来实现
- 创建一个实现了Runable接口的类
- 实现类去实现Runable中的抽象方法:run()
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象的调用start()
1
2
3
4
5
6class mThread implements Runable{
public void run(){
p
}
} -
比较创建线程的两种方式:
- 开发中优先选择:实现Runable接口的方式
- 原因:
- 实现方式没有类的单继承性的局限性
- 实现的方式更适合来处理多个线程有共享数据的情况
- 联系:Thread类实现了Runable方法:public class Thread implements Runable
- 相同点:两种方式都需要重写run(),将线程执行的逻辑声明在run()中
- 以上两种方式,要想启动线程,都是要调用Thread类中的start()方法来进行调用的
-
Thread类的有关方法
- void start:启动线程
- run:线程在被调度的实行的操作
- String getName():返回线程的名称
- void setName(String Name):设置该线程的方法
- static Thread currentThread():返回当前线程, 在Thread子类中就是this,通常使用与主线程和Runnable实现类
- static void yield():线程让步
- 暂停当前线程,把执行机会让给优先级相同或者更高的线程
- 若队列中没有同优先级的线程,则忽略此方法
- join():当某个执行流中调用其他线程的join方法,调用线程将会被堵塞,知道join()方法加入的join线程执行完毕之后自己在开始执行
- static void sleep(long millis)
- 让当前的线程在指定时间段之内放弃对CPU的控制,使得其他的线程有机会被执行,时间到后从新排队
- 抛出InterruptedException异常
- stop():强制结束线程生命进程,不推荐使用
- boolean isAlive(), 返回bool值,判断线程是否还活着
线程的调度
- 时间片:
- 抢占式:
- 高优先级的线程抢占CPU
- 同优先线程组成先进先出队列(先到先服务),使用时间片策略
- 对高优先级,使用优先调度的抢占式策略
优先级的方法:
- getPriority():返回线程优先级
- setPriority(int newPriority):改变线程的优先级
- MAX_PRIORITY: 10
- MIN_PRIORITY: 1
- NORM_PRIORITY: 5
一般的时候线程的优先级都为 NORM_PRIORITY ,这个我们可以自己设置
说明:
- 线程创建的时候继承父线程的优先级
- 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
线程的生命周期
一个完整的生命周期有以下几种状态:
- 新建:当一个Thread类或者其子类的对象被声明创建的时候,新生的状态处于新建状态。
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时他已经具备了运行条件,只是还没有被分配到CPU资源
- 运行:当就绪的线程被调度并获得CPU资源的时候,便进入了运行状态,run()方法定义了线程的操作和功能
- 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,进入阻塞状态
- 死亡:线程完成了它的全部工作,或线程被提前强制性终止,或出现异常导致结束
线程的同步(线程安全)
-
问题:
多个线程的不确定性,引起执行结果的不稳定
多个线程对账本的共享 ,会造成操作的不完整性,会破坏数据
-
原因:
某个线程在操作共享数据的时候,在还没有操作完的时候,其他线程也参与进来,这就会导致线程的安全问题
-
解决方案:
当一个线程在操作公共数据的时候,其他线程不能够参与进来,直到这个线程完成了对公共数据的操作之后,其他线程才能进来,这种情况,即使这个线程发生了阻塞,别的线程也不能进来操作
-
在Java中我们通过同步机制来解决线程安全问题
-
方式1:同步代码块
1
2
3
4synchronized(同步监视器){
// 需要被同步的代码
}说明:
- 操纵共享数据的代码,就是要被同步的代码 ------> 不能够包多,也不能包少
- 共享数据:多个线程共同操作的数据
- 同步监视器:俗称 锁。任何一个类的对象都可以充当锁
- 要求:多个线程必须公用同一把锁
- 补充:
- 在实现Runable接口中创建多线程的方法中,我们可以考虑使用this充当同步监视器
- 在继承Tread类中创建多线程方法中,我们可以考虑使用 类.class 来充当同步监视器
-
方式2:同步方法:
- 使用同步方法来解决实现Runable接口的线程安全问题·
- 同步方法仍然涉及到同步监视器,只是不需要我们显示的声明
- 非静态方法(实现Runable接口的方法),同步监视器是this
- 静态方法(继承Thread类的方法),同步监视器是当前类本身
- 实现方法,直接使用synchronized 修饰监视器即可
-
方式3:Lock锁 — JDK5.0新增方法:
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
31class window implements Runable{
private int ticket = 100;
// 1.实例化ReentrantLock对象
private ReentrantLock lock = new ReentrantLock();
public void run(){
while(true){
try{
// 2.调用锁定方法Lock()
lock.lock();
if(ticket > 0){
Thread.sleep(100);
} catch(InterruputedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"票数"+ticket);
ticket--;
}else{
break;
}
}finally{
// 3.调用解锁方法 unlock()
lock.unlock();
}
}
}面试题:
synchronzied 与 Lock的异同?
相同:
- 两者都可以解决线程安全问题
不同:
- Lock需要手动的启用同步(Lock()),同时结束同步也需要手动实现(unlock())
- synchronized机制在执行完全相同的代码以后,自动释放同步监视器
优先使用方式:
Lock -> 同步代码块(已经进入方法体,分配了相应的资源) -> 同步方法(在方法体之外 )
-
-
同步操作的方式
- 解决了线程的安全问题 ----好处
- 弊端:操作同步代码的时候,只能有一个线程参与,其他线程等待,这相当于一个单线程的问题,效率比较低
-
解决懒汉式的线程安全问题:(懒汉式:单例化类):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class Bank(){
// 私有化类的构造器,不能够从外面新建类
private Bank(){};
private Bank instance = null;
private Bank getInsance(){
if(instance == null){
synchronized(Banc.class){
if(instance == null){
instance = new Back();
}
}
}
return instance;
}
} -
线程的死锁问题:
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
- 解决方法:
- 专门的算法、原则
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
- 解决方法:
线程的通信
使线程交替运行,让每一个线程都有机会抢到运行的代码
线程通信的三个方法:
-
wait():一旦执行此方法,当前线程就被进入阻塞状态,并释放同步监视器。
-
notify():一旦执行此方法,就会唤醒wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个
-
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
-
这三个方法只能够出现在同步代码块中
-
这三个方法调用者必须是同步代码块或同步方法中的同步监视器
-
这三个方法是定义在java.lang.Object类中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24while(true){
synchronized(this){
// 释放前几个线程 防止线程的阻塞
notifyAll();
if(number <= 100){
try{
Thread.sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("number = :"+number);
number++;
try{
// 调用wait方法的线程进入阻塞状态,这时候下一个线程就可以进入,实现线程的交替
wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}else{
break;
}
}
}
sleep() 和wait()的异同?
- 相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
- 不同点:
- 两个方法声明位置不同:Thread类中声明sleep(),Object类中声明wait()
- 调用的位置不同:sleep()可以在任何需要的场景下调用,wait()必须在同步代码快中使用
- 关于是否释放同步监视器:如果两个方法都是用在同步代码块或同步方法中
经典例题:生产者和消费者问题:
1 | /** |
JDK5.0新增线程方式
新增方式一:实现Callable接口,实现call方法
- 相比于run方法,可以有返回值
- 方法可以抛出异常
- 支持泛型的返回值
- 需要借助FutureTrue类,比如说获取返回结果
- Future接口
- 可以对具体的Runable, Callable任务的执行结果进行取消,查询是否完成,获取结果等。
- FutureTask是Future接口的唯一实现类
- FutureTask 同时实现了Runable,Future接口。它既可以作为Runable被线程执行,又可以作为Future得到Callable的返回值
- Future接口
1 | // 1.创建一个实现Callable接口的类 |
为什么Callable接口方式创建多线程比实现Runable接口创建多线程的方式强大?
- call()可以有返回值
- call()可以抛出异常,被外面的操作捕获,获取异常信息
- Callable是支持泛型的
新增方式二:使用线程池
-
背景:
经常创建和销毁,使用量特别大的资源,比如并发下的线程,对性能影响很大
-
思路:
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创造销毁,实现重复利用,类似生活中的公共交通问题
-
好处:
- 提高反应速度(减少了创建新线程的时间)
- 降低资源消耗(重复线程池中的线程,不需要每次都创建)
- 便于线程的管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
-
方法:
ExecutorService:真正的线程池接口、常见的子类ThreadPoolExecutor
- void execute(Runable commend):执行命令。没有返回值,一般用来执行Runable
Future submit(Callable task):执行任务,有返回值,一般又来执行Callable - void shutdown():关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
- Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程
- Exscutors.newFixedThreadPool(n);创建一个可重用固定线程数的线程池
- Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
- Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
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
44import java.util.concurrent.Executors;
class NumberThread implements Runable{
public void run(){
for(int i = 0; i < 100; i++){
if(i % 2 == 0){
System.out.println(i);
}
}
}
}
class NumberThread1 implements Runable{
public void run(){
for(int i = 0; i < 100; i++){
if(i % 2 != 0){
System.out.println(i);
}
}
}
}
public class ThreadPool{
public static void main(String[] args){
// 1. 提供指定数量的线程池,返回值是一个接口对象
ExecutorService service = Executors.newFixedThreadPool(10);
// 可以将这个接口强制转换为类对象,通过这个类的对象来调用各个属性
ThreadPoolExecutor service1 = (ThreadPollExecutor) service;
service1.setCorePoolSize(15);
// 2. 执行指定的线程操作, 需要提供实现Runable接口或者Callable接口的对象
// 适用于Runable
service.execute(new NumberThread);
service.execute(new NumberThread1);
// 适用于Callable
// service.submit();
// 关闭线程池
service.shutdown();
}
}
常用类
字符串相关的类
String对象的特性
/*
String:字符串,使用一对“”括起来表示
1. String声明为final的 ,表示不可以被继承
2. String实现了Serializable接口:表示该类型是可序列化的
实现了Comparable接口:表示该类型是可以比较大小的
3. String内部定义了 final char[] value用于存储字符串的数据
4. String:代表了不可变的字符序列。简称不可变性
体现: 1.当对字符串进行从新赋值的时候,需要重新指定区域赋值,不能够对原有的value进行赋值
2. 当进行字符串的拼接操作的时候,也需要重新指定区域赋值,不能够对原有的value进行赋值
3. 当调用String的replace方法修改指定字符串的时候,也需要重新指定区域赋值,不能够对原有的value进行赋值
5. 通过字面量方式(区别于new)给一个字符串赋值,此时字符串值声明在,字符串常量池中,
6. 字符串常量池中是不会出现相同内容的字符串的
*/
@Test
public void test1(){
String s1 = "abc"; // 字面量的定义方式
String s2 = "abc";
System.out.println(s1 == s2); // 比较s1和s2的地址值,返回值是true
s1 = “hello”;
System.out.println(s1);
System.out.println(s2);
String s3 = “abc”;
s3 += “def”;
System.out.println(s3);
System.out.println(s2);
String s4 = “abc”;
String s5 = s4.replace(‘a’, ‘b’);
System.out.println(s4);
System.out.println(s5);
}
String对象的创建:
面试题:String s = new String(“abc”);方式创建对象,在内存中创建了几个对象?
两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据"abc"
1 | String str = "hello"; |
1 | public void test2(){ |
- 常量和常量的拼接结果在常量池,并且常量池中不会出现相同内容的常量
- 在字符串拼接的时候,只要其中有一个是变量,结果就在堆中
- 如果拼接的结果是调用intern()方法,返回值就在常量池中
1 | String s1 = "hello";string s2 = "world"; |
String常用方法:
- int Length():返回字符串的长度:return value.Length
- char charAt(int index):返回某索引处的字符return value[index]
- boolean isEmpty():判断是否是空字符串: return value.length == 0
- String toLowercase():使用默认语言环境,将 String中的所有字符转换为小写
- String toupperCase():使用默认语言环境,将String中的所有字符转换为大写
- String trim():返回字符串的副本,忽略前导空白和尾部空白
- boolean equals(object obj):比较字符串的内容是否相同
- boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
- String concat(String str):将指定字符串连接到此字符串的结尾。等价于用+”
- int compareTo(String anotherString):比较两个字符串的大小, 返回值是0
- String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取
- string substring(int beginIndex,int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取,到endIndex结束不包括endIndex
1 |
|
-
boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
-
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
-
boolean startsWith(String prefix,int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
1
2
3
4
5
6
7
8
9
10
11public void test(){
String str1 = "helloworld";
boolean b1 = str1.endWith("rld"); // 返回值是boolean类型
System.out.println(b1); // true
boolean b2 = str1.startsWith("He");
System.out.println(b2); // false
boolean b3 = str1.startWith("ll", 2);
System.out.println(b3); // true
} -
boolean contains(CharSequence s):当且仅当此字符串包含指定的char值序列时,返回true
-
int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
-
int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
-
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
- indexof和lastIndexOf方法如果未找到都是返回-1
-
int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后次出现处的索引,从指定的索引开始反向搜索
1 | public void test(){ |
-
String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用newChar替换此字符串中出现的所有oldChar得到的。
-
String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串
-
String replaceAll(String regex,String replacement):使用给定的replacement替换此字符串所有匹配给定的正则表达式的子字符串。
-
String replaceFirst(String regex,String replacement):使用给定的replacement替换此字符串匹配给定的正则表达式的第一个子字符串。
1
2
3
4
5
6
7
8
9
10public void test4(){
String str1 = "河北地质大学,真大!";
String str2 = str1.replace('大', '小');
System.out.prinln(str1); //河北地质大学,真大!
System.out.prinln(str2); //河北地质小学,真小!
String str3 = str1.replace("河北", "中国");
System.out.println(str3); // 中国地质大学,真大!
} -
boolean matches(String regex):告知此字符串是否匹配给定的正则表达式.
-
Stringsplit(String regex):根据给定正则表达式的匹配拆分此字符串。
-
Stringsplit(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
String与基本数据类型的转换
String 与基本数据类型, 包装类之间的转换。
String —> 基本数据类型,包装类 :调用包装类中的parseXxx(str)
基本数据类型,包装类 ------>String:调用String中的valueOf方法 或者+""
String与字符数组转换
String 与 char[]:之间的转化
String ----> char[]:调用了String中的toCharArray()
char[] ----> String:调用了String类型的构造器
1 | public void test(){ |
String与字节数组进行转换
String与byte[]之间的转换
编码: String --> byte[]:调用string的getBytes()解码: byte[] --> String:调用String的构造器
编码:字符串–>字节(看得懂—>看不懂的二进制数据)
解码:编码的逆过程,字节–>字符串(看不懂的二进制数据—>看得懂)
说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码
1 | public void test(){ |
StringBuffer 和 StringBuilder的使用
String、StringBuffer.StringBuilder三者的异同?
String:不可变的字符序列;底层使用char[]存储
StringBuffer:可变的字符序列;线程安全的,效率低;底层使用char[]存储
StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储
源码分析:
1 | String str = new String();//char[ ] value = new char[0]; |
问题1.System.out.printLn(sb2.Length());//3
问题2.
- 扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。
- 默认情况下,扩容为原来容量的2倍+2,同时将原有数组中的元素复制到新的数组中。
指导意义:开发中建议使用StringBuffer(int capacity) 或者StringBuilder(int capacity)
常用方法StringBuffer的常用方法:
StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
StringBuffer delete(int start,int end):删除指定位置的内容
StringBuffer replace(int start, int end,String str):把[start,end)位置替换为str
StringBuffer insert(int offset, xxx):在指定位置插入xxx
StringBuffer reverse() :把当前字符序列逆转
public int indexof (String str)
public string substring(int start,int end)public int length()
public char charAt(int n )
public void setCharAt(int n ,char ch)
总结:
增: append(xxx)
删:delete(int start,int end)
改: setCharAt(int n ,char ch) / replace(int start,int end,String str)
查: charAt(int n )
插: insert(int offset, xxx)长度:Length();
遍历: for() + charAt() / tostring()
日期和事件API
JDK8 以前
System类提供的public static long currentTimeMillis() 用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。此方法适用于计算时间差
计算世界时间的主要标准有:
UTC(Coordinated Universal Time)
GMT(Greenwich Mean Time)
CST(Central Standard Time)
1 | // 1. System类中的currentTimeMillis() |
java.util.Date类
表示特定的时间,精确到毫秒
构造器:
Date():使用无参构造器创建的对象可以获取本地当前时间。
Date(long date)
常用方法
getTime(): 返回自1970年1月1日00:00:00 GMT以来此Date对象表示的毫秒数。
toString():把此 Date对象转换为以下形式的 String: dow mon dd hh:mm:ss zzz yyyy其中: dow是一周中的某一天(Sun,Mon,Tue,Wed,Thu, Fri, Sat),zzz是时间标准。
其它很多方法都过时了
1 | /* |
Date类的API不易于国际化,大部分被废弃了,java.text.SimpleDateFormat类是一个不与语言环境有关的方式来格式化和解析日期的具体类。
它允许进行 格式化:日期→文本、 解析:文本→日期
格式化:
SimpleDateFormat() :默认的模式和语言环境创建对象public SimpleDateFormat(String pattern):该构造方法可以用参数pattern指定的格式创建一个对象,该对象调用:
public String format(Date date):方法格式化时间对象date
解析:
public Date parse(String source):从给定字符串的开始解析文本,以生成一个日期。
1 | /* |
Calendar是一个抽象基类,主要完成日期字段之间相互操作的功能。
获取Calendar实例的方法
使用Calendar.getlnstance()方法
调用了它的子类GregorianCalendar的构造器
一个Calendar的实例是系统时间的抽象表示,通过get(int field)方法来取得想要的时间信息。比如YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY、MINUTE、SECOND
public void set(int field,int value)
public void add(int field,int amount)
public final Date getTime()
public final void setTime(Date date)
注意:
获取月份时:一月是0,二月是1,以此类推,12月是11
获取星期时:周日是1,周二是2,。。。。周六是7让天下没有难学的技
JDK8以后
新方法出现的背景新方法的优势Calendar 和Date的缺陷:
可变性:像日期和时间这样的类应该是不可变的。
偏移性:Date中的年份是从1900开始的,而月份都从O开始。
格式化:格式化只对Date有用,Calendar则不行。
此外,它们也不是线程安全的;不能处理闰秒等。
Java8吸收了Joda-Time的精华,以一个新的开始为Java创建优秀的API。
新的 java.time 中包含了所有 关于:
本地日期(LocalDate)
本地时间(LocalTime)、
本地日期时间(LocalDateTime)、
时区(ZonedDateTime)
持续时间(Duration)的类
历史悠久的 Date类新增了tolnstant()方法,用于把 Date转换成新的表示形式。这些新增的本地化时间П期API大大简化了日期时间和本地化的管理。
方法 | 描述 |
---|---|
now()/* now(Zoneld zone) | 静态方法,根据当前时间创建对象/指定时区的对象 |
of() | 静态方法,根据指定日期/时间创建对象 |
getDayOfMonth(getDayOfYear() | 获得月份天数(1-31)/获得年份天数(1-366) |
getDayofWeek() | 获得星期几(返回一个 DayOfWeek枚举值) |
getMonth() | 获得月份,返回一个Month 枚举值 |
getMonthvalue()/getYear() | 获得月份(1-12)/获得年份 |
getHouro/getMinute0getSecond() | 获得当前对象对应的小时、分钟、秒 |
withDayOfMonth()/ withDayOfYear()/withMonth() / withYear() |
将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象 |
plusDays(0), plusWeeks(), plusMonths(). plusYears(),plusHours() |
向当前对象添加几天、几周、几个月、几年、几小时 |
minusMonths()/minusWeeks() minusDays()/minusYears()/minusHours() |
从当前对象减去几月、几周、几天、几年、几小时 |
1 |
|
瞬时:instant类
Instant:时间线上的一个瞬时点,这个可能被用来记录应用程序中的事件时间戳
在处理时间和日期的时候,我们通常会想到年月日时分秒,这种记录时间的方法,这只是时间的一个模型,是面向人类的,第二种通用模式是面向机器的,或者说是连续处理的。在此模型中时间线中的一个点表示为一个很大的数,这也有利于计算机的处理。在UNIX中,这个数从1970年开始,以秒为单位;同样的,在java中也是从1970年开始,但是以毫秒为单位
java.time包通过值类型Instant提供机器视图,不提供处理人类意义上的时间单位。 Instant表示时间线上的一点,而不需要任何上下文信息,例如,时区。概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC)开始的秒数。因为java.time包是基于纳秒计算的,所以Instant的精度可以达到纳秒级。 1秒 = 1000毫秒 = $10^6$微妙 = $10^9$纳秒
方法 | 描述 |
---|---|
now() | 静态方法,返回默认UTC时区的Instant类的对象 |
ofEpochMilli(long epochMilli) | 静态方法,返回在1970-01-01 00:00:00基础上加上指定毫秒数之后的Instant类的对象 |
atOffset(ZoneOffset offset) | 结合即时的偏移来创建一个 OffsetDateTime |
toEpochMilli() | 返回1970-01-01 00:00:00到当前时间的毫秒数,即为时间戳 |
1 |
|
格式化与解析日期或时间
java.time.format.Date.TimeFormatter 类:该类提供了三种格式化方法
方式一:预定义的标准格式
ISO_LOCAL_DATE_TIME;
ISO_LOCAL_DATE;
ISO_LOCAL_TIME
方式二:本地化相关格式:
ofLocalizedDateTime()
FormatStyle.LONG
FormatStyle.MEDIUM
FormatStyle.SHORT : 适用于LocalDateTime
ofLocalizedDate()
FormatStyle.FUL
FormatStyle.LONG
FormatStyle.MEDIUM
FormatStyle.SHORT: 适用于LocalDate
自定义格式:如ofPattern("yyyy-MM-dd hh-mm-ss ")
方法:
方法 | 描述 |
---|---|
ofPattern(String pattern) | 静态方法,返回一个指定字符串格式的DateTimeFormatter |
format(TemporaAccessor t) | 格式化一个日期,时间,返回字符串 |
parse(CharSequence text) | 将指定格式的字符串序列解析为一个日期,时间 |
1 |
|
其他API
Zoneld:该类中包含了所有的时区信息,一个时区的ID,如 Europe/Paris
ZonedDate Time:一个在ISO-8601日历系统时区的目期时间,如 2007-12-03T10:15:30+01:00 Europe/Paris。
其中每个时区都对应着ID,地区ID都为“{区域}/{城市}”的格式,例如Asia/Shanghai等
Clock:使用时区提供对当前即时、日期和时间的访问的时钟。
持续时间:Duration,用于计算两个“时间”间隔
日期间隔:Period,用于计算两个“日期”间隔
TemporalAdjuster :时间校正器。有时我们可能需要获取例如:将日期调整到“下一个工作日”等操作。
TemporalAdjusters :该类通过静态方法
(firstDayOfXxx()/lastDayOfXxx()/nextXxx())提供了大量的常用TemporalAdjuster的实现。
与传统的日期处理相互转换
类 | To遗留类 | From遗留类 |
---|---|---|
java.time.Instant与java.util.Date | Date.from(instant) | date.tolnstant() |
java.time.Instant与java.sql.Timestamp | Timestamp.from(instant) | timestamp tolnstant() |
java.time.ZonedDate Time与 java.util.GregorianCalendar |
GregorianCalendar.from(zonedDateTime) | cal.toZonedDateTime() |
java.time.LocalDate java.sqI.Time | Date.valueOf(localDate) | date.toLocalDate() |
java.time.Local Time与java.sql.Time | Date.valueOf(localDate) | date.toLocalTime() |
java.time.LocalDate Time与 java.sql.Timestamp |
Timestamp.valueOf(localDateTime) | timestamp.toLocalDateTime() |
java.time.Zoneld与java.util. TimeZone | Timezone.getTimeZone(id) | timeZone.toZoneld() |
java.time.format.Date TimeFormatter与java.text.DateFormat | formatter.toFormat() | 无 |
JAVA比较器
一:说明:java中的 对象,正常情况下,只能比较== 或者!=。不能够使用 > 或 < 的但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小,如何实现?使用两个接口中的任意一个:comparable 或 comparator
1 | /* |
二:Comparable接口和Comparator的使用比较
Comparable接口的方式一旦确定,保证Comparable接口实现类的对象在任何位置都可以比较大小
Comparator接口属于临时性的比较
System类,Math类,BigInteger与BigDecimal
System类
-
System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。该类位于java.lang包。
-
由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便的进行调用。
-
成员变量
System类内部包含in、out和err三个成员变量,分别代表标准输入流(键盘输入),标准输出流(显示器)和标准错误输出流(显示器)。
- 成员方法
native long currentTimeMillis():
该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。
void exit(int status):
该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等。void gc():
该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况。String getProperty(String key):
该方法的作用是获得系统中属性名为key的属性对应的值。系统中常见的属性名以及属性的作用如下表所示:
属性名 属性说明 java.version java运行时环境版本 java.home java的安装目录 os.name 操作系统的名称 os.version 操作系统的版本 user.name 用户账户的名称 user,home 用户主目录 user.dir 用户当前的工作目录
1 |
|
Math类
java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回值类型一般为double型。
- abs绝对值
- acos,asin,atan,cos,sin,tan三角函数sqrt平方根
- pow(double a,doble b)a的b次幂log自然对数
- expe为底指数
- max(double a,double b)min(double a,double b)
- random()返回0.0到1.0的随机数
- long round(double a)double型数据a转换为long型(四舍五入)toDegrees(double angrad)弧度—>角度
- toRadians(double angdeg)角度—>弧度
BigInteger类
Integer类作为int的包装类,能存储的最大整型值为,Long类也是有限的,最大为。如果要表示再大的整数,不管是基本数据类型还是他们的包装类都无能为力,更不用说进行运算了。
java.math包的BigInteger可以表示不可变的任意精度的整数 。BigInteger提供所有Java的基本整数操作符的对应物,并提供java.lang.Math 的所有相关方法。另外,BigInteger还提供以下运算:模算术、GCD计算、质数测试、素数生成、位操作以及一些其他操作。
构造器
Biglnteger(String val):根据字符串构建Biglnteger对象
常用方法:
public Biglnteger abs():返回此 BigInteger的绝对值的 BigInteger。
BigInteger add(BigInteger val):返回其值为(this + val)的 Biglnteger
BigInteger subtract(BigInteger val):返回其值为(this - val)的 BigInteger
BigInteger multiply(BigInteger val):返回其值为(this * val)的 BigInteger
BigInteger divide(BigInteger val):返回其值为(this / val)的 BigInteger。整数相除只保留整数部分。
BigInteger remainder(BigInteger val):返回其值为(this % val)的 BigInteger。
Biglnteger[]divideAndRemainder(BigInteger val):返回包含(this / val)后跟(this % val)的两个 BigInteger的数组。
BigInteger pow(int exponent):返回其值为()的 BigInteger。
BigDecimal类
一般的Float类和Double类可以用来做科学计算或工程计算,但在商业计算中要求数字精度比较高,故用到java.math.BigDecimal类。
BigDecimal类支持不可变的、任意精度的有符号十进制定点数。
构造器
public BigDecimal(double val)public BigDecimal(String val)
常用方法
public BigDecimal add(BigDecimal augend)
public BigDecimal subtract(BigDecimal subtrahend)
public BigDecimal multiply(BigDecimal multiplicand)
public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
枚举与注解
枚举类的使用
枚举的定义:当一个类中的对象是有限个,并且确定的 时候我们称其为枚举类
当需要定义一组常量的时候,强烈建议使用枚举类
枚举类的使用:
枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类
当我们需要定义一组常量的时候,强烈建议使用枚举类
如何定义枚举类
方式一:jdk5.0之前,自定义枚举类
方式二:jdk5.0之后,可以使用enum关键字定义枚举类
方式一:
1 | public class EnumTest { |
方式二:
1 | public class EnumTest1 { |
Enum类的主要方法
values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:lllegalArgumentException。
toString():返回当前枚举类对象常量的名称
1 | public static void main(String[] args) { |
使用enum关键字定义的枚举类实现接口的情况
- 情况一:实现接口,在enum类中实现抽象方法
- 情况二:让枚举类的对象分别实现接口中的抽象方法
注解(Annotation)
Annotation其实就是代码里的特殊标记 ,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过使用Annotation,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。
Annotation可以像修饰符一样被使用,可用于修饰包,类,构造器,方法,成员变量,参数,局部变量的声明,这些信息被保存在Annotation的“name=value”对中。
在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。
示例一:生成文档相关注解
@author 表明开发该类的作者
@version 表明该类模块的版本
@see 参考转向也就是相关主题
@since 从那个版本开始增加的
@param 对方法中参数的说明
@return 方法的返回值
@exception 对方法可能抛出的异常进行说明,如果方法中没有使用throws显示抛出异常,就不能写
示例二:在编译时进行格式检查JDK内置的三个基本注解
@Override:限定重写父类方法,该注解只能用于方法
@Deprecated:用于表示所修饰的元素已经过时。通常是因为所修饰的结构危险,或存在更好的选择
@SuppressWarnings:抑制编译器警告
3.如何自定义注解:参照@Suppresswarnings定义
-
注解声明为:@interface
-
内部定义成员,通常使用value表示
-
可以指定成员的默认值,使用default定义
-
如果自定义注解没有成员,表明是一个标识作用。
-
如果注解有成员,在使用注解时,需要指明成员的值。
- 自定义注解必须配上注解的信息流程(使用反射)才有意义
-
自定义注解通常都会指明两个元注解:Retention, Target
1
2
3public MyAnnotation {
String value() default "hello";
}
JDK中的元注解:
-
JDK中的元Annotation 用于修饰 其他Annotation定义
-
JDK5.0提供了4个标准的meta-annotation类型,分别是:
Retention :指定修饰Annotation的声明周期
SOURCE
CLASS(默认行为)
RUNTIME:只有声明为RUNTIME声明周期的注解,才能通过反射获取。
Target :用于指定被修饰的Annotation能用于那些程序元素
Documented:表示所修饰的注解在被javadoc解析时保留下来
Inherited:被他修饰的Annotation注解将具有继承性
-
通过反射获取注解信息
JDK 8 中注解的新特性:可重复注解,类型注解
可重复注解:
MyAnnotation上声明@Repeatable,成员值为MyAnnotations.calss
MyAnnotation的Target和Retention等元注解与MyAnnotations相同
类型注解:
ElementType.TYPE_PARAMETER 表示该注解能写在类型变量声明语句中(如:泛型声明)
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中
集合
出现的背景
一方面,面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。另一方面,使用Array存储对象方面具有一些弊端 ,而Java集合就像一种容器,可以动态 地把多个对象的引用放入容器 中。
数组在内存存储方面的特点:
数组初始化以后,长度就确定了。
数组声明的类型,就决定了进行元素初始化时的类型
数组在存储数据方面的弊端:
数组初始化以后,长度就不可变了,不便于扩展
数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。同时无法直接获取存储元素的个数
数组存储的数据是有序的、可以重复的。---->存储数据的特点单一
Java集合类可以用于存储数量不等的多个对象,还可以保存具有映射关系的关联数组。
1 | /** |
Collection
add1 |
|
Collection中其他的方法的测定
1 | /* |
1 |
|
1 |
|
Iterator的使用
1 | /* |
foreach
jdk5.0新增了foreach循环(增强for循环),用于遍历集合,数组
1 | public void test1(){ |
List接口
List接口:存储有序的,可重复的数据。 —>“动态数组”
-
ArrayList
-
LinkedList
-
Vector
ArrayList,LinkedList,Vector三者的异同
相同点:三个类都实现了List的接口,存储数据的特点相同:存储有序的,可重复的数据
不同点:
- ArrayList:作为list接口的主要实现类。线程不安全的,效率高, 底层使用Object[] elementData存储(数组)
- LinkedList:对于频繁使用插入和删除操作的集合,此类比使用ArrayList效率高,底层使用双向链表存储
- Vector:作为list接口的古老实现类;线程安全的,效率低,底层使用Object[] elementData存储(数组)
1 | /** |
常用方法:
- 增:add(Object obj)
- 删:remove(int index) / remove(Object obj)
- 改:set(int index, Object ele)
- 查:get(int index)
- 插:add(int index, Object obj)
- 长度:size()
- 遍历:①Iterator迭代器方式 ②增强for循环, ③普通循环
set接口
set中没有额外定义的新的方法,使用的都是Collection中声明的过的方法
要求:
向Set中添加数据,其所在类一定要重写equals和hashCode这两个方法
重写hashCode() 和equals() 尽可能的保持一致:相同的对象,必须有相同的散列码
set接口:存储无序的,不可重复的数据
- hashSet:作为set接口中的主要实现类,线程不安全;可以存储null值
- LinkedHashSet:作为hashSet的子类;在遍历数据的时候,可以按照添加的顺序遍历
- TreeSet:可以按照添加对象的指定属性,进行排序
hashSet
一:Set:存储无序的,不可重复的数据,以Hashset为例说明
无序性 :不等于随机性, 存储的数据,在底层数组中并非按照数组索引的顺序添加的,而是根据数据的哈希值进行添加的
不可重复性:保证添加的元素按照equals()进行判断的时候,不能返回true,即:相同的元素只能有一个
二:添加元素的过程,以HashSet为例
我们向HashSet中添加元素a,首先调用元素a的HashSet方法获取hashCode()方法,计算元素a的hash值此Hash值通过某种计算方法,算出在HashSet底层数组中的存放位置(即:索引位置),判断此数组位置上是否有其他元素:
如果此位置上没有其他元素的话,则直接插入 -----情况一
如果此位置上有元素b(或以链表形式存放的多个元素),则比较元素a和b的hash值是否相同
如果不相同的话则直接插入,
如果相同的话,则需要调用元素a的equals方法:
equals返回false ,则a可以插入 ---- 情况二
equals返回true,则a不可以插入 ---- 情况三
对于添加成功的情况二和三;元素a以链表的形式在指定位置的索引上插入。
jdk7:元素a放在当前数组中,指向原来的数据
jdk8:原来的元素位置不变,指向元素a
总结:七上八下
HashSet底层:数组 + 链表的结构
1 |
|
LinkHashSet的使用
1 | // LinkHashSet的使用 |
TreeSet的使用
1 | /* |
测试:在List内去除重复的数字值,要求尽量简单
使用HashSet类的特性:不可重复性,来进行筛选,如果我们筛选的目标是自定义类的话,我们需要在自定义类的下面重写HashCode 和 equals方法
1 | public static List duplicateList(List list) { |
Set结构掌握:
1 | public void test3({ |
Map
DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
MAXIMUM_CAPACITY : HashMap的最大支持容量,
DEFAULT_LOAD_FACTOR: HashMap的默认加载因子
TREEIFY_THRESHOLD: Bucket中链表长度大于该默认值,转化为红黑树
UNTREEIFY_THRESHOLD: Bucket中红黑树存储的Node小于该默认值,转化为链表
MIN_TREEIFY_CAPACITY: 桶中的Node被树化时最小的hash表容量。(当桶中Node的数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应执行resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。)
table:存储元素的数组,总是2的n次幂
entrySet:存储具体元素的集
size: HashMap中存储的键值对的数量
modCount: HashMap扩容和结构改变的次数。
threshold:扩容的临界值,=容量*填充因子
loadFactor:填充因子
1 | /** |
Treemap测试类
1 | // 向TreeMap中添加key - value, 要求key一定是同一个类创建的对象 |
properties测试
1 | public static void main(String[] args) throws IOException { |
Collections测试
1 | /** |
面试题:负载因子值的大小,对HashMap有什么影响
负载因子的大小决定了HashMap的数据密度。
负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长,造成查询或插入时的比较次数增多,性能会下降。
负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性能会更高。但是会浪费一定的内容空间。而且经常扩容也会影响性能,建议初始化预设大一点的空间。
按照其他语言的参考及研究经验,会考虑将负载因子设置为0.7~0.75,此时平均检索长度接近于常数。
集合掌握要求
层次一:选择合适的集合类去实现数据的保存,调用其内部方法
层次二:不同的集合底层数据机构为何?如何实现数据操作的:增删改查等
泛型
概念
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
在没有泛型之前:
1 | public void test(){ |
泛型的使用
-
jdk5.0新增特性
-
在集合中使用泛型:
总结:- 集合接口或者集合类在jdk5.0时都修改为带泛型的结构
- 在实例化集合类的时候,可以指明具体的集合泛型
- 指明完之后,在集合类或者接口中凡是定义类或者接口的时候,内部结构(比如:方法, 构造器,属性)
比如:add(E e) -----> 实例化之后:add(Integer e) - 注意点:泛型的类型必须是类,不能是基本数据类型,需要用到基本数据类型的位置,拿包装类替换
- 如果实例化的时候没有指明泛型的类型,默认类型为java.lang.Object类型
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public void test(){
// 使用泛型之前的情况
ArrayList list = new ArrayList();
// 存放学生成绩
list.add(82);
list.add(92);
list.add(85);
list.add(96);
// 问题一:类型不安全, 可能存储一些非成绩的对象
// list.add("Tom");
for (Object obj : list){
// 问题二:强转时会报错:ClassCastException类型转换异常
int stuScore = (Integer) obj;
System.out.println(stuScore);
}
}
public void test2(){
// 集合中使用泛型的情况
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(86);
list.add(96);
list.add(75);
list.add(89);
// 如果使用泛型的话,在我们添加的非法类型的时候就会直接报错
// 编译时就是进行数据类型检查,保证数据的安全
// list.add("AAA");
// 方式一
//for(Integer score : list){
// int stuScore = (int) score;
// System.out.println(stuScore);
//}
// 方式二:
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
int stuScore = (int) iterator.next();
System.out.println(stuScore);
}
}
// 在集合中泛型使用情况
public void test3(){
Map<String, Double> map = new HashMap<String, Double>();
map.put("Tom", 88.0);
map.put("Jerry", 98.0);
map.put("LiHua", 90.0);
// 泛型的嵌套
Set<Map.Entry<String, Double>> entry = map.entrySet();
Iterator<Map.Entry<String, Double>> iterator = entry.iterator();
while(iterator.hasNext()){
Map.Entry<String, Double> next = iterator.next();
String key = next.getKey();
Double value = next.getValue();
System.out.println(key + "----" + value);
}
}-
泛型在继承方面的使用
虽然类A是类B的父类,但是G 和 G二者不具备子父类关系,二者是并列关系
-
通配符的使用:
通配符: ?
类A是类B的父类, G 和 G 是没有关系的,二者共同的父类是:G<?>
使用通配符定义的变量,不能够向内部添加除了null意外的任何数据
允许读取数据,读取数据的类型是Object
-
有限制条件的通配符的使用
? extends Person:
G<? extends A> 可以作为G 和 G的父类, 其中B是A的子类
? super Person:
G<? extends B> 可以作为G 和 G 的父类的, 其中B是A的父类
1 |
|
泛型类,泛型接口,泛型方法
泛型类,泛型接口
-
泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
-
泛型类的构造器如下: public GenericClass(){}。而下面是错误的: public GenericClass
(){} -
实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
-
泛型不同的引用不能相互赋值。
尽管在编译时ArrayList< String >和ArrayList< Integer >是两种类型,但是,在运行时只有一个ArrayList被加载到JVM中。
1
2
3
4ArrayList<String> list1 = null;
ArrayList<Integer> list2 = null;
// 泛型不同的引用不能相互赋值
list1 = list2; // 这样是错误的 -
泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。
经验:泛型要使用一路都用。要不用,一路都不要用。
-
如果泛型类是一个接口或抽象类,则不可创建泛型类的对象。
-
jdk1.7,泛型的简化操作:ArrayList
flist = new ArrayList<>(); -
泛型的指定中不能使用基本数据类型,可以使用包装类替换。
-
在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。
-
异常类不能是泛型的
-
不能使用new E]。但是可以:E]elements =(E)new Object[capacity]:
参考: ArrayList源码中声明:Object[]elementData,而非泛型参数类型数组。
-
父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
子类不保留父类的泛型:按需实现
没有类型擦除
具体类型
子类保留父类的泛型:泛型子类
全部保留
部分保留
1 | class Fathre<T1, T2> { |
结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加 自己的泛型
泛型方法
实例:
泛型方法:在方法中出现了泛型结构,泛型参数和类的泛型参数没有关系。
换句话说,泛型方法所属类是不是泛型都没有关系
泛型方法:可以声明为静态的,原因,泛型参数是在调用方法的时候确定的,并非在实例化类的时候确定
1 | <T> T[] toArray(T[] a); |
IO流原理及流的分类
File类的使用
- File类的一个对象,代表一个文件或者一个文件目录(文件夹)
- File类生命在Java.io包下
- File类中涉及到文件或者文件目录的创建,删除,重命名,修改时间,文件大小等方法,并未涉及到文件读写内容的的操作,如果需要读取或者写入文件内容的操作,则必须使用IO流来操作
- 后续FIle类对象常会作为参数传递到流的构造器中,指明读取或写入的“终点”
常用构造器
-
如何创建File类的实例
File(String filePath)
File(String parentPath, String childPath)
File(File parentFile, String childPath) -
相对路径:相较于某个路径下,指明的路径。
绝对路径:包含盘符在内的文件或文件目录的路径说明:
IDEA中:如果在开发中使用的是单元测试方法,相对路径为当前的Module下。
如果使用的main方法的话,相对路径为当前的工程下
-
路径分隔符
windows:\
unix: /
1 |
|
常用方法
获取功能:- public string getAbsolutePath():获取绝对路径public string getPath() :获取路径
- public string getName() :获取名称
- public string getParent():获取上层文件目录路径。若无,返回nuLl
- public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
- public long lastModified():获取最后一次的修改时间,毫秒值
1 |
|
- public string[ ] list():获取指定目录下的所有文件或者文件目录的名称数组
- public File[] listFiles():获取指定目录下的所有文件或者文件目录的File数组
1 |
|
-
public boolean renameTo(File dest)
比如:file1.renameTo(file2)为例要想保证返回ture。需要file1在内存中是存在的,file2在内存中不存在
将file1文件目标改为file2的文件目标
1
2
3
4
5
6
public void test2(){
File file = new File("E:\\JavaProject\\IOModel\\hi.txt");
File file2 = new File("C:\\Users\\Mr.zhang\\Desktop\\hello.txt");
System.out.println(file.renameTo(file2));
}
-
public boolean isDirectory():判断是否是文件目录
-
public boolean isFile():判断是否是文件
-
public boolean exists():判断是否存在
-
public boolean canRead():判断是否可读
-
public boolean canWrite():判断是否可写
-
public boolean isHidden():判断是否隐藏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void test3(){
File file = new File("hi.txt");
// 当文件存在的使用有以下几种情况, 当文件不存在的时候,返回值都为false
System.out.println(file.isDirectory()); // false
System.out.println(file.isFile()); // true
System.out.println(file.exists()); // true
System.out.println(file.canRead()); // true
System.out.println(file.canWrite()); // ture
System.out.println(file.isHidden()); // false
// 当文件为文件目录时:
file = new File("E:\\JavaProject\\IOModel");
System.out.println(file.isDirectory()); // true
System.out.println(file.isFile()); // false
System.out.println(file.exists()); // true
System.out.println(file.canRead()); // true
System.out.println(file.canWrite()); // ture
System.out.println(file.isHidden()); // false
}
- public boolean createNewFile():创建文件。若文件存在,则不创建,返回false
- public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
- public boolean mkdirs():创建文件目录。如果上层文件目录不存在,一并创建
注意事项:如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目路径下。
File类删除功能-
public boolean delete():删除文件或者文件夹删除注意事项:
Java中的删除不走回收站。
要删除一个文件目录,请注意该文件目录内不能包含 文件或者文件目录
IO流
IO是Input/Output的缩写,I/O技术是非常实用的技术,用于处理设备之间的数据传输 。如读/写文件,网络通讯等。
Java程序中,对于数据的输入/输出操作以“流(stream)” 的方式进行。
java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法 输入或输出数据。
-
流的分类方式:
按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)
按数据流的流向不同分为:输入流,输出流
按流的角色的不同分为:节点流,处理流
抽象基类 字节流 字符流 输入流 InputStream Reader 输出流 OutputStream Writer
IO流体系:
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
访问字符串 | StringReader | StringWriter | ||
缓冲流 | BufferedInputStream | BufferedInputStream | BufferedReader | BufferedWriter |
转换流 | InputStreamReader | OutputStreamWriter | ||
对象流 | ObjectInputStream | ObjectOutputStream | ||
FilterInputStream | FilterOutputStream | FilterReader | FilterReader | |
打印流 | PrintStream | PrintWriter | ||
推回输入流 | PushbackInputStream | PushbackReader | ||
特殊流 | DataInputStream | DataOutputStream |
字符流的读入
read 方法,返回值是读入字符的ASCII码
1 |
|
流读入的说明:
- read()的理解,返回读入的一个字符,如果达到文件末尾,返回-1
- 异常的处理:为了保证流资源可以执行关闭操作,需要使用try-catch-finally处理
- 读入文件操作一定要存在,否则会报FileNotFoundException
对read()操作升级,使用read的重载方法
read(char[]) 返回值是读入的几个字符
1 | public void testFileReader() throws IOException { |
字符流的输出
说明:
-
输入操作,对应的file可以是不存在的,并不会报异常
- File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
- File对应的硬盘中的文件如果存在:
- 如果流使用的构造器是:Filewriter(file,false) / FiLewriter(file):对原有的文件进行覆盖
- 如果流使用的构造器是:Filewriter(file,true),不会对原有的文件进行覆盖,而是在原有的文件基础追加内容
字符型总测试1
2
3
4
5
6
7
8
9
10
11
12
13public void testFileWriter() Throws IOException{
// 1. 提供File类的对象,指明写出到的文件:
File file = new File("hello.txt");
// 2.提供FileWriter的对象,用于数据的写出, 使用false参数对原数据进行覆盖
FileWriter fw = new FileWriter(file, false);
// 3.写出的操作
fw.writer("I have a dream!\n");
fw.writer("you need to have a dream!");
// 4. 流的资源的关闭
fw.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
31
32
33
34
35
36public void testFileReaderWriter(){
FileReader fr = null;
FileWriter fw = null;
try {
// 1.创建File类的对象,指明写入和写出的文件
File srcFile = new File("hello.txt");
File destFile = new File("hello1.txt");
// 创建输入流和输出流
fr = new FileReader(srcFile);
fw = new FileWriter(destFile);
// 数据的读入和写出操作
char[] cbuf = new char[5];
int len = 0;
while((len = fr.read(cbuf))!=-1){
String str = new String(cbuf, 0, len);
fw.write(str);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 流的关闭
try {
fr.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
fw.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
字节流的读入和输出
1 | public void testPicture() { |
缓冲流的使用
四种缓冲流:
- BufferedInputStream
- BufferedOutputStream
- BufferedReader
- BufferedWirter
缓冲流的作用:提高流的读取和写入速度
1 | public void BufferTest() { |
转换流
提供了字符流和字节流之间的转换
- Java提供了两个转换流
InputStreamReader:将InputStream转换为Reader:将一个字节的输入流转换为字符的输入流
OutputStreamWriter:将Writer转换为OutputStream:将一个字符的输出流转换成字节的输出流
-
作用:提供字节流和字符流之间的转换
-
解码:字节,字节数组 —> 字符数组,字符串
编码:字符数组,字符串 —> 字节,字节数组
1 | public void InputStreamReaderTest() { |
-
字符集:
ASCIl:美国标准信息交换码。
用一个字节的7位可以表示。
ISO8859-1:拉丁码表。欧洲码表
用一个字节的8位表示。
GB2312:中国的中文编码表。最多两个字节编码所有字符GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。
UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。
标准输入输出流
System.in和System.out分别代表了系统标准的输入和输出设备
默认输入设备是:键盘,输出设备是:显示器
System.in的类型是InputStream
System.out的类型是PrintStream,其是OutputStream的子类FilterOutputStream 的子类
重定向:通过System类的setIn,setOut方法对默认设备进行改变。默认是输出到控制台,和从控制台中获取,如果我们使用setIn或者setOut的话,可以改变读入和写出的位置
public static void setIn(InputStream in)
public static void setOut(PrintStream out)
练习:将输入的字符都转换成大写的,当输入e或者exit的时候退出
1 | public static void main(String[] args) throws IOException { |
打印流
实现将基本数据类型的数据格式转化为字符串输出
打印流: PrintStream和PrintWriter
提供了一系列重载的print()和println()方法,用于多种数据类型的输出
PrintStream和PrintWriter的输出不会抛出IOException异常
PrintStream和PrintWVriter有自动flush功能
PrintStream打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用PrintWriter类。
System.out返回的是PrintStream的实例
1 | public void test2() { |
数据流
为了方便地操作Java语言的基本数据类型和String的数据,可以使用数据流。
作用:用于读取或者写出基本数据类型的变量或字符串
数据流有两个类:(用于读取和写出基本数据类型、String类的数据)
DatalnputStream和 DataOutputStream
分别“套接”在InputStream和 OutputStream子类的流上
DatalnputStream中的方法
boolean readBoolean() byte readByte()
char readChar() float readFloat()
double readDouble() short readShort()
long readLong() int readlnt()
String readUTF() void readFully(byte[] b)
DataOutputStream中的方法:
将上述的方法的read改为相应的write即可。
1 |
|
对象流
ObjectInputStream和OjbectOutputSteam
用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
序列化 :用ObjectOutputStream类保存基木类型数据或对象的机制 反序列化 :用ObjectInputStream类读取基木类型数据或对象的机制ObjectOutputStream和IObjectInputStream不能序列化static和ltransient修饰的成员变量
对象的序列化:-
对象的序列化机制:
对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象|
-
序列化的好处:序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据使其在保存和传输时可被还原
-
序列化是 RMI (Remote Method Invoke-远程方法调用)过程的参数和返回值都必须实现的机制,而 RMI是JavaEE的基础。因此序列化机制是JavaEE平台的基础
-
如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,会抛出NotSerializableException异常
Serializable
Externalizable -
凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:
private static final long serialVersionUID;
serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。
如果类没有显示定义这个静态变量[它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,seriaVersionUID可能发生变化。故建议,显式声明。
-
简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的
serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)
-
ObjectOutputStream 和ObjectInputStream 不能序列化static和transient修饰的成员变量
1 | /* |
随机存取文件流
RandomAccessFile声明在java.io包下,但直接继承于java.lang.Object类。并且它实现了Datalnput、DataOutput这两个接口,也就意味着这个类既可以读也可以写。
RandomAccessFile类支持“随机访问”的方式,程序可以直接跳到文件的任意地方来读、写文件
支持只访问文件的部分内容
可以向己存在的文件后追加内容
RandomAccessFile对象包含一个记录指针,用以标示当前读写处的位置。
RandomAccessFile类对象可以自由移动记录指针:
long getFilePointer():获取文件记录指针的当前位置
void seek(long pos):将文件记录指针定位到pos位置
创建RandomAccessFile类实例需要指定一个mode参数,该参数指定RandomAccessFile的访问模式:
r:以只读方式打开
rw:打开以便读取和写入
rwd:打开以便读取和写入;同步文件内容的更新
rws:打开以便读取和写入;同步文件内容和元数据的更新
如果模式为只读r。则不会创建文件,而是会去读取一个已经存在的文件,如果读取的文件不存在则会出现异常。如果模式为rw读写。如果文件不存在则会去创建文件,如果存在则不会创建。
1 | /* |
NIO.2中Path, Paths,Files类的使用
Java NIO (New lO,Non-Blocking lO)是从Java 1.4版本开始引入的一套新的IOAPI,可以替代标准的Java lO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的)、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。
JavaAPI中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO。
java.nio.channels.Channel
FileChannel:处理本地文件
SocketChannel:TCP网络编程的客户端的Channel
ServerSocketChannel:TCP网络编程的服务器端的Channel
DatagramChannel:UDP网络编程中发送端和接收端的Channel
早期的Java只提供了一个File类来访问文件系统,但File类的功能比较有限,所提供的方法性能也不高。而且,大多数方法在出错时仅返回失败,并不会提供异常信息。
NIO.2为了弥补这种不足,引入了Path接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置。Path可以看成是File类的升级版本 ,实际引用的资源也可以不存在。
以前的IO操作符是这样写的:
import java.io.File;
File file = new File(“index.html”);
但在Java7中,我们可以这样写:
import java.nio.file.Path;
import java.nio.file.Paths;
Path path = Patts.get(“index.html”);
同时,NIO.2在java.nio.file包下还提供了Files、Paths工具类,Files包含了大量静态的工具方法来操作文件; Paths则包含了两个返回Path的静态工厂方法。
Paths类提供的静态get()方法用来获取Path 对象:
static Path get(String first, String … more):用于将多个字符串串连成路径
static Path get(URI uri):返回指定uri对应的Path路径
网络编程
-
计算机网络
把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源。
-
网络编程的目的:
直接或者间接的通过网络协议与其他计算机实现数据交换,进行通讯
-
网络编程的两个主要目的:
如何精确的定位网络上一台或者多台主机,定位主机上特定的应用
找到主机后如何可靠高效的进行数据的传输
网络通讯中的两个要素
IP和端口号
IP:每一个主机在网络上都有唯一的IP地址(InetAddress)
- 唯一的标识Internet上的计算机(通信实体)
- 本地回环地址(hostAddress): 127.0.0.1 主机名(hostName): localhost
- IP地址分类方式1:IPV4和 IPV6
IPV4:4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。2011年初已经用尽。以点分十进制表示,如192.168.0.1
IPV6:128位(16个字节),写成8个无符号整数,每个整数用四个十六进制位表示,数之间用冒号(:)分开,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984
- IP地址分类方式2:公网地址(万维网使用)和私有地址(局域网使用)。192.168开头的就是私有址址,范围即为192.168.0.0–192.168.255.255,专门为组织机构内部使用
- 特点:不易记忆
端口号:标识正在计算机上运行的进行(程序)
不同的进程有不同的端口号
被规定为一个16位的整数0~65535。
端口分类:
公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23)
注册端口:1024~49151。分配给用户进程或应用程序。(如: Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。
动态/私有端口:49152~65535。
端口号与IP地址组合得出一个网络套接字:Socket
1 | /* |
网络通讯协议
网络通信协议
计算机网络中实现通信必须有一些约定,即通信协议,对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准。
问题:网络协议太复杂
计算机网络通信涉及内容很多,比如指定源地址和目标地址,加密解密,压缩解压缩,差错控制,流量控制,路由控制,如何实现如此复杂的网络协议呢
通信协议分层的思想
在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。最常用的复合方式是层次方式,即同层间可以通信、上一层可以调用下一层,而与再下一层不发生关系 。各层互不影响,利于系统的开发和扩展。
TCP和UDPTCP协议:
使用TCP协议前,须先建立TCP连接,形成传输数据通道
传输前,采用“三次握手”方式,点对点通信,是可靠的
TCP协议进行通信的两个应用进程:客户端、服务端。
在连接中可进行大数据量的传输
传输完毕,需释放已建立的连接,效率低(“四次挥手”)
UDP协议:
将数据、源、目的封装成数据包,不需要建立连接
每个数据报的大小限制在64K内
发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
可以广播发送
发送数据结束时无需释放资源,开销小,速度快
TCP网络编程
-
客户端发信息给服务端,服务端将数据显示在控制台上
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public void client(){
Socket socket = null;
OutputStream os = null;
try {
// 1. 创建Socket对象,指明服务器端的IP和端口号
InetAddress inet = InetAddress.getByName("127.0.01");
socket = new Socket(inet, 8899);
// 2. 获取一个输出流,用于输出数据
os = socket.getOutputStream();
// 3. 写出数据
// 因为os写入的需要时字节流文件,所以我们需要使用getBytes将字符串转换成字节流
os.write("你好,我是客户端".getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (os != null){
try {
os.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (socket != null){
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
// 服务端
public void server(){
Socket socket = null;
InputStream inputStream = null;
try {
// 1. 创建一个服务器端的ServerSocket, 指明自己的端口号
ServerSocket serverSocket = new ServerSocket(8899);
// 2. 调用accept() 表示接受来在客户端的socket
socket = serverSocket.accept();
// 3. 获取输入流
inputStream = socket.getInputStream();
// 4. 从输入流中读取数据
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[20];
int len;
while ((len = inputStream.read(buffer)) != -1){
baos.write(buffer, 0, len);
}
System.out.println(baos.toString());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (inputStream != null){
try {
inputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (socket != null){
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
} -
客户端发送文件给服务器端,服务器端将文件保存在本地
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
public void client() {
Socket socket = null;
OutputStream os = null;
FileInputStream fis = null;
try {
// 1. 创建Socket对象
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
socket = new Socket(inetAddress, 8899);
// 2. 获取输出流, 向服务器端输出
os = socket.getOutputStream();
// 3. 获取一个输入流,从内存中读取文件
fis = new FileInputStream("wallhaven-72vwpo.jpg");
// 4. 向服务器端写数据
byte[] buffer = new byte[1024];
int len;
while((len = fis.read(buffer)) != -1){
os.write(buffer, 0, len);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (os != null){
try {
os.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (fis != null){
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (socket != null){
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
public void server(){
ServerSocket ss = null;
Socket socket = null;
InputStream inputStream = null;
FileOutputStream fos = null;
try {
// 1. 创建一个服务器端的对象
ss = new ServerSocket(8899);
// 2. 读取客户端发来的数据
socket = ss.accept();
// 3. 创建一个输入流
inputStream = socket.getInputStream();
fos = new FileOutputStream("picture.jpg");
// 4. 将客户端发来的图片写入到本地
byte[] buffer = new byte[1024];
int len;
while((len = inputStream.read(buffer)) != -1){
fos.write(buffer, 0, len);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fos != null){
try {
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (inputStream != null){
try {
inputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (socket != null){
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (ss != null){
try {
ss.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
} -
从客户端发送文件到服务端,服务端保存到本地。并返回发送成功到服务端。并关闭相应的链接
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
public void client() {
Socket socket = null;
OutputStream os = null;
FileInputStream fis = null;
ByteArrayOutputStream baos = null;
try {
// 1. 创建Socket对象
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
socket = new Socket(inetAddress, 8899);
// 2. 获取输出流, 向服务器端输出
os = socket.getOutputStream();
// 3. 获取一个输入流,从内存中读取文件
fis = new FileInputStream("wallhaven-72vwpo.jpg");
// 4. 向服务器端写数据
byte[] buffer = new byte[1024];
int len;
while((len = fis.read(buffer)) != -1){
os.write(buffer, 0, len);
}
// 停止传送数据
socket.shutdownOutput();
// 接受来自服务器端的反馈
InputStream inputStream = socket.getInputStream();
baos = new ByteArrayOutputStream();
while((len = inputStream.read(buffer)) != -1){
baos.write(buffer, 0, len);
}
System.out.println("好好好");
System.out.println(baos.toString());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (os != null){
try {
os.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (fis != null){
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (socket != null){
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (baos != null){
try {
baos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
public void server(){
ServerSocket ss = null;
Socket socket = null;
InputStream inputStream = null;
FileOutputStream fos = null;
OutputStream outputStream = null;
try {
// 1. 创建一个服务器端的对象
ss = new ServerSocket(8899);
// 2. 读取客户端发来的数据
socket = ss.accept();
// 3. 创建一个输入流
inputStream = socket.getInputStream();
fos = new FileOutputStream("picture.jpg");
// 4. 将客户端发来的图片写入到本地
byte[] buffer = new byte[1024];
int len;
while((len = inputStream.read(buffer)) != -1){
fos.write(buffer, 0, len);
}
// 给予客户端反馈
outputStream = socket.getOutputStream();
outputStream.write("照片已收到".getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fos != null){
try {
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (inputStream != null){
try {
inputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (socket != null){
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (ss != null){
try {
ss.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (outputStream != null){
try {
outputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
UDP网络编程
类 DatagramSocket和 DatagramPacket实现了基于UDP协议网络程序。
UDP数据报通过数据报套接字DatagramSocket发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
DatagramPacket对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号。
UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接。如同发快递包裹一样。
1 |
|
URL编程
URL(Uniform Resource Locator):统一资源定位符,它表示Internet上某资源的地址。
它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。
通过URL我们可以访问 Internet上的各种网络资源,比如最常见的www,ftp站点。浏览器通过解析给定的URL可以在网络上查找相应的文件或其他资源。
URL的基本结构由5部分组成:
<传输协议>:/<主机名>:<端口号><文件名>#片段名?参数列表>
例如:
http;😕/192.168.1.100:8080/helloworld/index.jsp#a?username=shkstart&password=123>#片段名:即锚点,例如看小说,直接定位到章节
参数列表格式:参数名=参数值&参数名=参数值…
1 | public static void main(String[] args) { |
反射机制
概述
Reflection(反射)被视为动态语言的关键,反射机制允许程序在执行期间,借助于Reflection API取得任何类的内部信息,并能直接操作,任意对象内部属性和方法。
加载完类之后,在堆内存的方法区中就产生了一个class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
正常方式:引入需要的包类的名称 ----> 通过new实例化 -----> 取得实例化对象
反射方式:实例化对象 ----> getClass() 方法 -----> 得到完整的包类的名称
- 动态语言
是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的发化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。 .
主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。
-
静态语言
与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。
-
Java不是动态语言,但Java可以称之为“准动态语言 ”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。
Java的动态性让编程的时候更加灵活!
Java反射机制提供的功能
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时获取泛型信息
在运行时调用任意一个对象的成员变量和方法
在运行时处理注解
生成动态代理
1 | // 反射之前对于Person类的操作 |
1 | /* |
类的加载
ClassLoader:类的加载器,作用是把类(class)加载到内存中的
Bootstap ClassLoader:引导加载器,是JVM自带的加载器,负责java平台核心库,用来装载核心类库,该加载类无法直接获取
Extension ClassLoader:扩展类加载器:负责jre/lib/ext目录下的jar包,或指定目录下的jar包装入工作库,JDK1.9以后的版本中ExtClassLoader 变成PlatFormClassLoader
System ClassLoader: 系统类加载器:负责java -classpath 或指定目录下的类与jar包装入工作,是最常用的加载器
1 |
|
创建运行时类对象
1 |
|
获取运行时类的完整结构
属性:
1 |
|
方法:
1 |
|
其他类
1 | /* |
调用运行时类的指定结构
1 |
|
动态代理
代理设计模式的原理:
使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
之前的代理机制的操作,属于静态代理,特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。最好可以通过一个代理类完成全部的代理功能。
静态代理功能
1 | interface ClothFactory { |
动态代理
1 | interface Human { |
Java8新特性
- 速度更快
- 代码更少(增加了新的语法:lambda表达式)
- 强大的Stream API
- 便于并行
- 最大化减少空指针异常 Optional
- Nashorn 引擎,允许在JVM上运行JS应用
Lambda表达式
Lambda是一个匿名函数 ,我们可以把Lambda表达式理解为是一段可以传递的代码 (将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
1 | /** |
函数式接口
只包含一个抽象方法的接口,称为函数式接口。你可以通过Lambda表达式来创建该接口的对象。(若Lambda表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
我们可以在一个接口上使用@Functionallnterface ,这样做可以检查它是否是一个函数式接口。同时 javadoc也会包含一条声明,说明这个接口是一个函数式接口。
在java.util.function包下定义了Java8的丰富的函数式接口函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer 消费型接口 |
T | void | 对类型为T的对象应用操作,包含方法, void accept(T t) |
Supplier 供给型接口 |
无 | T | 返回值类型为T的对象,包含方法,T.get() |
Function<T, R> 函数型接口 |
T | R | 对于类型为T的对象,应用操作,并返回结果。 结果是R型对象,包含方法:R apply(T t) |
Predicate 断定性接口 |
T | boolean | 确定类型为T的对象,是否满足某约束,并返回 Boolean值。包含方法:boolean test(T t) |
方法引用与构造器引用
方法引用:
1 | public class LambdaTest2 { |
构造器引用:
1 | /** |
StreamAPI
Stream API ( java.util.stream)把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
Stream是 Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream 。也可以使用Stream API来并行执行操作。简言之,Stream API提供了一种高效且易于使用的处理数据的方式。
-
Stream关注的是数据的运算,和CPU有关系
集合关注的是数据的存储,与内存有关系 -
Stream自己不会存储元素
Stream 不会改变原对象,相反它会返回一个持有结果的新Stream
Stream 操作是延迟的这意味着他们会等到需要结果的时候才执行 -
Stream 执行流程
Stream的实例化
一系列的中间操作
终止操作 -
说明:
一个中间操作链,对数据源的数据进行处理
一旦执行终止操作,就执行中间操作链,并产生结果,之后不会再被使用
StreamAPI实例化的四种方法
- 通过集合
- 通过数组
- 通过Stream的of
- 创建无限流
1 | public class StreamAPITest { |
Stream的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
-
筛选与切片
方法 描述 filter(Predicate p) 接受Lambda,从流中排除某些元素 distinct() 筛选,通过流生成元素的hashCode() 和 equals()去除重复元素 limit(long maxSize) 截断流,使其元素不超过指定数量 skip(long n) 跳过元素,返回一个扔掉了前n个元素的流,若流中的元素不足
n个,则返回一个空流,与limit互补
1 | // 1-筛选与切片 |
- 映射
方法 | 描述 |
---|---|
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个 元素上,并将其映射成一个新的元素 |
map ToDouble(ToDoubleFunction f) | 接受一个函数作为参数,该函数会被应用到每一个元素 上,产生成一个新的DoubleStream |
map ToInt(ToIntFunction f) | 接受一个函数作为参数,该函数会被应用到每个元素 上产生一个新的IntStream |
map ToLong(ToLongFunction f) | 接受一个函数作为参数,该函数会被应用到每个元素 上产生一个新的LongStream |
flatMap(Function f) | 接受一个函数作为参数,将流中的每个值都换成另 一个流,然后把所有流连接成一个流 |
1 | public class StreamAPITest1 { |
-
排序
方法 描述 sorted() 产生一个新流,其中按照自然顺序排序 sorted(Comparator com) 产生一个新流,其中按照比较器顺序排序 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void test3() {
// sorted() - 自然排序
List<Integer> list = Arrays.asList(12, 36, 25, 2, -5, 17, 32);
Stream<Integer> sorted = list.stream().sorted();
sorted.forEach(System.out::println);
// 如果一个类中没有实现Comparable接口的话,不能够使用sorted方法
// sorted(Computer com) - 定制排序
List<Person> list1 = PersonData.getPersons();
list1.stream().sorted((p1, p2) -> {
return -Integer.compare(p1.getAge(), p2.getAge());
}).forEach(System.out::println);
}
终止操作
- 匹配与查找
方法 | 描述 |
---|---|
allMatch(Predicate p) | 查找是否匹配所有元素 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
findFirst() | 返回第一个元素 |
findAny() | 返回当前流中的任意的元素 |
count() | 返回流中元素总和 |
max(Comparator c) | 返回流中的最大值 |
min(Comparator c) | 返回流中的最小值 |
forEach(Consumer c) | 内部迭代 |
1 | public void test1() { |
- 归约
方法 | 描述 |
---|---|
reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值,返回T |
reduce(BinaryOperator b) | 可以将流中的元素反复结合起来 得到一个值,返回Optional |
1 |
|
-
收集
方法 描述 collect(Collector c) 将流转换为其他形式,接收一个Collector接口的实现
用于给Stream中元素做汇总的方法
1 |
|
Optional类
尽可能的避免在开发中遇到空指针异常
Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
创建Optional类对象的方法:
Optional.of(T t)∶创建一个 Optional实例,t必须非空;
Optional.empty():创建一个空的Optional实例
Optional.ofNullable(T t): t可以为null
判断Optional容器中是否包含对象:
boolean isPresent():判断是否包含对象
void ifPresent(Consumer<? super T> consumer):如果有值,就执行Consumer
接口的实现代码,并且该值会作为参数传给它。
获取Optional容器的对象:
T get():如果调用对象包含值,返回该值,否则抛异常
T orElse(T other):如果有值则将其返回,否则返回指定的other对象。
T orElseGet(Supplier<? extends T> other):如果有值则将其返回,否则返回由
Supplier接口实现提供的对象。T orElse Throw(Supplier<? extends X>exceptionSupplier):如果有值则将其返
回,否则抛出由Supplier接口实现提供的异常。
1 | public class OptionalTest { |