Fork me on GitHub

面向对象深入

包装类

JDK 1.5提供了自动装箱与自动拆箱功能。

mark

将字符串类型转换为基本类型:

  • 利用包装类提供的parseXxx(String s)静态方法
  • 利用包装类提供的Xxx(String s)构造器

将基本类型转换为字符串类型:

  • 利用String类提供的多个重载的valueOf()方法

虽然包装类型为引用数据类型,但包装类的实例可以与数值类型进行比较,这种比较是直接取出包装类的实例所包装的数值来进行比较的。

将一个int类型的值赋给一个Integer类型的实例:

  • 当整数在-128到127之间时,int类型的值会放入一个数组中缓存起来,故同一个整数总是引用同一个数组元素,它们全部相等。
  • 当整数不在这个范围内时,自动装箱后同一个整数对应的实例不相等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class IntegerCacheTest{
public static void main(String[] args){
Integer in0 = 6;
Integer in8 = 6;
System.out.println(in0 == in8);//输出true
Integer in1 = new Integer(6);
Integer in2 = Integer.valueOf(6);
Integer in3 = Integer.valueOf(6);
System.out.println(in0 == in1);//输出false
System.out.println(in0 == in2);//输出true
System.out.println(in2 == in3);//输出true
System.out.println(in1 == in3);//输出false

Integer in4 = Integer.valueOf(200);
Integer in5 = Integer.valueOf(200);
Integer in6 = 200;
Integer in7 = 200;
System.out.println(in4 == in5);//输出false
System.out.println(in6 == in7);//输出false
System.out.println(in4 == in6);//输出false
}
}

处理对象

toString()方法

当方法输出一个对象时,实际上输出的是该对象的toString()方法的返回值。

Object类提供的toString()方法总是返回”类名+@+hashCode”值

1
2
3
Person p = new Person();
System.out.println(p);
System.out.println(p.toString());

大部分时候,重写toString()方法总是返回该对象的所有令人感兴趣的信息所组成的字符串。

==

  • 对基本类型变量,只要两个变量的值相等即返回true。(不要求数据类型严格相同)
  • 对引用类型变量,只有两个相同或具有父子关系的对象才可判断,只有指向同一对象时才返回true
1
2
3
4
5
6
7
8
int r = 4;
short t = 4;
float y = 4.0f;
double q = 4.0;
System.out.println(r == t);//输出true
System.out.println(r == q);//输出true
System.out.println(r == y);//输出true
System.out.println('A' == 65);//输出true

常量池

专门用来管理在编译时被确定并保存在已编译的.class文件中的一些数据,它包括了关于类、方法、接口中的常量,还包括字符串常量。

String str = “hello”;与String str = new String(“hello”);的区别:

  • 前者使用”hello”字符串直接量,保存在常量池中
  • 后者先用常量池保存”hello”直接量,再调用String的构造器来创建一个新的对象,保存在堆内存中。

JVM常量池保证相同的字符串直接量只有一个,不会产生多个副本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2);//输出true
System.out.println(str1.equals(str2));//输出true

String str3 = new String("hello");
String str4 = new String("hello");
System.out.println(str3 == str4);//输出false
System.out.println(str3.equals(str4));//输出true

String s1 = "疯狂java";
String s2 = "疯狂";
String s3 = "java";
String s4 = "疯狂" + "java";
String s5 = s2 + s3;//编译时不能确定下来
String s6 = new String("疯狂java");
final String s7 = "疯狂";//直接量
final String s8 = "java";//直接量
String s9 = s7 + s8;//两个直接量的连接运算,可以在编译阶段被确定
System.out.println(s1 == s4);//输出true
System.out.println(s1 == s5);//输出false
System.out.println(s1 == s9);//输出true
System.out.println(s1 == s6);//输出false
System.out.println(s1.equals(s5));//输出true
System.out.println(s1.equals(s6));//输出true

equals()方法

Object类提供的equals()方法同样要求两个引用变量指向同一个对象,与==没什么区别,故实际应用中常常需要重写。

String类重写了Object类的equals()方法,只要两个字符串所包含的字符序列相同,即返回true。

正确重写equals()方法需要满足条件:

  • 自反性
  • 对称性
  • 传递性
  • 一致性:只要x与y不变,x.equals(y)返回值不变
  • 对任何不是null的x,x.equals(null)一定返回false

单例类

始终只能创建一个实例的类。

将构造器用private修饰,提供一个public static修饰的方法作为访问点。

需要一个private static修饰的成员变量来保存曾经创建过的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SingletonTest{
public static void main(String[] args){
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);//输出true
}
}

class Singleton{
private static Singleton instance;//用来缓存曾经创建的实例

private Singleton(){}

public static Singleton getInstance(){//提供一个静态方法用于返回单例类的实例
if (instance == null){//可加入自定义控制保证只产生一个对象
instance = new Singleton();
}
return instance;
}
}

final修饰符

final可用于修饰类、变量、方法,用于表示它修饰的类、变量、方法不可改变。

final成员变量

必须由程序员显示指定初始值。

类变量:必须在静态初始化块中指定初始值或声明该变量时指定初始值,两者之一。

实例变量:必须在非静态初始化块、声明该实例变量或构造器中指定初始值,三者之一。

如果打算在构造器或初始化块中对final成员变量进行初始化,则不要在初始化之前就访问成员变量,因为系统不会进行隐式初始化。

final局部变量

如果在定义时未指定默认值,则可以在后面的代码中对该final变量赋初始值,但只能一个,不能重复赋值。若定义时已经指定默认值,则不可再次赋值。

final修饰的形参不能被赋值。

final修饰引用变量时,只能保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变。

只要满足以下条件,final变量不再是一个变量,而是相当于一个直接量:

  • 使用final修饰符修饰
  • 在定义该final变量时指定了初始值
  • 该初始值可以在编译时被确定下来

final修饰符的一个重要用途。编译器会把程序中所有用到该变量的地方直接替换成该变量的值。

1
2
3
4
5
6
7
final int o = 5;
final int u = 3-3;
System.out.println(u*u);//输出0,没有c语言#define那样的骚操作
final String book = "疯狂java讲义:" + 99.0;
final String book1 = "疯狂java讲义:" + String.valueOf(99.0);//调用了方法,无法在编译时被确定下来
System.out.println(book == "疯狂java讲义:99.0");//输出true
System.out.println(book1 == "疯狂java讲义:99.0");//输出false

final方法

final修饰的方法不可被重写,然而可以在子类中定义相同方法名、形参列表、返回值类型的方法。

可以被重载。

final类

不可以有子类。

不可变类

创建该类的实例后,该实例的实例变量不可改变。

8个包装类与String类都是不可变类。

原则:

  • 成员变量用private final修饰
  • 提供带参数的构造器初始化成员变量
  • 仅为成员变量提供getter方法,不提供setter方法
  • 如有必要,重写hashCode()方法与equals()方法(用equals()判断为相等的对象的hashCode()也相等)

当创建不可变类时,如果它包含的成员变量的类型是可变的,那么其对象的成员变量的值依然可变,这个不可变类创建失败。(例如包含一个引用类型的成员变量,而这个引用类为可变类)

解决方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Name{//一个可变类
private String firstName;
private String lastName;
public Name(){}
public Name(String firstname, String lastname){
this.firstName = firstname;
this.lastName = lastname;
}
public void setFirstName(String firstname){
this.firstName = firstname;
}
public void setLastName(String lastname){
this.lastName = lastname;
}
public String getFirstName(){
return firstName;
}
public String getLastName(){
return lastName;
}
}

public class Person{//设计出的不可变类
private final Name name;
public Person(Name name){
this.name = new Name(name.getFirstName(), name.getLastName());
}
public Name getName(){
return new Name(name.getFirstName(), name.getLastName());//返回一个匿名对象
}

public static void main(String[] args){
Name name = new Name("Ben","Wen");
Person p = new Person(name);
System.out.println(p.getName().getFirstName());//输出Ben
name.setFirstName("John");
System.out.println(p.getName().getFirstName());//输出Ben
}
}

抽象类

规则:

  • 使用abstract修饰符来定义。
  • 有抽象方法的类一定是抽象类,抽象类中可以没有抽象方法,也可以有普通方法。
  • 抽象方法没有方法体(即没有{}),结尾有;
  • 抽象类不能被实例化。
  • 抽象类可以包括成员变量、方法、构造器、初始化块、内部类(接口、枚举),构造器不能用于创建实例,主要被子类调用。
  • 如果子类没有完全实现抽象类中的抽象方法,则子类也必须用abstract修饰。

使用abstract修饰类时,表明这个类必须被继承;使用abstract修饰方法时,表明这个方法必须被重写,因此final与abstract永远不能同时使用!!!

static修饰的方法可以通过类直接调用,但抽象方法被调用时会出现错误,因此static与abstract不能同时修饰一个方法。(不是绝对互斥,可以同时修饰内部类)

private与abstract也不能同时修饰方法。

private和final同时修饰方法语法上合法,但意义不大。

抽象类体现的是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在此基础上进行扩展、改造。

接口

接口不提供任何实现,它体现的是规范和实现分离的设计哲学。

规则:

  • 使用interface关键字定义接口
  • 接口内不能包含构造器和初始化块,只能包含成员变量(必须为静态常量)、方法(只能为抽象实例方法、类方法或默认方法)、内部类(接口、枚举)
  • 接口的修饰符组合只能为public abstract或只有abstract,后者为包访问权限,abstract为系统自动添加
  • 接口中的成员变量总是public static final修饰,在定义时必须指定初始值,系统自动添加
  • 接口中的普通方法总是public abstract修饰,不能有方法体,系统自动添加
  • 接口中的类方法、默认方法必须有方法体,默认方法为java 8开始支持,必须由public default修饰,public为系统自动添加
  • 接口中的内部类、内部接口、内部枚举总是public static修饰,系统自动添加
  • 接口支持多继承,但只能继承接口,不能继承类。

接口可以用于声明引用类型变量,这个引用类型变量必须引用到实现了该接口的对象。

一个类可以实现多个接口,实现用implements关键字表示。

一个类实现了一个或多个接口后,这个类必须完全实现这些接口里所定义的全部抽象方法,否则将会保留剩下的抽象方法,该类必须声明为抽象类。

实现类中实现的接口中的方法必须为public,因为访问权限只能更大或相等而接口中的抽象实例方法全为public。

所有接口类型的引用变量都可以直接赋给Object类型的引用变量。

默认方法不能重写Object中的方法,但可以重载Object中的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
interface Product{
int getProduceTime();
}

interface Output{
int MAX_CACHE_LINE = 50;

void out();
void getData(String msg);
default void print(String... msgs){
for (String msg : msgs){
System.out.println(msg);
}
}
default void test(){
System.out.println("默认的test()方法");
}
static String staticTest(){
return "接口里的类方法";
}
}

public class Printer implements Output , Product{
private String[] printData = new String[MAX_CACHE_LINE];
private int dataNum = 0;

public void out(){
while (dataNum > 0){
System.out.println("打印机打印:" + printData[0]);
System.arraycopy(printData,1,printData,0,--dataNum);//把作业队列整体前移1位,并将剩下的作业数减1
}
}

public void getData(String msg){
if(dataNum >= MAX_CACHE_LINE){
System.out.println("输出队列已满,添加失败");
} else{
printData[dataNum++] = msg;
}
}

public int getProduceTime(){
return 45;
}

public static void main(String[] args){
Output o = new Printer();
o.getData("1111111");
o.getData("2222222");
o.getData("3333333");
o.out();
System.out.println("----------");
o.getData("4444444");
o.out();
o.print("abc","def","xyz");
o.test();
Output.staticTest();
// System.out.println(o.getProduceTime());
Printer r = new Printer();
System.out.println(r.getProduceTime());
r.getData("7777777");
r.out();

Product p = new Printer();
System.out.println(p.getProduceTime());
// p.getData("5555555");某种接口类型的变量只能调用该种接口中的方法的实现
Object obj = p;
// System.out.println(obj.getProduceTime());
}
}
/*输出:
打印机打印:1111111
打印机打印:2222222
打印机打印:3333333
----------
打印机打印:4444444
abc
def
xyz
默认的test()方法
45
打印机打印:7777777
45
*/

简单工厂模式、命令模式,见疯狂java讲义第三版P196

内部类

静态内部类与非静态内部类

内部类成员可以直接访问外部类的私有数据,但外部类不能访问内部类的实现细节。

内部类比外部类多三个修饰符:private、protected、static。

非静态内部类不能拥有静态成员(如静态方法、静态成员变量、静态初始化块)。

内部类的class文件总是OuterClass$InnerClass.class格式。

静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。(静态内部类只持有对外部类的引用,没有持有对外部类对象的引用。)

若外部类成员变量、内部类成员变量与内部类中的局部变量同名,则可用外部类类名.this、this来区分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class DiscernVariable{
private String prop = "外部类的实例变量";

public class Inclass{
String prop = "内部类的实例变量";

public void info(){
String prop = "局部变量";
System.out.println("外部类的实例变量值:" + DiscernVariable.this.prop);
System.out.println("内部类的实例变量值:" + this.prop);
System.out.println("局部变量的值:" + prop);
}
}

public void test(){
Inclass in = new Inclass();
in.info();
}

public static void main(String[] args){
new DiscernVariable().test();//只创建了外部类对象,未创建内部类对象
new DiscernVariable().new Inclass().info();
new CreateInnerInstance().test();
// new Inclass();
}
}

class CreateInnerInstance{
public void test(){
DiscernVariable.Inclass in = new DiscernVariable().new Inclass();//在外部类之外使用非静态内部类方法
/*等价于:
DiscernVariable.Inclass in;
DiscernVariable temp = new DiscernVariable();
in = temp.new Inclass();
*/
in.info();
}
}
/*输出:
外部类的实例变量值:外部类的实例变量
内部类的实例变量值:内部类的实例变量
局部变量的值:局部变量
外部类的实例变量值:外部类的实例变量
内部类的实例变量值:内部类的实例变量
局部变量的值:局部变量
外部类的实例变量值:外部类的实例变量
内部类的实例变量值:内部类的实例变量
局部变量的值:局部变量
*/

使用内部类:

  • 在外部类内部使用内部类时与使用普通类区别不大。
  • 在外部类之外使用非静态内部类时语法为:OuterClass.InnerClass a = new OuterClass().new InnerClass();
  • 在外包了之外使用静态内部类时语法为:OuterClass.InnerClass a = new OuterClass.InnerClass();

如果有一个非静态内部类子类的对象存在,则一定存在与之对应的外部类对象。

创建内部类对象时,静态内部类只需使用外部类即可调用构造器,而非静态内部类必须使用外部类对象来调用构造器。

局部内部类

方法里定义的内部类称为局部内部类。局部内部类与匿名内部类都不是类成员。

所有局部成员都不能使用static及访问控制修饰符修饰。

局部内部类的class文件名中增加了一个数字,即OuterClass$NInnerClass.class

匿名内部类

匿名内部类适合创建那种只需要一次使用的类。

规则:

  • 匿名内部类必须且只能继承一个父类或实现一个接口
  • 匿名内部类不能是抽象类(创建匿名内部类时立即创建其对象)
  • 匿名内部类不能定义构造器(无类名),但可以定义初始化块
  • 匿名内部类必须实现它的抽象父类或接口中包含的全部方法

当通过实现接口来创建匿名内部类时,不能显示创建构造器,new 接口名后的括号里不能传入参数。

当通过继承父类来创建匿名内部类时,匿名内部类将拥有与父类相似的构造器,即拥有相同的形参列表。

“effectively final”:被匿名内部类访问的局部变量,可以用final修饰,也可以不用final修饰,但必须按照有final修饰的方法来用—-即一次赋值后,不可再次赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface A{
void test();
}

public class ATest{
public static void main(String[] args){
int age = 8;
A a = new A(){
public void test(){
System.out.println(age);
}
};
a.test();
// age = 2;错误: 从内部类引用的本地变量必须是最终变量或实际上的最终变量
}
}

修饰符的适用范围

mark

Lambda表达式

Lambda表达式与函数式接口

Lambda表达式的主要作用就是代替匿名内部类的繁琐语法。

它由三部分组成:

  • 形参列表。当形参列表中只有一个参数时,圆括号可以省略。
  • 箭头 ->。
  • 代码块。如果代码块只包含一条语句,则可以省略花括号。如果代码块只包含一条return语句,则可以省略return,Lambda表达式自动返回这条语句的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
interface Eatable{
void taste();
}

interface Flyable{
String f = "飞碟";
void fly(String weather);
}

interface Addable{
int add(int a,int b);
}

public class LambdaQs{
public void eat(Eatable e){
e.taste();
}

public void drive(Flyable f){
System.out.println("我正在驾驶:" + Flyable.f);
f.fly("[碧空如洗的晴日]");
}

public void test(Addable add){
System.out.println("5与3的和为:" + add.add(5,3));
}

public static void main(String[] args){
LambdaQs lq = new LambdaQs();
lq.eat(() -> System.out.println("苹果味道不错!"));
lq.drive(weather ->
{
System.out.println("今天的天气是:" + weather);
System.out.println("直升机飞行平稳");
});
lq.test((a, b) -> a + b);
}
}
/*输出:
苹果味道不错!
我正在驾驶:飞碟
今天的天气是:[碧空如洗的晴日]
直升机飞行平稳
5与3的和为:8
*/

Lambda表达式的目标类型必须是明确的“函数式接口”,即只包含一个抽象方法的接口。函数式接口可以包含多个默认方法、类方法,但只能声明一个抽象方法(因为Lambda表达式只能实现一个方法)。

java 8提供@FunctionalInterface注释函数式接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Runnable r = () -> {
for (int i = 0;i < 100;i++){
System.out.println();
}
}//合法

Object obj = () -> {
for (int i = 0;i < 100;i++){
System.out.println();
}
}//非法,Object不是明确的函数式接口

Object obj = (Runnable)() -> {
for (int i = 0;i < 100;i++){
System.out.println();
}
}//合法,可以强制类型转换

同样的Lambda表达式的目标类型完全是可以变化的,唯一的要求是其实现的匿名方法与目标类型中唯一的抽象方法具有相同得形参列表。

匿名内部类与Lambda表达式的区别:

  • 匿名内部类可以为任意接口、抽象类甚至普通类创建实例,而Lambda表达式只能为函数式接口创建实例。
  • 匿名内部类实现的抽象方法的方法体中允许调用接口中定义的默认方法,Lambda表达式的代码块不允许调用接口中定义的默认方法。

方法引用与构造器引用

mark

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@FunctionalInterface
interface Converter{
Integer convert(String name);
}

Converter converter1 = from -> Integer.valueOf(from);
Integer val1 = converter1.convert("99");
System.out.println(val);
第一条语句可转换为:
Converter converter1 = Integer :: valueOf;

Converter converter2 = from -> "fikt.org".indexOf(from);
Integer value = converter2.convert("it");
System.out.println(value);
第一条语句可转换为:
Converter converter2 = "fikt.org" :: indexOf;


@FunctionalInterface
interface MyTest{
String test(String a, int b, int c);
}

MyTest mt = (a,b,c) -> a.substring(b,c);
String str = mt.test("Java I Love you",2,9);
System.out.println(str);//输出va I Lo
第一条语句可转换为:
MyTest mt = String :: substring;


@FunctionalInterface
interface YourTest{
JFrame win(String title);
}

YourTest yt = (String a) -> new JFrame(a);
JFrame jf = yt.win("我的窗口");
System.out.println(jf);
第一条语句可转换为:
YourTest yt = JFrame :: new;

枚举类

规则:

  • 枚举类默认继承java.lang.Enum类,而不是Object类,因此枚举类不能显示继承其他父类。
  • 使用enum定义、非抽象的枚举类默认使用final修饰,因此枚举类不能派生子类。
  • 枚举类的构造器只能使用private访问控制符。
  • 枚举类的所有实例必须在枚举类的第一行显示列出,否则这个枚举类永远不能产生实例。
  • 枚举类的实例由系统自动添加public static final修饰,无须显示添加。
  • 枚举类默认提供一个value()方法,可以很方便地遍历所有的枚举值。

switch控制表达式使用枚举类型时,后面的case表达式中可以直接使用枚举类的名字,无须添加枚举类作为限定。

java.lang.Enum提供的部分方法:

  • int compareTo(E o):与指定枚举对象比较顺序,该枚举对象位于指定枚举对象之后返回正整数。
  • int ordinal():返回枚举值在枚举类中的索引值。第一个枚举值的索引值为0。
  • String toString():返回枚举常量的名称。
  • public static <T extends Enum> T valueOf(ClassenumType,String name):返回指定枚举类中指定名称的枚举值。

枚举类通常应设计为不可变类:

1
2
3
4
5
6
7
8
9
10
11
12
public enum Gender{
MALE("男"),FEMALE("女");
//等同于public static final Genter MALE = new Gender("男");
//等同于public static final Genter FEMALE = new Gender("女");
private final String name;
private Gender(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}

枚举类实现接口里的方法时,若需要每个枚举类呈现不同的行为方式,可以使用匿名内部类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public enum Genter implements GenderDesc{
MALE("男"){
public void info(){
//方法实现
}
},
FEMALE("女"){
public void info(){
//方法实现
}
};
//其他部分
}

枚举类里定义抽象方法时不能使用abstract关键字将枚举类定义为抽象类(因为系统会自动添加),且定义每个枚举值时必须为抽象方法提供实现。

对象与垃圾回收

垃圾回收特点:

  • 垃圾回收只负责回收堆内存中的对象,不会回收任何物理资源。(数据库连接、网络IO等)
  • 程序无法精确控制垃圾回收的进行,垃圾回收只会在合适的时候进行。
  • 在;垃圾回收机制回收任何对象之前,总会先调用它的finalize()方法,该方法可能使对象重新复活从而导致垃圾回收机制取消回收。

对象的三种状态:

  • 可达状态:有一个以上的引用变量引用它。
  • 可恢复状态:不再有任何引用变量引用它,系统准备调用其finalize()方法进行资源清理。
  • 不可达状态:系统已经调用finalize()方法仍没有使该对象变成可达状态。系统准备真正回收该对象所占有的资源。

强制垃圾回收:通知系统进行回收,但系统听不听话依然不确定。

两种方法:

  • 调用System类的gc()静态方法:System.gc()
  • 调用Runtime类的gc()实例方法:Runtime.getRuntime().gc()

finalize()方法的特点:

  • 永远不要主动调用某个对象的finalize()方法,该方法应交给垃圾回收机制调用。
  • 不要把finalize()当成一定会执行的方法。
  • 当JVM执行finalize()方法出现异常时,垃圾回收机制不会报告异常,程序继续进行。
  • 可通过System.runFinalization()方法强制垃圾回收机制调用可恢复对象的finalize()方法。

对象的引用方式:

  • 强引用:一般方式。
  • 软引用:用SoftReference类实现,当一个对象只有软引用时且系统内存足够时,它不会被回收;当系统内存不足时,系统可能会回收它。
  • 弱引用:用WeakReference类实现,当一个对象只有弱引用时,当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所占用的内存。
  • 虚引用:用PhantomReference类实现,完全类似于没有引用,对象感受不到虚引用的存在。虚引用必须和引用队列ReferenceQueue联合使用,主要用于跟踪对象被垃圾回收的状态。
-------------本文结束感谢您的阅读-------------
undefined