3.面向对象
1.类和对象的定义
1.类
:指对现实中对象的共性抽取,通常用来表示同一批具有相同特征和行为的对象 2.对象
:指类的实例化,其是计算机内存中的一块存储空间,用来存储现实中对象特性和行为的描述 3.类和对象的关系
2.类
1.属性
:用来描述对象的特征
局部变量 成员变量(属性) 位置 形参或方法、代码块内部 类的内部、方法的外部 默认值 没有 有 作用范围 从定义开始到定义的{}结束 当前整个类 命名冲突 局部变量之间不能重名 属性可以和局部重名,局部变量优先更高
2.方法
:用来描述对象行为方法修饰符 返回值类型 方法名(参数列表){
}
3.规则:
1.一个源文件中只能有一个public
类 2.一个源文件可以有多个非public
类 3.源文件的名称应该和public
类的类名保持一致 4.如果一个类定义在某个包中,那么package
语句应该在源文件的首行 5.如果源文件包含import
语句,那么应该放在package
语句和类定义之间;如果没有package
语句,那么import
语句应该在源文件中最前面 6.import
语句和package
语句对源文件中定义的所有类都有效;同一源文件中不能给不同的类不同的包声明
1.属性
1.类的属性分为
1.成员变量
1.类变量/静态变量(static
修饰) 2.实例变量
类变量 实例变量 修饰符 static关键字 无 访问方式 类或类对象 类的对象 生存周期 静态成员不依赖于类的特定实例,被类的所有实例共享;static 修饰的方法或者变量不需要依赖于对象来进行访问,只要这个类被加载,Java 虚拟机就可以根据类名找到它们 与对象同存亡,被对象调用后才分配内存,调用结束时内存释放 加载时机 运行时Java 虚拟机只为静态变量分配一次内存,在加载类的过程中完成静态变量的内存分配 每创建一个实例,Java 虚拟机就会为实例变量分配一次内存
2.方法
1.方法重载
1.规则
1.方法名必须相同,参数列表必须不同(数据类型,个数,顺序) 2.跟访问修饰符,返回值,异常没有关系 2.本质
1.重载就是同一个类中多个同名方法
根据不同的传参
来执行不同的逻辑处理
2.方法重写
1.规则
1.存在于继承关系 2.方法名,参数列表,返回值类型必须与父类保持一致 3.访问修饰符必须大于等于父类 4.子类不允许抛出比父类更大的异常 5.构造方法无法被重写 6.子类可以继承并重写父类中的静态方法,但是多态情况下,子类的重写没有意义,因为父类引用执行的静态方法结果永远是父类内容 2.本质
1.重写是子类对父类的允许访问的方法的实现过程进行重新编写 2.当子类对方法进行重写之后,子类对象调用方法执行的是重写之后的内容
区别点 重载方法 重写方法 发生范围 同一个类 子类中 参数列表 必须修改 一定不能修改 返回类型 可修改 一定不能修改 异常 可修改 可以减少或删除,一定不能抛出新的或者更广的异常 访问修饰符 可修改 一定不能做更严格的限制(可以降低限制) 发生阶段 编译期 运行期
3.this()方法
1.用来调用当前类的构造方法 2.只能写在构造方法
有效代码第一行
3.由this()
实参列表决定调用哪个构造方法 4.构造方法无法通过this()
调用自身,这样会成为死循环调用,会造成内存溢出 5.构造方法之间也不能相互循环调用
,否则会死循环,会造成内存溢出
4.super()方法
1.用来调用父类构造方法 2.只能写在构造方法
有效代码第一行
3.由super()
实参列表决定调用哪个父类构造方法 4.this()
和super()
不能同时出现 5.如果子类构造方法
没有显式调用父类构造,那么无参的super()
默认存在于子类构造方法第一行 6.当子类构造显式调用this()
时,当前构造方法就只能使用默认的无参super()
5.构造方法
1.构造方法
是用来创建对象,并且同时给对象的属性赋值 2.实例变量
没有手动赋值的时候,系统会赋默认值 3.访问修饰符
只能使用public
,且构造方法名和类名
必须一致 4.构造方法
不需要指定返回值类型 5.使用new
关键字来调用构造方法 6.当类中没有提供显式构造方法
,默认提供一个无参数的构造方法 7.当类中提供了显式构造方法
,将不再默认提供无参数构造方法 8.建议将无参数构造方法显示声明,这样一定不会出问题 9.构造方法支持方法重载
,不支持方法重写
6.静态方法和普通方法
1.定义
: 使用static
修饰的方法称为静态方法 2.加载时机
:随着类加载
而加载到内存中,非静态方法
属于对象的具体实例,只有在类的对象创建时才会加载入内存中 3.静态方法
只能访问静态数据
,非静态方法
既可以访问静态数据
又可以访问非静态数据
4.因为静态方法
和静态数据
会随着类加载而被加载到内存中,而非静态方法
和非静态数据
只有在对象创建时
才会加载到内存中,如果静态方法
调用了了非静态数据
,则在静态方法加载时无法从内存中找到非静态数据
,势必会出错,这种做法是Java虚拟机
不允许的 5.引用静态方法时,可以用类名.方法名
或者对象名.方法名
的形式 7.静态方法中不能使用this
和super
关键字
7.代码块和静态代码块
class 类名{
static {
}
{
}
}
1.static
修饰的代码块称为静态代码块
2.静态代码块
只有在类加载
时才会执行,而且一个静态代码块
没有执行完成时不会执行下一个静态代码块
,一般将一些只需要进行一次的初始化操作都放在静态代码块中进行 3.普通代码块
是在创建对象的时执行,创建几个对象就执行多少次 4.执行顺序
:静态代码块
> 普通代码块
> 构造函数
5.静态代码块
只能访问类中的静态成员变量
6.普通代码块
只能访问类中的成员变量
7.位置
:两者一般都写在属性之下,方法之上,静态代码块类似于一个方法,但它不可以存在于任何方法体中,静态代码块可以置于类中的任何地方,类中可以有多个静态初始化块 8.作用
:静态代码块一般是为了给静态成员变量赋值,代码块一般是为了给成员变量赋值 9.如果类中包含多个静态代码块,则Java
虚拟机将按它们在类中出现的顺序依次执行它们,每个静态代码块只会被执行一次 10.静态代码块与静态方法一样,不能直接访问类的实例变量和实例方法,而需要通过类的实例对象
来访问
8.方法返回值
1.指获取到的某个方法体中的代码执行后产生的结果
2.返回值的作用是接收结果,使得其可以用于其他的操作 3.方法返回值可以是基本数据类型
,也可以是引用数据类型
4.类加载机制
3.对象
1.对象的创建方式
1.new
关键字
2.反射
,Class
类的newInstance
使用类的public
无参构造,因此必须有public
无参构造才行,内部调用还是Constructor
的 newInstance()
方法 3.Constructor.newInstance
,java.lang.relect.Constructor
有一个newInstance
方法可以创建对象,通过这个newInstance
方法调用有参数(不再必须是无参)的和私有的构造函数(不再必须是public
) 4.Clone
方法,必须先实现Cloneable
接口并复写Object
的clone
方法,因为该方法是protected
的,若不复写外部无法调用 5.反序列化,当序列化和反序列化一个对象,JVM
会创建一个单独的对象,在反序列化时,JVM
创建对象并不会调用任何构造函数,类需要实现Serializable
接口 6.可参考:https://cloud.tencent.com/developer/article/1497720
7.静态方法只能访问静态成员,不可以访问非静态成员;因为静态方法加载时,优先于对象存在,所以没有办法访问对象中的成员 8.静态方法中不能使用this
,super
关键字;因为this
代表对象,而静态在时有可能没有对象,所以this
无法使用
2.对象的创建过程
1.给属性分配空间,赋予默认值 2.给属性赋予初始值 3.利用构造给属性赋值
3.对象的内存结构
1.对象引用
保存在栈
中,引用保存的是对象的地址(堆地址)
4.this关键字
1.this
代表当前对象 2.this.属性名
代表当前对象的属性 3.位置
:可以存在于构造方法
和普通方法
内部 4.当构造方法中实参和形参的变量名相同
时,可以使用this
关键字指定当前类的属性
5.super关键字
1.super.属性名
:用来指明父类属性 2.super.方法名
:用来指明调用父类方法
6.深拷贝和浅拷贝
1.浅拷贝
:对基本数据类型进行值
传递,对引用数据类型进行引用传递
的拷贝 2.深拷贝
:对基本数据类型进行值
传递,对引用数据类型创建一个新的对象,并复制其内容
1.浅拷贝
1.Java
中数据类型可以分为两大类:基本类型
和引用类型
1.基本类型
:byte、char、short、int、long、float、double、boolean
2.引用类型
:类、接口、数组、枚举等 3.浅拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象,因此原始对象及其副本引用同一个对象,当修改其中一个对象的属性,另一个也会跟着变化
1.实现浅拷贝
1.实现浅拷贝需要满足以下条件
1.实现Cloneable
接口,否则会抛出 CloneNotSupportedException
异常 2.重写clone()
方法,并使用super.clone()
来创建新的对象 2.注意:Cloneable
接口是个空接口,super.clone()
调用的是Object
的clone()
方法,Object
类中clone()
是一个本地方法
2.深拷贝
深拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,无论该字段是值类型的还是引用类型,都复制独立的一份,当修改其中一个对象的任何内容时,都不会影响另一个对象的内容
1.实现深拷贝
1.深拷贝的原理就是要让原始对象
和克隆对象
所具有的引用类型属性不指向同一块堆内存
,实现方式如下如示
1.让每个引用类型属性内部都重写clone()
方法
1.引用类型不能实现深拷贝,那么可以将每个引用类型都拆分为基本类型,分别进行浅拷贝 2.这种方式的缺点
:有多少个引用类型,就要重写多少次,如果存在很多引用类型,那么代码量会很大,所以这种方法不合适 package com. ys. test ;
public class Address implements Cloneable {
private String provices;
private String city;
public void setAddress ( String provices, String city) {
this . provices = provices;
this . city = city;
}
@Override
public String toString ( ) {
return "Address [provices=" + provices + ", city=" + city + "]" ;
}
@Override
protected Object clone ( ) throws CloneNotSupportedException {
return super . clone ( ) ;
}
}
@Override
protected Object clone ( ) throws CloneNotSupportedException {
Person p = ( Person ) super . clone ( ) ;
p. address = ( Address ) address. clone ( ) ;
return p;
}
2.利用序列化和反序列化
1.序列化
是将对象写到流中便于传输,反序列化
则是把对象从流中读取出来 2.写到流中的对象则是原始对象的一个拷贝
,因为原始对象还存在JVM
中,所以可以利用对象的序列化
产生克隆对象,然后通过反序列化
获取这个对象 3.因为序列化产生的是两个完全独立的对象
,所有无论嵌套多少个引用类型,序列化都是能实现深拷贝的
public Object deepClone ( ) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream ( ) ;
ObjectOutputStream oos = new ObjectOutputStream ( bos) ;
oos. writeObject ( this ) ;
ByteArrayInputStream bis = new ByteArrayInputStream ( bos. toByteArray ( ) ) ;
ObjectInputStream ois = new ObjectInputStream ( bis) ;
return ois. readObject ( ) ;
}
4.抽象类
1.用abstract
修饰的类 2.抽象类不能实例化对象
,设计理念是为了服务其他子类,所以规定不能实例化,可参考https://blog.csdn.net/qq_42859864/article/details/103394768
3.其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样 4.由于抽象类不能实例化对象,所以抽象类必须被继承才能被使用
5.父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法 6.Java
中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口 7.private
,static
,final
不能与abstract
连用,因为abstract
需要被继承实现且无法被实例化
1.抽象方法
1.用abstract
修饰的方法 2.抽象类中的抽象方法只是声明,不包含方法体 3.抽象方法具体实现由它的子类实现 4.抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类 5.任何子类必须重写父类的抽象方法
,或者声明自身为抽象类 6.最终必须有子类实现该抽象方法,否则从最初的父类到最终的子类都不能用来实例化对象
7.构造方法,静态方法(用 static
修饰的方法)不能声明为抽象方法
5.接口
interface 接口名{
}
class 类名 implements 接口名{
}
1.使用关键字interface
2.类是描述对象的属性和方法,接口则是包含类要实现的方法
3.类通过实现接口的方式,从而来实现接口的抽象方法 4.除非实现接口的类是抽象类
,否则该类要定义接口中的所有方法 5.接口无法被实例化
,但是可以被实现,一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类
6.接口中可以有属性,且属性都是公开静态常量
(反编译后可以看到,所以需要赋初始值) 7.接口中的方法都是公开抽象方法
(反编译后可以看到,所以没有方法体) 8.构造方法不能是抽象的
,所以接口中不存在构造方法,抽象类中存在默认的构造方法 9.因为接口中内容的修饰符固定,所以一般可以省略不写 10.jdk8
后,接口中可以存在默认方法default 返回值类型 方法名( 参数列表) {
}
11.为了能给接口拓展新功能,而又不必每个子类都要实现此方法,因此Java8
新加了default
关键字,被其修饰的方法可以不必由子类实现,并且由dafault
修饰的方法在接口中有方法体,这打破了Java
之前对接口方法的规范 12.如果接口中的方法跟父类中的方法出现冲突,优先执行父类方法(类优先规则) 13.如果实现类实现的多个接口中存在方法冲突,则实现类必须对该方法再次进行重写,执行自己重写后的内容 14.接口中可以存在静态方法
static 返回值类型 方法名( 参数列表) {
}
15.JDK9
接口中可以存在私有方法private 返回值类型 方法名( 参数列表) {
}
16.Java
接口可以继承多个接口
1.Java
不允许类多继承的主要原因:如果A
同时继承B
和C
,而B
和C
同时有一个D
方法,A
无法决定继承哪一个 2.接口不存在这样的问题,接口全都是抽象方法继承谁都无所谓,所以接口可以继承多个接口 3.一个类如果实现了一个接口,则要实现该接口的所有方法,包括该接口继承的接口的所有方法 4.因为接口的方法默认是public
类型的,所以在实现时一定要用public
来修饰,否则缩小了方法的使用范围
1.接口的分类
1.常量式接口
:接口中只定义了常量,未定义方法 2.标记式接口
:接口中未定义任何内容 3.函数式接口
:接口中只定义一个方法 4.普通接口
:接口中定义了多个方法
2.实现类
class 类名 extends 父类类名 implements 接口名{
}
1.必须对接口中所有的方法提供方法实现 2.如果实现类不想实现接口中的方法,则其自身只能成为抽象类
3.一个实现类可以同时实现多个接口,一个接口也可以拥有多个实现类 4.当实现类实现多个接口时,必须对所有接口中的所有方法都提供方法实现 5.实现类在实现接口的同时也可以继承父类 6.仍然可以使用多态创建实现类对象,但利用多态创建对象时,引用无法实现类独有内容 7.多态中如果想要使用实现类独有内容,则需要进行强制类型转换,实现类之间不能进行强转
3.接口和抽象类的区别
抽象类 接口 关键字 abstract class /extends interface/implements 属性 没有具体要求 公开静态常量 方法 可以存在非抽象的方法 公开抽象方法 构造方法 有构造方法 没有构造方法 继承性 单继承 多继承
1.接口
:自上向下,不需要考虑子类,实现类需要去实现接口的内容 2.抽象类
:自下向上,是对子类共性的抽取
6.三大核心思想
1.封装
1.保证了程序和数据都不受外部干扰且不被误用 2.封装的目的在于保护信息,保护对象中的数据不被外界随意访问 3.步骤:
1.属性私有化
private 数据类型 属性名;
2.提供取值和赋值的方法(getter\setter
方法),使外界通过调用方法的形式访问属性
2.继承
1.对象创建过程(继承关系)
1.给父/子类属性分配空间,给父/子类属性赋默认值 2.给父类属性赋初始值 3.利用父类构造给父类属性再次赋值 4.给子类属性赋初始值 5.利用子类构造给子类属性再次赋值 6.先创建父类对象才能创建子类对象
3.多态
父类引用 引用名 = new 子类类名( ) ;
1.父类引用可以指向多个不同的子类对象
2.使用
1.建立在继承关系
之上 2.实际创建的是子类对象
3.方法执行优先执行子类中重写
的内容 4.父类引用无法使用子类独有
的内容 3.引用类型之间的相互转换
1.自动类型转换
父类类名 引用名 = new 子类类名( ) ;
2.强制类型转换
1.父类引用赋值给子类引用 2.转换的子类类型必须跟父类引用指向的子类类型保持一致 3.子类之间不能进行类型转换 子类类名 引用名 = (子类类名)父类引用名;
4.多态的使用场景
1.数组
:将数组数据类型声明为父类类型,则该数组中可以存放父类对象及其子类对象
2.参数
:将形参声明为父类类型,调用方法时则可以传入父类对象及其子类对象
3.返回值
:将返回值类型声明为父类类型,则该方法可以返回父类对象及其子类对象
5.多态的好处
1.instanceof关键字
1.instanceof
是Java
中的二元运算符
,左边是对象,右边是类 2.当对象是类及其子类所创建对象时,返回true
;否则,返回false
3.作用
:判断引用是否与指定类类型兼容,兼容返回true
,不兼容返回false
4.语法引用名 instanceof 类名
5.应用场景:通常用于类型强转之前判断是否兼容,否则不能强转
8.内部类
1.类的内部再次声明定义类 2.作用
:打破封装,又不破坏封装 3.分类
1.成员内部类 2.静态内部类 3.局部内部类 4.匿名内部类
1.成员内部类
class 外部类名{
class 内部类类名{
}
}
2.静态内部类
class 外部类类名{
static class 内部类类名{
}
}
1.位置
2.使用
1.可以定义静态内容
,但是无法访问外部类非静态内容
3.如果外部类属性,内部类属性,内部类局部变量出现重名
1.访问外部类静态属性:外部类类名.属性名 2.访问内部类静态属性:外部类类名.内部类类名.属性名 4.静态内部类对象创建要依赖于外部类类名
,可直接通过外部类类名.内部类类名.静态内容
的方式直接访问内部类静态内容
3.局部内部类
class 外部类类名{
访问修饰符 返回值类型 方法名( 参数列表) {
class n内部类类名{
}
}
}
1.位置
2.使用
1.作用范围:从定义行开始,到所在代码块结束 2.无法定义静态内容
3.可以访问外部类的局部变量
,但是该局部变量必须是常量
(jdk7
之前必须用final
显式修饰,jdk8
之后只需要没有再次更改值) 4.局部内部类对象只能在所属的外部类方法
中创建
4.匿名内部类
接口名/ 父类类名 引用名 = new 接口名/ 父类类名( ) {
}
1.位置
2.特点
3.使用
1.必须继承一个父类或者实现一个接口 2.一个匿名内部类只能创建一个对象,并且该对象只属于语法声明的父类或者接口引用 3.一个引用只能拥有一个匿名内部类,一个匿名内部类也只能属性一个引用 4.匿名内部类中只能拥有一个默认的无参构造,无法显式声明构造 5.匿名内部类无法定义静态内容
5.lambda表达式
接口名 引用名 = (形参列表)-> {
1.简化匿名内部类
,为接口提供实现类对象 2.使用
1.jdk8
之后只能作用于函数式接口
2.小括号中的形参声明
要严格跟函数式接口
中的形参声明保持一致
3.大括号中的内容是对接口方法提供的方法实现
3.简化方式
1.形参的数据类型可省略 2.如果形参只有一个
,小括号可省略,注意如果没有参数,()
不可以省略 3.如果方法体语句只有一条
,大括号可省略 4.如果方法体语句只有一条return返回语句
,大括号和return
都可省略,注意return
和{}
必须同时省略
9.包装类
1.基于八大基本数据类型
byte short int long float double char boolean Byte Short Integer Long Float Double Character Boolean
2.基本数据类型跟包装类型转换
3.基本数据类型和String
转换
4.包装类型和String
转换
10.常用类
1.Object
1.所有类的父类 2.如果一个类没有显式声明父类,那一定直接继承自Object
3.Object
中存放所有类都默认拥有的内容,子类可以根据自身需要选择是否对继承过来的方法进行重写 4.常用方法
1.getClass()
:返回当前引用的实际对象类型 2.hashCode()
:获取当前对象的哈希码值 3.equals(Object o)
:判断两个对象是否相同,默认比较的是内存地址
4.toString()
:返回对象的详细信息,会在输出引用名时自动调用 5.finalize()
:回收垃圾对象
1.finalize()
1.finalize()
方法用于垃圾回收,一般情况下不需要实现 2.当对象被回收的时候需要释放一些资源;如socket
链接,在对象初始化时创建,整个生命周期内有效,那么需要实现finalize
方法关闭这个链接 3.但是当调用finalize
方法后,并不意味着GC
会立即回收该对象,所以有可能真正调用的时候,对象又不需要回收了,然后到了真正要回收的时候,因为之前调用过一次,这次又不会调用了,产生问题,所以不推荐使用finalize
方法 4.垃圾回收机制
:当内存已经满到不足以支持创建新的对象时,虚拟机会自动调用垃圾对象的finalize()
方法对该垃圾对象进行销毁,从而释放内存空间 5.垃圾对象的判断标准
:当对象没有引用指向的时候,虚拟机则会该对象为垃圾对象 6.手动进行垃圾回收
:代码中手动调用System.gc()
方法可以手动进行强制垃圾对象回收
2.String
3.Arrays
4.Date
整型和Date类之间并不存在直接的对应关系,只是你可以使用int型为分别表示年、月、日、时、分、秒,这样就在两者之间建立了一个对应关系,在作这种转换时,你可以使用Date类构造函数的三种形式: Date(int year, int month, int date):以int型表示年、月、日 Date(int year, int month, int date, int hrs, int min):以int型表示年、月、日、时、分 Date(int year, int month, int date, int hrs, int min, int sec):以int型表示年、月、日、时、分、秒 在长整型和Date类之间有一个很有趣的对应关系,就是将一个时间表示为距离格林尼治标准时间1970年1月1日0时0分0秒的毫秒数。对于这种对应关系,Date类也有其相应的构造函数:Date(long date)。 获取Date类中的年、月、日、时、分、秒以及星期你可以使用Date类的getYear()、getMonth()、getDate()、getHours()、getMinutes()、getSeconds()、getDay()方法,你也可以将其理解为将Date类转换成int。 而Date类的getTime()方法可以得到我们前面所说的一个时间对应的长整型数,与包装类一样,Date类也有一个toString()方法可以将其转换为String类。 有时我们希望得到Date的特定格式,例如20020324,我们可以使用以下方法,首先在文件开始引入
import java. text. SimpleDateFormat ;
java. util. Date date = new java. util. Date( ) ;
SimpleDateFormat sy1= new SimpleDateFormat ( "yyyyMMdd" ) ;
String dateFormat= sy1. format ( date) ;
SimpleDateFormat sy= new SimpleDateFormat ( "yyyy" ) ;
SimpleDateFormat sm= new SimpleDateFormat ( "MM" ) ;
SimpleDateFormat sd= new SimpleDateFormat ( "dd" ) ;
String syear= sy. format ( date) ;
String smon= sm. format ( date) ;
String sday= sd. format ( date) ; ```
11.整型常量池
1.Integer
常量池是Java 5
中引入的一个有助于节省内存、提高性能的特性 2.Integer
中有个静态内部类IntegerCache
,里面有个常量cache[]
,即Integer
常量池,常量池的大小为一个字节(-128~127)
,当用包装类直接使用区间内的数字时而不new
对象时,会直接从常量池中取出,而不会额外在堆中开辟空间,节省内存空间 3.所有整数类型的类都有类似的缓存机制
1.ByteCache
用于缓存Byte
对象 2.ShortCache
用于缓存Short
对象 3.LongCache
用于缓存Long
对象 4.Byte
,Short
,Long
的常量池范围默认都是: -128 到 127
;Byte
的所有值都在常量池中,所以用生成的相同值对象都是相等的 5.Character
对象也有CharacterCache
缓存 池,范围是 0
到 127
6.所有整型(Byte,Short,Long
)的比较规律与Integer
是一样的,但只有 Integer
可以通过参数改变范围,通过 JVM
的启动参数 -XX:AutoBoxCacheMax=size
修改
Integer a = 1 ;
Integer a = Integer . valueOf ( 1 ) ;
public static Integer valueOf ( int i) {
assert IntegerCache . high >= 127 ;
if ( i >= IntegerCache . low && i <= IntegerCache . high)
return IntegerCache . cache[ i + ( - IntegerCache . low) ] ;
return new Integer ( i) ;
}
当 i >= - 128 && i <= 127 时,Integer . valueOf ( i) 会将 i 存储在内部类 IntegerCache 的static final Integer cache[ ] 里,这一字节的缓存内存地址是静态的,返回值即:
IntegerCache . cache[ i + ( - IntegerCache . low) ]
因此:
Integer a = 1 ;
Integer b = 1 ;
a 和 b 的引用都指向同一个对象,即 a == b。
3.泛型
1.Java
泛型(generics
)是JDK 5
中引入的一个特性,,泛型
提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型 2.泛型的本质是参数化类型
,即操作的数据类型
被指定为一个参数
1.泛型方法
1.该方法在调用时可以接收不同类型的参数
,根据传递给泛型方法的参数类型
,编译器适当地处理每一个方法调用 2.规则
1.所有泛型方法声明都有一个类型参数声明部分
(由尖括号分隔),该类型参数声明部分在方法返回类型之前
2.每一个类型参数声明部分
包含一个或多个类型参数,参数间用逗号隔开,一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符 3.类型参数能被用来声明返回值类型
,并且能作为泛型方法得到的实际参数类型的占位符 4.泛型方法体的声明和其他方法一样,类型参数只能代表引用类型
,不能是基本数据类型
3.java
中泛型标记符
1.E - Element
(在集合中使用,因为集合中存放的是元素) 2.T - Type
(Java 类) 3.K - Key
(键) 4.V - Value
(值) 5.N - Number
(数值类型) 6.?
- 表示不确定的 java
类型
2.泛型类/接口
1.泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分
2.泛型类的类型参数声明部分
包含一个或多个类型参数,参数间用逗号隔开 3.一个泛型参数
,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符
3.泛型集合
1.使用泛型集合
时注意类型要保持一致
,否则会出现类型转化异常
4.有界泛型
1.有界的泛型
:限制那些被允许传递到一个类型参数的类型种类范围
2.其界限分为上界
和下界
1.上界
1.关键字extends
2.比如一个操作数字的方法
可能只希望接受Number
或者Number子类
的实例 3.声明一个有界的类型参数
,首先列出类型参数
的名称,后跟extends
关键字,最后紧跟它的上界
4.//1
处会出现错误,因为getUperNumber()
方法中的参数已经限定了参数泛型上限为Number
,所以泛型为String
是不在这个范围之内 2.下界
1.类型通配符下界
通过形如List<? super Number>
来定义 2.表示类型只能接受Number
及其上层父类类型
,如Object
类型的实例
4.枚举(Enum)
package com. wd. entity ;
public enum Season {
SPRING , SUMMER , AUTUMN , WINTER
}
package com. wd. test ;
import com. wd. entity. Season ;
public class Test01 {
public static void main ( String [ ] args) {
test ( ) ;
}
public static void test ( ) {
System . out. println ( "春天:" + Season . SPRING ) ;
System . out. println ( "夏天:" + Season . SUMMER . name ( ) ) ;
System . out. println ( "秋天:" + Season . AUTUMN . ordinal ( ) ) ;
System . out. println ( "冬天:" + Season . WINTER . ordinal ( ) ) ;
}
}
1.枚举类型的关键字:enum
,引入版本:JDK5
2.枚举类本质上是一个class
隐式继承于Enum
抽象类(该继承关系不显示,可通过反编译显示),且不能继承其他类,因为Java
类不支持多继承,但可以实现接口 3.枚举类是final
的,因此无法继承该类 4.枚举类的构造函数只能是private
,因为枚举的实现是多例模式
,为了防止在类的外部就可以通过构造器来新建实例 5.抽象Enum
类有name
和ordinal
函数,其中name
方法的作用是取得名字,ordinal
方法的作用的是返回每个枚举常量的索引,类似数组索引
6.values()
:返回枚举类中所有的值,valueOf()
:方法返回指定字符串值的枚举常量 7.定义的每个枚举值都是该类的一个公共,静态,常量成员,且该成员的类型仍然是当前类
5.注解(Annotation)
1.Java
注解,JDK5
引入的一种注释机制,同Class
和Interface
一样,注解
也属于一种类型 2.Java
注解的修饰符是@Interface
,注解可以修饰Java
中的类
、方法
、变量
、参数
和包
等 3.不同于Javadoc
,Java注解
可以通过反射
获取注解内容,同时编译期生成.class
文件时注解可以被嵌入到字节码
中 4.Java
虚拟机可以保留注解
内容,在运行时可以获取到注解
内容 , 同时支持自定义Java注解
5.元数据
:注解又称为元数据
,即一种描述数据的数据 6.元注解
:作用在其他注解上的注解,即一种描述注解的注解,一般用于自定义注解
7.Java SE
提供 11 个内置注解
,其中有5 个是基本注解
,自于java.lang
包;有 6个是元注解
,来自于java.lang.annotation
包 8.基本注解包括
1.@Override
2.@Deprecated
3.@SuppressWarnings
4.@SafeVarargs
5.@FunctionalInterface
9.元注解包括
1.@Documented
2.@Target
3.@Retention
4.@Inherited
5.@Repeatable
6.@Native
10.注解常见作用
1.生成帮助文档
: @see
、@param
和 @return
等 2.跟踪代码依赖性,实现替代配置文件功能
:Spring 2.5
开始的基于注解配置,作用就是减少配置 3.编译时进行格式检查
:@Override
编译时检查是否重写父类方法,如果父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法
1.基本注解
1.@Override
1.@Override
只能修饰方法
且只能用于方法重写
2.@Override
检查该方法是否是重写方法,如果发现其父类或引用的接口中并没有该方法时,会报编译错误
2. @Deprecated
3.@SuppressWarining
1.@SuppressWarnings
注解指示被该注解修饰的元素(以及该程素中的所有子元素)取消显示指定的编译器警告
,且会作用于该元素的所有子元素 2.通常情况下如果程序中使用没有泛型限制的集合
将会引起编译器警告,为了避免这种编译器警告,可以使用 @SuppressWarnings
注解消除这些警告 3.注解的使用方式有以下三种
1.抑制单类型的警告:@SuppressWarnings("unchecked")
2.抑制多类型的警告:@SuppressWarnings("unchecked","rawtypes")
3.抑制所有类型的警告:@SuppressWarnings("all")
关键字 用途 all 抑制所有警告 boxing 抑制装箱、拆箱操作时候的警告 cast 抑制映射相关的警告 dep-ann 抑制启用注释的警告 deprecation 抑制过期方法警告 fallthrough 抑制在 switch 中缺失 breaks 的警告 finally 抑制 finally 模块没有返回的警告 hiding 抑制相对于隐藏变量的局部变量的警告 incomplete-switch 忽略不完整的 switch 语句 nls 忽略非 nls 格式的字符 null 忽略对 null 的操作 rawtypes 使用 generics 时忽略没有指定相应的类型 restriction 抑制禁止使用劝阻或禁止引用的警告 serial 忽略在 serializable 类中没有声明 serialVersionUID 变量 static-access 抑制不正确的静态访问方式警告 synthetic-access 抑制子类没有按最优方法访问内部类的警告 unchecked 抑制没有进行类型检查操作的警告 unqualified-field-access 抑制没有权限访问的域的警告 unused 抑制没被使用过的代码的警告
4.@SafeVarargs
1.@SafeVarargs
注解忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告 2.@SafeVarargs
注解不适用于非 static
或非 final
声明的方法,对于未声明为 static
或final
的方法,如果要抑制unchecked
警告,可使用@SuppressWarnings
注解
5.@FunctionalInterface
1.@FunctionalInterface
注解用来指定某个接口必须是函数式接口 2.@FunctionalInterface
注解只能修饰接口,不能修饰其它程序元素 3.函数式接口
是为Java 8
的Lambda
表达式准备的,Java 8
允许使用 Lambda
表达式创建函数式接口
的实例,因此Java 8
专门增加@FunctionalInterface
注解 4.@FunctionalInterface
注解的作用只是告诉编译器检查这个接口,保证该接口只能包含一个抽象方法,否则就会编译出错
2.元注解
1.元注解
是负责对其它注解进行说明的注解,自定义注解时可以使用元注解 2.Java 5
定义了4
个元注解
1.@Documented
2.@Target
3.@Retention
4.@Inherited
3.Java 8
增加2
个元注解
4.这些注解都在java.lang.annotation
包中
1.@Documented
1.@Documented
是一个标记注解,没有成员变量 2.@Documented
标记这些注解是否包含在用户文档中,注解修饰的注解类会被JavaDoc
工具提取成文档 3.默认情况下JavaDoc
不包括注解,如果声明注解时指定@Documented
,就会被JavaDoc
工具处理,注解类型信息会被包括在生成的帮助文档中
2.@Target
1.@Target
注解用来指定一个注解的使用范围
,即被 @Target
修饰的注解可以用在什么地方 2.@Target
注解有一个成员变量value
用来设置范围,value
是 java.lang.annotation.ElementType
枚举类型的数组 3.下表为ElementType
的枚举常量
名称 说明 TYPE 用于 类、接口(包括注解类型)或 enum 声明 FIELD 用于 字段声明(包括enum常量) METHOD 用于 方法声明 PARAMETER 用于 正式参数声明 CONSTRUCTOR 用于 构造函数声明 LOCAL_VARIABLE 用于 局部变量声明 ANNOTATION_TYPE 用于 注释类型声明 PACKAGE 用于 包声明 TYPE_PARAMETER 用于 类型参数声明 TYPE_USE 用于 类型的使用
3.@Retention
1.@Retention
用于描述注解的生命周期
,即该注解被保留的时间长短 2.@Retention
注解中的成员变量value
用来设置保留策略,value
是 java.lang.annotation.RetentionPolicy
枚举类型 3.下表为RetentionPolicy
的枚举常量
名称 说明 SOURCE 源文件有效 ,编译期丢弃 CLASS 默认状态,字节码文件中有效,注释将由编译器记录在类文件中,运行时丢弃 RUNTIME 运行时有效,注释将由编译器记录在类文件中,并由VM在运行时保留,因此用RUNTIME修饰的注解可以反射式读取
3.生命周期长短排序为:SOURCE
< CLASS
< RUNTIME
4.如果需要运行时动态获取注解信息,只能用RUNTIME
注解;如果需要编译时进行一些预处理操作,可选用CLASS
注解;如果只是做一些检查性的操作,可选用SOURCE
注解
4.@Inherited
1.@Inherited
是一个标记注解,用来指定该注解可以被子类继承 2.@Inherited
即如果某个类使用了被@Inherited
修饰的注解,则其子类将自动具有该注解
5.@Repeatable
1.注解是Java 8
增加的,它允许在相同的程序元素中重复注解 2.Java 8
版本以前,同一个程序元素前最多只能有一个相同类型的注解,Java 8
开始支持@Repeatable
,标识某注解可以在同一个声明上使用多次
6.@Native
1.@Native
注解修饰成员变量,则表示这个变量可以被本地代码引用
3.自定义注解
1.声明自定义注解使用@interface
关键字,默认情况下,注解可以在程序的任何地方使用,通常用于修饰类、接口、方法和变量等
public @interface Test {
}
2.定义注解和定义类相似,注解前面的访问修饰符
和类一样有两种
1.公有访问权限(public
) 2.默认访问权限(default
,默认不写) 3.不包含任何成员变量
的注解称为标记注解
,上面声明的Test
注解以及基本注解中的 @Override
注解都属于标记注解 4.根据需要注解中可以定义成员变量
,成员变量以无形参的方法
形式来声明,其方法名和返回值定义了该成员变量的名字和类型 5.如果没有在声明时指定成员变量的默认值
则在使用时需要手动赋值
,否则会报错public @interface MyTag {
String name ( ) ;
int age ( ) ;
}
public class Test {
@MyTag ( name= "xx" , age= 6 )
public void info ( ) {
. . .
}
. . .
}
6.成员变量也可以有访问权限修饰符
,但是只能有公有权限
和默认权限(不显式声明)
7.注解中的成员变量
也可以有默认值,可使用default
关键字指定默认值public @interface MyTag {
String name ( ) default "java" ;
int age ( ) default 1 ;
}
8.如果为注解的成员变量指定了默认值,那么使用该注解时可以不为这些成员变量赋值,而是直接使用默认值public class Test {
@MyTag
public void info ( ) {
. . .
}
. . .
}
9.可以在使用注解时为成员变量指定值,如果为注解的成员变量指定了值,则默认值不会起作用
10.根据注解是否包含成员变量
分为以下两类
1.标记注解
:没有定义成员变量的注解类型被称为标记注解,这种注解仅利用自身的存在与否来提供信息 2.元数据注解
:包含成员变量的注解,因为可以接受更多的元数据,所以也被称为元数据注解 11.注解的使用基于反射
,通过反射可以去获取注解中的信息
1.实例
6.反射
1.反射的定义
1.Java反射是指:在运行状态中,对于任意一个类,能够知道该类的所有属性和方法等信息,对于任意一个对象,能够调用该对象的任意方法和属性等信息,并且能改变它的属性
2.反射的作用
1.反射机制是Java
被视为准动态语言 的一个关键性质(该机制允许程序在运行时
通过反射取得任何一个已知名称的class
的内部信息(运行时类的属性,方法,构造,访问修饰符,注解等),并可于运行时改变内容或调用方法
2.反射的优缺点
1.优点
1.反射机制
允许程序在运行时取得任何一个已知名称的class
的内部信息,包括包括其modifiers
(修饰符),fields
(属性),methods
(方法)等,并可于运行时改变fields
内容或调用methods
2.可以动态的创建和使用对象
(框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支持 2.缺点
1.使用反射基本是解释执行
,对执行速度有影响 2.使用反射可以打破封装性,导致java对象的属性不安全(可以修改对象的私有属性)
3.反射的使用步骤
1.使用反射需要首先需要获取Class
类 2.构造该类的实例对象
3.通过实例对象获取到需要的成员变量和方法
等信息或直接通过Class
对象调用内部信息 说明:
1.获得Class
有三种方式
1.通过Class
对象的forName()
静态方法,可能抛出ClassNotFoundException
异常 2.通过对象调用getClass()
方法 3.通过类名.class
方式,该方法最为安全可靠,程序性能更高,同时说明任何一个类都有一个隐含的静态成员变量class
2.一个类在JVM
中有且只有
一个Class
实例 3.setAccessible(true)
启用和禁用访问安全检查的开关,true
表示反射的对象在使用时应该取消java
语言的访问检查,反之不取消;如果不取消直接改变类的私有内容
会报错 4.反射只能获取当前类声明的方法,无法获取父类中声明的方法,直接通过反射获取子类的对象是不能获取父类的属性值的,必须根据反射获得的子类Class对象调用 getSuperclass()方法获取父类对象,然后再通过父类对象去获取父类的属性值
7.序列化和反序列化
1.序列化
1.序列化的定义
1.序列化
:指把内存中的Java对象
通过某种方式存储到磁盘文件
或传递给其他网络节点
(网络上传输) 2.即将数据结构或对象
转换成二进制
的过程 3.如果想一次序列化多个对象可以将对象放到集合当中然后序列化集合,参与序列化的集合以及集合中的元素都需要实现java.io.Serializable
接口
2.序列化的意义
1.分布式系统
:一般需要将Java对象
通过网络在不同的节点间传输,此时需要将共享的对象数据
转换为二进制形式
,即序列化 2.服务器钝化
1.如果服务器发现某些对象很久不使用,那么服务器就会把这些内存中的对象以二进制
的形式持久化
在本地磁盘文件中(Java对象
转换为二进制文件
) 2.如果服务器发现某些对象需要使用时,先去内存中寻找,找不到再去磁盘文件中反序列化对象数据
,将二进制数据
恢复成 Java 对象
,这样能节省服务器内存
3.序列化的步驟
1.需要序列化的类必须实现序列化接口Java.lang.Serializable
,这是一个标志接口
,没有任何抽象方法 2.JVM
底层会判断,如果当前对象是Serializable
接口的实例才允许序列化,通过Java对象 instanceof Serializable
判断 3.Java
中使用对象流
来完成序列化
和反序列化
,具体IO流
操作可参考IO/NIO
文章中的对象流实现序列化和反序列化
2.反序列化
1.反序列化的定义
1.反序列化
:指把磁盘文件
中或网络节点
上的对象数据,恢复成Java对象
模型的过程 2.即将在序列化过程中所生成的二进制
转换成数据结构或对象
的过程
3.序列化和反序列化的问题
1.transient问题
2.序列化版本问题
1.完成序列化操作
后由于项目的升级或修改,可能会对序列化对象进行修改
,那么进行反序列化可能会报错 2.只需要在类上增加一个 serialVersionUID
字段,用来固定版本
,无论怎么修改,只要版本都是一致的,就能进行反序列化 3.注意
8.Java各版本特性
1.Java8
1.Lambda
表达式 2.函数式接口 3.方法引用 4.默认方法 5.Stream API
6.Optional
类 7.Date Time API
8.Nashorn、JavaScript
引擎
1.Lambda表达式
1.定义
:Lambda
表达式是对匿名函数
的简写形式
,Lambda
允许把函数作为一个方法的参数,可以写出更简洁的代码 2.实质
:语法糖
,实质底层还是一个方法
package domains ;
import lombok. AllArgsConstructor ;
import lombok. Data ;
import lombok. Getter ;
import lombok. NoArgsConstructor ;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class Employee {
private String name;
private int age;
private double salary;
}
package dao ;
public interface TestPredicate < T > {
boolean test ( T e) ;
}
package dao. daoImpl ;
import dao. TestPredicate ;
import domains. Employee ;
public class FilterEmployeeByAge implements TestPredicate < Employee > {
@Override
public boolean test ( Employee employee) {
return employee. getAge ( ) > 24 ;
}
}
package dao. daoImpl ;
import dao. TestPredicate ;
import domains. Employee ;
public class FilterEmployeeBySalary implements TestPredicate < Employee > {
@Override
public boolean test ( Employee employee) {
return employee. getSalary ( ) > 8000 ;
}
}
import dao. TestPredicate ;
import dao. daoImpl. FilterEmployeeByAge ;
import dao. daoImpl. FilterEmployeeBySalary ;
import domains. Employee ;
import org. junit. Test ;
import java. util. * ;
public class TestLambda {
List < Employee > employees = Arrays . asList (
new Employee ( "张三" , 8 , 100 ) ,
new Employee ( "李四" , 12 , 500 ) ,
new Employee ( "王五" , 18 , 3000 ) ,
new Employee ( "赵六" , 24 , 8000 ) ,
new Employee ( "田七" , 35 , 15000 ) ,
new Employee ( "吴八" , 40 , 25000 )
) ;
@Test
public void test1 ( ) {
Comparator < Integer > comparator = new Comparator < Integer > ( ) {
@Override
public int compare ( Integer o1, Integer o2) {
return Integer . compare ( o1, o2) ;
}
} ;
TreeSet < Integer > treeSet = new TreeSet < > ( comparator) ;
}
@Test
public void test2 ( ) {
Comparator < Integer > comparator = ( x, y) -> Integer . compare ( x, y) ;
TreeSet < Integer > treeSet = new TreeSet < > ( comparator) ;
TreeSet < Integer > integerTreeSet = new TreeSet < > ( ( x, y) -> Integer . compare ( x, y) ) ;
}
@Test
public void test3 ( ) {
List < Employee > filterEmployees = filterAgeEmployees ( employees) ;
for ( Employee filterEmployee : filterEmployees) {
System . out. println ( filterEmployee) ;
}
}
@Test
public void test4 ( ) {
List < Employee > filterEmployees = filterEmployee ( employees, new FilterEmployeeByAge ( ) ) ;
for ( Employee filterEmployee : filterEmployees) {
System . out. println ( filterEmployee) ;
}
}
@Test
public void test5 ( ) {
List < Employee > filterEmployees = filterSalaryEmployees ( employees) ;
for ( Employee filterEmployee : filterEmployees) {
System . out. println ( filterEmployee) ;
}
}
@Test
public void test6 ( ) {
List < Employee > filterEmployees = filterEmployee ( employees, new FilterEmployeeBySalary ( ) ) ;
for ( Employee filterEmployee : filterEmployees) {
System . out. println ( filterEmployee) ;
}
}
public List < Employee > filterAgeEmployees ( List < Employee > employees) {
ArrayList < Employee > employeeArrayList = new ArrayList < > ( ) ;
for ( Employee employee : employees) {
if ( employee. getAge ( ) > 24 ) {
employeeArrayList. add ( employee) ;
}
}
return employeeArrayList;
}
public List < Employee > filterSalaryEmployees ( List < Employee > employees) {
ArrayList < Employee > employeeArrayList = new ArrayList < > ( ) ;
for ( Employee employee : employees) {
if ( employee. getSalary ( ) > 8000 ) {
employeeArrayList. add ( employee) ;
}
}
return employeeArrayList;
}
public List < Employee > filterEmployee ( List < Employee > list, TestPredicate < Employee > predicate) {
ArrayList < Employee > employeeArrayList = new ArrayList < > ( ) ;
for ( Employee employee : employees) {
if ( predicate. test ( employee) ) {
employeeArrayList. add ( employee) ;
}
}
return employeeArrayList;
}
@Test
public void test7 ( ) {
List < Employee > filterEmployees = filterEmployee ( employees, new TestPredicate < Employee > ( ) {
@Override
public boolean test ( Employee employee) {
return employee. getSalary ( ) < 5000 ;
}
} ) ;
for ( Employee filterEmployee : filterEmployees) {
System . out. println ( filterEmployee) ;
}
}
@Test
public void test8 ( ) {
List < Employee > filterEmployees = filterEmployee ( employees, ( e) -> e. getSalary ( ) < 5000 ) ;
filterEmployees. forEach ( System . out:: println ) ;
}
@Test
public void test9 ( ) {
employees. stream ( ) . filter ( ( e) -> e. getSalary ( ) < 5000 ) . forEach ( System . out:: println ) ;
}
}
1.Lambda基础语法
import org. junit. Test ;
import java. util. Comparator ;
import java. util. function. Consumer ;
public class TestLambda2 {
@Test
public void test1 ( ) {
Runnable runnable = new Runnable ( ) {
@Override
public void run ( ) {
System . out. println ( "Hello World" ) ;
}
} ;
runnable. run ( ) ;
Runnable lambda = ( ) -> System . out. println ( "Hello Lambda" ) ;
lambda. run ( ) ;
}
@Test
public void test2 ( ) {
Consumer < String > consumer = x -> System . out. println ( x) ;
consumer. accept ( "测试成功!" ) ;
}
@Test
public void test3 ( ) {
Comparator < Integer > comparator = ( x, y) -> {
System . out. println ( "测试成功!" ) ;
return Integer . compare ( x, y) ;
} ;
}
@Test
public void test4 ( ) {
Comparator < Integer > comparator = ( x, y) -> Integer . compare ( x, y) ;
}
}
2.Lambda上下文推断
String [ ] strs = { "aaa" , "bbb" , "ccc" }
List < String > list = new ArrayList < > ( ) ;
show ( new HashMap < > ( ) ) ;
public void show ( Map < String , Integer > map) {
. . .
}
2.函数式接口
1.定义: 只包含一个抽象方法
的接口 2.使用@FunctionalInterface
注解,可以检查是否为函数式接口
1.四大核心函数式接口
函数式接口 参数类型 返回类型 用途 Consumer<T> 消费型接口 T void 对类型为T的对象应用操作。包含方法:void accept(T t) Supplier<T> 供给型接口 无 T 返回类型为T的对象。包含方法:T get() Function<T, R> 函数型接口 T R 对类型为T的对象应用操作,并返回R类型对象的结果。包含方法:R apply(T t) Predicate<T> 断言型接口 T boolean 确定类型为T的对象是否满足某约束。包含方法:boolean test(T t)
import org. junit. Test ;
import java. util. ArrayList ;
import java. util. Arrays ;
import java. util. List ;
import java. util. function. Consumer ;
import java. util. function. Function ;
import java. util. function. Predicate ;
import java. util. function. Supplier ;
public class TestLambda4 {
@Test
public void test1 ( ) {
shopping ( 1000 , ( m) -> System . out. println ( "书包,消费" + m/ 4 ) ) ;
}
public void shopping ( double money, Consumer < Double > consumer) {
consumer. accept ( money) ;
}
@Test
public void test2 ( ) {
List < Integer > integers = getIntegers ( 10 , ( ) -> ( int ) ( Math . random ( ) * 100 ) ) ;
for ( Integer integer : integers) {
System . out. println ( integer) ;
}
}
public List < Integer > getIntegers ( int num, Supplier < Integer > supplier) {
List < Integer > list = new ArrayList < > ( ) ;
for ( int i = 0 ; i < num; i++ ) {
Integer n = supplier. get ( ) ;
list. add ( n) ;
}
return list;
}
@Test
public void test3 ( ) {
String s = strHandler ( "三生三世:十里桃花" , ( str) -> str. replace ( ":" , "+" ) ) ;
System . out. println ( s) ;
}
public String strHandler ( String str, Function < String , String > function) {
return function. apply ( str) ;
}
@Test
public void test4 ( ) {
List < String > statement = Arrays . asList ( "三生三世,十里桃花" , "人生若只如初见" , "三生三世,情非得已" ) ;
List < String > stringList = filterStr ( statement, ( str) -> str. contains ( "三生三世" ) ) ;
for ( String s : stringList) {
System . out. println ( s) ;
}
}
public List < String > filterStr ( List < String > list, Predicate < String > predicate) {
List < String > stringList = new ArrayList < > ( ) ;
for ( String s : list) {
if ( predicate. test ( s) ) {
stringList. add ( s) ;
}
}
return stringList;
}
}
2.其他函数式接口
函数式接口 参数类型 返回类型 用途 BiConsumer<T,U> T,U void 对类型为T,U的对象应用操作。包含方法:void accept(T t,U u) BiFunction<T,U,R> T,U R 对类型为T,U参数的对象应用操作,返回R类型的结果。包含方法:R apply(T t,U u) BiPredicate<T,U> T,U boolean 确定类型为T,U的对象是否满足某约束。包含方法:boolean test(T t, U u)
3.引用
1.Lambda
表达式的另一种写法 2.当要传递给Lambda
体的操作已经有实现则可以使用方法引用
1.方法引用
import domains. Employee ;
import org. junit. Test ;
import java. util. Comparator ;
import java. util. function. BiPredicate ;
import java. util. function. Consumer ;
import java. util. function. Function ;
import java. util. function. Supplier ;
public class TestLambda5 {
@Test
public void test1 ( ) {
Consumer < String > consumer = ( x) -> System . out. println ( x) ;
Consumer < String > consumer1 = System . out:: println ;
consumer1. accept ( "200" ) ;
shopping ( 1000 , System . out:: println ) ;
}
public void shopping ( double money, Consumer < Double > consumer) {
consumer. accept ( money) ;
}
@Test
public void test2 ( ) {
Comparator < Integer > comparator = ( x, y) -> Integer . compare ( x, y) ;
Comparator < Integer > comparator1 = Integer :: compareTo ;
}
@Test
public void test3 ( ) {
BiPredicate < String , String > biPredicate = ( x, y) -> x. equals ( x) ;
BiPredicate < String , String > biPredicate1 = String :: equals ;
}
}
2.构造器引用
@Test
public void test4 ( ) {
Supplier < Employee > supplier = ( ) -> new Employee ( ) ;
supplier. get ( ) ;
Supplier < Employee > supplier1 = Employee :: new ;
Employee employee = supplier1. get ( ) ;
System . out. println ( employee) ;
Function < Integer , Employee > function = Employee :: new ;
Employee apply = function. apply ( 1 ) ;
System . out. println ( apply) ;
}
3.数组引用
@Test
public void test5 ( ) {
Function < Integer , String [ ] > function = ( x) -> new String [ x] ;
String [ ] apply = function. apply ( 10 ) ;
System . out. println ( apply. length) ;
Function < Integer , String [ ] > function1 = String [ ] :: new ;
String [ ] apply1 = function1. apply ( 20 ) ;
System . out. println ( apply1. length) ;
}
5.Stream
1.Stream的概念
Stream是Java8处理集合的关键抽象概念,它可以指定对集合进行的操作,可以执行非常复杂的查找,过滤,映射数据等操作 流(Stream)是数据通道,用于操作数据源(集合,数组等)所生成的元素序列 完整流包含以下结构:
1.数据源(集合,数组等) 2.通道(做一系列流水线式的中间操作) 3.新流(终止操作产生) 注意:
1.Stream不会存储元素 2.Stream不会改变数据源,而是返回一个持有结果的新Stream 3.Stream操作是延迟执行的,即等到需要结果(终止操作)的时候才会被执行
2.Stream的操作三个步骤
1.创建Stream
2.中间操作
Stream的一个中间操作链,对数据源的数据进行处理 3.终止操作
Stream的一个终止操作,执行中间操作链,并产生结果
3.创建Stream的四种方式
import domains. Employee ;
import org. junit. Test ;
import java. util. ArrayList ;
import java. util. Arrays ;
import java. util. List ;
import java. util. Random ;
import java. util. stream. Stream ;
public class TestLambda6 {
@Test
public void test1 ( ) {
List < String > list = new ArrayList < > ( ) ;
Stream < String > stream = list. stream ( ) ;
Employee [ ] employees = new Employee [ 10 ] ;
Stream < Employee > stream1 = Arrays . stream ( employees) ;
Stream < String > stream3 = Stream . of ( "aa" , "bb" , "cc" ) ;
Stream < Integer > stream4 = Stream . iterate ( 0 , ( x) -> x + 2 ) ;
Stream < Integer > limit = stream4. limit ( 10 ) ;
limit. forEach ( System . out:: println ) ;
Stream . generate ( ( ) -> new Random ( ) . nextInt ( 100 ) ) . limit ( 10 ) . forEach ( System . out:: println ) ;
}
}
4.中间操作
延迟加载:中间操作默认不会执行任何操作,只有执行了终止操作,所有的中间操作才会执行,这个过程叫做惰性求值或者延迟加载(多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理,而在终止操作时一次性全部处理) 内部迭代:迭代操作由Stream API完成 外部迭代:手动完成
1.筛选与切片
List < Employee > employees = Arrays . asList (
new Employee ( "张三" , 8 , 100 ) ,
new Employee ( "李四" , 12 , 500 ) ,
new Employee ( "王五" , 18 , 3000 ) ,
new Employee ( "赵六" , 24 , 8000 ) ,
new Employee ( "赵九" , 24 , 6000 ) ,
new Employee ( "田七" , 35 , 15000 ) ,
new Employee ( "吴八" , 40 , 25000 ) ,
new Employee ( "吴八" , 40 , 25000 )
) ;
@Test
public void test2 ( ) {
employees. stream ( ) . filter ( ( e) -> e. getAge ( ) > 24 ) . forEach ( System . out:: println ) ;
employees. stream ( ) . filter ( ( e) -> e. getAge ( ) > 24 ) . limit ( 1 ) . forEach ( System . out:: println ) ;
employees. stream ( ) . filter ( ( e) -> e. getAge ( ) > 24 ) . skip ( 1 ) . forEach ( System . out:: println ) ;
employees. stream ( ) . filter ( ( e) -> e. getAge ( ) > 24 ) . distinct ( ) . forEach ( System . out:: println ) ;
}
2.映射
@Test
public void test3 ( ) {
List < String > list = Arrays . asList ( "aaa" , "bbb" , "ccc" ) ;
list. stream ( )
. map ( ( str) -> str. toUpperCase ( ) )
. forEach ( System . out:: println ) ;
employees. stream ( ) . map ( Employee :: getName ) . forEach ( System . out:: println ) ;
Stream < Stream < Character > > streamStream = list. stream ( ) . map ( TestLambda6 :: filterCharacter ) ;
streamStream. forEach ( ( stream) -> {
stream. forEach ( System . out:: println ) ;
} ) ;
Stream < Character > characterStream = list. stream ( ) . flatMap ( TestLambda6 :: filterCharacter ) ;
characterStream. forEach ( System . out:: println ) ;
List < String > list1 = Arrays . asList ( "aaa" , "bbb" , "ccc" ) ;
List list2 = new ArrayList < > ( ) ;
list2. add ( "111" ) ;
list2. add ( "222" ) ;
list2. add ( "333" ) ;
list2. add ( list1) ;
list2. addAll ( list1) ;
System . out. println ( list2) ;
}
public static Stream < Character > filterCharacter ( String str) {
List < Character > list = new ArrayList < > ( ) ;
for ( Character c : str. toCharArray ( ) ) {
list. add ( c) ;
}
return list. stream ( ) ;
}
3.排序
@Test
public void test4 ( ) {
List < String > list = Arrays . asList ( "ccc" , "aaa" , "bbb" ) ;
list. stream ( )
. sorted ( )
. forEach ( System . out:: println ) ;
employees. stream ( ) . sorted ( ( e1 , e2) -> {
if ( e1. getAge ( ) == e2. getAge ( ) ) {
return e1. getName ( ) . compareTo ( e2. getName ( ) ) ;
} else {
return - e1. getAge ( ) - e2. getAge ( ) ;
}
} ) . forEach ( System . out:: println ) ;
}
5.终止操作
1.查找与匹配
@Test
public void test5 ( ) {
boolean b = employees. stream ( ) . allMatch ( ( e) -> e. getStatus ( ) . equals ( Employee. Status . BUSY ) ) ;
System . out. println ( b) ;
boolean b1 = employees. stream ( ) . anyMatch ( ( e) -> e. getStatus ( ) . equals ( Employee. Status . BUSY ) ) ;
System . out. println ( b1) ;
boolean b2 = employees. stream ( ) . noneMatch ( ( e) -> e. getStatus ( ) . equals ( Employee. Status . BUSY ) ) ;
System . out. println ( b2) ;
Optional < Employee > first = employees. stream ( ) . findFirst ( ) ;
System . out. println ( first. get ( ) ) ;
Optional < Employee > any = employees. parallelStream ( ) . findAny ( ) ;
System . out. println ( any. get ( ) ) ;
long count = employees. stream ( ) . count ( ) ;
System . out. println ( count) ;
Optional < Employee > max = employees. stream ( ) . max ( Comparator . comparingDouble ( Employee :: getSalary ) ) ;
System . out. println ( max. get ( ) ) ;
Optional < Double > min = employees. stream ( ) . map ( Employee :: getSalary ) . min ( Double :: compareTo ) ;
System . out. println ( min. get ( ) ) ;
}
2.归约
@Test
public void test6 ( ) {
List < Integer > list = Arrays . asList ( 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ) ;
Integer sum = list. stream ( ) . reduce ( 0 , ( x, y) -> x + y) ;
System . out. println ( sum) ;
}
3.收集
@Test
public void test7 ( ) {
List < Person > list = new ArrayList < > ( ) ;
list. add ( new Person ( "Ram" , 30 ) ) ;
list. add ( new Person ( "Shyam" , 20 ) ) ;
list. add ( new Person ( "Shiv" , 20 ) ) ;
list. add ( new Person ( "Mahesh" , 30 ) ) ;
String nameByAge = list. stream ( ) . collect ( Collectors . mapping ( Person :: getName , Collectors . joining ( "," , "[" , "]" ) ) ) ;
System . out. println ( nameByAge) ;
nameByAge = list. stream ( ) . map ( person -> person. getName ( ) ) . collect ( Collectors . joining ( "," , "[" , "]" ) ) ;
System . out. println ( nameByAge) ;
Map < Integer , String > nameByAgeMap = list. stream ( ) . collect (
Collectors . groupingBy ( Person :: getAge , Collectors . mapping ( Person :: getName , Collectors . joining ( "," , "[" , "]" ) ) ) ) ;
nameByAgeMap. forEach ( ( k, v) -> System . out. println ( "Age:" + k + " Persons: " + v) ) ;
List < String > list1 = Arrays . asList ( "A" , "B" , "C" , "D" ) ;
String result= list1. stream ( ) . collect ( Collectors . joining ( "-" , "[" , "]" ) ) ;
System . out. println ( result) ;
List < String > collect = employees. stream ( )
. map ( Employee :: getName )
. collect ( Collectors . toList ( ) ) ;
collect. forEach ( System . out:: println ) ;
Set < String > collect1 = employees. stream ( )
. map ( Employee :: getName )
. collect ( Collectors . toSet ( ) ) ;
collect1. forEach ( System . out:: println ) ;
Map < String , Double > collect8 = employees. stream ( ) . collect ( Collectors . toMap ( Employee :: getName , Employee :: getSalary , ( e1, e2) -> e1) ) ;
for ( Map. Entry < String , Double > stringDoubleEntry : collect8. entrySet ( ) ) {
System . out. println ( stringDoubleEntry. getKey ( ) + ":" + stringDoubleEntry. getValue ( ) ) ;
}
ConcurrentMap < String , Double > collect9 = employees. stream ( ) . collect ( Collectors . toConcurrentMap ( Employee :: getName , Employee :: getSalary , ( e1, e2) -> e1) ) ;
for ( Map. Entry < String , Double > stringDoubleEntry : collect9. entrySet ( ) ) {
System . out. println ( stringDoubleEntry. getKey ( ) + ":" + stringDoubleEntry. getValue ( ) ) ;
}
HashSet < String > collect2 = employees. stream ( )
. map ( Employee :: getName )
. collect ( Collectors . toCollection ( HashSet :: new ) ) ;
collect2. forEach ( System . out:: println ) ;
Long collect3 = employees. stream ( )
. collect ( Collectors . counting ( ) ) ;
System . out. println ( collect3) ;
Double collect4 = employees. stream ( )
. collect ( Collectors . averagingDouble ( Employee :: getSalary ) ) ;
System . out. println ( collect4) ;
DoubleSummaryStatistics collect5 = employees. stream ( )
. collect ( Collectors . summarizingDouble ( Employee :: getSalary ) ) ;
System . out. println ( collect5) ;
Optional < Employee > collect6 = employees. stream ( )
. collect ( Collectors . maxBy ( ( e1, e2) -> Double . compare ( e1. getSalary ( ) , e2. getSalary ( ) ) ) ) ;
System . out. println ( collect6. get ( ) ) ;
Optional < Employee > collect7 = employees. stream ( )
. collect ( Collectors . minBy ( ( e1, e2) -> Double . compare ( e1. getSalary ( ) , e2. getSalary ( ) ) ) ) ;
System . out. println ( collect7. get ( ) ) ;
}
4.分组
@Test
public void test8 ( ) {
Map < Employee. Status , List < Employee > > collect = employees. stream ( ) . collect ( Collectors . groupingBy ( Employee :: getStatus ) ) ;
for ( Map. Entry < Employee. Status , List < Employee > > statusListEntry : collect. entrySet ( ) ) {
System . out. println ( statusListEntry. getKey ( ) + ":" + statusListEntry. getValue ( ) ) ;
}
Map < Employee. Status , Map < String , List < Employee > > > collect1 = employees. stream ( ) . collect ( Collectors . groupingBy ( Employee :: getStatus , Collectors . groupingBy ( ( e) -> {
if ( e. getAge ( ) <= 35 ) {
return "青年" ;
} else if ( e. getAge ( ) <= 50 ) {
return "中年" ;
} else {
return "老年" ;
}
} ) ) ) ;
for ( Map. Entry < Employee. Status , Map < String , List < Employee > > > statusMapEntry : collect1. entrySet ( ) ) {
System . out. println ( statusMapEntry. getKey ( ) + ":" + statusMapEntry. getValue ( ) . entrySet ( ) ) ;
}
}
5.分区
@Test
public void test9 ( ) {
Map < Boolean , List < Employee > > collect = employees. stream ( ) . collect ( Collectors . partitioningBy ( ( e) -> e. getSalary ( ) > 8000 ) ) ;
for ( Map. Entry < Boolean , List < Employee > > booleanListEntry : collect. entrySet ( ) ) {
System . out. println ( booleanListEntry. getKey ( ) + ":" + booleanListEntry. getValue ( ) ) ;
}
}
4.并行流与顺序流
并行流是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流 Java8中将并行流进行了优化,可以更方便的对数据进行并行操作,Stream API可以声明性地通过parallel()与sequential()在并行流与顺序流之间进行切换
Fork/Join框架
注意:需要继承RecursiveTask 概念:在必要的情况下将一个大任务,进行拆分(fork)成若干个小任务,拆到不可再拆时,再将一个个的小任务运算的结果进行join汇总 Fork/Join框架与传统线程池的区别
1.Fork/Join框架采用工作窃取模式(work-stealing) 即:当执行新的任务时它可以将其拆分,分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中 2.相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上,在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态;而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行,那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行,这种方式减少了线程的等待时间,提高了性能 package domains ;
import java. util. concurrent. RecursiveTask ;
public class ForkJoinCalculate extends RecursiveTask < Long > {
private static final long serialVersionUID = - 8301484471666236858L ;
private long start;
private long end;
private static final long THRESHOLD = 10000 ;
public ForkJoinCalculate ( long start, long end) {
this . start = start;
this . end = end;
}
@Override
protected Long compute ( ) {
long length = end - start;
if ( length <= THRESHOLD ) {
long sum = 0 ;
for ( long i = start; i <= end; i++ ) {
sum += i;
}
return sum;
} else {
long middle = ( start + end) / 2 ;
ForkJoinCalculate left = new ForkJoinCalculate ( start, middle) ;
left. fork ( ) ;
ForkJoinCalculate right = new ForkJoinCalculate ( middle+ 1 , end) ;
right. fork ( ) ;
return left. join ( ) + right. join ( ) ;
}
}
}
import domains. ForkJoinCalculate ;
import org. junit. Test ;
import java. time. Duration ;
import java. time. Instant ;
import java. util. concurrent. ForkJoinPool ;
import java. util. concurrent. ForkJoinTask ;
import java. util. stream. LongStream ;
public class TestForkJoin {
@Test
public void test1 ( ) {
Instant start = Instant . now ( ) ;
ForkJoinPool pool = new ForkJoinPool ( ) ;
ForkJoinTask < Long > task = new ForkJoinCalculate ( 0 , 1000000000L ) ;
Long sum = pool. invoke ( task) ;
System . out. println ( "累加和为:" + sum) ;
Instant end = Instant . now ( ) ;
System . out. println ( "消耗时间为:" + Duration . between ( start, end) . toMillis ( ) ) ;
}
@Test
public void test2 ( ) {
Instant start = Instant . now ( ) ;
long sum = 0L ;
for ( int i = 0 ; i < 1000000000L ; i++ ) {
sum += i;
}
System . out. println ( "累加和为:" + sum) ;
Instant end = Instant . now ( ) ;
System . out. println ( "消耗时间为:" + Duration . between ( start, end) . toMillis ( ) ) ;
}
@Test
public void test3 ( ) {
Instant start = Instant . now ( ) ;
long sum = LongStream . rangeClosed ( 0 , 1000000000L )
. parallel ( )
. reduce ( 0 , Long :: sum ) ;
System . out. println ( "累加和为:" + sum) ;
Instant end = Instant . now ( ) ;
System . out. println ( "消耗时间为:" + Duration . between ( start, end) . toMillis ( ) ) ;
}
}
5.Optional类
java.util.Optional类是一个容器类,代表一个值存在或不存在 原来用null表示一个值不存在,现在Optional可以更好的表达这个概念,并且可以避免空指针异常package domains ;
import lombok. AllArgsConstructor ;
import lombok. Data ;
import lombok. NoArgsConstructor ;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Godness {
private String name;
}
package domains ;
import lombok. AllArgsConstructor ;
import lombok. Data ;
import lombok. NoArgsConstructor ;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Man {
private Godness godness;
}
package domains ;
import lombok. AllArgsConstructor ;
import lombok. Data ;
import lombok. NoArgsConstructor ;
import java. util. Optional ;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class NewMan {
private Optional < Godness > godness = Optional . empty ( ) ;
}
import domains. Employee ;
import domains. Godness ;
import domains. Man ;
import domains. NewMan ;
import org. junit. Test ;
import java. util. Arrays ;
import java. util. List ;
import java. util. Optional ;
public class TestOptional {
@Test
public void test1 ( ) {
Optional < Employee > optional = Optional . of ( new Employee ( ) ) ;
Employee employee = optional. get ( ) ;
System . out. println ( employee) ;
Optional < Object > o = Optional . ofNullable ( null ) ;
if ( o. isPresent ( ) ) {
System . out. println ( o. get ( ) ) ;
}
Optional < Employee > objectOptional = Optional . ofNullable ( null ) ;
System . out. println ( objectOptional. orElse ( new Employee ( 25 ) ) ) ;
System . out. println ( objectOptional. orElseGet ( ( ) -> new Employee ( "李四" , 3 , 35.5 , Employee. Status . BUSY ) ) ) ;
Optional < Employee > employee1 = Optional . ofNullable ( new Employee ( "李四" , 3 , 35.5 , Employee. Status . BUSY ) ) ;
Optional < String > optional1 = employee1. map ( Employee :: getName ) ;
System . out. println ( optional1. get ( ) ) ;
Optional < String > s = employee1. flatMap ( ( e) -> Optional . of ( e. getName ( ) ) ) ;
System . out. println ( s. get ( ) ) ;
List < Employee > employees = Arrays . asList (
new Employee ( "张三" , 8 , 100 , Employee. Status . BUSY ) ,
new Employee ( "李四" , 12 , 500 , Employee. Status . BUSY ) ,
new Employee ( "王五" , 18 , 3000 , Employee. Status . FREE ) ,
new Employee ( "赵六" , 24 , 8000 , Employee. Status . FREE ) ,
new Employee ( "赵九" , 24 , 6000 , Employee. Status . FREE ) ,
new Employee ( "田七" , 35 , 15000 , Employee. Status . VOCATION ) ,
new Employee ( "吴八" , 40 , 25000 , Employee. Status . FREE )
) ;
Optional < Employee > optional2 = Optional . ofNullable ( employees. get ( 5 ) ) . filter ( e -> e. getAge ( ) > 24 ) ;
System . out. println ( optional2. get ( ) ) ;
}
@Test
public void test2 ( ) {
Man man = new Man ( ) ;
String godnessName = getGodnessName2 ( Optional . ofNullable ( null ) ) ;
System . out. println ( godnessName) ;
}
public String getGodnessName ( Man man) {
if ( man != null ) {
Godness godness = man. getGodness ( ) ;
if ( godness != null ) {
return godness. getName ( ) ;
}
}
return "测试" ;
}
public String getGodnessName2 ( Optional < NewMan > man) {
return man. orElse ( new NewMan ( ) )
. getGodness ( )
. orElse ( new Godness ( "测试" ) )
. getName ( ) ;
}
}
6.接口中的默认方法与静态方法
Java8中允许接口中包含具有具体实现的方法,该方法称为默认方法,默认方法使用default关键字修饰
接口默认方法的类优先原则
若一个接口定义了一个默认方法,而另外一个父类或接口又定义了一个同名的方法时
1.选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略 2.接口冲突。如果一个父接口提供一个默认方法,而另一个父接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么实现类必须选择一个接口或者覆盖该方法来解决冲突 package dao ;
public interface MyFun1 {
default String getName ( ) {
return "myFun1" ;
}
static void show ( ) {
System . out. println ( "接口中的静态方法" ) ;
}
}
package dao ;
public interface MyFun2 {
default String getName ( ) {
return "myFun2" ;
}
}
package domains ;
public class MyClassFun {
public String getName ( ) {
return "MyClassFun" ;
}
}
package domains ;
import dao. MyFun1 ;
import dao. MyFun2 ;
public class SubClass implements MyFun1 , > MyFun2 {
@Override
public String getName ( ) {
return MyFun1 . super . getName ( ) ;
}
}
import domains. SubClass ;
import org. junit. Test ;
public class TestDefault {
@Test
public void test1 ( ) {
SubClass subClass = new SubClass ( ) ;
String name = subClass. getName ( ) ;
System . out. println ( name) ;
MyFun1 . show ( )
}
}
7.传统时间格式化的线程安全问题
java.util.Date() JDK1.0版本 java.util.Calender() JDK1.1版本 java.util.TimeZone() java.text.SimpleDateFormat 声明不规范,不安全
8.新时间与日期API
java.time包下 java.time. java.time.format java.time.temporal java.time.zone 不可变,会产生新的实例 线程安全
9.重复注解与类型注解
常见面试题
1.JDK和JRE的区别
1.JDK(Java SE开发工具集) :包括JRE
和命令行开发工具(编译,运行,调试Java
程序所需要的基本工具) 2.JRE(运行环境) :提供Java
虚拟机和运行Java
应用程序所必需的类库 3.JRE与JDK的区别
1.如果只需要在某种操作系统下运行Java
应用程序,则安装支持该操作系统的JRE
即可 2.如果不仅要运行Java
应用程序,还要开发Java
应用程序,就需要安装支持该操作系统的JDK
2.a+=b 和 a=a+b 的区别
1.使用+=,-=,*=,/=,%=
运算符进行赋值时,强制类型转换会自动完成,程序不需要做任何显式地声明 2.当a
和b
结果类型不一致时,a+=b
会自动完成类型强制转换,而a=a+b
会报错 3.当a
和b
结果类型一致时,两者没区别
3.==和equals的区别
1.==
1.基本数据类型的变量存放在虚拟机栈的局部变量表中的是值,==
在比较基本数据类型时,比较的是变量值是否相同 2.引用数据类型的变量存放在虚拟机栈的局部变量表中的是对象引用/堆空间的内存地址,比较的是堆地址是否相同 2.equals
1.equals
用来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object
类的,所以适用于所有对象,如果没有对该方法进行覆盖的话,调用的仍然是Object
类中的方法,而Object
中的equals
方法返回的是==
的判断 2.Java
中所有的类都是继承与Object
基类,Object
类中定义了一个equals
方法,这个方法的初始行为是比较对象的内存地址
,但在一些类库中已经重写了这个方法,所以不再是比较类在堆中的地址,(一般都是用来比较对象的成员变量值是否相同String
,Integer
,Date
等类)
4.equals和hashcode关系
1.equals
方法和hashCode
方法是Object
类中的两个基础方法,它们共同协作来判断两个对象是否相等 2.如果两个对象相等,即equals()
结果为true
,那么二者的hashCode()
也必须相同;但如果两个对象的hashCode
相同,则二者不一定相同 3.并不要求根据equals()
不相等的两个对象,调用二者各自的hashCode()
方法必须产生不同的结果,但是对于不同的对象产生不同的hashcode
,可能会提高hashtable
的性能 4.当对比两个对象是否相等时,可以先使用hashCode
进行比较,如果比较的结果是true
,可以使用equals
再次确认两个对象是否相等,如果比较的结果是true
,那么这两个对象就是相等的,否则其他情况就认为两个对象不相等;这样大大的提升了对象比较的效率,这也是为什么Java
设计使用hashCode
和 equals
协同的方式,来确认两个对象是否相等的原因 5.不能直接使用hashCode
就确定两个对象是否相等,因为不同对象的 hashCode
可能相同,但hashCode
不同的对象一定不相等,所以使用 hashCode
可以起到快速初次判断对象是否相等的作用
5.重写equals时为什么一定要重写hashCode
1.重写equals
方法
1.Object
类中的equals
方法用于检测一个对象是否等于另外一个对象 2.Object
类中,这个方法将判断两个对象是否具有相同的引用,如果两个对象具有相同的引用,则一定是相等的 3.通过上述源码和equals
的定义可以看出,大多数情况下equals
的判断是没有什么意义的 4.例:使用 Object
中的equals
比较两个自定义的对象是否相等完全没有意义(因为无论对象是否相等,结果都是 false
),因此通常要判断两个对象是否相等,一定要重写equals
方法,这就是要重写 equals
方法的原因 2.重写hashCode
方法
1.hashCode
是一个散列码,它是由对象推导出的一个整型值,并且这个值为任意整数,包括正数或负数 2.散列码是没有规律的,如果 x
和 y
是两个不同的对象,x.hashCode()
与y.hashCode()
基本上不会相同;但如果 a
和 b
相等,则a.hashCode()
一定等于b.hashCode()
3.相同的值hashCode
一定相同 4.不同的值hashCode
也有可能相同 3.一起重写
1.Set
集合是用来保存不同对象的,如果Set 集合中存储的是只重写了 equals
方法的自定义对象时会出现异常 2.如上图所示即使两个对象是相等的,Set
集合却没有将二者进行去重与合并,这就是重写了equals
方法,但没有重写hashCode
方法的问题所在 4.问题原因
1.如果只重写了equals
方法,那么默认情况下,Set
进行去重操作时,会先判断两个对象的hashCode
是否相同,此时因为没有重写hashCode
方法,所以会直接执行Object
中的hashCode
方法,而 Object
中的hashCode
方法对比的是两个不同引用地址的对象,所以结果是false
,那么equals
方法就不会执行,直接返回的结果就是 false
:两个对象不是相等的,于是就在 Set
集合中插入了两个相同的对象。 2.但如果在重写equals
方法时,也重写了hashCode
方法,那么执行判断时会先执行重写的hashCode
方法,此时对比的是两个对象的所有属性的 hashCode
是否相同,于是调用hashCode
返回的结果就是true
,再去调用 equals
方法,发现两个对象确实是相等的,于是就返回true
,因此Set
集合不会存储两个一样的数据 5.具体细节可参考集合及其使用方式
文章中的HashSet
6.String的底层实现
1.String
类的一个最大特性是不可修改性,其不可修改的原因是在String
内部定义了一个常量char数组
,因此每次对字符串的操作实际上都会另外分配一个新的常量数组空间 2.Java
打印String
对象的地址:当使用System.out.println()
方法打印String
类型对象时,会输出String
对象代表的字符串,并不会输出对象的地址 3.一般被打印对象的形式为:全限定类名+@+十六进制数组成
4.性能优化:对于直接相加字符串JVM
在编译期便确定其值,如I+love+java
的字符串相加,在编译期间便被优化成了Ilovejava
;对于间接相加(即包含字符串引用或方法调用) 效率要比直接相加低,因为在编译期不会对引用变量或方法进行优化 5.String str = new String("abc")
创建了多少个对象
1.从字节码看new
只调用了一次,也就是说只创建了一个对象 2.这段代码在运行期间
确实只创建了一个对象,即在堆
上创建了abc
对象 3.为什么一般说创建了2
个对象:该段代码执行过程
和类的加载过程
是有区别的,在类加载的过程中,确实在运行时常量池
中创建了一个abc
对象,而在代码执行过程
中确实只创建了一个String
对象,因此这个问题合理的解释是2个
7.String,StringBuilder,StringBuffer的区别
1.String
是常量字符数组,而StringBuilder
和StringBuffer
是变量字符数组 2.String
没有默认初始化容量,而StringBuilder
和StringBuffer
默认初始化容量为16
3.StringBuilder
线程不安全,StringBuffer
线程安全,因为StringBuffer
的大部分方法都使用synchronized
关键字加锁
8.final、finalize和finally的区别
1.final
1.final
是一个修饰符,其可以修饰类
,方法
和属性
2.final
修饰的类
,不能作为父类被子类继承,因此一个类不能既被abstract
声明,又被final
声明 3.final
修饰的变量
,称为常量,不能被修改,必须在声明时赋值 4.final
修饰的方法
,不能被重写 2.finally
1.finally
是异常处理时提供来执行最终结果的finally
代码块 2.不管有没有异常被抛出、捕获,finally
代码块都会被执行 3.finalize
1.finalize
是方法名,属于Object
类 2.finalize()
方法用于垃圾回收,一般情况下不需要实现 3.当对象被回收的时候需要释放一些资源;如socket
链接,在对象初始化时创建,整个生命周期内有效,那么需要实现finalize
方法关闭这个链接 4.但是当调用finalize
方法后,并不意味着GC
会立即回收该对象,所以有可能真正调用的时候,对象又不需要回收了,然后到了真正要回收的时候,因为之前调用过一次,这次又不会调用了,产生问题,所以不推荐使用finalize
方法
9.volatile
1.并发编程中通常会遇到以下三个问题
1.原子性问题
1.原子性
:指一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断
,要么就都不执行
2.可见性问题
1.可见性
:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
3.有序性问题
1.有序性
:指程序执行的顺序按照代码的先后顺序执行 2.指令重排序
:一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的 3.指令重排序
不会影响单个线程的执行,但是会影响到线程并发执行的正确性 2.要想并发程序正确地执行
,必须要保证原子性、可见性以及有序性;只要有一个没有被保证,就有可能会导致程序运行不正确 3.Java
解决以上三个问题
1.原子性
1.Java
内存模型只保证了基本读取和赋值
是原子性
操作,如果要实现更大范围操作的原子性,可以通过synchronized
和Lock
来实现; 2.因为synchronized
和Lock
能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性 2.可见性
1.Java
提供了volatile
关键字来保证可见性,当一个共享变量被volatile
修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值 2.而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性 3.通过synchronized
和Lock
也能够保证可见性,synchronized
和Lock
能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性 3.有序性
1.Java
内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性 2.Java里面,可以通过volatile
关键字来保证一定的有序性 3.也可以通过synchronized
和Lock
来保证有序性,因为synchronized
和Lock
保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性 4.volatile
关键字的两层语义
1.一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile
修饰之后,那么就具备了两层语义:
1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,新值对其他线程来说是立即可见的 2.禁止进行指令重排序,但是只能对当前修饰的操作保证,其他操作无法保证,所以volatile能在一定程度上保证有序性 5.volatile
关键字无法保证对变量的任何操作都是原子性
6.具体可参考:https://www.cnblogs.com/ustc-anmin/p/11434769.html
10.BigDecimal
11.new关键字的作用
1、为对象分配内存空间 2、引起对象构造方法的调用 3、为对象返回一个引用
值传递:在方法调用时,传递的是实际参数的值的副本。当参数变量被赋予新的值时,只会修改副本的值,不会影响原始值。Java 中的基本数据类型都采用值传递方式传递参数变量的值。
引用传递:在方法调用时,传递的是实际参数的引用(即内存地址)。当参数变量被赋予新的值时,会修改原始值的内容。Java 中的对象类型采用引用传递方式传递参数变量的值。
Java定时任务
1.通过循环和sleep实现
public class Task {
public static void main ( String [ ] args) {
final long timeInterval = 1000 ;
Runnable runnable = new Runnable ( ) {
@Override
public void run ( ) {
while ( true ) {
System . out. println ( "Hello !!" ) ;
try {
Thread . sleep ( timeInterval) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
}
} ;
Thread thread = new Thread ( runnable) ;
thread. start ( ) ;
}
}
1.该方式简单直接,但能够实现的功能有限,工作中一般不会使用
2.通过JDK自带Timer类实现
package com. wd. time ;
import java. util. Date ;
import java. util. TimerTask ;
public class DoTimerTask extends TimerTask {
private String taskName;
public DoTimerTask ( String taskName) {
this . taskName = taskName;
}
@Override
public void run ( ) {
System . out. println ( new Date ( ) + " : 任务「" + taskName + "」被执行。" ) ;
}
}
package com. wd. time ;
import java. util. Date ;
import java. util. Timer ;
public static void main ( String [ ] args) {
Timer timer = new Timer ( ) ;
System . out. println ( "开始时间" + new Date ( ) ) ;
timer. scheduleAtFixedRate ( new DoTimerTask ( "FixedRateDemo" ) , 5000L , 2000L ) ;
}
1.使用Timer
执行定时任务首先需要一个任务类去继承TimerTask
抽象类 2.schedule
与scheduleAtFixedRate
相同点:
1.任务执行未超时:下次执行时间 = 上次执行开始时间 + 间隔时间(period
) 2.任务执行超时:下次执行时间 = 上次执行结束时间 3.schedule
与scheduleAtFixedRate
不同点:
1.schedule
方法侧重保持间隔时间的稳定,即当有任务超时后续任务会按照新的任务时间+间隔继续执行 2.scheduleAtFixedRate
方法侧重保持执行频率的稳定,即当有任务超时后续任务会安装最初的任务时间+间隔继续执行 4.实际生产中一般不使用Timer
(缺陷)
1.Timer
对调度的支持是基于绝对时间的,而不是相对时间,所以它对系统时间的改变非常敏感 2.Timer
线程不会捕获异常,如果TimerTask
抛出的了未检查异常则会导致Timer
线程终止 3.此时Timer
也不会重新恢复线程的执行,它会错误的认为整个Timer
线程都会取消 4.同时已经被安排单尚未执行的TimerTask
也不会再执行,新的任务也不能被调度,故如果TimerTask
抛出未检查的异常,Timer
将会产生无法预料的行为
3.通过JDK自带ScheduledExecutorService实现
4.通过Quartz框架实现
5.通过Spring Task实现
6.通过XXL-Job实现