Java反射机制

作者: 一个有故事的程序员 | 来源:发表于2017-10-10 16:30 被阅读102次

    导语

    反射机制是Java程序开发语言的重要特征之一。

    主要内容

    • 认识反射
    • 理解反射作用
    • 利用反射来调用类的结构

    具体内容

    认识反射

    反射的话先通过“反”来理解,既然有“反”就有“正”,在正常情况下,一定是先有类而后再产生对象
    所谓的“反”就是指可以利用对象找到对象的出处,在Object类里面提供有一个方法:

    取得Class对象:public final Class<?> getClass()。

    范例:观察反

    import java.util.Date;  // 先有类
    public class TestDemo {
        public static void main(String args[]) {
            Date date = new Date(); // 产生对象
            System.out.println(date.getClass());
        }
    }
    

    输出结果

    class java.util.Date
    

    发现调用了getClass()方法后的输出就输出了类的完整名称,等于是找到了对象的出处。

    Class类对象实例化

    java.lang.Class是一个类,这个类是反射操作的源头,即:所有的反射都要从此类开始进行,而最关键的是这个类有三种实例化方式:

    • 第一种:调用Object类中的getClass()方法。
    import java.util.Date;
    public class TestDemo {
        public static void main(String args[]) {
            Date date = new Date();
            Class<?> cls = date.getClass();
            System.out.println(cls);
        }
    }
    
    • 第二种:使用“类.class”取得。
    import java.util.Date;
    public class TestDemo {
        public static void main(String args[]) {
            Class<?> cls = Date.class;
            System.out.println(cls);
        }
    }
    

    之前是在产生了类的实例化对象之后取得的Class对象,但是此时并没有实例化对象的产生。

    • 第三种:调用Class类提供的一个方法。实例化Class对象:public static Class<?> forName(String className) throws ClassNotFoundException。
    public class TestDemo {
        public static void main(String args[]) {
            Class<?> cls = Class.forName("java.util.Date);
            System.out.println(cls);
        }
    }
    

    此时可以不使用import语句导入一个明确的类,而类名称是采用字符串的形式进行描述的。

    反射实例化对象

    当拿到一个类的时候,肯定要直接使用关键字new进行对象的实例化操作,这属于习惯 性的做法,但是如果有了Class类对象,那么就可以做到,利用反射来实现对象实例化操作:

    • 实例化对象方法:public T newInstance() throws throws InstantiationException, IllegalAccessException。

    范例:利用反射实例化对象

    package cn.cc.demo;
    class Book {
        public Book() {
            System.out.println("Book类的无参构造方法");
        }
        @Override
        public String toString() {
            return "这是一本书!";
        }
    }
    public class TestDemo {
        public static void main(String args[]) {
            Class<?> cls = Class.forName("cn.cc.demo.Book");
            Object obj = cls.newInstance();  // 相当于使用new调用无参构造实例化
            Book book = (Book) obj;
            System.out.println(book);
        }
    }
    

    输出结果

    Book类的无参构造方法
    这是一本书!
    

    有了反射之后,以后进行对象实例化的操作不再只是单独的依靠关键字new完成了,反射也同样可以完成,但是这并不表示new就被完全取代了。

    在任何的开发之中,new是造成耦合的最大元凶,一切的耦合都起源于new。

    范例:观察工厂设计模式

    package cn.cc.test;
    
    interface Fruit {
        public void eat();
    }
    
    public class Apple implements Fruit {
        @Override
        public void eat() {
            System.out.println("吃苹果!");
        }
    }
    
    public class Factory {
        public static Fruit getInstance(String className) {
            if("apple".equals(className)) {
                return new Apple();
            }
            return null;
        }
    }
    
    public class TestFactory {
        public static void main(String args[]) {
            Fruit f = Factory.getInstance("apple");
            f.eat();
        }
    }
    

    如果此时增加了Fruit接口子类,那么就表示程序要修改工厂类。

    package cn.cc.test;
    
    interface Fruit {
        public void eat();
    }
    
    public class Apple implements Fruit {
        @Override
        public void eat() {
            System.out.println("吃苹果!");
        }
    }
    
    public class Orange implements Fruit {
        @Override
        public void eat() {
            System.out.println("吃橘子!");
        }
    }
    
    public class Factory {
        public static Fruit getInstance(String className) {
            if("apple".equals(className)) {
                return new Apple();
            } else if() {
                return new Orange();
            } else {
                return null;
            }
        }
    }
    
    public class TestFactory {
        public static void main(String args[]) {
            Fruit f = Factory.getInstance("apple");
            f.eat();
        }
    }
    

    每增加一个类就要去修改工厂类,那么如果随时都可能增加子类呢?工厂类要一直被进行修改。
    因为现在工厂类中的对象都是通过关键字new直接实例化的,而new就成了所有问题的关键点。要想解决这一问题,就只能够依靠反射来完成。

    package cn.cc.test;
    
    interface Fruit {
        public void eat();
    }
    
    public class Apple implements Fruit {
        @Override
        public void eat() {
            System.out.println("吃苹果!");
        }
    }
    
    public class Orange implements Fruit {
        @Override
        public void eat() {
            System.out.println("吃橘子!");
        }
    }
    
    public class Factory {
        public static Fruit getInstance(String className) {
            Fruit f = null;
            try {
                f = (Fruit) Class.forName(className);
            } catch(Exception e) {}
            return f;
        }
    }
    
    public class TestFactory {
        public static void main(String args[]) {
            Fruit f = Factory.getInstance("cn.cc.test.Apple");
            f.eat();
        }
    }
    

    输出结果

    吃苹果!
    

    此时的程序就真正完成了解耦合的目的,而且可扩展性非常的强。

    使用反射调用构造

    在之前 所编写的代码实际上发现都默认使用了类之中的无参构造方法,可是类中还有可能不提供无参构造。

    范例:观察代码问题
    定义Book类:

    package cn.cc.po;
    
    public class Book {
        private String title;
        private double price;
        public Book(String title, double price) {
            this.title = title;
            this.price = price;
        }
        @Overide
        public String toString() {
            return "书名:" + this.title + "  价格:" + this.price;
        }
    }
    

    定义TestDemo类:

    package cn.cc.demo;
    
    public class TestDemo {
        public static void main(String args[]) {
            Class<?> cls = Class.forName("cn.cc.po.Book");
            Object obj = cls.newInstance();  // 相当于使用new调用无参构造实例化
            System.out.println(obj);
        }
    }
    

    由于此时Book类没有提供无参构造方法,所以代码会出错。

    Exception in thread "main" java.lang.InstantiationException: cn.cc.po.Book
        at java.lang.Class.newInstance(Class.java:427)
        at cn.cc.demo.TestDemo.main(TestDemo.java:5)
    Caused by: java.lang.NoSuchMethodException: cn.cc.po.Book.<init>()
        at java.lang.Class.getConstructor0(Class.java:3082)
    ...
    

    以上所出现的错误指的就是因为当前Book类里面并没有无参构造方法,所以程序无法进行对象的实例化。在这种情况下,只能够明确的调用有参构造方法。
    在Class类里面提供有一个方法能够取得构造:

    • 取得全部构造:public Constructor<?>[] getConstructors() throws SecurityException。
    • 取得一个指定参数顺序的构造: public Constructor<T> getConstructor(Class<?>... parameterTypes)
      throws NoSuchMethodException, SecurityException。

    以上两个方法返回的都是“java.lang.reflect.Constructor”类的对象。在这个类中提供有一个明确传递有参构造内容的实例化对象的方法:public T newInstance(Object... args) throws InstantiationException,
    IllegalAccessException, IllegalArgumentException, InvocationTargetException。

    范例:明确调用类中的有参构造

    package cn.cc.demo;
    
    public class TestDemo {
        public static void main(String args[]) {
            Class<?> cls = Class.forName("cn.cc.po.Book");
            // public Book(String title, double price),找到类型
            Constructor<?> con = cls.getConstructor(String.class, double.class);
            Object obj = con.newInstance("Java开发", 79.8);  // 实例化对象
            System.out.println(obj);
        }
    }
    

    输出结果

    书名:Java开发  价格:79.8
    

    简单Java类的开发之中不管提供有多少个构造方法,请至少保留有无参构造(为反射实例化对象的时候方便)。

    反射调用方法

    类中的普通方法只有在一个类产生实例化对象之后才可以调用,并且实例化对象的方式有三种:

    • new实例化对象。
    • 克隆对象。
    • 通过反射实例化对象。

    在Class类里面提供有以下取得类中Method(方法)的操作:

    • 取得一个类中的全部方法:public Method[] getMethods() throws SecurityException。
    • 取得指定方法:public Method getMethod(String name, Class<?>... parameterTypes)
      throws NoSuchMethodException, SecurityException。

    以上的两个操作返回的是java.lang.reflect.Method类的对象,在这个类里面重点关注一个方法,调用方法:public native Object invoke(Object receiver, Object... args)
    throws IllegalAccessException, IllegalArgumentException, InvocationTargetException。

    范例:使用反射调用方法
    定义一个Book类。

    package cn.cc.po;
    
    public class Book {
        private String title;
        public void setTitle(String title) {
            this.title = title;
        }
        public String getTitle() {
            return title;
        }
    }
    

    定义TestDemo类:

    package cn.cc.demo;
    
    public class TestDemo {
        public static void main(String args[]) {
            String fieldName = "title";  // 要操作的成员
            Class<?> cls = Class.forName("cn.cc.po.Book");
            Object obj = cls.newInstance();  // 必须给出实例化对象
            Method setMet = cls.getMethod("set" + initcap(fieldName), String.class);
            Method getMet = cls.getMethod("get" + initcap(fieldName);
            setMet.invoke(obj, "Java开发");  // 等价于:Book类对象.setTitle("Java开发");
            System.out.println(getMet.invoke(obj));  // 等价于:Book类对象.getTitle();
        }
        public static String initcap(String str) {
            return str.substring(0, 1).toUpperCase() + str.substring(1);
        }
    }
    

    输出结果

    Java开发
    

    此时完全看不见具体的操作类型,也就是说利用反射可以实现任意类的指定方法的调用。

    反射调用成员

    类中的属性一定要在本类 实例化对象产生之后才可以分配内存空间。在Class类里面提供有取得成员的方法:

    • 取得全部成员:public Field[] getDeclaredFields() throws SecurityException。
    • 取得指定成员:public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException。

    返回的类型是java.lang.reflect.Field类,在这个类里面有两个重要的方法:

    • 取得属性内容:public native Object get(Object object)
      throws IllegalAccessException, IllegalArgumentException。
    • 设置属性内容:public native void set(Object object, Object value)
      throws IllegalAccessException, IllegalArgumentException。

    一般情况下,类中的私有属性,按原始的做法,此时它一定无法被外部所使用。
    在java.lang.reflect.AccessibleObjject类下面(JDK1.8之后修改):

    • Executable:下面继续继承了Constructor、Method。
    • Field。

    在这个类中提供了一个方法:public void setAccessible(boolean flag) throws SecurityException,设置是否封装。

    范例:使用反射调用成员
    定义一个Book类。

    package cn.cc.po;
    
    public class Book {
        private String title;
    }
    

    定义TestDemo类:

    package cn.cc.demo;
    
    public class TestDemo {
        public static void main(String args[]) {
            String fieldName = "title";  // 要操作的成员
            Class<?> cls = Class.forName("cn.cc.po.Book");
            Object obj = cls.newInstance();  // 必须给出实例化对象
            Field titleField = cls.getDeclaredField(fieldName);
            titleField.setAccessible(true);  // 封装取消了,否则不能调用private属性
            titleField.set(obj, "Java开发");  // 等价于:Book类对象.title = "Java开发";
            System.out.println(titleField.get(obj));  // 等价于:Book类对象.title;
        }
    }
    

    输出结果

    Java开发
    

    构造方法与普通方法也同样可以取消封装,只不过很少这样去做,而且对于属性的访问还是建议使用setter、getter方法完成。

    总结

    • 实例化对象的方式又增加了一种反射。
    • 对于简单Java类的定义应该更加清晰了。
    • 反射调用类结构只是一个开始。

    更多内容戳这里(整理好的各种文集)

    相关文章

      网友评论

      • 知识学者:好像 jdbc哪里典型的反射,这些有些模糊了,记得forname好像最好
        知识学者:@一个有故事的程序员 我是学语言的:joy: 没有精通的框架,和东西。学的杂,也不好。
        算法和理论,还是很虚。
        一个有故事的程序员:@东风冷雪 你的涉及还挺广的嘛。 我就不一样了。我现在编程就会Java,一点点的C++,还有Android。其它方面的东西其实还有,Ps,Ae,Flash,Ai。再其它的啊,就是上课老师讲过,但是没听。。。 >_>

      本文标题:Java反射机制

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