Java反射

/ 反射Java SE / 0 条评论 / 2243浏览

今天学习的内容是Java反射,在之前有尝试过应用一些反射相关的代码,但是那个时候是在网上东拼西凑的看了些博客去写的,没有系统地去学习,这篇博客记录今天学习反射的内容。

反射是另一种实例化对象的方式,是一种非常好的解耦解决方案。在Mybatis、Spring、Jdbc Driver等地方都能看到它的身影,特别是Spring框架,可以说它将反射的功能发挥到了极致,得到众多开发者的喜爱。

Class类

java.lang.Class是一个类,可以这样说,这个类是所有反射操作的源头,要想运用反射必须先获取Class对象,getClass()方法可以获取到Class类对象。

/**
 * @Author beifengtz
 * @Site www.beifengtz.com
 * @Date Created in 22:27 2019/1/21
 * @Description:
 */
public class ReflexTest {
    public static void main(String[] args) {
        String str = new String();
        System.out.println(str.getClass());
    }
}

输出结果

class java.lang.String

发现调用getClass()方法后的输出就输出了类的完整名称,找到了对象的所在位置。

Class类实例化对象

有三种方式实例化对象

package language;

/**
 * @Author beifengtz
 * @Site www.beifengtz.com
 * @Date Created in 22:27 2019/1/21
 * @Description:
 */
public class ReflexTest {
    public static void main(String[] args) {
        String str = new String();
        Class<?> cls = str.getClass();
        System.out.println(cls);
    }
}
package language;

/**
 * @Author beifengtz
 * @Site www.beifengtz.com
 * @Date Created in 22:27 2019/1/21
 * @Description:
 */
public class ReflexTest {
    public static void main(String[] args) {
        Class<?> cls = String.class;
        System.out.println(cls);
    }
}
package language;

/**
 * @Author beifengtz
 * @Site www.beifengtz.com
 * @Date Created in 22:27 2019/1/21
 * @Description:
 */
public class ReflexTest {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> cls = Class.forName("java.lang.String");
        System.out.println(cls);
    }
}

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

反射实例化对象

在平常的做法,要想实例化一个对象一般做法都是使用new关键字,而如果有了Class对象就可以利用反射来实现对象实例化操作:

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

示例:

我新建了一个regex包,并在其下创建了Book类

package regex;

/**
 * @Author beifengtz
 * @Site www.beifengtz.com
 * @Date Created in 23:07 2019/1/21
 * @Description:
 */
public class Book {
    private String title;

    private double price;

    public Book(){
        System.out.println("Book类被创建");
    }

    @Override
    public String toString() {
        return "这是一本书!title="+this.title+",price="+price;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}

下面是用反射方法实例化对象

package language;

/**
 * @Author beifengtz
 * @Site www.beifengtz.com
 * @Date Created in 22:27 2019/1/21
 * @Description:
 */
public class ReflexTest {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class<?> cls = Class.forName("regex.Book");
        Object obj = cls.newInstance(); // 调用无参构造实例化
        Book book = (Book) obj;
        System.out.println(book);
    }
}

输出结果:

Book类被创建
这是一本书!title=null,price=0.0

上面方法并没有使用new关键字也没有用import语句导入对象就实现了对象的创建,但是并不是new关键字就被完全取代了,平常的开发new使用的还是比较多。

new关键字容易造成耦合,而反射恰恰可以解决这一问题,这也是反射的优势所在,下面用一个工厂模式来展示两者的区别:

将上面的Book类进行一些修改,然后新增一个ToolFactory类

package regex;

/**
 * @Author beifengtz
 * @Site www.beifengtz.com
 * @Date Created in 23:07 2019/1/21
 * @Description:
 */
class Book {

    private double price;

    public Book(){
        System.out.println("这是一本书");
    }
}
public class ToolFactory{
    public static Object getInstance(String name){
        if ("book".equals(name)){
            return new Book();
        }else {
            return null;
        }
    }
}

当我们需要实例化Book类时可以直接调用ToolFactory.getInstance()方法就可以了

package language;

import regex.ToolFactory;

/**
 * @Author beifengtz
 * @Site www.beifengtz.com
 * @Date Created in 22:27 2019/1/21
 * @Description:
 */
public class ReflexTest {
    public static void main(String[] args){
        Object obj = ToolFactory.getInstance("book");
    }
}

现在很成功控制台打印出了“这是一本书”,说明Book类通过工厂类进行了实例化,这是用new来实例化对象的。但是现在问题来了,如果我现在要增加一个新的类,并且我仍然想用这个工厂类进行进行实例化,如果是上面的代码必须要修改原来的工厂类才能实现,那岂不是要新增100个类就得修改100次工厂类?很明显这是不可能的,但是如果使用反射就可以避免这个问题,修改后代码如下:

package regex;

/**
 * @Author beifengtz
 * @Site www.beifengtz.com
 * @Date Created in 23:07 2019/1/21
 * @Description:
 */
class Book {

    private double price;

    public Book(){
        System.out.println("这是一本书");
    }
}
class Pencil{
    private double price;

    public Pencil(){
        System.out.println("这是一支笔");
    }
}
public class ToolFactory{
    public static Object getInstance(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        return Class.forName(className).newInstance();
    }
}

新增了一个Pencil类,然后现在使用ToolFactory实例化Book类和Pencil类:

package language;


import regex.ToolFactory;

/**
 * @Author beifengtz
 * @Site www.beifengtz.com
 * @Date Created in 22:27 2019/1/21
 * @Description:
 */
public class ReflexTest {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        ToolFactory.getInstance("regex.Book");
        ToolFactory.getInstance("regex.Pencil");
    }
}

运行结果:

这是一本书
这是一支笔

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

反射调用构造

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

Book类修改如下:

package regex;

/**
 * @Author beifengtz
 * @Site www.beifengtz.com
 * @Date Created in 23:07 2019/1/21
 * @Description:
 */
public class Book {
    private String title;
    private double price;

    public Book(String title,double price){
        this.title = title;
        this.price = price;
    }

    @Override
    public String toString() {
        return "这是一本书,title="+this.title+",price="+this.price;
    }
}

此时我们仍然用上面的方法获取Book对象

package language;


/**
 * @Author beifengtz
 * @Site www.beifengtz.com
 * @Date Created in 22:27 2019/1/21
 * @Description:
 */
public class ReflexTest {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        Class.forName("regex.Book").newInstance();
    }
}

这个时候就抛出了一个异常

Exception in thread "main" java.lang.InstantiationException: regex.Book
	at java.lang.Class.newInstance(Class.java:427)
	at language.ReflexTest.main(ReflexTest.java:12)
Caused by: java.lang.NoSuchMethodException: regex.Book.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.newInstance(Class.java:412)
	... 1 more

这个异常对于Spring使用者肯定一点都不陌生吧,因为很多时候一不小心就可能造成这样的错误,反正我是遇到过它,正是因为Book类没有提供无参构造,而newInstance()方法未传入任何值表示调用无参构造,所以会抛出这个异常。

Class类中提供了一个方法可以调用有参构造:

public T newInstance(Object... initargs)
              throws InstantiationException,
                     IllegalAccessException,
                     IllegalArgumentException,
                     InvocationTargetException

实现代码:

package language;


import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * @Author beifengtz
 * @Site www.beifengtz.com
 * @Date Created in 22:27 2019/1/21
 * @Description:
 */
public class ReflexTest {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException {
        Class<?> cls = Class.forName("regex.Book");
        Constructor<?> con = cls.getConstructor(String.class,double.class); // 第一个参数是String类型,第二个参数是double。根据有参构造参数类型来传入
        Object obj = con.newInstance("深入理解Spring 4.x",89.2); // 传入有参构造的值
        System.out.println(obj);
    }
}

输出结果:

这是一本书,title=深入理解Spring 4.x,price=89.2

这样我们就实现了通过反射的方法来实现构造函数的调用。

反射调用普通方法

一个类产生之后才能调用其中的方法,并且Java中实例化对象的方式有三种:

仍然在Book类中做修改,修改如下:

package regex;

/**
 * @Author beifengtz
 * @Site www.beifengtz.com
 * @Date Created in 23:07 2019/1/21
 * @Description:
 */
public class Book {
    private String title;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}

接下来就是需要用反射的方式来调用getter和setter方法。Class类提供有两个方法获取Method:

以上两个方法都是返回的java.lang.reflect.Method类的对象,在该类中使用public Object invoke(Object obj,Object... args)方法来进行调用, 第一个参数obj是实例化的对象,后面的是方法的参数,下面是代码实现:

package language;

import java.lang.reflect.Method;

/**
 * @Author beifengtz
 * @Site www.beifengtz.com
 * @Date Created in 22:27 2019/1/21
 * @Description:
 */
public class ReflexTest {
    public static void main(String[] args) throws Exception {
        String fieldName = "title"; // 要操作的成员
        Class<?> cls = Class.forName("regex.Book");
        Object obj = cls.newInstance();
        Method setMethod = cls.getMethod("set" + initcap(fieldName),String.class);
        Method getMethod = cls.getMethod("get" + initcap(fieldName));

        setMethod.invoke(obj,"一本书的书名");
        System.out.println(getMethod.invoke(obj));
    }

    /**
     * 将传入字符串首字母改为大写
     * @param str 传入的字符串
     * @return String
     */
    public static String initcap(String str){
        return str.substring(0,1).toUpperCase() + str.substring(1);
    }
}

输出结果:

一本书的书名

这样我们就实现了通过反射的方式调用类方法,并且在调用过程中并未导入该类以及使用new来实例化类对象。

反射调用成员

调用成员的方法与构造和方法类似,这里直接给出Class类中的支持:

返回的对象都是java.lang.reflect.Field类,这个类中有两个重要的方法:

将Book类再一次修改如下:

package regex;

/**
 * @Author beifengtz
 * @Site www.beifengtz.com
 * @Date Created in 23:07 2019/1/21
 * @Description:
 */
public class Book {
    private String title;
}

此时类中未提供任何操作成员的方法,现在通过反射操作成员title:

package language;

import java.lang.reflect.Field;

/**
 * @Author beifengtz
 * @Site www.beifengtz.com
 * @Date Created in 22:27 2019/1/21
 * @Description:
 */
public class ReflexTest {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("regex.Book");
        Object obj = cls.newInstance();
        Field field = cls.getDeclaredField("title");
        field.set(obj,"这是通过反射设置的书名");
        System.out.println(field.get(obj));
    }
}

最后输出结果是:

Exception in thread "main" java.lang.IllegalAccessException: Class language.ReflexTest can not access a member of class regex.Book with modifiers "private"
	at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
	at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
	at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
	at java.lang.reflect.Field.set(Field.java:761)
	at language.ReflexTest.main(ReflexTest.java:16)

它抛出了一个异常:非法访问异常,这是因为我们的类中title成员是private,是一个私有属性,我们不能对它进行操作。但是!这些访问权限在反射面前都形同虚设,因为它可以取消访问权限的限制。在java.lang.reflect.AccessibleObject类下面(jdk1.8之后):

在这个类中提供有一个方法:public void setAccessible(boolean flag) throws SecurityException,设置是否封装。现在我们就可以无视private封装而直接对title进行操作,仅仅增加一行取消封装的代码:

package language;

import java.lang.reflect.Field;

/**
 * @Author beifengtz
 * @Site www.beifengtz.com
 * @Date Created in 22:27 2019/1/21
 * @Description:
 */
public class ReflexTest {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("regex.Book");
        Object obj = cls.newInstance();
        Field field = cls.getDeclaredField("title");
        field.setAccessible(true);  // 取消封装
        field.set(obj,"这是通过反射设置的书名");
        System.out.println(field.get(obj));
    }
}

运行结果:

这是通过反射设置的书名

虽然这样可以对其属性进行操作,但是还是使用setter和getter比较好。

微信公众号浏览体验更佳,在这里还有更多优秀文章为你奉上,快来关注吧!

北风IT之路