Java八股(对象)

创建对象

使用 new 关键字创建

使用new关键字:这是最常见、最基础的创建对象方式。通过调用类的构造器来实例化对象。
这种创建对象的方式要求我们必须知道对象所属类的名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 定义一个类
public class Person {
private String name;

public Person() {} // 默认构造器
public Person(String name) { // 带参构造器
this.name = name;
}

public void sayHello() {
System.out.println("Hello, " + name);
}
}

// 使用 new 创建对象
public class Main {
public static void main(String[] args) {
Person person1 = new Person(); // 调用无参构造
Person person2 = new Person("Alice"); // 调用有参构造

person2.sayHello(); // 输出: Hello, Alice
}
}

使用Class类的newInstance()方法

这是利用了Java的反射API,在运行的时候动态的创建对象,在这种方式下我们不需要知道具体的类。

应用场景:框架设计(如 Spring 的 IOC 容器)、动态代理等。

使用Constructor类的newInstance()方法

同样是通过反射机制,可以使用Constructor类的newInstance()方法创建对象。

1
2
Constructor<MyClass> constructor = MyClass.class.getConstructor();
MyClass obj = constructor.newInstance();

使用clone()方法:

通过实现 Cloneable 接口并重写 Object 类的 clone() 方法,可以基于一个现有对象(原型)创建一个新的副本对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 实现 Cloneable 接口
public class Person implements Cloneable {
private String name;

// ... 构造器和其他方法 ...

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); // 浅拷贝
}
}

public class Main {
public static void main(String[] args) {
Person original = new Person("Charlie");
try {
Person copy = (Person) original.clone(); // 创建副本
copy.sayHello(); // 输出: Hello, Charlie
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}

Object.clone() 默认是浅拷贝,对于引用类型的字段,复制的是引用地址,而不是引用的对象本身。如果需要深拷贝,必须在 clone() 方法中手动对引用对象进行克隆。

使用反序列化

通过 ObjectInputStream 从一个字节流(通常是文件或网络)中重建一个对象。

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
import java.io.*;

// 必须实现 Serializable 接口
public class Person implements Serializable {
private String name;
// ... 构造器和其他方法 ...
}

public class Main {
public static void main(String[] args) {
Person personToSave = new Person("David");

// 序列化对象到文件
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.dat"))) {
oos.writeObject(personToSave);
} catch (IOException e) {
e.printStackTrace();
}

// 从文件反序列化对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.dat"))) {
Person restoredPerson = (Person) ois.readObject(); // 创建新对象
restoredPerson.sayHello(); // 输出: Hello, David
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}

特点是不会调用类的任何构造器,类必须实现 java.io.Serializable 接口。

使用工厂模式

这是一种设计模式,不直接使用 new,而是通过一个方法来返回对象实例。 getInstance()valueOf() 等都是常见的工厂方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Person {
private String name;

private Person(String name) { // 构造器可以是私有的
this.name = name;
}

// 静态工厂方法
public static Person createPerson(String name) {
// 这里可以做一些额外的逻辑,比如缓存、日志、返回子类实例等
return new Person(name);
}
}

public class Main {
public static void main(String[] args) {
// 不是用 new,而是用工厂方法创建
Person person = Person.createPerson("Eva");
person.sayHello();
}
}

优点是将对象的创建与使用分离,降低耦合,还可以隐藏创建对象的复杂逻辑(如池化技术、缓存)。

Java 标准库中的例子:Integer.valueOf(int)Calendar.getInstance()

New出的对象什么时候回收?

由Java的垃圾回收器(Garbage Collector)负责回收,它会周期性地检测不再被引用的对象,并将其回收释放内存。

垃圾回收器算法:

  1. 引用计数法:某个对象的引用计数为0时,表示该对象不再被引用,可以被回收。
  2. 可达性分析算法:从根对象(如方法区中的类静态属性、方法中的局部变量等)出发,通过对象之间的引用链进行遍历,如果存在一条引用链到达某个对象,则说明该对象是可达的,反之不可达,不可达的对象将被回收。
  3. 终结器(Finalizer):如果对象重写了finalize()方法,垃圾回收器会在回收该对象之前调用finalize()方法,对象可以在finalize()方法中进行一些清理操作。然而,终结器机制的使用不被推荐,因为它的执行时间是不确定的,可能会导致不可预测的性能问题。

私有对象

私有对象通常指的是类中被声明为 private 的成员变量或方法。由于 private 访问修饰符的限制,这些成员只能在其所在的类内部被访问。

可以通过下面两种方式来间接获取私有对象。

通过公共访问器方法访问私有对象

如果类的设计者遵循良好的编程规范,通常会为私有成员变量提供公共的访问器方法(即 getter 方法),通过调用这些方法可以安全地获取私有对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MyClass {
// 私有成员变量
private String privateField = "私有字段的值";

// 公共的 getter 方法
public String getPrivateField() {
return privateField;
}
}

public class Main {
public static void main(String[] args) {
MyClass obj = new MyClass();
// 通过调用 getter 方法获取私有对象
String value = obj.getPrivateField();
System.out.println(value);
}
}

使用反射机制访问私有对象

反射机制允许在运行时检查和修改类、方法、字段等信息,通过反射可以绕过 private 访问修饰符的限制来获取私有对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.lang.reflect.Field;

class MyClass {
private String privateField = "私有字段的值";
}

public class Main {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
MyClass obj = new MyClass();
// 获取 Class 对象
Class<?> clazz = obj.getClass();
// 获取私有字段
Field privateField = clazz.getDeclaredField("privateField");
// 设置可访问性
privateField.setAccessible(true);
// 获取私有字段的值
String value = (String) privateField.get(obj);
System.out.println(value);
}
}