• Java反射笔记


      第一:先认识Class类

    类也是一种实例,类的实例创建有三种方式。

    类的实例对象创建的方式有两种。以下的代码详细的介绍:

    package com.company;
    import java.util.*;
    
    public class Main {
    
        public static void main(String[] args) {
    
            //类的实例创建有两种方式
            Vae xs = new Vae();
            xs.music();
            //每个类都是一个实例的对象,Vae类是一个Class类的对象,其实例表达方式有3种
            //第1种,类的class
            Class c1 = Vae.class;
            //第2种,类的实例的getClass
            Class c2 = xs.getClass();
            //第3种,类的名字,这种是最常用的方式,我们在写Java反射的时候会经常使用
            Class c3 = null;
            try {
                c3 = Class.forName("com.company.Vae");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    
            System.out.println(c1 == c2);
            System.out.println(c2 == c3);
    
            //类的实例的第2种创建方式
            try {
                Vae xs2 = (Vae) c1.newInstance();
                xs2.music();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
    
        }
    }
    
    class Vae{
        public void music(){
            System.out.println("许嵩是最佳歌手");
        }
    }

    这段代码的输出结果是:

    下面来整理一下代码,首先,类的实例的创建的两种方法:

    //第一种,最常见的new
     Vae xs = new Vae();
    
    
    //第二种,newInstance()方式,这种方式必须加try catch,否则会报错
    try {
        Vae xs2 = (Vae) c1.newInstance();
        xs2.music();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }

    其次,类实例的创建方法,其实就是获取这个类的类型,有三种:

    //第1种,类的class
    Class c1 = Vae.class;
    //第2种,类的实例的getClass
    Class c2 = xs.getClass();
    //第3种,类的名字,这种是最常用的方式,我们在写Java反射的时候会经常使用
    Class c3 = null;
    try {
        c3 = Class.forName("com.company.Vae");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }

    第三种的确是最常见的,而且c1,c2,c3是一样的,输出==的时候结果是true。

    这些就是Class类的全部知识。先记住。接下来讲解Java的动态加载和静态加载。很快,就可以触及到反射了。

      第二:Java的静态加载和动态加载

      加载分为静态加载和动态加载。

    什么是静态加载呢?你写的代码在编译的时候,就是静态加载。凡是编译不能通过的代码,都是属于静态加载的。例如类创建对象的new关键字。

    什么是动态加载呢?动态加载就是在代码运行的时候才加载的,C#里面有一个类型是dynamic,而在Java里面就是利用的我们上面讲的Class.forName

    我先来举个例子,解释一下上面两句话:

    import java.util.Scanner;
    
    public class Main {
    
        public static void main(String[] args) {
    
            Scanner scan=new Scanner(System.in);
            Start(scan.nextLine());
    
        }
    
        public static void  Start(String name)
        {
            if("Vae".equals(name)){
                Vae xs=new Vae();
                xs.music();
            }
            else{
                System.out.println("我是出口");
            }
        }

    这是我在idea里面写的代码,编译的时候,报错,说没有Vae这个类。这就说明了在new Vae的对象的时候,编译是不通过的,因为在编译的时候就已经开始加载了。这说明了,new 类的对象是属于静态加载的。

    现在我们写一个Vae类出来:

    public class Vae {
        public void music() {
            System.out.println("许嵩是最佳歌手");
        }
    }

    然后我们把Vae类名传递过去。运行,输入 Vae

    结果如下:

    非常完美。那么现在问题来了。很严重的问题。我现在不想用Vae类了,使用Vae类只能输出许嵩是最佳歌手。这个是许嵩的专属类。

    我还想去输入其它歌手怎么办?例如我想添加一个shuyunquan类,去输出蜀云泉是一名歌手。那么我们要做两件事:

    1.新写一个shuyunquan类

    2.在Main类的Start方法中,必须新加一个if语句去判断我的输入是否等于shuyunquan

    第一步是毫无疑问必须要写的,但是第二步!!!假如我们已经发布了代码,你怎么改?假如我想再增加10名歌手,你怎么改?

    一个一个的在Start方法里面加?

    这是一个很愚蠢的行为。这个时候我们需要使用我们第一步中学到的Class类的知识。

    我改动了Main类中的Start方法,如下:

    import java.util.Scanner;
    
    public class Main {
    
        public static void main(String[] args) {
    
            Scanner scan=new Scanner(System.in);
            Start(scan.nextLine());
    
        }
    
        public static void  Start(String name)
        {
            try {
                Class c=Class.forName(name);
                singer s=(singer)c.newInstance();
                s.music();
    
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
    
    
        }
    
    }

    可以看到,我使用Class.forName方法获取了我输入的类类型。然后使用newInstance方法new出了一个类的实例对象。但是newInstance方法是必须要加强制类型转换的怎么办?

    因为我输入的都是歌手类。所以我写了一个接口,定义为singer:

    interface singer {
        public void music();
    }

    然后给我的Vae类实现这个接口:

    public class Vae implements singer {
        public void music() {
            System.out.println("许嵩是最佳歌手");
        }
    }

    现在,如果我想再加10个,20个歌手进来,都是完全没有问题的。我只需要写出我需要的歌手类。然后继承这个接口。再然后,我直接输入就可以了。

    完全不需要修改我的Main类中的Start方法。这一点和QQ的在线更新啊,其它软件的升级功能啊,貌似都是这样实现的。

      第三:Java获取类的方法名,方法返回值类型,方法的参数类型

    在第一我们学到了Java如何去获取类类型,在第二我们使用了类类型去完成了Java的动态加载。这一次,我们学习下,Java如何去获取方法的详细信息。

    import java.lang.reflect.Method;
    
    public class Main {
    
        public static void main(String[] args) {
    
            Vae xs=new Vae();
            //getMethods()是获取所有的public方法,包括父类继承的,系统写的。getDeclaredMethods()是获取所有自己写的方法,public和private都获取。我喜欢getDeclaredMethods()
            //Method [] methods=xs.getClass().getMethods();
            Method [] methods=xs.getClass().getDeclaredMethods();
            for (Method temp:methods) {
                //获取方法的返回值类型
                Class returnType=temp.getReturnType();
                System.out.print("方法名"+temp.getName()+"    返回值类型"+ returnType);
                ///获取方法名
                //System.out.println(temp.getName());
                ///获取参数数组
                Class [] params=temp.getParameterTypes();
                for (Class param:params) {
                    System.out.print("      方法的参数"+param.getSimpleName()+",");
                }
                System.out.println();
    
            }
    
    
        }
    }

    第一亮点:import java.lang.reflect.Method  我们已经引用了Java反射

    第二亮点:getMethods()和getDeclaredMethods()这两个方法的区别。

    getMethods()是获取所有的public方法,包括父类继承的,系统写的。getDeclaredMethods()是获取所有自己写的方法,public和private都获取。

    我特意传进去的我写的Vae类,我们来看看我的Vae类:

    public class Vae implements singer {
        public void music() {
            System.out.println("许嵩是最佳歌手");
        }
        public int test(int a,String b) {
            return 1;
        }
    }

    里面有两个方法,参数,返回值一目了然。我们执行上面的代码,结果如下:

     完美!自己手动写写吧。

      第四:Java获取类的成员变量的名称,类型 。获取构造函数的名称,参数类型。

    由于第四和第三步是差不多的。所以我直接贴出代码和结果了

            //获取成员变量
    
            Field [] fs=xs.getClass().getFields();  //只能获取public
            Field [] fs1=xs.getClass().getDeclaredFields(); //获取所有
    
            for (Field field:fs1) {
                //成员变量的类型
                Class fieldType=field.getType();
                String typeName=fieldType.getName();
                //成员变量的名称
                String fileName=field.getName();
    
                System.out.println("变量名:"+fileName+"   变量类型"+typeName);
            }
    
            //获取构造函数信息
            Constructor [] cs=xs.getClass().getConstructors(); //获取public的构造函数
            Constructor [] cs1=xs.getClass().getDeclaredConstructors();  //获取所有的构造函数,包括public和private
            for (Constructor constructor:cs) {
                System.out.print("构造函数名称是:" + constructor.getName() + "     构造函数的参数有:");
                Class[] paramTypes = constructor.getParameterTypes();
                for (Class class1:paramTypes){
                    System.out.print(class1.getName()+",");
                }
                System.out.println();
            }

    就这么多,还是以我的Vae类为例子

    public class Vae implements singer {
    
        public int age=32;
    
        public String name="许嵩";
    
        private String love="写歌";
    
        public void music() {
            System.out.println("许嵩是最佳歌手");
        }
        public int test(int a,String b) {
            return 1;
        }
    }

    结果:

     自己敲一敲代码,记一记就完事了。下面开始讲反射。

      第五:方法的反射

    终于,我们开始方法的反射了。先看代码:

    package com.company;
    import java.lang.reflect.Method;
    
    public class Main {
    
        public static void main(String[] args) {
    
         Class c=Vae.class;
         Vae xs=new Vae();
         //方法的反射
            try {
                Method s=c.getMethod("music", int.class, int.class);
                //invoke方法就是反射的方法,Object是接收的返回值,如果是void方法,返回值是null
                Object object=s.invoke(xs,2,3);
                System.out.println(object);
                //有返回值的方法
                Method s1=c.getMethod("music", String.class, String.class);
                object=s1.invoke(xs,"许嵩","蜀云泉");
                System.out.println(object);
                //没有参数的方法
                Method s2=c.getMethod("music");
                object=s2.invoke(xs);
                System.out.println(object);
    
            } catch (Exception e) {
                e.printStackTrace();
            }
    
    
        }
    }
    
    class Vae{
        public void music(int a,int b){
            System.out.println(a+b);
        }
    
        public String music(String a,String b){
            return a+b;
        }
    
        public  void music(){ System.out.println("没有参数的空方法");}
    
    }

    看看输出的结果:

    现在来讲解一下方法的反射。Invoke方法是重点,现在写到这里应该是有两个疑问的?

    第一:我既然实例化了Vae类的对象,我为什么不直接xs.music(),反而要反射的去做。这不是麻烦吗?

    存疑,这个我暂时没想通。  

    第二:在反射方法的时候,我怎么知道参数怎么填写?有几个?什么类型的?

    这个疑问在想出来的时候就已经想到答案了,答案就是第四步,我们可以输出方法的名称和参数类型,个数还有方法的返回值类型。其实。。。这也完全没有必要,因为你的那些类都是继承了接口的,你只需要看看接口有哪些方法,就可以了。所以这个问题完全不是问题。

    其它的在代码的注释里面,已经写的很清楚了。

    还有一个反射去绕过泛型的例子待补充。我在网上搜了一下,貌似都说反射这个技能比较消耗资源,在普通情况下,是不建议使用的。反射大量应用的场景应该是写框架的时候。

    我再找找看看有没有反射的最佳实践吧。反正我学习到这里,只觉得第二步是有意思的。

  • 相关阅读:
    Java基础-3y
    对线面试官面试系列-3y
    从零单排学Redis【青铜】
    mock官方文档
    route路由组件传递参数
    axios拦截器与vue代理设置
    Sass使用方法
    Less使用方法
    Vue-cli
    Vue-组件注册
  • 原文地址:https://www.cnblogs.com/yunquan/p/9545473.html
Copyright © 2020-2023  润新知