天天看点

JavaSE(10):反射机制和注解一、反射机制二、注解

文章目录

  • 一、反射机制
    • 1.1 基本概念
    • 1.2 获取字节码文件
    • 1.3 路径问题
    • 1.4 资源绑定器
    • 1.5 Field相关操作
      • 1.5.1 获取Filed
      • 1.5.2 反编译Field
      • 1.5.3 通过反射机制访问对象属性(重点)
    • 1.6 可变长参数
    • 1.6 Method相关操作
      • 1.6.1 反射Method
      • 1.6.2 反编译Method
      • 1.6.3 通过反射机制调用一个对象的方法(重点)
    • 1.7 Constructor相关操作
      • 1.7.1 反编译Constructor
      • 1.7.2 反射机制调用构造方法
    • 1.8 获取父类和父接口
    • 1.9 类加载器
  • 二、注解
    • 2.1 基础
    • 2.2 注解中的属性
    • 2.3 反射注解
    • 2.4 注解在开发中的作用

一、反射机制

1.1 基本概念

1 通过反射机制可以操作字节码文件(.class文件),虽然代码变复杂了,但是很灵活。

2 包:java.lang.reflect.*

  • 重要类

1 java.lang.Class:代表整个字节码。

2 java.lang.reflect.Method:代表字节码中的方法字节码。

3 java.lang.reflect.Constructor:代表字节码中的构造方法字节码。

4 java.lang.reflect.Field:代表字节码中的属性字节码(静态变量+实例变量)。

1.2 获取字节码文件

  • 三种方法

1 Class c = Class.forName(“完整类名带包名”);

该方法是一个静态方法,参数是一个字符串,这个字符串要是一个完整类名。

2 Class c = 对象.getClass();

java中每一个对象都有一个getClass()方法。

3 Class c = 任何类型.class;

java语言中任何一种类型,包括基本数据类型,它都有.class属性。

  • 获取字节码的用处

1 获取到字节码后,可以通过Class的newInstance()方法来实例化对象。这样就可以做到在不改变代码的情况下,做到不同对象的实例化。

2 一些高级框架比如ssh,ssm的底层实现原理就是反射机制。

3 注意:newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以。

  • 只执行静态代码块

1 如果你只是希望一个类的静态代码块执行,其它代码一律不执行,你可以使用:Class.forName(“完整类名”);

2 这个方法的执行会导致类加载,类加载时,静态代码块执行。

3 后面JDBC技术的时候我们会使用到这个。

  • 示例
1 以下代码是灵活的,代码不需要改动,可以修改配置文件,配置文件修改之后,可以创建出不同的实例对象。
// 创建classinfo.properties配置文件。之后想要实例化其他对象,只需修改此文件即可。
className=java.lang.String
           
import java.io.FileReader;
import java.util.Properties;
public class ReflectTest02 {
    public static void main(String[] args) throws Exception {
        // 通过IO流读取classinfo.properties文件
        FileReader reader = new FileReader("chapter05/src/classinfo.properties");
        // 创建属性类对象Map
        Properties pro = new Properties();
        // 加载
        pro.load(reader);
        // 关闭流
        reader.close();
        // 通过key获取value
        String className = pro.getProperty("className");
        // 通过反射机制实例化(创建)对象
        Class c = Class.forName(className);
        Object obj = c.newInstance();
        System.out.println(obj);
    }
}
           

1.3 路径问题

1 对于路径会存在这样一个问题,把程序换到另一个地方进行运行时,有时候路径就无效了。

2 有一种通用的方式,但是这种方式需要文件再类路径下,类路径就是src文件。

  • 示例

1 Thread.currentThread() 当前线程对象

2 getContextClassLoader() 是线程对象的方法,可以获取到当前线程的类加载器对象。

3 getResource() 【获取资源】这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源。

4 Thread.currentThread().getContextClassLoader().getResource(“classinfo.properties”) 直接以流(InputStream)的形式返回。

//创建一个classinfo.properties文件在src下。
public class AboutPath {
    public static void main(String[] args) {
    	// 获取绝对路径,该文件需要在src文件下。
        String path = Thread.currentThread().getContextClassLoader()
                .getResource("classinfo.properties").getPath();
        System.out.println(path);
    }
}
           

1.4 资源绑定器

1 java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容。

2 资源绑定器,只能绑定xxx.properties文件。并且这个文件必须在类路径下。文件扩展名也必须是properties,并且在写路径的时候,路径后面的扩展名不能写。

  • 示例
import java.util.ResourceBundle;
public class ResourceBoundleTest {
    public static void main(String[] args) {
        ResourceBundle bundle = ResourceBundle.getBundle("classinfo");
        String className = bundle.getString("className");
        System.out.println(className);
    }
}
           

1.5 Field相关操作

只要看懂重点即可。

1.5.1 获取Filed

Class studentClass = Class.forName(“com.malidong.reflect.Student”);

1获取简单类名

String simpleName = studentClass.getSimpleName();

2 获取完整类名

String className = studentClass.getName();

3 获取类中所有的public修饰的Field

Field[] fields = studentClass.getFields();

4 获取所有的Field

Field[] fs = studentClass.getDeclaredFields();

5 获取属性的修饰符列表

int i = field.getModifiers(); // 返回的修饰符是一个数字,每个数字是修饰符的代号!!!

6 可以将这个“代号”数字转换成“字符串”吗?

String modifierString = Modifier.toString(i);

7 获取属性的类型

Class fieldType = field.getType();

String fName = fieldType.getName();

String fName = fieldType.getSimpleName();

field.getName()

1.5.2 反编译Field

通过反射机制,反编译一个类的属性Field。就是把类的属性全部打印出来。
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class ReflectTest05 {
    public static void main(String[] args) throws Exception{
        // 创建这个是为了拼接字符串。
        StringBuilder s = new StringBuilder();
        Class studentClass = Class.forName("java.lang.Thread");
        s.append(Modifier.toString(studentClass.getModifiers()) + " class " + studentClass.getSimpleName() + " {\n");
        Field[] fields = studentClass.getDeclaredFields();
        for(Field field : fields){
            s.append("\t");
            s.append(Modifier.toString(field.getModifiers()));
            s.append(" ");
            s.append(field.getType().getSimpleName());
            s.append(" ");
            s.append(field.getName());
            s.append(";\n");
        }
        s.append("}");
        System.out.println(s);
    }
}
           

1.5.3 通过反射机制访问对象属性(重点)

给属性赋值set,获取属性的值get。

将框架采用,很少使用。

import java.lang.reflect.Field;
public class ReflectTest06 {
    public static void main(String[] args) throws Exception{
        // 我们不使用反射机制,怎么去访问一个对象的属性呢?
        Student s = new Student();
        // 给属性赋值
        s.no = 1111; 
        // 读属性值
        System.out.println(s.no);
        // 使用反射机制,怎么去访问一个对象的属性。(set get)
        Class studentClass = Class.forName("com.malidong.reflect.Student");
        Object obj = studentClass.newInstance(); // obj就是Student对象。(底层调用无参数构造方法)
        Field noFiled = studentClass.getDeclaredField("no"); // 获取no属性(根据属性的名称来获取Field)
        noFiled.set(obj, 22222); // 给obj对象的no属性赋值2222
        // 读取属性的值
        System.out.println(noFiled.get(obj));
        // 可以访问私有的属性吗?
        Field nameField = studentClass.getDeclaredField("name");
        // 打破封装(反射机制的缺点:打破封装,可能会给不法分子留下机会!!!)
        // 这样设置完之后,在外部也是可以访问private的。
        nameField.setAccessible(true);
        // 给name属性赋值
        nameField.set(obj, "jackson");
        // 获取name属性的值
        System.out.println(nameField.get(obj));
    }
}
           

1.6 可变长参数

1 可变长度参数要求的参数个数是:0~N个。

2 可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只能有1个。

3 可变长度参数可以当做一个数组来看待。

  • 示例
m();
m(10);
m(10, 20);
public static void m(int... args){
	System.out.println("m方法执行了!");
}
           

1.6 Method相关操作

只要看懂重点即可。

1.6.1 反射Method

1 获取类了

Class userServiceClass = Class.forName(“com.malidong.reflect.UserService”);

2 获取所有的Method(包括私有的!)

Method[] methods = userServiceClass.getDeclaredMethods();

3 获取修饰符列表

Modifier.toString(method.getModifiers())

4 获取方法的返回值类型

method.getReturnType().getSimpleName()

5 获取方法名

method.getName()

6 方法的修饰符列表(一个方法的参数可能会有多个。)

Class[] parameterTypes = method.getParameterTypes();

for(Class parameterType : parameterTypes){

System.out.println(parameterType.getSimpleName());

}

1.6.2 反编译Method

只能返回方法名,不能返回方法里的内容。反编译就是利用编译把方法打出来。
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class ReflectTest08 {
    public static void main(String[] args) throws Exception{
        StringBuilder s = new StringBuilder();
        Class userServiceClass = Class.forName("java.lang.String");
        s.append(Modifier.toString(userServiceClass.getModifiers()) + " class "+userServiceClass.getSimpleName()+" {\n");
        Method[] methods = userServiceClass.getDeclaredMethods();
        for(Method method : methods){
            s.append("\t");
            s.append(Modifier.toString(method.getModifiers()));
            s.append(" ");
            s.append(method.getReturnType().getSimpleName());
            s.append(" ");
            s.append(method.getName());
            s.append("(");
            // 参数列表
            Class[] parameterTypes = method.getParameterTypes();
            for(Class parameterType : parameterTypes){
                s.append(parameterType.getSimpleName());
                s.append(",");
            }
            // 删除指定下标位置上的字符
            s.deleteCharAt(s.length() - 1);
            s.append("){}\n");
        }
        s.append("}");
        System.out.println(s);
    }
}
           

1.6.3 通过反射机制调用一个对象的方法(重点)

反射机制,让代码很具有通用性,可变化的内容都是写到配置文件当中,将来修改配置文件之后,创建的对象不一样了,调用的方法也不同了,但是java代码不需要做任何改动。这就是反射机制的魅力。
import java.lang.reflect.Method;
public class ReflectTest09 {
    public static void main(String[] args) throws Exception{
        // 不使用反射机制,怎么调用方法
        // 创建对象
        UserService userService = new UserService();
        // 调用方法
        boolean loginSuccess = userService.login("admin","123");
        System.out.println(loginSuccess ? "登录成功" : "登录失败");
        // 使用反射机制来调用一个对象的方法该怎么做?
        Class userServiceClass = Class.forName("com.malidong.reflect.UserService");
        // 创建对象
        Object obj = userServiceClass.newInstance();
        // 获取Method
        Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);
        /*
        四要素:
        loginMethod方法
        obj对象
        "admin","123" 实参
        retValue 返回值
         */
        Object retValue = loginMethod.invoke(obj, "admin","123123");
        System.out.println(retValue);
    }
}
           

1.7 Constructor相关操作

只要看懂重点即可。

1.7.1 反编译Constructor

反编译一个类的Constructor构造方法。操作class文件,把所有的构造方法打印出来。
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
public class ReflectTest10 {
    public static void main(String[] args) throws Exception{
        StringBuilder s = new StringBuilder();
        Class vipClass = Class.forName("java.lang.String");
        s.append(Modifier.toString(vipClass.getModifiers()));
        s.append(" class ");
        s.append(vipClass.getSimpleName());
        s.append("{\n");
        // 拼接构造方法
        Constructor[] constructors = vipClass.getDeclaredConstructors();
        for(Constructor constructor : constructors){
            //public Vip(int no, String name, String birth, boolean sex) {
            s.append("\t");
            s.append(Modifier.toString(constructor.getModifiers()));
            s.append(" ");
            s.append(vipClass.getSimpleName());
            s.append("(");
            // 拼接参数
            Class[] parameterTypes = constructor.getParameterTypes();
            for(Class parameterType : parameterTypes){
                s.append(parameterType.getSimpleName());
                s.append(",");
            }
            // 删除最后下标位置上的字符
            if(parameterTypes.length > 0){
                s.deleteCharAt(s.length() - 1);
            }
            s.append("){}\n");
        }
        s.append("}");
        System.out.println(s);
    }
}
           

1.7.2 反射机制调用构造方法

通过反射机制调用构造方法实例化java对象。
import java.lang.reflect.Constructor;
public class ReflectTest11 {
    public static void main(String[] args) throws Exception{
        // 不使用反射机制怎么创建对象
        Vip v1 = new Vip();
        Vip v2 = new Vip(110, "zhangsan", "2001-10-11", true);
        // 使用反射机制怎么创建对象呢?
        Class c = Class.forName("com.malidong.reflect.Vip");
        // 调用无参数构造方法
        Object obj = c.newInstance();
        System.out.println(obj);
        // 调用有参数的构造方法怎么办?
        // 第一步:先获取到这个有参数的构造方法
        Constructor con = c.getDeclaredConstructor(int.class, String.class, String.class,boolean.class);
        // 第二步:调用构造方法new对象
        Object newObj = con.newInstance(110, "jackson", "1990-10-11", true);
        System.out.println(newObj);
        // 获取无参数构造方法
        Constructor con2 = c.getDeclaredConstructor();
        Object newObj2 = con2.newInstance();
        System.out.println(newObj2);
    }
}
           

1.8 获取父类和父接口

public class ReflectTest12 {
    public static void main(String[] args) throws Exception{
        // String举例
        Class stringClass = Class.forName("java.lang.String");
        // 获取String的父类
        Class superClass = stringClass.getSuperclass();
        System.out.println(superClass.getName());
        // 获取String类实现的所有接口(一个类可以实现多个接口。)
        Class[] interfaces = stringClass.getInterfaces();
        for(Class in : interfaces){
            System.out.println(in.getName());
        }
    }
}
           

1.9 类加载器

专门负责加载类的命令/工具。

JDK自带有3个类加载器

1启动类加载器:rt.jar

2 扩展类加载器:ext/*.jar

3 应用类加载器:classpath

假设有这样一段代码:String s = “abc”;

1 首先通过“启动类加载器”加载。注意:启动类加载器专门加载:C:\ProgramFiles\Java\jdk1.8.0_101\jre\lib\rt.jar。rt.jar中都是JDK最核心的类库。

2 如果通过“启动类加载器”加载不到的时候,会通过"扩展类加载器"加载。注意:扩展类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext*.jar。

3 如果“扩展类加载器”没有加载到,那么会通过“应用类加载器”加载。注意:应用类加载器专门加载:classpath中的类。

  • 双亲委派机制

1 java中为了保证类加载的安全,使用了双亲委派机制。

2 优先从启动类加载器中加载,这个称为“父”,“父”无法加载到,再从扩展类加载器中加载,这个称为“母”。双亲委派。如果都加载不到,才会考虑从应用类加载器中加载。直到加载到为止。

二、注解

2.1 基础

[修饰符列表] @interface 注解类型名{

}
           

1 注解又叫做注释,是一种引用数据类型,编译后也是生成.class文件。

2 注解使用时的语法格式是:@注解类型名

3 注解可以出现在类上,属性上,方法上,变量上,等等。注解还可以出现在注解类型上。

  • jdk内置注解
java.lang包下一共有三个注解类型:Deprecated,Override,SuppressWarnings。重点掌握前两个。

Override:

1 只能注解方法,是给编译器参考的,和运行阶段没有关系。

2 凡是java中的方法带有这个注解的,编译器都会进行编译检查,如果这个方法不是重写父类的方法,编译器报错,就是@override下有红曲线。

Deprecated:

1 用 @Deprecated 注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。

2 表示注解的东西已过时。调用的话,会直接被横线划掉。

  • 元注解
1 元注解是用来标注“注解类型”的“注解”。常用的有Target,Retention。

Target:

1 用来标注“被标注的注解”可以出现在哪些位置上。

2 @Target(ElementType.METHOD):表示“被标注的注解”只能出现在方法上。

3 @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})

Retention:

1 用来标注“被标注的注解”最终保存在哪里。

2 Retention(RetentionPolicy.SOURCE):表示该注解只被保留在java源文件中。

3 @Retention(RetentionPolicy.CLASS):表示该注解被保存在class文件中。

4 @Retention(RetentionPolicy.RUNTIME):表示该注解被保存在class文件中,并且可以被反射机制所读取。

2.2 注解中的属性

1 如果一个注解当中有属性,那么必须给属性赋值。(除非该属性使用default指定了默认值。)

2 如果属性名是value,并且只有一个属性,名字可以省略。@MyAnnotation(“zhangsan”)

3 属性的类型可以是: byte short int long float double boolean char String Class 枚举类型以及以上每一种的数组形式。

public @interface MyAnnotation {
    String name();
    /*
    年龄属性
     */
    int age() default 25; //属性指定默认值
}
           
public class MyAnnotationTest {
    //@MyAnnotation(属性名=属性值)
    @MyAnnotation(name = "zhangsan")
    public void doSome(){
    }
}
           

2.3 反射注解

读取注解的class文件,如果能被反射,则需要是@Retention(RetentionPolicy.RUNTIME):
public class ReflectAnnotationTest {
    public static void main(String[] args) throws Exception{
        // 获取这个类
        Class c = Class.forName("com.bjpowernode.java.annotation5.MyAnnotationTest");
        // 判断类上面是否有@MyAnnotation
        //System.out.println(c.isAnnotationPresent(MyAnnotation.class)); // true
        if(c.isAnnotationPresent(MyAnnotation.class)){
            // 获取该注解对象
            MyAnnotation myAnnotation = (MyAnnotation)c.getAnnotation(MyAnnotation.class);
            //System.out.println("类上面的注解对象" + myAnnotation); // @com.bjpowernode.java.annotation5.MyAnnotation()
            // 获取注解对象的属性怎么办?和调接口没区别。
            String value = myAnnotation.value();
            System.out.println(value);
        }

        // 判断String类上面是否存在这个注解
        Class stringClass = Class.forName("java.lang.String");
        System.out.println(stringClass.isAnnotationPresent(MyAnnotation.class)); // false
    }
}
           
  • 通过反射获取方法上的注解信息
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
    /*
    username属性
     */
    String username();
    /*
    password属性
     */
    String password();
}
           
import java.lang.reflect.Method;
public class MyAnnotationTest {

    @MyAnnotation(username = "admin", password = "456456")
    public void doSome(){

    }
    public static void main(String[] args) throws Exception{
        // 获取MyAnnotationTest的doSome()方法上面的注解信息。
        Class c = Class.forName("com.bjpowernode.java.annotation6.MyAnnotationTest");
        // 获取doSome()方法
        Method doSomeMethod = c.getDeclaredMethod("doSome");
        // 判断该方法上是否存在这个注解
        if(doSomeMethod.isAnnotationPresent(MyAnnotation.class)) {
            MyAnnotation myAnnotation = doSomeMethod.getAnnotation(MyAnnotation.class);
            System.out.println(myAnnotation.username());
            System.out.println(myAnnotation.password());
        }
    }
}
           

2.4 注解在开发中的作用

1 假设有这样一个注解,叫做:@Id

2 这个注解只能出现在类上面,当这个类上有这个注解的时候,要求这个类中必须有一个int类型的id属性。如果没有这个属性就报异常。如果有这个属性则正常执行!

/*
自定义异常
 */
public class HasNotIdPropertyException extends RuntimeException {
    public HasNotIdPropertyException(){

    }
    public HasNotIdPropertyException(String s){
        super(s);
    }
}
           
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 表示这个注解只能出现在类上面
@Target(ElementType.TYPE)
// 该注解可以被反射机制读取到
@Retention(RetentionPolicy.RUNTIME)
public @interface MustHasIdPropertyAnnotation {

}
// 这个注解@Id用来标注类,被标注的类中必须有一个int类型的id属性,没有就报异常。
           
@MustHasIdPropertyAnnotation
public class User {
    int id;
    String name;
    String password;
}
           
import java.lang.reflect.Field;

public class Test {
    public static void main(String[] args) throws Exception{
        // 获取类
        Class userClass = Class.forName("com.bjpowernode.java.annotation7.User");
        // 判断类上是否存在Id注解
        if(userClass.isAnnotationPresent(MustHasIdPropertyAnnotation.class)){
            // 当一个类上面有@MustHasIdPropertyAnnotation注解的时候,要求类中必须存在int类型的id属性
            // 如果没有int类型的id属性则报异常。
            // 获取类的属性
            Field[] fields = userClass.getDeclaredFields();
            boolean isOk = false; // 给一个默认的标记
            for(Field field : fields){
                if("id".equals(field.getName()) && "int".equals(field.getType().getSimpleName())){
                    // 表示这个类是合法的类。有@Id注解,则这个类中必须有int类型的id
                    isOk = true; // 表示合法
                    break;
                }
            }

            // 判断是否合法
            if(!isOk){
                throw new HasNotIdPropertyException("被@MustHasIdPropertyAnnotation注解标注的类中必须要有一个int类型的id属性!");
            }

        }
    }
}