/记录java基础与高级,除了较简单的内容,没有必要记录的没有记录外,其余的都记录了/
java初学者看这一篇就够了,全文 6万+ 字。
java会出现内存溢出和泄露。
JDK与JRE的关系。
a. JDK(Java Development Kit Java开发工具包)
JDK是提供给JAVA开发人员使用的,其中包含了Java的开发工具,也包含了JRE,所以安装装了JDK就不用再单独安装JRE了。其中的开发工具:编译工具(javac.exe),打包工具(jar.exe)、javadoc等。
b. JRE(Java Runtime Enviroment Java运行环境)
包括Java虚拟机(Java Virtual Machine,JVM)和Java程序所需的核心类库等,如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。
总结:JDK = JRE + 开发工具集, JRE = JVM+Java SE标准类库。 其中Java SE表示Java的标准版本(桌面和服务器),Java EE表示企业版本。Java SE(之前为J2SE)是Java EE(之前为J2EE)的子集。
字节码文件是指以.class结尾的文件。
Java程序的终端运行。
• Java程序要想在终端运行,首先要使用 javac 文件名.java (这个叫做源文件)进行编译生成 .class文件(也称为字节码文件)
• 然后使用 java 文件名.class 进行运行。
• 字节码的文件名,是代码中的类名。
文档注释
/**
文档注释
@author
@version
作用是可以共javadoc 所解析
*/
主类用public修饰
javadoc解析可以查看程序信息
使用方法:javadoc -d 文件名 -version -author HelloJava.java
java注释不可嵌套使用,可嵌套单行
Java语言的特点
完面向对象性:
两个要素:类、对象
四个特征:抽象、封装、多态、继承。
健壮性:去除了c语言中的指针 自动的垃圾回收机制 -->仍会内存溢出和泄露
JVM实现跨平台性。
java关键字和保留字
• Java保留字:goto、 const自己命名时避免使用。
Java标识符
• 由26个英文字母大小写,0-9,_或 $ 组成
• 数字不可以开头
• 不可以使用关键字和保留字
• Java中严格区分大小写,长度无限制
• 标识符不能包含空格
Java中的命名规范
• 包名:多单词组成时,所有字母都小写xxxyyyzzz
• 类名、接口名:多单词组成时,所有单词的首字母大写:XxxYyyZzz (大驼峰)
• 变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个首字母大写:xxxYyyZzz (小驼峰)
• 常量名:所有字母都大写。多单词时每个单词用下划线链接:XXX_YYY_ZZZ
Java变量的数据类型
基本数据类型
•整型:byte(1字节 = 8bit) \ short(2字节) \ int(4字节) \ long(8字节) byte范围:-128~127 long型变量要以L标明。
•浮点型:float(4字节,单精度,7位有效数字) \ double(8字节,双精度,14位有效数字) float表示的数值范围比long还要大,末尾必须要用f/F结尾。
•字符型:char(1字符,两个字节) 可以用Unicode码声明,c6 = ‘\u0043’相当于字符 c 。
•布尔型:boolean
引用数据类型
◇ 类(class)
◇ 接口(interface)
◇ 数组(array)
ASCII与Unicode的区别
ASCII码
• 一共包含128个字符,每个字符对应一个ASCII码,将字符转换为二进制需要进行的操作。
• 缺点是,不能表示所有字符,不同国家相同的编码表示的字符不一样,如130法语为6,在希伯来语中为Gimel。
• 会出现乱码问题,由于相同的二进制数字表示不同的字符,因此解码时,必须要知道编码方式。
Unicode码
• 一种编码,将世界上所有的符号都纳入其中,每个符号对应一个独一无二的编码,使用Unicode没有乱码问题。
• 缺点:Unicode只规定了符号的二进制代码,并没有规定这个二进制代码怎么存储,使其与ASCII码无法区别,
计算机无法区别三个字节表示的是一个符号还是三个符号。另外,我们知道英文字符只需要一个字节就够了,如
果Unicode规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,极大的浪费
了存储空间。
UTF-8编码
• UTF-8是在互联网上最广使用的一种Unicode编码的实现方式。
• UTF-8是一种变长的编码方式。它可以使用1-6个字节表示一个符号,根据不同的符号而变化字节长度。
• UTF-8的编码规则:
◇ 对于单字节的UTF-8编码,该字节最高位为0,其余7位用来对字符进行编码(等同于ASCII码)
◇ 对于多字节的UTF-8编码,如果编码包含n个字节,那么第一个字节的前n位为1,第n+1位为0,
该字节的剩余各位用来对字符进行编码,在第一个字节之后的所有字节,都是最高位为“10”,其余6位
用来对字符进行编码。
数据之间的运算规则
自动类型提升
byte ,short,char -> int -> long -> float -> double。
容量小的与容量大的做运算,会自动转换成容量大的。
说明:此时的容量大小指的是表示数的范围的大小。
特别的:当byte、char、short三种类型的变量做运算时,结果为int型。
强制类型转换
由容量大的类型转到容量小的类型,可能会发生损失精度。
如:int a = (long)b;
整型常量默认类型为int, 浮点类型默认为double。
char=‘’是错误的
String类型可以与基本数据类型做运算
如:char c = ‘a’; int num = 10;
String str = “hello”;
System.out.println(c + num + str); //107hello
System.out.println(c + str + num); //ahello10
System.out.println((c + num) + str); //107hello
System.out.println(str + num + c); //hello10a
各个进制
二进制以0b或0B开头,八进制必须以0开头,十六进制必须以0x开头。
s += 2不会改变数据类型,s=s+2会改变
s += 2,会自动强制类型转换,而s = s + 2可能会报错
逻辑运算符&和&&、|和||
• &操作的是boolean类型则是逻辑运算符,否则是位运算符。|也一样
• 相同点1:&与&&的运算结果相同
• 相同点2:当左边为true时,二者都会执行右边的语句。
• 不同点:当左边是false时,&会执行右边,而&&(短路与)不会执行右边。
| 与 || 类似。
☐ 开发中推荐使用短路&&和短路||。
最高效的2*8?2 << 3 或8 << 1
m = (m ^ n)^n
Scanner
Scanner sc = new Scanner(System.in);
int a = sc.nextInt();
String s = sc.next();
double d = sc.nextDouble();
• 特别的String是sc.next(),别的大部分都是sc.nextMingZi(); 除此之外,没有nextChar()。
switch-case
格式:
switch(表达式){
case 常量1:
执行语句。。。
break;
case 常量2:
执行语句。。。
break;
default:
执行语句。。。
break;
}
说明:
1. 根据switch表达式中的值,一次匹配各个case中的常量,一旦匹配成功,则进入case结构中,调用其执行语句,
当调用完执行语句之后,则仍然向下继续执行其他case结构中的执行语句,直到遇到break关键字,或此switch-case结构末尾为止结束。
2. break关键字可以用在switch表达式中,表示一旦遇到就会跳出该结构。
3. switch条件判断句只能是如下的6种数据类型之一:byte,short、char、int、枚举类型(JDK5.0新增)、String(JDK7.0新增)。
4. 特别注意boolean类型不可以做判断句。
5. case之后只能是常量,尤其注意不能是范围。
6. default,相当于if-else中的else,是可选择有无的,而且位置是灵活的,不一定是尾部,开发时一般写到尾部。
7. 如果多个case语句执行的是相同的,case语句是可以合并的。
8. 如果既可以用if-else也可以用switch则优先使用switch,因为其执行效率较高。
continue只用在循环结构中:关键字之后不可以紧跟着写语句。
break、continue结束指定标签的循环 ,加标签,然后break或者continue
出现乱码是因为编码方式不一样,改一致可以解决。
数组
一维数组:
静态初始化:
int[] ids = new int[]{1001,1002}.数组的初始化和数组元素的赋值操作同时进行。
动态初始化:
String[] names = new String[5];
注:int[] arr = {1,2,3} //自动类型推断
二维数组:
静态初始化:
int[][] arr1 = new int[][]{{1,2},{1,2}}
动态初始化:
int[][] arr1 = new int[3][4];
int[][] arr1 = new int[3][];
或者 int a[][],int [] a[];
二维数组里每一个一维数组可以变长。
Arrays工具类提供了一系列操作数组的方法。
其中Arrays.sort()底层用的快排。
Arrays.binarySearch(),二分查找。
main()方法的使用说明
main()方法作为程序的入口
main()方法是一个普通的静态方法。
main()可以作为我们与控制台交互的方式(可以传入数据,编译完,运行时传)。
>>>表示处理的时无符号的右移运算,它不把最高位看作符号位。
面向对象的三条主线
Java类及类的成员:属性、方法、构造器;代码块、内部类。
面向对象的四大特征:抽象、封装、继承性、多态性。
其他关键字:this、super、static、final、abstract、interface、pakage、import等。
面向对象与面向过程的差别
• 面向过程:强调的是功能行为,以函数为最小单位,考虑怎么做。
如:把大象放进冰箱,
1) 把冰箱门打开
2) 抬起大象,塞进冰箱
3) 把冰箱门关闭
• 面向对象:强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。
人{ 打开冰箱{冰箱.open()} 抬起(大象){} 关闭(冰箱){} }
冰箱{ open(){} ,关闭(){}}
大象{ 进冰箱(){} }
面向对象的两个要素
类:对一类事物的描述,是抽象、概念上的定义。
对象:是实际存在的该类事务的每个个体,因此也成为实例(instance)
面向对象程序设计的重点是类的设计。
类的设计,其实就是对类成员的设计。
如果一个类对应多个对象,则每个对象都拥有一套独立的属性。
类和对象的使用(面向对象思想落地的实现)
1.创建类、设计类成员
2.创建类的对象
3.通过“对象.属性” 或“对象.方法”调用对象的结构。
类的成员
属性 = 成员变量 = field = 域、字段
方法 = 成员方法 = 函数 = method
- 属性:
对应类中的成员变量
默认初始化
显式初始化
构造器中初始化
有了对象以后,可以用对象.属性等初始化
在代码块中赋值。
- 方法:要描述类应该具备的功能。对应类中的成员方法。方法中不能定义方法。
- 构造器(构造方法)
类的默认的构造器的权限和类的权限是一样的。
类如果显示定义了构造器,就不会再提供默认的构造器。
- 代码块(或初始化块)
◇ 就是初始块,用来初始化类或对象,内部可以有输出语句。
◇ 代码块如果有修饰的话,只能使用static
◇ 分类:静态代码块 VS 非静态代码块
◇ 静态代码块 (可以多个,根据定义的顺序执行,只能调用静态的属性和方法):随着类的加载而执行,并且只执行一次,初始化类的信息。
◇ 非静态代码块(属性和方法都可以调用):随着对象的创建而执行,每创建一个对象就执行一次,可以对对象的属性进行初始化。
◇ 静态代码块优先于非静态的代码块的执行。
就是大括号
{
}
5.内部类
a. Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B成为外部类。
b. 内部类的分类:成员内部类(静态的、非静态的) vs 局部内部类(方法内、代码块内、构造器内)。
成员内部类:一方面,作为外部类的成员,调用外部类的结构。
可以用static修饰。(外部类不用)
可以被四种不同的权限修饰符修饰。
另一方面,作为一个类。可以包括类的属性、方法、构造器、代码块、内部类。
可以用final修饰(表示不可以被继承了),
可以被abstract修饰。
所应该关注的问题:
如何实例化成员内部类的对象?
Person.Dog d = new Person.Dog(); //静态的
Person.Bird bird= (new Person()).new Bird();
如何在成员内部类中区分调用外部类的结构?
Person.this.name //类似
局部内部类的使用。
局部内部类使用的注意点:
在局部内部类的方法中,如果调用局部内部类所在方法中的局部变量时,需要其为final的。
封装
- 只需要用功能,不需要去关注功能怎么实现的。
- 高内聚:类的内部数据操作细节自己完成,不允许外部干涉。
低耦合:仅对外暴露少量的方法用于使用。
把该隐藏的隐藏起来,该暴露的暴露出来,这就是封装性的设计思想。
继承
• 继承的好处:
减少了代码的冗余,提高了代码的复用性。
便于功能的拓展。
为之后多态的使用,提供了前提。
• 使用extends关键字,class A extends B{}。
◇A为子类,B为父类、基类、超类。
◇一旦子类A继承父类B,子类A中就获得了父类B中声明的结构、属性、方法(私有的虽不能访问,但是继承了)。
• 一个类只能有一个父类(单继承),一个父类可以有多个子类。
• 子类直接继承的类叫做直接父类,间接继承的是间接父类。
• public class Student extends Person{ }
多态
- 对象的多态性:父类的引用指向子类的对象。(不能去调用子类特有的方法)
2. 多态的使用:虚拟方法调用:只能调用父类中定义的方法,当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法。(编译看左边,执行看右边)
3. 多态性的使用前提:
1) 类的继承性 2)方法的重写
4. 多态性不适用于属性(用于属性的话是编译和运行都看左边)。
5. 多态性是运行时行为。
子类也可以重载父类的方法。
对于重载而言,在方法调用前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”。
而对于多态,只有等到方法调用的那一刻,编译器才会确定所要调用的具体方法,这成为“晚绑定”或“静态绑定”。
向下转型
有了对象的多态性以后,内存中实际上是加载了子类持有的属性和方法的,但是由于变量声明为父类类型,
导致编译时,只能调用父类中声明的属性和方法,子类持有的属性和方法不能调用。
方法:向下转型:使用强制类型的转换,将父类引用转换成子类的。
Person p1 = new Man();
Man p = (Man)p1;
向上转型就是指多态,子类转成父类。
使用强转时,不一定都能转。
其它关键字
- package关键字:
声明类或者接口所属的包,放在源文件首行。
包,属于标识符,要符合 命名规则。包名小写。
2. import
如果遇到两个不同的包下的同名的类,至少有一个类,以全类名(带着包名)的形式显示。
用import会出问题。
3. this
不能成为一个环调用。
并且通过构造器的调用需要放在首行。再执行自己的代码。
this可以用来调用构造器。如:
public class Person{
public Person(){
}
public Person(int age){
this(); //可以有效的防止代码的冗余
this.age = age;
}
public Person(int name){
this(age);
this.name = name;
}
}
4.super关键字
1) super理解为父类的
2) super可以调用属性、方法、构造器。
3) super的使用:
1- 我们可以在子类中,通过使用super.属性或super.方法的方式,显示的调用
父类中声明的属性或方法,但是,通常情况下,我们习惯省略“super”。
2- 特殊情况,当子类和父类中属性重名时,需要使用super去访问父类中的属性。
3- 当子类重写了父类中的方法以后,当想用父类中的方法时需要使用super.父类方法名。
4) super调用构造器:
1- 可以使用super(形参列表) ,调用父类中声明的制定的构造器。
2- super(形参列表) 必须写在首行
3- super(形参列表)和this(形参列表)只能二选一
4- 在构造器的首行没有显示的声明 super(形参列表)和this(形参列表)时,默认是 super()。父类中要有空参构造器。
5.instanceof
a instanceof A :判断对象a是否是A的实例,如果是则返回true,否则返回false。
向下转型前,要先判断一下对象的类型。
假设A为B的父类,则B的对象b,使用 b instanceof A也返回true;反过来不可以。、
6.static
static可以用来修饰,属性、方法、代码块、内部类。
使用static修饰属性:成为静态变量(类变量)
1)属性:按照是否使用static修饰,又分为静态属性(类变量) 和 非静态属性(实例变量)。
2)实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性,当修改其中一个
对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
3)静态变量:我们创建了类的多个对象,每个对象都共享一套类中的静态属性,当修改其中一个对象中的
静态属性时,会导致其他对象中同样的属性值的修改。
4)类变量随着类的加载而加载。可以通过“类.静态变量”来访问
静态变量的加载要早于对象的创建。
由于类只加载一次,所以类变量只加载一次。(存在方法区的静态域中)。
关于调用:
类变量 实例变量
类 是 否
对象 是 是
静态结构可以用类来调用。
静态方法:
1)随着类的加载而加载,可以使用类来调用。
2) 静态方法 非静态方法
类 是 否
对象 是 是
3)静态方法中,只能调用静态的方法或属性。
非静态方法都快可以用。
静态的方法内,不能使用this关键字、super关键字。this指的的某个对象。
在开发当中,如何确定一个属性或者方法为static。
》属性是可以被多个对象所共享的,不会随着对象不同而不同。
》操作静态属性的方法,通常设置为static
》工具类中的方法,习惯上声明为static的。
7.final
1. final可以用来修饰:类、方法、变量。
2. final用来修饰一个类,则此类不能被其它类继承。如:String、System,StringBuilder类。
3. final用来修饰方法,表明方法不能再被重写。比如Object中的getClass。
4. final用来修饰变量,此时的“变量”就称为一个常量。
▪final修饰属性,可以赋值的位置有:显示初始化、代码块中初始化、构造器初始化(必须对变量初始化)。
▪ final修饰局部变量,尤其是final修饰形参时,表明此形参是一个常量,当我们调用此方法时,给常量形参赋值一个实参,一旦赋值不可更改。
▪ static final 用来修饰属性,全局常量。
8.抽象类(abstract)
1.abstract:抽象的
2.abstract可以用来修饰的结构:类、方法。
3.abstract修饰类:抽象类。
abstract class Person{ }
》此类不能够实例化(一定有构造器供子类用)。
》开发中,都会提供抽象类的子类,让子类实例化。
4.abstract修饰方法:抽象方法。
public abstract void eat();
》只有方法的声明,没有方法体,不应该被调用。
》包含抽象方法的类,一定是抽象类。反之不一定。
》若子类重写了父类中的所有抽象方法后,则此类方可实例化。
若子类没有重写父类(直接间接的都算)中的所有的抽象方法,则此类一定是抽象类。
不能用来修饰属性、构造器等结构。
可以用多态。
5. 抽象类的匿名子类。
Person p = new Person(){
@Override
public void eat(){}
@Override
public void walk(){}
}
有名字的对象,匿名的类。
属性(成员变量)vs 局部变量
- 相同点
◇定义变量的格式,数据类型 变量名 = 变量值
◇先声明,后使用
◇变量都有其对应的作用域
- 不同点
◇在类中声明的位置不同:
1- 属性:直接定义在类的一段{}内。
2- 局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量。
◇关于权限修饰符的不同
1- 属性:可以在声明属性时,指明其权限、使用权限修饰符。常用的有private、protected、缺省、public。
2-局部变量是不可以使用权限修饰符的。
◇默认初始化情况
1-类的属性,根据其类型,有默认初始化值
2-局部变量是没有初始化值。特别的,形参在调用时,赋值即可。
◇在内存中加载的位置。
1-类的属性,加载到堆。
2-局部变量,加载到栈。
理解万事万物皆对象
- 在Java语言范畴中,我们都将功能、结构等封装到类中,通过类的实例化。来调用具体的功能结构。
2. 涉及到Java语言与前端html、后端的数据库交互时,前后端的结构在java层面都表现为类和对象。
匿名对象的使用
匿名对象:
new Phone().sendEmail();
1. 创建的对象没有一个变量名。
2. 匿名对象只能调用一次。
3. 开发中的使用:mail.show(new Phone());
方法的重载、方法详解
方法重载:
定义:在同一个类中,允许存在一个以上的同名方法,只要它们参数个数或者参数类型不同即可。
“两同一不同” 同一个类、相同方法名、参数列表不同(包括个数或类型、顺序)。
注:跟方法的权限修饰符、返回值类型、形参变量名、方法体都无关。
可变个数的形参
public void show(String … strs){ }
调用的时候,传入的参数个数可以是任意个。
他和 public void show(String[] s){} 是一样的。
可变参数不能放在前面
public void show(String … strs,int i){ } //这是错误的。
并且最多只能声明一个可变形参。
在数据库中可能会用。
变量赋值
- 如果变量是基本数据类型,此时赋值的是变量所保存的数据值。
2. 如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。
有趣的问题
int[] a = new int[]{1,2,3};
System.out.println(a); //输出是一个地址
char[] a = new char[]{'a','b','c'};
System.out.println(a); //输出的是a,b,c;
原因是:调用的println不一样。
权限修饰符
可以修饰类及类的内部结构、属性、方法、构造器、内部类。
MVC设计模式
MVC是常用的设计模式之一,将整个程序分成三个层次:视图模型层、控制器层、
与数据模型层。这种将程序数据的展示、输入输出、数据处理分离开来的设计模式使程序结构变的灵活而且清晰,同时也描述了程序各个对象间
的通信方式,降低了程序的耦合性。
1. 模型层 :model主要处理数据
数据对象封装 model.bean/domain
数据库操作类 model.dao
数据库: model.db
2. 视图层: view显示数据
相关工具类:view.utils
自定义view:view.ui
3. 控制层 controller 处理业务逻辑
应用界面相关 controller.activity
存放fragment controller.fragment
显示列表的适配器 controller.adapter
服务相关的 controller.service
抽取的基类 controller.base
javaBean
定义:javaBean是一种java语言写成的可重用组件。
所谓javaBean,是指符合如下标准的java类:
1) 类是公共的
2) 有一个无参的公共的构造器(便于反射)
3) 有属性、且有对应的get、set方法。
用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用Java代码创造的
对象进行打包,并且其他的开发者可以通过内部的JSP页面、servlet、其他JavaBean、applet
程序或者应用来使用这些对象,用户可以认为JavaBean提供了一种随时随地的复制和粘贴的
功能,而不用关心任何改变。
Object类
- 如果我们没有显示的声明一个类的父类的话,则此类继承于java.lang.Object类。
2. 所有的java类(除Object类之外)都直接或间接的继承于java.lang.Object类。
Object类的功能,函数和属性:
无属性。
clone():复制一个对象。
equals():比较两个对象是否相等。
finalize():将对应的堆空间的对象回收。
getClass():获取当前对象的所属类。
hashCode(): 返回对象的哈希值。
notify()
notifyAll()
toString()
wait()
方法重写
定义:在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的
重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
@override
应用:重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名参数的方法时,
实际执行的是子类重写父类的方法。
方法重写注意点:
方法的声明: 权限修饰符 返回值类型 方法名(参数列表) throws 异常的类型{
//方法体
}
1.子类重写的方法的方法名和形参列表与父类中被重写的方法的方法名和形参列表相同。
2.子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符。
特殊情况:子类不能重写父类中private修饰的方法。
3. 父类中被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void,
父类被重写方法返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类。
父类被重写方法返回值类型是基本数据类型,则子类重写的方法的返回值类型必须是相同类型。
子类重写的方法的异常的类型不大于父类被重写方法的异常类型。
注:子类和父类中的同名同参数方法要么都声明为非static(考虑重写),要么都声明为static(不是重写)。
开发时:直接复制父类方法的声明,只写方法体。
子类对象的实例化过程
从过程上来看:
当我们通过子类的构造器创建子类对象时,我们一定会直接或者间接的调用其父类的构造器。
== 和equals
==和equals的区别。
一、是运算符(两边类型得一致)
1. 可以判断基本数据类型变量(判断值是否相等)和引用数据类型变量中。
2. 如果比较的是基本数据类型,比较两个变量保存的数据是否相等。(不一定类型相同)。
如果比较的是引用型变量,则比较的是地址值是否相等。
二、 equals()是一个方法。
1.只能适用于引用数据类型。
2.Object类中equals()的定义:
public boolean equals(Object obj){
return (this == obj);
}
说明Object类中定义的equals()和的作用是相同的。
3. 像String、Date、File、包装类等都重写了equals()方法。重写以后比较的不是地址,而是比较两个对象实体内容是否相等。
4. 通常情况下,我们定义的类,如果使用equals方法,也是想比较内容,所以需要重写。
可以自动生成重写的equals()方法和hashcode()方法。
toString()方法
- 当我们输出一个引用时,其实就是调用了当前对象的toString()方法。
2. Object类中toString()方法的定义。
public String toString(){
return getClass().getName() + “@” + Integer.toHexString(hashCode());
}
3. 像String、Date、File、包装类等都重写了toString()方法,返回的是实体内容。
4. 如果我们想获得内容的信息,则需要手动重写toString()方法。
Java重的JUnit单元测试
Java重的JUnit单元测试
步骤:
1.选中当前工程 -> 右键选择:build path -》 add libraries -》JUnit。
2. 创建Java类,进行单元测试。
1) 此时的Java类要求:此类是public的 、 此类提供公共无参的构造器。
3. 此类中声明单元测试方法。
此时的单元测试方法,方法的权限是public,返回值为void、没有形参。
4. 此单元测试方法上需要生命注解: @Test,并在单元测试类中导入:import org.junit.Test。
5. 声明好单元测试方法后,就可以在方法体内测试相关的代码。
6. 写完代码后,左键双击单元测试方法名,右键run as - JUint Test。
7. 如果执行方法正常则为绿,否则为红。
包装类
包装类:
• 正对八种基本数据类型定义相应的应用类型 - 包装类(封装类)。
• 有了类的特点,就可以调用类中的方法,Java才是真正的面向对象。
• byte —— Byte short——Short int——Integer long——Long
• float——Float double——Double boolean——Boolean char——Character
• 数值型的有一个共同的父类Number
• 基本数据类型 —— 包装类 ——String之间的转换。
◇基本数据类型 —— 包装类,通过调用构造器。(装箱)
◇包装类——基本数据类型,调用包装类的xxxValue(). (拆箱)
• 在JDK5.0中加入了自动装箱和拆箱操作。
如int b。Integer a = b;
转换为String,String str1 = num1 + “”
或者调用String.valueOf(f);
要String转为想要的,则:
调用包装类的parseXxx()方法,
如Integer.parseInt();
对于Boolean,只要不是标准的true,那就是false。
面试题
1. Integer i = 1;
Integer j = 1;
i == j ? //结果是true
Integer i = 1;
Integer j = 1;
i==j? //结果是false
原因:Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer数组,
保存了从-128 ~ 127 范围的整数。如果我们使用自动装箱的方式,给Integer赋值的
范围在-128 ~127范围内时,可以直接使用数组中的元素,不用再去new了。
设计模式
单例设计模式:
• 设计模式:是在大量的事件中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。
• 所谓单例设计模式,对某个类只能存在一个对象实例。并且该类只提供一个取得对象实例的方法。
• 如何实现:
• /饿汉式单例模式/(先造好了)
• 1.私有化类的构造器
• 2.内部创建类的对象
• 3.提供公共的静态方法,返回类的对象。
• 4.要求此对象必须声明为静态的。
• /懒汉式单例模式/(啥时候用啥时候造)
• 1.私有化类的构造器
• 2.声明当前类对象,没有初始化。
• 3.声明public 、static的返回类对象的方法。
• 4.此对象也必须声明为static的。
◇ 饿汉式:坏处:对象加载时间过长(先创建好)。 好处:线程安全的
◇ 懒汉式:好处,延迟对象的创。 坏处:线程不安全的(多线程改成安全的)。
单例模式的优点:
减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,
则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
如java.lang.Runtime.
应用:
网站的计数器
应用程序的日志应用
数据库链接池
项目中,读取配置文件的类。
windows的任务管理器
windows的回收站。
模板方法设计模式
当功能内部一部分实现时确定的,一部分实现是不确定的,这时可以把不确定的部分暴露出去,让子类去实现。
换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,
这些步骤已经在父类中写好了,但是某些部分易变,一边部分可以抽象出来,共不同子类实现,这就是一种模板模式。
代理模式
概述:
代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理,以控制 对这个对象的访问。
代理类和被代理类要统一实现某一接口。然后,被代理类想要实现某一个功能,由代理类去代理执行。
应用场景:
1. 安全代理:屏蔽对真实角色的直接访问
2. 远程代理:通过代理类处理远程方法调用(RMI)
3. 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象。
4. 静态代理:(静态定义代理类)(专门针对某一接口)
5. 动态代理:JDK自带的动态代理,需要反射等的知识。
工厂模式
工厂模式:
实现创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来。
达到提高灵活性的目的。
分类:
1. 简单工厂模式:用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码)
2. 工厂方法模式:用来生产同一等级结构中的固定产品。(支持增加任意产品)
3. 抽象工厂模式:用来生产不同产品族的全部产品。(对于增加新的产品,无能为力,支持增加产品族)。
xxxFactory():工厂。
接口
有时候,必须从几个类派生出一个子类。即多继承的实现,需要接口。
事物之间不是“is a”的关系。
某些事务具有共同的功能(接口)。
• 接口的使用
1. 接口使用interface来定义
2. Java中,接口和类时并列的两个结构。
3. 如何定义接口,定义接口中的成员:
3.1 JDK7及以前,只能定义全局常量和抽象方法
全局常量:public static final,但是书写时可以省略不写。
抽象方法:public abstract的
3.2 JDK8中,除了定义全局常量和抽象方法外,还可以定义静态方法、默认方法(略 )。
4.接口中不能定义构造器,意味着接口不可以实例化。
5.Java开发中,接口都通过让类去实现使用(implements)。
如果实现类覆盖了接口中的所有抽象方法,则可以实例化。
如果实现类没有覆盖了接口中的所有抽象方法,则仍为接口。
6.Java类可以实现多个接口 -->弥补了Java中多继承的缺陷。
格式:class AA extends BB implements CC,DD,EE{
}
7.接口与接口之间可以继承,而且可以多继承。
格式 interface CC extends AA,BB{}
8.接口的具体使用,体现多态性(形参定义为接口)。
9. 接口,实际上可以看作是一个规范。
开发中,体验面向接口编程。
都重写了
ball是 public static final的
JDK8中接口的新特性:
- JDK8中,除了定义全局常量和抽象方法外,还可以定义静态方法、默认方法(略 )。
2. 默认方法:调用的话使用 接口名.super.方法名().
public default void method2(){
System.out.println(“nihao”);
}
public 可以省略。
3. 接口中定义的静态方法,只能通过接口来调用。
4. 通过类的对象,可以调用接口中的默认方法。
5. 如果实现类,重写了默认方法,仍然调用的是重写以后的方法。
6. 如果子类继承的父类和接口中有同名的方法,并且尚未重写,优先调用父类中的方法。
7. 如果实现类实现了多个接口,接口中有同样的方法不都是抽象的,则没重写就报错。
异常概述
Exception:其他因编程错误或偶然的外在因素导致的一般性问题,可以用针对性的代码来处理。
如:
空指针访问
试图读取不存在的文件
网络链接中断
数组角标越界。
异常的分类
一、异常的体系结构
顶级父类:java.lang.Throwable
|---- java.lang.Error: 一般不编写针对性的代码进行处理
|---- java.lang.Exception:可以进行异常的处理
|—编译时异常(checked)
|—IOException
|—运行时异常(unchecked)
异常处理机制概述
异常处理避免了大量的if-else判断。
Java中使用的是:抓抛处理。
过程一:“抛”,程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象。
并将此对象抛出。
对象一旦抛出,后边的代码(异常括号内的)将不再执行。
过程二:“抓”,可以理解为异常的处理方式:
try-catch-finally 和 throws。
方式一:
try-catch-finally:(自己能解决)
try{
//可能出现异常的代码 }
catch(异常类型1 变量名1){
//。。处理异常的方式1
}
catch(异常类型2 变量名2){
//。。处理异常的方式2
}
catch(异常类型3 变量名3){
//。。处理异常的方式3
}
finally{
//一定会执行的代码
}
说明:
1. finally是可选的。
2. 使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个
对应异常类的对象,根据此对象的类习惯去catch中进行匹配。
3. 一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理,一旦处理完成,就跳出当前的
try-catch结构(在没有写finally的情况下),继续执行其后的代码。
4.catch中的异常类型如果没有子父类关系,则谁声明在上,没有关系。
catch中的异常类型如果有子父类关系,则子类要声明在上。
5. e.getMessage() 和 e.printStackTrace()可以看到各个结构;常用。
6. try{}中定义的变量,在外边不能用。
7. 处理编译时异常时,编译时不报错,运行时可能会报错。
8. try-catch可以嵌套。
9. 开发中由于运行时异常比较常见,我们通常不针对运行时异常编写程序,
•finally的使用:
◇finally中声明的是一定会被执行的代码,即使catch中又出现异常了,try中有return语句,catch中有return语句等情况。
◇像数据库链接、输入输出流、网络编程Socket等资源,JVM是不能自动回收的,我们需要自己手动回收进行资源的释放,
资源释放的代码放在finally里边,防止异常的产生。
方式二:
throws + 异常类型(报给上级处理,调用他的)
public void method1() throws 异常1,异常2{ }
1. throws + 异常类型 写在方法的声明处,指明此方法执行时,可能会抛出的异常类型。
一旦当方法体被执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常
类型时,就会被抛出。异常代码后续的代码,就不再执行!
2. try-catch-finally :真正的将异常给处理掉了。
throws的方式只是将异常抛给了方法的调用者,并没有将异常处理掉。
注:子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型。
开发中,如何选择try-catch-finally,还是使用throws?
1. 如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果
子类重写的方法中有异常,必须使用try-catch-finally方式处理。
2.执行的方法a中,先后又调用了另外几个方法,这几个方法是递进关系执行的,我们建议这几个方法使用
throws的方法进行处理,而执行的方法a,可以考虑try-catch-finally
手动抛出异常
关于异常对象的产生:
1.系统自动生成的异常对象
2.手动的生成一个异常对象,并抛出(throw)。
throw new RuntimeException("…")
然后再处理。
用户自定义异常类
1.继承于现有的异常结构,RuntimeException、Exception
2.提供全局常量,serialVersionUID
3.提供重载的构造器。
throw和throws的区别
throw:生成一个异常对象,并抛出,使用在方法内部。相对的是自动抛出异常对象。
throws:处理异常的方式,使用在方法声明处的末尾 。 相对的是try-catch-finally。
线程的创建
方式一:继承于Thread类:
1.创建一个继承于Thread类的子类
2.重写Thead类的run(),将此线程要做的事情,写在run里。
3.创建Thread子类的对象
4.通过此对象调用start()。 start()方法的作用,a. 启用当前线程 b.调用该线程的run()方法。
注:可以创建Thread的匿名子类,使得更加方便。
问题一:我们不能直接调用run()方法来启用线程。
问题二:再启动一个线程,遍历100以内的偶数。
不可以让已经start()的线程去执行,会报线程状态不合法的异常。
需要再new 一个线程对象来执行。
方式二:#### 实现Runnable接口
1. 创建一个实现了Runnable接口的类
2. 实现类去实现接口中的run()方法.
3. 创建实现类的对象
4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象。
5. 通过Thread类的对象调用start()。->启动线程, 调用了Runnable类型的targert的run方法。
6. 多个线程执行相同的操作时,不需要new两个Runnable,只需要new 两个Thread;
两种方式的比较:
开发中:优先选择实现Runnable接口的方式。
原因:1. 实现的方式没有类的单继承的局限性。
2. 实现的方式更适合来处理多个线程共享数据的情况。
联系:
Thread类本身也实现了Runnable接口。
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
JDK5.0新增线程创建方式
方式三: 实现Callable接口。
1.创建一个实现Callable的实现类
2.实现call方法,将此线程需要执行的操作写在call方法中。
3.创建Callable接口实现类的对象。
4.将此Callable接口实现类的对象传递到FutureTask构造器中,创建FutureTask对象。
5.将FutureTask对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法。
6.获取Callable中call方法的返回值,使用“FutureTask对象.get()”。
如何理解实现Callable接口的方式比实现Runnable接口的方法更强大?
1.call()可以有返回值。
2.call()可以抛出异常,被外面的操作捕获,获取异常的信息。
3.Callable是执行泛型的。
方式四:使用线程池
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完
放回池中。可以避免频繁创建销毁、实现重复利用。类似正火中的公共交通工具。
好处:
1.提高响应速度(减少了创建新线程的时间)
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)。
3.便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间会终止。
代码实现:
1.提供指定线程数量的线程池。
ExecutorService service = Executors.newFixedThreadPool(10);
2.执行指定的线程的操作,需要提供实现Runnable接口或Callable接口
service.execute(new OddNumber()); //适用于Runnable接口。
service.submit(new numberThree()); //适用于Callable接口。
3.关闭线程池
service.shutdown().
注:实现管理的话,需要找到service对应的类,然后new 一个对应类的引用,将service强转为该类型,去设置。
线程的常用方法
Thread类中的常用方法:
1. start():启动当前线程,并调用该线程的run方法。
2. run():通常需要重写Thread类中的此方法,将创建线程要执行的操作声明再此方法中。
3. currentThread():静态方法,返回执行当前代码的线程。
4. getName():获取当前线程的名字。
5. setName():设置当前线程的方法。
6. yield():释放cpu的使用权。
7. jion():在线程A中调用线程b的jion(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才可以执行。
8. stop():已过时,当执行此方法时,强制结束当前线程。
9. sleep(long millitime):让当前线程睡眠指定的时间millitime毫秒。在指定的时间内,线程阻塞。
10. isAlive()判断线程是否存活。
线程的优先级设置
线程的优先级:
涉及到三个常量:
1. MAX_PRIORITY:10
2. MIN_PRIORITY:1
3. NORM_PRIORITY:5
获取和设置当前线程的优先级:
getPriority()
setPriority()
说明:高优先级会抢占低优先级的程序的运行,但并不意味着低优先级
一定在高优先级执行完之后执行。
线程的生命周期
状态:
1.新建:
2.就绪:
3.运行:
4.阻塞:
5.结束:
线程安全问题
出现了线程安全问题,即共享的数据出现了错误。
Java中通过同步机制,来解决线程的安全问题。
• 方法一:同步代码块 -->不能包含多了,也不能包含少了。
synchronized(同步监视器){
//需要被同步的代码,即操作共享数据的代码。
}
同步监视器:俗称锁。任何类的对象都可以充当锁。
要求,多个线程必须要共用同一把锁。
补充,在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
在继承Thread类创建多线程的方式中,慎用this作为锁,可以考虑用当前类作为锁,类也是对象。
说明:同步的方式,解决了线程的安全问题。—好处
操作同步代码的同时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程。效率低 -----局限性
• 方式二:同步方法
访问共享数据的代码恰好在一个方法内。
使用 private synchronized void method(){}
1.同步方法仍然涉及到同步监视器,只是不需要我们显示声明
2.非静态方法时,同步监视器为:this
静态同步方法:同步监视器为当前类本身
• 方式三:Lock锁 — JDK5.0新增。
1.实例化ReentrantLock
ReentrantLock lock = new ReentrantLock()。 构造器中如果传入true,则表示为公平锁。
2.在访问共享资源的代码块中,使用lock.lock(),用try环绕;
3.在finally中使用lock.unlock()释放锁。
synchronized锁 与 lock 锁的异同:
synchronized机制在执行完相应的同步代码块以后,自动的释放同步监视器。
Lock锁需要手动的启动同步( lock() ),同时结束同步也需要手动的 unlock()。
三种方式(建议使用):
Lock -> 同步代码块(已经进入了方法体,分配了相应资源)-> 同步方法(在方法体之外)
线程的通信
涉及到的三个方法:
1.wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
2.notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级高的那个。
3.notifyAll():一旦执行此方法,就会唤醒被wait的所有线程。
说明:
1.上述三个方法,必须使用在同步代码块或同步方法中。
2.上述三个方法的调用者,必须是同步代码块中的同步监视器。
否则会出现异常。
sleep()和wait()的异同
sleep() 和 wait()
1.相同点:
一旦执行方法,都可以使得当前的线程进入阻塞状态。
2.不同点:
◇两个方法声明的位置不同
Thread类中声明的sleep(),Object类中声明wait();
◇调用的要求不同:sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块或者同步方法中。
◇关于是否释放同步监视器:如果两个方法都是用在同步代码块或同步方法中,sleep()不释放,wait()释放。
String
String的底层是数组。
String:字符串,使用一对“”引起来表示,
1.String声明为final的,不可被继承。
2.String实现了Serializable接口(表示字符串是支持序列化的)
实现了Comparable接口:表示String可以比较大小。
3.String内部定义了final char[] value用于存储字符串数据数组,
4.String:代表一个不可变的字符序列。简称:不可变性。
◇当对字符串重新赋值时,需要重新开辟内存区域赋值,不能使用原有的value进行赋值。
◇当对现有的字符串进行链接操作时,也需要重新指定内存区域赋值,不能修改原值。
◇当调用String的replace()修改指定的字符和字符串时,也不是修改的原值。
5.通过字面量的方式(“abc”)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
6.字符串常量池中是不会存储相同内容的字符串结构。
String a = “abc”;字面量 和 String b = new Sting(“abc”);构造器构造。两种方式的区别?
String s = new String(“abc”);方式创建对象,在内存中创建了几个对象?
两个:一个是对空间中new结构,另一个是char[]对应的常量池中的数据:“abc”
☐ 结论:
☐ 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
☐ 只要其中有一个是变量,结果就在堆中。
☐ 如果拼接的结果调用intern()方法,返回值就在常量池中。
String类中常用方法
int length(): 返回字符串的长度 return value.length。
char charAt(int index):返回某索引处的字符return value[index]
boolean isEmpty():判断是否是空字符串:return value.length == 0;
String toLowerCase():转换为小写。
String toUpperCase(): 转换为大写。
String trim():去除字符串的首尾空格。
boolean equals(Object obj): 比较字符串的内容是否相同。
boolean equalsIgnoreCase(Object obj): 与equals方法类似,忽略大小写。
String concat(String str):将指定字符串连接到此字符串的结尾。等价于用”+“。
int compareTo(String anotherString): 比较两个字符串的大小。
String substring(int beginIndex): 返回一个新的字符串,它是此字符串的从beginIndex开始截取,到结尾。左闭右开
String substring(int beginIndex,int endIndex):返回一个新字符串,它是此字符串从beginIndex 到endIndex的截取。不包括endIndex。
boolean endsWith(String suffix): 测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix,int toffset):测试此字符串从指定索引开始的,是否以prefix开始的。
boolean contains(CharSequence s):当且仅当此字符串包含指定的char值序列时,返回true。
int indexOf(String str): 返回指定子字符串在此字符串中第一次出现处的索引。找不到则返回-1.
int indexOf(String str,int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从fromIndex开始。
int lastIndexOf(String str):从后往前找,找第一个出现str的位置。
int lastIndexOf(String str,int fromIndex):从指定位置开始,从后往前找第一次出现str的位置。
String replace(char oldChar,char newChar):返回一个新的字符串,它是用newChar来代替oldChar。
String replace(CharSequence target,CharSequence replacement):使用前面指定的字面值,代替后面指定的字面值。
☐ 将String转换为char[]。
char[] c = str.toCharArray();
☐ char[] 转换为String。
char[] str2 = new char[];
String s = new String(str2);
String 与 byte[]之间的转换
String -> byte[] : 调用String的getBytes()。 //称为编码
byte[]->String : String a = new String(bytes,【可以指定解码字符集】) //称为解码
byte[] bytes = str1.getByte(); //使用默认的字符集。
str1.getBytes(“gbk”); //指定特定的字符集
StringBuffer
String、StringBuffer、StringBuilder的异同:
String:不可变的字符序列;底层使用char[]数组存储,
StringBuffer:可变的字符序列;线程安全,效率低,底层使用char[]数组存储,
StringBuilder:可变的字符序列,jdk5.0新增的,线程不安全,效率高;底层使用char[]数组存储,
源码分析:
String str = new String();//new char[0];
String str1 = new String(“abc”);//new char[]{‘a’,‘b’,‘c’};
StringBuffer sb1 = new StringBuffer(); //char[] value = new char[16],底层创建了一个长度为16的数组。
StringBUffer sb2 = new StringBuffer(“abc”); //char[] value = new char[“abc”.length() + 16],底层创建了一个长度为16的数组。
问题一:System.out.println(sb2.length()) -> 3
问题二:扩容问题,如果要添加数据超过底层默认长度,需要扩容。
默认情况下,扩容为原来容量的2倍 + 2,同时将原有数组中的元素复制到新的数组中,
如果过长,直接将字符串的长度作为扩容长度。
指导意义:开发中建议大家使用StringBuffer(int capacity) 或 StringBuilder(int capacity);
StringBuffer的常用方法:
StringBuffer append(xxxx) //字符串的拼接
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):删除指定位置的内容。
改:public void setCharAt(int n,char ch) / StringBuffer replace(int start, int end, String str)
查:charAt(int n)
插:insert(int offset,xxx):在指定位置插入xxx。
长度:public int length()
遍历:for + charAt();/ toString。
StringBuilder 与 StringBuffer类似。
效率方面:String < StringBuffer < StringBuilder
JDK8之前日期和时间的API测试。
System类中时间戳的使用。
long time = System.currentTimeMillis(); //返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。称为时间戳。
2.Date类:
java.util.Date类
|—java.sql.Date类
◇两个构造器的使用
构造器一:
Date date1 =new Date();
date1.toString() //显示当前日期
date1.getTime() //返回的是一个毫秒,时间戳。
构造器二(创建一个指定毫秒数的日期):
Date date2 = new Date(1515156515155L);
◇两个方法的使用
date1.toString()
date1.getTime()
◇java.sql.Date的使用
实例化:java.sql.Date date3 = new java.sql.Date(544645645L);
sql.Date → util.Date对象。 //可以用多态
sql.Date <---- util.Date对象 //可以用毫秒数new一个
SimpleDateFormat
SimpleDateFormat的使用:SimpleDateFormat对日期Date类的格式化和解析。
1.两个操作:
1.1 格式化:日期->字符串
SimpleDateFormat sdf = new SimpleDateFormat();
System.out.println(sdf.format(t1));
SimpleDateFormat sdf1 = new SimpleDateFormat(“yyyy-MM-dd a hh:mm:ss”);
System.out.println(sdf1.format(t1));
1.2 解析:格式化的逆过程,字符串 -> 日期。
String s = “2020-02-03 下午 13:15:15”;
Date t2 = null;
try {
t2 = sdf1.parse(s);
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(t2.toString());
2.SimpleDateFormat的实例化
SimpleDateFormat sdf = new SimpleDateFormat();
SimpleDateFormat sdf1 = new SimpleDateFormat(“yyyy-MM-dd a hh:mm:ss”);
Calendar日历类(抽象类)的使用
1.实例化
方式一:创建其子类(GregorianCalendar)的对象
方式二:调用其静态方法getInstance()
Calendar calendar = Calendar.getInstance();
2.常用方法
int days = calendar.get(Calendar.DAY_OF_MONTH); //今天是这个月的第几天
calendar.set(Calendar.DAY_OF_MONTH,22) //改成22天
calendar.add(Calendar.DAY_OF_MONTH,22) //从22天的基础上加三天
calendar.getTime(); //日历类---->Date
Date date = calendar.getTime();
calendar.setTime(date1); //修改后的时间给date1.
JDK8中新日期时间API
LocalDate、LocalTime、LocalDateTime。
实例化:
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime locaDateTime = LocalDateTime.now();
LocalDateTime locaDateTime1 = LocalDateTime.of(2020,10,03);
localDateTime.getDayOfMonth(); //当月的第几天
//体现了不可变性:不会改变元对象
LocalDateTime locaDateTime1 = localDateTime.withDayOfMonth(22); //改为这个月的第22天。
LocalDateTime locaDateTime1 = localDateTime.plusMonths(3); //改为这个月的第25天,这是加操作。
LocalDateTime locaDateTime1 = localDateTime.minusDays(3); //减去三天
DateTimeFormatter:格式化或解析日期、时间。类似于SimpleDateFormat
实例化:
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT) //改变格式
DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL) //改变格式
自定义格式:
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern(“yyyy-MM-dd”)
格式化:
String formatter.format(localDateTime);
解析:
TemporalAccessor parse = formatter.parse(format); //接口的形式
1.实现方式:
方式一:Comparable接口。(自然排序)
1.像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象的方法。(默认从小到大)
2.重写compareTo(obj)的规则:
如果当前对象this大于形参对象obj,则返回正整数。
如果当前对象this小于形参对象obj,则返回负整数。
如果当前对象this等于形参对象obj,则返回零。
3.对于自定义的类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)方法。
方式二:定制排序Comparator。
1.背景:
当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码。
或者是想了java.lang.Comparable接口的排序规则不适合当前的操作,
那么可以考虑使用Comparator的对象来排序。
2.重写compare(Object o1,Object o2)方法,比较o1和o2的大小。
如果方法返回正整数,则表示o1大于o2;
如果返回0,则表示相等;
返回负整数,表示o1小于o2.
枚举类的理解:
• 类的对象只有有限个,确定的。称为枚举类。
• 如:星期:星期一,…,星期五。
• 当需要定义一组常量时,强烈建议使用枚举类。
• 如果枚举类的对象只有一个,就是单例模式。
一.如何定义枚举类
方式一:jdk5.0之前,自定义枚举类。
1.私有化类的构造器。
2.声明对象的属性,需要用private final来修饰。
3.提供当前类的多个对象,用public static final来修饰。
4.其他诉求:获取枚举类对象的属性、toString。
方式二:jdk5.0之后,使用enum关键字。定义的枚举类,继承于java.lang.Enum类。
1.将class改为enum。
2.首先提供当前枚举类的对象,多个对象之间用“,”隔开,末尾对象用“;”结束。
如:SPRING(“春天”)。
3.声明对象的属性,需要用private final来修饰。
4.私有化类的构造器。
5.其他诉求:获取枚举类对象的属性、toString()(被重写过,返回对象名)
Enum类中的常用方法:
values()方法:返回枚举类型的对象数组,该方法可以很方便地遍历所有的枚举值。
valueOf(String str):可以把一个字符串转为对应的枚举对象,要求字符串必须是枚举类对象。如果名字写错则抛出异常
toString():返回当前枚举类对象常量的名称。
Enum类实现接口。
情况一:实现接口,在enum类中实现抽象方法。
情况二:让枚举类的对象分别实现接口中的抽象方法。
• 注解(Annotation)(jdk50新增):其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过使用Annotation程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具,可以通过这些补充信息进行验证或者进行部署。
Annotation可以像修饰符一样被使用,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明。这些信息被保存
在Annotation的“name = value”对中。
可以说:框架 = 注解 + 反射 + 设计模式。
例如:
@override 方法的重写。
@Deprecated:用于表示过时了。
@SuppressWarnings :定义的变量没有用过。
• 如何自定义注解:
@interface 注解名{
String value() ; // 这是属性。
或者:
String value() default “hello”;
}
如果自定义注解没有成员,表明是一个标识作用。
如果注解有成员,在使用注解时需要给成员赋值。
自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
• JDK中的4种元注解:用于修饰其他注解的注解。
@Retention(RetentionPolicy.RUNTIME): 表明注解的生命周期。有SOURCE\CLASS(默认)\RUNTIME;
只有声明为RUNTIME生命周期的注解才能通过反射获取。
@Target({TYPE,FIELD}) : 标明所修饰的注解能够修饰的数据类型
上两个一般自定义的时候会指明。
@Documented:表示所修饰的注解,在进行javadoc解析时,会保留下来。
@inherited:表明注解修饰的类的子类会继承该注解。
• jdk8中注解的新特性:
jdk8 之前的写法:
@MyAnnotation({@My(“hi”),My(“hello”)})
1.可重复注解:
在My注解上声明:@Repeatable(MyAnnotations.class)
1.1.在MyAnnotation上声明@Repeatable(MyAnnotations.class)。
1.2.MyAnnotation的Target和Retention等元注解和MyAnnotations相同。
2.类型注解:
在Target中加入ElemmentType.TYPE_PARAMETER,TYPE_USE
集合概述
一、集合框架的概述。
1.集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt)
2.数组在存储多个数据方面的特点
一旦初始化以后,其长度就确定了。
数组一旦定义好,其元素的类型也就确定了,只能操作指定类型的数据。
缺点:一旦初始化以后,其长度就不可修改
数组中提供的方法非常有限,对于添加、删除、插入等操作,非常不便,同时效率不高。
获取数组中实际元素个数的需求,数组没有现成的属性和方法可用。
数组存储数据的特点:有序、可重复,对于无序、不可重复的,不能满足。
java集合可以分为Collection和Map两种体系。
Collection接口:单列数据,定义了存取对象的方法的集合
List:元素有序、可重复的集合
Set:元素无序、不可重复的集合。
Map接口:双列数据,保存具有映射关系”key-value对“的集合。
二、集合框架:
|----Collection接口:单列集合,用来存储一个一个的对象。
|----List接口:存储有序的,可重复的数据。 -->”动态数组“。
|----ArrayList、LinkedList、Vector
|----set接口:存储无序的,不可重复的数据。 -->高中将的”集合“
|----HashSet、LinkedHashSet、TreeSet。
|----Map接口:双列集合,用来存储一对(key - value)一对的数据。 --->高中函数: y = f(x);
|---HashMap、LinkedHashMap、TreeMap、Hashtable、Properties。
三、Collection接口中的方法的使用
Collection coll = new ArrayList();
1.coll.add(Object e): 将元素e添加到集合coll中
2.coll.add(Collection coll1 = new ArrayList()); 将coll1集合中的元素加到当前集合。
3.coll.size() ; 返回集合长度。
4.coll.isEmpty(); 判断是否为空。
5.coll.clear(); 清空集合元素。
6.coll.contains(Obj) ; 判断的是是否包含该内容。会调用obj对象所在类的equals方法,看一下该对象有没有重写equals方法。
7.coll.contains(Collection coll1); 判断集合coll1中的元素是否都在coll中。
8.coll.remove(obj); 移除obj,仍然会调用equals方法。成功移除返回true,否则为false。
9.coll.remove(Collection coll1); 从当前集合中移除coll1中所有的元素。移除的是共有的元素。得到的是差集。
10.coll.retainAll(Colection coll1);得到两者的交集。
11.coll.equals(coll1); 比较两集合是否相同,调用equals方法。
12.coll.hashCode(); 返回当前对象的hash值。
13.Object【】 a = coll.toArray(); 集合转换为数组。
14.Collection c = Arrays.asList(new String[]{“AA”,“BB”}); 由数组到集合。整形要注意,要Arrays.asList(123,456).
Iterator迭代器
• Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素。只遍历Collection。
使用:
Iterator iterator = coll.iterator();
iterator.next(); //返回下一个元素。先下移,然后取数据,一气呵成。
iterator.hasNext(); //判断是否还有下一个元素。
iterator.remove(); //可以删除next当前指向的元素。
jdk5.0新增的foreach
jdk5.0 新增foreach循环,用于遍历集合和数组。内部仍然是迭代器。
for(Object obj : coll){
obj = new; //不会改变原数组。
}
List接口
☐ List接口:存储有序的、可重复的数据。 -》动态数组,替换原有的数组。
三个实现类:
ArrayList:作为List接口的主要实现类 jdk1.2出现,线程不安全的,效率高。底层使用Object[] 存储。
LinkedList:对于频繁的插入、删除操作,使用此比ArrayList效率高,底层使用的是双向链表存储,线程不安全。
Vector:作为List接口的古老实现类 jdk1.0 出现,线程安全的,效率低。底层使用Object[] 存储。
相同点都是List的实现类,存储有序的、可重复的数据。
• ArrayList源码分析。
jdk7情况下:
ArrayList array = new ArrayList(); //底层创建了一个长度为10的数组。
array.add(). //如果此次的添加导致底层elementData数组容量不够,则扩容。默认情况下扩容为原来的1.5倍,并将原有数组
中的数据复制到新的数组中。
结论:建议开发中使用带参的构造器。
jdk8中:
ArrayList array = new ArrayList(); 第一次中不会分配数组。
在第一次调用add时才创建长度为10的数组。后续操作一样。
小结:jdk7中的ArrayList的对象创建类似于单例模式的饿汉式,而jdk8中的ArrayList的对象创建类似于单例的懒汉式,延迟了数组
的创建,节省了内存。
• LinkedList源码分析。
LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认为null。
list.add(); //将123封装到Node中,创建了Node对象。
• Vector源码
Vector v = new Vector();
线程安全的,初始为10,jdk8中也没变,一次扩容后为原来的2倍。
List接口中常用的方法
void add(int index,Object obj);
void addAll(list1);
void get(int index);
int indexOf(Object obj); 当前对象首次出现的位置。如果不存在返回-1.
int lastIndexOf(Object obj); 当前对象最后出现的位置。
Object remove(int index); 移除指定索引位置的元素,并返回此元素。可以按照索引删除,可以按照内容删除。首先匹配index。
Object set(inr index,Object ele):设置指定index位置的元素为ele。
List subList(int fromIndex,int toIndex);返回子序列前闭后开。
总结,常用方法:
增:add(Object obj);
删:remove(int index),remove(Object obj);
改:set(int index,Object ele);
查:get(int index)
插:add(int index,Object ele)
长度:size()
遍历:Iterator
foreach
普通for。
Set接口
Set接口:存储无序的,不可重复的数据
|---HashSet:作为Set接口的主要实现类,线程不安全,可以存储null。
|-----LinkedHashSet:作为HashSet的子类:遍历其内部数据时,可以按照添加的顺序遍历。
对于频繁的遍历,效率要高一些
|---TreeSet:可以按照对象的指定属性,进行排序。
Set接口:存储无序的,不可重复的数据。Set中没有新定义的方法,都是Collection中的。
一、解释:
1.无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是按照哈希值存放。
2.不可重复性:保证添加的元素按照equals()判断时,不能返回true。
二、添加元素的过程:以HashSet为例。
• HashSet底层是数组+链表。其实底层是HashMap,jdk7,添加新元素七上八下。Object类中的hashCode(),每个对象随机返回一个值。
Set删除的时候,会调用hashCode()。计算hash值。
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值。
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即索引位置),判断数组此位置上是否已经有元素:
如果此位置上没有其他元素,则元素a添加成功 ---》情况1
如果此位置上有其他元素b(或以链表形式存在多个元素),则比较元素a与元素b的hash值:
如果hash值不同,则元素a添加成功 --》情况2
如果hash值相同,进而需要调用元素a所在类的equals()方法:
equals()返回true,则元素a添加失败。
equals()返回false,则元素a添加成功。 ---》情况3
对于添加成功的情况2和情况3而言,元素a与已经存在指定索引位置上数据以链表的方式存储。
jdk7中:元素a放到数组中,指向原来的元素。
jdk8中:原来的元素在数组中,指向元素a。
总结:七上八下。
• LinkedHashSet
作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据的前一个数据和后一个数据。
优点:对于频繁的遍历,效率要高一些。
• TreeSet
1.向TreeSet中添加的数据,要求是相同类的对象。
2.两种排序方式,Comparator(定制排序)和Comparable接口(自然排序)。
3.TreeSet判重复是,如果实现的Comparable接口,则用这个来判断会不会相同。
Comparator:
TreeSet set = new TreeSet(com);com为Comparator对象。
三、要求:向Set中添加的数据,其所在的类一定要重写hashCode()和equals()
重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码。
重写技巧:直接用生成的。eclipse中生成的hashCode往往会将一个属性的值乘以31
Map接口
|----Map接口:双列集合,用来存储一对(key - value)一对的数据。key无序的、不可重复 --->高中函数: y = f(x);
|---HashMap:作为Map的主要实现类。线程不安全的,效率高。可以存储null的key和value。
|---- LinkedHashMap:保证遍历Map元素时,按照添加的顺序实现遍历。
原因:在原有的HashMap底层结构基础上,增加了前后指针,在频繁遍历时效率高。
|---TreeMap:保证按照添加key-value对进行排序。实现排序遍历,此时考虑key的自然排序或定制排序。
底层使用红黑树。
|---Hashtable:作为古老的实现类。线程安全的,效率低。不可以存储null的key和value。
|---Properties:常用来处理配置文件,key和value都是String类。
1.HashMap的底层?
2.HashMap和HashTable的异同?
3.CurrentHashMap和HashMap、Hashtable?
一、Map结构的理解:
1.Map中的key:无序的、不可重复的,使用Set存储所有的key。
2.Map中的Value:无序的、可重复的,用Collection来存。
3.一个键值对key-value称为一个Entry对象,Map中的entry是无序的、不可重复的,用set存储所有的entry。
4.要求:一定要重写equals()和hashCode()方法。
二、HashMap的底层实现原理
以jdk7为例:
1.添加。
HashMap map = new HashMap()。
在实例化后,底层创建了长度是16的一维数组Entry[] table。
----- 可能经过了多次put ------
map.put(key1,value)。
首先调用key1所在类的hashCode()方法,计算key1的hash值,然后通过某种算法,计算出该hash值要在数组中存放的索引,
如果该位置为空,则此时key1,value添加成功 -- 情况1
如果该位置不为空,(意味着此位置上有一个或多个数据,多个数据用链表存储)比较key1和已经存在的一个或多个数据的hash值。
如果不同,则key1,value保存成功。
如果相同,则继续比较key1.equals(key2);
如果不同,则保存成功。
如果相同,则用value1替换value2.
补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的形式存储。
2.扩容
在不断的添加过程中,会涉及到扩容问题,当大于加载因子*容量时,或单个链表长度大于8,
默认的扩容方式:扩容为原来容量的2倍,并将原有的数据赋值过来。
jdk8 相较于jdk7在底层实现方面的不同。
1. new hashMap():底层没有创建一个长度为16的数组。
2. jdk8底层的数组是Node类型,不是Entry。
3. 首次调用put()方法时,底层创建长度为16的数组。
4. jdk7底层结构只有数组+链表,jdk8中底层结构:数组+链表+红黑树。
注:当数组的某一个索引位置上的数组以链表形式存在的数据个数 > 8,且当前数组的长度 > 64 时,链表变成红黑树。
此时索引位置上的所有数据改为使用红黑树存储。
三、LinkedHashMap底层结构。
加了一个前后指针,使得可以按照输入顺序打印。
四、TreeMap,按照key进行排序。
向TreeMap中添加key-value,要求key必须是由同一个类创建的对象。
排序方式,自然排序(类实现接口)、定制排序(往TreeMap的构造器中传比较器)。
TreeMap map = new TreeMap(new Comparator(){
@Override
compare
})
五、Properties:常用来处理配置文件,key和value都是String类。
Properties p = new Properties();
FileInputStream fis = new FileInputStream(“wenjianming.properties”);
p.load(fis);
String s1 = p.getProperty(“name”);
Map常用方法
1. Object put(Object key,Object value):将指定key,value放入到map中。
2. void putAll(Map map1);
3. remove(key); //返回的是value。
4. map.clear(); //清空map中的数据。
5. map.get(key);
6. map.containsKey();
7. map.containsValue();
8. size();
9. isEmpty();
10. equals(); //判断两个map是否相同。
11. map的遍历:map.keySet(); //获得key的Set
12. map.values(); //获得Collection的values()。
13. map.entrySet(); //获得所有的key - value。然后再用entry.getKey() 和entry.getValue().
Collections工具类
Collections:操作Collection、Map的工具类。
1. reverse(List); //修改的List本身。
2. shuffle(List); //将List中的数据随机排列。
3. sort(List); //自然排序、
4. sort(List,Comparator); 或定制排序。
5. swap(List, int ,int); //将指定list集合中的i处元素和j处元素进行交换。
6. Object max(Collection);
7. Object max(Collection, Comparator);
8. Object min(Collection)
9. Object min(Collection, Comparator)
10. int frequency(Collection,Object); 返回指定集合中指定元素的出现次数。
11. void copy(List dest,List src); 将src中的内容复制到dest中。 dest的size()要大于等于src的size()。List dest = Arrays.asList(new Object(list.size()));
12. boolean replaceAll(List list,Object oldVal, Object newVal); 使用新值替换List中的旧值。
13. synchronizedXXX(list); //将list转为线程安全的。
泛型定义
定义:把元素的类型设计成一个参数,这个类型参数叫做泛型,像数组一样。如:Collection,List,ArrayList,Iterator
这个就是类型参数称为泛型。(从jdk5.0之后增加的泛型)
- 集合接口或集合类再jdk5.0时都修改为带泛型的结构。
2. 在实例化时,可以指明具体的泛型类型。
3. 指明完以后,所有的内部结构都要用泛型。
4. 注意:泛型必须是类,不可以时基本数据类型。
5. 如果不指定泛型,则是Object类型。
6. 接口名. 泛型类、 泛型方法.
jdk7新特性:类型推断:
Mapmap = new HashMap<>();
泛型方法
• 泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。
• 换句话说,泛型方法所属的类是不是泛型类都没有关系。
public List copy(E[] arr){
}
泛型方法在调用时,指明泛型参数的类型。
Integer[] arr = new Integer[]{1,2,3}
List list = order.copy(arr);
泛型方法,可以生命为静态的。原因:泛型参数是在调用方法时确定的,并非在实例化类时确定。
泛型类和泛型方法的使用
>DAO:data(base) access object。操作数据库中的数据,每个数据表对应一个类。
public class DAO{
//zeng
//shan
//gai
//cha
}
class StudentDAO extends DAO< Student>{ }
泛型在继承方面的体现。
• List
通配符的使用
通配符的使用:
1. List list1 = null; List list2 = null; List> list = null; list = list1; list = list2; //这样是正确的。
2. public void print(List> list){
Iterator> iterator = list.iterator();
while(iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
}
3.List> list = new List(); list不能添加数据。除了添加null之外。
4.Object o = list.get(0); // 是可以允许读的,读取的数据类型是Object。
总结:类A是类B的父类,G 和G是并列的。他们共同的父类是G>;
有限制条件的通配符
有限制条件的通配符的使用:
1. G<? extends C>: 可以是G 和G,B是C的子类。
2. G<? super C>: 可以是G 和G,D是C的父类。 可以填入C类。
File类的使用
java.io.File类:
- File类的一个对象,表示一个文件或文件目录(俗称文件夹)。
2. File类在java.io包里。
3. File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法。
并未涉及到写入或读取文件内容的操作,如果需要读取或写入文件内容,必须使用IO流来完成。
4. 后续File类常会作为参数传递到流的构造器中,指明写入和读的起点和终点。
• 如何创建一个File类的实例:
File file = new File(String filePath);
File file = new File(String parentPath,String childPath);
File file = new File(File parentFile,String childPath);
• 路径分隔符:文件路径,在windows和dos中“\”,在linux和unix和URL:/. 也可以用File.separator。
File类的常用方法:
1. public String getAbsolutePath(); 获取绝对路径。
2. public String getPath(): 获取路径。
3. public String getName():获取名称。
4. public String getParent():获取上层文件目录路径。若无,返回null。
5. public long length():获取文件长度(即字节数),不能获取目录的长度。
6. public long lastModified():获取最后一次修改时间,毫秒值。
7. public String[] list(): 获取指定目录下的所有文件或者文件目录的名称数组。
8. public File[] listFiles():获取指定目录下的所有文件或者文件目录的File数组。
9. public boolean renameTo(File dest):把文件重命名为指定的文件路径。
比如:file1.renameTo(file2)为例:
要想保证返回true,需要file1在硬盘中是存在的,且file2不能在硬盘中存在。
10. public boolean isDirectory():判断是否是文件目录。
11. public boolean isFile():判断是否是文件。
12. public boolean exists():判断文件是否存在。
13. public boolean canRead():判断是否可读的。
14. public boolean canWrite():判断是否是可写的。
15. public boolean isHidden(): 判断是否是隐藏的。
16. public boolean createNewFile():创建文件,若文件存在,则不创建,返回false。
17. public boolean mkdir(): 创建文件目录。如果此文件目录存在,就不创建了。上级目录不存在,则不创建了。
18. public boolean mkdirs():创建文件目录,如果上层文件目录不存在,一并创建。
19. public boolean delete(): 删除文件或者文件夹,删除注意事项:java中的删除不走回收站。
IO流原理及流的分类
• 数据的输入输出操作以“流(Stream)”的方式进行。
• 流的分类:
• 按操作数据单位不同分为:字节流(8bit),字符流(16bit)
• 按照数据流的流向不同分为:输入流、输出流。
• 按流的角色的不同分为:节点流、处理流。
抽象基类 字节流 字符流
输入流:InputStream Reader
输出流:OutputStream Writer
• 抽象基类 节点流(或文件流) 缓冲流(处理流的一种)
• InputStream FileInputStream BufferedInputStream
• OutputStream FileOutputStream BufferedOutputStream
• Reader FileReader BufferedReader
• Writer FileWriter BufferedWriter
FileReader的使用
一、方法:
FileReader f = null;
try {
//1.实例化File对象,指明要操作的文件。
File file = new File("src\\com\\atguigu\\IOTest\\hello.txt");
System.out.println(file.getAbsolutePath());
//2.提供具体的流
FileReader f = new FileReader(file);
//3.数据的读入,read():以整型的形式返回读入的一个字符,如果到达文件末尾则返回-1.
int data = f.read();
while(data != -1){
System.out.print((char)data);
data = f.read();
}
}catch (IOException e){
}
finally {
//4.流的关闭操作。
try {
if(f != null)
f.close();
} catch (IOException e) {
e.printStackTrace();
} }
说明:异常的处理,为了保证流一定可以关闭,则用try-catch-finally。 要读入的文件一定要存在。
四步总结:
1.File类的实例化。
2.FileReader流的实例化(或者换成其他流)。
3.读入操作(或其他操作)。
4.资源的关闭。
二、FileReader中的read()方法。
f.read() //以整型的形式返回读入的一个字符,如果到达文件末尾则返回-1.
f.read(char[] c) //返回每次读入c数组中的字符的个数,如果达到文件末尾则返回-1.
f.read(char[] c, int off, int len) //从off开始,往c中放len个字符,返回放入的个数。
String s = new String(c,0,len).
f.readLine(); 读一行,以String返回,没有了返回null,不包含换行符。
f.newLine();提供换行。
FileWriter的使用
一、方法:
FileWriter f = null;
try {
//1.提供File对象,指明要写到的文件。
File file = new File("dream.txt");
//2.创建FileWriter流。
f = new FileWriter(file);
//3.写出的操作
f.write("你有一个梦想");
} catch (IOException e) {
e.printStackTrace();
}finally {
if(f != null) {
try {
//4.关闭流
f.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
说明:
1. 输出操作,对应的File可以不存在。
如果不存在,在输出的过程中,会自动创建此文件。
如果存在:
如果使用的流构造器是:FileWriter(file,false)/FileWriter(file): 对原有文件的覆盖。
如果使用的流构造器是:FileWriter(file,true):不会对原有文件覆盖,而是在原有文件的末尾追加内容。
注:不能使用字符流处理图片这种字节类型的数据。
FileInputStream和FileOutputStream的使用。和FileReader、FileWriter相似。
字符流和字节流的使用场景
结论:
对于文本文件(.txt,.java,.c,.cpp),使用字符流处理。
对于非文本文件(.jpg,.mp3,.mp4,avi,.doc,.ppt,…),使用字节流处理。
缓冲流的使用
处理流之一:
1.缓冲流:
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter
2.作用:提供流的读取、写入的速度。
原因:内部提供了一个缓冲区。8192字节的。
3.步骤:
1. 造文件
2. 造流
2.1 造节点流 / 造字节流-》造转换流
2.2 造缓冲流(将字节流包装)
3.复制的细节:读取、写入
4.资源关闭:
要求:先关闭外层的流,再关闭内层的流。
说明:关闭外层流的同时,内层也会自动关闭。
说明:
buf.flash() //刷新缓冲区
处理流:包在已有的流之上。
标准流-输入输出流
1.标准的输入、输出流
System.in :标准的输入流,默认从键盘输入。
System.out: 标准的输出流,默认从控制台输出。
2.System类的setIn(InputStream is) / setOut(PrintStream ps)方式重新指定输入和输出位置。
3.使用System.in实现,System.in --→ 转换流 ----→ BufferedReader的readLine()
打印流
打印流:提供了一系列重载的print和println方法。输出的数据不想在控制台,可以使用setOut方法
1.PrintStream
2.PrintWriter
数据流
数据流:
DataInputStream和DataOutputStream
作用:用于读取或写出基本数据类型的变量或字符串。
对象流
处理文件中对应的对象类型(属于处理流) ->需要首先声明一个节点流,将节点流作为构造器参数放在对象流中。
1.ObjectInputStream和ObjectOutputStream
作用:用于存储和读取基本数据类型数据或对象地处理流。
2. 序列化:用ObjectOutputStream类保存基本数据类型数据或对象的机制。 内存->文件
反序列化:用ObjectInputStream类读取基本类型数据或对象的机制。 文件->内存
3. 对象序列化机制:允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将
这种二进制流传输到网络地另一个网络节点。//当其他程序获取了这种二进制流,就可以恢复成原来地Java对象。
4.要想一个java对象实现序列化需要满足:
1.对象类要实现Serializable接口 //没有抽象方法,是一个标识接口。
2.需要指定一个全局常量serialVersionUID,即:
public static final long serialVersionUID = 45451151515415L
一定要指定,未指定的话虽然可以默认生成,但是修改时会出问题。
3.除了当前类需要可序列化外,该类中的所有属性也必须可以序列化。基本数据类是可序列化的。
说明:ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量。
transient表示不可序列化。
随机存取文件流
RandomAccessFile类:
- RandomAccessFile直接继承于java.lang.Object类,实现了DataInput和DataOutput接口。
2. RandomAccessFile既可以作为一个输入流,又可以作为一个输出流。通过指定构造器中的“r”,"rw"等。
3. 如果RandomAcessFile作为输出流,写出到的文件如果不存在,则在执行过程中自动创建。
如果文件存在,则会对原有文件内容从头覆盖。可以通过seek(int pos) 指定覆盖位置。
4.通过操作实现文件的插入操作。
5.seek(int pos),方法可以指定覆盖文件中的某个位置,非常方便。
NIO概述
Java NIO概述
- Java NIO(New IO,Non-Blocking IO)是从Java 1.4版本开始引入的一套新的IO API,可以代替标准的Java IO API。
NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的)、基于通道的IO操作,
NIO将以更加高效的方式进行文件的读写操作。
2.Java API中提供了两套NIO,一套针对标准输入输出NIO,另一套就是网络编程NIO。
|-----java.nio.channels.Channel
|---FileChannel: 处理本地文件
|---SocketChannel:TCP网络编程的客户端的Channel。
|---ServerSocketChannel:TCP网络编程的服务器端的Channel。
|---DatagramChannel: UDP网络编程中发送端和接收端的Channel。
jdk7 后,产生了NIO.2.
Path、Paths、Files。
Path可以看作是File类的升级版。
网络编程概述
Java实现网络编程只需要关注Java实现的网络库。
网络编程的目的:直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯。
一、网络编程中有两个主要的问题:
1.如何准确地定位网络上一台或多台主机,定位主机上的特定的应用。
2.找到主机后如何可靠高效地进行数据传输
二、网络编程中的两个要素:
1.对应问题一:IP和端口号。
2.对应问题二:提供网络通信协议:TCP/IP参考模型
IP地址的理解
- IP:唯一的标识Internet上的计算机(通信实体)
2. 在Java中使用InetAddress类代表IP。
3. IP分类:IPv4和IPv6,万维网和局域网。
4. 本地回路地址:127.0.0.1 对应着localhost。
5. 如何实例化InetAddress:
InetAddress inet1 = InetAddress.getByName(域名/IP);
InetAddress inet2 = InetAddress.getLocalHost(); //获取本机IP
两个常用方法:
inet1.getHostName() //获取主机名
inet1.getHostAddress() //获取IP地址
IP和端口号
• IP地址标识主机,端口号标识正在计算机上运行的进程
1. 不同进程有不同的端口号
2. 端口号被规定为一个16位的整数0~65535.
3. 端口分类:
1) 公认端口:0~1023.被预先定义的服务通信占用(HTTP占用80, FTP占用21, Telnet占用23)
2) 注册端口:1024~49151.分配给用户进程或应用程序。如(Tomcat占用端口8080, MySQL占用端口3306,Oracle占用端口1521等)
3) 动态/私有端口:49152~65535.
• 端口号与IP地址的组合得出一个网络套接字:Socket。因此网络编程也成为Socket编程。
网络协议
TCP协议:
1) 使用TCP协议前,须先建立TCP连接,形成传输数据通道
2) 传输前,采用“三次握手”方式,点对点通信,是可靠的。
3) TCP协议进行通信的两个应用进程,客户端、服务器
4) 在连接中可进行大数据量的传输
5) 传输完毕,需要释放已建立的连接,效率低。
UDP协议:
1) 将数据、源、目的封装成数据包,不需要建立连接
2) 每个数据报的大小限制在64k内
3) 发送不管对方是否准备好, 接收方收到也不确认,故是不可靠的
4) 可以广播发送
5) 发送数据结束时无序释放资源, 开销小,速度快。
TCP网络编程
客户端(Client):
1.创建Socket对象,指明服务器的ip和端口号
InetAddress inet = InetAddress.getByName(“127.0.0.1”);
Socket socket = new Socket(inet,8899);
2.获取一个输出流,用于输出数据
os = socket.getOutputStream();
3.写出数据的操作
os.write(“你好,我是客户端”.getBytes());
4.资源的关闭
服务器端(server):
1.创建服务器端的ServerSocket,指明自己的端口号。
ServerSocket ss = new ServerSocket(8899);
2.调用accept()表示接收来自客户端的socket。
Socket socket = ss.accept();
3.获取输入流。
InputStream in = socket.getInputStream();
4.读取输入流中的数据。
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] bytes = new byte[5];
int len = 0;
while ((len = in.read(bytes)) != -1){
baos.write(bytes,0,len);
}
可以用socket.getInetAddress().getHostName() //获取是谁发来的。
5.关闭资源。
UDP网络编程
UDP网络编程:
系统不保证UDP数据包一定能够安全送到目的地,也不确定什么时候可以抵达。
发送方:
public void sender(){
DatagramSocket socket = new DatagramSocket();
String str = "我是UDP方式发送的导弹";
byte[] data = str.getBytes();
InetAddress inet = InetAddress.getLocalHost();
DatagramPacket packet = new DatagramPacket(data,0,data.length,inet,9090);
socket.send(packet);
socket.close();
}
接收方:
DatagramSocket socket = null;
try {
socket = new DatagramSocket(9090);
byte[] data = new byte[100];
DatagramPacket packet = new DatagramPacket(data,0,data.length);
socket.receive(packet);
System.out.println(new String(packet.getData(), 0, packet.getLength()));
} catch (IOException e) {
e.printStackTrace();
} finally {
if(socket != null)
socket.close();
}
URL类的理解与实例化
- URL:统一资源定位符,对应着互联网的某一资源网址。
- URL的基本结构由5部分组成:
<传输协议>://<主机名>:/<文件名>#片段名?参数列表
http://192.168.1.100:8080/helloworld/index.jsp#a?username=shkstart&password=123
#片段名:即错点,例如看小说,直接定位到章节
参数列表格式:参数名=参数值&参数名=参数值- URL url = null;
try {
url = new URL(“http://baidu.com”);
} catch (MalformedURLException e) {
e.printStackTrace();
}
System.out.println(url.getProtocol()); //获取URL的协议名
System.out.println(url.getHost()); //获取URL的主机名
System.out.println(url.getPort()); //获取端口号
System.out.println(url.getPath()); //获取URL的文件路径
System.out.println(url.getFile()); //获取URL的文件名
System.out.println(url.getQuery()); //获取URL的查询名
反射的概述
• Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期,借助于Reflection API取得任何类的内部信息,
并能直接操作任意对象的内部属性和方法。
• Java反射机制提供的功能
1. 在运行时判断任意一个对象所属的类
2. 在于运行时构造任意一个类的对象
3. 在运行时判断任意一个类所具有的成员变量和方法
4. 在运行时获取泛型信息
5. 在运行时调用任意一个对象的成员变量和方法
6. 在运行时处理注解
7. 生成动态代理
• 反射相关的主要API
1.java.lang.Class: 代表一个类
2.java.lang.reflect.Method 代表类的方法
3.java.lang.reflect.Field: 代表类的成员变量
4.java.lang.reflect.Constructor: 代表类的构造器
利用反射构造对象
Class clazz = Person.class;
//1.通过反射,创建Person类的对象
Constructor constructor = clazz.getConstructor(String.class,int.class);
Object object = constructor.newInstance("张三",10);
Person p = (Person) object;
System.out.println(p.toString());
//2.通过反射,调用对象属性、方法
//调用属性
Field age = clazz.getDeclaredField("age");
age.set(p,12);
System.out.println(p.toString());
//调用方法
Method show = clazz.getDeclaredMethod("show");
show.invoke(p);
//反射可以调用私有的构造器、属性、方法
Constructor cons = clazz.getDeclaredConstructor(String.class);
cons.setAccessible(true);
Person p1 = (Person)cons.newInstance("chenxiaolin");
System.out.println(p1);
//访问私有属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p1,"888888");
System.out.println(p1.toString());
//访问私有方法
/*遇到重载的方法会怎么样,也是根据参数列表调整*/
Method show1 = clazz.getDeclaredMethod("show",String.class);
show1.setAccessible(true);
show1.invoke(p1,"zhongguoren");
反射与封装性
一、通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底用哪个?
建议:直接new的方式
什么时候会使用反射的方式。 反射的特征:动态性,运行期间不知道是造哪个对象。
二、反射机制与面向对象中的封装性是不是矛盾?如何看待两个技术?
不矛盾,
Class类的理解
关于java.lang.Class类的理解:
1.类的加载过程:
程序经过javac.exe命令后,会生成一个或多个字节码文件(.class结尾),接着我们使用java.exe命令对某个字节码文件进行解释运行。
相当于将某个字节码文件加载到内存中,此过程就称为类的加载。加载到内存中的类,称为运行时类,此运行时类,就作为Class的一个
实例。
换句话说:Class类的一个实例就对应着一个运行时类。
• 哪些类型可以有Class对象?
1. class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类。
2. interface:接口
3. []:数组
4. enum:枚举
5. annotation:注解@interface
6. primitive type:基本数据类型
7. void
8. CLass本身
注:只要数组的元素类型与维度一样,就是同一个Class。
获取Class类的实例的四种方式
• 加载到内存中的运行时类,会缓存一定的时间,在此时间之内,可以通过不同的方式获取此
运行时类,获得的时同一个运行时类。
方式一:调用运行时类的属性:.class
Class clazz1 = Person.class
方式二:通过运行时类的对象,调用getClass()
Person p1 = new Person();
Class clazz2 = p1.getClass();
方式三:调用class的静态方法:forName(String classPath)
Class clazz3 = Class.forName("com.atguigu.java.Person")
方式四:使用类的加载器:CLassLoader
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");
clazz1 == clazz2 == clazz3 == clazz4。输出为true。
ClassLoader加载配置文件
//方式一:
Properties pros = new Properties();
fis = new FileInputStream("src//jdbc1.properties");
pros.load(fis);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
System.out.println(user + "-" + password);
//方式二:
Properties pros = new Properties();
ClassLoader classLoader = Test_ClassLoader.class.getClassLoader();
fis = classLoader.getResourceAsStream("jdbc1.properties");
pros.load(fis);
通过反射创建运行时类的对象
1.调用newInstance()方法,创建对应的运行时类的对象,调用的是空参构造器。
要求:
• 运行时类必须提供空参的构造器
• 空参的构造器的访问权限要够,通常,设置为public。
在javabean中要求提供一个public的空参构造器,原因:
1.便于通过反射,创建运行时类的对象
2.便于子类继承此运行时类,默认调用super()时,保证父类有此构造器。
Class clazz = Person.class;
Person p = clazz.newInstance();
获取运行时类属性及内部结构
调用运行时类的指定结构:属性、方法、构造器。
获取属性:
@Test
public void test1(){
Class<Person> clazz = Person.class;
//获取属性结构
//getFields():获取当前运行时类及其父类中声明为public的属性
Field[] field = clazz.getFields();
for(Field f : field){
System.out.println(f);
}
//getDeclaredFields():获取当前运行时类中声明的所有属性,不包含父类中的。
Field[] fields = clazz.getDeclaredFields();
for(Field f : field){
System.out.println(f);
}
//获取属性的权限修饰符、数据类型、变量名
for(Field f:fields){
//获取权限修饰符
int modifiers = f.getModifiers();
System.out.println(Modifier.toString(modifiers));
//获取数据类型
Class type = f.getType();
System.out.println(type);
//获取变量名
String name = f.getName();
System.out.println(name);
}
}
获取方法:
@Test
public void test2(){
Class<Person> clazz = Person.class;
//getMethods():获取当前运行时类及其父类中声明为public的方法
Method[] methods = clazz.getMethods();
for(Method method : methods){
System.out.println(method);
}
//获取当前运行时类声明的所有方法
Method[] methods1 = clazz.getDeclaredMethods();
for(Method method:methods1){
System.out.println(method);
}
}
还可以获取方法的权限修饰符、返回值类型 方法名(参数类型1,参数类型2) throws XxxException等。
获取构造器:
public void test3(){
Class<Person> clazz = Person.class;
//getConstructors()获取当前运行时类中声明为public的构造器
Constructor[] cons = clazz.getConstructors();
for(Constructor con:cons){
System.out.println(con);
}
//getDeclaredConstructors获取当前运行时类声明的所有的构造器
Constructor[] cons1 = clazz.getDeclaredConstructors();
for(Constructor con : cons){
System.out.println(con);
}
}
获取父类及父类的泛型
@Test
public void test4(){
//获取当前类的父类
Class<Person> clazz = Person.class;
Class su = clazz.getSuperclass();
System.out.println(su.getName());
//获取带泛型的父类
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);
//获取泛型的类型
ParameterizedType paramType = (ParameterizedType) genericSuperclass;
Type[] type = paramType.getActualTypeArguments();
System.out.println(type[0].getTypeName());
}
获取当前类实现的接口:getInterface()
获取当前类的注解: getAnnotation()
获取当前类的包:getPackage()
调用运行时类的指定结构
调用运行时类的指定结构:属性、方法、构造器。
调用运行时类的指定结构:属性、方法、构造器。
获取指定的属性:
Class<Person> clazz = (Class<Person>) Class.forName("com.atguigu.FanSheTest.Person");
Person p = clazz.newInstance();
Field name = clazz.getDeclaredField("name");
name.setAccessible(true); //保证当前属性时可访问的
name.set(p,"10");
System.out.println(name.get(p));
获取指定的方法:
Class<Person> clazz = (Class<Person>) Class.forName("com.atguigu.FanSheTest.Person");
Person p = clazz.newInstance();
Method show = clazz.getDeclaredMethod("show", String.class);
show.setAccessible(true);
/*invoke(): 参数1:方法的调用者, 参数2:给方法赋值的实参
* invoke()的返回值即为方法的返回值。
* */
//静态方法时,p可以省略不写。
Object obj = show.invoke(p,"xiaogougou");
System.out.println(obj);
获取指定构造器:
Class<Person> clazz = (Class<Person>) Class.forName("com.atguigu.FanSheTest.Person");
Constructor<Person> cons = clazz.getDeclaredConstructor(String.class,int.class);
Person p = cons.newInstance("li",10);
System.out.println(p);
代理模式:动态代理或者静态代理
1.静态代理实例:代理类和被代理类在编译期间,就确定下来了。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public class DongTaiDaiLi {
public static void main(String[] args) {
/*静态代理*/
NikeClothFactory nike = new NikeClothFactory();
ProxyClothFactory pro = new ProxyClothFactory(nike);
pro.produceCloth();
}
}
interface ClothFactory{
void produceCloth();
}
/*代理类*/
class ProxyClothFactory implements ClothFactory{
private ClothFactory factory;
public ProxyClothFactory() {
}
public ProxyClothFactory(ClothFactory factory) {
this.factory = factory;
}
@Override
public void produceCloth() {
System.out.println("代理工厂做一些准备");
factory.produceCloth();
System.out.println("生产完成");
}
}
//被代理类,被代理类实例化后给代理类
class NikeClothFactory implements ClothFactory{
@Override
public void produceCloth() {
System.out.println("生产nike产品");
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2.动态代理: 动态创建代理类。
要实现动态代理,需要解决的问题:
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
问题二:当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
package com.atguigu.FanSheTest;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface Human{
String getBelieve();
void eat(String food);
}
//被代理类
class SupMan implements Human{
@Override
public String getBelieve() {
return "I believe I can fly";
}
@Override
public void eat(String food) {
System.out.println("我喜欢吃" + food);
}
}
class ProxyFactory{
//调用此方法,返回一个代理类的对象
public static Object getProxyInstance(Object obj){
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
//返回一个代理类对象,问题一、二的解决。
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
}
}
class MyInvocationHandler implements InvocationHandler{
private Object obj;
public void bind(Object obj){
this.obj = obj;
}
//当我们通过代理类的对象调用方法A时,就会调用下面的invoke()方法。
//将被代理类要执行的方法A的功能就声明在invoke()中。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//proxy:代理类对象
//method:即为被代理类对象调用的方法,此方法也就成为了被代理类对象要调用的方法。
//obj:被代理类对象
Object returnVal = method.invoke(obj, args);
//上述方法的返回值即为invoke方法的返回值。
return returnVal;
}
}
public class DongTaiDaiLi {
public static void main(String[] args) {
SupMan supMan = new SupMan();
Human proxySupMan = (Human)ProxyFactory.getProxyInstance(supMan);
String s = proxySupMan.getBelieve();
System.out.println(s);
proxySupMan.eat("xiaopingguo ");
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public static void main(String[] args) {
/*传统写法*/
Comparator<Integer> com1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
int comr1 = com1.compare(21,12);
System.out.println(comr1);
/*Lambda表达式,接口里边只重写一个方法*/
Comparator<Integer> com2 = (o1,o2) -> o1.compareTo(o2);
int comr2 = com2.compare(21,12);
System.out.println(comr2);
/*方法的引用*/
Comparator<Integer> com3 = Integer :: compare;
int comr3 = com3.compare(21,12);
System.out.println(comr3);
}
- Lambda表达式语法的使用一:
举例:(o1,o2)->Integer.compare(o1,o2);
格式:
->: lambda操作符 或箭头操作符
->: 左边:lambda形参列表(其实就是接口中的抽象方法的形参列表)
->:右边:lambda体(其实就是重写的抽象方法的方法体)
使用(分六种情况介绍):
语法格式一:无参,无返回值
Runnable r = ()-> {System.out.println(“Hello Lambda!”);};
语法格式二:Lambda需要一个参数,但是没有返回值
Comsumer con = (String str) -> {System.out,println(str);};
语法格式三:数据类型可以省略,因为可有编译器推断得出,称为“类型推断”
Comsumer con = (str) -> {System.out,println(str);};
语法格式四:Lambda若只需要一个参数时,参数的小括号可以省略。
Comsumer con = str -> {System.out,println(str);};
语法格式五:Lambda需要两个或以上的参数,多条执行语句,并且可以有返回值。
Comparatorcom = (x,y)->{
System.out.println(“实现函数时接口方法!”);
return Integer.compare(x,y);
};
语法格式六:当Lambda体只有一条语句时,return与大括号若有,都可以省略。
Comparator com = (x,y) -> Integer.compare(x,y);
本质:作为函数式接口的实例--一个对象。
• 定义:只包含一个抽象方法的接口,称为函数式接口。
• 可以通过Lambda表达式来创建该接口的对象。
• 可以在接口上定义@FunctionalInterface注解。
• 在java.util.function包下定义了Java 8的 丰富的函数式接口。
方法引用和构造器引用
• 方法引用
1. 使用情境:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用。
2.方法引用,本质上就是lambda表达式,而lambda表达式作为函数式接口的实例,所以方法引用,也是函数式接口的实例。
- 使用格式: 类(或对象):: 方法名
4.具体分为如下的三种情况:
对象 :: 非静态方法
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//Consumer中的void accept(T) 和 PrintStream中的 println(T)
PrintStream ps = System.out;
Consumer cons = ps :: println;
cons.accept("你好");
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
类 :: 静态方法
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//Comparator中的int compare(T1,T2) 和 Integer中的int compare(T1,T2)
Comparator<Integer> com = Integer :: compare;
System.out.println(com.compare(21, 12));
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
类 :: 非静态方法 参数不匹配,但是有的参数作为方法的调用者。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//BiPredicate中的boolean test(T1,T2) 和 String中的boolean t1.equals(t2)
BiPredicate<String,String> bi = String :: equals; //这种的只能用类来调用静态方法。
System.out.println(bi.test("a", "b"));
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5.方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与
方法引用的方法的形参列表和返回值类型相同(针对情况一和情况二)。
• 构造器引用
和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。
抽象方法的返回值类型即为构造器所属的类的类型。
• 数组引用
可以把数组看作是一个特殊的类。
Stream API
一、StreamAPI概述
• 使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询。 Stream主要是来做计算的。集合Collection讲的是数据与内存打交道。
• 注意:
• Stream自己不会存储元素。
• Stream不会改变源对象,相反,他们会返回一个持有结果的新Stream。
• Stream操作是延迟执行的,这意味着他们会等到需要结果的时候才执行。
• Stream的执行流程:
• 1.Stream的实例化
• 2.一系列的中间操作(过滤,映射、、、、):一个中间操作链,对数据源的数据进行处理。
• 3.终止操作:一旦执行终止操作,就执行中间操作链,并产生结果,之后,不会再被使用。
二、Stream的实例化
1. 创建Stream方式一:通过集合。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//方式一:通过集合
List<Integer> list = Arrays.asList(123,456);
// default Stream stream() : 返回一个顺序流。按顺序取
Stream<Integer> stream = list.stream();
//default Stream prarallelStream(): 返回一个并行流。多线程取
Stream<Integer> stream1 = list.parallelStream();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2.创建Stream方式二:通过数组
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//方式二:通过数组
int[] arr = new int[]{1,2,3,4,5};
//调用Arrays类的static Stream stream(T[] array): 返回一个流.
// int返回的是IntStream,long返回的是LongStream。其他的返回Stream。
IntStream stream2 = Arrays.stream(arr);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3.创建Stream方式三:通过Stream的of方法。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Stream<Integer> stream3 = Stream.of(1, 2, 3, 4, 5);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4.创建Stream方式四:创建无限流。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//方式四:创建无限流
//迭代
//public static Stream iterate(final T seed,final UnaryOperator f)
Stream.iterate(0,t->t + 2).limit(10).forEach(System.out :: println);
//生成
//public static Stream generate(Supplier s)
Stream.generate(Math::random).limit(10).forEach(System.out ::println);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
三、中间操作
1. 筛选与切片
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int[] arr = new int[]{1,2,3,4,4};
//1.filter(Predicate p) - 接收Lamda,从流中排除某些元素
IntStream stream = Arrays.stream(arr);
stream.filter(a -> a > 2).forEach(System.out :: println);
//2.limit(n):阶段流,使其元素不超过给定数量
Arrays.stream(arr).limit(2).forEach(System.out :: println);
//3.skip(n):跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回空流。
Arrays.stream(arr).skip(3).forEach(System.out :: println);
//4.distinct():通过流所生成元素的hashCode()和equals()去除重复元素
Arrays.stream(arr).distinct().forEach(System.out :: println);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2. 映射
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//1.map(Function f): 接收一个函数作为参数 ,将元素转换成其他形式或提取信息。
// 该函数会被应用到每一个元素上,并将其映射为其他形式。 类似于add
String[] arr = new String[]{"aa","bb"};
Arrays.stream(arr).map(str -> str.toUpperCase()).forEach(System.out::println);
//2.flatMap(Function f):接收一个函数作为参数,
// 将流中的每一个值都换成另一个流,然后把所有流连成一个流,类似于addAll,便于解决集合里边套集合
Arrays.stream(arr).flatMap(StreamTest::fromStringToStream).forEach(System.out::println);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3.排序
四、终止操作
1. 匹配与查找
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//allMatch(Predicate p):检查是否匹配所有元素
List<Integer> list = Arrays.asList(12,1,2,86,4,6);
System.out.println(list.stream().allMatch(a -> a > 50));
//anyMatch(Predicate p):检查是否至少匹配一个元素
System.out.println(list.stream().anyMatch(a -> a > 50));
//noneMatch(Predicate p):检查是否没有匹配的元素
System.out.println(list.stream().noneMatch(a -> a > 50));
//findFirst:返回第一个元素
Optional<Integer> o = list.stream().findFirst();
System.out.println(o);
//findAny():返回当前流中的任意元素
Optional<Integer> o1 = list.stream().findAny();
System.out.println(o1);
//count():返回流中的元素个数,返回的值为long。
System.out.println(list.stream().filter(a -> a > 10).count());
//max(Comparator c):返回流中的最大值
Optional<Integer> o2 = list.stream().filter(a -> a > 10).max((x,y)-> x - y);
System.out.println(o2);
//min(Comparator c):返回流中的最小值
Optional<Integer> o3 = list.stream().filter(a -> a > 10).min((x,y) -> x - y);
System.out.println(o3);
//forEach(Consumer c) -内部迭代
list.stream().filter(a -> a > 10).forEach(System.out :: println);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2. 归约
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//reduce(T identity,BinaryOperator),可以将流中元素反复结合起来,得到一个值,返回T。
List<Integer> list = Arrays.asList(12,1,2,86,4,6);
System.out.println(list.stream().reduce(0, Integer::sum));
//reduce(BinaryOperator):可以将流中元素反复结合起来,得到一个值,返回Optional.
Optional<Integer> o = list.stream().reduce(Integer::sum);
System.out.println(o);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3.收集
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//collect(Collector c)--将流转换成其他形式,接收一个Collector接口的实现。将Stream转换成list或map。
List<Integer> list = Arrays.asList(12,1,2,86,4,6);
List<Integer> list1 = list.stream().filter(x -> x > 10).collect(Collectors.toList());
list1.forEach(System.out ::println);
Set<Integer> set1 = list.stream().filter(x -> x > 10).collect(Collectors.toSet());
set1.forEach(System.out :: println);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Optional类
Optional:解决空指针异常
Optional类(java.util.Optional)是一个容器类,它可以保存类型T的值,代表这个值存在。
或者仅仅保存null,表示这个值不存在。原来用null表示一个值不存在,现在Optional可以更好的表达这个概念,并且可以避免空指针异常。
1. Optional类的实例化:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//Optional.of(T t):创建一个Optional实例,t必须非空。
//Optional.empty(): 创建一个空的Optional类的实例。
//Optioanl.ofNullable(T t):t可以为null。
Person p = new Person();
Optional<Person> o = Optional.of(p);
System.out.println(o);
p = null;
Optional<Person> o1 = Optional.ofNullable(p);
System.out.println(o1);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2.使用:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public void test1(){
Person p = null;
System.out.println(getPage(p));
}
public int getPage(Person person){
Optional<Person> p1 = Optional.ofNullable(person);
Person p = p1.orElse(new Person("121",4)) ;
return p.getAge();
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~