面向对象(下)
本章思维导图
包装类(Wrapper Class)
解决8种基本数据类型当成Object
类型变量使用
基本数据类型的包装类
-
byte
==Byte
-
short
==Short
-
int
==Integer
-
long
==Long
-
char
==Character
-
float
==Float
-
double
==Double
-
boolean
==Boolean
JKD1.5提供了自动装箱(Autoboxing)和自动拆箱(AutoUnboxing)
- 自动装箱是把一个基本数据类型变量直接赋给对应的包装类变量或者赋给
Object
变量 - 自动拆箱是直接把包装类对象直接赋给一个对应的基本类型变量
包装类可实现基本类型变量和字符串之间的转换
- 把字符串类型的值转换为基本类型的值
-
parseXxx(String s)
静态方法,除Character之外所有包装类都提供了该方法 -
valueOf(String s)
静态方法
-
包装类的引用类型变量可以直接和基本数据类型作比较
- 但是
Integer
包装类直接把-128~127之间的整数放入cache数组中缓存起来 - 包装类提供了一个静态
compare(xxx var1, xxx var2)
方法,来比较两个基本类型值的大小
包装类无符号运算的方法
Integer、Long
static String toUnsignedString(int/long i)
- 指定int或long型整数转换为无符号整数对应的字符串
static String toUsignedString(int\long i, int radix)
- 指定int或long型整数转换为指定进制的无符号整数对应的字符串
static xxx parseUnsignedXxx(String s)
- 指定字符串解析成无符号整数
static xxx parseUnsignedXxx(String s, int radix)
- 指定字符串按指定进制解析成无符号整数
static int compareUnsigned(xxx x, xxx y)
- 将x、y两个整数转换为无符号整数后比较大小
Byte、Short
toUnsignedInt(xxx x)
toUnsignedLong(xxx x)
- 无符号类就是把二进制代码的最高位的符号位当成数值类处理
处理对象
打印对象和toString方法
- 直接打印对象名是
类名+@+hashCode
-
toString()
方法是Object类的一个实例方法- 是一个特殊的方法,是一个“自我描述”的方法
- 重写类的
toString()
方法,可对类介绍 类名[field1=值1, field2=值2, ...]
==和equals方法
- 对于两个引用类型变量,只有都指向同一个对象时,
==
才会返回true
-
==
不可用于比较类型上没有父子关系的两个对象 -
"hello"
直接量和new String("hello")
的区别- 使用形如
"hello"
的字符串直接量,JVM将会使用常量池来管理这些字符串 - 使用
new String("hello")
时,JVM会先使用常量池来管理"hello"
直接量,在调用String类的构造器来创建一个新的String对象,新创建的String对象被保存在堆内存中 -
new String("hello")
一共产生了两个字符串对象 - 常量池(constant pool)专门用户管理在编译时被确定并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口中的常量,还包括字符串常量
- 常量池保住相同的字符串直接量只有一个
- 使用形如
-
-
equals()
方法-
equals()
方法是Object类提供的一个实例方法- 重写
equals()
方法来实现自定义的相等标准
- 重写
- String的
equals()
方法用来判断两个字符串是否相等 -
实例名.getCall()
和类名.class
- 正确重写
euqls()
方法应该满足的条件- 自反性:对任意x,x.equals(x)一定返回true
- 对称性:对任意x和y,如果y.equals(x)返回true,则x.equals(y)也返回true
- 传递性:对任意x、y、z,如果x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)一定返回true
- 对任何不是null的x,x.equals(null)一定返回false
-
类成员
static关键字修饰的成员就是类成员
static可以修饰成员变量、方法、初始化块、内部类(接口、枚举)
不允许类成员访问实例成员
单例(Singleton)类
- 一个类始终只能创建一个实例
- 把类构造器使用
private
修饰,提供一个public
方法作为该类的访问点,用于创建该类的对象,且该方法必须使用static
修饰 - 该类还必须缓存已经创建过的对象,用来判断该类是否曾经创建过对象
final修饰符
final
关键字可用于修饰类、变量、方法,用于表示不可改变
final成员变量
- 必须由程序员显式地指定初始值
- 类变量
- 必须在静态初始化块中指定初始值或声明该类变量时指定初始值
- 实例变量
- 必须在非静态初始化块、声明该实例变量或构造器中指定初始值
- 在显式初始化之前不能访问,但可以通过方法来访问(java的一个缺陷)
final局部变量
final
修饰的形参,不允许在方法内被赋值
可执行“宏替换”的final变量
- 只要满足3个条件,
final
变量就不再是一个变量,而是相当于一个直接量- 使用
final
修饰 - 在定义时制定了初始值
- 该初始值可以在编译时确定下来
- 使用
final方法
-
final
修饰的方法不可被重写 - 子类可重写
private final
修饰的方法- 因为
private
修饰的方法,子类无法访问,如果子类定义了相同方法名、相同形参列表、相同返回值类型的方法,只是重新定义了一个新方法
- 因为
final类
-
final
修饰的类不可以有子类
不可变(immutable)类
创建类的实例后,该实例的实例变量不可改变
- 规则
- 使用private和final修饰该类的成员变量
- 提供带参数的构造器,用于根据传入参数来初始化类里的成员变量
- 仅为类的成员变量提供getter方法,不提供setter方法
- 注意引用类型的成员变量
抽象类
- 有抽象方法的类只能定义成抽象类
- 抽象类里可以没有抽象方法
规则
- 抽象类必须使用
abstract
来修饰,抽象方法也必须使用abstract
来修饰,抽象方法不能有方法体 - 抽象类不能被实例化
- 抽象类可以包含成员变量、方法(普通方法、抽象方法)、构造器、初始化块、内部类(接口、枚举)
- 抽象类的构造器不能用于创建实例,主要是用于被其子类调用
- 含有抽象方法的类(直接定义了一个抽象方法、继承了一个抽象父类,但没有完全实现父类包含的抽象方法、实现了一个接口,但没有完全实现接口包含的抽象方法)只能被定义成抽象类
abstract修饰类时,表明这个类只能被继承
abstract修饰方法时,表明这个方法必须由子类提供实现(重写)
final和abstract永远不能同时使用
接口
更特殊的抽象类,接口(interface)
[修饰符] interface 接口名 extends 父接口1,... {
零个到多个常量定义...
零个到多个抽象方法定义...
零个到多个内部类、接口、枚举定义...
零个到多个私有方法、默认方法、类方法定义.
}
- 修饰符可以是
public
或省略 - 一个接口可以由多个直接父接口,但接口只能继承接口,不能继承类
接口里不能包含构造器和初始化块定义
接口里可以包含成员变量,方法,内部类,都只能是public
访问权限,如果省略也是public
- 成员变量只能是静态常量
- 方法只能是抽象实例方法、类方法、默认方法、私有方法
- 内部类包括内部接口、枚举
私有方法可以拥有方法体,可以使用static
修饰
静态常量,系统会自动增加static
和final
修饰符
如果定义的不是默认方法、类方法、私有方法,系统会自动为普通方法增加abstract
修饰符
- 普通方法不能有方法体
- 类方法、默认方法、私有方法都必须由方法体
- 默认方法需要用
default
修饰 - 私有方法用
private
修饰
接口的继承
- 接口支持多继承
- 子接口扩展某个父接口,会获得父接口里定义的所有抽象方法、常量
使用接口
- 接口不能用于创建实例,但可以用于声明引用类型变量,这个引用类型变量必须引用到其实现类的对象
- 接口的用途
- 定义变量,可用于强制类型转换
- 调用接口中定义的常量
- 被其他类实现
- 实现接口使用
implements
关键字-
一个类可以实现多个接口
[修饰符] class 类名 implements 接口1,接口2... { // 类体部分 }
实现接口会获得接口里定义的常量(成员变量)、方法(抽象方法、默认方法)
-
如果类实现接口后没有重写接口里的抽象方法,那就必须定义成抽象类
- 如果A类实现了B接口和C接口,那么A对象既可以直接赋给B变量,也可以直接赋给C变量
- 实现接口时必须使用
public
访问控制符,因为接口里方法都是public
,而子类重写父类时访问权限只能更大或相等
-
接口和抽象类
相同特征
- 接口和抽象类都不能被实例化
- 接口和抽象类都快可以包含抽象方法
接口体现的是一种规范,类似于整个系统的“总纲”,一旦接口改变,将可能导致系统中大部分类需要改写
差别
- 接口里只能包含抽象方法、默认方法、私有方法、类方法,不能为普通方法提供方法实现;抽象类完全可以包含普通方法
- 接口里只能定义静态常量,不能定义普通成员变量;抽象类里则都可以定义
- 接口里不包含构造器;抽象类可以包含构造器,但抽象类构造器不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作
- 接口里不能包含初始化块;抽象类可以
- 一个类最多只能有一个直接父类,包括抽象类;但一个接口可以实现多个接口
内部类
把一个类放在另一个类的内部定义,定义在其他类内部的类被称为内部类(嵌套类),包含内部类的类被称为外部类(宿主类)
作用
- 内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中其他类访问该类
- 内部类成员可以直接访问外部类的私有数据,内部类被当成外部类成员,同一个类的成员之间可以互相访问
- 但外部类不能访问内部类的实现细节
- 匿名内部类适合用于创建那些仅需要一次使用的类
- 内部类最多可使用三个修饰符:private、proteced、static
- 外部类不可以使用这三个修饰符
- 非静态内部类不能拥有静态成员
非静态内部类
内部类都被作为成员内部类定义,而不是作为局部内部类
- 成员内部类是一种与成员变量、方法、构造器和初始化块相似的类成员
- 局部内部类和匿名内部类则不是类成员
成员内部类
- 静态内部类
- 使用static修饰的成员内部类
- 非静态内部类
- 没有使用static修饰的成员内部类
- 内部类生成的class文件格式:
OuterClass$InnerClass.class
- 非静态内部类里可以直接访问外部类的
private
成员 - 在非静态内部类对象里,保存了一个它所寄生的外部类对象的引用
- 当调用非静态内部类的实例方法时,必须有一个非静态内部类实例,非静态内部类实例必须寄生在外部类实例里
- 非静态内部类的方法访问某个变量时:
该方法的局部变量->内部类成员变量->外部类成员变量
- 如果外部类成员变量、内部类成员变量、内部类方法局部变量同名
- 使用
this
、外部类类名.this区分
- 使用
- 非静态内部类的成员不能在外部类直接使用
- 需要在外部类显示创建内部类对象来调用访问实例成员
- 如果外部类成员变量、内部类成员变量、内部类方法局部变量同名
- 不允许在外部类的静态成员中直接使用非静态内部类
- 不允许在非静态内部类定义静态成员
静态内部类
使用static来修饰一个内部类,这个内部类就属于外部类本身
- 静态内部类可以包含静态成员,也可以包含非静态成员
- 静态内部类不能访问外部类实例变量,只能访问外部类的类成员
- 内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员
- 静态内部类时外部类的类相关的,静态内部类对象不是寄生在外部类的实训中,而是寄生在外部类的类本身中
- 外部类不能直接访问内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类对象作为调用者来访问静态内部类的实例成员
- 接口里可以定义内部类
- 接口里定义内部类默认使用
public static
修饰 - 接口内部类只能是静态内部类
- 接口也可以定义内部接口
- 内部接口时接口的成员,系统默认添加
public static
修饰符- 意义不大
- 内部接口时接口的成员,系统默认添加
- 接口里定义内部类默认使用
使用内部类
- 在外部类内部使用内部类
- 在外部类以外使用非静态内部类
- 若想在外部类以外的地方使用内部类,内部类不能使用
private
访问控制权限-
private
修饰的内部类只能在外部类内使用 - 省略访问控制符,内部类只能在和外部类在同一包下的其它类使用
- 使用
protected
修饰的内部类,可被与外部类同一包下的其他类和外部类的子类使用 - 在外部类以外的地方定义内部类(包括静态和非静态两种)变量
OuterClass.InnerClass varName
-
- 非静态内部类的对象必须寄生在外部类的对象里,因此创建非静态内部类对象之前,必须先创建其外部类对象
- 若想在外部类以外的地方使用内部类,内部类不能使用
- 在外部类以外的地方创建非静态内部类实例
OuterInstance.new InnerConstructor()
- 在外部类以外使用静态内部类
- 静态内部类是外部类类相关的,创建静态内部类时无须创建外部类对象
new OuterClass.InnerConstructor()
- 创建静态内部类的子类
-
public class StaticSubClass extends StaticOut.StaticIn{}
- 定义一个静态内部类时,外部类像一个包
-
- 不管静态内部类还是非静态内部类,声明变量的语法完全一样,区别只是在创建内部类对象时
- 静态内部类是外部类类相关的,创建静态内部类时无须创建外部类对象
局部内部类
- 把一个内部类放在方法里定义
- 局部内部类仅在该方法里有效
- 局部内部类不能是使用访问控制符和
static
修饰符- 不管是局部变量还是局部内部类,它们的上一级程序单元都是方法而不是类,都不能使用
static
修饰
- 不管是局部变量还是局部内部类,它们的上一级程序单元都是方法而不是类,都不能使用
- 局部内部类的class文件命名格式:
OuterClass$NInnerClass.class
- 局部内部类在实际开发中很少使用
匿名局部内部类
创建只需要一次使用的类
-
创建匿名内部类时会立即创建一个该类的实例,这个类定义立即消失,匿名内部类不能重复使用
new 实现接口() | 父类构造器(实参列表) { // 匿名内部类的类体部分 }
匿名内部类必须继承一个父类或实现一个接口,但最多只能继承一个父类或实现一个接口
-
规则
- 匿名内部类不能是抽象类
- 匿名内部类不能定义构造器,但可以定义初始化块
通过继承父类来创建匿名内部类时,匿名内部类将拥有和父类相似的构造器,相似是指拥有相同形参列表
如果局部变量内匿名内部类访问,该局部变量相当于自己使用了
final
修饰
Lambda表达式
lambda表达式入门
支持将代码块作为方法参数,允许使用更简洁的代码来创建只有一个抽象方法的接口(这种接口成为函数式接口)的实例
代替匿名内部类创建对象,lambda表达式的代码块将会代替实现抽象方法的方法体
-
相当于一个匿名方法
(形参列表) -> { // 代码块 }
-
组成
- 形参列表
- 允许省略形参类型;如果形参列表中只有一个参数,形参列表的圆括号也可以省略
- 箭头
->
- 代码块
- 如果代码块只有一条语句,允许省略代码块的花括号;若只有一条return语句,return关键字也可以省略;
- 形参列表
lambda表达式会被当成一个“任意类型”的对象
lambda表达式与函数式接口
- lambda表达的类型,也被称为
目标类型(target type)
- lambda表达式的目标类型必须是
函数式接口(functional interface)
- 函数式接口代表只包含一个抽象方法的接口
- 函数式接口可以包含多个默认方法、类方法,但只能声明一个抽象方法
- lambda表达式的结果就是被当成对象,因此程序中完全可以使用lambda表达式进行赋值
- lambda表达式的限制
- lambda表达式的目标类型必须是明确的函数式接口
- lambda表达式只能为函数式接口创建对象
- 常见方式
- 将lambda表达式赋值给函数式接口类型的变量
- 将lambda表达式作为函数式接口类型的参数传给某个方法
- 使用函数式接口对lambda表达式进行强制类型转换
方法引用与构造器引用
- 需要使用两个英文冒号
- 引用类方法
类名::类方法
- 函数式接口中被实现方法的全部参数传给该类方法作为参数
(a, b, ...) -> 类名.类方法(a, b, ...)
- 引用特定对象的实例方法
特定对象::实例方法
- 函数式接口中被实现方法的全部参数传给该方法作为参数
(a, b, ...) -> 特定对象.实例方法(a, b, ...)
- 引用某类对象的实例方法
类名::实例方法
- 函数式接口中被实现方法的第一个参数作为调用者,后面的参数全部传给该方法作为参数
(a, b, ...) -> a.实例方法(b, ...)
- 引用构造器
类名::new
- 函数式接口中被实现方法的全部参数传给该构造器作为参数
(a, b, ...) -> new 类名(a, b, ...)
-
Java.util.function
包下预定义了大量函数式接口-
XxxFunction
:这类接口中通常包含一个apply()
抽象方法, 该方法对参数进行处理、转换(apply()
方法的处理逻辑由lambda表达式来实现),然后返回一个新的值。该函数式接口通常用于对指定数据进行转换处理 -
XxxConsumer
:这类接口通常包含一个accept()
抽象方法,该方法与XxxFunction
接口中的apply()
方法基本相似,也负责对参数进行处理,只是该方法不会返回处理结果 -
XxxPredicate
:这类接口中通常包含一个test()
抽象方法,该方法通常用来对参数进行某种判断(test()
方法的判断逻辑由lambda表达式来实现(,然后返回一个Boolean
值。该接口通常用于判断参数是否满足特定条件,经常用于进行筛滤数据 -
XxxSuppliter
:这类接口通常包含一个getAsXxx()
抽象方法,该方法不需要传入参数,该方法会按某种逻辑算法(getAsXxx()
方法的逻辑算法由lambda表达式来实现)返回一个数据。
-
lambda表达式与匿名内部类的联系和区别
- lambda表达式时匿名内部类的一种简化,可以部分取代匿名内部类的作用
- 相同点
- lambda表达式和匿名内部类一样,都可以直接访问
effectively final
的局部变量,以及外部类的成员变量(包括实例变量和类变量) - lambda表达式创建的对象与匿名内部类生成的对象一样,都可以直接调用从接口红继承的默认方法
- lambda表达式和匿名内部类一样,都可以直接访问
- 区别
- 匿名内部类可以为任意接口创建实例,不管接口包含多少个抽象方法,只要匿名内部类实现所有的抽象方法即可;但lambda表达式只能为函数式接口创建实例
- 匿名内部类可以为抽象类甚至普通类创建实例;但lambda表达式只能为函数式接口创建实例
- 匿名内部类实现的抽象方法的方法体允许调用接口中定义的默认方法;但lambda表达式的代码块不允许调用接口中定义的默认方法
使用lambda表达式调用Arrays
的类方法
-
Arrays类
的有些方法需要Comparator
、XxxOperator
、XxxFunction
等接口的实例,这些接口都是函数式接口
枚举类
一个类的对象是有限而且固定的,即实例有限而且固定的类
手动实现枚举类
- 在早期代码中,可能会使用简单的静态常量来表示枚举
- 定义类的方式实现
- 通过
private
将构造器隐藏起来 - 把类所有可能实例都使用
public static final
修饰的类变量来保存 - 如果有必要,提供一些静态方法,允许其他程序根据特定参数来获取与之匹配的实例
- 通过
- 通过定义类实现枚举的代码量比较大,实现起来也比较麻烦
枚举类入门
- 关键字
enum
(与class
、interface
关键字地位相同),定义枚举类 - 一个源文件中最多只能定义一个
public
访问权限的枚举类,且该源文件也必须和枚举类类名相同 - 与普通类的区别
- 枚举类可以实现一个或多个接口,枚举类默认继承了
java.lang.Enum类
,而不是默认继承Object类,因此枚举类不能显式继承其他父类-
java.lang.Enum类
实现了java.lang.Serializable
和java.lang.Comparable
两个接口
-
- 使用
enum
定义、非抽象的枚举类默认会使用final
修饰,因此枚举类不能派生子类 - 枚举类的构造器只能使用
private
访问控制符 - 枚举类的所有实例必须在枚举类的第一行显式列出,否则这个枚举类永远都不能产生实例
- 列出这些实例时,系统会自动添加
public static final
修饰,无需显式添加
- 列出这些实例时,系统会自动添加
- 枚举类可以实现一个或多个接口,枚举类默认继承了
- 枚举类默认提供了一个
values()
方法,该方法可以很方便地遍历所有的枚举值 - 可通过
EnumClass.variable
形式来访问枚举实例 -
switch
表达式还可使用枚举类对象作为表达式,case
表达式中的值直接使用枚举值的名字 -
java.lang.Enum类
提供的方法-
int compareTo(E o)
- 该方法用于指定枚举对象比较顺序,同一个枚举实例只能与相同类型的实例进行比较。如果该枚举对象位于指定枚举对象之后,则返回正整数;如果该枚举对象位于指定枚举对象之前,则返回负整数,否则返回零
-
String name()
- 返回此枚举实例的名称,这个名称就是定义枚举类时列出的所有枚举值之一
- 应优先考虑使用
toString()
方法
- 应优先考虑使用
- 返回此枚举实例的名称,这个名称就是定义枚举类时列出的所有枚举值之一
-
int ordinal()
- 返回枚举值在枚举类中的索引值(在枚举声明中的位置,第一个枚举值的索引值为零)
-
String toString()
- 返回枚举常量的名称,与
name
方法相似,但toString()
方法更常用
- 返回枚举常量的名称,与
-
public static <T extends Enum<T>> T valueOf(Class<T>enumType, String name)
- 静态方法,返回指定枚举类中指定名称的枚举值。名称必须与在枚举类中声明枚举值时所用的标识符完全匹配
-
枚举类的成员变量、方法和构造器
- 枚举类产生对象的方式不同于普通类,枚举类的实例只能是枚举值,而不是随意地通过
new
来创建枚举类对象 - 建议将枚举类的成员变量都使用
private final
修饰 - 构造器
-
枚举类实例名 (构造器形参列表)
- 必须定义和定义枚举实例的形参列表相同的构造器
-
实现接口的枚举类
枚举类也可以实现一个或多个接口
-
若需要每个枚举值在调用该方法时呈现出不同的行为方式,则可以让每个枚举值分别来实现该方法
枚举实例名 (构造器形参列表) { // 实现接口的抽象方法 }
匿名内部类
非抽象的枚举类默认使用
final
修饰;对于一个抽象的枚举类,只要它包含了抽象方法,系统会默认使用abstract
修饰
包含抽象方法的枚举类
- 不能使用
abstract
关键字将枚举类定义成抽象类(系统自动会添加abstract
关键字),但因为枚举类需要显式创建枚举值,而不是作为父类,所以定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误
对象与垃圾回收
当对象、数组等引用类型实体不再被任何引用变量引用时,等待垃圾回收机制进行回收
特征
- 垃圾回收机制只负责回收堆内存的对象,不会回收任何物理资源(例如数据库连接、网络IO等资源)
- 程序无法精确控制垃圾回收的运行,垃圾回收会在合适的时候进行;当对象永久性地失去引用后,系统就会在合适的时候回收它所占的内存
- 在垃圾回收机制回收任何对象之前,总会调用它的
finalize()
方法,该方法可能使对象重新复活(让一个引用变量重新引用该对象),从而导致垃圾回收机制取消回收
对象在内存中的状态
- 当一个对象在堆内存中运行时,根据它被引用变量所引用的状态,可分为三种状态
- 可达状态
- 当一个对象被创建后,若有一个以上的引用变量引用它,则这个对象在程序中处于可达状态,程序可通过引用变量来调用该对象的实例变量和方法
- 可恢复状态
- 如果程序中某个对象不再有任何引用变量引用它,它就进入了可恢复状态
- 在这种状态下,系统的垃圾回收机制准备回收该对象所占用的内存,在回收该对象之前,系统会调用所有可恢复状态对象的
finalize()
方法进行资源清理。 - 如果系统在调用
finalize()
方法时重新让一个以引用变量引用该对象,则这个对象会再次变为可达状态;否则该对象将进入不可达状态
- 不可达状态
- 当对象与所有引用变量的关系都被切断,且系统已经调用所有对象的
finalize()
方法后依然没有使对象变成可达状态,那么这个对象将永久性地失去引用,最后变成不可达状态 - 只有当一个对象处于不可达状态时,系统才会真正回收该对象所占有的资源
- 当对象与所有引用变量的关系都被切断,且系统已经调用所有对象的
- 可达状态
- 一个对象可以被一个方法的局部变量引用,也可以被其他类的类变量引用,或被其他对象的实例变量引用。
- 当某个对象被其他类的类变量引用时,只有该类被销毁后,该对象才会进入可恢复状态;当某个对象被其他对象的实例变量引用时,只有当该对象被销毁后,该对象才会进入可恢复状态
强制垃圾回收
- 程序只能控制一个对象何时不再被任何引用变量引用,绝不能控制它何时被回收
- 程序无法精确控制Java垃圾回收的时机,但依然可以强制系统进行垃圾回收
- 这种强制只是通知系统进行垃圾回收,但系统是否进行垃圾回收依然不确定
- 方式
- 调用
System类
的gc()
静态方法:System.gc()
- 调用
Runtime对象
的gc()
实例方法:Runtime.getRuntime().gc()
- 调用
-
java -verbose:gc 类名
- 可以看到每次垃圾回收后的提示信息
- 这种强制只是建议系统立即进行垃圾回收,垃圾回收机制会在收到通知后,尽快进行垃圾回收
finalize方法
- 在垃圾回收机制回收某个对象所占用的内存之前,通常要求程序调用适当的方法来清理资源,在没有明确指定清理资源的情况下,Java提供了默认机制来清理该对象的资源,这个机制就是finalize()方法
- 方法原型
protected void finalize() throws Throwable
-
throws Throwable
表示可以抛出任何类型的异常
- 当
finalize()
方法返回后,对象消失,垃圾回收机制开始执行 - 任何Java类都可以重写Object类的
finalize()
方法,在该方法中清理该对象占用的资源 - 如果程序终止之前始终没有进行垃圾回收,则不会调用失去引用对象的
finalize()
方法来清理资源 - 垃圾回收机制何时调用对象的
finalize()
方法是完全透明的,只有当程序认为需要更多的额外内存时,垃圾回收机制才会进行垃圾回收 - 特点
- 永远不要主动调用某个对象的
finalize()
方法,该方法应交给垃圾回收机制调用 -
finalize()
方法何时被调用,是否被调用具有不确定性,不要把finalize()
方法当成一定会被执行的方法 - 当JVM执行可恢复对象
finalize()
方法时,可能使该对象或系统中其他对象重新编程可达状态 - 当JVM执行
finalize()
方法时出现异常时,垃圾回收机制不会报告异常,程序继续执行
- 永远不要主动调用某个对象的
- 强制垃圾回收机制调用可恢复对象的
finalize()
方法Runtime.getRuntime().runFianlization();
System.runFinaliaztion();
对象的软、弱和虚引用
- 对于大部分而言,程序里会有一个引用变量引用该对象,这是最常见的引用方式
java.lang.ref
包下提供3个类
-
SoftReference
- 软引用
-
PhantomReference
- 虚引用
-
WeakReference
- 弱引用
-
强引用(StrongReference)
- Java程序中最常见的引用方式
- 程序创建一个对象,并把这个对象赋给一个引用变量,程序通过该引用变量来操作实际的对象
-
软引用(SoftReference)
- 需要通过
SoftReference类
来实现,当一个对象只有软引用时,它有可能被垃圾回收机制回收 - 对于只有软引用的对象而言,当系统内存空间足够时,它不会被系统回收,程序也可使用该对象;当系统内存空间不足时,系统可能会回收它
- 软引用通常用于对内存敏感的程序中
- 需要通过
-
弱引用(WeakReference)
- 弱引用通过
WeakReference类
实现,弱引用和软引用很像,但弱引用的引用级别更低 - 对于只有弱引用的对象而言,当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所占用的内存
- 必须等到系统垃圾回收机制运行时才会被回收
- 弱引用通过
-
虚引用(PhantomReference)
- 虚引用通过
PhantomReference类
实现,虚引用完全类似于没有引用 - 虚引用对对象本身没有太大影响,对象甚至感觉不到虚引用的存在
- 如果一个对象只有一个虚引用时,那么它和没有引用的效果大致相同
- 虚引用主要用于跟踪对象被垃圾回收的状态,虚引用不能单独使用,虚引用必须和引用队列(ReferenceQueue)联合使用
- 虚引用通过
以上三个引用类型都包含了一个
get()
方法,用于获取被它们所引用的对象-
队列引用
- 引用队列由
java.lang.ref.ReferenceQueue类
表示,它用于保存被回收后对象的引用 - 当联合使用软引用、弱引用和引用队列时,系统在回收被引用的对象之后,将把被回收对象对应的引用添加到关联的引用队列中
- 与软引用和弱引用不同的是,虚引用在对象被释放之前,将把它对应的虚引用添加到它关联的引用队列中,这使得可以在对象被回收之前采取行动
- 引用队列由
软引用和弱引用可以单独使用,但虚引用不能单独使用,单独使用虚引用没有太大的意义
虚引用的主要作用就是跟踪对象被垃圾回收的状态,程序可以通过检查与虚引用关联的引用队列中是否已经包含了该虚引用,从而链接虚引用所引用的对象是否即将被回收
系统无法通过虚引用来获得被引用的对象;当程序强制垃圾回收后,只有虚引用引用的对象被垃圾回收,当被引用的对象被回收后,对应的虚引用将被添加到关联的引用队列
strictfp
关键字
- 含义:FP-strict
- 精确浮点
- 使用
strictfp
关键字来修饰类、接口或方法时,在修饰的范围内Java的编译器和运行环境会完全依照浮点规范IEEE-754
来执行
native
关键字
- 主要用于修饰一个方法,使用
native
修饰的方法类似于一个抽象方法 - 与抽象方法不同的是,
native
方法通常采用C语言实现 - 如果某个方法需要利用平台相关特性,或者访问系统硬件等,则可以使用
native
修饰该方法,再把该方法交给C语言去实现 - 一旦Java程序中包含了
native
方法,这个程序将失去跨平台的功能
JAR包
JAR文件的全称是
Java Archive File
,意思是Java档案文件。通常JAR文件是一种压缩文件;JAR文件ZIP文件的区别是在JAR文件中默认包含了一个名为META-INF/MANIFEST.MF
的清单文件,这个清单文件是生成JAR文件时由系统自动创建的。
将开发的应用程序提供给别人使用,通常将这些类文件打包成一个JAR文件;别人在系统的CLASSPATH环境变量中添加这个JAR文件,则Java虚拟机就可以自动在内存中解压这个JAR包,把JAR文件当成一个路径,在这个路径中查找所需要的类或包层次对应的路径结构。
使用JAR文件的好处:
- 安全。能够对JAR文件进行数字签名,只让能够识别数字签名的用户使用里面的东西。
- 加快下载速度。在网上使用
Applet
时,如果存在多个文件而不打包,为了能够把每个文件都下载到客户端,需要为每个文件单独建立一个HTTP连接。 - 压缩。使文件变小,JAR的压缩机制和ZIP完全相同
- 包封装。能够让JAR包里的文件依赖于同一版本的类文件。
- 可移植性。JAR包作为嵌在Java平台内部的标准,能够在各种平台上直接使用。
JAR命令
JAR使随JDK自动安装的,在JDK安装目录下的bin目录中,Windows下文件名未jar.exe,Linux下文件名为jar
-
创建JAR文件:
jar cf jarName.jar -C dist\.
该命令没有显示压缩过程,执行结果使将当前路径下的dist路径下的全部内容生成一个jarName.jar文件。 -
创建JAR文件,并显示压缩过程:
jar cvf jarName.jar -C dist\.
该命令的结果与第一个命令相同,但是由于v
参数的作用,显示出了打包过程。 -
不使用清单文件:
jar cvfM jarName.jar -C dist\.
该命令的结果与第二个命令相似,其中M
选项表明不生成清单文件。因此生成的JAR包中没有META-INF/MANIFEST.MF
文件。 -
自定义清单文件内容:
jar cvfm jarName.jar MANIFEST.MF -C dist\.
运行结果与第二个命令相似,其中m
选项指定读取用于清单文件信息。因此在生成的JAR包中青岛文件META-INF/MANIFEST.MF
的内容有所不同,它会在原有清单文件基础上增加MANIFEST.MF
文件的内容。
当开发者向MANIFEST.MF
清单文件中增加自己的内容时,清单文件的内容由如下格式的多个key-value
对组成。
key:<空格>value
清单文件的内容格式要求如下:- 每行只能定义一个
key-value
对,每行的key-value
对之前不能有空格 - 每组
key-value
对之间以;
(英文冒号后紧跟一个英文空格)分隔 - 文件开头不能有空行
- 文件必须以一个空行结束
可以将上面文件保存在任意位置,以任意文件名存放。
- 每行只能定义一个
-
查看JAR包内容:
jar tf jarName.jar
当JAR包中的文件路径和文件非常多时,直接执行该命令将无法查看到包的全部内容,此时可利用重定向将显示结果保存到文件中。
jar tf jarName.jar > a.txt
执行上面命令看不到任何输出,但命令执行结束后,将在当前路径下生成一个a.txt文件,该文件保存了包里的文件的详细信息。 -
查看JAR包详细内容:
jar tvf jarName.jar
该命令与第五个命令基本相似,但它更详细。 -
解压缩:
jar xf jarName.jar
将jar文件解压到当前目录下,不显示任何信息。 -
带提示信息解压缩:
jar xvf jarName.jar
解压缩效果与第七个命令相似,但系统会显示解压过程的详细信息。 -
更新JAR文件:
jar uf jarName.jar Hello.class
更新JAR文件中的Hello.class文件;如果JAR包中已有Hello.class文件,则使用新的文件替换原来的文件;如果包中没有Hello.class文件,则把文件添加到JAR文件中。 -
更新时显示详细信息:
jar uvf jarName.jar Hello.class
这个命令与第九个命令相似,也用于更新JAR包的文件,但它会显示详细信息。 -
创建多版本的JAR包:
jar cvf jarName.jar -C dist1\. --release 9 -C dist\.
多版本JAR包时JDK9新增的功能,它允许同一个JAR包中包含针对多个Java版本的class文件。JDK9为JAR命令增加了一个--release
选项,用于创建多版本JAR包,该选项的参数值必须大于或等于9。
在使用多版本JAR包之前,可以使用javac
的--release
选项针对指定Java进行编译。比如命令:javac --release 7 Test.java
,这个命令代表使用Java7的语法来编译Test.java文件。
创建可执行的JAR包
当一个应用程序开发成功后,大致有如下三种发布方式
使用平台相关的编译器将整个应用编译成平台相关的可执行性文件
为应用编辑一个批处理文件,以Windows系统为例,批处理文件只需要定义如下命令:
java package.MainClass
,当用户运行这个批处理文件时,系统将运行程序的主类。如果不想保留运行Java程序的命令行窗口,也可以在批处理文件中定义如下命令:start javaw package.MainClass
-
将一个应用程序制作成可执行的JAR包,通过JAR包来发布应用程序。
在Windows下安装JRE时,安装文件会将*.jar
文件映射成由javaw.exe
打开。
JAR命令有一个-e
选项,该选项指定JAR包中作为程序在入口的主类的类名,例如下命令:jar cvfe jarName.jar test.Test test
,这个命令把test目录下所有文件都压缩到JAR包中,并指定使用test.Test类作为程序的入口。
运行上面的JAR包有两种方式:- 使用
java
命令,语法是:java -jar jarName.jar
- 使用
javaw
命令,语法是:javaw jarName.jar
当创建JAR包时,所有类都必须放在与包结构对应的目录结构中,必须在JAR包下包含
*.class
文件 - 使用