zoukankan      html  css  js  c++  java
  • java的反射机制,看完这篇轻松应对高级框架(超详细总结)

    导读:很多优秀的高级框架都是通过反射完成的,反射的重要性,由此可见一斑。反射机制可以使得程序更加灵活,只有学习好反射的基础语法,这样才能自己写出优秀的框架。好了一起打卡学习吧,别忘记了素质三连哦!

    1.反射机制概述

    • 反射机制有什么作用?
      • java语言中反射机制可以操作字节码文件。
      • 优点:可以直接读和修改字节码文件。通过反射机制可以操作class文件。
      • 反射在框架中使用很多,掌握它,你注定不凡。
    • 在java.lang.reflect.*;包下。
    • 反射机制相关重要的类:
      • java.lang.Class:代表字节码,代表一个类型(整个类)。
      • java.lang.reflect.Method:代表字节码中的方法字节码(类中的方法)
      • java.lang.reflect.Constructor:代表字节码中的构造方法字节码(类中的构造方法)
      • java.lang.reflect.Field:代表字节码中的属性字节码。(类中的静态变量和成员变量)
    • 要操作一个类的字节码,需要首先获取到这个类的字节码,那如何获取java.lang.Class实例呢?
      • 有三种方式:
      • 第一种:class.forName(“ ”)方法:静态方法,方法的参数是一个字符串(完整的类名),不能省略包。返回一个Class类型.(lang.class字节码文件)。使用这个方法会加载类到JVM,使static静态代码块执行(只执行一次),如果你只想让程序执行static静态代码块,只用这个方法就好了。(重点,JDBC中会用)
      • 第二种:getClass()方法,任何一个对象都有这个方法。(返回字节码文件)同一个类的对象返回的java.lang.Class字节码相同(所指的地址相同)。
      • 第三种:java语言中任何一中类型,包括基本数据类型,他都有.class属性。例如:
        • Class x = String.class;
        • Class a = int.class;

    2.通过反射机制实例化对象

    • 通过反射机制,获取Class,通过Class来实例化对象.
      • newInstance()方法:创建一个和之前反射相同类型的对象,会自动调用类的无参构造方法。没有无参构造方法,会报异常。(JDK8之后过时了,不过现在主要用的就是JDK8,sun公司长期支持JDK8和JDK11)
      • 举个栗子:
    package Day2;
    
    
    public class ReflectTest1 {
        public static void main(String[] args) {
            Class s1 = null;
            try {
                //通过反射机制获取Class
                 s1= Class.forName("Day2.User");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            Object a=null;
            try {
               a =s1.newInstance();//用反射获得的Class,创建一个对象
                System.out.println(a);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
    
        }
    }
    class User{
    
        public User() {
            System.out.println("无参构造方法!");
        }
    }
    
    • 使用反射机制创建对象有什么作用呢?
      • 反射机制让程序更加灵活。
      • java代码写一遍,在不改变java源代码的基础上,可以做到不同对象的实例化。(符合OCP原则:对外开放,修改关闭。)
      • 高级框架:底层实现原理:采用了反射机制。
      • 举个栗子:(使用IO流,properties集合,通过属性文件来实例化对象)(重点)
    package Day2;
    
    import java.io.FileNotFoundException;
    import java.io.FileReader;
    import java.io.IOException;
    import java.util.Properties;
    
    public class ReflectTest2 {
        public static void main(String[] args) throws Exception {
            //通过IO流读取flie.properties文件
            FileReader reader = new FileReader("file.properties");
            //创建属性类对象(Properties对象,Map集合,key和value都是字符串类型
            Properties pro = new Properties();
            //将文件中的数据加载到Map集合中加载
            pro.load(reader);
            reader.close();//这个流不能在没加载数据之前就关闭了
            //通过key获取value
            String a = pro.getProperty("ClassName");
            Class bb = Class.forName(a);//反射获得Class
            Object c = bb.newInstance();//通过Class,新建一个对象
            System.out.println(c);
        }
    }
    class User1 {
        public User1() {
            System.out.println("无参构造方法!");
        }
    }
    
    • 如何获取类路径下文件的绝对路径?
      • 为什么聊这个?因为上述代码可能换一个位置,当前路径名就失效了,(即移植性差)所以我们的聊聊这个。
      • 使用以下通用方式,这个文件必须保存在类路径下,(即src目录下)。
      • String path = Thread.currentThread().getContextClassLoader().getResource(" ").getPath();(重点)使用这个方法可以获取文件的绝对路径。(适用于各种操作系统)
      • 哈哈,看不懂?别急解释一下上述程序:
      • Thread.currentThread()当前线程对象
      • getContextClassLoader()使对象的方法,可以获取当前线程的类加载器对象。
      • getResource()这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源。
      • 示例:
    public class Path1 {
        public static void main(String[] args) {
            String path = Thread.currentThread().getContextClassLoader().getResource("File2").getPath();
            System.out.println(path);
        }
    }
    
    • 从执行结果可以看出:源文件和字节码文件存放在不同的位置,执行后与之对应的文件保存在out/production目录下(IDEA中)。
    • 直接以流的方式读取文件,省去获取绝对路径的环节。
      • InputStream reader = Thread.currentThread().getContextClassLoader().getResoutceAsStream("文件名“(类路径下))
      • 举个栗子:
    package Day2;
    
    
    import java.io.InputStream;
    import java.util.Properties;
    
    public class Path2 {
        public static void main(String[] args) throws Exception {
            //创建一个流,读取文件
            InputStream reader = Thread.currentThread().getContextClassLoader().getResourceAsStream("File2");
            //创建一个Properties集合,保存数据
            Properties pro = new Properties();
                pro.load(reader);//将文件数据加载到集合中
                reader.close();
            String f =pro.getProperty("NewName");
            Class bb = Class.forName(f);//反射获得Class
            Object cc = bb.newInstance();//通过Class实例化对象
            System.out.println(cc);
    
        }
    }
    class User2 {
        public User2() {
            System.out.println("无参构造方法!");
        }
    }
    
    • 资源绑定器(重点)
      • 在java.util包下,便于获取属性配置文件的内容。
      • 使用这种方式也必须将属性配置文件放置类路径下。
      • 只能绑定xxx.properties文件。并且不用写路径的扩展名。
      • 此方法简洁简单。所以,以后就用这个方法就好了,集合配合流的方法就不需要用了。
      • 举个栗子:
    import java.util.ResourceBundle;
    
    public class Path3 {
        public static void main(String[] args) {
            ResourceBundle bundle = ResourceBundle.getBundle("File2");
            String Name = bundle.getString("NewName");
            System.out.println(Name);
        }
    }
    

    3.反射Field(属性)获取字节码

    • 反射属性之前,应该获取整个类。(Class.forName(" ")方法获取。)
    • 获取整个类中的所有的Field。
      • **getFields()方法;用之前获取的整个类调用;**其获取所有public修饰的属性,返回到Field[]数组中。
      • **getDeclaredFields()方法;获取所有属性。**不受修饰符现在,返回到Field[]数组中。
      • getName()方法:可以获取属性名字。对于类也有名字,也用这个方法,对于反射获得的类还有simpleName()方法,来获取简单名字。
      • **getType()方法:获取属性的类型,返回值为类,**然后我们再通过这个得到的类用getName方法获取属性类型的名字。
      • getModifiers()方法:获取属性的修饰符列表,返回的是int类型,每个修饰符的数字是,修饰符的代号。我们可以用toString方法(Modifier.toString),Modefier为reflect的子类,返回一个字符串类型。
      • 举个栗子:
    package Day1;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.Modifier;
    
    public class FieldTest1 {
        public static void main(String[] args) {
            Class First=null;
    
            try {
                First = Class.forName("Day1.Student");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            Field[] fields = First.getDeclaredFields();
            System.out.println(fields.length);
            for(Field arg:fields) {
                int a = arg.getModifiers();
                System.out.println(Modifier.toString(a));//Modefier为reflect的子类
                Class ff = arg.getType();
                System.out.println(ff.getName());
                System.out.println(arg.getName());
            }
        }
    }
    class Student{
        public String name;
        private int age;
        protected double scroe;
        boolean flag;
    }
    
    • 通过反射机制,反编译一个类的属性Field
    • 怎么通过反射机制访问对象属性?(赋值,获取属性的值)
      • 首先获取整个Class。
      • 然后通过Class实例化对象。
      • 获取对象的属性;依据名字。
      • 属性.set(对象,222)方法:以获取的属性调用,给对象的属性赋值。
      • 反射机制代码复杂,但灵活。
      • 读取属性的值:属性.get(对象)。
      • 举个栗子:
    package Day1;
    
    import java.lang.reflect.Field;
    
    public class FieldTest2 {
        public static void main(String[] args) {
            Class fio = null;//获取Class
            Object ff = null;
            {
                try {
                    fio = Class.forName("Day1.Student");
                    ff = fio.newInstance();//实例化对象
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                }
            }
            //获取属性
            Field fl = null;
            {
                try {
                    fl = fio.getDeclaredField("name");//通过类获取属性,无法通过对象获取
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                }
            }
            //给fio对象的fl属性赋值
            try {
                fl.set(ff,"sfdsa");
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            //获取属性的值
            try {
                System.out.println(fl.get(ff));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
    
    
    • 可以用上述方法访问私有属性吗?
      • 不可以?当时我们可以用:属性.setAccessible(true)方法打破封装。
      • 因此安全性差。

    4.反射Mothod(方法)

    反射Mothod

    • 首先获取Class。
    • 获取所有的Method方法。
    • Method[] methods=类.getDeclaredMethod();方法:获取所有的Method.
    • 获取方法的返回值类型:方法.getReturnType().getSimpleName();
    • 获取修饰符列表:Modifer.toString(方法.getModifiers();
    • 获取方法参数的类型:方法.getParameterTypes();返回Class[]数组。再调用getSimpleName()即可。
    • Mothod的反编译(不要求掌握,有兴趣的小伙伴可以自行了解一下)
    • 通过反射机制怎么调用对象的方法。(重点)
    • 1.获取Calss,并用Calss实例化对象。
    • 2.获取Method:使用Method logMethod = 类.getDeclaredMethod("方法名“,类型名.class,类型名.class);方法。
    • 3.调用方法:(对象,方法名,参数,返回值)
      • Object value = 方法.invoke("对象”,“参数值”,“参数值”);invoke方法返回一个Object类型。
      • 举个栗子:
    package Day1;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class MethodTest1 {
        public static void main(String[] args) {
            Class fos =null;
            try {
                 fos = Class.forName("Day1.Student1");//获取Class
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            Object fis =null;
            try {
                fis = fos.newInstance();//创建对象
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            Method method =null;
            try {
                 method = fos.getDeclaredMethod("GetScore",String.class,double.class);//获取方法
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
            Object value  = null;
            try {
                value = method.invoke(fis,"jia",520);//调用方法
    
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            System.out.println(value);
        }
    }
    class Student1{
        private String name;
        private  int age;
        private double score;
        public String   GetScore(String name,double score){
            if(score>500){
                System.out.println("恭喜成功!");
            }
            else {
                System.out.println("继续加油!");
            }
            return  name;
        }
    }
    
    • 反射机制的优点:让代码具有通用性,可变化的内容都是写到配置文件中,将来修改配置文件之后,创建的对象不一样了,调用的方法也不同了,但是java代码不需要做任何修改。非常的灵活。

    5.反射Constructor(构造方法)

    • 反射机制怎么调用构造方法?
    • 使用反射机制创建对象?
      • 1.调用无参构造方法。(newInstance方法)
      • 2.调用有参构造方法(也可以用这种方法调用无参构造方法)。
      • 第一步:先获取这个有参构造方法;Constructor con = 类.getDeclaredConstructor(int.class,String.classs)第二步:调用构造方法new对象。Object aa =con.newInstance(参数列表)。
      • 举个栗子:
    package Day1;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    
    public class ConstructorTest1 {
        public static void main(String[] args){
            Class fio=null;
            Constructor con=null;
            Object cc = null;
            try {
                //获取类Class
                fio = Class.forName("Day1.Student3");
                //获取构造方法,参数很重要
                con =fio.getDeclaredConstructor(int.class,String.class);
                cc= con.newInstance(11,"Jia");//调用构造方法new对象,这种方法没有过期
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            //输出对象
            System.out.println(cc);//自动调用toString方法
    
        }
    }
    class Student3{
        private  int age;
        private  String name;
    //构造方法
        public Student3() {
        }
    
        public Student3(int age, String name) {
            this.age = age;
            this.name = name;
            System.out.println("调用成功!");
        }
    }
    

    6.通过反射获取父类和父接口

    • 1.获取类的Class
    • 2.获取Class父类:Class superClass = 子类.getSuperclass();
    • 获取子类实现的所有接口:Class[] interfaces = 子类.getInterfaces();方法。
    • 举个栗子:
    package Day1;
    
    public class SuperClassTest1 {
        public static void main(String[] args){
            Class fos =null;
            Class superClass=null;
            try {
                //获取类的Class
                fos= Class.forName("Day1.Student5");
                //获取父类的Class
                superClass = fos.getSuperclass();
                //实例化父类对象
                Object cc = superClass.newInstance();
                System.out.println(cc);
                //获取子类的所有接口
                Class[] interfaces = superClass.getInterfaces();
                for(Class ff : interfaces){
                    //获取接口的简称,即不含包名
                    System.out.println(ff.getSimpleName());
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
    }
    //学生类
    class Student5 extends Parents{
        private  int age;
        private  String name;
        //构造方法
        public Student5() {
        }
    
        public Student5(int age, String name) {
            this.age = age;
            this.name = name;
            System.out.println("调用成功!");
        }
    }
    //家长类
    class Parents implements Study,Sport{
        public Parents() {
            super();
        }
    }
    //接口
    interface Study{
    
    }
    interface Sport{
    
    }
    

    7.扩展知识

    1.类加载器

    • 什么是类加载器?
      • 专门负责加载类的命令/工具。
      • ClassLoader
    • JDK中自带了3个类加载器
      • 启动类加载器(加载jrelib t.jar)父
      • 扩展类加载器(母)
      • 应用类加载器
    • 假设有这样一段代码:
    • String s = “abc”;
      • 代码在开始执行之前,会将所需要的类全部加载JVM。看到以上代码,类加载器会找到String.class文件,找到就加载,那么是怎么进行加载的呢?
      • 首先通过:启动类加载器”加载。
      • rt.jar中都是Jdk最基本的核心类库。
      • 如果通过“启动类加载器"加载不到的时候:会通过”扩展类加载器“加载。
      • 注意:扩展类加载器专门加载:jrelibext*.jar
      • 如果扩展类加载器没有加载到,那么会通过“应用类加载器”加载。
      • 应用类加载器:classpath中的jar包的类。(class文件)
    • java中为了保证类加载的安全,使用了双亲委派机制。
      • 优先从启动类,(父)然后是扩展类(母),最后是应用类加载器。

    2.可变长度参数

    • int…args:这就是可变长度参数。个数为0到N个。
    • 可变长只能在参数的最后一个,且只能有一个。
    • 可变成长度参数可以当做数组看待,具有length属性,有下标,调用的时候可以传一个数组。

    3.注解

    • 注解:或者叫做注释(Annotation)
    • 注解有什么作用?
      • 对程序的注释。
    • 注解是一个引用数据类型。编译之后也是生成xxx.class文件。
    • 怎么自定义注解呢?语法格式是怎么样的?
      • [修饰符列表] @interface 注解类型名{
        }
    • 注解怎么使用,用在什么地方?
    • 注解使用时语法格式时:@注解类型名
    • 可以用在构造方法,类,属性,接口,方法,注解等,注解可以修饰注解。
    • 自定义注解:
      • 如果注解中有属性,必须给其赋值。可以在注释的时候@注解名(属性=值),属性名为value时并且只有一个属性的时候,注解的时候可以不写value。定义属性的时候后面需要小括号,可以用default给属性赋默认值。
    • 注解的属性可以是哪些类型?
      • byte,short,int, long,double,boolean,char,float,String,枚举,Class类型,和其数组类型。如果数组中只有一个元素给其赋值时大括号可以省略。
    • 什么是元注解?
      • 用来标注“注解类型”的注解。
    • 常见的元注解:
      • Target注解:(用来标注,被注解的注解可以出现在哪个位置上,其由后面的小括号参数决定。)如:@Target({ElementType.Type,ElementType.MEYHOD})只允许该注解标注类,方法
      • Retention注解:用来标注“被注解的注解”最终保存在哪里。
      • @Retention(RetentionPolicy,SOURCE)表示只被保留在java源文件中。
      • @Retention(RetentionPolicy,CLASS)class文件中。
      • @Retention(RetentionPolicy,RUNTIME)class文件中,并能被注解。//RetentionPolicy是一个枚举类型。
    • JDK内置的注解。
    • java.lang包下:
    • 1.Override注解:只能注解方法,只给编译器参考。如果这个方法不是重写父类的方法,编译器报错。(标识性注解)
    • 2.Deprecated注解:表示某个类,方法已过时。为了告知别人某个类,方法等已过时。

    反射注解

    如何获取注解类的属性?
    1.获取类Class
    * Class dd = Class.forName(“完整类名”);
    2.判断类上面是否存在特定的注解。
    * if(dd(类).isAnnotationPresent(注解类名.class)
    3.如果存在特定的注解,则获取注解对象。
    * 注解类名 fs = (注解类名)dd.getAnnotation(注解类名.class);
    4.获取注解对象的属性。
    * 注解对象.属性();
    如果获取方法上的注解信息?
    1.获取类Class。
    2.获取方法。
    3.判断方法上是否存在该注解。存在的话,获取方法上的注解。调用方式为:方法.getAnnotation(注解类名.class)
    4.再通过得到的注解获得属性。
    注解在开发中的使用场景:
    举个栗子:假设存在一个注解,只能出现在类上面;当这个类有这个注解:类必须有int类型的属性,否则报异常。

    原创不易,码字不易,点个赞再走吧!

    以梦为马,不负韶华。
  • 相关阅读:
    php数组
    php 函数
    数据库操作
    PHP基础
    mysql常用函数
    10.25 (下午) 开课一个月零二十一天(抽象)
    10.25 (上午) 开课一个月零二十一天 (继承多态)
    10.24 (下午) 开课一个月零二十天 (封装)
    10.24 (上午) 开课一个月零二十天 (面向对象)
    10.23 开课一个月零十九天 (PHP数组)
  • 原文地址:https://www.cnblogs.com/huangjiahuan1314520/p/12698754.html
Copyright © 2011-2022 走看看