Java笔记之Java反射

Java反射其实是指在运行状态中,我们可以知道任何一个类的方法和属性。对于任何一个对象,我们都能对他的方法和属性进行调用。我们把这种动态获取对象信息和调用对象方法的功能称之为反射机制。

Class类

①Java除去基本类型以外都是class(包括这个interface)
②class/interface的数据类型是Class
③每加载一个class,JVM就为其创建一个Class类型的实例

public final class Class{
	private Class(){...}
	//注意是private的,我们自己的程序是无法创建Class实例的
}

④一个Class实例包含了一个class的完整信息
⑤通过Class实例获取class的信息就称做反射(reflect)
⑥Class实例在JVM中是唯一的,可以用==作比较,但这种比较是精确的比较不同于instanceof的可追溯父类的比较

如何获取class的Class实例
Class cls1 = String.class;//Type.class     

String s = "Hello";
Class cls2 = s.getclass();//getclass方法       (使用的比较少)

Class cls3 = Class.forname("java.lang.String");//Class.forname(类名)方法{使用得多)
boolean cls1 == cls2//ture
boolean cls2 == cls3//ture
Class cls = String.class;
String s = (String)cls.newInstance();
//我们可以用newInstance这个方法来创建不带参数的新实例

访问字段

①Field getField(name):根据字段名获取某个public的field(包括父类)
②Field getDeclaredField(name):由字段名获取当前类的某个field(不包括父类)
③Field[] getFields():获取所有public的field(包括父类)
④Field[] getDeclaredFields():获取当前类的所有field(不包括父类)

class Person {
    public String name;
}
class Student extends Person {
    public int score;
    private int grade;
}
public class Main {
    public static void main(String[] args) throws Exception {
        Class stdClass = Student.class;
        // 获取public字段"score":
        System.out.println(stdClass.getField("score"));
        // 获取继承的public字段"name":
        System.out.println(stdClass.getField("name"));
        // 获取private字段"grade":
        System.out.println(stdClass.getDeclaredField("grade"));
    }
}

获取与设置field的值

利用反射拿到字段的一个Field实例只是第一步,我们还可以拿到一个实例对应的该字段的值。

Integer n = new Integer(123);
Class cls = n.getclass();
Feild f = cls.getDeclaredFeild("value");
//取值用get,赋值用set
f.get(n);//123  这两句话相当于n.value
f.set(n,456);//n的值就变成456了


注意这段代码实际上是有问题的,value在定义时用的肯定是private的
所以说需要在get()之前做一点声明:
调用Field.setAccessible(true)的意思是,别管这个字段是不是public,一律允许访问

调用方法

①Method getMethod(name, Class…):获取某个public的Method(包括父类)
②Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)
③Method[] getMethods():获取所有public的Method(包括父类)
④Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)

class Person {
    public String getName() {
        return "Person";
    }
}
class Student extends Person {
    public int getScore(String type) {
        return 100;
    }
    private int getGrade(int year) {
        return 1;
    }
}
public class Main {
    public static void main(String[] args) throws Exception {
        Class stdClass = Student.class;
        // 获取public方法getScore,参数为String:
        System.out.println(stdClass.getMethod("getScore", String.class));
        // 获取继承的public方法getName,无参数:
        System.out.println(stdClass.getMethod("getName"));
        // 获取private方法getGrade,参数为int:
        System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));
    }
}
Invoke
public class Main {
    public static void main(String[] args) throws Exception {
        String s = "Hello world";
        Method m = String.class.getMethod("substring", int.class);
        // 在s对象上调用该方法
        String r = (String) m.invoke(s, 6);
        //对Method实例调用invoke就相当于调用该方法
        //invoke的第一个参数是对象实例
        //在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错
        //比如这里的s就是要执行方法的实例对象,6就是方法中的Integer参数
        System.out.println(r);
    }
}
如果获取到的Method表示一个静态方法
调用静态方法时,由于无需指定实例对象
所以invoke方法传入的第一个参数永远为null

多态

通过反射调用方法时,同样遵循多态的原则:即总是调用实际类型的覆写方法(如果存在的话)。
所以说

Method m = Person.class.getMethod("hello");
m.invoke(new Student());

实际上相当于:

Person p = new Student();
p.hello();

调用构造方法

前面讲到我们可以通过newInstance创建实例但调用Class.newInstance()也是有局限的,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。
为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象和Method非常类似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回实例:

public class Main {
    public static void main(String[] args) throws Exception {
        // 获取构造方法Integer(int):
        Constructor cons1 = Integer.class.getConstructor(int.class);
        // 调用构造方法:
        Integer n1 = (Integer) cons1.newInstance(123);
        // 获取构造方法Integer(String)
        Constructor cons2 = Integer.class.getConstructor(String.class);
        Integer n2 = (Integer) cons2.newInstance("456");
    }
}
通过Class实例获取Constructor的方法
getConstructor(Class):获取某个public的Constructor;
getDeclaredConstructor(Class):获取某个Constructor;
getConstructors():获取所有public的Constructor;
getDeclaredConstructors():获取所有Constructor。

注意Constructor总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。调用非public的Constructor时,必须首先通过setAccessible(true)设置允许访问。

获取继承关系

获取父类
前面提到过了,我们可以用三种不同的方法获取一个class的Class实例,那么怎么获取他们的父类的Class实例呢。

public class Main {
    public static void main(String[] args) throws Exception {
        Class i = Integer.class;
        Class n = i.getSuperclass();//获取其父类
        }
}
//除Object外,其他任何非interface的Class都必定存在一个父类类型。

获取interface
由于一个类可能实现一个或多个接口,通过Class我们就可以查询到实现的接口类型。例如,查询Integer实现的接口:

public class Main {
    public static void main(String[] args) throws Exception {
        Class s = Integer.class;
        Class[] is = s.getInterfaces();
        //遍历打印出其所有接口
        for (Class i : is) {
            System.out.println(i);
        //运行后得到Ineger有以下接口
        //java.lang.Comparable
	//java.lang.constant.Constable
	//java.lang.constant.ConstantDesc
        }
    }
}

①getInterfaces()只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型
②对所有interface的Class调用getSuperclass()返回的是null,获取接口的父接口要用getInterfaces()
③如果一个类没有实现任何interface,那么getInterfaces()返回空数组。
继承关系
当我们判断一个实例是否是某个类型时,正常情况下,使用instanceof操作符:

Object n = new Integer(123);
boolean isDouble = n instanceof Double; // false
boolean isInteger = n instanceof Integer; // true
boolean isNumber = n instanceof Number; // true

如果是两个Class实例,要判断一个向上转型是否成立,可以调用isAssignableFrom():

Integer.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Integer
Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number
Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object
Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer

简而言之就是判断isAssignable From后面的类型可不可以赋值给前者(即后者是不是前者的子类)

发布了85 篇原创文章 · 获赞 10 · 访问量 3708

猜你喜欢

转载自blog.csdn.net/LebronGod/article/details/104679331