Java中的反射

什么是反射呢?

Java的反射机制是在运行状态中,对于任何一个类,我们都能知道这个类有哪些方法和属性。对于任何一个对象,我们都能够对它的方法和属性进行调用。我们把这种动态获取对象信息和调用对象方法的功能叫做反射机制。

我们为什么要用反射呢?

首先我们先明确两个概念,静态编译和动态编译。

静态编译:在编译时确定类型,绑定对象
动态编译:在运行是确定类型,绑定对象

动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。
而反射就是运用了动态编译创建对象。

  • 先举个例子来说明
public class FruitFactory {

    interface fruit{
        void eat();
    }

    private static class Apple implements fruit{
        @Override
        public void eat() {

        }
    }

    private static class Watermelon implements fruit{
        @Override
        public void eat() {

        }
    }

    public static fruit getInstance(String fruitName){
            fruit fName = null;
            if ("Apple".equals(fruitName)){
                fName = new Apple();
            }else if ("Watermelon".equals(fruitName)){
                fName = new Watermelon();
            }else {
                try {
                    throw new Exception("类型错误!");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return fName;

    }

    static class Demo{
        public static void main(String[] a){
            fruit apple = FruitFactory.getInstance("Apple");
            apple.eat();
        }
    }
}

当我们每增加一种水果是不得不修改工厂类中的方法,随着水果的增加,工厂会越来越臃肿,我们来对比一下反射如何做呢

public static fruit getInstance(String ClassName){
    fruit fName = null;
    try{
        fName = (fruit)Class.forName(ClassName).newInstance();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return fName;
}

static class Demo{
    public static void main(String[] a){
        fruit f= FruitFactory.getInstance("com.csii.tech.Apple");
        if(f!=null){
            f.eat();
        }
    }
}

看到了吧,这就是反射的优越性,完全不用去修改原有代码。
为什么我们在写代码的时候很少用的或时看到呢,因为它的开销很昂贵,所以尽量在最需要的时候用反射。

怎么去用反射呢?

这里需要跟大家说一下,所谓反射其实是获取类的字节码文件,也就是.class文件,那么我们就可以通过Class这个对象进行获取。
1、根据对象:对象.getClass()

Test test =  new Test();
test.getClass();

2、根据类名:类名.class

Test.getClass();

3、根据全限定类名:Class.forName(全限定类名)

"这里需要注意,通过类的全路径名获取Class对象会抛出一个异常,如果根据类路径找不到这个类那么就会抛出这个异常。"
try {
    Class test = Class.forName("com.csii.tech.Test");//字符串完整路径包含包名
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

那么这3中方式我们一般选用哪种方式呢?第一种已经创建了对象,那么这个时候就不需要去进行反射了,显得有点多此一举。第二种需要导入类的包,依赖性太强。所以我们一般选中第三种方式

如何通过反射获取类的构造方法、方法以及属性呢?

先放一个用来测试的对象

public class Test {
    private static final String TAG = "Test";
    public String publicStr = "0";
    private String fruitName = "apple";
    private int fruitNumber = 0;

    public String getFruitName() {
        return fruitName;
    }

    public void setFruitName(String fruitName) {
        this.fruitName = fruitName;
    }

    public int getFruitNumber() {
        return fruitNumber;
    }

    public void setFruitNumber(int fruitNumber) {
        this.fruitNumber = fruitNumber;
    }

    //-------构造方法------------
    public Test(){
        System.out.println("调用了无参构造方法");
    }

    public Test(String str){
        System.out.println("调用了有参构造方法");
    }


    private Test(Integer integer){
        System.out.println("调用了私有参构造方法");
    }

    public void a(){
        System.out.println("调用了公共方法");
    }

    public void a(String a){
        System.out.println("调用了公共方法"+a);
    }

    private void d(){
        System.out.println("调用了私有方法");
    }

    private void d(Integer integer){
        System.out.println("调用了私有方法"+integer.toString());
    }
}

1、 获取对象的构造方法
public class MyClass {
    public static void main(String[] str){
        try {
            Class test =Class.forName("com.lrd.lib.Test");

            System.out.println("-----------所有的构造方法-------------");
            Constructor[] declaredConstructors = test.getDeclaredConstructors();
            for (Constructor c : declaredConstructors){
                System.out.println(c);
            }

            System.out.println("-----------所有的公共构造方法-------------");
            Constructor[] constructors = test.getConstructors();
            for (Constructor c : constructors){
                System.out.println(c);
            }

            System.out.println("-----------公共&无参 构造方法-------------");
            Constructor constructor = test.getConstructor(null);
            System.out.println(constructor);

            System.out.println("-----------公共&有参 构造方法-------------");
            Constructor constructor1 = test.getConstructor(new Class[]{String.class});
            System.out.println(constructor1);

            System.out.println("-----------私有&有参 构造方法-------------");
            Constructor declaredConstructor = test.getDeclaredConstructor(new Class[]{Integer.class});
            System.out.println(declaredConstructor);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

OK,接下来看控制台输出

-----------所有的构造方法-------------
public com.lrd.lib.Test(java.lang.String)
private com.lrd.lib.Test(java.lang.Integer)
public com.lrd.lib.Test()
-----------所有的公共构造方法-------------
public com.lrd.lib.Test(java.lang.String)
public com.lrd.lib.Test()
-----------公共&无参 构造方法-------------
public com.lrd.lib.Test()
-----------公共&有参 构造方法-------------
public com.lrd.lib.Test(java.lang.String)
-----------私有&有参 构造方法-------------
private com.lrd.lib.Test(java.lang.Integer)
2、获取类的属性
System.out.println("-----------所有的字段-------------");
Field[] declaredFields = test.getDeclaredFields();
for (Field field : declaredFields){
    System.out.println(field);
}
System.out.println("-----------所有的公共字段-------------");
Field[] fields = test.getFields();
for (Field field : fields){
    System.out.println(field);
}

控制台打印结果

-----------所有的字段-------------
private static final java.lang.String com.lrd.lib.Test.TAG
public java.lang.String com.lrd.lib.Test.publicStr
private java.lang.String com.lrd.lib.Test.fruitName
private int com.lrd.lib.Test.fruitNumber
-----------所有的公共字段-------------
public java.lang.String com.lrd.lib.Test.publicStr

当我们知道字段名后就可以使用它们了,如下

System.out.println("-----------使用公有字段-------------");
Field publicStr = test.getField("publicStr");
Object obj = test.getConstructor().newInstance();
Test newTest = (Test) obj;
System.out.println("默认值:"+newTest.publicStr);
publicStr.set(obj,"1");
System.out.println("更改后:"+newTest.publicStr);

System.out.println("-----------使用私有字段-------------");
Field fruitName = test.getDeclaredField("fruitName");
Object obj2 = test.getConstructor().newInstance();
fruitName.setAccessible(true);//暴力反射
Test newTest2 = (Test) obj2;
System.out.println("默认值:"+newTest2.getFruitName());
fruitName.set(obj2,"watermelon");
System.out.println("更改后:"+newTest2.getFruitName());

控制台打印结果

-----------使用公有字段-------------
调用了无参构造方法
默认值:0
更改后:1
-----------使用私有字段-------------
调用了无参构造方法
默认值:apple
更改后:watermelon

这里有个点要注意,使用私有字段时要使用.setAccessible(true)方法暴力反射,不然就会报如下错误

java.lang.IllegalAccessException: Class com.lrd.lib.MyClass can not access a member of class com.lrd.lib.Test 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 com.lrd.lib.MyClass.main(MyClass.java:62)

3、获取类中的方法

System.out.println("-----------获取所有的方法-------------");
Method[] declaredMethods = test.getDeclaredMethods();
for (Method method : declaredMethods){
    System.out.println(method);
}
Thread.sleep(1000);
System.out.println("-----------获取所有公共的方法-------------");
Method[] methods = test.getMethods();
for (Method method : methods){
    System.out.println(method);
}
Thread.sleep(1000);
System.out.println("-----------获取指定的方法-------------");
Method a = test.getMethod("a");
System.out.println(a);
Method b = test.getDeclaredMethod("d");
System.out.println(b);
System.out.println("-----------获取指定的方法 含有参数-------------");
Method b2 = test.getDeclaredMethod("d",Integer.class);
b2.setAccessible(true);
System.out.println(b2);
//使用这个方法
Object obj = test.getConstructor().newInstance();
Object invoke = b2.invoke(obj, 100);

控制台打印结果(这里面有Object里的方法,也同时反射出来了,不清楚Object里方法的同学请移步到这里https://www.jianshu.com/p/ea26dba0acc5
)

-----------获取所有的方法-------------
public java.lang.String com.lrd.lib.Test.getFruitName()
public void com.lrd.lib.Test.setFruitName(java.lang.String)
public int com.lrd.lib.Test.getFruitNumber()
public void com.lrd.lib.Test.setFruitNumber(int)
public void com.lrd.lib.Test.a(java.lang.String)
public void com.lrd.lib.Test.a()
private void com.lrd.lib.Test.d()
private void com.lrd.lib.Test.d(java.lang.Integer)
-----------获取所有公共的方法-------------
public java.lang.String com.lrd.lib.Test.getFruitName()
public void com.lrd.lib.Test.setFruitName(java.lang.String)
public int com.lrd.lib.Test.getFruitNumber()
public void com.lrd.lib.Test.setFruitNumber(int)
public void com.lrd.lib.Test.a(java.lang.String)
public void com.lrd.lib.Test.a()
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
-----------获取指定的方法-------------
public void com.lrd.lib.Test.a()
private void com.lrd.lib.Test.d()
-----------获取指定的方法 含有参数-------------
private void com.lrd.lib.Test.d(java.lang.Integer)
调用了无参构造方法
调用了私有方法100

最后非常感谢前人的文章

https://www.cnblogs.com/zhaoguhong/p/6937364.html
https://www.cnblogs.com/yrstudy/p/6500982.html
https://baijiahao.baidu.com/s?id=1619748187138646880&wfr=spider&for=pc

屏幕快照 2019-07-24 下午4.30.40.png

你可能感兴趣的:(Java中的反射)