1 - CH01-面向对象

三大特性

封装

利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体。数据被保护在抽象数据类型的内部,尽可能的隐藏内部的细节,只保留一些对外接口使其与外部发生关系。用户无需知道对象内部的细节,但可以通过对象所提供的接口来访问对象。

优点:

  • 减少耦合:可以独立的开发、测试、优化、使用、理解、修改
  • 减少维护负担:可以更容易的被其他开发者理解,并且在调试的时候可以不影响其他模块
  • 有效的调节性能:可以通过剖析确定哪些模块影响了系统的性能
  • 提高软件的可重用性
  • 降低了构建大型系统的风险:即使整个系统不可用,但是这些独立的模块却有可能是可用的

继承

继承实现了 IS-A 关系,比如 Cat 和 Animal 就是一种 IS-A 关系,因此 Cat 可以继承自 Animal,从而获得属于 Animal 的非私有的属性和方法。

继承应该遵循里氏替换原则,子类对象必须能够替换掉父类对象(可以直接将子类对象看做是父类对象)。

Cat 可以当做 Animal 来使用,也就是说可以使用 Animal 引用 Cat 对象。父类引用指向子类对象称为 向上转型

多态

多态分为编译时多态和运行时多态:

  • 编译时多态主要指方法的重载
  • 运行时多态指程序中定义的对象引用所指向的具体类型在运行期才确定

运行时多态有三个条件:

  • 继承
  • 覆写
  • 向上转型

类图

泛化关系

Java extend。

NAME

实现关系

Java implement。

NAME

聚合关系

表示整体由部分组成,但是整体和部分不是强依赖,整体不存在了部分还会存在。

NAME

组合关系

和聚合不同,组合中整体和部分是强依赖的,整体不存在了部分也不存在了。比如公司和部门,公司没了部门就不存在了。

NAME

关联关系

表示不同类对象之间有关联,这是一种静态关系,与运行过程的状态无关,在最开始就是可以确定的。因此也可以用一对一、一对多、多对一、多对多这种表述来表示。

比如学生和学校就是一种关联关系,一个学校可以有多个学生,但是一个学生只属于一个学校,因此这是一种多对一关系,在运行开始就能确定。

NAME

依赖关系

依赖关系是在运行中起作用的。A 类对 B 类的依赖关系主要有三种形式:

  • A 类是 B 类中的局部变量
  • A 类是 B 类方法中的参数
  • A 类向 B 类发送消息,从而影响 B 类
NAME

Vihicle 的 move 方法接收一个 MoveBehavior 对象作为参数来实现移动逻辑,而 MoveBehavior 可能的实现是向上或向下移动。

参考资料

2 - CH02-基本知识

数据类型

包装类型

八个基本类型:

  • boolean:1
  • byte:8
  • char:16
  • short:16
  • int:32
  • float:32
  • long:64
  • double:64

基本类型都拥有对应的包装类型,它们之间的赋值使用自动装箱与拆箱完成:

Integer x=2;	// 装箱
int y=x;			// 拆箱

缓存池

new Integer(21)Integer.valueOf(21) 的区别在于:

  • 前者每次都会创建一个新的对象
  • 后者会使用缓存池中的对象,多次调用会获得同一个对象的引用
Integer x = new Integer(21);
Integer y = new Integer(21);
System.out.println(x == y);	// false
Integer m = Integer.valueOf(21);
Integer n = Integer.valueOf(21);
System.out.println(m == n);	// true

valueOf() 方法的实现比较简单,它会先判断值是否在缓存池中,有则返回否则新建并返回。

public static Integer valueOf(int i) {
  if(i >= IngegerCache.low && i <= Integer.high){
    return IntegerCache[i + (-IngegerCache.low)];
  }
  return new Integer(i);
}

在 Java 8 中,Integer 缓存池的大小默认为 -128 至 127。

编译器会在缓冲池范围内的基本类型自动装箱过程中调用 valueOf 方法,因此多个取值相同的 Integer 实例在使用自动装箱来创建时,会引用相同的对象。

Integer m = 123;
Integer n = 123;
System.out.println(m == n); // true

基本类型对应的缓冲池如下:

boolean: true, false

all byte values

short values between -128 to 127

int values between -128 to 127

char in range \u0000 to \u007F

String

概览

String 被声明为 final,因此不可被继承。

其内部使用 char 数组存储数据,该数据也被声明为 final,表示该 value 数组初始化之后便不能再被修改。并且没有提供修改该数组的方法,因此可以保证 String 不可变。

不可变的好处

  1. 可以缓存哈希值

因为 String 的 hash 值经常被使用,比如 String 作为 HashMap 的 key。不可变使得其 hash 值也不会变,因此仅需计算一次。

  1. String Pool 的需要

如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。

  1. 安全性

String 经常被作为参数类型,String 不可变性可以保证参数不可变。

  1. 线程安全

不可变特性天生具备线程安全性,可以在多个线程中并发使用。

Program Creek: Why String is immutable in Java?

String, StringBuffer, String Builder

  • 可变性
    • String 不可变
    • StringBuffer、StringBuilder 可变
  • 线程安全
    • String 不可变,即线程安全
    • StringBuilder 非线程安全
    • StringBuffer 保证线程安全,内部使用 synchronized 实现同步

StackOverflow : String, StringBuffer, and StringBuilder

Stirng.intern()

使用 Stirng.intern() 可以保证相同内容的字符串变量引用同一个内存对象。

String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2);           // false
String s3 = s1.intern();
System.out.println(s1.intern() == s3);  // true

如果直接使用 "bbb" 这种形式而非 new 来创建字符串实例,会自动将其放入 String Pool 中。

String s4 = "bbb";
String s5 = "bbb";
System.out.println(s4 == s5);  // true

在 Java 7 之前,字符串常量池被放在运行时常量池中,属于永久代。在 Java 7 中,字符串常量池被移到 Native Method 中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OOM。

运算

参数传递

Java 中的参数是以值传递的形式传入方法中,而不是引用传递。(这里的值值得是引用的地址值)

以下代码中,Dog dog 是一个指针,存储的是对象的地址值。在将一个参数传入一个方法时,本质上是将对象的地址以值的形式传递到形参中。因此在方法中改变指针引用的对象,那么这两个指针将指向不同的对象,一方改变自身指向的对象不会对另一方产生影响。

public class Dog {
    String name;

    Dog(String name) {
        this.name = name;
    }

    String getName() {
        return this.name;
    }

    void setName(String name) {
        this.name = name;
    }

    String getObjectAddress() {
        return super.toString();
    }
}

public class PassByValueExample {
    public static void main(String[] args) {
        Dog dog = new Dog("A");
        System.out.println(dog.getObjectAddress()); // Dog@4554617c
        func(dog);
        System.out.println(dog.getObjectAddress()); // Dog@4554617c
        System.out.println(dog.getName());          // A
    }

    private static void func(Dog dog) {
        System.out.println(dog.getObjectAddress()); // Dog@4554617c
        dog = new Dog("B");
        System.out.println(dog.getObjectAddress()); // Dog@74a14482
        System.out.println(dog.getName());          // B
    }
}

但是如果在方法中改变对象的字段值,则会改变原对象的字段值,因为改变的是同一个地址指向的内容。

class PassByValueExample {
    public static void main(String[] args) {
        Dog dog = new Dog("A");
        func(dog);
        System.out.println(dog.getName());          // B
    }

    private static void func(Dog dog) {
        dog.setName("B");
    }
}

StackOverflow: Is Java “pass-by-reference” or “pass-by-value”?

float 与 double

1.1 这样的字面量属于 double 类型,不能直接将其赋值给 float 变量,因为是向下转型。Java 不能隐式执行向下转型,因为会使得精度降低。

float f = 1.1;  // error
float m = 1.1f; // right

隐式类型转换

因为字面量 1 是 int 类型,它比 short 类型精度要高,因此不能隐式的将 int 类型向下转型为 short 类型。

但是使用 += 运算可以执行隐式类型转换:

short s1 = 1;
// s1 = s1 + 1;

s1 += 1;

这其实相当于将 s1 + 1 的计算结果执行了向下转型:

s1 = (short) (s1 + 1);

StackOverflow : Why don’t Java’s +=, -=, *=, /= compound assignment operators require casting?

switch

从 Java 7 开始,可以在 switch 条件判断语句中使用 String 对象。

Switch 不支持 long,是因为 switch 的设计初衷是对那些只有极少数的几个值进行等值判断,如果值的范围过大,那么还是使用 if 比较合适。

Why can’t your switch statement data type be long, Java? - Stack Overflow

继承

访问权限

Java 中有三个访问权限修饰符:private、protected、public,如果不加访问修饰符,则表示 package 范围内可见。

可以对类或类中的成员(字段/方法)添加修饰符。

  • 类可见表示其他类可以用这类来创建实例对象。(即通过类来引用成员)
  • 成员可见表示其他类可以用这个类的实例对象访问到该成员。(即通过类实例来引用成员)

protected 用于修饰成员,表示在继承体系中成员对于子类可见,但是该修饰符对于类没有作用。

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

如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以在使用父类实例的地方都能使用子类实例,即子类实例提供了父类应该具体的能力,即里氏替换原则。

字段决不能是 public,因为这么做就是去了对这个字段修改行为的控制,外部可以对其执行任意修改。

如果是 package 范围或私有的嵌套类,那么直接暴露成员不会有太大的影响。

抽象类与接口

  1. 抽象类

抽象类和抽象方法使用 abstract 关键字声明。抽象类一般会包含抽象方法,抽象方法一定位于抽象类中。

抽象类和普通类之间最大的区别是,抽象类不能被实例化,需要继承抽象类并实例化子类。

  1. 接口

接口是抽象类型的延伸,在 Java 8 之前,可以看做是一个完全抽象的类,即不能拥有任何实现方法。

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

接口的成员都是 public 可见,并且不允许定义为 private 或 protected。

接口的字段默认都是 static final 的,即静态的。

  1. 比较
  • 从设计层面看,抽象类提供了 IS-A 关系,那么就必须满足里氏替换原则,即子类对象必须能够替换所有父类对象。
    • 接口更像是 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。
  • 从使用上来看,一个类可以实现多个接口,但不能同时继承多个抽象类。
  • 接口的字段只能是 static final,抽象类没有该限制。
  • 即可的成员只能是 public 可见,而抽象类的成员可以有多种范围可见。
  1. 选择

使用接口:

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

使用抽象类:

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

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

super

  • 访问父类的构造函数:从而委托父类完成一些初始化工作。
  • 方位父类的成员:如果子类重写了父类的某个方法实现,则可以通过 super 来引用父类原有的方法实现。

Using the Keyword super (opens new window) (oracle.com)

重写与重载

  1. 重写(overwrite)

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

为了满足里氏替换原则,重写必须有以下两个限制:

  • 子类方法的访问权限不能低于父类方法
  • 子类方法的返回类型必须是父类方法返回类型的原类型或其子类型。

使用 @Overwrite 注解可以通过编译器来检查这两个限制是否满足。

  1. 重载(overload)

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

如果仅返回值不同,其他都相同,不能认为是重载。

Object 通用方法

public final native Class<?> getClass()

public native int hashCode()

public boolean equals(Object obj)

protected native Object clone() throws CloneNotSupportedException

public String toString()

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

protected void finalize() throws Throwable {}

equals

  1. 等价关系

    • 自反性:x.equals(x) 为 true
    • 对称性:x.equals(y) == y.equals(x) 为 true
    • 传递性:x.equals(y) && y.equals(z)x.equals(z) 为 true
    • 一致性:多次调用 equals 方法的结果不会变化
    • 与 null 比较:任何不为 null 的对象 x 调用 x.equals(null) 均为 false
  2. equals 与 ==

    • 对于基本类型,== 判断值是否相等,基本类型没有 equals 方法
    • 对于引用类型,== 判断两变量的引用是否相等,而 equals 则判断引用的对象是否等价
  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;
    }
}

hashCode

hashCode 返回哈希值,而 equals 用于判断两个对象是否等价。等价的两个对象的哈希值也一定相等,但是哈希值相等的两个对象不一定等价。

在重写 equals 方法时应当总是重写 hashCode 以确保等价对象的哈希值也一定相等。

理想的哈希函数应该具有均匀性,不相等的对象应该均匀分布到所有可能的哈希值上。这就要求哈希函数能把所有域的值都考虑进来,可以将每个域都当做 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,其中 @ 符号后面的值为散列码的无符号十六进制表示。

Clone

  1. cloneable

clone() 是 Object 对象的 protected 方法,并非 public,如果一个类不显式的重新 clone 方法,其他类就无法直接去调用该类实例的 clone 方法。

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

CloneExample e1 = new CloneExample();
CloneExample e2 = e1.clone(); // will throw: CloneNotSupportedException

直接调用重写后的 clone 方法会抛出以上异常,这是因为 CloneExample 并未实现 Cloneable 接口。

注意,clone 并非 Cloneable 接口的方法,而是 Object 的一个 protected 方法。Cloneable 只是规定了:如果一个类没有实现 Cloneable 接口,直接调用 clone 方法时就会抛出该异常。

  1. 浅拷贝

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

// 像上面一样实现 Cloneable 接口并重写 clone 方法
ShallowCloneExample e1 = new ShallowCloneExample();
ShallowCloneExample e2 = e1.clone();

e2.setValue(11);
assert e1.getValue() == 11; // true
  1. 深拷贝

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

public class DeepCloneExample implements Cloneable {
  private int[] arr;
  
  @Override
  protected DeepCloneExample clone() throws CloneNotSupportException {
    DeepCloneExample cloned = (DeepCloneExample) super.clone();
    result.arr = new int[arr.length];
    for(int i=0;i<arr.length;i++){
      cloned.arr[i]=arr[i];
    }
    return cloned;
  }
}
  1. clone 的替代方案

使用 clone 方法来拷贝对象既复杂又有风险,可能会抛出异常,并且还需要类型转换。可以基于 Effective Java 中的建议,通过拷贝构造函数或拷贝工厂来拷贝一个对象。

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];
    }
}

关键字

final

  1. 变量

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

  • 对于基本类型,fianl 使数值不变
  • 对于引用类型,final 使引用不变,即不能被重新赋值为别的对象,但是所引用对象的值可以被修改
  1. 方法

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

被 private 修饰的方法隐式的被指定为 fianl,如果在子类中定义的方法和基类中某个 private 方法签名一样,此时子类的方法并被重写基类方法,而是在子类中重新定义了一个新的方法。

声明类不能被继承。

static

  1. 静态变量

静态变量:又称为类变量,该变量属于类,而非该类的实例,类的所有实例都共享该变量,并通过类名来引用该变量,在内存中仅有一份。

实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。

  1. 静态方法

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

只能访问所属类的静态字段(变量)和静态方法,方法中不能有 this 和 super 关键字。

  1. 静态语句块

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

  1. 静态内部类

非静态内部类属于外部类的实例,而静态内部类属于外部类本身,通过外部类来引用该静态内部类。

  1. 静态导包

通过静态导包,可以在使用静态变量和方法时不用再逐个指明 ClassName,可一件简化代码:

import static com.xxx.ClassName.*;
  1. 初始化顺序

静态变量和语句块优先于实例变量和普通语句块,它们的顺序由代码中的编写顺序决定。然后才是构造函数。

存在集成的情况下,初始化顺序为:

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

反射

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

类加载相当于 Class 对象的加载。类在第一次使用时才会动态加载到 JVM 中,或者使用 Class.forName(...) 主动加载一个类,该方法会返回一个 Class 对象。

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

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

  • Field:可以使用 get 和 set 方法读取或修改 Field 对象关联的字段。
  • Method:可以使用 invoke 方法滴啊用与 Method 对象关联的方法。
  • Constructor:可以使用 Constructor 创建新的对象。

高级用法:

  • Extensibility Features : An application may make use of external, user-defined classes by creating instances of extensibility objects using their fully-qualified names.

  • Class Browsers and Visual Development Environments : A class browser needs to be able to enumerate the members of classes. Visual development environments can benefit from making use of type information available in reflection to aid the developer in writing correct code.

  • Debuggers and Test Tools : Debuggers need to be able to examine private members on classes. Test harnesses can make use of reflection to systematically call a discoverable set APIs defined on a class, to insure a high level of code coverage in a test suite.

反射的缺点:

反射虽然强大但不应被直接使用。如果能够在不使用反射的情况下完成操作,则应避免使用反射。使用反射时应注意以下几点:

  • Performance Overhead : Because reflection involves types that are dynamically resolved, certain Java virtual machine optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications.
  • Security Restrictions : Reflection requires a runtime permission which may not be present when running under a security manager. This is in an important consideration for code which has to run in a restricted security context, such as in an Applet.
  • Exposure of Internals :Since reflection allows code to perform operations that would be illegal in non-reflective code, such as accessing private fields and methods, the use of reflection can result in unexpected side-effects, which may render code dysfunctional and may destroy portability. Reflective code breaks abstractions and therefore may change behavior with upgrades of the platform.

异常

Throwable 可以用来表示任何可以作为异常抛出的类,分为两种:Error 和 Exception。其中 Error 用来表示 JVM 无法处理的错误,Exception 分为两种:

  • 受检异常:需要 try…catch 语句捕获并进行处理,并且可以从异常中恢复。
  • 非受检异常:程序运行时错误,比如除零操作会引起 ArithmeticException,这时程序崩溃且无法恢复。
NAME

泛型

注解

Java 注解是附加在代码中的一些元信息,用于一些工具在编译期、运行时执行解析并使用,起到说明、配置的功能。注解不会也不应该影响代码的实际逻辑,仅仅起到辅助性的作用。

特性

版本特性

Java 8

  • Lambda Expressions
  • Pipelines and Streams
  • Date and Time API
  • Default Methods
  • Type Annotations
  • Nashhorn JavaScript Engine
  • Concurrent Accumulators
  • Parallel operations
  • PermGen Error Removed

Java 7

  • Strings in Switch Statement

  • Type Inference for Generic Instance Creation

  • Multiple Exception Handling

  • Support for Dynamic Languages

  • Try with Resources

  • Java nio Package

  • Binary Literals, Underscore in literals

  • Diamond Syntax

与 C++

  • Java 是纯粹的面向对象语言,所有的对象都继承自 Object,C++ 为了兼容 C 同时支持面向对象和面向过程。
  • Java 通过虚拟机实现跨平台特性,但是 C++ 依赖于特定的平台。
  • Java 没有指针,其引用可以理解为安全指针,而 C++ 具有与 C 一样的指针。
  • Java 支持自动垃圾回收,而 C++ 需要手动回收。
  • Java 不支持多重继承,只能通过实现多个接口到达相同的目的,而 C++ 支持多重继承。
  • Java 不支持操作符重载,虽然可以对两个 String 对象执行加法运算,但是是语言内置的操作,不属于操作符重载,而 C++ 可以。
  • Java 的 goto 是保留字,不可以使用,C++ 可以使用。
  • Java 不支持条件编译,C++ 通过 #ifdef #ifndef 等预处理制冷可以实现条件编译。

JRE 与 JDK

  • JRE 是 JVM 程序,Java 应用需要运行在 JRE 之上。
  • JDK 是 JRE 的超集,带有 JRE 和用于编写 Java 程序的工具,比如 javac。

参考资料

3 - CH03-基本图谱

概述

  • 术语
    • JDK:Java 开发者工具,Java Developer’s Kit
    • JRE:Java 运行时环境,Java Runtime Environment
    • JVM:Java 虚拟机,Java Vitual Machine
    • API:应用程序编程接口,Application Programming Interface
  • IDE
    • Eclipse
    • Intellij IDEA
  • 程序构造块
    • package
    • import
    • class
    • main
    • 大括号
    • 语句
    • 注释
  • 工作方式
    • 先编译后执行
      • Java 扩平台的根本
      • 编译生成中间代码
      • JVM 加载执行中间代码

语言核心

语言元素

  • 关键字:有特殊含义的单词
    • 48 个可用
    • 2 个保留:goto,const
  • 标识符
    • 字符、数字、下划线、$
    • 不能以数字开头
    • 不能有!等特殊字符
    • 不能是关键字
    • 大小写敏感
    • 驼峰风格
  • 运算符
    • 赋值:=
    • 算术:+,-,*,/,%
    • 关系:>,<,>=,<=,!=,==
    • 短路:&&,||
    • 三目:..?..:..
    • 逻辑:&,|,!
    • 自增/减运:++,--
    • 下标运:[]
    • 类型运:(ClassName)
    • 其他:
      • new
      • instanceOf
      • 位与
      • 访问成员
  • 字面量:
    • 整形:122
    • 实数:3.14,2.1E-3
    • 字符:'a'
    • 字符串:"b"
    • 布尔:true
    • 引用:null
    • 类型:int.class, String.class
  • 分隔符

变量/常量

数据类型

  • 基本类型:
    • byte-1
    • short-2
    • int-4
    • long-8
    • float-4
    • double-8
    • boolean
    • char-2
  • 枚举类型:符号常量
  • 引用类型:对象

字符串

  • 创建
    • String str = new String("abc")
    • String str = "abc";
  • 操作

数组

  • 一维数组:int[] a = [1,2,3]
  • 二维数组:int[][] a = [[1,2,3],[4,5,6]]

循环

  • for

  • while

  • do-while

分支

  • if-else
  • switch-case

FAQ

使用哪种数据类型表示价格

如果不是特别关心内存和性能的话,使用BigDecimal,否则使用预定义精度的 double 类型。

怎么将 byte 转换为 String

可以使用 String 接收 byte[] 参数的构造器来进行转换,需要注意的点是要使用的正确的编码,否则会使用平台默认编码,这个编码可能跟原来的编码相同,也可能不同。

Java 中怎样将 bytes 转换为 long 类型

String接收bytes的构造器转成String,再 Long.parseLong。

我们能将 int 强制转换为 byte 类型的变量吗

是的,我们可以做强制转换,但是 Java 中 int 是 32 位的,而 byte 是 8 位的,所以,如果强制转化是,int 类型的高 24 位将会被丢弃,byte 类型的范围是从 -128 到 127。

存在两个类,B 继承 A,C 继承 B,我们能将 B 转换为 C 么? 如 C = (C) B

可以,向下转型。但是不建议使用,容易出现类型转型异常.

哪个类包含 clone 方法? 是 Cloneable 还是 Object?

java.lang.Cloneable 是一个标示性接口,不包含任何方法,clone 方法在 object 类中定义。并且需要知道 clone() 方法是一个本地方法,这意味着它是由 c 或 c++ 或 其他本地语言实现的。

Java 中 ++ 操作符是线程安全的吗?

不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交差。还会存在竞态条件(读取-修改-写入)。

a = a + b 与 a += b 的区别

+= 隐式的将加操作的结果类型强制转换为持有结果的类型。如果两这个整型相加,如 byte、short 或者 int,首先会将它们提升到 int 类型,然后在执行加法操作。

我能在不进行强制转换的情况下将一个 double 值赋值给 long 类型的变量吗?

不行,你不能在没有强制类型转换的前提下将一个 double 值赋值给 long 类型的变量,因为 double 类型的范围比 long 类型更广,所以必须要进行强制转换。

3*0.1 == 0.3 将会返回什么? true 还是 false?

false,因为有些浮点数不能完全精确的表示出来。

int 和 Integer 哪个会占用更多的内存?

Integer 对象会占用更多的内存。Integer 是一个对象,需要存储对象的元数据。但是 int 是一个原始类型的数据,所以占用的空间更少。

为什么 Java 中的 String 是不可变的(Immutable)?

Java 中的 String 不可变是因为 Java 的设计者认为字符串使用非常频繁,将字符串设置为不可变可以允许多个客户端之间共享相同的字符串。更详细的内容参见答案。

我们能在 Switch 中使用 String 吗?

从 Java 7 开始,我们可以在 switch case 中使用字符串,但这仅仅是一个语法糖。内部实现在 switch 中使用字符串的 hash code。

Java 中的构造器链是什么?

当你从一个构造器中调用另一个构造器,就是Java 中的构造器链。这种情况只在重载了类的构造器的时候才会出现。

枚举类

JDK1.5出现 每个枚举值都需要调用一次构造函数

什么是不可变对象(immutable object)? Java 中怎么创建一个不可变对象?

不可变对象指对象一旦被创建,状态就不能再改变。任何修改都会创建一个新的对象,如 String、Integer及其它包装类。

  • 对象的状态在创建之后就不能发生改变,任何对它的改变都应该产生一个新的对象。
  • 类的所有的属性都应该是final的。
  • 对象必须被正确的创建,比如: 对象引用在对象创建过程中不能泄露(leak)。
  • 对象应该是final的,以此来限制子类继承父类,以避免子类改变了父类的immutable特性。
  • 如果类中包含mutable类对象,那么返回给客户端的时候,返回该对象的一个拷贝,而不是该对象本身(该条可以归为第一条中的一个特例)

我们能创建一个包含可变对象的不可变对象吗?

是的,我们是可以创建一个包含可变对象的不可变对象的,你只需要谨慎一点,不要共享可变对象的引用就可以了,如果需要变化时,就返回原对象的一个拷贝。最常见的例子就是对象中包含一个日期对象的引用。

有没有可能两个不相等的对象有有相同的 hashcode?

有可能,两个不相等的对象可能会有相同的 hashcode 值,这就是为什么在 hashmap 中会有冲突。相等 hashcode 值的规定只是说如果两个对象相等,必须有相同的hashcode 值,但是没有关于不相等对象的任何规定。

两个相同的对象会有不同的的 hash code 吗?

不能,根据 hash code 的规定,这是不可能的。

我们可以在 hashcode() 中使用随机数字吗?

不行,因为对象的 hashcode 值必须是相同的。

Java 中,Comparator 与 Comparable 有什么不同?

Comparable 接口用于定义对象的自然顺序,而 comparator 通常用于定义用户定制的顺序。Comparable 总是只有一个,但是可以有多个 comparator 来定义对象的顺序。

为什么在重写 equals 方法的时候需要重写 hashCode 方法?

因为有强制的规范指定需要同时重写 hashcode 与 equal 是方法,许多容器类,如 HashMap、HashSet 都依赖于 hashcode 与 equals 的规定。

“a==b”和”a.equals(b)”有什么区别?

如果 a 和 b 都是对象,则 a==b 是比较两个对象的引用,只有当 a 和 b 指向的是堆中的同一个对象才会返回 true,而 a.equals(b) 是进行逻辑比较,所以通常需要重写该方法来提供逻辑一致性的比较。例如,String 类重写 equals() 方法,所以可以用于两个不同对象,但是包含的字母相同的比较。

a.hashCode() 有什么用? 与 a.equals(b) 有什么关系?

hashCode() 方法是相应对象整型的 hash 值。它常用于基于 hash 的集合类,如 Hashtable、HashMap、LinkedHashMap等等。

它与 equals() 方法关系特别紧密。根据 Java 规范,两个使用 equal() 方法来判断相等的对象,必须具有相同的 hash code。

final、finalize 和 finally 的不同之处?

  • final 是一个修饰符,可以修饰变量、方法和类。如果 final 修饰变量,意味着该变量的值在初始化后不能被改变。
  • Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的,但是什么时候调用 finalize 没有保证。
  • finally 是一个关键字,与 try 和 catch 一起用于异常的处理。finally 块一定会被执行,无论在 try 块中是否有发生异常。

Java 中的编译期常量是什么? 使用它又什么风险?

变量也就是我们所说的编译期常量,这里的 public 可选的。实际上这些变量在编译时会被替换掉,因为编译器知道这些变量的值,并且知道这些变量在运行时不能改变。这种方式存在的一个问题是你使用了一个内部的或第三方库中的公有编译时常量,但是这个值后面被其他人改变了,但是你的客户端仍然在使用老的值,甚至你已经部署了一个新的jar。为了避免这种情况,当你在更新依赖 JAR 文件时,确保重新编译你的程序。

静态内部类与顶级类有什么区别?

一个公共的顶级类的源文件名称与类名相同,而嵌套静态类没有这个要求。一个嵌套类位于顶级类内部,需要使用顶级类的名称来引用嵌套静态类,如 HashMap.Entry 是一个嵌套静态类,HashMap 是一个顶级类,Entry是一个嵌套静态类。

Java 中,Serializable 与 Externalizable 的区别?

Serializable 接口是一个序列化 Java 类的接口,以便于它们可以在网络上传输或者可以将它们的状态保存在磁盘上,是 JVM 内嵌的默认序列化方式,成本高、脆弱而且不安全。Externalizable 允许你控制整个序列化过程,指定特定的二进制格式,增加安全机制。

说出 JDK 1.7 中的三个新特性?

虽然 JDK 1.7 不像 JDK 5 和 8 一样的大版本,但是,还是有很多新的特性,如 try-with-resource 语句,这样你在使用流或者资源的时候,就不需要手动关闭,Java 会自动关闭。Fork-Join 池某种程度上实现 Java 版的 Map-reduce。允许 Switch 中有 String 变量和文本。菱形操作符(<>)用于泛型推断,不再需要在变量声明的右边申明泛型,因此可以写出可读写更强、更简洁的代码。另一个值得一提的特性是改善异常处理,如允许在同一个 catch 块中捕获多个异常。

说出 5 个 JDK 1.8 引入的新特性?

Java 8 在 Java 历史上是一个开创新的版本,下面 JDK 8 中 5 个主要的特性: Lambda 表达式,允许像对象一样传递匿名函数 Stream API,充分利用现代多核 CPU,可以写出很简洁的代码 Date 与 Time API,最终,有一个稳定、简单的日期和时间库可供你使用 扩展方法,现在,接口中可以有静态、默认方法。 重复注解,现在你可以将相同的注解在同一类型上使用多次。

下述包含 Java 面试过程中关于 SOLID 的设计原则,OOP 基础,如类,对象,接口,继承,多态,封装,抽象以及更高级的一些概念,如组合、聚合及关联。也包含了 GOF 设计模式的问题。

接口是什么? 为什么要使用接口而不是直接使用具体类?

接口用于定义 API。它定义了类必须得遵循的规则。同时,它提供了一种抽象,因为客户端只使用接口,这样可以有多重实现,如 List 接口,你可以使用可随机访问的 ArrayList,也可以使用方便插入和删除的 LinkedList。接口中不允许普通方法,以此来保证抽象,但是 Java 8 中你可以在接口声明静态方法和默认普通方法。

Java 中,抽象类与接口之间有什么不同?

Java 中,抽象类和接口有很多不同之处,但是最重要的一个是 Java 中限制一个类只能继承一个类,但是可以实现多个接口。抽象类可以很好的定义一个家族类的默认行为,而接口能更好的定义类型,有助于后面实现多态机制 参见第六条。

Object有哪些公用方法?

clone equals hashcode wait notify notifyall finalize toString getClass 除了clone和finalize其他均为公共方法。

11个方法,wait被重载了两次

equals与==的区别

  • == 是一个运算符 equals是Object类的方法
  • 比较时的区别
    • 用于基本类型的变量比较时: ==用于比较值是否相等,equals不能直接用于基本数据类型的比较,需要转换为其对应的包装类型。
    • 用于引用类型的比较时。==和equals都是比较栈内存中的地址是否相等 。相等为true 否则为false。但是通常会重写equals方法去实现对象内容的比较。

String、StringBuffer与StringBuilder的区别

第一点: 可变和适用范围。String对象是不可变的,而StringBuffer和StringBuilder是可变字符序列。每次对String的操作相当于生成一个新的String对象,而对StringBuffer和StringBuilder的操作是对对象本身的操作,而不会生成新的对象,所以对于频繁改变内容的字符串避免使用String,因为频繁的生成对象将会对系统性能产生影响。

第二点: 线程安全。String由于有final修饰,是immutable的,安全性是简单而纯粹的。StringBuilder和StringBuffer的区别在于StringBuilder不保证同步,也就是说如果需要线程安全需要使用StringBuffer,不需要同步的StringBuilder效率更高。

接口与抽象类

  • 一个子类只能继承一个抽象类,但能实现多个接口
  • 抽象类可以有构造方法,接口没有构造方法
  • 抽象类可以有普通成员变量,接口没有普通成员变量
  • 抽象类和接口都可有静态成员变量,抽象类中静态成员变量访问类型任意,接口只能public static final(默认)
  • 抽象类可以没有抽象方法,抽象类可以有普通方法,接口中都是抽象方法
  • 抽象类可以有静态方法,接口不能有静态方法
  • 抽象类中的方法可以是public、protected;接口方法只有public abstract

抽象类和最终类

抽象类可以没有抽象方法, 最终类可以没有最终方法

最终类不能被继承, 最终方法不能被重写(可以重载)

异常

相关的关键字 throw、throws、try…catch、finally

  • throws 用在方法签名上, 以便抛出的异常可以被调用者处理
  • throw 方法内部通过throw抛出异常
  • try 用于检测包住的语句块, 若有异常, catch子句捕获并执行catch块

关于finally

  • finally不管有没有异常都要处理
  • 当try和catch中有return时,finally仍然会执行,finally比return先执行
  • 不管有木有异常抛出, finally在return返回前执行
  • finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,仍然是之前保存的值),所以函数返回值是在finally执行前确定的

注意: finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值

finally不执行的几种情况: 程序提前终止如调用了 System.exit、断电

受检查异常和运行时异常

  • 受检查的异常(checked exceptions),其必须被try…catch语句块所捕获, 或者在方法签名里通过throws子句声明。受检查的异常必须在编译时被捕捉处理,命名为Checked Exception是因为Java编译器要进行检查, Java虚拟机也要进行检查, 以确保这个规则得到遵守。
  • 运行时异常(runtime exceptions), 需要程序员自己分析代码决定是否捕获和处理,比如空指针,被0除…
  • Error的,则属于严重错误,如系统崩溃、虚拟机错误、动态链接失败等,这些错误无法恢复或者不可能捕捉,将导致应用程序中断,Error不需要捕获。

super出现在父类的子类中。有三种存在方式

  • super.xxx(xxx为变量名或对象名)意思是获取父类中xxx的变量或引用
  • super.xxx(); (xxx为方法名)意思是直接访问并调用父类中的方法
  • super() 调用父类构造

this() & super()在构造方法中的区别

  • 调用super()必须写在子类构造方法的第一行, 否则编译不通过
  • super从子类调用父类构造, this在同一类中调用其他构造均需要放在第一行
  • 尽管可以用this调用一个构造器, 却不能调用2个
  • this和super不能出现在同一个构造器中, 否则编译不通过
  • this()、super()都指的对象,不可以在static环境中使用
  • 本质this指向本对象的指针。super是一个关键字

序列化

声明为static和transient类型的数据不能被序列化, 反序列化需要一个无参构造函数

Java移位运算符

  • << :左移运算符,x << 1,相当于x乘以2(不溢出的情况下),低位补0
  • >> :带符号右移,x >> 1,相当于x除以2,正数高位补0,负数高位补1
  • >>> :无符号右移,忽略符号位,空位都以0补齐

形参&实参

形式参数可被视为local variable.形参和局部变量一样都不能离开方法。只有在方法中使用,不会在方法外可见。 形式参数只能用final修饰符,其它任何修饰符都会引起编译器错误。但是用这个修饰符也有一定的限制,就是在方法中不能对参数做任何修改。不过一般情况下,一个方法的形参不用final修饰。只有在特殊情况下,那就是: 方法内部类。一个方法内的内部类如果使用了这个方法的参数或者局部变量的话,这个参数或局部变量应该是final。 形参的值在调用时根据调用者更改,实参则用自身的值更改形参的值(指针、引用皆在此列),也就是说真正被传递的是实参。

局部变量为什么要初始化

局部变量是指类方法中的变量,必须初始化。局部变量运行时被分配在栈中,量大,生命周期短,如果虚拟机给每个局部变量都初始化一下,是一笔很大的开销,但变量不初始化为默认值就使用是不安全的。出于速度和安全性两个方面的综合考虑,解决方案就是虚拟机不初始化,但要求编写者一定要在使用前给变量赋值。

Java语言的鲁棒性

Java在编译和运行程序时,都要对可能出现的问题进行检查,以消除错误的产生。它提供自动垃圾收集来进行内存管理,防止程序员在管理内存时容易产生的错误。通过集成的面向对象的例外处理机制,在编译时,Java揭示出可能出现但未被处理的异常,帮助程序员正确地进行选择以防止系统的崩溃。另外,Java在编译时还可捕获类型声明中的许多常见错误,防止动态运行时不匹配问题的出现。

4 - CH04-泛型机制

概览

Java 的泛型特性由 JDK 1.5 引入,因此为了兼容之前的版本,Java 泛型的实现采用了“伪泛型”策略。即 Java 在语法上支持泛型,但是在编译阶段会执行“类型擦除(Type Erasure)”,将所有的泛型表示都替换为实际的类型,就像完全没有泛型一样。

泛型的本质是为了参数化类型,在不创建新类型的情况下,通过泛型指定的不同类型来控制形式化参数所限制的具体类型。也就是说在泛型的使用过程中,操作的数据类型被指定为一个参数,该类型参数可以用在类、接口、方法中,分别被称为泛型类、泛型接口、泛型方法。

引入泛型的最直接意义在于:

  • 适用于多种数据类型执行相同的代码逻辑(代码复用)
  • 泛型中的类型在使用时指定,不需要强制类型转换(类型安全,由编译器执行检查)。

基本使用

泛型类

class Point<T> {
  private T var;
  
  public T getVar() {
    return var;
  }
  
  public void setVar(T var){
    this.var = var;
  }
}

Point<String> point = new Point<>();
pioint.setVar("value");

泛型接口

interface Info<T> {
  T getVar();
}

class InfoImpl<T> implements Info<T> {
  private T var;
}

泛型方法

class Caster {
  public static <T> T castAs(Class<T> clazz, Object value) {
    return clazz.cast(value);
  }
}

泛型边界

  • 参考 “泛型型变-Scala”。

泛型擦除

擦除原则:

  • 消除类型参数声明,即删除 <> 及其包围的部分。
  • 根据类型参数的上下界推断并替换所有的类型参数为具体类型:
    • 如果类型参数是无限制通配符或没有上下界限定则替换为 Object。
    • 如果存在上下界限定则根据子类替换原则取类型参数的最左侧限定类型,即父类。
  • 为了保证类型安全,必要时插入强制类型转换代码。
  • 自动产生“桥接方法”以保证类型擦除后的代码仍然具有泛型的“多态性”。

如何执行类型擦除:

  • 参数类定义中的类型参数:无限制类型擦除

    当类定义中类型参数没有任何限制时,直接将其替换为 Object,如 <T><?> 会被直接替换为 Object。

    NAME
  • 擦除类定义中的类型参数:有限制类型擦除

    如果类定义中的类型参数有上下界限定,在擦除时将其替换为类型参数的上界或下界,如 <T extends Number><? extends Number> 会被替换为 Number,<? super Number> 会被替换为 Object。

    NAME
  • 擦除方法定义中的类型参数

    原则和擦除类定义中的类型参数一致,这里仅擦除方法定义中的有限制类型参数为例。

    NAME

如何证明类型擦除

  • 原始类型相等

    ArrayList<String> list1 = new ArrayList<>();
    list1.add("abc");
    
    ArrayList<Integer> list2 = new ArrayList<>();
    list2.add(123);
    
    assert list1.getClass() == list2.getClass();
    
  • 通过反射添加其他类型的元素

    ArrayList<Integer> list = new ArrayList<>();
    list.add(1);
    
    list.getClass().getMethod("add", Object.class).invoke(list, "abc");
    

类型擦除后的原生类型(Raw Type)

原生类型为擦除掉泛型信息后,保留在字节码中的类型变量的真正类型,无论何时定义一个泛型类型,对应的原始类型都会自动提供,类型变量擦除,并使用其限定类型或 Object 替换。

如何理解编译期检查

Java 编译器首先检查代码中泛型的类型,然后再执行类型擦除,然后再执行编译。

基于代码的类型检查是如何进行的呢?

比如以下代码:

ArrayList<String> list1 = new ArrayList(); //第一种 情况
ArrayList list2 = new ArrayList<String>(); //第二种 情况

以上代码可以编译通过,且不会出现变异警告。但是仅第一种可以实现与完全使用泛型参数一样的效果,第二种不行。

因为类型检查就是编译时完成的,new ArrayList() 只是在内存中开辟一个存储空间,可以存储任何类型的对象,而真正涉及类型检查的是它的引用,因为我们是通过引用 list1 来调用它的方法,比如调用其 add 方法,所以 list1 引用能能完成泛型类型的检查。而引用 list2 没有使用泛型,所以不行。

ArrayList<String> list1 = new ArrayList(); 
list1.add("1"); //编译通过
list1.add(1); //编译错误  
String str1 = list1.get(0); //返回类型就是String 

ArrayList list2 = new ArrayList<String>();
list2.add("1"); //编译通过
ist2.add(1); //编译通过 
Object object = list2.get(0); //返回类型就是Object

new ArrayList<String>().add("11"); //编译通过
new ArrayList<String>().add(22); //编译错误

String str2 = new ArrayList<String>().get(0); //返回类型就是String

因此可以得出,类型检查就是针对引用执行的检查,谁是一个引用,通过该引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象

泛型多态/桥接方法

类型擦除会造成多态冲突,而 JVM 通过桥接方法类解决。

class Pair<T> {  

    private T value;  

    public T getValue() {  
        return value;  
    }  

    public void setValue(T value) {  
        this.value = value;  
    }  
}

然后实现一个子类:

class DateInter extends Pair<Date> {  

    @Override  
    public void setValue(Date value) {  
        super.setValue(value);  
    }  

    @Override  
    public Date getValue() {  
        return super.getValue();  
    }  
}

在这个子类中,我们设定父类的泛型类型为Pair<Date>,在子类中,我们覆盖了父类的两个方法,我们的原意是这样的:将父类的泛型类型限定为 Date,那么父类里面的两个方法的参数都为 Date 类型。

实际上类型擦除后,父类的泛型类型全部变成了 Object,所以父类编译之后会成为如下形式:

class Pair {  
    private Object value;  

    public Object getValue() {  
        return value;  
    }  

    public void setValue(Object  value) {  
        this.value = value;  
    }  
} 

这时子类中的两个重写方法:

@Override  
public void setValue(Date value) {  
    super.setValue(value);  
}  
@Override  
public Date getValue() {  
    return super.getValue();  
}

首先是 setValue 方法,父类的类型是 Object,而子类的类型是 Date,参数类型不一样,如果是在普通的继承体系中,根本就不会是重写,而是重载。但是如果通过基于重载的形式以子类来调用父类的方法:

DateInter dateInter = new DateInter();  
dateInter.setValue(new Date());                  
dateInter.setValue(new Object()); //编译错误

如果是重载,那么子类中会有两个 setValue 方法,一个参数为 Object,一个参数为 Date,但编译错误可以得出这里实际上是重写。

原因是,当我们集成父类并设定其类型参数为 Date,期望将泛型类变成如下形式:

class Pair {  
    private Date value;  
    public Date getValue() {  
        return value;  
    }  
    public void setValue(Date value) {  
        this.value = value;  
    }  
}

然后在子类中重写参数类型为 Date 的两个方法,实现继承中的多态。

但是由于种种原因,JVM 并不能将泛型类型转换为 Date,只能将其擦除,称为原生类型 Object。这样我们本意是重写来实现多态,但类型擦除后只能变为了重载。从而导致类型擦除与多态的冲突。因此 JVM 决定通过桥接来解决该问题。

首先通过命令 javap -c className 的方式反编译 DateInter 子类的字节码,结果如下:

class com.tao.test.DateInter extends com.tao.test.Pair<java.util.Date> {  
  com.tao.test.DateInter();  
    Code:  
       0: aload_0  
       1: invokespecial #8                  // Method com/tao/test/Pair."<init>":()V  
       4: return  

  public void setValue(java.util.Date);  //我们重写的setValue方法  
    Code:  
       0: aload_0  
       1: aload_1  
       2: invokespecial #16                 // Method com/tao/test/Pair.setValue:(Ljava/lang/Object;)V  
       5: return  

  public java.util.Date getValue();    //我们重写的getValue方法  
    Code:  
       0: aload_0  
       1: invokespecial #23                 // Method com/tao/test/Pair.getValue:()Ljava/lang/Object;  
       4: checkcast     #26                 // class java/util/Date  
       7: areturn  

  public java.lang.Object getValue();     //编译时由编译器生成的巧方法  
    Code:  
       0: aload_0  
       1: invokevirtual #28                 // Method getValue:()Ljava/util/Date 去调用我们重写的getValue方法;  
       4: areturn  

  public void setValue(java.lang.Object);   //编译时由编译器生成的巧方法  
    Code:  
       0: aload_0  
       1: aload_1  
       2: checkcast     #26                 // class java/util/Date  
       5: invokevirtual #30                 // Method setValue:(Ljava/util/Date; 去调用我们重写的setValue方法)V  
       8: return  
}

从编译结果来看,我们原本要重写方法实际上成了 4 个方法,最后两个方法即为编译器自动生成的桥接方法。可以发现桥接方法的参数类型都是 Object,也就是说,子类中真正覆盖父类方法的就是这两个桥接方法。桥接方法的内部会执行类型转换并调用我们重写的那两个方法。

基本类型不能作为泛型参数

比如只能使用 List<Integer> 而不能使用 List<int>。因为当类型擦除后,List 的原生类型变为 Object,但是 Object 不能存储 int 值,只能是引用类型 Integer 的值。

然后实际操作中使用的 list.add(1),则是基本类型的自动装箱机制。

泛型类型不能实例化

T t = new T();

以上代码会直接报错。因为 Java 编译期没法确定泛型参数的实际类型,也就找不到对应的类的字节码文件,所以自然就不能完成编译。此外由于 T 被擦除为 Object,如果可以执行 new T(),则就变成了 new Object(),失去了代码的本意。

泛型数组:通过具体类型参数初始化

List<String>[] lsa = new List<String>[10]; // Not really allowed.
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // Unsound, but passes run time store check
String s = lsa[1].get(0); // Run-time error ClassCastException.

由于擦除机制,上面代码可以给 oa[1] 赋值为 ArrayList 类型的变量也不会出现异常。但是在取出数据时要做一次类型转换,所以就会出现 ClassCastException。

如果可以执行泛型数组的声明则上面这种情况就不会出现任何警告和错误,只有在运行时才会报错,但是泛型的出现就是为了避免转型错误,所有 Java 支持泛型数组初始化则与初衷违背。

下面的代码是成立的:

List<?>[] lsa = new List<?>[10]; // OK, array of unbounded wildcard type.
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // Correct.
Integer i = (Integer) lsa[1].get(0); // OK

所以说采用通配符的方式初始化泛型数组是允许的,因为对于通配符的方式是最后取出数据时才执行转型,符合预期逻辑。

Java 的泛型数组初始化时数组类型不能是具体的泛型类型,只能是通配符的形式,因为具体类型会导致可存入任意类型对象,在取出时会发生类型转换异常,会与泛型的设计思想冲突,而通配符形式本来就需要自己强转,符合预期。

泛型数组:正确的初始化数组实例

无论是 new ArrayList[10] 的形式还是通过泛型通配符的形式来初始化都存在转型异常风险。

在需要使用泛型数组时应尽量使用列表集合替代,此外也可以通过反射来创建一个具有指定类型的数组:

public class ArrayWithTypeToken<T> {
    private T[] array;

    public ArrayWithTypeToken(Class<T> type, int size) {
        array = (T[]) Array.newInstance(type, size);
    }

    public void put(int index, T item) {
        array[index] = item;
    }

    public T get(int index) {
        return array[index];
    }

    public T[] create() {
        return array;
    }
}
//...

ArrayWithTypeToken<Integer> arrayToken = new ArrayWithTypeToken<Integer>(Integer.class, 100);
Integer[] array = arrayToken.create();

泛型类中的静态方法与静态变量

public class Test<T> {    
    public static T one;   //编译错误    
    public static T show(T one){ //编译错误    
        return null;    
    }    
}

因为泛型类中的类型参数是在定义对象时才指定的,而静态变量或静态方法不需要使用对象实例来调用。对象未被创建,如何确定该泛型参数的具体类型,所以错误。

public class Test<T> {    

    public static <T> T show(T one){ //这是正确的    
        return null;    
    }    
}

注意这里 show 方法的类型参数 T 并非 Test 类的类型参数 T。

异常中使用泛型

  • 不能抛出也不能捕获泛型对象。继承 Throwable 时使用泛型也是非法的。
    • 因为异常仅在运行时抛出,编译期擦除参数类型后无法区分不同具体类型的泛型异常。
  • 不能在 catch 子句中使用泛型变量
    • 因为泛型信息会被擦除为原生类型,catch(T e) 会变成 catch(Throwable e)
    • 根据异常捕获原则,如果下面还有别的 catch 语句来处理其他异常类型,导致冲突。
  • 可以在声明中抛出参数啊类型的异常,<T extends Throwable> void do(T t) throws T

如何获取类型参数

java.lang.reflect.Type 是 Java 中所有类型的公共高级接口,代表了所有 Java 中的类型。Type 体系中的类型包括:

  • 数组类型:GenericArrayType
  • 参数化类型:ParameterizedType
  • 类型变量:TypeVariable
  • 通配符类型:WildcardType
  • 原始类型:Class
  • 基本类型:Class

5 - CH05-注解机制

概览

注解是 JDK 1.5 版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。它主要的作用有以下四方面:

  • 生成文档,通过代码里标识的元数据生成javadoc文档。

  • 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。

  • 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。

  • 运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。

比如:

  • Java自带的标准注解,包括@Override@Deprecated@SuppressWarnings,分别用于标明重写某个方法、标明某个类或方法过时、标明要忽略的警告,用这些注解标明后编译器就会进行检查。
  • 元注解,元注解是用于定义注解的注解,包括@Retention@Target@Inherited@Documented@Retention用于标明注解被保留的阶段,@Target用于标明注解使用的范围,@Inherited用于标明注解可继承,@Documented用于标明是否生成javadoc文档。
  • 自定义注解,可以根据自己的需求定义注解,并可用元注解对自定义注解进行注解。

内置注解

  • @Override
  • @Deprecated
  • @SuppressWarnings

元注解

  • @Target
  • @Retention,@RetentionTarget
  • @Documented
  • @Inherited
  • @Repeatable
  • @Native

注解与反射接口

通过反射工具包 java.lang.reflect 中的 AnnotatedElement 可以访问注解信息。注意仅在注解被声明为 RUNTIME 时,才能在运行时获取注解信息,当 class 文件被加载时保存在 class 文件中的注解信息才会被 JVM 读取。

AnnotatedElement 接口是所有成语元素的父接口,所有程序通过反射获得了某个类的 AnnotatedElement 对象之后,就可以调用该对象的方法来访问具体的注解信息:

  • boolean isAnnotationPresent(Class<?extends Annotation> annotationClass)
  • <T extends Annotation> T getAnnotation(Class<T> annotationClass)
  • Annotation[] getAnnotations()
  • <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)
  • <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass)
  • Annotation[] getDeclaredAnnotations()

自定义注解

定义注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyMethodAnnotation {

    public String title() default "";

    public String description() default "";

}

使用注解:

public class TestMethodAnnotation {

    @Override
    @MyMethodAnnotation(title = "toStringMethod", description = "override toString method")
    public String toString() {
        return "Override toString method";
    }

    @Deprecated
    @MyMethodAnnotation(title = "old static method", description = "deprecated old static method")
    public static void oldMethod() {
        System.out.println("old method, don't use it.");
    }

    @SuppressWarnings({"unchecked", "deprecation"})
    @MyMethodAnnotation(title = "test method", description = "suppress warning static method")
    public static void genericsTest() throws FileNotFoundException {
        List l = new ArrayList();
        l.add("abc");
        oldMethod();
    }
}

读取注解:

Method[] methods = TestMethodAnnotation.class.getClassLoader()
        .loadClass(("com.pdai.java.annotation.TestMethodAnnotation"))
        .getMethods();

for (Method method : methods) {
    // 方法上是否有MyMethodAnnotation注解
    if (method.isAnnotationPresent(MyMethodAnnotation.class)) {
        try {
            // 获取并遍历方法上的所有注解
            for (Annotation anno : method.getDeclaredAnnotations()) {
                System.out.println("Annotation in Method '"
                        + method + "' : " + anno);
            }

            // 获取MyMethodAnnotation对象信息
            MyMethodAnnotation methodAnno = method
                    .getAnnotation(MyMethodAnnotation.class);

            System.out.println(methodAnno.title());

        } catch (Throwable ex) {
            ex.printStackTrace();
        }
    }
}

注解特性

Java 8 新特性

  • @Repeatable
  • @ElementType.TYPE_USE
  • @ElementType.TYPE_PARAMETER

TYPE_USE 包括类型声明和类型参数声明,是为了方便设计者进行类型检查。

// 自定义ElementType.TYPE_PARAMETER注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_PARAMETER)
public @interface MyNotEmpty {
}

// 自定义ElementType.TYPE_USE注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
public @interface MyNotNull {
}

// 测试类
public class TypeParameterAndTypeUseAnnotation<@MyNotEmpty T>{

  //使用TYPE_PARAMETER类型,会编译不通过
//		public @MyNotEmpty T test(@MyNotEmpty T a){
//			new ArrayList<@MyNotEmpty String>();
//				return a;
//		}

  //使用TYPE_USE类型,编译通过
  public @MyNotNull T test2(@MyNotNull T a){
    new ArrayList<@MyNotNull String>();
    return a;
  }
}

6 - CH06-异常机制

概览

Java 异常是 Java 提供的一种识别及响应错误的机制,Java 异常机制可以使程序中异常处理的代码和正常业务代码分离,保证程序代码更加优雅,并提高程序的健壮性。

层次结构

异常是指非预期的各种状况,如:文件找不到、网络连接失败、非法参数等。异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程。Java 通过 API 中 Throwable 类的众多子类描述各种不同的异常。因此,Java 异常都是对象,是 Throwable 子类的实例,描述了出现在一段编码中的错误条件。当条件生成时,错误将引发异常。

NAME

Throwable

Throwable 是 Java 语言中所有错误与异常的超类。

Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。

Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。

Error

Error 类及其子类:程序中无法处理的错误,表示运行应用程序中出现了严重的错误。

此类错误一般表示代码运行时 JVM 出现问题。通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。

这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误。按照 Java 惯例,我们是不应该实现任何新的 Error 子类的!

Exception

程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时异常。

  • 运行时异常
    • 都是 RuntimeException 类及其子类异常,如 NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
    • 运行时异常的特点是 Java 编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用 try-catch 语句捕获它,也没有用 throws 子句声明抛出它,也会编译通过。
  • 编译期异常
    • 是 RuntimeException 以外的异常,类型上都属于 Exception 类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如 IOException、SQLException 等以及用户自定义的 Exception 异常,一般情况下不自定义检查异常。

受检异常/非受检异常

  • 受检异常:编译器妖气必须处理的异常
    • 正确的程序在运行中,很容易出现的、情理可容的异常状况。可查异常虽然是异常状况,但在一定程度上它的发生是可以预计的,而且一旦发生这种异常状况,就必须采取某种方式进行处理。
    • 除了 RuntimeException 及其子类以外,其他的 Exception 类及其子类都属于受检异常。这种异常的特点是 Java 编译器会检查它,也就是说,当程序中可能出现这类异常,要么用 try-catch 语句捕获它,要么用 throws 子句声明抛出它,否则编译不会通过。
  • 非受检异常:编译器不强制要求检查的异常
    • 包括运行时异常(RuntimeException与其子类)和错误(Error)。

异常基础

关键字

  • try:监听异常
  • catch:捕获异常
  • finally:总是执行
  • throw:抛出异常
  • throws:声明异常

异常捕获

  • try-catch
  • try-catch-finally
  • try-finally
  • try-with-resource: AutoCloseable

常见异常

  • RuntimeException
    • ArrayIndexOutOfBoundsException:数组索引越界
    • ArithmeticException:除零异常
    • NullPointerException:空指针
    • ClassNotFoundException:类加载
    • NegativeArraySizeException:数组长度为负
    • ArrayStoreException:数组元素类型不兼容
    • SecurityException:安全性异常
    • IllegalArgumentException:非法参数
  • IOException
    • IOException:输出输出流异常
    • EOFException:文件结束
    • FileNotFoundException:文件未找到
  • Others
    • ClassCastException
    • SQLException
    • NoSuchFieldException
    • NoSuchMethodException
    • NumberFormatException
    • StringIndexOutOfBoundsException
    • IllegalAccessException
    • InstantiationException

应用实践

  • 在恰当的级别处理异常,即知道该如何处理的地方及时处理异常。
  • 解决问题并且重新调用产生异常的方法。
  • 进行少许修补,然后绕过异常发生的地方继续执行。
  • 用别的数据进行计算,以代替方法预计会返回的值。
  • 把当前运行环境下能做的事尽量做完,然后把相同的异常重抛到更高层。
  • 终止程序。
  • 进行简化。
  • 让类库和程序更安全。
  • 不要通过异常来实现业务流程。

底层机制

try-catch

public static void simpleTryCatch() {
   try {
       testNPE();
   } catch (Exception e) {
       e.printStackTrace();
   }
}

通过 javap 的带编译后的代码:

//javap -c Main
 public static void simpleTryCatch();
    Code:
       0: invokestatic  #3                  // Method testNPE:()V
       3: goto          11
       6: astore_0
       7: aload_0
       8: invokevirtual #5                  // Method java/lang/Exception.printStackTrace:()V
      11: return
    Exception table:
       from    to  target type
           0     3     6   Class java/lang/Exception

异常表中包含了一个或多个异常处理器的信息,内容如下:

  • from:可能发生异常的起始点
  • to:可能发生异常的结束点
  • target:从 from 到 to 之间发生异常后异常处理器的位置
  • type:异常处理器所处理的异常类型

当发生一个异常时:

  1. JVM 会在当前出现异常的方法中查找异常表,检查是否有合适的处理器
  2. 如果当前方法的异常表不为空,且异常符合表中 from to 的范围,同时 type 匹配,则 JVM 将调用位于 target 的处理器来处理异常
  3. 如果上一步没有找到处理器,则继续检查异常表中的下一项
  4. 如果当前异常表无法处理,则向上查找(弹栈)调用方法的调用点,并重复 1-3 步的操作。
  5. 如果所有栈帧都被弹出,仍然没有处理,则抛出异常给当前 Thread,Thread 会终止。
  6. 如果当前 Thread 为最后一个非守护线程,且异常未处理,则导致 JVM 终止运行。

try-catch-finally

public static void simpleTryCatchFinally() {
   try {
       testNPE();
   } catch (Exception e) {
       e.printStackTrace();
   } finally {
       System.out.println("Finally");
   }
}

通过 javap 得到异常表部分:

public static void simpleTryCatchFinally();
    Code:
       0: invokestatic  #3                  // Method testNPE:()V
       3: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
       6: ldc           #7                  // String Finally
       8: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      11: goto          41
      14: astore_0
      15: aload_0
      16: invokevirtual #5                  // Method java/lang/Exception.printStackTrace:()V
      19: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      22: ldc           #7                  // String Finally
      24: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      27: goto          41
      30: astore_1
      31: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      34: ldc           #7                  // String Finally
      36: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      39: aload_1
      40: athrow
      41: return
    Exception table:
       from    to  target type
           0     3    14   Class java/lang/Exception
           0     3    30   any
          14    19    30   any
  • 如果 0-3 之间发生了 Exception 异常,调用 14 位置的处理器。
  • 如果 0-3 之间无论发生哪种异常,都调用 30 位置的处理器。
  • 如果 14-19(catch) 之间无论发生什么异常,都调用 30 位置的处理器。

注意异常表会代码中 finally 的部分同时在异常表的 catch 和 finally 部分注册,以实现无论是否发生异常都执行的逻辑。

catch 先后顺序

先 catch 类层级较高的异常类型会导致编译错误。

return 与 finally

public static String tryCatchReturn() {
   try {
       testNPE();
       return  "OK";
   } catch (Exception e) {
       return "ERROR";
   } finally {
       System.out.println("tryCatchReturn");
   }
}

这里无论是否抛出一查,finally 部分都会执行。

性能损耗

创建一个异常对象是创建一个普通对象所需耗时的几十倍,抛出、捕获异常则是创建异常对象所需耗时的数倍。

7 - CH07-反射机制

反射基础

Runtime Type Identification(RTTI) 运行时类型识别,作用是在运行时识别一个对象的类型和类信息。主要有两种方式:

  • 传统的的 RTTI,它假设我们在编译期就知道了所有的类型。
  • 反射机制,它允许我们在运行时发现和使用类的信息。

Class 类

Class 类就像 String 类、Object 类一样,是一个实实在在的类,存在与 java.lang 包中。Class 类的实例表示 java 应用运行时的类(class ans enum)或接口(interface and annotation),可以通过 类名.class类型.classClass.forName(类名)等方法获取 Class 类的对象实例。

数组同样也被映射为为 class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本类型boolean,byte,char,short,int,long,float,double 和关键字 void 同样表现为 class 对象。

  • Class 类也是类的一种,与 class 关键字是不一样的。
  • 手动编写的类被编译后会产生一个 Class 对象,其表示的是创建的类的类型信息,而且这个 Class 对象保存在 同名.class 的文件中(字节码文件)
  • 每个通过关键字 class 标识的类,在内存中有且只有一个与之对应的 Class 对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。
  • Class 类只有私有构造函数,因此对应 Class 对象只能由 JVM 创建和加载。
  • Class 类的对象作用是运行时提供或获得某个对象的类型信息。

类加载

类加载流程:

  • 加载
  • 连接
    • 验证
    • 准备
    • 解析
  • 初始化
  • 使用
  • 卸载
NAME

反射应用

在Java中,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。在反射包中,我们常用的类主要有Constructor类表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象、Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)、Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private),下面将对这几个重要类进行分别说明。

Class 对象

  • 类名.class
  • 对象.getClass()
  • 完全限定名:Class.forName(全限定类名)

Class 方法

  • forName()
  • Object.getClass()
  • getName():类的全限定名,可用于 Class.forName
  • getSimpleName():仅类名
  • getCanonicalName():更易理解的完全限定名,数组时表示不同
  • isInterface()
  • getInterfaces()
  • getSupercalss()
  • newInstance()
  • getFields()
  • getDeclaredFields()
  • getConstructors()

Constructor

Field

Method

反射流程

public class HelloReflect {
    public static void main(String[] args) {
        try {
            // 1. 使用外部配置的实现,进行动态加载类
            TempFunctionTest test = (TempFunctionTest)Class.forName("com.tester.HelloReflect").newInstance();
            test.sayHello("call directly");
            // 2. 根据配置的函数名,进行方法调用(不需要通用的接口抽象)
            Object t2 = new TempFunctionTest();
            Method method = t2.getClass().getDeclaredMethod("sayHello", String.class);
            method.invoke(test, "method invoke");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e ) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    
    public void sayHello(String word) {
        System.out.println("hello," + word);
    }

}
NAME

反射获取类实例

通过 Class 的静态方法,获取类信息:

@CallerSensitive
public static Class<?> forName(String className) throws ClassNotFoundException {
    // 先通过反射,获取调用进来的类信息,从而获取当前的 classLoader
    Class<?> caller = Reflection.getCallerClass();
    // 调用native方法进行获取class信息
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

最后,JVM 会回调 ClassLoader 执行类加载:

public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}

    // sun.misc.Launcher
    public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
        int var3 = var1.lastIndexOf(46);
        if(var3 != -1) {
            SecurityManager var4 = System.getSecurityManager();
            if(var4 != null) {
                var4.checkPackageAccess(var1.substring(0, var3));
            }
        }

        if(this.ucp.knownToNotExist(var1)) {
            Class var5 = this.findLoadedClass(var1);
            if(var5 != null) {
                if(var2) {
                    this.resolveClass(var5);
                }

                return var5;
            } else {
                throw new ClassNotFoundException(var1);
            }
        } else {
            return super.loadClass(var1, var2);
        }
    }
// java.lang.ClassLoader
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    // 先获取锁
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        // 如果已经加载了的话,就不用再加载了
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                // 双亲委托加载
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            // 父类没有加载到时,再自己加载
            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

protected Object getClassLoadingLock(String className) {
    Object lock = this;
    if (parallelLockMap != null) {
        // 使用 ConcurrentHashMap来保存锁
        Object newLock = new Object();
        lock = parallelLockMap.putIfAbsent(className, newLock);
        if (lock == null) {
            lock = newLock;
        }
    }
    return lock;
}

protected final Class<?> findLoadedClass(String name) {
    if (!checkName(name))
        return null;
    return findLoadedClass0(name);
}

以下是 newInstance 的实现:

// 首先肯定是 Class.newInstance
@CallerSensitive
public T newInstance()
    throws InstantiationException, IllegalAccessException
{
    if (System.getSecurityManager() != null) {
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);
    }

    // NOTE: the following code may not be strictly correct under
    // the current Java memory model.

    // Constructor lookup
    // newInstance() 其实相当于调用类的无参构造函数,所以,首先要找到其无参构造器
    if (cachedConstructor == null) {
        if (this == Class.class) {
            // 不允许调用 Class 的 newInstance() 方法
            throw new IllegalAccessException(
                "Can not call newInstance() on the Class for java.lang.Class"
            );
        }
        try {
            // 获取无参构造器
            Class<?>[] empty = {};
            final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
            // Disable accessibility checks on the constructor
            // since we have to do the security check here anyway
            // (the stack depth is wrong for the Constructor's
            // security check to work)
            java.security.AccessController.doPrivileged(
                new java.security.PrivilegedAction<Void>() {
                    public Void run() {
                            c.setAccessible(true);
                            return null;
                        }
                    });
            cachedConstructor = c;
        } catch (NoSuchMethodException e) {
            throw (InstantiationException)
                new InstantiationException(getName()).initCause(e);
        }
    }
    Constructor<T> tmpConstructor = cachedConstructor;
    // Security check (same as in java.lang.reflect.Constructor)
    int modifiers = tmpConstructor.getModifiers();
    if (!Reflection.quickCheckMemberAccess(this, modifiers)) {
        Class<?> caller = Reflection.getCallerClass();
        if (newInstanceCallerCache != caller) {
            Reflection.ensureMemberAccess(caller, this, null, modifiers);
            newInstanceCallerCache = caller;
        }
    }
    // Run constructor
    try {
        // 调用无参构造器
        return tmpConstructor.newInstance((Object[])null);
    } catch (InvocationTargetException e) {
        Unsafe.getUnsafe().throwException(e.getTargetException());
        // Not reached
        return null;
    }
}

newInstance 的主要逻辑:

  1. 权限检测,如果不通过则直接报错
  2. 查找无参构造器,并将其缓存
  3. 调用具体方法的无参构造方法,生成实例并返回

以下是获取构造器过程:

private Constructor<T> getConstructor0(Class<?>[] parameterTypes,
                                    int which) throws NoSuchMethodException
{
    // 获取所有构造器
    Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
    for (Constructor<T> constructor : constructors) {
        if (arrayContentsEq(parameterTypes,
                            constructor.getParameterTypes())) {
            return getReflectionFactory().copyConstructor(constructor);
        }
    }
    throw new NoSuchMethodException(getName() + ".<init>" + argumentTypesToString(parameterTypes));
}

获取构造器分为三步:

  1. 先获取所有的 constructors, 然后执行参数类型比较;
  2. 如果存在匹配,通过 ReflectionFactory copy 一份 constructor 返回;
  3. 否则抛出 NoSuchMethodException;

下面是获取所有构造器的过程:

// 获取当前类所有的构造方法,通过jvm或者缓存
// Returns an array of "root" constructors. These Constructor
// objects must NOT be propagated to the outside world, but must
// instead be copied via ReflectionFactory.copyConstructor.
private Constructor<T>[] privateGetDeclaredConstructors(boolean publicOnly) {
    checkInitted();
    Constructor<T>[] res;
    // 调用 reflectionData(), 获取保存的信息,使用软引用保存,从而使内存不够可以回收
    ReflectionData<T> rd = reflectionData();
    if (rd != null) {
        res = publicOnly ? rd.publicConstructors : rd.declaredConstructors;
        // 存在缓存,则直接返回
        if (res != null) return res;
    }
    // No cached value available; request value from VM
    if (isInterface()) {
        @SuppressWarnings("unchecked")
        Constructor<T>[] temporaryRes = (Constructor<T>[]) new Constructor<?>[0];
        res = temporaryRes;
    } else {
        // 使用native方法从jvm获取构造器
        res = getDeclaredConstructors0(publicOnly);
    }
    if (rd != null) {
        // 最后,将从jvm中读取的内容,存入缓存
        if (publicOnly) {
            rd.publicConstructors = res;
        } else {
            rd.declaredConstructors = res;
        }
    }
    return res;
}

// Lazily create and cache ReflectionData
private ReflectionData<T> reflectionData() {
    SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;
    int classRedefinedCount = this.classRedefinedCount;
    ReflectionData<T> rd;
    if (useCaches &&
        reflectionData != null &&
        (rd = reflectionData.get()) != null &&
        rd.redefinedCount == classRedefinedCount) {
        return rd;
    }
    // else no SoftReference or cleared SoftReference or stale ReflectionData
    // -> create and replace new instance
    return newReflectionData(reflectionData, classRedefinedCount);
}

// 新创建缓存,保存反射信息
private ReflectionData<T> newReflectionData(SoftReference<ReflectionData<T>> oldReflectionData,
                                            int classRedefinedCount) {
    if (!useCaches) return null;

    // 使用cas保证更新的线程安全性,所以反射是保证线程安全的
    while (true) {
        ReflectionData<T> rd = new ReflectionData<>(classRedefinedCount);
        // try to CAS it...
        if (Atomic.casReflectionData(this, oldReflectionData, new SoftReference<>(rd))) {
            return rd;
        }
        // 先使用CAS更新,如果更新成功,则立即返回,否则测查当前已被其他线程更新的情况,如果和自己想要更新的状态一致,则也算是成功了
        oldReflectionData = this.reflectionData;
        classRedefinedCount = this.classRedefinedCount;
        if (oldReflectionData != null &&
            (rd = oldReflectionData.get()) != null &&
            rd.redefinedCount == classRedefinedCount) {
            return rd;
        }
    }
}
  1. 首先阐释缓存中获取
  2. 如果没有缓存,则从 JVM 中重新加载,并存入缓存,缓存使用软引用保存,保证内存可用。

另外,使用 relactionData() 进行缓存保存;ReflectionData 的数据结构如下:

// reflection data that might get invalidated when JVM TI RedefineClasses() is called
private static class ReflectionData<T> {
    volatile Field[] declaredFields;
    volatile Field[] publicFields;
    volatile Method[] declaredMethods;
    volatile Method[] publicMethods;
    volatile Constructor<T>[] declaredConstructors;
    volatile Constructor<T>[] publicConstructors;
    // Intermediate results for getFields and getMethods
    volatile Field[] declaredPublicFields;
    volatile Method[] declaredPublicMethods;
    volatile Class<?>[] interfaces;

    // Value of classRedefinedCount when we created this ReflectionData instance
    final int redefinedCount;

    ReflectionData(int redefinedCount) {
        this.redefinedCount = redefinedCount;
    }
}

比较构造是否是要查找构造器,其实就是比较类型完全相等性,有一个不相等则返回 false。

最终通过以下逻辑获得构造器:

private static boolean arrayContentsEq(Object[] a1, Object[] a2) {
    if (a1 == null) {
        return a2 == null || a2.length == 0;
    }

    if (a2 == null) {
        return a1.length == 0;
    }

    if (a1.length != a2.length) {
        return false;
    }

    for (int i = 0; i < a1.length; i++) {
        if (a1[i] != a2[i]) {
            return false;
        }
    }

    return true;
}
// sun.reflect.ReflectionFactory
/** Makes a copy of the passed constructor. The returned
    constructor is a "child" of the passed one; see the comments
    in Constructor.java for details. */
public <T> Constructor<T> copyConstructor(Constructor<T> arg) {
    return langReflectAccess().copyConstructor(arg);
}

// java.lang.reflect.Constructor, copy 其实就是新new一个 Constructor 出来
Constructor<T> copy() {
    // This routine enables sharing of ConstructorAccessor objects
    // among Constructor objects which refer to the same underlying
    // method in the VM. (All of this contortion is only necessary
    // because of the "accessibility" bit in AccessibleObject,
    // which implicitly requires that new java.lang.reflect
    // objects be fabricated for each reflective call on Class
    // objects.)
    if (this.root != null)
        throw new IllegalArgumentException("Can not copy a non-root Constructor");

    Constructor<T> res = new Constructor<>(clazz,
                                           parameterTypes,
                                           exceptionTypes, modifiers, slot,
                                           signature,
                                           annotations,
                                           parameterAnnotations);
    // root 指向当前 constructor
    res.root = this;
    // Might as well eagerly propagate this if already present
    res.constructorAccessor = constructorAccessor;
    return res;
}

然后只需调用对应构造器的 newInstance 方法即可返回实例:

// return tmpConstructor.newInstance((Object[])null); 
// java.lang.reflect.Constructor
@CallerSensitive
public T newInstance(Object ... initargs)
    throws InstantiationException, IllegalAccessException,
           IllegalArgumentException, InvocationTargetException
{
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, null, modifiers);
        }
    }
    if ((clazz.getModifiers() & Modifier.ENUM) != 0)
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
    ConstructorAccessor ca = constructorAccessor;   // read volatile
    if (ca == null) {
        ca = acquireConstructorAccessor();
    }
    @SuppressWarnings("unchecked")
    T inst = (T) ca.newInstance(initargs);
    return inst;
}
// sun.reflect.DelegatingConstructorAccessorImpl
public Object newInstance(Object[] args)
  throws InstantiationException,
         IllegalArgumentException,
         InvocationTargetException
{
    return delegate.newInstance(args);
}
// sun.reflect.NativeConstructorAccessorImpl
public Object newInstance(Object[] args)
    throws InstantiationException,
           IllegalArgumentException,
           InvocationTargetException
{
    // We can't inflate a constructor belonging to a vm-anonymous class
    // because that kind of class can't be referred to by name, hence can't
    // be found from the generated bytecode.
    if (++numInvocations > ReflectionFactory.inflationThreshold()
            && !ReflectUtil.isVMAnonymousClass(c.getDeclaringClass())) {
        ConstructorAccessorImpl acc = (ConstructorAccessorImpl)
            new MethodAccessorGenerator().
                generateConstructor(c.getDeclaringClass(),
                                    c.getParameterTypes(),
                                    c.getExceptionTypes(),
                                    c.getModifiers());
        parent.setDelegate(acc);
    }

    // 调用native方法,进行调用 constructor
    return newInstance0(c, args);
}

返回实例之后,可以根据实际需要执行类型转化,以调用具体类型的方法。

反射获取方法

首先获取 Method:

// java.lang.Class
@CallerSensitive
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
    throws NoSuchMethodException, SecurityException {
    checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
    Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
    if (method == null) {
        throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
    }
    return method;
}
  1. 获取所有方法列表
  2. 更具方法名和方法列表,找到符合要求的方法
  3. 如果没有则抛出异常,有则返回方法

首先获取类的所有方法:

// Returns an array of "root" methods. These Method objects must NOT
// be propagated to the outside world, but must instead be copied
// via ReflectionFactory.copyMethod.
private Method[] privateGetDeclaredMethods(boolean publicOnly) {
    checkInitted();
    Method[] res;
    ReflectionData<T> rd = reflectionData();
    if (rd != null) {
        res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
        if (res != null) return res;
    }
    // No cached value available; request value from VM
    res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
    if (rd != null) {
        if (publicOnly) {
            rd.declaredPublicMethods = res;
        } else {
            rd.declaredMethods = res;
        }
    }
    return res;
}

与构造器类似,首先读取缓存,没有缓存则从 JVM 中获取。

不同的是,方法列表执行过滤 Reflection.filterMethods。

// sun.misc.Reflection
public static Method[] filterMethods(Class<?> containingClass, Method[] methods) {
    if (methodFilterMap == null) {
        // Bootstrapping
        return methods;
    }
    return (Method[])filter(methods, methodFilterMap.get(containingClass));
}
// 可以过滤指定的方法,一般为空,如果要指定过滤,可以调用 registerMethodsToFilter(), 或者...
private static Member[] filter(Member[] members, String[] filteredNames) {
    if ((filteredNames == null) || (members.length == 0)) {
        return members;
    }
    int numNewMembers = 0;
    for (Member member : members) {
        boolean shouldSkip = false;
        for (String filteredName : filteredNames) {
            if (member.getName() == filteredName) {
                shouldSkip = true;
                break;
            }
        }
        if (!shouldSkip) {
            ++numNewMembers;
        }
    }
    Member[] newMembers =
        (Member[])Array.newInstance(members[0].getClass(), numNewMembers);
    int destIdx = 0;
    for (Member member : members) {
        boolean shouldSkip = false;
        for (String filteredName : filteredNames) {
            if (member.getName() == filteredName) {
                shouldSkip = true;
                break;
            }
        }
        if (!shouldSkip) {
            newMembers[destIdx++] = member;
        }
    }
    return newMembers;
}

然后根据方法名和参数类型过滤指定方法返回

private static Method searchMethods(Method[] methods,
                                    String name,
                                    Class<?>[] parameterTypes)
{
    Method res = null;
    // 使用常量池,避免重复创建String
    String internedName = name.intern();
    for (int i = 0; i < methods.length; i++) {
        Method m = methods[i];
        if (m.getName() == internedName
            && arrayContentsEq(parameterTypes, m.getParameterTypes())
            && (res == null
                || res.getReturnType().isAssignableFrom(m.getReturnType())))
            res = m;
    }

    return (res == null ? res : getReflectionFactory().copyMethod(res));
}

在执行匹配的过程将会尝试最精确的匹配,最后会通过 ReflectionFactory.copy 返回方法。

调用 method.invoke

@CallerSensitive
public Object invoke(Object obj, Object... args)
    throws IllegalAccessException, IllegalArgumentException,
       InvocationTargetException
{
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, obj, modifiers);
        }
    }
    MethodAccessor ma = methodAccessor;             // read volatile
    if (ma == null) {
        ma = acquireMethodAccessor();
    }
    return ma.invoke(obj, args);
}

通过 MethodAccessor 执行调用,而 MethodAccessor 为接口,在第一次使用时或调用 acquireMethodAccessor 新建实例。

// probably make the implementation more scalable.
private MethodAccessor acquireMethodAccessor() {
    // First check to see if one has been created yet, and take it
    // if so
    MethodAccessor tmp = null;
    if (root != null) tmp = root.getMethodAccessor();
    if (tmp != null) {
        // 存在缓存时,存入 methodAccessor,否则调用 ReflectionFactory 创建新的 MethodAccessor
        methodAccessor = tmp;
    } else {
        // Otherwise fabricate one and propagate it up to the root
        tmp = reflectionFactory.newMethodAccessor(this);
        setMethodAccessor(tmp);
    }

    return tmp;
}
// sun.reflect.ReflectionFactory
public MethodAccessor newMethodAccessor(Method method) {
    checkInitted();

    if (noInflation && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
        return new MethodAccessorGenerator().
            generateMethod(method.getDeclaringClass(),
                           method.getName(),
                           method.getParameterTypes(),
                           method.getReturnType(),
                           method.getExceptionTypes(),
                           method.getModifiers());
    } else {
        NativeMethodAccessorImpl acc =
            new NativeMethodAccessorImpl(method);
        DelegatingMethodAccessorImpl res =
            new DelegatingMethodAccessorImpl(acc);
        acc.setParent(res);
        return res;
    }
}

两种 Accessor 的细节:

//     NativeMethodAccessorImpl / DelegatingMethodAccessorImpl
class NativeMethodAccessorImpl extends MethodAccessorImpl {
    private final Method method;
    private DelegatingMethodAccessorImpl parent;
    private int numInvocations;

    NativeMethodAccessorImpl(Method method) {
        this.method = method;
    }

    public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException
    {
        // We can't inflate methods belonging to vm-anonymous classes because
        // that kind of class can't be referred to by name, hence can't be
        // found from the generated bytecode.
        if (++numInvocations > ReflectionFactory.inflationThreshold()
                && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
            MethodAccessorImpl acc = (MethodAccessorImpl)
                new MethodAccessorGenerator().
                    generateMethod(method.getDeclaringClass(),
                                   method.getName(),
                                   method.getParameterTypes(),
                                   method.getReturnType(),
                                   method.getExceptionTypes(),
                                   method.getModifiers());
            parent.setDelegate(acc);
        }

        return invoke0(method, obj, args);
    }

    void setParent(DelegatingMethodAccessorImpl parent) {
        this.parent = parent;
    }

    private static native Object invoke0(Method m, Object obj, Object[] args);
}
class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
    private MethodAccessorImpl delegate;

    DelegatingMethodAccessorImpl(MethodAccessorImpl delegate) {
        setDelegate(delegate);
    }

    public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException
    {
        return delegate.invoke(obj, args);
    }

    void setDelegate(MethodAccessorImpl delegate) {
        this.delegate = delegate;
    }
}

执行 method.invoke(obj, args) 时,滴啊用 DelegatingMethodAccessorImpl.invoke(),最后被委托到 NativeMethodAccessorImpl.invoke():

public Object invoke(Object obj, Object[] args)
    throws IllegalArgumentException, InvocationTargetException
{
    // We can't inflate methods belonging to vm-anonymous classes because
    // that kind of class can't be referred to by name, hence can't be
    // found from the generated bytecode.
    if (++numInvocations > ReflectionFactory.inflationThreshold()
            && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
        MethodAccessorImpl acc = (MethodAccessorImpl)
            new MethodAccessorGenerator().
                generateMethod(method.getDeclaringClass(),
                               method.getName(),
                               method.getParameterTypes(),
                               method.getReturnType(),
                               method.getExceptionTypes(),
                               method.getModifiers());
        parent.setDelegate(acc);
    }

    // invoke0 是个 native 方法,由jvm进行调用业务方法。从而完成反射调用功能。
    return invoke0(method, obj, args);
}

其中,generateMethod 是生成具体类的方法:

/** This routine is not thread-safe */
public MethodAccessor generateMethod(Class<?> declaringClass,
                                     String   name,
                                     Class<?>[] parameterTypes,
                                     Class<?>   returnType,
                                     Class<?>[] checkedExceptions,
                                     int modifiers)
{
    return (MethodAccessor) generate(declaringClass,
                                     name,
                                     parameterTypes,
                                     returnType,
                                     checkedExceptions,
                                     modifiers,
                                     false,
                                     false,
                                     null);
}

generate 的实现中会出现:ClassDefiner.defineClass(xx, declaringClass.getClassLoader()).newInstance()。

ClassDefiner.defineClass方法实现中,每被调用一次都会生成一个DelegatingClassLoader类加载器对象 ,这里每次都生成新的类加载器,是为了性能考虑,在某些情况下可以卸载这些生成的类,因为类的卸载是只有在类加载器可以被回收的情况下才会被回收的,如果用了原来的类加载器,那可能导致这些新创建的类一直无法被卸载。

而反射生成的类,有时候可能用了就可以卸载了,所以使用其独立的类加载器,从而使得更容易控制反射类的生命周期。

反射汇总

  1. 反射类及反射方法的获取,都是通过从列表中搜寻查找匹配的方法,所以查找性能会随类的大小方法多少而变化;
  2. 每个类都会有一个与之对应的Class实例,从而每个类都可以获取method反射方法,并作用到其他实例身上;
  3. 反射也是考虑了线程安全;
  4. 反射使用软引用relectionData缓存class信息,避免每次重新从jvm获取带来的开销;
  5. 反射调用多次生成新代理Accessor, 而通过字节码生成的则考虑了卸载功能,所以会使用独立的类加载器;
  6. 当找到需要的方法,都会copy一份出来,而不是使用原来的实例,从而保证数据隔离;
  7. 调用反射方法,最终是由jvm执行invoke0()执行;

8 - CH08-SPI 机制

概览

Service Provider Interface(SPI) 是 JDK 内置的服务发现机制,可以用来启用框架扩展或替换组件,主要用于框架。比如 java.sql.Driver 接口,不同的数据库厂商可以针对同一接口提供不同的实现,MySQL 和 PostgreSQL 分别为用户提供了不同的实现。

Java SPI 机制的主要思想是将装配的控制权移交到程序之外,目的在于解耦。

NAME

当服务的提供者提供了一种接口的实现后,需要在 classpath 下的 META-INF/services/ 目录中创建一个以服务接口命名的文件,并在该文件中添加实现类的完全限定名。其他程序可以通过 ServiceLoader 查找这个 jar 包的配置文件,根据其中的实现类名加载并实例化,然后使用实现类提供的功能。

示例

  1. 定义接口

    public interface Serach {
    	List<String> serach(String keyword);
    }
    
  2. 实现接口

    public class FileSearch implements Search {
      @Override
      public List<String> search(String keyword){
        return ...;
      }
    }
    
    public class DatabaseSearch implements Search {
      @Override
      public List<String> search(String keyword){
        return ...;
      }
    }
    
  3. /resource/META-INF/services/ 目录下创建 com.example.Search 文件,在其中添加我们要使用的某个实现类的完全限定名,如com.example.FileSearch

  4. 加载接口实现类

    ServiceLoader<Search> impls = SeraiceLoader.load(Serach.class);
    Iterator<Serach> itor = impls.iterator();
    while(itor.hasNext()){
      Search search = itor.next();
      search.search("keyword");
    }
    

实际应用

JDBC DriverManager

  • JDBC 接口定义:首先 Java 中定义了接口 java.sql.Driver
  • MySQL 实现类:在 mysql-connector-java-version.jar 中,可以找打 META-INF/services 目录查看其中的 java.sql.Driver 文件,其中定义了实现类名 com.mysql.cj.jdbc.Driver
  • 加载实现类:DriverManager.getConnection(uil,username,password)

DriverManager 的具体加载过程:

private static void loadInitialDrivers() {
    String drivers;
    try {
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                return System.getProperty("jdbc.drivers");
            }
        });
    } catch (Exception ex) {
        drivers = null;
    }

    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
			      //使用SPI的ServiceLoader来加载接口的实现
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator<Driver> driversIterator = loadedDrivers.iterator();
            try{
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {
            // Do nothing
            }
            return null;
        }
    });

    println("DriverManager.initialize: jdbc.drivers = " + drivers);

    if (drivers == null || drivers.equals("")) {
        return;
    }
    String[] driversList = drivers.split(":");
    println("number of Drivers:" + driversList.length);
    for (String aDriver : driversList) {
        try {
            println("DriverManager.Initialize: loading " + aDriver);
            Class.forName(aDriver, true,
                    ClassLoader.getSystemClassLoader());
        } catch (Exception ex) {
            println("DriverManager.Initialize: load failed: " + ex);
        }
    }
}
  1. 从系统变量中获取有关驱动的定义。
  2. 使用 SPI 获取驱动实现。
  3. 遍历 SPI 获取到的具体实现类,实例化各个实现类。
  4. 根据第一步得到的驱动列表实例化具体实现类。

Common Logging

通过 LogFactory.getLog 获取日志实例:

public static getLog(Class clazz) throws LogConfigurationException {
    return getFactory().getInstance(clazz);
}

LogFactory 是一个抽象类,它负责加载具体的日志实现,具体过程为:

  1. 从 JVM 系统属性获取相关配置
  2. 使用 SPI 查找得到 org.apache.commons.logging.LogFactory 实现
  3. 查找 classpath 根目录 commons-logging.properties 的属性是否设置特定的实现
  4. 使用默认实现类

Eclipse OSGI 插件体系

Eclipse使用OSGi作为插件系统的基础,动态添加新插件和停止现有插件,以动态的方式管理组件生命周期。

一般来说,插件的文件结构必须在指定目录下包含以下三个文件:

  • META-INF/MANIFEST.MF: 项目基本配置信息,版本、名称、启动器等
  • build.properties: 项目的编译配置信息,包括,源代码路径、输出路径
  • plugin.xml:插件的操作配置信息,包含弹出菜单及点击菜单后对应的操作执行类等

当eclipse启动时,会遍历plugins文件夹中的目录,扫描每个插件的清单文件MANIFEST.MF,并建立一个内部模型来记录它所找到的每个插件的信息,就实现了动态添加新的插件。

这也意味着是 eclipse 制定了一系列的规则,像是文件结构、类型、参数等。插件开发者遵循这些规则去开发自己的插件,eclipse并不需要知道插件具体是怎样开发的,只需要在启动的时候根据配置文件解析、加载到系统里就好了,是spi思想的一种体现。

Spring Factory

在 springboot 的自动装配过程中,最终会加载META-INF/spring.factories文件,而加载的过程是由SpringFactoriesLoader加载的。从 CLASSPATH 下的每个 Jar 包中搜寻所有META-INF/spring.factories配置文件,然后将解析 properties 文件,找到指定名称的配置后返回。需要注意的是,其实这里不仅仅是会去 ClassPath 路径下查找,会扫描所有路径下的 Jar 包,只不过这个文件只会在 Classpath 下的 jar 包中。

实现原理

ServiceLoader 的具体实现:

//ServiceLoader实现了Iterable接口,可以遍历所有的服务实现者
public final class ServiceLoader<S>
    implements Iterable<S>
{

    //查找配置文件的目录
    private static final String PREFIX = "META-INF/services/";

    //表示要被加载的服务的类或接口
    private final Class<S> service;

    //这个ClassLoader用来定位,加载,实例化服务提供者
    private final ClassLoader loader;

    // 访问控制上下文
    private final AccessControlContext acc;

    // 缓存已经被实例化的服务提供者,按照实例化的顺序存储
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // 迭代器
    private LazyIterator lookupIterator;

    //重新加载,就相当于重新创建ServiceLoader了,用于新的服务提供者安装到正在运行的Java虚拟机中的情况。
    public void reload() {
        //清空缓存中所有已实例化的服务提供者
        providers.clear();
        //新建一个迭代器,该迭代器会从头查找和实例化服务提供者
        lookupIterator = new LazyIterator(service, loader);
    }

    //私有构造器
    //使用指定的类加载器和服务创建服务加载器
    //如果没有指定类加载器,使用系统类加载器,就是应用类加载器。
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

    //解析失败处理的方法
    private static void fail(Class<?> service, String msg, Throwable cause)
        throws ServiceConfigurationError
    {
        throw new ServiceConfigurationError(service.getName() + ": " + msg,
                                            cause);
    }

    private static void fail(Class<?> service, String msg)
        throws ServiceConfigurationError
    {
        throw new ServiceConfigurationError(service.getName() + ": " + msg);
    }

    private static void fail(Class<?> service, URL u, int line, String msg)
        throws ServiceConfigurationError
    {
        fail(service, u + ":" + line + ": " + msg);
    }

    //解析服务提供者配置文件中的一行
    //首先去掉注释校验,然后保存
    //返回下一行行号
    //重复的配置项和已经被实例化的配置项不会被保存
    private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
                          List<String> names)
        throws IOException, ServiceConfigurationError
    {
        //读取一行
        String ln = r.readLine();
        if (ln == null) {
            return -1;
        }
        //#号代表注释行
        int ci = ln.indexOf('#');
        if (ci >= 0) ln = ln.substring(0, ci);
        ln = ln.trim();
        int n = ln.length();
        if (n != 0) {
            if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
                fail(service, u, lc, "Illegal configuration-file syntax");
            int cp = ln.codePointAt(0);
            if (!Character.isJavaIdentifierStart(cp))
                fail(service, u, lc, "Illegal provider-class name: " + ln);
            for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
                cp = ln.codePointAt(i);
                if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                    fail(service, u, lc, "Illegal provider-class name: " + ln);
            }
            if (!providers.containsKey(ln) && !names.contains(ln))
                names.add(ln);
        }
        return lc + 1;
    }

    //解析配置文件,解析指定的url配置文件
    //使用parseLine方法进行解析,未被实例化的服务提供者会被保存到缓存中去
    private Iterator<String> parse(Class<?> service, URL u)
        throws ServiceConfigurationError
    {
        InputStream in = null;
        BufferedReader r = null;
        ArrayList<String> names = new ArrayList<>();
        try {
            in = u.openStream();
            r = new BufferedReader(new InputStreamReader(in, "utf-8"));
            int lc = 1;
            while ((lc = parseLine(service, u, r, lc, names)) >= 0);
        }
        return names.iterator();
    }

    //服务提供者查找的迭代器
    private class LazyIterator
        implements Iterator<S>
    {

        Class<S> service;//服务提供者接口
        ClassLoader loader;//类加载器
        Enumeration<URL> configs = null;//保存实现类的url
        Iterator<String> pending = null;//保存实现类的全名
        String nextName = null;//迭代器中下一个实现类的全名

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            }
            if (!service.isAssignableFrom(c)) {
                fail(service, "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            }
        }

        public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    }

    //获取迭代器
    //返回遍历服务提供者的迭代器
    //以懒加载的方式加载可用的服务提供者
    //懒加载的实现是:解析配置文件和实例化服务提供者的工作由迭代器本身完成
    public Iterator<S> iterator() {
        return new Iterator<S>() {
            //按照实例化顺序返回已经缓存的服务提供者实例
            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();

            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }

            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

    //为指定的服务使用指定的类加载器来创建一个ServiceLoader
    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

    //使用线程上下文的类加载器来创建ServiceLoader
    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

    //使用扩展类加载器为指定的服务创建ServiceLoader
    //只能找到并加载已经安装到当前Java虚拟机中的服务提供者,应用程序类路径中的服务提供者将被忽略
    public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        ClassLoader prev = null;
        while (cl != null) {
            prev = cl;
            cl = cl.getParent();
        }
        return ServiceLoader.load(service, prev);
    }

    public String toString() {
        return "java.util.ServiceLoader[" + service.getName() + "]";
    }
}
  1. SeriviceLoader 实现了 Iterable 接口,所有具有迭代器属性,即 hasNext 和 next。其中主要是调用 lookupIterator 的对应 hasNext 和 next 方法,lookupIterator 为懒加载迭代器。
  2. LazyIterator 中的 hasNext 方法,静态变量 PREFIX 为 META-INF/services 目录。
  3. 通过反射 Class.forName 加载类对象,并通过 newInstance 方法创建实现类的实例,并将实例缓存,然后返回实例对象。