11.1 注解
11.1.1 注解的理解
下面说的话可能不是人话,如果想更通俗的理解注解,跳转链接文章,个人觉得讲的5星赞。主要用标签类比了注解
- 注解也称为元数据,用于修饰解释包、类、方法、属性、构造器、局部变量等信息
- 和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息
- 在
JavaSE中,注解的功能比较简单,例如标记过时方法,忽略警告等。但在JavaEE中十分重要
11.1.2 Java内置注解
Deprecated:用来标记过时的方法、类或者成员变量Override:提示子类要重写父类的方法SuppressWarnings:抑制警告SafeVarargs:参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作FunctionalInterface:函数式接口注解
11.1.3 注解的定义
注解使用@interface关键字进行定义。如下代码定义了一个注解类TestAnnotation。它的形式和接口很像,注意区分。
public @interface TestAnnotation{}
11.1.4 元注解
元注解就是用来注解注解的注解(有点绕)。有5种元注解下面一一介绍(具体解释及案例见链接):
Retention:用来描述注解的生命周期Documented:将注解中的元素包含到Javadoc中Target:指定了注解运用的地方Inherited:说明注解可继承Repeatable:说明注解可以多次使用
11.1.5 注解的属性
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface TestAnnotation {int id();String msg();}代码分析: 对于自定义注解类来说,它拥有两个成员变量: id和msg,类型分别为int和String。此外根据元注解可知,该注解可以给一个类型进行注解,比如类、接口、枚举;此外,该注解可以保留到程序运行的时候,它会被加载进入到JVM中,所以在程序运行时可以获取到它们。
当注解有成员变量时,在使用注解时就需要对成员变量进行赋值,赋值方式为在注解括号内以value=""形式,多个属性间用逗号隔开。比如:TestAnnotation(id=3, msg="hello, annotation")。
在注解中定义属性时它的类型必须是 8 种基本数据类型外加类、接口、注解及它们的数组。
也可以在定义注解时给成员变量设置默认值,此时在使用注解时,就可以不用赋值。
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface TestAnnotation {public int id() default -1;public String msg() default "Hi";}代码分析: id和msg的默认值分别为-1和"Hi",所以在使用注解时可以无需赋值,如下所示@TestAnnotation()public class Test{}
此外还有一种特殊情况,即如果一个注解内只有一个名为value的属性时,应用这个注解可以直接将属性值填写在括号内。
public @interface Check {
String value();
}
---cut----
@Check("hi")
int a;
最后一种情况即注解没有任何属性,此时应用注解时无需括号。
11.1.6 注解与反射
当一个注解可以保留到程序运行过程中时,就可以利用反射机制去对注解进行操作。很多框架的底层都使用了这种机制。由于还没有介绍到反射,所以这里简单介绍反射和注解如何关联。
注解通过反射获取。首先可以通过Class对象的isAnnotationPresent()方法判断它是否应用了某个注解。如果应用了注解,就可以通过方法getAnnotation()或getAnnotation()来获取Annotation对象。如果获取到的Annotation对象不为空,就可以调用它们的属性方法了。
@TestAnnotation()
public class Test {
public static void main(String[] args) {
boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);
if ( hasAnnotation ) {
TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
System.out.println("id:"+testAnnotation.id());
System.out.println("msg:"+testAnnotation.msg());
}
}
}
代码分析: 第7、8行分别调用了注解类TestAnnotation对象的id和msg属性。
11.2 反射
11.2.1 Java Reflection Introduction
- 反射机制允许程序在执行期间借助于
Reflection API取得任何类的内部信息(比如成员变量,构造器,成员方法等),并能操作对象的属性和方法 - 加载完类后,在堆中产生了一个
Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息,通过这个对象得到类的结构。这个Class对象就像一面镜子,透过这个镜子可以看到类的结构,所以形象的称为反射。
11.2.2 反射机制原理图

Java反射机制可以完成以下功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时得到任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
11.2.3 反射相关的主要类
java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示构造器
11.2.4 反射的优点和缺点
- 优点:可以动态的创建和使用对象,使用灵活
- 缺点:使用反射基本是解释执行,对执行速度有影响
有一种方式可以优化速度,但是效果很有限,即关闭访问检查。
Method、Field和Constructor对象都有setAccessible(),该方法用于启动或禁用访问安全检查的开关,当参数为true时,表示取消访问检查,可以一定程度上提高反射的执行速度。
11.2.5 Class类
Class类的基本介绍
Class类也是一种类,它的继承关系如下图所示:
该类具有如下特点:
Class不是new出来的,而是系统创建的,即在类加载时,就会创建一个Class类对象在堆上- 每个类的
Class类对象,在内存中只有一份,因为类只加载一次 - 每个类的实例都会记得自己是由哪个
Class实例所生成的 - 通过
Class类对象可以完整的得到一个类的结构 Class对象是放在堆中的- 类的字节码二进制数据,是放在方法区的
Class类的常用方法
forName:根据给定的类名返回一个Class对象newInstance:创建一个类的实例getMethod:获取方法getField:获取属性getFields:获取所有属性getConstructor:获取构造器getPackage:获取类所属的包public class ClassMethod { @SuppressWarnings("all") public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException { String classAllPath = "com.reflection_.Car"; // 1. 获取Car类对应的Class对象 Class cls = Class.forName(classAllPath); // 2. 输出cls System.out.println(cls); System.out.println(cls.getClass()); // 3. 得到包名 System.out.println(cls.getPackage().getName()); // 4. 得到全类名 System.out.println(cls.getName()); // 5. 通过cls创建对象实例 Car car = (Car) cls.newInstance(); System.out.println(car.toString()); // 6. 通过反射获取属性 Field field = cls.getField("brand"); System.out.println(field); System.out.println(field.get(car)); // 7. 通过反射给属性赋值 field.set(car, "奔驰"); System.out.println(field.get(car)); // 8. 获取所有的属性 System.out.println("=====输出所有的属性====="); Field[] fields = cls.getFields(); for (Field f : fields) { System.out.println(f.getName()); } } }
获取Class对象的6种方式
Class.forName()类名.class对象.getClass()classloader.loadClass("类的全类名")基本数据类型.class包装类.TYPE
11.2.6 类加载
静态加载与动态加载
静态加载指编译时加载相关的类,如果没有则报错,依赖性强;动态加载指运行时加载需要的类,如果运行时不用该类,就不会加载,及时不存在也不会报错,降低了依赖性。
进行静态加载的时机:
new一个对象时- 当子类被加载时,父类也被加载
- 调用类中的静态成员时
进行动态加载的时机:
- 通过反射
以下用一段简单的代码说明两种加载概念:
public class LoadDemo {
public static void main(String[] args)
throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
Scanner sc = new Scanner(System.in);
String key = sc.next();
switch (key) {
case "1":
Cat cat = new Cat();
cat.eat();
break;
case "2":
Class cls = Class.forName("com.reflection_.Person");
Object o = cls.newInstance();
Method method = cls.getMethod("study");
method.invoke(o);
break;
}
}
}
//代码分析: 这段代码在编译时,由于Cat类属于静态加载,所以在编译时就会加载该类,由于该类不存在,所以编译不通过;而Person类通过反射进行加载,不会在编译时进行加载,所以即使没有Person类,编译也不会受影响,但是运行时,会检查到没有Person类而报错
类加载的过程图

- 加载阶段
将类的class文件读入内存,并为之创建一个java.lang.Class对象,此过程由类加载器完成。
- 链接阶段-验证
对class文件进行安全性检查,包括文件格式验证,元数据验证,字节码验证和符号引用验证。
- 链接阶段-准备
JVM在该阶段对静态变量分配内存并默认初始化。这些变量所使用的的内存都将在方法区中进行分配。
- 链接阶段-解析
JVM将常量池中的符号引用替换为直接引用的过程。
- 初始化
- 此阶段开始真正开始执行类中定义的代码,是执行
<clinit>()的过程 <clinit>()是由编译器按语句在源代码中出现的顺序,依次自动收集类中所有静态变量的赋值动作和静态代码块中的语句,并进行合并- 虚拟机保证一个类的
<clinit>()方法在多线程环境能被正常的加锁
11.2.7 通过反射获取类的结构(纯介绍API,上图)
java.lang.Class类的相关方法
java.lang.reflect.Field类的相关方法
java.lang.reflect.Method类的相关方法
java.lang.reflect.Constructor类的相关方法
11.2.8 通过反射创建对象
方式一:调用类中的无参构造器
方式二:调用类中的指定的构造器
利用反射创建对象的步骤:
- 获取类对象
cls - 如果要调用类的无参构造器,直接使用
cls.newInstance()即可 - 如果要调用其他有参构造器,需要使用
cls.getConstructor()或cls.getDeclaredConstructor(),前者只能获取public修饰的构造器,后者可以获取所有声明的构造器 - 获取到
Constructor对象后,调用其newInstance()方法,在小括号中声明构造函数参数类型的Class对象,即可实现通过指定构造器创建对象,以下代码简单演示了调用不同构造器的方法
public class ReflectCreateInstance {
@SuppressWarnings("all")
public static void main(String[] args)
throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class cls = Class.forName("com.reflection_.User");
// 通过无参构造器创建实例
Object o = cls.newInstance();
System.out.println(o);
// 获取第一个有参构造器
Constructor constructor = cls.getConstructor(String.class);
Object tianyichen = constructor.newInstance("tianyichen");
System.out.println(tianyichen);
// 获取第二个有参构造器
Constructor constructor1 = cls.getDeclaredConstructor(int.class, String.class);
constructor1.setAccessible(true); // 如果没有这句, 会报IllegalAccessException
Object tanke = constructor1.newInstance(20, "tanke");
System.out.println(tanke);
}
}
class User { //User类
private int age = 10;
private String name = "韩顺平教育";
public User() {//无参 public
}
public User(String name) {//public的有参构造器
this.name = name;
}
private User(int age, String name) {//private 有参构造器
this.age = age;
this.name = name;
}
public String toString() {
return "User [age=" + age + ", name=" + name + "]";
}
}
11.2.9 通过反射访问类中成员
访问属性
- 根据属性名获取
Field对象:Field field = cls.getDeclaredField(属性名) - 爆破:
field.setAccessible(true) - 访问
field.set(o, value)f.get(o),如果获取的是静态属性,则访问时o可以写成null。 ```java public class ReflectAccessField { @SuppressWarnings(“all”) public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException { // 1. 得到Class类对象 Class cls = Class.forName(“com.reflection_.Student”); Object o = cls.newInstance(); // 2. 获取public属性 Field age = cls.getField(“age”); System.out.println(age.get(o)); System.out.println(o); age.set(o, 25); System.out.println(o); // 3. 获取private属性 Field name = cls.getDeclaredField(“name”); name.setAccessible(true); System.out.println(o); name.set(o, “tianyichen”); System.out.println(o); // 4. 获取所有的属性 Field[] fields = cls.getDeclaredFields(); for (Field f : fields) {
} }System.out.println(f.getName());
}
class Student { public int age; private static String name;
public Student() {//构造器
}
public String toString() {
return "Student [age=" + age + ", name=" + name + "]";
}
}
<a name="PB2y3"></a>
##### 访问方法
1. 根据方法名和参数列表获取`Method`对象:`Method method = cls.getDeclaredMethod(方法名,xx.class)`
1. 暴破:`method.setAccessible(true)`
1. 调用:`method.invoke(o, 实参列表)`,如果是静态方法,`o`写成`null`
```java
public class ReflectInvoleMethod {
@SuppressWarnings("all")
public static void main(String[] args)
throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class cls = Class.forName("com.reflection_.Boss");
Object o = cls.newInstance();
// 调用public类型的方法
Method hi = cls.getMethod("hi", String.class);
hi.invoke(o, "reflection");
// 调用private static方法
Method say = cls.getDeclaredMethod("say", int.class, String.class, char.class);
say.setAccessible(true);
System.out.println(say.invoke(null, 5, "amazing", 'G'));
// 在反射中,如果方法有返回值,统一返回Object , 但是他运行类型和方法定义的返回类型一致
Object returnVal = say.invoke(null, 10, "BIG", 'C');
System.out.println(returnVal);
System.out.println(returnVal.getClass());
}
}
class Monster {}
class Boss {
public int age;
private static String name;
public Boss() {//构造器
}
public Monster m1() {
return new Monster();
}
private static String say(int n, String s, char c) {//静态方法
return n + " " + s + " " + c;
}
public void hi(String s) {//普通public方法
System.out.println("hi " + s);
}
}
