Java反射和泛型的本质

来源:互联网 发布:欧洲的经济数据 编辑:程序博客网 时间:2024/05/21 07:58

1.Class类的使用

首先,我们要知道。在面向对象的世界里,万事万物都是对象。在java中,除了静态成员、普通数据类型不是对象,其他的都是类。那么类的对象是什么呢?
其实类也是对象,类是java.lang.Class类的实例对象。那么这个对象我们如何去表示呢?

我们通过以下这段代码去理解:

public class ClassDemo1 {    public static void main(String[] args) {        Foo foo1 = new Foo();        //Foo类也是一个实例对象,Class的实例对象,但是它不可以new出来        //任何一个类都是Class类的实例对象,这个实例对象有三种表达方式        //第一种表达方式 --> 实际在告诉我们任何一个类都有一个隐含的静态成员        Class c1 = Foo.class;        //第二种表达方式 --> 已经知道该类的对象通过getClass方法        Class c2 = foo1.getClass();        /**         * 官网上对c1, c2解释为:表示了Foo类的类类型(Class type)         * 类也是对象,是Class类的对象         * 这个对象,我们成为该类的类类型         */        //不管c1 or c2代表了Foo类的类类型,一个类只能是Class类的一个实例对象        System.out.println(c1 == c2);        //第三种表示方式        Class c3 = null;        try {            //引号里面的name根据个人命名自行修改            c3 = Class.forName("com.inheritance.Foo");        }catch (ClassNotFoundException e){            e.printStackTrace();        }        //同上        System.out.println(c2 == c3);        //我们可以通过类类型创建该类的对象实例 --> 通过c1        try {            Foo foo = (Foo)c1.newInstance();        } catch (InstantiationException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        }    }}class Foo{    void print(){        System.out.println();    }}

总而言之,Class类是所有类的类,且我们知道有三种方式去得到它。类也是对象,是Class类的对象,这个对象,我们称之为类类型。

2.Java动态加载类

如果直接说动态加载,可能很多人和我一样是糊涂的,不知道动态加载到底是什么,有什么用。那接下来,将详细地讲一讲。
首先,我们要分清楚什么是编译,什么是运行。编译时刻加载类是静态加载类,运行时刻加载类是动态加载类。在前面我们讲到的Class.forName(“类的全称”),其实不仅表示类的类类型,还表示了动态加载类。

我们通过下面这段代码来看一下什么是静态加载,什么是动态加载。以下代码和例子,建议在记事本上写,然后在cmd下用javac编译。
我们首先写一个Office类:

public class Office {    public static void main(String[] args) {        if ("Word".equals(args[0])) {            Word w = new Word();            w.start();        }        if ("Excel".equals(args[0])) {            Excel e = new Excel();            e.start();        }    }}

很明显,这段代码是不能通过编译的,会告诉你这些问题:

这里写图片描述

但是,我们现在有没有想过这样一个问题,这个Word类和Excel类我们真的一定要用到吗?其实我们是不一定要用到的!

比如现在我们就写一个这个Word类:

class Word{    public static void start(){        System.out.println();    }}

好,这个时候你再去编译,会发现只有两个错了,提示你Excel类不存在。那有什么问题呢?你可能就会想,没什么问题啊,Excel类不存在当然跑不起来啊。可是Word类已经存在了啊!我又不一定要用到Excel类。所以问题就在于,new创建对象是静态加载类,在编译时刻就需要加载所有可能使用到的类。

这就导致我想用Word用不了,它总在告诉我Excel有问题,可是我不一定就要用Excel类啊。在实际情况中,我们当然希望,Word类存在我们就可以使用;Excel类不存在,当我用的时候你再告诉我不能用。比如:一个程序有100个功能,但是就是其中一个功能没写好,导致我其他99个都不能用,这是很不好的。

所以,我们希望我用的时候你再加载,不用的时候就不加载。也就是说在运行的时候加载,通过动态加载类我们就可以解决这个问题。

那我们怎么去动态加载类呢?请看下面这段代码,重新写一个OfficeBetter类:

class OfficeBetter {    public static void main(String[] args) {        try {            //动态加载类,在运行时刻加载            Class c = Class.forName(args[0]);            //通过类类型,创建该对象            /**             * 通过newInstance()创建实例化对象的时候,             * 必须进行强转,因为这个c本身只是类类型             * 如果我们往Word强转:Word wd = (Word)c.newInstance()             * 但是加载的是Excel类怎么办呢?             * 所以我们就要统一标准,去实现一个OfficeAble接口             */            OfficeAble oa = (OfficeAble) c.newInstance();            oa.start();        }        catch (Exception e){            e.printStackTrace();        }    }}

在此处我们重新写一个文件,实现一个OfficeAble的接口:

interface OfficeAble{    public void start();}

然后Word类,我们也要重新写一下,让它去实现这个接口:

class Word implements OfficeAble{    public void start(){        System.out.println("word...start");    }}

这个时候你去编译,不会有错,使用Word也不会有错。只有当你使用Excel的时候才会报错。当你想使用Excel的时候,你随便换个人只要去实现这个接口就好了,那么Excel也能用了。而且,就算你加进来了,OfficeBetter的代码也不会变,也不需要重新编译,直接运行一样可以正常使用。甚至你可以轻松地增加其他的功能,比如:PPT、OneNote等等。
如果换做之前,我们想增加功能,必须再增加代码。而且只要有一个出问题了,其他的也不能使用了。所以,我们在写功能性的类的时候,一般都会采取动态加载的方式。

3.Java获取方法信息

包括基本数据类型在内的,几乎只要是属于类里面的东西都有类类型。甚至void等关键字也有类类型,那我们怎么通过类类型去获得它的类的名称呢?

代码如下:

public class ClassDemo2 {    public static void main(String[] args) {        Class c1 = int.class;        Class c2 = String.class;        Class c3 = double.class;        Class c4 = Double.class;        Class c5 = void.class;        System.out.println(c1.getName());        System.out.println(c2.getName());        System.out.println(c3.getName());        System.out.println(c4.getSimpleName()); //不包含包名        System.out.println(c5.getName());    }}

既然我们已经通过这种方法得到了类的名称,那么接下来我们就可以得到这个类的所有的方法信息,下面是一些相关的API,先新写一个类:

public class ClassUtil {    /**     * 打印类的信息,包括类的成员函数、成员变量     * @param obj     */    public static void printClassMessage(Object obj) {        //要获取类的信息,首先要获取类的类类型        Class c = obj.getClass();   //传递的是那个子类的对象,c就是该子类的类类型        //获取类的名称        System.out.println("类的名称是:" + c.getName());        /**         * Method类,方法对象         * 一个成员方法就是一个Method对象         * getMethods()方法获取的是所有public的函数,包括继承而来的         * getDeclaredMethods()获取的是所有自己的方法,不问访问权限         */        Method[] ms = c.getMethods();        for (int i = 0; i < ms.length; i++) {            //得到方法的返回值类型的类类型            Class returnType = ms[i].getReturnType();            System.out.print(returnType.getName() + " ");            //得到方法的名称            System.out.print(ms[i].getName() + "(");            //获取参数类型-->得到的是参数列表的类型的类类型            Class[] paramTypes = ms[i].getParameterTypes();            for (Class class1 : paramTypes) {                System.out.print(class1.getName() + ",");            }            System.out.println(")");        }    }}

然后再在主函数里面调用:

public class ClassDemo3 {    public static void main(String[] args) {        String s = "hello";        ClassUtil.printClassMessage(s);    }}

我们可以在运行结果看到,所有的方法已经打印:

这里写图片描述

有兴趣的可以自己试试其他的!

4.Java获取成员变量构造函数信息

其实原理同上,直接在ClassUtil类中补充一下代码,然后直接调用就可以看到效果:

public static void printConstructMessage(Object obj) {        Class c = obj.getClass();        /**         * 构造函数也是对象         * java.lang.Constructor中封装了构造函数的信息         * getConstructors获取所有的public的构造函数         * getDeclaredConstructors得到所有的构造函数         */        Constructor[] cs = c.getDeclaredConstructors();        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(")");        }    }

也就是说,以后如果你要获取类的任何信息,你首先得到该类的类类型就可以任意施为了。

5.Java方法反射的基本操作

那我们如何通过一个对象去获取它的方法信息,并进行方法的反射操作呢?首先,让我们写一个A类,并给两个print方法:

class A {    public void print(int a, int b) {        System.out.println(a + b);    }    public void print(String a, String b) {        System.out.println(a.toUpperCase() + " " + b.toUpperCase());    }}

然后,我们通过A类实例化的对象,去获取它的类类型,并且获取它的方法。最后通过 invoke 去对它的方法进行反射操作。

public class MethodDemo1 {    public static void main(String[] args) {        /**         * 获取print(int, int)方法,首先获取类的信息         * 然后得到其类类型         */        A a1 = new A();        Class c = a1.getClass();        /**         * 获取方法名称和参数列表来决定         * getMethod获取的是public的方法         * getM=DeclaredMethod是自己声明的方法         */        try {            //Method m = c.getMethod("print", new Class[]{int.class, int.class});            Method m = c.getMethod("print", int.class, int.class);            //方法的反射操作是用m对象来操作的            //方法如果没有返回值,则返回null;有则返回相应的            //Object o = m.invoke(a1, new Object[]{10, 20});            Object o = m.invoke(a1, 10, 20);            System.out.println("======================");            Method m1 = c.getMethod("print", String.class, String.class);            o = m1.invoke(a1, "hello", "world");        }catch (Exception e){            e.printStackTrace();        }    }}

6.通过反射了解泛型的本质

泛型的本质其实就是防止错误输入,只在编译阶段有效,绕过编译就无效了,我们可以通过下面这段代码来看看:

public class MethodDemo2 {    public static void main(String[] args) {        ArrayList list = new ArrayList();        ArrayList<String> list1 = new ArrayList<String>();        list1.add("Hello");        //list1.add(20)是错误的,加不进去        Class c1 = list.getClass();        Class c2 = list.getClass();        System.out.println(c1 == c2);        //反射的操作都是编译后的操作        /**         * c1 == c2的结果返回true说明,编译之后集合的泛型是去泛型的         * Java中集合的泛型,是防止错误输入的,只在编译阶段有效         * 绕过编译就无效了         * 验证:我们通过方法的反射操作,绕过编译         */        try {            Method m = c2.getMethod("add", Object.class);            m.invoke(list1, 20);  //绕过编译就绕过了泛型            System.out.println(list1.size());            System.out.println(list1);            //现在不能用foreach遍历,或有类型转换的错误            /*for (String string : list1) {                System.out.println(string);            }*/        }catch (Exception e){            e.printStackTrace();        }    }}
0 0
原创粉丝点击