Java反射机制

导语

反射机制是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类的定义应该更加清晰了。
  • 反射调用类结构只是一个开始。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容