Java反射和泛型的本质

1##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 = list1.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();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值