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(),反而要反射的去做。这不是麻烦吗?

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

 

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

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

 

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

 

 

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

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

 

posted @ 2018-08-27 23:19  蜀云泉  阅读(198)  评论(0编辑  收藏  举报