美文网首页
java反射

java反射

作者: liangxifeng833 | 来源:发表于2017-02-17 13:48 被阅读9次

    一.Class类的使用

    1.在面向对象的世界中,完事万物都是对象
    2.java语言中静态成员和普通数据类型不是对象
    3.类也是对象,是java.lang.Class的实例对象

    class Foo{
        public void print()
        {
            System.out.println("it is Foo");
        }
    }
    public class ClassDemo1 {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            
            //Foo类的实例对象
            Foo foo1 = new Foo(); //foo1表示Foo类的实例对象
            
            /*
             * Foo这个类本身也是一个实例对象,就是Class(java.lang.Class)类的实例对象
             * /任何一个类都是Class类的实例对象, 该实例对象有三种表示方法
             */
            //第一种 --> 任何一个类都有一个隐含的静态成员变量class
            Class c1 = Foo.class; //c1表示Class类的实例对象
            
            //第二种,已知该类的对象, 通过该类对象的getClass方法表示
            Class c2 = foo1.getClass(); //c2表示Class类的实例对象
            
            /*
             * 以上c1,c2都表示Class类的实例对象,但是这个实例对象又是说Foo这个类
             * c1,c2表示Foo类的类类型 ( class type ), 因为Foo类可以理解为Class类的实例对象
             * 也就是世界万物皆对象,类也是对象,是Class类的实例对象
             * 这个对象我们称为该类的类类型
             */
            
            //不管c1 or c2都代表了Foo类的类类型,一个类只可能是Class类的一个实例对象, 所以c1=c2
            System.out.println(c1==c2);//输出: true
            
            //第三种
            Class c3 = null;
            try {
                c3 = Class.forName("com.lxf.reflect.Foo");
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(c3==c2); //输出:true
            
            //我们可以通过类的类类型创建该类的实例--->通过c1 or c2 or c3创建Foo的实例
            try {
                Foo foo2 = (Foo)c2.newInstance(); //需要有无参数的构造方法
                foo2.print();
            } catch (InstantiationException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    
    }
    

    二.静态加载

    • 在编译java源文件的时候的加载类叫做静态加载,比如:Test.java文件如下
    class Test{
      public static void main(String[] args) {
          People p = new People();
          p.eat();
      }
    }
    
    

    编译Test.java

    javac Test.java
    这时候People.class字节码文件并没有,所以在编译的时候会报错
    

    创建People.java

    class People
    {
      public void eat()
      {
            System.out.println("我喜欢美食");
      }
    }
    

    此时在编译

    先编译People.java
    javac People.java //编译后会产生People.class字节码文件
    javac Test.java     //编译后会产生Test.class字节码文件
    

    执行Test

    java Test  //会输出:我喜欢美食
    

    三.动态加载

    Java的一个强大的特性是能够动态加载一个给定名称的类,而事先不需要指导这个类的名字。这个特性使得Java的开发人员能够构造一个不需要重新编译即可扩展和修改的灵活动态的系统,在Java中,动态加载通常是调用类 java.lang.ClassforName 方法来实现;
     
    问题描述:
    例如,下面的代码在Main方法中调用ClassLoader来加载一个命令行传入的class.
    LoaderTest.java文件

    package aa
    public class LoaderTest
    {
        public static void main(String[] args)
        {
            LoadClass(args[0]);
        }
    
        public static void LoadClass(String clsName)
        {
            try
            {
                 beLoaded bl = 
                (beLoaded)Class.forName(clsName).newInstance();
                bl.PrintInfo();
             }
             catch (Exception e)
            {
                e.printStackTrace();
            }
    
          }
    }
    

    beLoaded.java文件

    package aa;
    public class beLoaded
    {
        public void PrintInfo()
        {
            System.out.println("I am be loaded!");
        }
    }
    

    上面的代码在正常情况下非常好使,并且使得整个系统可以同具体的java类分离开来,只需要传入一个class的类名即可完成功能调用。而且从扩展的角度来说,定义一些从beLoaded类上继承下来的类,并将类名通过参数传入系统,即可实现各种不同的功能类的调用,非常方便。
    在命令行上键入如下命令:

    java aa.LoaderTest aa.beLoaded
    屏幕会输出下面的内容:
    I am be loaded!

    下面我们创建一个beLoaded的子类,类名叫做beLoadedChild,代码如下:

    package aa;
    public class beLoadedChild extends beLoaded
    {
        public void PrintInfo()
        {
            System.out.println("I am be loaded and I am Child");
        }
    }
    

    在命令行上键入如下命令:

    java aa.LoaderTest aa.beLoadedChild
    屏幕会输出下面的内容:
    I am be loaded and I am Child

    通过上面的例子我们可以看出,只要设计好LoaderTest这个类和beLoaded类,就可以实现系统的扩展性,对不同的功能目标调用不同的beLoaded的子类,LoaderTest类beLoaded类是不需要重新编译的,系统十分的灵活和方便。

    四.基本数据类型,void关键字都存在类类型

            //int的类类型
            Class c1 = int.class;
            System.out.println(c1.getName());
            
            //String的类类型, String的字节码
            Class c2 = String.class;
            System.out.println(c2.getName());
            
            Class c3 = double.class;
            Class c4 = Double.class;
            Class c5 = void.class;
            Class c6 = Package.class;
    

    五.Class类的基本API

    • 获取类的所有方法
        /**
         * 打印类的信息,成员方法
         * @param obj 
         */
        public static  void printMethodMessage(Object obj)
        {
            //获取参数对象类的类类型, 参数传递的是哪个子类的对象,c就代表该子类的类类型
            Class c = obj.getClass();
            System.out.println("类名为:" + c.getName());
            
            /*
             * 获取类的成员方法:
             * 成员方法是java.lang.reflect.Method的对象
             * 一个成员方法就是一个Method对象
             * getMethods() 方法获取的是所有Public类型的函数,包括从父类继承来的
             * getDeclaredMethods() 获取的是所有该类自己声明的方法,不问访问权限
             */
            //获取参数对象类的所有方法
            Method[] methods = c.getMethods();
            for (Method method : methods) {
                //获取方法的返回值类类型
                Class returnType = method.getReturnType();
                //打印方法返回值类型
                System.out.print(returnType.getName());
                System.out.print("方法名为:" + method.getName() + " ( ");
                //获取返回值类型---->得到的是参数列表的类类型( int.class, String.class等 )
                Class[] paramTypes = method.getParameterTypes();
                for (Class c1 : paramTypes) {
                    System.out.print(c1.getName() + ",");
                }
                System.out.println(" ) ");          
            }
        }
    
    • 获取类所有的属性
        /*
         * 获取类的成员属性
         * 
         * 成员属性是java.lang.reflect.Field的对象
         * Field类封装了关于成员属性的操作
         * getFields() 方法获取的是所有public的成员变量的信息
         * getDeclaredField() 获取的是该类自己声明的成员属性的信息(包括私有)
         */
        private static void printFieldMessage(Object obj) {
            Class c = obj.getClass();
            Field[] fs = c.getDeclaredFields();
            for (Field field : fs) {
                //得到类成员属性类型的类类型(  int.class, String.class等等)
                Class fieldType = field.getType();
                //获取成员属性类型名
                String typeName  = fieldType.getName();
                //获取成员属性名
                String fieldName = field.getName();
                System.out.println(typeName + " " + fieldName);
            }
        }
    
    • 获取类所有构造方法的信息
        /*
         * 打印对象构造方法的信息
         */
        public static void printConMessage(Object obj)
        {
            Class c = obj.getClass();
            /*
             * 构造函数也是对象
             * java.lang.Constructor中封装了构造函数的信息
             * getConstructors 获取所有Pubic的构造函数
             * getDeclaredConstructors 获取所有构造方法
             */
            //Constructor[] cs = c.getConstructors();
            Constructor[] cs = c.getDeclaredConstructors();
            for (Constructor constructor : cs) {
                System.out.println(constructor.getName());
                //获取构造函数的参数列表 --- 得到的是参数列表的类类型
                Class[] paramTypes = constructor.getParameterTypes();
                for (Class class1 : paramTypes) {
                    System.out.print(class1.getName() + ", ");
                }
                System.out.println(" ) ");
            }
        }
    
    • 获取类方法,属性,构造方法的测试
        public static void main(String[] args) {
            String test = "hello";
            System.out.println("得到类的成员方法信息:");
            ClassUtil.printMethodMessage(test);
            
            System.out.println("得到类的成员属性信息:");
            ClassUtil.printFieldMessage(test);
            
            System.out.println("得到类的构造方法信息:");
            ClassUtil.printConMessage("hello world");
        }
    

    五.方法的反射

    class A{
        public void print(int a, int b)
        {
            System.out.println(a+b);
        }
    }
    
    //获取print(int, int)方法, 要获取方法,就是获取类的信息,首先获取类的类型s
        public static void main(String[] args){
            A a1 = new A();
            Class c= a1.getClass();
            /**
             * 获取方法名和参数列表
             * getMethod() 获取public方法
             * getDelcaredMethod 获取自己声明的方法
             */
                Method m = c.getMethod("print", new Class[]{int.class,int.class});
                //Method m2 =  c.getMethod("print", int.class, int.class); 和上面一行等效
                
                //方法的反射,用m对象进行方法的调用,和a1.print(10,20)效果相同
                //方法如果没有返回值则返回null, 否则返回具体的返回值
                Object o = m.invoke(a1, new Object[]{10,20}); //或m.invoke(a1, 10,20); //输出30
    }
    

    五.通过Class,Method了解泛型的本质

    • 我们先看一段代码
            ArrayList list1 = new ArrayList();
            ArrayList<String>  list2 = new ArrayList<String>();
            Class c1 = list1.getClass();
            Class c2 = list2.getClass();
            System.out.println(c1==c2);//输出结果为true
            //list2.add(100); //会报错,因为list2定义的是String类型
    

    可以看到以上list1是未定义泛型的,而list2定义为String类型的泛型,
    以上 c1==c2 打印输出 true, 说明编译后集合的泛型是 去泛型化的,也就证明了java中集合的泛型,是防止错误输入的,只在编译阶段有效,绕过编译就无效了.

    • 我们现在通过方法的反射,调用list2.add()方法添加非String类的数据
            try {
                Method m = c1.getMethod("add", Object.class);
                m.invoke(list2, 10); //绕过编译操作,等同于绕过了泛型
                System.out.println(list2.size());//输出1
            } catch (Exception e) {
                // TODO: handle exception
            }
    

    以上代码可以正常运行,我们想list2中添加了100整型,却添加成功了,说明我们的 反射机制绕过编译阶段 的,直接体现在 运行阶段

    相关文章

      网友评论

          本文标题:java反射

          本文链接:https://www.haomeiwen.com/subject/deehwttx.html