'); } '); } 反射机制 | Journey to paradise

反射机制


反射机制和注解简介

反射机制简介

       通过java语言中的反射机制可以操控(读/写)字节码文件。反射机制相关类在java.lang.reflect.*下,相关重要类包括:java.lang.Class(代表整个字节码,代表整个类);java.lang.reflect.Method(代表字节码中的方法字节码,代表类中的方法);java.lang.reflect.Constructor(代表字节码中的构造方法字节码,代表类中的构造方法);java.lang.reflect.Field(代表字节码中的属性字节码,代表类中的属性)...

获取类的三种方式

       要操作一个类的字节码,需要首先获取到这个类的字节码,获取java.lang.Class实例有三种方式。

要点

第一种:Class c = Class.forName()
    1、静态方法
    2、方法的参数是一个字符串。
    3、字符串需要的是一个完整类名(必须带有整个包名)

第二种:Class c = 对象.getClass()//getClass方法是Object类中的方法,任何对象都有。
第三种:Class c = 数据类型.class //Java语言中任何一种类型都有class属性。

实例一

    public class ReflectTest01 {
        public static void main(String[] args) {
            Class c = null;
            try {
                c=Class.forName("java.lang.String");//c指向String.Class文件
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    
            String s ="abc";
            Class x = s.getClass();
            System.out.println(c==x);//true,c和x内存地址相同都指向String.class
    
            Class z = String.class;//z指向String.class文件
            System.out.println(x == z);//true,x和z内存地址相同都指向
            
            Class c1 = Class.forName(com.Reflect.User);//获取User类型字节码
    Object o =c1.newInstance();//newInstance()方法调用User类的无参构造方法,完成对象           
    
        }
    }

反射机制的作用

要点

1、通过反射机制,java代码写一遍,在不改变java源代码的基础之上,可以做到不同对象的实例化,非常灵活。

2、如果你只是希望一个类的静态代码块执行,其它代码一律不执行,你可以使用:
Class.forName("完整类名");这个方法的执行会导致类加载,类加载时,静态代码块执行。

3、当文件在类的根路径(src)下时,以下路径比较通用,不受文件移植的影响
    获取文件的绝对路径:
        String path = Thread.currentThread().getContextClassLoader().getResource(name: "文件名.后缀").getPath();

    更简单的方式直接返回文件流:
        InputStream reader = Thread.currentThread().getContextClassLoader()
    .getResourceAsStream( name: "文件名.后缀");

4、java.util包下提供了一个资源绑定器ResourceBundle,便于获取属性配置文件中的内容。
    资源绑定器,只能绑定xxx.properties文件。并且这个文件必须在类路径下。
    文件扩展名也必须是properties,但在写路径的时候,路径后面的扩展名不能写。

实例一

public class ReflectTest02 {
    public static void main(String[] args) throws Exception {
        //这种方式就写死了
        //User user = new User();

        //使用反射机制,不改动代码,通过配置文件动态获取类名,可以创建出不同的类
        //通过IO流读取classinfo.properties文件(className=“java.util.Date”)

        //这种方式的路径缺点是:移植性差,在IDEA中默认的当前路径是project的根,假设离开IDEA,路径发生变化就找不到了。
        //FileReader reader = new FileReader("chaper23/classinfo.properties");
    /**
        //当文件在类的根路径(src)下时,以下路径比较通用,不受文件移植的影响
        String path = Thread.currentThread().getContextClassLoader()
        .getResource(name: "classinfo.properties").getPath();
        //采用以上的代码可以拿到一个文件的绝对路径。
        System.out.println(path); 

        FileReader reader = new FileReader(path);

        /*
        解释:
            Thread.currentThread()  当前线程对象
            getContextClassloader()  线程对象的方法,可以获取到当前线程的类加载器对象。
            getResource()  这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源。
        */
    **/
        
        //直接以流的形式返回,文件要在类路径(src)当中
        InputStream reader = Thread.currentThread().getContextClassLoader()
            .getResourceAsStream( name: "classinfo.properties");

        //创建属性类对象Map;
        Properties pro = new Properties();
        //加载
        pro.load(reader);
        //关闭流
        reader.close();
        //通过key获取value
        String className = pro.getProperty();
        System.out.println(className);

        //通过反射机制实例化对象
        Class c = Class.forName(className);
        Object obj = c.newInstance();
        System.out.println(obj);
    }
}

实例二

public class ReflectTest03 {
    public static void main(String[] args) {
        try {
            Class.forName("reflect.MyClass");
        } catch (ClassNotFoundException e) {
            e. printStackTrace();
        }
    }
public Class MyClass {
    //静态代码块在类加载时执行,并且只执行一次。
    static {
        System.out.println("MyClass类的静态代码块执行了! ");
    }
}   

实例三

public class ResourceBundleTest {
    public static void main(String[] args) {
        //资源绑定器,只能绑定xxx.properties文件。并且这个文件必须在类路径下。
        //文件扩展名也必须是properties,但在写路径的时候,路径后面的扩展名不能写。
        ResourceBundle bundle = ResourceBundle.getBundle("classinfo");
        String className = bundle.getString(key: "className");
        System.out.println(className);
    }
}

类加载器(了解)

要点

1、什么是类加载器?
    专门负责加载类的命令/工具(ClassLoader)
2、JDK中自带了3个类加载器
    启动类加载器(父加载器)
    扩展类加载器(母加载器)
    应用类加载器 
3、假设有这样一段代码:
    String s = "abc";

    代码在开始执行之前,会将所需要类全部加载到JVM当中。
    通过类加载器加载,看到以上代码类加载器会找String.class文件,找到就加载,
    那么是怎么进行加载的呢?
        首先通过"启动类加载器"加载。
            注意:启动类加载器专门加载: C:\Program Files\Java\jdk1.8.0_ 101\jre\lib\rt.jar,rt.jar中都是JDK最核心的类库。
        如果通过“启动类加载器”加载不到的时候,会通过"扩展类加载器"加载。
            注意:扩展类加载器专门加载: C:\Program Files\Java\jdk1.8.0_ 101\jre\lib\ext.jar。
        如果"扩展类加载器”没有加载到,那么会通过"应用类加载器"加载。
            注意:应用类加载器专门加载:classpath中的类。
4、java中为了保证类加载的安全,使用了双亲委派机制。
    优先从启动类加载器中加载,这个称为"父","父"无法加载到,再从扩展类加载器中加载,这个称为"母"。双亲委派,如果都加载不到,才会考虑从应用类加载器中加载。直到加载到。
到为止。

反射属性Field

要点

相关方法查文档。

实例

/*
通过反射机制访问一个java对象的属性
* */
public class ReflectTest03 {
    public static void main(String[] args) throws Exception {
        //获取类
        Class studentClass = Class.forName("Reflect.Student");
        //new类的对象
        Object obj = studentClass.newInstance();
        //获取对象no属性对象
        Field noField = studentClass.getDeclaredField("no");
        //给no对象赋值(no为public)
        noField.set(obj,1000);
        //读取no对象属性值
        System.out.println(noField.get(obj));

        //访问私有属性name
        Field nameField = studentClass.getDeclaredField("name");
        //打破封装(反射机制缺点),在外部也可以访问private属性
        nameField.setAccessible(true);
        //获取name属性值
        System.out.println(nameField.get(obj));
    }
}

反射方法Method

要点

1、可变长度参数 1、int.. . args这就是可变长度参数 语法是:类型...(注意:一定是3个点。) 2、可变长度参数要求的参数个数是:0~N个。 3、可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只能有1个。 4、可变长度参数args有length属性,可以当做一个数组看待,也可以传一个数组进去。

2、常用方法查文档

实例一

public class ReflectTest04 {
    public static void main(String[] args) throws Exception {
        //使用反射机制调用一个对象的方法
        Class studentClass = Class.forName("Student");
        //无参构造(JDK9已过时)
        Object obj = studentClass.newInstance();
        System.out.println(obj);//Student{no=0, name='null'}
        //获取无参数构造方法
        Constructor con2 = studentClass.getDeclaredConstructor();
        Object nobj2 = con2.newInstance( );
        System.out.println(nobj2);//Student{no=0, name='null'}
        //有参构造
        Constructor con = studentClass.getConstructor(int.class,String.class);
        Object nobj=con.newInstance(1001,"wangwu");
        System.out.println(nobj);//Student{no=1001, name='wangwu'}
        //获取方法
        Method setNameMethod = studentClass.getDeclaredMethod("setName",String.class);
        //调用方法
        Object retValue = setNameMethod.invoke(obj,"zhangsan");
        System.out.println(retValue);
    }
}

实例二

/*
重点:给你一个类,怎么获取这个类的父类,已经实现了哪些接口?
*/
public class ReflectTest05 {
    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、注解,或者叫做注释类型,英文单词是: Annotation
2、注解Annotation是一种引用数据类型。编译之后也是生成xxx.class文件。
3、怎么自定义注解呢?语法格式?
    [修饰符列表] @interface 注解类型名{
    }
4、注解怎么使用,用在什么地方?
    第一:注解使用时的语法格式是:@注解类型名
    第二:注解可以出现在类上、属性上、方法上、变量上等....注解还可以出现在注解类型上
5、JDK内置了哪些注解呢?
    java.lang包下的注释类型:
    掌握:
        Deprecated用@Deprecated注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。
        源代码∶
        public @interface Deprecated {//JDK8
        }
        表示被注解方法已弃用,暂时可用,但不会再更新,以后可能会删除,
        提示其他程序员尽量不要调用此方法。

    掌握:
        Override表示一个方法声明打算重写超类中的另一个方法声明。
        源代码∶
        public @interface Override {
        }
        标识性注解,给编译器做参考的。只能注解方法。
        编译器看到方法上有这个注解的时候,编译器会自动检查该方法是否重写了父类的方法,
        如果没有重写,报错。这个注解只是在编译阶段起作用,和运行期无关。

    不用掌握:
        SuppressWarnings指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中
        取消显示指定的编译器警告。
    
6、元注解
        用来标注 注解类型 的注解叫元注解,常见的元注解有:Target、Retention。
            Target注解用来标注 被注解的注解 可以出现的位置。 eg:
                Target(ElementType.METHOD):表示 被标注的注解 只能出现在方法上。

            Retention注解用来标注 被注解的注解 最终保存的位置。 eg:
                @Retention(RetentionPolicy.SOURCE):表示该注解只被保留在java源文件中。
                @Retention(RetentionPolicy.CLASS):表示该注解被保存在class文件中。
                @Retention(RetentionPolicy.RUNTIME):表示该注解被保存在class文件中,并且可以被反射机制读取。
            Retention源码:
            //元注解
            public @interface Retention {
                //属性
                RetentionPolicy value();
            }

            RetentionPolicy的源代码:
            public enum RetentionPolioy {
                SOURCE,
                CLASS,
                RUNTIME
            }
            
7、注解属性
我们通常在注解当中可以定义属性,着着像1个方法,但实际上我们称之为属性。
属性的类型可以是:
    byte short int long float double boolean char String Class 枚举类型
    以及以上每一种的数组形式。

如果注解中有属性,必须给属性赋值,除非该注解有默认值
如果注解中只有一个属性并且属性名为value,则属性名可以省略,直接写属性值

实例

//只允许该注解可以标注类、方法
@Target({ElementType.TYPE,ElementType.METHOD})
//希望这个注解可以被反射
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String name();
    String  color();
    int age()   default 18;//指定默认值
    String[] email();
}

@MyAnnotation(name="zs",color = "red",email = "zhangsan@123.com")
public class AnnotationTest {
    public static void main(String[] args) {
        doSome();//已过时
    }

    //@Override这个注解只能注解方法。
    //@Override这个注解是给编译器参考的,和运行阶段没有关系。
    //凡是java中的方法带有这个注解的,编译器都会进行编译检查,如果这个方法不是重写父类的方法,编译器报错。
        @Override
        public String toString(){
            return "toString";
        }

    //Deprecated这个注解标注的元素已过时。
    //这个注解主要是向其它程序员传达一个信息,告知已过时,有更好的解决方案存在。
    @Deprecated
        public  static  void  doSome(){
        }

        //@MyAnnotation(属性名1=属性值1,属性名2=属性值2,...)
        //如果注解中有属性,必须给属性赋值,除非该注解有默认值
        //如果注解中只有一个属性并且属性名为value,则属性名可以省略,直接写属性值
        //如果注解属性为数组且数组中只有一个元素则该属性值可以不加大括号
        @MyAnnotation(name="ww",color = "yellow",email = "ww@456.com")
        public  void doOther(){
        }
}

public class ReflectAnnotationTest {
    public static void main(String[] args) throws Exception{
        //获取类
        Class c = Class.forName("AnnotationTest");

        //判断这个类上是否有@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);
            //获取该类注解对象上的属性
        String name= myAnnotation.name();
            System.out.println(name);

            //获取AnnotationTest的doOther()方法上的注解信息
            //获取方法doOther()
            Method doOtherMethod = c.getDeclaredMethod("doOther");
            //判断该方法上是否存在MyAnnotation注解
            if(doOtherMethod.isAnnotationPresent(MyAnnotation.class)){
                MyAnnotation myAnnotation1 = doOtherMethod.getAnnotation(MyAnnotation.class);
                System.out.println(myAnnotation1.name()+" "+myAnnotation1.age());
            }
        }
    }
}

文章作者: 涂爽
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 涂爽 !
评论
 上一篇
Java中的进程 Java中的进程
Java中的进程 进程简述      进程是一个应用程序,线程是一个进程中的执行场景/执行单元。进程和进程的内存独立不共享,线
2021-12-01
下一篇 
IO流 IO流
IO流简述 IO流简述      IO流即输入输出流,通过IO可以完成硬盘文件的读和写,输入输出是相对于内存而言的。 IO流
2021-11-12
  目录