[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 { }
}
1 | public class Object { |
反射
- 反射就是把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
14public 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
6public 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
6public 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
5public static void main(String[] args) {
System.out.println(Integer.TYPE == Integer.class);
}
out:
false - 我们发现,包装类型的Class对象并不是基本类型Class对象。数组类型也是一种类型,只是编程不可见,因此我们可以直接获取数组的Class对象:
1
2
3
4
5
6
7
8public 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
6public static void main(String[] args) {
String str = "";
System.out.println(str instanceof String);
}
out:
true判断一个对象是否为此接口或是类的实现或是子类:
1
2
3
4
5
6public static void main(String[] args) {
String str = "";
System.out.println(str.getClass() == String.class); //直接判断是否为这个类型
}
out:
true果需要判断是否为子类或是接口/抽象类的实现,我们可以使用asSubClass()方法:
1
2
3
4public static void main(String[] args) {
Integer i = 10;
i.getClass().asSubclass(Number.class); //当Integer不是Number的子类时,会产生异常
}通过getSuperclass()方法,我们可以获取到父类的Class对象:
1
2
3
4public 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
10package 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 开始支持,标识某注解可以在同一个声明上使用多次。