What is Java ?(二)

内容:拷贝泛型对象反射

本篇博客是笔者作为初学者记录自己对Java一些基本概念的理解。内容参考了大量网络资源,篇幅很长,旨在作为个人学习笔记,供自己日后回顾和复习。

拷贝

浅拷贝和深拷贝的区别

浅拷贝(Shallow Copy)

浅拷贝复制了一个对实例对象的引用,因此在内存中这两个对象指向的是同一个对象。换句话说,浅拷贝只复制了对象的引用,而不是对象本身。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ShallowCopyExample {
int value;

public ShallowCopyExample(int value) {
this.value = value;
}

public static void main(String[] args) {
ShallowCopyExample original = new ShallowCopyExample(10);
ShallowCopyExample copy = original; // 浅拷贝

System.out.println("Original: " + original.value); // 输出: Original: 10
System.out.println("Copy: " + copy.value); // 输出: Copy: 10

copy.value = 20;

System.out.println("Original after modification: " + original.value); // 输出: Original after modification: 20
System.out.println("Copy after modification: " + copy.value); // 输出: Copy after modification: 20
}
}

解释:originalcopy 指向同一个对象,因此修改 copy 的值也会影响 original

深拷贝(Deep Copy)

深拷贝新建一个一模一样的实例对象,两个对象的引用指向的是不同的地址。换句话说,深拷贝复制了对象本身,而不是对象的引用。

示例代码

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
class DeepCopyExample implements Cloneable {
int value;

public DeepCopyExample(int value) {
this.value = value;
}

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}

public static void main(String[] args) throws CloneNotSupportedException {
DeepCopyExample original = new DeepCopyExample(10);
DeepCopyExample copy = (DeepCopyExample) original.clone(); // 深拷贝

System.out.println("Original: " + original.value); // 输出: Original: 10
System.out.println("Copy: " + copy.value); // 输出: Copy: 10

copy.value = 20;

System.out.println("Original after modification: " + original.value); // 输出: Original after modification: 10
System.out.println("Copy after modification: " + copy.value); // 输出: Copy after modification: 20
}
}

解释:originalcopy 指向不同的对象,因此修改 copy 的值不会影响 original

图示

浅拷贝和深拷贝

实现深拷贝的方法

常用的深拷贝方法有三种:

  1. 实现 Cloneable 接口的 clone 方法
  2. 使用序列化和反序列化
  3. 手写递归复制

1. 实现 Cloneable 接口的 clone 方法

通过实现 Cloneable 接口并重写 clone 方法,可以实现深拷贝。需要注意的是,clone 方法默认是浅拷贝,因此需要手动处理对象的深拷贝。

示例代码

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
class DeepCopyExample implements Cloneable {
int value;
List<String> list;

public DeepCopyExample(int value, List<String> list) {
this.value = value;
this.list = list;
}

@Override
protected Object clone() throws CloneNotSupportedException {
DeepCopyExample cloned = (DeepCopyExample) super.clone();
cloned.list = new ArrayList<>(this.list); // 深拷贝列表
return cloned;
}

public static void main(String[] args) throws CloneNotSupportedException {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");

DeepCopyExample original = new DeepCopyExample(10, list);
DeepCopyExample copy = (DeepCopyExample) original.clone();

System.out.println("Original: " + original.value + ", " + original.list); // 输出: Original: 10, [A, B]
System.out.println("Copy: " + copy.value + ", " + copy.list); // 输出: Copy: 10, [A, B]

copy.value = 20;
copy.list.add("C");

System.out.println("Original after modification: " + original.value + ", " + original.list); // 输出: Original after modification: 10, [A, B]
System.out.println("Copy after modification: " + copy.value + ", " + copy.list); // 输出: Copy after modification: 20, [A, B, C]
}
}

2. 使用序列化和反序列化

通过将对象序列化为字节流,然后再反序列化为新的对象,可以实现深拷贝。这种方法适用于实现了 Serializable 接口的对象。

示例代码

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

class DeepCopyExample implements Serializable {
int value;
List<String> list;

public DeepCopyExample(int value, List<String> list) {
this.value = value;
this.list = list;
}

public Object deepCopy() throws IOException, ClassNotFoundException {
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();
}

public static void main(String[] args) throws IOException, ClassNotFoundException {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");

DeepCopyExample original = new DeepCopyExample(10, list);
DeepCopyExample copy = (DeepCopyExample) original.deepCopy();

System.out.println("Original: " + original.value + ", " + original.list); // 输出: Original: 10, [A, B]
System.out.println("Copy: " + copy.value + ", " + copy.list); // 输出: Copy: 10, [A, B]

copy.value = 20;
copy.list.add("C");

System.out.println("Original after modification: " + original.value + ", " + original.list); // 输出: Original after modification: 10, [A, B]
System.out.println("Copy after modification: " + copy.value + ", " + copy.list); // 输出: Copy after modification: 20, [A, B, C]
}
}

3. 手写递归复制

通过手写递归方法,逐层复制对象的每个字段,可以实现深拷贝。这种方法适用于任何对象,但需要手动处理每个字段的复制。

示例代码

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
import java.util.ArrayList;
import java.util.List;

class DeepCopyExample {
int value;
List<String> list;

public DeepCopyExample(int value, List<String> list) {
this.value = value;
this.list = list;
}

public DeepCopyExample deepCopy() {
List<String> newList = new ArrayList<>(this.list);
return new DeepCopyExample(this.value, newList);
}

public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");

DeepCopyExample original = new DeepCopyExample(10, list);
DeepCopyExample copy = original.deepCopy();

System.out.println("Original: " + original.value + ", " + original.list); // 输出: Original: 10, [A, B]
System.out.println("Copy: " + copy.value + ", " + copy.list); // 输出: Copy: 10, [A, B]

copy.value = 20;
copy.list.add("C");

System.out.println("Original after modification: " + original.value + ", " + original.list); // 输出: Original after modification: 10, [A, B]
System.out.println("Copy after modification: " + copy.value + ", " + copy.list); // 输出: Copy after modification: 20, [A, B, C]
}
}

泛型

什么是泛型?

泛型是 Java 语言中的一个重要特性,它允许类、接口和方法在定义时使用一个或多个类型参数,而这些参数在实际运行时才会指定具体的参数类型。泛型提供了一种在编译时进行类型检查的机制,从而提高代码的类型安全性和可读性。

为什么需要泛型?

泛型的主要目的是提高代码的类型安全性和可读性,具体体现在以下几个方面:

  1. 适用于多种类型执行相同的代码

    • 泛型允许在定义类、接口和方法时使用类型参数,从而可以在不同的类型上执行相同的代码。
  2. 类型安全

    • 泛型在编译时进行类型检查,确保类型的一致性,避免运行时类型转换错误。
    • 使用泛型可以减少强制类型转换的需求,提高代码的可读性和安全性。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.ArrayList;
import java.util.List;

public class NonGenericExample {
public static void main(String[] args) {
// 不使用泛型,列表中可以存储任意类型的对象
List nonGenericList = new ArrayList();
nonGenericList.add("Hello");
nonGenericList.add(123); // 可以添加不同类型的对象

// 需要强制类型转换,容易引发类型转换错误
String firstElement = (String) nonGenericList.get(0);
System.out.println(firstElement); // 输出: Hello

// 运行时类型转换错误
Integer secondElement = (Integer) nonGenericList.get(1);
System.out.println(secondElement); // 输出: 123

// 运行时类型转换错误
// String thirdElement = (String) nonGenericList.get(1); // 运行时错误:ClassCastException
}
}

通过这些特性,泛型在 Java 编程中提供了更强大的类型检查和代码复用能力,使得代码更加健壮和易于维护。

对象

创建对象的方式

  1. 使用new关键字

    这是最常见和最直接的对象创建方式。通过调用类的构造函数来实例化对象。

    示例:

    1
    MyClass obj = new MyClass();
  2. 使用clone方法

    通过调用对象的clone方法来创建对象的副本。需要注意的是,类必须实现Cloneable接口。

    示例:

    1
    MyClass obj2 = (MyClass) obj.clone();
  3. 使用反序列化

    通过将对象序列化为字节流,然后再反序列化来创建对象。这通常用于对象的持久化存储和传输。

    示例:

    1
    2
    3
    4
    5
    try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj"))) {
    MyClass obj3 = (MyClass) in.readObject();
    } catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
    }
  4. 使用Class类的newInstance方法

    通过调用Class类的newInstance方法来创建对象。需要注意的是,该方法要求类具有无参构造函数。

    示例:

    1
    2
    3
    4
    5
    try {
    MyClass obj4 = MyClass.class.newInstance();
    } catch (InstantiationException | IllegalAccessException e) {
    e.printStackTrace();
    }
  5. 使用ConstructornewInstance方法

    通过调用Constructor类的newInstance方法来创建对象。这种方式允许使用带参数的构造函数。

    示例:

    1
    2
    3
    4
    5
    6
    try {
    Constructor<MyClass> constructor = MyClass.class.getConstructor(String.class);
    MyClass obj5 = constructor.newInstance("exampleParameter");
    } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
    e.printStackTrace();
    }

new的对象什么时候回收?

通过关键字new创建的对象,由Java的垃圾回收器(Garbage Collector, GC)负责回收。垃圾回收器的工作是在程序运行过程中自动进行的,它会周期性地检测不再被引用的对象,并将其回收以释放内存。

回收时机

Java对象的回收时机是由垃圾回收器根据一些算法来决定的,主要有以下几种情况:

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

流程如下:

对象回收流程

反射

什么是反射?

Java反射机制是指在运行状态中,任意一个类都能知道这个类中的所有方法和属性,并且任意对象都能调用这个类对象的属性和方法。这种动态获取的信息和调用对象方法的功能称为Java的反射机制。

反射机制如图所示:

反射机制(图片来源小林codin)

  1. 动态性

    • 反射允许在运行时动态地获取类的完整结构信息,包括类名、父类、方法和属性等,并调用其方法(包括私有方法),而不需要在编译时确定。
    • 示例:通过Class.forName()方法动态加载类,并使用getMethods()getFields()等方法获取类的结构信息。
    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
    import java.lang.reflect.Method;
    import java.lang.reflect.Field;

    public class DynamicReflectionExample {
    public static void main(String[] args) {
    try {
    // 动态加载类
    Class<?> clazz = Class.forName("com.example.MyClass");

    // 获取类的所有方法
    Method[] methods = clazz.getDeclaredMethods();
    for (Method method : methods) {
    System.out.println("Method: " + method.getName());
    }

    // 获取类的所有字段
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
    System.out.println("Field: " + field.getName());
    }

    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    }
    }
    }
  2. 灵活性

    • 反射提供了灵活的编程方式,可以在运行时根据需要创建对象、调用方法和访问属性。
    • 示例:使用反射API动态地创建对象实例,即使在编译时不知道具体的类名。这是通过Class类的newInstance()方法或Constructor对象的newInstance()方法实现的。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import java.lang.reflect.Constructor;

    public class FlexibleReflectionExample {
    public static void main(String[] args) {
    try {
    // 动态加载类
    Class<?> clazz = Class.forName("com.example.MyClass");

    // 使用无参构造函数创建对象实例
    Object obj = clazz.getDeclaredConstructor().newInstance();

    // 使用带参构造函数创建对象实例
    Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);
    Object objWithParam = constructor.newInstance("Parameter");

    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }
  3. 访问私有成员

    • 反射可以访问类的私有成员(如私有方法和私有属性),这在正常情况下是不允许的。
    • 示例:通过Field类的setAccessible(true)方法绕过访问控制,使用get()set()方法访问和修改私有字段的值。
    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.lang.reflect.Field;
    import java.lang.reflect.Method;

    public class PrivateAccessReflectionExample {
    public static void main(String[] args) {
    try {
    // 动态加载类
    Class<?> clazz = Class.forName("com.example.MyClass");

    // 创建对象实例
    Object obj = clazz.getDeclaredConstructor().newInstance();

    // 访问私有字段
    Field privateField = clazz.getDeclaredField("privateField");
    privateField.setAccessible(true);
    privateField.set(obj, "New Value");
    System.out.println("Private Field Value: " + privateField.get(obj));

    // 调用私有方法
    Method privateMethod = clazz.getDeclaredMethod("privateMethod");
    privateMethod.setAccessible(true);
    privateMethod.invoke(obj);

    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }

反射常见应用场景

  1. 框架开发:许多框架(如Spring、Hibernate)使用反射来动态加载和配置类,实现依赖注入和AOP(面向切面编程)等功能。

  2. 序列化和反序列化:在对象的序列化和反序列化过程中,反射用于动态地访问和设置对象的属性。

  3. 动态代理:反射与动态代理结合使用,可以在运行时创建代理对象,实现方法的拦截和增强。

  4. 单元测试:单元测试框架(如JUnit)使用反射来动态地发现和执行测试方法。

  5. 插件化系统:反射用于动态加载和执行插件,使得系统具有扩展性和灵活性。

  6. ORM框架:ORM(对象关系映射)框架使用反射来将数据库表映射到Java对象,并动态地生成SQL语句。

反射的优缺点

优点

  • 灵活性:反射提供了极大的灵活性,允许在运行时动态地操作类和对象。
  • 扩展性:反射使得系统具有更好的扩展性,可以通过插件或配置文件动态加载和执行代码。

缺点

  • 性能开销:反射操作通常比直接调用方法或访问属性要慢,因为它涉及到动态解析和安全检查。
  • 安全风险:反射可以访问和修改类的私有成员,这可能会导致安全问题。
  • 代码可读性:反射代码通常比直接调用方法或访问属性的代码更复杂,可读性较差。