[JAVA]类和反射、JDK注解

Object类

我们所有的类都是继承Object类。

  • Object类中的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class Object {

private static native void registerNatives(); //标记为native的方法是本地方法,底层是由C++实现的
static {
registerNatives(); //这个类在初始化时会对类中其他本地方法进行注册,本地方法不是我们SE中需要学习的内容,我们会在JVM篇视频教程中进行介绍
}

//获取当前的类型Class对象,这个我们会在最后一章的反射中进行讲解,目前暂时不会用到
public final native Class<?> getClass();

//获取对象的哈希值,我们会在第五章集合类中使用到,目前各位小伙伴就暂时理解为会返回对象存放的内存地址
public native int hashCode();

//判断当前对象和给定对象是否相等,默认实现是直接用等号判断,也就是直接判断是否为同一个对象
public boolean equals(Object obj) {
return (this == obj);
}

//克隆当前对象,可以将复制一个完全一样的对象出来,包括对象的各个属性
protected native Object clone() throws CloneNotSupportedException;

//将当前对象转换为String的形式,默认情况下格式为 完整类名@十六进制哈希值
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

//唤醒一个等待当前对象锁的线程,有关锁的内容,我们会在第六章多线程部分中讲解,目前暂时不会用到
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 {
...
}

//当对象被判定为已经不再使用的“垃圾”时,在回收之前,会由JVM来调用一次此方法进行资源释放之类的操作,这同样不是SE中需要学习的内容,这个方法我们会在JVM篇视频教程中详细介绍,目前暂时不会用到
protected void finalize() throws Throwable { }
}

反射

  • 反射就是把Java类中的各个成分映射成一个个的Java对象。
  • 在运行状态中,对于任意一个类,都能够知道这个类所有的属性和方法。
  • 对于任意一个对象,都能调用它的任意一个方法和属性。
  • 这种动态获取信息及动态调整对象方法的功能叫Java的反射机制。

简而言之,我们可以通过反射机制,获取到类的一些属性,暴扣类里面有哪些字段,继承自哪个类,甚至还能获取到泛型。他的权限非常高,谨慎使用。

Java类加载机制

  • 在Java程序启动时,JVM会将一部分类(class问价)先加载(并不是所有的类都会在一开始加载),通过ClassLoader将类加载。
  • 在加载过程中,会将类的信息提取出来(存放在元空间中,1.8之前存放在用就代)。
  • 同时也会生成一个Class对象存放在堆内存。
  • 次Class对象只会存在一个,与加载的类为一对应。

class类详解

  • 三种方式获取到类的Class对象:
  • 注意Class类也是一个泛型类,只有第一种方法,能够直接获取到对应类型的Class对象,而以下两种方法使用了?通配符作为返回值,但是实际上都和第一个返回的是同一个对象:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public static void main(String[] args) throws ClassNotFoundException {
    //第一种方式
    Class<Student> clazz1 = Student.class;
    //第二种方式
    Class<? extends Student> clazz2 = new Student().getClass();
    //第三种方式
    Class<?> clazz3 = Class.forName("com.test.Student");
    System.out.println(clazz1 == clazz2);
    System.out.println(clazz1 == clazz3);

    }
    out:
    true
    true
  • 通过比较,验证了我们一开始的结论,在JVM中每个类始终只存在一个Class对象,无论通过什么方法获取,都是一样的。
  • 基本数据类型有Class对象吗:
    1
    2
    3
    4
    5
    6
    public static void main(String[] args) {
    Class<?> clazz = int.class; //基本数据类型有Class对象吗?
    System.out.println(clazz);
    }
    out:
    int
  • 实际上,基本数据类型也有对应的Class对象,而且我们不仅可以通过class关键字获取,其实本质上是定义到对应的包装类中。
  • 包装类:是一种特殊的类,它允许将基本数据类型封装成对象。
  • 每个包装类中(包括Void),都有一个获取原始类型Class方法,注意,getPrimitiveClass获取的是原始类型,并不是包装类型,只是可以使用包装类来表示。
    1
    2
    3
    4
    5
    6
    public static void main(String[] args) {
    Class<?> clazz = int.class;
    System.out.println(Integer.TYPE == int.class);
    }
    out:
    true
  • 通过对比,我们发现实际上包装类型都有一个TYPE,其实也就是基本类型的Class,那么包装类的CLass和基本类的Class一样吗:
    1
    2
    3
    4
    5
    public static void main(String[] args) {
    System.out.println(Integer.TYPE == Integer.class);
    }
    out:
    false
  • 我们发现,包装类型的Class对象并不是基本类型Class对象。数组类型也是一种类型,只是编程不可见,因此我们可以直接获取数组的Class对象:
    1
    2
    3
    4
    5
    6
    7
    8
    public static void main(String[] args) {
    Class<String[]> clazz = String[].class;
    System.out.println(clazz.getName()); //获取类名称(得到的是包名+类名的完整名称)
    System.out.println(clazz.getSimpleName());
    System.out.println(clazz.getTypeName());
    System.out.println(clazz.getClassLoader()); //获取它的类加载器
    System.out.println(clazz.cast(new Integer("10"))); //强制类型转换
    }

Class对象与多态

  • 正常情况下,我们使用instanceof进行类型比较:

    1
    2
    3
    4
    5
    6
    public static void main(String[] args) {
    String str = "";
    System.out.println(str instanceof String);
    }
    out:
    true
  • 判断一个对象是否为此接口或是类的实现或是子类:

    1
    2
    3
    4
    5
    6
    public static void main(String[] args) {
    String str = "";
    System.out.println(str.getClass() == String.class); //直接判断是否为这个类型
    }
    out:
    true
  • 果需要判断是否为子类或是接口/抽象类的实现,我们可以使用asSubClass()方法:

    1
    2
    3
    4
    public static void main(String[] args) {
    Integer i = 10;
    i.getClass().asSubclass(Number.class); //当Integer不是Number的子类时,会产生异常
    }
  • 通过getSuperclass()方法,我们可以获取到父类的Class对象:

    1
    2
    3
    4
    public static void main(String[] args) {
    Integer i = 10;
    System.out.println(i.getClass().getSuperclass());
    }

类加载器

  • 既然说Class对象和加载的类唯一对应,那如果我们手动创建一个与JDK包名一样,同时类名也保持一致,JVM会加载这个类吗?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package java.lang;

    public class String { //JDK提供的String类也是
    public static void main(String[] args) {
    System.out.println("我姓🐴,我叫🐴nb");
    }
    }
    out:
    错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
    public static void main(String[] args)
  • 但是我们明明在自己写的String类中定义了main方法啊,为什么会找不到此方法呢?

  • 实际上这是ClassLoader的双亲委派机制在保护Java程序的正常运行:

  • 同名文件优先加载JDK的类。

  • AppClassLoader自己写的加载类。

注解

预设注解

JDK预设了以下注解,作用于代码:

  • @Override -检查(仅仅是检查,不保留到运行时)该方法是否是重写方法。如果发现其父类,或者引用的借口中并没有该方法,会报编译错误。
  • @Deprecated -标记过时方法。如果使用该方法,会报编译警告。
  • @SuppressWarning -指示编译器去忽略注解中声明的警告(仅仅是编译阶段,不保留到运行时)。
  • @Functionllnterface -Java8开始支持,表示一个匿名函数或函数式接口。
  • @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。

元注解

元注解时作用于注解上的注解,用于我们编写自定义的注解:

  • @Retention -标识这个注解怎么保存,是只在代码中,还是编入class文件,或者是在运行时可以通过反射访问。
  • @Documented -标记这些注解是否包含在用户文档中。
  • Target -标记这个注解应该是那种Java成员
  • Inherited -标记这个注解是继承与哪个注解类(默认 注解并没有继承于任何子类)
  • @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。