一、Java基本语法
(一)关键字与保留字
关键字:被Java赋予特殊含义,用作专门用途的单词
特点:所有关键字都是小写
定义数据类型
- class
- interface
- enum
- byte:1字节,-128~127
- short:2字节,-215~215-1
- int:4字节
- long:8字节,声明long变量,必须以“l”或者“L”结尾
- float:4字节,精确到7位有效数字,声明float变量,必须加“f”或者“F”结尾
- double:8字节,精度是float的两倍
- char:2字节,内部必须写一个字符且只能写一个字符
- boolean
- void
定义流程控制
- if
- else
- switch
- case
- default
- while
- do
- for
- break
- continue
- return
定义访问权限修饰符
- private
- protected
- public
定义类、函数、变量修饰符
- abstract
- final
- static
- synchronized
定义类与类之间
- extends
- implements
定义建立实例及引用实例或者判断实例
- new
- this
- super
- instanceof
异常处理
- try
- catch
- finally
- throw
- throws
用于包
- package
- import
其他修饰符
- native
- strictfp
- transient
- volatile
- assert
(二)变量
1、变量的分类
(按数据类型)
基本数据类型:
- 整型:byte / short / int / long
- 浮点型:float / double
- 布尔型:boolean
引用数据类型:
- 类
- 接口
- 数组
(按声明的位置)
成员变量:
局部变量:
2、基本数据类型转换
自动类型提升
当容量小的数据类型与容量大的数据类型进行运算时,结果自动提升为容量大的。
byte、short、char - int - long - float - double
特别地,byte、short、char三种类型运算时结果为int类型
强制类型转化
当需要将容量大的数据类型转化为容量小的数据类型,需要使用强转符
double num1 = 12.9;
int num2 = (int)num1;//截断操作,num2 = 12
//特例1
int num3 = 128;
byte num4 = (byte)num3;//num4 = -128
//原因:int类型强转为byte只截取最后8个字节,截取为1000 0000,此时1为符号位,而-128的补码就等于1000 0000
//特例2
long num5 = 1234;//long型没有加L,默认数值类型为int,此处相当于自动类型提升
long num6 = 12334556678786677;//超出int范围,编译报错
//特例3
float num7 = 3.4;//double类型转化为float会有精度损失
byte num8 = 12;
byte num9 = num8 + 1;//int类型转化为byte类型会有精度损失
float num10 = num8 + 12.3;//double类型转化为float会有精度损失
特例:引用数据类型String
String和8种基本数据类型变量做运算时,运算只能是连接运算,运算的结果为String类型
(三)进制
二进制:0b开头
八进制:0开头
十进制:
十六进制:0x开头
**反码:**符号位不变,各位取反
**补码:**反码加一
正数的原码、反码、补码都相同
(四)运算符
1、算术运算符
+,-,*,/,%,++,–
除号
int num1 = 12;
int num2 = 5;
double result1 = num1 / num2;//2
double result2 = num1 / (num2 + 0.0);//2.4
double result3 = (num1 + 0.0) / num2;//2.4
double result4 = (double)num1 / num2;//2.4
取余:结果的正负号取决于被模数
int m1 = -12;
int n1 = 5;// m1 % n1 = -2
自增自减:不会改变变量的数据类型
short s = 10;
s++;//区别于s = (short)(s + 1);
byte b = 127;
b++;//b = -128,0111 1111加一为1000 0000,此时为-128
2、赋值运算符
=,+=,-=,*=,/+,%=
自增自减一样,不会改变变量的数据类型
3、比较运算符
==,!=,>,<,>=,<=,instanceof
4、逻辑运算符
&,&&,|,||,!,^异或
if(false & num > 0){
//逻辑与:左边为false时,右边会继续运算
}
if(false && num > 0){
//短路与:左边为false时,右边不会继续运算
}
5、位运算符
操作的都是整型数据
<<左移
面试题:最高效的方法计算2*8 --> 2<<3
>>右移
>>>无符号右移(注意:没有<<<)
&与运算
|或运算
^异或运算:m=(m ^ n) ^ n
~取反
6、三元运算符
(条件表达式)?表达式1:表达式2
(五)程序流程控制
1、顺序结构
2、分支结构
if(){
}
if(){
}else{
}
if(){
}else if(){
}else if(){
}else{
}
switch(){
case:
break;
case:
break;
case:
break;
default:
break;
}
3、循环结构
for(int i = 0;i < 5;i++){
代码块;
}
while(条件表达式){
代码块;
}
do{
代码块;
}while(条件表达式);
特殊关键字:break(结束当前循环),continue(结束当次循环)
label:for(;;){
for(;;){
for(;;){
if(){
break label;//跳出指定循环
}
}
}
}
二、数组
(一)概念
-
数组是有序排列的
-
数组属于引用数据类型
-
创建数组对象会在内存中开辟一整块连续的空间
-
数组的长度一旦确定,就不能修改
(二)一维数组
1、一维数组的声明和初始化
数组一旦初始化完成,其长度就确定了
int[] arr1 = new int[]{1,2,3,4};//静态初始化
int[] arr1 = new int[5];//动态初始化
int arr1[] = new int[]{1,2,3,4};
int arr1[] = new int[5];
int arr1[] = {1,2,3};
2、调用数组元素
arr1[0];
arr1[1];
3、获得数组的长度
arr1.length;
4、数组的默认初始化值
//整型--0;
//浮点型--0.0;
//char--0或者‘\u0000’;注意不是‘0’;
//boolean:false;
//引用数据类型:null;
5、数组的内存解析
数组存放在堆空间中(heap)
(三)二维数组
1、二维数组的声明和初始化
int[][] arr2 = new int[][]{{1,2,3},{4,5,6}};
int[][] arr2 = new int[2][3];
int arr2[][] = new int[][]{{1,2,3},{4,5,6}};
int arr2[][] = new int[2][3];
int[] arr2[] = new int[2][3];
2、调用数组元素
arr2[0][1];
3、二维数组的长度
arr2.length;//2
arr2[0].length;//3
4、二维数组的默认初始化值
/**
外层元素:arr[0],arr[1]
内层元素:arr[0][0]
*/
System.out.println(arr);//地址值
System.out.println(arr[0]);//地址值
(四)Arrays工具类的使用
boolean equals(int[] a,int[] b);
String toString(int[] a);
void fill(int[] a,int val);
void sort(int[] a);//默认从小到大
三、面向对象
(一)面向对象的概念
三大特征:
- 封装性
- 继承性
- 多态性
(二)类和对象
1、类的五大成员
- 属性:对应类中的成员变量 / Field=属性=成员变量
- 行为:对应类中的成员方法 / Method = 成员方法 = 函数
- 代码块
- 构造器
- 内部类
//注意:
class Person{
//正确写法
int age1;
//正确写法
int age2 = 100;
//错误写法
int age3;
age3 = 100;//不能在属性后面写执行语句,可以在方法里赋值,或者在代码块里赋值
}
2、类和对象的使用
- 创建类,设计类的成员
- 创建类的对象
- 通过“对象.属性“或”对象.方法“调用对象的结构
3、对象的内存解析
在堆中new出来对象实体,栈空间的变量指向对象实体的地址,对象实体包含成员变量。
4、成员变量与局部变量的区别
相同点:
- 定义变量的格式:数据类型 变量名 = 变量值;
- 先声明,后使用;
- 变量都有其对应作用域。
不同点:
- 在类中声明的位置不同:属性-直接定义在类的{}中;局部变量-声明在方法内、代码块内、方法形参、构造器形参、构造器内部变量;
- 权限修饰符的不同:属性-可以在声明时指明其权限;局部变量-不可以使用权限修饰符;
- 默认初始化的值不同:属性-根据其类型都有默认初始化值;局部变量-没有默认初始化值;
- 在内存中加载位置不同:属性-加载到堆空间;局部变量:加载到栈空间。
5、匿名对象
class Phone{
double price;
public void sendEmail(){
}
}
Phone p = new Phone();//有名对象
new Phone().sendEmail;//匿名对象调用方法
- 创建的对象没有显式的赋给一个变量名
- 匿名对象只能调用一次
(三)方法
1、方法的重载:
overload:在同一个类中,允许存在多个同名的方法,只要参数个数或参数类型不同即可。和权限修饰符,返回值类型,方法体都没有关系。
2、可变个数形参:
public void show(String ... strs){
//TODO
}
//等同于public void show(String[] strs)
可变个数形参在方法的形参中,必须声明在末尾,最多只能声明一个可变形参
3、方法参数的值传递机制(重点):
- 形参是基本数据类型,将实参基本数据类型变量的==“数据值”==传递给形参;
- 形参是引用数据类型,将实参引用数据类型变量的==“地址值”==传递给形参。
//特殊的
char[] arr = new char[]{'a','b','c'};
System.out.println(arr);//此时输出的是abc,原因是println的重载
int[] arr1 = new int[]{1,2,3};
System.out.println(arr1);//地址值
(四)封装性
1、封装性的体现
-
将类的属性私有化(private),通过公共(public)的方法设置属性(set)和获取属性(get);
-
不对外暴露的方法;
-
单例模式…
2、权限修饰符
private,default/缺省,protected,public
private | default | protected | public | |
---|---|---|---|---|
类内部 | ☑️ | ☑️ | ☑️ | ☑️ |
同一个包 | ☑️ | ☑️ | ☑️ | |
不同包的子类 | ☑️ | ☑️ | ||
同一个工程 | ☑️ |
4种权限可以用来修饰类及类的内部结构:属性,方法,构造器,内部类
注意:对于class的权限修饰符只能使用public和default
3、构造器
constructor
作用:
- 创建对象
- 初始化对象属性
说明:
- 如果未定义构造器,系统默认提供空参的构造器;一旦定义构造器,系统便不再提供空参构造器;
- 格式:权限修饰符 类名(形参列表){};
- 一个类中定义的多个构造器,彼此构成重载;
- 一个类中至少有一个构造器。
4、属性赋值的先后顺序
- 默认初始化
- 显式初始化
- 构造器中赋值
- 通过“对象.方法“或”对象.属性“赋值
5、JavaBean
可重用组件,特点:
- 类是公共的;
- 有一个无参的公共的构造器;
- 有属性,且有对应的set、get方法。
6、关键字this,import
this
- this可以用来修饰:属性、方法、构造器
- this修饰属性、方法:this理解为当前对象;
- this修饰构造器:
public Person(){
//TODO
}
public Person(int age){
this();
this.age = age;
}
public Person(String name,int age){
this(age);
this.name = name;
}
package
- 为了更好的实现项目中类的管理;
- 使用package声明类或接口所属的包,声明在源文件的首行;
- 属于标识符,遵循命名规范(xxxyyyzzz);
- 每“.”一次,就代表一层文件目录;
- 同一个包下,不能命名同名的接口、类。
import
- 在源文件中显式的使用import结构导入指定包下的类、接口;
- 声明在包的声明和类的声明之间;
- 可以使用“xxx.*”表示导入xxx包下的所有结构;
- 如果使用的类或接口是java.lang包下定义的,可以省略import结构。
Person p1 = new Person("tom");
//不同包下的同类名的类,使用全类名的方式
com.wuhangji.pojo1.Person p2 = new com.wuhangji.pojo1.Person("jerry",23);
(五)继承性
1、继承性概述
好处:
- 减少了代码的冗余,提高了代码的复用率;
- 便于功能的扩展;
- 为之后多态性的提供了前提。
格式:
calss A extends B{}
A:子类,派生类,subclass
B:父类,超类,基类,superclass
体现:
一旦子类A继承父类B以后,子类A中就获取了父类声明的结构:属性、方法。
特别的,父类中声明为private的属性或方法,子类继承父类之后,实际上是获取到了父类中私有的结构。(封装性指的是看不看得见,继承性指的是拿不拿得到)
2、关于继承性的规定
- Java支持单继承和多层继承,不允许多重继承。
- 一个子类只能有一个父类;
- 一个父类可以拥有多个子类;
- 子父类是相对的概念(直接父类,间接父类);
- 如果没有显式声明一个类的父类,则此类继承于java.lang.Object类。
3、方法的重写
override/overwrite:子类继承父类之后,可以对父类中同名同参数的方法进行覆盖操作。
规定:
- 子类重写的方法的方法名、形参列表和父类被重写的方法的方法名、形参列表相同;
- 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符(子类不能重写父类中声明为private的方法)
- 父类被重写的方法的返回值为void,子类重写的方法的返回值也只能是void;
- 父类被重写的方法的返回值为A类,子类重写的方法的返回值只能是A类或者A类的子类;
- 父类被重写的方法的返回值为基本数据类型(eg:double),子类重写的方法的返回值也只能是相同的基本数据类型(double)(为什么不能自动类型提升,因为int和double是并列的类关系,不存在子父类关系);
- 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型。
4、关键字:super
super可以理解为:父类的;
super可以用来调用属性,方法:
//子类的属性
System.out.println(this.id);
//调用父类的属性(可能会调用间接父类的属性,直到找到为止)
System.out.println(super.id);
super可以用来调用构造器:没有显式的使用this()或者super(),则默认调用父类中的空参构造器。也就是说,在子类的多个构造器中,至少有一个构造器调用了父类的构造器。
注意:(1)必须声明在子类构造器的首行;(2)this()和super()不能同时出现
//super调用构造器
public Student(String name,int age,String major){
super(name,age);
this.major = major;
}
5、子类对象实例化过程
- 从结果上看:子类继承父类之后,就获取了父类中声明的属性或方法,堆空间就会加载所有父类中声明的属性;
- 从过程上看:通过子类的构造器创建子类对象时,一定会之间或者间接调用其父类的构造器,直到调用java.lang.Object类中的空参构造器为止。
(六)多态性
1、理解
多态性:可以理解为一个事物的多种形态
//父类的引用指向子类的对象
Person p1 = new Man();
Person p2 = new Women();
2、多态的使用
使用前提:1-类的继承;2-方法的重写
虚拟方法调用:
- 在编译期,只能调用父类中声明的方法;
- 在运行期,实际执行的是子类重写父类的方法。
注意:对象的多态性只适用于方法,不适用于属性
//父类Person id = 1001;
//子类Man id = 1002;
Person p1 = new Man();
p1.id;//此时id=1001;
多态是运行时行为
3、重载和重写的区别
重载:在同一个类中,允许存在多个同名的方法,只要参数个数或参数类型不同即可。
重写:子类继承父类之后,可以对父类中同名同参数的方法进行覆盖操作。
从编译和运行的角度上看:
重载对于编译器而言,重载的方法在编译期就已经确定了,可以称为“早绑定”或者“静态绑定”;
对于多态,只有等到方法调用的那一刻,解释运行器才会确定调用的方法,可以称为“晚绑定”。
4、关键字instanceof
有了多态性之后,内存实际加载了子类特有的属性和方法,但是由于变量声明为父类类型,导致编译时只能调用父类声明的属性和方法,如何调用子类特有的属性和方法?
//向上转型,即多态
Person p1 = new Man();
//向下转型
Man m1 = (Man)P1;
为了避免ClassCastException,使用instanceof关键字
a instanceof A;//判断对象a是否是类A的实例,如果是,返回true;反之返回false
a instanceof AA;//AA是A的父类,也返回true
转型的常见问题:
Person p1 = new Women();
Man m1 = (Man)p1;//编译通过,运行不通过
Person p2 = new Person();//new Person时,内存中根本没加载子类的属性和方法
Man m2 = (Man)p2;//编译通过,运行不通过
Object obj = new Man();
Person p3 = (Person)obj;//编译通过,运行通过
(七)Object类和包装类
1、Object类
- Object类是所有类的根父类;
- Object类中的方法具有通用性:equals() , toString() , getClass() , hashCode() , clone() , finalize() , wai() , notify() , notifyAll() …
== 和 equals() 区别
==:
- 比较的是基本数据类型变量,比较两个变量的保存的数据大小是否相同;
- 比较的是引用数据类型变量,比较的是两个对象的地址值是否相同,即两个引用是否指向同一个对象实体。
equals():
- 只能适用于引用数据类型;
- Object类中的equals()方法和==的作用是相同的,比较的是地址值;
- 像String、Date、File、包装类等都重写了equals()方法,比较的是对象的内容是否相同。
2、包装类Wrapper
基本数据类型<–>包装类
/**
基本数据类型 --> 包装类,直接调用包装类的构造器即可
*/
Integer int1 = new Integer(123);
Integer int2 = new Integer("123");
//特殊的
Boolean isMale;//默认值为null
/**
包装类 --> 基本数据类型,调用包装类的xxxValue()方法
*/
int1.intValue;
自动装箱与自动拆箱:
//自动装箱
int num1 = 10;
Integer int3 = num1;
//自动拆箱
int num2 = int3;
基本数据类型、包装类 <–> String
/**
基本数据类型、包装类 --> String
*/
//方式一:连接运算
int num1 = 10;
String str1 = num1 + "";
//方式二:调用String的valueOf()方法
String str2 = String.valueOf(num1);
/**
String --> 基本数据类型、包装类
*/
String str1 = "123";
String str2 = "true";
int num1 = Integer.parseInt(str1);
boolean b1 = Boolean.parseBoolean(str2);
面试题:
Object obj = true ? new Integer(1) : new Double(2.0);//三元运算符的前后的类型要相同,所以int会提升为double
System.out.println(obj);//1.0
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j);//false
/**
在Integer类中,提前造好了-128~127的数值,一旦超出这个范围就只能new一个新的对象
*/
Integer m = 1;
Integer n = 1;
System.out.println(m == n);//true
Integer x = 1;
Integer y = 1;
System.out.println(x == y);//false
(八)关键字static
static可以修饰属性、方法、代码块、内部类
1、修饰属性
-
用static修饰的属性称为静态属性(类变量);
-
创建类的多个对象,多个对象共享同一个静态变量。当一个对象修改静态变量,其他对象的中的静态变量也会跟着改变;
-
静态变量随着类的加载而加载,其加载早于对象的创建,所以可以通过“ 类.静态变量 ”的方式进行调用;
-
由于类只会加载一次,静态变量在内存中也只有一份,存在于方法区的静态域中;
举例:System.out , Math.PI ;
类变量和实例变量的内存解析:
- 在类加载的时候类变量就已经加载到了方法区的静态域;//nation=null
- 通过new在堆空间创建对象;//p1
- 实例变量赋值,在堆空间,地址指向方法区的常量池;//tom
class Person{
String name;
static String nation;
}
...
Person p1 = new Person();
p1.name = "tom";
...
2、修饰方法
- 随着类的加载而加载,可以通过 ” 类.静态方法 “调用;
- 静态方法中,只能调用静态方法或静态属性;非方法中,既可以调用静态方法或属性,也可以调用非晶态或属性;
- 在静态方法内,不能使用this关键字以及super关键字,因为static加载时对象还没有加载,静态属性和方法的生命周期与类的声明周期有关;
3、修饰代码块
代码块只能用static修饰,详情看“(十)代码块”部分。
4、修饰内部类
5、main()方法
- main()方法作为程序的入口;
- main()也是普通的静态方法;
- main()方法也可以做为与控制台交互的方式;
(九)代码块
**作用:**初始化类或者对象。
代码块只能使用static修饰
- 静态代码块:
- 随着类的加载而执行,而且只执行一次;
- 可以初始化类的信息;
- 如果一个类中定义了多个静态代码块,则按在类中中声明的先后顺序执行;
- 非静态代码块:
- 随着对象的创建而执行,每创建一个对象就执行一次;
- 可以在创建对象时,对对象的属性等进行初始化;
属性赋值的先后顺序
- 默认初始化;
- 显式初始化 / 代码块赋值;
- 构造器初始化;
- 通过对象赋值;
(十)关键字final
final可以修饰类,方法,变量。
修饰类
被final修饰的类不能被其他类继承。
修饰方法
被final修饰的方法不能被重写。
修饰变量
被final修饰的变量不能更改,称为常量。
- 修饰属性:显式初始化、代码块中赋值、构造器中初始化;
public class Person{
final int ONE = 1;//显式初始化
final int TWO;
final int THREE;
{
TWO = 2;//代码块中赋值
}
//构造器中初始化
public Person(){
THREE = 3;
}
public Person(int n){
THREE = n;
}
}
- 修饰局部变量:使用final修饰形参时,表明形参是一个常量。
(十一)抽象类和抽象方法
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类变得更普通、更通用。于是之后可能用不到父类,所以就想不再创建父类的对象,使其没有具体的实例,用abstract修饰。
修饰类
- 此类不能实例化;
- 抽象类中一定有构造器,便于子类实例化时调用;
- 开发中,都会提供抽象类的子类,让子类对象实例化;
修饰方法
- 抽象方法只有方法的声明,没有方法体;
- 包含抽象方法的类,一定是抽象类;
- 若子类重写了父类(包括间接父类)中所有的抽象方法,子类才可以实例化;
- 若子类没有重写父类(包括间接父类)中所有的抽象方法,那子类也是一个抽象类,要用abstract修饰。
注意:abstract不能用来修饰私有方法、静态方法、final修饰的方法以及类
抽象类的匿名子类
public class test{
public static void main(String[] args){
//非匿名的类,非匿名的对象
Worker worker = new Worker();
method1(worker);
//非匿名的类,匿名的对象
method1(new Worker());
//匿名子类
Person p = new Person(){
@override
public void eat(){
//TODO
}
};
method2(p);
//匿名子类,匿名对象
method2(new Person(){
@override
public void eat(){
//TODO
}
});
}
public static void method1(Worker w){
}
public static void method2(Person p){
}
}
//Person是抽象类
class Worker extends Person{
@override
public void eat(){
//TODO
}
}
(十二)接口interface
有时必须从几个类中派生出一个子类,继承他们所有的属性和方法,但是Java不支持多重继承。子类可以从几个类中抽取一些共同的行为特征,即子类可以实现多个接口。
jdk8之后,接口中成员可以是全局常量、抽象方法、静态方法、默认方法。
接口中不能定义构造器
全局常量
public static final int MAX_SPEED = 10;
int MIN_SPEED = 1;//省略了public static final
抽象方法
public abstract void method1();
void method2();//省略了public abstract
静态方法
public interface A{
public static void method1(){
//TODO
}
}
//接口中定义的静态方法,只能通过接口来调用
//A.method1();
Java开发中,接口通过让类去实现(implements)的方式来使用:
- 如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化;
- 如果实现类没有覆盖接口中所有抽象方法,则此抽象类仍为一个抽象类。
接口与接口之间可以继承,而且可以多继承。
面试题:
interface A{
int x = 0;
}
class B{
int x = 1;
}
class C extends B implements A{
public static void main(String[] args){
new C().px();
}
public void px(){
//要求输出x=0;
System.out.println(A.x);//接口中定义的变量省略了public static final
//要求输出x=1;
System.out.println(super.x);
}
}
注意:
- 和上述例子不同,如果子类(或者继承类)继承的父类和实现的接口中声明了同名同参数的方法,那么子类在没有重写此方法的情况下,默认调用的父类的同名同参数的方法==「类优先原则」==;
- 而如果是实现的两个接口中有同名同参数的方法,此时会报错==「接口冲突」==。
(十三)内部类
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。
成员内部类(静态,非静态) vs 局部内部类(方法内,代码块内,构造器内)
class Person{
//静态成员内部类
static class Head{
}
//非静态成员内部类
class Body{
//调用外部类的方法
//Person.this.method();
}
//方法内
public void method(){
class A{
}
}
//代码块内
{
class B{
}
}
//构造器内
public void Person(){
class C{
}
}
}
1、成员内部类
作为外部类的成员:
- 可以调用外部类的结构;
- 可以被static修饰;
- 可以被4种权限修饰符修饰。
作为一个类:
- 类内可以定义属性、方法、构造器等;
- 可以被final修饰,表示此类不能被继承;
- 可以被abstract修饰,表示此类不能实例化。
创建静态成员内部类的实例
Person.Head head = new Person.Head();
//head.method();
创建非静态成员内部类的实例
Person p = new Person();
Person.Body body = p.new Body();
//body.method();
2、局部内部类
开发中比较少见,此处略。
四、异常处理
(一)异常概述
在Java中,将程序执行中发生的不正常情况称为“异常”。语法错误和逻辑错误不是异常
异常可以分为两类:
-
Error:jvm都无法解决的严重问题。比如:StackOverflowError,OutOfMemoryError等;
-
Exception:其他因编程错误或偶然的外在因素导致的一般性问题。
-
编译时异常(checked)
-
IOException
- FileNotFoundException
-
ClassNotFoundException
-
-
运行时异常(unchecked)
- NullPointerException
- ArrayIndexOutOfBoundsException
- ClassCastException
-
(二)异常处理方式概述
1、抓抛模型
- 抛:程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个相应异常类的对象,并将此对象抛出。一旦抛出对象之后,其后的代码便不再执行。
- 抓:可以理解为异常的处理方式
- try-catch-finally
- throws
2、try-catch-finally
try {
//可能出现异常的代码
}catch(异常类型1 变量名1){
//处理异常方式一
}catch(异常处理2 变量名2){
//处理异常方式二
}...{
...
}finally{
//一定会执行的代码
}
- 一旦try中的异常对象匹配到一个catch,就进入异常处理,一旦处理完成,就跳出当前的try-catch结构,继续执行结构之外的代码;
- catch中的异常对象如果满足子父类关系,则要求子类一定要声明在父类的上面;
- 常用的异常对象处理的方式:
- e.getMessage()
- e.printStackTrace()
- finally中声明的是一定会被执行的代码,即使try中有return语句,catch中有return语句,在return前都要执行finally,此时如果finally中存在return语句,那么就直接return出结构。
3、throws
public void method() throws Exception{
//TODO
}
- 方法执行时,出现异常,会在异常处生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出,异常代码后续的代码就不再执行;
- throws方式只是将异常抛给了方法的调用者,并没有将异常真正处理掉;
- 子类重写的方法抛出的异常不大于父类被重写的方法抛出的异常类型。
4、手动抛出异常
异常对象的产生:1、系统自动生成的异常对象;2、手动的生成一个异常对象,并抛出(throw)。
if(i < 0){
//TODO
}else{
throw new RuntimeException("i大于等于0");
}
5、自定义异常类
public class MyException extends RuntimeException{
static final long servialVersionUID = -123456789L;
//无参构造器
//有参构造器
}
五、多线程
(一)程序、进程、线程的概念
- 程序(program):是为了完成特定任务,用某种语言编写的一组指令的集合。即指一段静态的代码。
- 进程(process):是程序的一次执行过程,或是正在运行的一个程序。作为资源分配的单位,系统在运行时会为每一个进程分配不同的内存区域。
- 线程(thread):进程可以进一步细化为线程。线程作为调度和执行的单位,每一个线程拥有独立的运行栈和程序计数器。一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。
并行和并发
- 并行:多个CPU同时执行多个任务,比如多个人做不同的事;
- 并发:一个CPU同时执行多个任务,比如多个人做同一件事。
(二)线程的创建和使用
Java的jvm允许程序运行多个线程,通过java.lang.Thread类来体现。
Thread类的特性:
- 每个线程都是通过某个特定的Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体;
- 通过给Thread对象的start()方法来启动这个线程,而非直接调用run()方法。
1、方式一:继承于Thread类
- 创建一个继承于Thread类的子类;
- 重写Thread类的run()方法,将此线程要执行的操作声明在run()方法中;
- 创建Thread类的子类的对象;
- 通过此对象调用start()方法。
class MyThread extends Thread{
@override
public void run(){
//TODO
}
}
public class Test{
public static void main(String[] args){
MyThread thread = new MyThread();
thread.start();//启动当前线程;调用当前线程的run()方法
}
}
2、方式二:实现Runnable接口
- 创建一个实现了Runnable接口的类;
- 实现类去实现Runnable接口中的抽象方法run();
- 创建实现类的对象;
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类对象;
- 通过Thread类的对象调用start()。
class MyThread implements Runnable{
@override
public void run(){
//TODO
}
}
public class Test{
public static void main(String[] args){
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();//启动当前线程;调用当前线程的run()方法,其中调用了Runnable类的target的run()方法;
}
}
3、两种方式的区别
- 实现的方式没有类的单继承的局限性;
- 实现的方式更适合来处理多个线程有共享数据的情况;
- Thread本身也实现了Runnable接口。
开发中优先选择实现Runnable接口方式。
4、Thread类的相关方法
start()://启动当前的线程,调用当前线程的run()方法
run()://通常需要重写Thread类的run()方法,将此线程要执行的操作声明在run()方法中
currentThread()://静态方法,返回当前执行代码的线程
getName()://获取当前线程的名字
setName()://设置当前线程的名字
yield()://线程让步,释放当前CPU的执行权,将执行机会让给优先级相同或更高的线程
join()://在线程A中调用线程B的join()方法,此时线程A进入阻塞状态,直到线程B完全执行之后,线程A才结束阻塞状态,低优先级的线程也可以获得执行
sleep(long millis)://线程阻塞一段时间
isAlive()://判断当前线程是否存活
5、方式三:实现Callable接口
- 创建一个实现Callable接口的实现类;
- 实现call方法,将此线程需要执行的操作声明在call()方法中;
- 创建Callable接口实现类的对象;
- 将Callable接口实现类的对象作为参数,创建FutureTask的对象;
- 将FutureTask的对象作为参数,创建Thread对象。//FutureTask继承了Runnable和Future
class MyThread implements Callable{
@override
public void call(){
//TODO
}
}
public class Test{
public static void main(String[] args){
MyThread myThread = new MyThread();
FutureTask task = new FutureTask(myThread);
Thread thread = new Thread(task);
thread.start();//启动当前线程;调用当前线程的run()方法,其中调用了Runnable类的target的run()方法;
//可以通过task.get()方法得到返回值
}
}
与Runnable相比,Callable接口功能更加强大:
- 相比run()方法,可以有返回值;
- 方法可以抛出异常;
- 支持泛型的返回值;
- 需要借助FutureTask类,比如获取返回结果。
6、方式四:使用线程池
线程池线相关API:ExecutorService和Executor
- 提供指定线程数的线程池;
- 执行指定线程的操作;
- 关闭连接池。
ExecutorService service = Executor.newFixedThreadPool(10);
service.execute(new myThread);//适用于Runnable
service.shutdown();
7、线程的优先级
//高优先级的线程要抢占低优先级线程CPU的执行权,但是只是从概率上讲
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5//默认的优先级
setPriority(int p);//设置优先级
getPriority();//获得优先级
(三)线程的生命周期
线程的状态:
public enum State{
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED,//终止,结束
}
(四)线程的同步机制
缺点:操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低。
1、方式一:同步代码块synchronized
synchronized(同步监视器){//同步监视器,即:锁,任何一个类的对象都可以充当锁”Object obj = new Object();“或者“Window.class”
//需要被同步的代码,即为操作共享数据的代码
}
注意:多个线程必须要共用同一把锁
2、方式二:同步方法
- 静态同步方法,同步监视器是:当前类本身;
- 非静态同步方法,同步监视器是:this。
public synchronized void method(){
//完整的操作共享数据的方法
}
3、死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃,就形成了线程的死锁。出现死锁后,不会出现异常,也不会提示,只是所有线程都处于阻塞状态,无法继续。
4、方式三:Lock锁
- 实例化ReentrantLock;
- 调用lock()方法;
- 调用unlock()方法。
private ReentrantLock lock = new ReentrantLock();
public void run(){
try{
lock.lock();
...
}catch(...){
...
}finally{
lock.unlock();
}
}
Lock锁要手动上锁和手动释放锁
5、线程的通信
//线程1执行完后,使其wait阻塞一下,此时会释放锁
wait();
//线程2拿到锁之后,需要唤醒线程1
notify();
//随后交替进行
- wait() , notify() , notifyAll() 三个方法必须使用在同步代码块中或者同步方法中;
- 以上三个方法的调用者必须是同步监视器进行调用;
- 并且以上三个方法都定义在Object类当中。
六、常用类
(一)字符串相关的类
1、String类
- String类是被final修饰,不可以被继承;
- 字符串实现了Serializable接口,表示字符串是支持序列化的;实现了Comparable接口,表示String可以比较大小;
- 字符串是常量,它们的值在创建之后不能更改;
final char[] value;
String对象的创建
String str1 = "hello";//在常量池中声明
String str2 = new String();//在堆空间中声明
String str3 = new String(char[] a);
String str4 = new String(char[] a,int startIndex,int count);
注意
- 常量与常量的拼接结果在常量池里;
- 其中只要有一个变量,结果就在堆里;
- 如果拼接时调用intern()方法,返回就在常量池里.
String str1 = "hello";
String str2 = "helloworld";
String str3 = str1 + "world";//此时返回值在堆空间
String str4 = str3.intern();//此时返回值在常量池内,str2 == str4//true
String常用方法:
//输出长度
str.length();
//返回某索引处的字符
str.charAt(int index);
//判断字符串是否为空
str.isEmpty();
//转换大小写,对原本的字符串并没有更改,只是新造了一个字符串
str.toLowerCase();
str.toUpperCase();
//忽略前部和尾部的空格
str.trim();
//比较字符串内容是否相同
str.equals(str2);
str.equalsIgnoreCase(str2);
//比较字符串大小
str.compareTo(str2);
//返回一个新的字符串,左闭右开[)
str.substring(int beginIndex,int endIndex);
String其他方法:
//判断字符串是否以指定的后缀结束
boolean endsWith(String suffix);
//判断字符串从指定索引开始的子字符串是否是指定的字符串
boolean startsWith(String prefix,int offset);
//判断字符串中是否包含指定的字符串
boolean contains(CharSequence s);
//返回第一次出现该字符串的索引,如果没有返回-1
int indexOf(String str);
int lastIndexOf(String str);
//从指定索引开始找第一次出现该字符串的位置并返回索引
int indexOf(String str,int fromIndex);
int lastIndexOf(String str,int fromIndex);
//替换
String replace(char oldChar,char newChar);
String replace(CharSequence target,CharSequence replacement);
//String转换为char[]
char[] toCharArray();
//char[]转换为String
char[] charArray = new char[]{'a','b'};
String str = new String(charArray);
//String转换为byte[]
byte[] getBytes(String charsetName);//设置字符集
//byte[]转换为String
String str = new String(Byte[] byteArray);
2、StringBuffer类和StringBuilder类
**StringBuffer:**可变的字符序列;线程安全,效率低。
StringBuffer str = new StringBuffer()
相当于在底层创建了一个长度为16的char[];StringBuffer str = new StringBuffer("abc")
相当于在"abc"之后再增加一个长度为16的char[];- 空间不够时会进行扩容,扩容为原来的(2倍+2),同时将原有数组中的元素复制到新的数组中;
- 可以用
StringBuffer(int capacity)
定义容量的大小。
**StringBuilder:**可变的字符序列;线程不安全,效率高。
常用方法
//添加,操作的是StringBuffer本身,是会改变其本身的数据的
StringBuffer append();
//删除
StringBuffer delete(int start,int end);
//把指定位置的字符串替换为指定字符串
StringBuffer replace(int start,int end,String str);
//在指定位置插入
StringBuffer insert(int offset,xxx);
//反转
StringBuffer reverse();
//修改指定位置上的字符
public void setCharAt(int n,char ch);
(二)jdk8之前的日期api
System.currentTimeMillis()
返回的是当前时间距离1970年1月1日0分0秒的单位为毫秒的时间差;java.util.Date
toString()
显示当前的年、月、日、时、分、秒getTime()
获取毫秒数
SimpleDateFormat
对日期Date类的格式化和解析
//SimpleDateFormat的使用
//格式化
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
String time = sdf.format(date);
//解析
sdf.parse("2021-09-29 20:12:34");
Calendar
日历类(一个抽象类)有可变性
Calendar calendar = Calendar.getInstance();
//get()方法
calendar.get(Calendar.DAY_OF_MONTH);
//set()方法
calendar.set(Calendar.DAY_OF_MONTH,20);
//add()方法
calendar.set(Calendar.DAY_OF_MONTH,2);//加2天
calendar.set(Calendar.DAY_OF_MONTH,-2);//减两天
//getTime()方法
calendar.getTime();
//setTime()方法
Date date = new Date();
calendar.setTime(date);
(三)jdk8中新的日期api
LocalDate
、LocalTime
、LocalDateTime
//now()方法
LocalDateTime localDateTime = LocalDateTime.now();
//of()方法,指定年月日,该方法没有偏移量
LocalDateTime localDateTime = LocalDateTime.of(2020,1,1,1,1,1);
//getXxx()方法,获取天、星期等
localDateTime.getDayOfWeek();
//with(),相当于set方法,不可变性
LocalDateTime localDateTime2 = localDateTime.withHour(2);
-
instant
时间线上的瞬时点 -
DateTimeFormatter
格式化或解析日期、时间
(四)Java比较器
比较对象的大小,实现两个接口:Comparable
或者Comparator
- Comparable接口,自然排序
/**
像String、包装类等实现了Comparable接口,重写了compareTo()方法
- 当前对象大于形参时,返回正整数;
- 当前对象等于形参时,返回0;
- 当前对象小于形参时,返回负整数。
*/
//自定义类需要重写方法
@Override
public int compareTo(Object obj){
if(obj instanceOf Person){
Person p1 = (Person)obj;
if(this.weight > p1.weight){
return 1;
}else if(this.weight < p1.weight){
return -1;
}else{
return 0;
}
}
return 0;
}
- Comparator接口,定制排序
/**
当元素的类型没有实现Comparable接口而又不方便修改代码,
或者想改变实现了Comparable接口的排序顺序,
可以使用定制排序
*/
//Arrays.sort()方法默认是从小到大排序
//我们可以重写compare()方法
String[] arr = new String[]{"aa","bb","cc"};
Arrays.sort(arr,new Comparator(){
@Override
public int compare(Object o1,Object o2){
//Todo
}
});
(五)System类
- System类表示系统,该类位于java.lang包;
- 该类的构造器时private的,所以无法创建该类的对象,其内部的成员变量和成员方法都是static;
- System类内部包含in、out和err三个成员变量,分别代表标准输入流(键盘输入),标准输出流(显示器)和标准错误输出流(显示器);
- 成员方法:
native long currentTimeMillis()
,返回当前的计算机时间;void exit(int status)
,退出程序,status的值为0代表正常退出,非0代表异常退出;void gc()
,请求系统进行垃圾回收;String getProPerty(String key)
,获取系统中属性名为key的属性对应的值。
(六)BigInteger和BigDecimal
java.math.BigInteger
可以表示不可变的任意精度的整数;- 在商业计算中,要求数字精度比较高,故用到
java.math.BigDecimal
。
七、枚举类
- 类的对象只有有限个,并且是确定的,比如星期、四季、性别等;
- 当需要定义一组常量时,建议使用枚举类。
//使用enum关键字
enum Season{
SPRING("春天"),//用逗号隔开
SUMMER("夏天"),
AUTUMN("秋天"),
WINTER("冬天");
}
Enum类的常用方法
//返回枚举类的对象数组,该方法可以很方便地遍历所有的枚举类
values();
//把一个字符串转换成对应的枚举类对象,返回的是枚举类对象名是str的对象
valueOf(String str);
//返回的是枚举类的名称
toString();
Enum类实现接口
//要求每一个枚举对象分别实现接口中的抽象方法
enum Season implements info{
SPRING("春天"){
@Override
public void show(){
//TODO
}
},
SUMMER("夏天"){
...
},
AUTUMN("秋天"){
...
},
WINTER("冬天"){
...
};
}
八、注解
Annotation,jdk内置的三个基本注解:@Override
,@Deprecated
,@SuppressWarnings
如何自定义注解
- 注解声明为@interface;
- 注解的成员变量以无参数方法的形式声明,可以用default关键字设置默认值;
public @interface MyAnnotation{
String value default "hello";
}
1、元注解
@Retention
:指明该Annotation的生命周期RetentionPolicy.SOURCE
RetentionPolicy.CLASS
,默认行为RetentionPolicy.RUNTIME
,只有声明为RUNTIME的注解,才能通过反射获取
@Target
:指定该Annotation能用于修饰那些程序元素ElementType.TYPE
,类/接口/枚举类ElementType.FIELD
,属性ElementType.METHOD
,方法ElementType.PARAMETER
,参数ElementType.CONSTRUCTOR
,构造器ElementType.LACAL_VARIABLE
,局部变量ElementType.ANNOTATION_TYPE
,注解类型ElementType.PACKAGE
,包
@Documented
:表示所修饰的注解在被javadoc解析时,会被保留@Inherited
:表示具有继承性,子类会自动获取父类的注解
2、可重复注解和类型注解
@Repeatable
,成员值为某一注解的类型,注意@Target
,@Retention
等元注解要相同。ElementType.TYPE_PARAMETER
,用注解修饰泛型;ElementType.TYPE_USE
,表示该注解能写在使用任何类型的语句中
九、集合
(一)概述
数组的缺点:
- 数组初始化之后,长度就不可变了;
- 数组对于添加、删除、插入等操作,十分的不便;
- 数组存储数据的特点:有序的、可重复的。
集合可以分为两个体系:
- Collection接口:单列数据
- List:有序,可重复
- Vector
- ArrayList
- LinkedList
- Set:无序,不可重复
- HashSet
- LinkedHashSet
- SortedSet
- TreeSet
- HashSet
- List:有序,可重复
- Map接口:双列数据
- HashTable
- Properties
- HashMap
- LinkedHashMap
- SortedMap
- TreeMap
- HashTable
(二)Collection接口
Collection coll = new ArrayList();
//添加数据
coll.add();
//返回集合长度
coll.size();
//将其他集合中的元素添加到当前集合
coll.addAll(Collection c);
//判断集合是否为空
coll.isEmpty();
//清空集合元素
coll.clear();
//判断当前集合中是否包含obj
coll.contains(Object obj);
coll.contains(new String("Tom"));//String类重写了equals()方法,比较的是内容
coll.contains(new Person("Tom",11));//自定义类若没有重写equals()方法,此时比较的是地址值
//判断形参中的所有元素是否都存在于当前集合中
coll.containsAll(Collection c);
//删除一个元素
coll.remove(Object obj);
//从当前集合中移除形参集合的所有元素,差集
coll.removeAll(Collection c);
//获取两个集合的交集,将结果返回当前集合
coll.retainAll(Collection c);
//返回哈希值
coll.hashCode();
//将集合转为数组
Object[] arr = coll.toArray();
//将数组转化为集合
List list = Arrays.asList(new String[]{"AA","BB"});
List list2 = Arrays.asList(new int[]{12,34});//此时会将括号内的内容看成是一个元素
List list3 = Arrays.asList(new Integer[]{12,34});//此时才是正常的两个元素
//返回Iterator接口的实例,用于遍历集合元素
interator();
1、迭代器:Iterator接口
Collection接口继承了java.lang.Iterable
接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
主要用于遍历Collection集合的元素
Iterator iterator = coll.iterator();
System.out.println(iterator.next());//若没有元素会报NoSuchElementException
while(iterator.hasNext()){
System.out.println(iterator.next());
}
//再次迭代需要新创建一个迭代器对象
Iterator iterator = coll.iterator();
while(iterator.hasNext()){
Object obj = iterator.next;
if("tom".euqals(obj)){
iterator.remove();//移除当前属性
}
}
2、foreach循环遍历
//for(集合中元素的类型 局部变量 : 集合/数组对象)
for(Object obj : coll){
System.out.println(obj);
}
3、List接口
List集合类中元素有序、且可重复
(1)ArrayList
底层使用Object[] elementData存储;线程不安全,效率高。
- 底层创建一个长度为10的Object数组,在jdk8中,底层只有一个空的数组,节省了内存空间;
- 添加
add()
元素,需要先确定容量是否满足ensureCapacityInternal(size + 1);
- 如果需要扩容,
newCapacity = oldCapacity + (oldCapacity >> 1);
,扩容为1.5倍,并将原来的集合元素复制到新集合中。
(2)LinkedList
底层使用双向链表存储,对于频繁的插入、删除,效率比ArrayList高。
- 两个基本属性
Node<E> first
,Node<E> last
; - 添加
add()
元素,创建Node对象new Node<>(last,e,next)
,如果是第一次添加那么此元素当作first
,否则当作last
,prev
指向上一个元素,next
指向下一个元素。
(3)Vector
底层使用Object[] elementData存储;线程安全,效率低。
与ArrayList相似,只是在扩容部分,扩容为原来的2倍
newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
(4)List常用方法
//获取指定index位置的元素
Object get(int index);
//返回集合中第一次出现obj出现的索引
int indexOf(Object obj);
//返回集合中最后一次出现obj出现的索引
int lastIndexOf(Object obj);
//移除指定index位置的元素,并返回此元素
Object remove(int index);
//设置指定index位置的元素为obj
Object set(int index,Object obj);
//提供当前list的子list
List subList(int fronIndex,int toIndex);
4、Set接口
Set集合类元素无序,且不可重复。
- 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。
- 不可重复性:相同的元素只能添加一个,根据equals()方法和hashCode()方法判断。
Set接口中没有额外新定义的方法,使用的都是Collection中声明的方法
(1)HashSet
作为Set接口的主要实现类,线程不安全,可以存储null值。
-
添加
add()
元素时,首先调用hashCode()方法,计算元素的哈希值返回; -
用哈希值通过某一种算法确定在HashSet底层的存放位置;
-
如果该位置没有其他元素,则添加成功;
如果该位置存在其他元素,则会比较其他元素和当前元素的哈希值:
- 如果哈希值不相同,当前元素则会以链表的形式存放在其他元素的下面;
- 如果哈希值相同,则会调用当前元素的equals()方法:
- 如果返回false,则以链表形式添加;
- 如果返回true,则添加失败。
LinkedHashSet
作为HashSet的子类,遍历其内部数据时,可以按照添加的顺序遍历。
因为每一个数据还维护了两个引用,记录了前一个数据和后一个数据。
(2)SortedSet
TreeSet
可以按照添加对象的指定属性,进行排序,添加的数据必须是相同类的对象。
两种排序方式:
- 自然排序(比较元素是否相同的标准为:compareTo()方法)
public class Person implements Comparable{
//TODO
@Override
public int compareTo(Object obj){
//按照姓名从小到大排列
if(obj instanceof Person){
Person person = (Person) obj;
return this.name.compareTo(person.name);
}else{
throw new RuntimeException("输入类型不匹配");
}
}
}
TreeSet set = new TreeSet();
set.add(new Person("tom",12));
set.add(new Person("allen",10));
set.add(new Person("jack",3));
set.add(new Perosn("jack",5));//如果要添加名字一样的对象,则需要加入二级排序
//按名字大小遍历输出
- 定制排序(比较元素是否相同的标准为:compare()方法)
Comparator com = new Comparator(){
@Override
public int compare(Object o1,Object o2){
//TODO
}
};
TreeSet set = new TreeSet(com);//定制排序
set.add(new Person("tom",12));
set.add(new Person("allen",10));
set.add(new Person("jack",3));
(三)Map接口
双列数据,存储key-value对的数据
- key:无序的,不可重复的,使用Set存储所有的key;
- value:无序的,可重复的,使用Collection存储所有的value;
- entry(key-value):无序的,不可重复的,使用Set存储所有的entry。
//添加
Object put(Object key,Object value);
//将m中所有的key-value对存放到当前map中
void putAll(Map m);
//移除指定key的键值对,返回value
Object remove(Object key);
//清空当前map中所有数据
void clear();
//获取指定key的value值
Object get(Object key);
//是否包含指定key
boolean containsKey(Object key);
//是否包含指定value
boolean containsValue(Object value);
//返回键值对个数
int size();
//判断当前map是否为空
boolean isEmpty();
//返回所有key构成的Set集合
Set keySet();
//返回所有value构成的Collection集合
Collection values();
//返回所有键值对构成的Set集合
Set entrySet();
1、HashMap
作为Map的主要实现类,线程不安全,效率高,可以存储null的key和value。
注意:key所在类要重写equals()方法和hashCode()方法;value所在类要重写equals()方法。
- 在实例化之后,底层创建了长度是16的一维数组Entry[] table,jdk8中,没有创建长度为16的数组,只有首次调用put方法时,底层才创建长度为16的Node[];
map.put(key1,value1)
,调用key1所在类的hashCode()方法计算哈希值,通过相关算法得到Entry在数组中的存放位置:- 如果此位置上为空,此时的entry1添加成功;
- 如果此位置上不为空,则比较它们的哈希值:
- 如果key1的哈希值与其都不相同,则entry1添加成功;
- 如果key1的哈希值与其中某一个相同,则调用key1所在类的equals()方法:
- equals()方法返回false,entry1添加成功;
- equals()方法返回true,则将value1覆盖原来的value值。
- 临界值 = 初始容量 * 加载因子,当插入的大小大于临界值且当前集合满了,就需要扩容,是扩容为原来的2倍;
- jdk8中,当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前的集合长度 > 64时,此索引上的所有数据改为使用红黑树存储。
DEFAULT_INITIAL_CAPACITY:HashMap的默认容量-16
DEFAULT_LOAD_FACTOR:HashMap默认加载因子-0.75
threshold:临界值 = 初始容量 * 加载因子
THEEIFY_THRESHOLD:链表长度大于该默认值,转化为红黑树-8
MIN_TREEIFY_CAPACITY:当前集合长度大于该默认值,转化为红黑树-64
扩容之后,原集合中的元素可能发生位置的变动;HashMap并不是在添加17个元素时扩容,而是提前扩容。
LinkedHashMap
保证在遍历map元素时,可以按照添加的顺序实现遍历
根据next可以记录添加的元素的顺序
2、Hashtable
线程安全,效率低
Properties
常用来处理配置文件,key和value都是String类型
3、SortedMap
TreeMap
保证按照添加的key-value对进行排序,实现排序遍历,添加的数据必须是相同类的对象。
(四)Collections工具类
Collections是一个操作List、Set和Map等集合的方法。
常用方法(均为静态方法):
//反转List中元素的顺序
reverse(List l);
//对List中的元素进行随机排序
shuffle(List l);
//根据元素的自然顺序对指定的List元素升序排序
sort(List l);
//定制排序
sort(List l,Comparator c);
//将指定List中的i处元素和j处元素互换
swap(List l,int i,int j);
//返回指定集合中指定元素出现的次数
int frequency(Collection,Object);
//将src中的内容复制到dest中
void copy(List dest,List src);
//将List中的旧值换成新值
boolean replaceAll(List list,Object oldVal,Object newVal);
十、泛型
集合容器类在设计/声明阶段不能确定实际存的是什么类型的对象,因此把元素的类型设计成一个参数,这个类型参数叫做泛型。
ArrayList<Integer> list = new ArrayList<Integer>();//定义Integer类
list.add(1);
list.add(2);
list.add("tom");//编译时就会报错
Map<String,Integer> map = new HashMap<String,Integer>();//key为String类,value为Integer类
map.put("tom",11);
map.put("mary",22);
(一)自定义泛型类、泛型接口
public class Person<T>{
int id;
T personT;
public Person(){}
public Person(int id,T personT){
this.id = id;
this.personT = personT;
}
}
(二)自定义泛型方法
public interface Flyable<T>{
//TODO
//在方法中出现了泛型的结构,泛型参数与类或接口的泛型参数没有任何关系
public <E> List<E> method(E[] e){
//TODO
}
}
泛型方法可以声明为静态,因为泛型参数是在调用方法时确定的,并非在实例化类时确定。
(三)泛型的继承说明
List<Object> list1 = null;
List<String> list2 = null;
list1 = list2;//此时会报错,因为list1和list2此时没有子父类关系,二者是并列关系
List<String> list3 = null;
ArrayList<String> list4 = null;
list3 = list4;//此时不会报错
(四)通配符
List<Object> list1 = null;
List<String> list2 = null;
List<?> list = null;//此时List<?>作为list1和list2的通用父类
list = list1;
list = list2;
对于List<?>
- 不能向其内部添加数据,但是可以添加null;
- 允许读取数据,读取的数据类型为Object。
十一、IO流
(一)File类
java.io.File类的一个对象,代表的是一个文件或一个文件夹。
-
相对路径:相较于某个路径下,指明的路径;
-
绝对路径:包含盘符在内的文件或文件目录的路径。
File file1 = new File("hello.txt");//当前module下
File file2 = new File("D:\\java\\world.txt");
File file3 = new File("D\\java","ducument");
File file4 = new File(file3,"hi.txt");
常用方法:
//获取绝对路径
public String getAbsolutePAth();
//获取路径
public String getPath();
//获取名字
public String getName();
//获取上层文件目录路径
public String getParent();
//获取文件长度(字节数)
public long length();
//获取最后一次的修改时间
public long lastModified();
//获取指定目录下的所有文件或者文件目录的名称数组
public String[] list();
//获取指定目录下的所有文件或者文件目录的File数组
//以绝对路径形式输出
public File[] listFiles();
//把文件重命名为指定的文件路径
public boolean renameTo(File dest);
//file1.renameTo(file2);
//要想返回true,需要file1在硬盘中是存在的,file2不能在硬盘中存在
//判断是否是文件目录
public boolean isDirectory();
//判断是否是文件
public boolean isFile();
//判断是否存在
public boolean exists();
//判断是否可读
public boolean canRead();
//判断是否可写
public boolean canWrite();
//判断是否隐藏
public boolean isHidden();
//创建文件,若文件存在则不创建,并返回false
public boolean createNewFile();
//创建文件目录,如果文件目录存在则不创建
public boolean mkdir();
//创建文件目录,如果上册目录不存在,则一并创建
public boolean mkdirs();
(二)IO流原理及流的分类
- 按操作数据单位不同:
- 字节流(8bit)
- 字符流(16bit)
- 按数据流流向不同:
- 输入流
- 输出流
- 按流的角色不同:
- 节点流
- 处理流
(抽象基类) | 节点流(或文件流) | 缓冲流(处理流的一种) |
---|---|---|
InputStream | FileInputStream | BufferedInputStream |
OutputStream | FileOutputStream | BufferedOutputStream |
Reader | FileReader | BufferedReader |
Writer | FileWriter | BufferedWriter |
对于文本文件(.txt .java .c .cpp),使用字符流处理;
对于非文本文件(.jpg .mp3 .mp4 .avi .doc .ppt),使用字节流处理。
(三)节点流
1、FileReader读入操作
- 实例化File类对象,指明要读入的文件;
- 提供具体的流;
- 数据的读入;
- 流的关闭。
FileReader fr = null;
try{
//实例化File对象
File file = new File("hello.txt");
//提供具体的流
fr = new FileReader(file);
//数据的读入
int data = fr.read();//返回读入的一个字符,如果读到末尾则返回-1
while(data != -1){
System.out.println((char)data);
data = fr.read();
}
} catch(IOException e) {
e.printStackTrace();
} finally{
try{
//流的关闭
if(fr != null)
fr.close;
} catch(IOException){
e.printStackTrace();
}
}
当文件的字符较多时,使用read()
方法的效率很低,可以使用重载的方法。
//数据的读入
char[] cbuf = new char[5];//每次读字符的个数
int len;
while((len = fr.read(cbuf)) != -1){
for(int i = 0;i < len;i++){
System.out.print(cbuf[i]);
}
}
注意,上述案例每次取5个字符,以“helloworld123”为例,第一次取的是“hello”,第二次取的是“world”,第三次取的是“123ld”,本质是将上一次的数组覆盖了。
我们还可以将结果转换为String类型:
String str = new String(cbuf,0,len);//从第一个开始取,每次只取len个
2、FileWriter写出操作
- 实例化File类对象,指明要写出的文件;
- 提供具体的流;
- 数据的写出;
- 流的关闭。
FileWriter fw = null;
try{
//实例化File对象
File file = new File("hello.txt");//如果文件不存在,则会自动创建
//提供具体的流
fw = new FileWriter(file,false);//第二个参数为false,表示不追加,直接覆盖文件;为true,表示在原文件内容上追加内容;默认为false
//数据的写出
fw.write("My name is Allen.");
} catch(IOException e) {
e.printStackTrace();
} finally{
try{
//流的关闭
if(fw != null)
fw.close;
} catch(IOException){
e.printStackTrace();
}
}
3、FileInputStream读入操作
- 实例化File类对象,指明要读入的文件;
- 提供具体的流;
- 数据的读入;
- 流的关闭。
FileInputStream fis = null;
try{
//实例化File对象
File file = new File("hello.txt");
//提供具体的流
fis = new FileInputStream(file);
//数据的读入
byte[] buffer = new byte[5];
int len;
while((len = fr.read(buffer)) != -1){
String str = new String(buffer,0,len);
System.out.print(str);
}
} catch(IOException e) {
e.printStackTrace();
} finally{
try{
//流的关闭
if(fis != null)
fis.close;
} catch(IOException){
e.printStackTrace();
}
}
4、FileOutputStream复制图片操作
FileInputStream fis = null;
FileOutputStream fos = null;
try{
//实例化File对象
File srcFile = new File("picture.jpg");
File destFile = new File("picture2.jpg");
//提供具体的流
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
//图片复制
byte[] buffer = new byte[5];
int len;
while((len = fis.read(buffer)) != -1){
fos.write(buffer,0,len);
}
} catch(IOException e) {
e.printStackTrace();
} finally{
try{
//流的关闭
if(fis != null)
fis.close;
} catch(IOException){
e.printStackTrace();
}
try{
if(fos != null)
fos.close;
} catch(IOException){
e.printStackTrace();
}
}
(四)缓冲流
实现文件的复制:
FileInputStream fis = null;
FileOutputStream fos = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try{
//实例化File对象
File srcFile = new File("picture.jpg");
File destFile = new File("picture2.jpg");
//提供节点流
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
//提供缓冲流
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//图片复制
byte[] buffer = new byte[10];
int len;
while((len = bis.read(buffer)) != -1){
bos.write(buffer,0,len);
}
} catch(IOException e) {
e.printStackTrace();
} finally{
try{
//流的关闭,先关外层流,再关内层流
//在关闭外层流的同时,内层流也会自动关闭
if(bos != null)
bos.close;
} catch(IOException){
e.printStackTrace();
}
try{
if(bis != null)
bis.close;
} catch(IOException){
e.printStackTrace();
}
}
-
缓冲流作用:提供流的读取、写入的数据
-
提高读写速度的原因:内部提供了一个缓冲区
DEFAULT_BUFFER_SIZE = 8192
,其中有一个方法flush()
刷新缓存区。
BufferedReader的readLine()
方法
//在写出数据时
String data;
while((data = br.readLine()) != null){
bw.write(data);//data中不包含换行符
bw.newLine();//提供换行的操作
}
(五)转换流
InputStreamReader
,字节流转换为字符流,charOutputStreamWriter
,字符流转换为字节流,byte
FileInputStream fis = new FileInputStream("hello.txt");
//FileOutputStream fos = new FileOutputStream("hello_gbk.txt");
InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
//OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");
char[] cbuf = new char[20];
int len;
while((len = isr.read(cbuf)) != -1){
//osw.write(cbuf,0,len)
String str = new String(cbuf,0,len);
System.out.print(str);
}
isr.close();
//osw.close();
(六)对象流
ObjectInputStream
,ObjectOutputStream
对象序列化机制允许把内存中的java对象转换为二进制流。
//序列化过程:将内存中的Java对象保存到磁盘中或通过网络传输
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
oos.writeObject(new String("hello,world!"));
oos.flush();
//oos.writeObject(new Person(3,"tom"));
//oos.flush();
oos.close();
//反序列化过程
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.dat"));
Object obj = ois.readObject();
String str = (String)obj;
//Person p = (Person)ois.readObject();
System.out.println(str);
//System.out.println(p);
ois.close();
public class Person implements Serializable{
private static final long serialVersionUID = 1234567890098L;
private int id;
private String name;
...
}
自定义类可序列化的要求:
- 需要实现接口:
Serializable
; - 当前类需要有一个全局变量:
serialVersionUID
; - 除了当前类需要实现
Serializable
接口,其内部所有属性也必须是可序列化的(默认情况下,基本数据类型可序列化); static
和transient
修饰的成员变量不能序列化。
(七)RandomAccessFile类
RandomAccessFile
直接继承于java.lang.Object
类,实现了DataInput
和DataOutput
接口;RandomAccessFile
既可以作为一个输入流,又可以作为一个输出流;
//构造器
public RandomAccessFile(File file,String mode)
其中mode参数可以选择:
//写入数据时,文件若存在,则会从头覆盖文件的内容
r:只读,不会创建文件,若文件不存在则会报错
rw:读取和写入,文件不存在则创建
rwd:读取和写入,同步文件内容
rws:读取和写入,同步文件内容和更新元数据
RandomAccessFile raf1 = new RandomAccessFile(new File("picture.jpg"),r);
RandomAccessFile raf2 = new RandomAccessFile(new File("picture1.jpg"),rw);
byte[] buffer = new byte[1024];
int len;
while((len = raf1.read(buffer)) != -1){
raf2.write(buffer,0,len);//覆盖操作
}
raf2.close();
raf1.close();
RandomAccessFile类的记录指针
RandomAccessFile
对象包含一个记录指针,用以标记当前读写处的位置,RandomAccessFile
类对象可以自由移动记录指针:
long getFilePointer()
:获取文件记录指针的位置;void seek(long pos)
:将文件记录指针定位到pos位置。
十二、网络编程
(一)概述
目的:直接或间接地通过网络协议与其他计算机实现数据交互,进行通信。
(二)IP和端口号
IP:InetAddress
- 唯一的标识Internet上的计算机;
- 本地回环地址:127.0.0.1;主机名:localhost
InetAddress inet1 = InetAddress.getByName("192.168.0.1");
InetAddress inet2 = InetAddress.getByName("www.baidu.com");
InetAddress inet3 = InetAddress.getLocalHost();
端口号:标识正在计算机上运行的进程
- 不同的进程有不同的端口号;
- 规定为16位的整数0~65535
端口号与IP地址的组合得出一个网络套接字:Socket
(三)网络协议
1、传输控制协议TCP
- 传输前,采用“三次握手”方式,点对点通信;
- 进行通信的两个应用进程:客户端、服务端;
- 需要释放已建立的连接,效率低。
三次握手
- 客户端发送syn报文,并设置发送序号X;
- 服务端接收到报文,并发送syn+ACK报文,设置发送序号Y,确认序号X+1;
- 客户端接收到报文,并发送ACK报文,设置发送序号Z,确认序号Y+1。
四次挥手
- 主动方发送Fin+Ack报文,设置发送序号X;
- 被动方发送Ack报文,设置发送序号Z,确认序号X+1;
- 断开之后,被动方发送Fin+Ack报文,设置发送序号Y,确认序号X;
- 主动方发送Ack报文,设置发送序号X,确认序号Y,查看是否断开。
2、用户数据报协议UDP
- 将数据、源、目的地封装成数据包,不需要建立连接;
- 数据包大小限制在64kb之内;
- 没有提前握手,因此是不可靠的;
- 无需释放资源,效率高。
(四)TCP网络编程
public void client(){
Socket socket = null;
OutputStream os = null;
ByteArrayOutputStream baos = null;
try {
InetAddress inet = InetAddress.getByName("127.0.0.1");
socket = new Socket(inet,60000);
os = socket.getOutputStream();
os.write("我是客户端~".getBytes());
socket.shutdownOutput();//关闭数据的输出,让服务端知道客户端已经传输完成了
//接受服务端的数据
InputStream is = socket.getInputStream();
//下列方法防止了汉字被截断,导致乱码问题
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[5];
int len;
while((len = is.read(buffer)) != -1){
baos.write(buffer,0,len);
}
System.out.println(baos.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void server(){
ServerSocket ss = null;//指明自己端口号
Socket socket = null;//接受来自客户端的socket
InputStream is = null;
OutputStream os = null;
try {
ss = new ServerSocket(60000);
socket = ss.accept();
is = socket.getInputStream();
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer)) != -1){
String str = new String(buffer,0,len);
System.out.println(str);
}
//服务端给予客户端反馈
os = socket.getOutputStream();
os.write("你好,我是服务端~".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
(五)UDP网络编程
public void send(){
DatagramSocket socket = new DatagramSocket();
String str = "我是发送端~";
byte[] data = str.getBytes();
InetAddress inet = InetAddress.getLoaclHost();
DatagramPacket packet = new DatagramPacket(data,0,data.length,inet,60000);
socket.send(packet);
socket.close();
}
public void receive(){
DatagramSocket socket = new DatagramSocket(60000);
byte[] buffer = new byte[100];
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
socket.receive(packet);
System.out.println(new String(packet.getData(),0,packet.getLength()));
socket.close();
}
(六)URL编程
Uniform Resource Locator:统一资源定位符
URL由五部分构成:<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
十三、反射
Reflection是被视为动态语言的关键,反射机制允许程序在执行期借助于APIjava.lang.Class
,java.lang.reflect.Method
,java.lang.reflect.Field
,java.lang.reflect.Constructor
等取得任何类的内部信息。
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构。
- 正常方式:引入“包类” --> new实例化 --> 取得实例化对象;
- 反射方式:实例化对象 --> getClass()方法 --> 得到完整的包类信息。
Class clazz = Person.class;
//通过反射,创建Person类对象
Constructor cons = clazz.getConstructor(String.class,int.class);
Object obj = cons.newInstance("tom",12);
//获取对象指定的属性、方法
Person p =(Person) obj;
Field age = clazz.getDeclaredField("age");
age.set(p,10);
Method age = clazz.getDeclaredMethod("show");
show.invoke(p);
反射可以调用类中私有结构
//比如私有的构造器
Constructor cons = clazz.getDeclaredConstructor(String.class);
cons.setAccessible(true);
Person p = (Person) cons.newInstance("jacky");
(一)Class类
**类的加载过程:**程序经过javac.exe命令之后,会生成一个或多个字节码文件(.class),接着使用java.exe命令对某个字节码文件进行解释运行,相当于把某个字节码文件加载到内存中。加载到内存中的类,就称为运行时类,作为Class的一个实例。
获取Class的4种方法:
public class Test{
//方式一:调用运行时类的属性
Class<Person> clazz1 = Person.class;
//方式二:调用运行时类的对象的getClass()方法
Person p = new Person();
Class clazz2 = p.getClass();
//方式三:调用Class的静态方法forName(String classPath)
Class clazz3 = Class.forName("java.lang.String");
//方式四:类加载器ClassLoader
ClassLoader classLoader = Test.class.getClassLoader();
Class clazz4 = classLoader.loadClass("java.lang.String");
}
(二)创建运行时类对象
Class clazz = Person.class;
//创建对应的运行时类对象
//内部调用了空参构造器,空参构造器访问权限需要注意设置
Person p = (Person) clazz.newInstance();
(三)获取运行时类的结构
属性:
Class clazz = Person.class;
//获取当前运行类及其父类中声明为public权限的属性
Field[] fields = clazz.getFields();
//获取当前运行类中声明的所有属性,不包括父类中的属性
Field[] declaredFields = clazz.getDeclaredFields();
Class clazz = Person.class;
Field[] declaredFields = clazz.getDeclaredFields();
for(Field f : declaredFields){
//权限修饰符
int modifier = f.getModifiers();
System.out.println(Modifier.toString(modifier));
//数据类型
Class type = f.getType();
System.out.println(type.getName());
//变量名
String fName = f.getName();
}
方法:
Class clazz = Person.class;
//获取当前运行类及其父类中声明为public权限的方法
Method[] methods = clazz.getMethods();
//获取当前运行类中声明的所有方法,不包括父类中的方法
Method[] declaredMethods = clazz.getDeclaredMethods();
Class clazz = Person.class;
Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method m : declaredMethods){
//获得生命周期声明为RUNTIME的注解
Annotation[] anno = m.getAnnotations();
//权限修饰符
int modifier = m.getModifiers();
System.out.println(Modifier.toString(modifier));
//返回值类型
String returnType = m.getReturnType().getName();
//方法名
Sting mName = m.getName();
//形参列表
Class[] parameterTypes = m.getParameterTypes();
//异常
Class[] exceptionTypes = m.getExceptionTypes();
}
构造器:
Class clazz = Person.class;
//获取当前运行时类中声明为public的构造器
Constructor[] cons = clazz.getConstructors();
//获取当前运行时类中声明的所有构造器
Constructor[] decCons = clazz.getDeclaredConstructors();
父类及父类的泛型:
Class clazz = Person.class;
//获取父类
Class superClass = clazz.getSuperclass();
//获取带泛型的父类
Type genericSuperclass = clazz.getGenericSuperclass();
//只输出带泛型的父类的泛型
ParameterizedType para = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = para.getActualTypeArguments();
System.out.println(actualTypeArguments[0].getTypeName);
(四)调用运行时类的结构
属性:
Class clazz = Person.class;
//获取运行时类对象
Person p = (Person) clazz.newInstance();
//获取指定属性
Field id = clazz.getField("id");
//获取私有属性:
//Field name = clazz.getDeclaredField("name");
//name.setAccessible(true);
//设置当前属性
id.set(p,1001);
//获取当前属性的值
int pId = (int) id.get(p);
方法:
Class clazz = Person.class;
Person p = (Person) clazz.newInstance();
//获取指定方法
Method show = clazz.getDeclaredMethod("show",String.class);//参数1:方法名称;参数2:形参列表
show.setAccessible(true);
//执行方法
Object returnVal = show.invoke(p,"中国");
//调用静态方法
Method showDecs = clazz.getDeclaredMethod("showDecs");
showDecs.setAccessible(true);
Object returnVal2 = showDesc.invoke(clazz);
构造器:
Class clazz = Person.class;
//获取指定构造器
Constructor cons = clazz.getDeclaredConstructor(String.class);
cons.setAccessible(true);
Person p = (Person) cons.newInstance("jacky");
(五)动态代理
使用一个代理将对象包装起来,然后用该代理对象取代原始对象。
interface ClothFactory{
void produceCloth();
}
//被代理类
class NikeClothFactory implements ClothFactory{
@Override
public void produceCloth() {
System.out.println("nike公司推出新产品");
}
}
class MyInvocationHandler implements InvocationHandler{
private Object object;
public void bind(Object object){
this.object = object;
}
//当我们通过代理类对象调用方法a时,会自动地调用如下的方法
//将被代理类要执行的方法a,声明在该方法中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//method即为代理类对象调用的方法,
Object returnVal = method.invoke(object,args);
return returnVal;
}
}
class ProxyFactory{
//调用此方法,返回一个代理类对象
public static Object getProxyInstance(Object obj){//obj 被代理类对象
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),handler);
}
}
public class ProxyTest {
public static void main(String[] args) {
NikeClothFactory nike = new NikeClothFactory();
ClothFactory proxyInstance = (ClothFactory) ProxyFactory.getProxyInstance(nike);
//当代理类对象调用方法时,会自动地调用被代理类中同名的方法
proxyInstance.produceCloth();
}
}
十四、Java8新特性
(一)Lambda表达式
Lambda是一个匿名函数,使用它可以写出更简洁,更灵活的代码。
Runnable r1 = new Runnable(){
@Override
public void run(){
System.out.println("hello");
}
};
r1.run();
//Lambda表达式
Runnable r2 = () -> System.out.println("world");
r2.run();
Comparator<Integer> com1 = new Comparator(){
@Override
public int compare(Integer o1,Integer o2){
return Integer.compare(o1,o2);
}
};
int result1 = com1.compare(1,2);
System.out.println(result1);
//Lambda表达式
Comparator<Integer> com1 = (o1,o2) -> Integer.compare(o1,o2);
int result12 = com1.compare(2,3);
System.out.println(result2);
(二)函数式接口
如果一个接口中,只声明了一个抽象方法,则称为函数式接口,可以使用@FunctionalInterface
注解。
Java内置四大核心函数式接口:
函数式接口 | 用途 |
---|---|
Consumer<T> | void accept(T t) |
Supplier<T> | T get() |
Function<T,R> | R apply(T t) |
Predicate<T> | Boolean test(T t) |
(三)方法引用
//当要传递给Lambda表达体的操作,已经有实现的方法了,就可以使用方法引用了
Consumer<String> con = str -> System.out.println(str);
con.accept("中国");
/**
上述方法中
Consumer的void accept(T t)
PrintStream的void println(T t)
可以使用格式: 类(或对象):: 方法名
*/
PrintStream ps = System.out;
Consumer<String> con = ps :: println;
/**
Supplier的T get()
Person的String getName()
*/
Person p = new Person("tom",23);
Supplier<String> sup = emp :: getName;
(四)Stream API
Stream API对集合数据进行操作,提供了一种高效且易于使用的处理数据的方式。
- Stream的实例化;
- 一系列中间操作(过滤、映射…);一个中间操作链,对数据源的数据进行处理
- 中止操作。一旦执行中止操作,就执行中间操作链,产生结果之后不再使用
1、实例化
方式一:通过集合
ArrayList<Integer> list = new ArrayList<>();
list.add(4);
list.add(2);
list.add(3);
//返回一个顺序流
Stream<Integer> stream = list.stream();
//返回一个并行流
Stream<Integer> parallelStream = list.ParallelStream();
方式二:通过数组
int[] arr = new int[]{2,3,6,1};
IntStream stream = Arrays.stream(arr);
方式三:
Stream<Integer> stream = Stream.of(1,2,3,4);
2、中间操作
(1)筛选与切片
//filter(Predicate p) - 接受Lambda,从流中排除某些元素
Stream<Person> stream = list.stream();
//查询年龄大于20的人
stream.filter(p -> p.getAge() > 20).forEach(System.out :: println);
//limit(n) - 截断流,使其元素不超过给定数量
Stream<Person> stream = list.stream();
//查询前三条数据
stream.limit(3).forEach(System.out :: println);
//skip(n) - 跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回空流
Stream<Person> stream = list.stream();
//排除掉前三条数据
stream.skip(3).forEach(System.out :: println);
//distinct() - 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
Stream<Person> stream = list.stream();
//去除重复元素
stream.distinct().forEach(System.out :: println);
(2)映射
//map(Function f) - 接收一个函数作为参数,将元素转换成其他形式
//List<String> list = Arrays.asList("aa","bb","cc");
//list.stream().map(str -> str.toUpperCase()).forEach(System.out :: println);
//返回姓名长度大于3的员工的姓名
Stream<String> stream = list.stream().map(Person :: getName);
stream.filter(name -> name.length() > 3).forEach(System.out :: println);
//flatMap(Function f) - 接收一个函数作为参数,将流中的每个值都换成另一个流,再连接为一个流
(3)排序
//sorted() - 产生一个新流,按自然排序排序
List<Integer> list = Arrays.asList(13,3,4,6,34,2);
list.stream().sorted().forEach(System.out :: println);
//sorted(Comparator com) - 产生一个新流,按比较器顺序排序
Stream<Person> stream = list.stream();
stream.sorted((p1,p2) -> {
return Integer.compare(p1.getAge(),p2.getAge());
}).forEach(System.out :: println);
3、中止操作
(1)匹配与查找
Stream<Person> stream = list.stream();
//allMatch(Predicate p) - 检查是否匹配所有元素
//检查是否所有人年龄都大于18岁
boolean allMatch = stream.allMatch(p -> p.getAge() > 18);
//anyMatch(Predicate p) - 检查是否至少匹配一个元素
//检查是否至少有一个人年龄大于18
boolean anyMatch = stream.anyMatch(p -> p.getAge() > 18);
//noneMatch(Predicate p) - 检查是否没有匹配的元素
//检查是否有人年龄大于18岁
boolean noneMatch = stream.noneMatch(p -> p.getAge() > 18);
//findFirst() - 返回第一个元素
Optional<Person> person1 = stream.findFirst();
//findAny() - 返回任意一个元素
Optional<Person> person2 = stream.findAny();
//count() - 返回流中元素总个数
long count = stream.filter(p -> p.getAge() > 18).count();
Stream<Integer> ageStream = stream.map(p -> p.getAge());
//max(Comparator c) - 返回流中最大值
Optional<Integer> maxAge = ageStream.max(Integer :: compare);
//min(Comparator c) - 返回流中最小值
Optional<Integer> minAge = ageStream.min(Integer :: compare);
//forEach(Consumer c) - 内部迭代
(2)归约
//reduce(T identity,BinaryOperator) - 将流中元素反复结合起来,得到一个值,返回T。identity-初始值
//计算1-10的和
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream().reduce(0,Integer :: sum);
//reduce(BinaryOperator) - 将流中元素反复结合起来,得到一个值,返回Optional<T>
//计算所有人的年龄综总和
Stream<Integer> ageStream = stream.map(Person :: getAge);
Integer sum = ageStream.reduce(Integer :: sum);
(3)收集
Stream<Person> stream = list.stream();
//collect(Collector c) - 将流转换为其他形式
//查找年龄大于18岁的人,结果返回为List
List<Person> personList = stream.filter(p -> p.getAge() > 18).collect(Collectors.toList());
personList.forEach(System.out :: println);
(五)Optional类
为了解决空指针异常,引入了Optional类。
//Optional.of(T t) - 创建一个Optional实例,t非空
//Optional.empty() - 创建一个空的Optional实例
//Optional.ofNullable(T t) - t可以为null
Girl girl = new Girl();
girl = null;
Optional<Girl> optionalGirl = Optional.ofNullable(girl);//输出的是Optional.empty()
Girl girl1 = optionalGirl.orElse(new Girl("Lucy"));//如果当前Optional内部封装的t是非空的,则返回内部的t;否则返回该方法中的参数