Java基础
本文最后更新于 414 天前,其中的信息可能已经有所发展或是发生改变。

1.数据类型的分类

*引用数据类型(除基本数据类型之外的,如String)

*基本数据类型:4大类8种

数据类型定义变量的几个补充知识点

⚫ 随便写一个整数字面值,默认是int类型的,如果希望随便写一个整数默认是long型的必须在数据后加L或者l表示。

⚫ 随便写一个小数字面值,默认是double类型的,如果希望这个小数是float类型的,必须在数据后加F或者f表示。

Java 基础

关键字

final

1. 数据

声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。

  • 对于基本类型,final 使数值不变;
  • 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。
 final int x = 1;
 // x = 2; // cannot assign value to final variable 'x'
 final A y = new A();
 y.a = 1;

2. 方法

声明方法不能被子类重写。

private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。

3. 类

声明类不允许被继承。

static

1. 静态变量

  • 静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份。
  • 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
 public class A {
 ​
    private int x;         // 实例变量
    private static int y; // 静态变量
 ​
    public static void main(String[] args) {
        // int x = A.x; // Non-static field 'x' cannot be referenced from a static context
        A a = new A();
        int x = a.x;
        int y = A.y;
    }
 }

2. 静态方法

静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法。

 public abstract class A {
    public static void func1(){
    }
    // public abstract static void func2(); // Illegal combination of modifiers: 'abstract' and 'static'
 }

只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字,因为这两个关键字与具体对象关联。

 public class A {
 ​
    private static int x;
    private int y;
 ​
    public static void func1(){
        int a = x;
        // int b = y; // Non-static field 'y' cannot be referenced from a static context
        // int b = this.y;     // 'A.this' cannot be referenced from a static context
    }
 }

3. 静态语句块

静态语句块在类初始化时运行一次。

 public class A {
     static {
         System.out.println("123");
     }
 ​
     public static void main(String[] args) {
         A a1 = new A();
         A a2 = new A();
     }
 }

4. 静态内部类

非静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。而静态内部类不需要。

 public class OuterClass {
 ​
    class InnerClass {
    }
 ​
    static class StaticInnerClass {
    }
 ​
    public static void main(String[] args) {
        // InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context
        OuterClass outerClass = new OuterClass();
        InnerClass innerClass = outerClass.new InnerClass();
        StaticInnerClass staticInnerClass = new StaticInnerClass();
    }
 }

静态内部类不能访问外部类的非静态的变量和方法。

5. 静态导包

在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。

 import static com.xxx.ClassName.*

6. 初始化顺序

静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。

 public static String staticField = "静态变量";
 static {
    System.out.println("静态语句块");
 }
 public String field = "实例变量";
 {
    System.out.println("普通语句块");
 }

最后才是构造函数的初始化。

 public InitialOrderTest() {
    System.out.println("构造函数");
 }

存在继承的情况下,初始化顺序为:

  • 父类(静态变量、静态语句块)
  • 子类(静态变量、静态语句块)
  • 父类(实例变量、普通语句块)
  • 父类(构造函数)
  • 子类(实例变量、普通语句块)
  • 子类(构造函数)

Object 通用方法

概览

 ​
 public native int hashCode()
 ​
 public boolean equals(Object obj)
 ​
 protected native Object clone() throws CloneNotSupportedException
 ​
 public String toString()
 ​
 public final native Class<?> getClass()
 ​
 protected void finalize() throws Throwable {}
 ​
 public final native void notify()
 ​
 public final native void notifyAll()
 ​
 public final native void wait(long timeout) throws InterruptedException
 ​
 public final void wait(long timeout, int nanos) throws InterruptedException
 ​
 public final void wait() throws InterruptedException

equals()

1. 等价关系

两个对象具有等价关系,需要满足以下五个条件:

Ⅰ 自反性

 x.equals(x); // true

Ⅱ 对称性

 x.equals(y) == y.equals(x); // true

Ⅲ 传递性

 if (x.equals(y) && y.equals(z))
    x.equals(z); // true;

Ⅳ 一致性

多次调用 equals() 方法结果不变

 x.equals(y) == x.equals(y); // true

Ⅴ 与 null 的比较

对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false

 x.equals(null); // false;

2. 等价与相等

  • 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。
  • 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。
 Integer x = new Integer(1);
 Integer y = new Integer(1);
 System.out.println(x.equals(y)); // true
 System.out.println(x == y);     // false

3. 实现

  • 检查是否为同一个对象的引用,如果是直接返回 true;
  • 检查是否是同一个类型,如果不是,直接返回 false;
  • 将 Object 对象进行转型;
  • 判断每个关键域是否相等。
 public class EqualExample {
 ​
    private int x;
    private int y;
    private int z;
 ​
    public EqualExample(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
 ​
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
 ​
        EqualExample that = (EqualExample) o;
 ​
        if (x != that.x) return false;
        if (y != that.y) return false;
        return z == that.z;
    }
 }

ashCode()

hashCode() 返回哈希值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价,这是因为计算哈希值具有随机性,两个值不同的对象可能计算出相同的哈希值。

在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象哈希值也相等。

HashSet 和 HashMap 等集合类使用了 hashCode() 方法来计算对象应该存储的位置,因此要将对象添加到这些集合类中,需要让对应的类实现 hashCode() 方法。

下面的代码中,新建了两个等价的对象,并将它们添加到 HashSet 中。我们希望将这两个对象当成一样的,只在集合中添加一个对象。但是 EqualExample 没有实现 hashCode() 方法,因此这两个对象的哈希值是不同的,最终导致集合添加了两个等价的对象。

 EqualExample e1 = new EqualExample(1, 1, 1);
 EqualExample e2 = new EqualExample(1, 1, 1);
 System.out.println(e1.equals(e2)); // true
 HashSet<EqualExample> set = new HashSet<>();
 set.add(e1);
 set.add(e2);
 System.out.println(set.size());   // 2

理想的哈希函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的哈希值上。这就要求了哈希函数要把所有域的值都考虑进来。可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。

R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位,最左边的位丢失。并且一个数与 31 相乘可以转换成移位和减法:31*x == (x<<5)-x,编译器会自动进行这个优化。

 @Override
 public int hashCode() {
    int result = 17;
    result = 31 * result + x;
    result = 31 * result + y;
    result = 31 * result + z;
    return result;
 }

toString()

默认返回 ToStringExample@4554617c 这种形式,其中 @ 后面的数值为散列码的无符号十六进制表示。

 public class ToStringExample {
 ​
     private int number;
 ​
     public ToStringExample(int number) {
         this.number = number;
     }
 }
 ToStringExample example = new ToStringExample(123);
 System.out.println(example.toString());
 ToStringExample@4554617c

clone()

1. cloneable

clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。

 public class CloneExample {
    private int a;
    private int b;
 }
 CloneExample e1 = new CloneExample();
 // CloneExample e2 = e1.clone(); // 'clone()' has protected access in 'java.lang.Object'

重写 clone() 得到以下实现:

以上抛出了 CloneNotSupportedException,这是因为 CloneExample 没有实现 Cloneable 接口。

应该注意的是,clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。

 public class CloneExample implements Cloneable {
    private int a;
    private int b;
 ​
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
 }

2. 浅拷贝

拷贝对象和原始对象的引用类型引用同一个对象。

 public class ShallowCloneExample implements Cloneable {
 ​
    private int[] arr;
 ​
    public ShallowCloneExample() {
        arr = new int[10];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }
    }
 ​
    public void set(int index, int value) {
        arr[index] = value;
    }
 ​
    public int get(int index) {
        return arr[index];
    }
 ​
    @Override
    protected ShallowCloneExample clone() throws CloneNotSupportedException {
        return (ShallowCloneExample) super.clone();
    }
 }
 ShallowCloneExample e1 = new ShallowCloneExample();
 ShallowCloneExample e2 = null;
 try {
    e2 = e1.clone();
 } catch (CloneNotSupportedException e) {
    e.printStackTrace();
 }
 e1.set(2, 222);
 System.out.println(e2.get(2)); // 222

3. 深拷贝

拷贝对象和原始对象的引用类型引用不同对象。

 public class DeepCloneExample implements Cloneable {
 ​
    private int[] arr;
 ​
    public DeepCloneExample() {
        arr = new int[10];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }
    }
 ​
    public void set(int index, int value) {
        arr[index] = value;
    }
 ​
    public int get(int index) {
        return arr[index];
    }
 ​
    @Override
    protected DeepCloneExample clone() throws CloneNotSupportedException {
        DeepCloneExample result = (DeepCloneExample) super.clone();
        result.arr = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            result.arr[i] = arr[i];
        }
        return result;
    }
 }
 DeepCloneExample e1 = new DeepCloneExample();
 DeepCloneExample e2 = null;
 try {
    e2 = e1.clone();
 } catch (CloneNotSupportedException e) {
    e.printStackTrace();
 }
 e1.set(2, 222);
 System.out.println(e2.get(2)); // 2

4. clone() 的替代方案

使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。

 public class CloneConstructorExample {
 ​
    private int[] arr;
 ​
    public CloneConstructorExample() {
        arr = new int[10];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }
    }
 ​
    public CloneConstructorExample(CloneConstructorExample original) {
        arr = new int[original.arr.length];
        for (int i = 0; i < original.arr.length; i++) {
            arr[i] = original.arr[i];
        }
    }
 ​
    public void set(int index, int value) {
        arr[index] = value;
    }
 ​
    public int get(int index) {
        return arr[index];
    }
 }
 CloneConstructorExample e1 = new CloneConstructorExample();
 CloneConstructorExample e2 = new CloneConstructorExample(e1);
 e1.set(2, 222);
 System.out.println(e2.get(2)); // 2

六、继承

访问权限

Java 中有三个访问权限修饰符:private、protected 以及 public,如果不加访问修饰符,表示包级可见。

可以对类或类中的成员(字段和方法)加上访问修饰符。

  • 类可见表示其它类可以用这个类创建实例对象。
  • 成员可见表示其它类可以用这个类的实例对象访问到该成员;

protected 用于修饰成员,表示在继承体系中成员对于子类可见,但是这个访问修饰符对于类没有意义。

设计良好的模块会隐藏所有的实现细节,把它的 API 与它的实现清晰地隔离开来。模块之间只通过它们的 API 进行通信,一个模块不需要知道其他模块的内部工作情况,这个概念被称为信息隐藏或封装。因此访问权限应当尽可能地使每个类或者成员不被外界访问。

如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例去代替,也就是确保满足里氏替换原则。

字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。例如下面的例子中,AccessExample 拥有 id 公有字段,如果在某个时刻,我们想要使用 int 存储 id 字段,那么就需要修改所有的客户端代码。

 public class AccessExample {
    public String id;
 }

可以使用公有的 getter 和 setter 方法来替换公有字段,这样的话就可以控制对字段的修改行为。

 public class AccessExample {
 ​
    private int id;
 ​
    public String getId() {
        return id + "";
    }
 ​
    public void setId(String id) {
        this.id = Integer.valueOf(id);
    }
 }

但是也有例外,如果是包级私有的类或者私有的嵌套类,那么直接暴露成员不会有特别大的影响。

 public class AccessWithInnerClassExample {
 ​
    private class InnerClass {
        int x;
    }
 ​
    private InnerClass innerClass;
 ​
    public AccessWithInnerClassExample() {
        innerClass = new InnerClass();
    }
 ​
    public int getValue() {
        return innerClass.x; // 直接访问
    }
 }

抽象类与接口

1. 抽象类

抽象类和抽象方法都使用 abstract 关键字进行声明。如果一个类中包含抽象方法,那么这个类必须声明为抽象类。

抽象类和普通类最大的区别是,抽象类不能被实例化,只能被继承。

 public abstract class AbstractClassExample {
 ​
     protected int x;
     private int y;
 ​
     public abstract void func1();
 ​
     public void func2() {
         System.out.println("func2");
     }
 }
 public class AbstractExtendClassExample extends AbstractClassExample {
     @Override
     public void func1() {
         System.out.println("func1");
     }
 }
 // AbstractClassExample ac1 = new AbstractClassExample(); // 'AbstractClassExample' is abstract; cannot be instantiated
 AbstractClassExample ac2 = new AbstractExtendClassExample();
 ac2.func1();

2. 接口

接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。

从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类,让它们都实现新增的方法。

接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。从 Java 9 开始,允许将方法定义为 private,这样就能定义某些复用的代码又不会把方法暴露出去。

接口的字段默认都是 static 和 final 的。

 public interface InterfaceExample {
 ​
     void func1();
 ​
     default void func2(){
         System.out.println("func2");
     }
 ​
     int x = 123;
     // int y;               // Variable 'y' might not have been initialized
     public int z = 0;       // Modifier 'public' is redundant for interface fields
     // private int k = 0;   // Modifier 'private' not allowed here
     // protected int l = 0; // Modifier 'protected' not allowed here
     // private void fun3(); // Modifier 'private' not allowed here
 }
 public class InterfaceImplementExample implements InterfaceExample {
     @Override
     public void func1() {
         System.out.println("func1");
     }
 }
 // InterfaceExample ie1 = new InterfaceExample(); // 'InterfaceExample' is abstract; cannot be instantiated
 InterfaceExample ie2 = new InterfaceImplementExample();
 ie2.func1();
 System.out.println(InterfaceExample.x);

3. 比较

  • 从设计层面上看,抽象类提供了一种 IS-A 关系,需要满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。
  • 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。
  • 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。
  • 接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。

4. 使用选择

使用接口:

  • 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Comparable 接口中的 compareTo() 方法;
  • 需要使用多重继承。

使用抽象类:

  • 需要在几个相关的类中共享代码。
  • 需要能控制继承来的成员的访问权限,而不是都为 public。
  • 需要继承非静态和非常量字段。

在很多情况下,接口优先于抽象类。因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。

super

  • 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。应该注意到,子类一定会调用父类的构造函数来完成初始化工作,一般是调用父类的默认构造函数,如果子类需要调用父类其它构造函数,那么就可以使用 super() 函数。
  • 访问父类的成员:如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。
 public class SuperExample {
 ​
     protected int x;
     protected int y;
 ​
     public SuperExample(int x, int y) {
         this.x = x;
         this.y = y;
     }
 ​
     public void func() {
         System.out.println("SuperExample.func()");
     }
 }
 public class SuperExtendExample extends SuperExample { ​
     private int z;
 ​
     public SuperExtendExample(int x, int y, int z) {
         super(x, y);
         this.z = z;
     }
 ​
     @Override
     public void func() {
         super.func();
         System.out.println("SuperExtendExample.func()");
     }
 }
     private int z;
 ​
     public SuperExtendExample(int x, int y, int z) {
         super(x, y);
         this.z = z;
     }
 ​
     @Override
     public void func() {
         super.func();
         System.out.println("SuperExtendExample.func()");
     }
 }




 SuperExample e = new SuperExtendExample(1, 2, 3);
 e.func();
 SuperExample.func()
 SuperExtendExample.func()

Using the Keyword super

重写与重载

1. 重写(Override)

存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。

为了满足里式替换原则,重写有以下三个限制:

  • 子类方法的访问权限必须大于等于父类方法;
  • 子类方法的返回类型必须是父类方法返回类型或为其子类型。
  • 子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型。

使用 @Override 注解,可以让编译器帮忙检查是否满足上面的三个限制条件。

下面的示例中,SubClass 为 SuperClass 的子类,SubClass 重写了 SuperClass 的 func() 方法。其中:

  • 子类方法访问权限为 public,大于父类的 protected。
  • 子类的返回类型为 ArrayList<Integer>,是父类返回类型 List<Integer> 的子类。
  • 子类抛出的异常类型为 Exception,是父类抛出异常 Throwable 的子类。
  • 子类重写方法使用 @Override 注解,从而让编译器自动检查是否满足限制条件。
 class SuperClass {
    protected List<Integer> func() throws Throwable {
        return new ArrayList<>();
    }
 }
 ​
 class SubClass extends SuperClass {
    @Override
    public ArrayList<Integer> func() throws Exception {
        return new ArrayList<>();
    }
 }

在调用一个方法时,先从本类中查找看是否有对应的方法,如果没有再到父类中查看,看是否从父类继承来。否则就要对参数进行转型,转成父类之后看是否有对应的方法。总的来说,方法调用的优先级为:

  • this.func(this)
  • super.func(this)
  • this.func(super)
  • super.func(super)
class A {

    public void show(A obj) {
        System.out.println("A.show(A)");
    }

    public void show(C obj) {
        System.out.println("A.show(C)");
    }
}

class B extends A {

    @Override
    public void show(A obj) {
        System.out.println("B.show(A)");
    }
}

class C extends B {
}

class D extends C {
}
public static void main(String[] args) {

    A a = new A();
    B b = new B();
    C c = new C();
    D d = new D();

    // 在 A 中存在 show(A obj),直接调用
    a.show(a); // A.show(A)
    // 在 A 中不存在 show(B obj),将 B 转型成其父类 A
    a.show(b); // A.show(A)
    // 在 B 中存在从 A 继承来的 show(C obj),直接调用
    b.show(c); // A.show(C)
    // 在 B 中不存在 show(D obj),但是存在从 A 继承来的 show(C obj),将 D 转型成其父类 C
    b.show(d); // A.show(C)

    // 引用的还是 B 对象,所以 ba 和 b 的调用结果一样
    A ba = new B();
    ba.show(c); // A.show(C)
    ba.show(d); // A.show(C)
}

2. 重载(Overload)

存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。

应该注意的是,返回值不同,其它都相同不算是重载。

 class OverloadingExample {
     public void show(int x) {
         System.out.println(x);
     }
 ​
     public void show(int x, String y) {
         System.out.println(x + " " + y);
     }
 }
 public static void main(String[] args) {
     OverloadingExample example = new OverloadingExample();
     example.show(1);
     example.show(1, "2");
 }

反射

每个类都有一个 Class 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。

类加载相当于 Class 对象的加载,类在第一次使用时才动态加载到 JVM 中。也可以使用 Class.forName("com.mysql.jdbc.Driver") 这种方式来控制类的加载,该方法会返回一个 Class 对象。

反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。

Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect 类库主要包含了以下三个类:

  • Field :可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段;
  • Method :可以使用 invoke() 方法调用与 Method 对象关联的方法;
  • Constructor :可以用 Constructor 的 newInstance() 创建新的对象。

反射的优点:

  • 可扩展性 :应用程序可以利用全限定名创建可扩展对象的实例,来使用来自外部的用户自定义类。
  • 类浏览器和可视化开发环境 :一个类浏览器需要可以枚举类的成员。可视化开发环境(如 IDE)可以从利用反射中可用的类型信息中受益,以帮助程序员编写正确的代码。
  • 调试器和测试工具 : 调试器需要能够检查一个类里的私有成员。测试工具可以利用反射来自动地调用类里定义的可被发现的 API 定义,以确保一组测试中有较高的代码覆盖率。

反射的缺点:

尽管反射非常强大,但也不能滥用。如果一个功能可以不用反射完成,那么最好就不用。在我们使用反射技术时,下面几条内容应该牢记于心。

  • 性能开销 :反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。
  • 安全限制 :使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。
  • 内部暴露 :由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
上一篇
下一篇