Java 反射(Reflection) 基本知识


Class类

对于每种类型的对象,Java虚拟机都会实例化一个不可变的Java .lang. class实例,该实例记录了对象的运行时属性,包括其成员和类型信息。同时该Class类还提供了创建新类和对象的能力。最重要的是,它是所有反射api的入口点。

NOTE

  • 一个 Class 对象实际上表示的是一个类型,而这个类型未必一定是一种类。例如,int 不是类, 但 int.class 是一个 Class 类型的对象。(Java 中的类型包括引用和基本类型)
  • 基本类型和它的包装类型的Class对象是不同的。
    在这里插入图片描述

获取 Class 对象

根据代码是否可以访问对象,类的名称,类型,有几种获取类的方法。

Object.getClass()

如果可以访问(存在)某个类的对象,则可以直接调用Object.getClass()。例如:

 Class<?> c1 = "foo".getClass();

 Integer i = 1;
 Class<?> c2 = i.getClass();

 // 在java中,枚举和数组也是对象
 enum E { A, B }
 Class c = A.getClass();

 byte[] bytes = new byte[1024];
 Class c3 = bytes.getClass();

Object.getClass()方法获取的是引用所指向的实际类型,而不是引用变量的类型

package reflectionTest;

public class A {

    public static void main(String[] args)  {
        B b = new C();
        System.out.println(b.getClass());
    }
}
class B{

}
class C extends B{

}
// 输出
class reflectionTest.C

.class 语法

知道类的类型但是没有类对象(实例),想要获取相应的Class对象,可以使用 .class 语法。例如:


// 基本类型
 Class<?> c1 = int.class;
 Class<?> c2 = double.class;
 
// 
 Class<?> c3 = String.class;
 Class<?> c4 = java.io.PrintStream.class;
 
// 枚举类
  enum E { A, B }
  Class<?> c5 = E.getClass();
  
// 数组类型
 Class<?> c6 = int[][][].class;
  

Class.forName()

如果已知一个类的全称(即,packagename.classname)可以通过调用Class类的静态方法Class.forName().这个方法不能用法基本类型,数组类型的全称可以参见Class.getName().
‘[’ 的数目表示数组嵌套深度(数组维度)

 [I      // int[]
 [S      // short[]
 [J      // long[]
 [B		// byte[]
 [F		// float[]
 [D		// double[]
 [C		// char[]
 [Z		// boolean[]
 [Ljava.lang.Object; // Object[]
 [Lclassname;		// class [] or interface []

示例:

 // 某个自定义类
 Class c = Class.forName("com.test.SampleClass");
 
 // 预定义类
 Class cString = Class.forName("java.lang.String");
 
 // 数组
 Class cDoubleArray = Class.forName("[D");

 Class cStringArray = Class.forName("[[Ljava.lang.String;");
 

基本类型包装类的TYPE域

虽然 class 语法可以更便捷的获取基本类型的Class对象,但是还有另一种方法,可以通过基本类型对应的包装类来获取。

 Class c = Double.TYPE;

 Class c = Void.TYPE;
 //Void.TYPE is identical to void.class.

获取类成员(方法,构造器,域)

获取域(Field)

Class APIList of Members?Inherited members?Private members?
getDeclareField()nonoyes
getField()noyesno
getDeclareField()yesnoyes
getFields()yesyesno

获取方法(Method)

Class APIList of membersInherited membersPrivate members
getDeclaredMethod()nonoyes
getMehod()noyesno
getDeclaredMethods()yesnoyes
getMethods()yesyesno

获取构造器(Constructor)

Class APIList of membersInherited membersPrivate members
getDeclaredConstructor()noN/A1yes
getConstructor()noN/A1no
getDeclaredConstructors()yesN/A 1yes
getConstructors()yesN/A1no

List of members 如果是 yes 表明方法返回数组,如果是no 返回指定的元素。
现在分析 获取方法的几个API:

getDeclaredMethods()

public Method[] getDeclaredMethods()
                            throws SecurityException

以数组的形式,返回该类中声明的所有方法(包括public ,protected ,default(package-private)和private 方法),但是不包括继承来的方法。

  • 如果这个Class对象所代表的类型有了多个名称和参数类型都相同但是返回值不同的方法,则返回的数组中会含有多个Method对象(每个Method对象对应一个相应的方法)。
    示例:
package reflectionTest;

import java.lang.reflect.Method;

/**
 * @author wzq20
 */
public class A {

    public static void main(String[] args) {
        Class<?> cls = C.class;
        Method[] methods = cls.getDeclaredMethods();
        for (Method m : methods) {
            System.out.println(m + (m.isSynthetic() ? " isSynthetic" : "   noSynthetic") + (m.isBridge() ? "  isBridge" : "  noBridge"));
        }
    }
    
}
class B {
    public B foo() {
    	...
        return new B();
    }

}

class C extends B {
    @Override
   public C foo() {
   		...
        return new C();
    }
    
}


    // 输出
    public reflectionTest.C reflectionTest.C.foo()   noSynthetic  noBridge
	public reflectionTest.B reflectionTest.C.foo() isSynthetic  isBridge

参考:Java 可协变的返回类型

  • 如果这个Class 对象代表一个没有声明任何方法的类或接口,则返回一个长度为0的数组
public class A {

    public static void main(String[] args) {
        Class<?> cls = B.class;
        Method[] methods = cls.getDeclaredMethods();
        System.out.println("the length of methods array is " + methods.length);
    }

}

class B {

}

// 输出
// the length of methods array is 0
  • 如果这个Class对象代表一个基本类型,数组,或者void ,则返回一个长度为0的数组。
    public static void main(String[] args) {

        try {
            Class<?> cInt = int.class;
            Method[] intDeclaredMethods = cInt.getDeclaredMethods();

            Class<?> cStringArray = Class.forName("[Ljava.lang.String;");
            Method[] stringDeclaredMethods = cStringArray.getDeclaredMethods();

            Class<?> cVoid = void.class;
            Method[] voidDeclareMethods = cVoid.getDeclaredMethods();

            System.out.println("the length of int methods array is " + intDeclaredMethods.length);
            System.out.println("the length of String array methods array is " + stringDeclaredMethods.length);
            System.out.println("the length of void methods array is " + voidDeclareMethods.length);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
  // 输出
  // the length of int methods array is 0
  // the length of String array methods array is 0
  // the length of void methods array is 0
  • 返回的数组中的元素没有经过排序,也没有特定的顺序。(和类中声明方法的顺序不一定一致)

getDeclaredMethod()

public Method getDeclaredMethod(String name,
                                Class<?>... parameterTypes)
                         throws NoSuchMethodException,
                                SecurityException
方法参数含义
name方法的最简名称(只包括方法名)
parameterTypes参数数组
  • 如果Class 对象声明了超过一个方法名和参数类型都相同的方法,则返回返回类型最为具体 2 的那个方法。
package reflectionTest;

import java.lang.reflect.Method;

/**
 * @author wzq20
 */
public class A {

    public static void main(String[] args) {
        Class<?> cls = C.class;
        Method[] methods = cls.getDeclaredMethods();
        for (Method m : methods) {
            System.out.println(m + (m.isSynthetic() ? " isSynthetic" : "   noSynthetic") + (m.isBridge() ? "  isBridge" : "  noBridge"));
        }

        try{
            Method  m1 = cls.getDeclaredMethod("foo");
            System.out.println("the more specific method is " + m1);

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

}
class B {
    public B foo() {
        return new B();
    }

}

class C extends B {
    @Override
    public C foo() {
        return new C();
    }

}
// 输出
 public reflectionTest.C reflectionTest.C.foo()   noSynthetic  noBridge
 public reflectionTest.B reflectionTest.C.foo() isSynthetic   isBridge
 the more specific method is public reflectionTest.C reflectionTest.C.foo()

类C是类B的子类,所以类C 比类B更具体。可以看见返回的方法是 public C foo()

  • 搜索泛型方法时,应该传递参数化类型的上界

getMethods()

public Method[] getMethods()
                    throws SecurityException

返回一个包含方法对象的数组,该数组包含了由这个类对象表示的类或接口的所有 public 方法,包括由类或接口声明的方法以及从超类和超接口继承的方法。

  • 如果这个Class对象所代表的类型有了多个名称和参数类型都相同但是返回值不同的方法,则返回的数组中会含有多个Method对象(每个Method对象对应一个相应的方法)。例子与getDeclareMethods() 的例子类似。
  • 如果这个Class对象表示一个数组类型,则返回的数组包含数组类型从object继承的每个public方法。但它不包含clone()的方法对象。(clone()方法是protected方法)

  public static void main(String[] args) {

        try{
            Class<?> cls = Class.forName("[I");
            Method[] methods = cls.getMethods();
            for (Method m : methods) {
                System.out.println(m);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
// 输出
public final void java.lang.Object.wait() throws java.lang.InterruptedException
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 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()
  • 如果这个Class对象表示一个接口,则返回的数组不包含任何来自Object的隐式声明的方法。因此,如果在这个接口或它的任何父接口中没有显式声明方法,则返回的数组长度为0。(注意,代表类的Class对象总是具有从Object继承的公共方法。

// the interface that has not explicitly declared method

interface Intf {

}
/**
 * @author wzq20
 */
public class A {

    public static void main(String[] args) {

        Class<?> cls = Intf.class;
        Method[] methods1 = cls.getMethods();
        System.out.println("the length of array(interface) is  " + methods1.length);

    }
}

// 输出
the length of array(interface) is  0

// the class that has not explicitly declared method
class C{

}

/**
 * @author wzq20
 */
public class A {

    public static void main(String[] args) {
        Class<?> cls = C.class;
        Method[] methods = cls.getMethods();
        for(Method m :methods) {
            System.out.println(m);
        }
    }
}
// 输出
public final void java.lang.Object.wait() throws java.lang.InterruptedException
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 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()
  • 如果这个Class对象表示基本类型或void,则返回的数组长度为0。
  • 在由这个Class对象表示的类或接口的超接口中声明的静态方法不被认为是类或接口的成员。(超类中声明的静态方法是的)
interface intf{
    static void a(){

    }
}
class B{
    public static void b(){
    }
}

class C extends B{
}

public class A {

    public static void main(String[] args) {
        Class<?> cls =C.class;
        Method[] methods = cls.getMethods();
        for(Method m :methods) {
            System.out.println(m);
        }
    }
}
// 输出
public static void reflectionTest.B.b()
...
  • 返回的数组中的元素没有经过排序,也没有特定的顺序。(和类中声明方法的顺序不一定一致)

getMethod()

public Method getMethod(String name,
                        Class<?>... parameterTypes)
                 throws NoSuchMethodException,
                        SecurityException
方法参数含义
name方法的最简名称
parameterTypes参数数组

方法的最简名称,即声明时的名称,不包括包名。
参数数组里元素的顺序要求按照方法形参声明的顺序。如果parameTypes是null,代表形参个数为0.

反射出的方法由以下算法决定:
(假定C是该Class对象代表的类或接口)


  • 在C中寻找匹配的方法,如果有,就返回。
  • 如果没有匹配的方法:
    • 如果C是Object类,则停止
    • 若C是一个非Object的类,则这个算法在他的直接超类中递归调用
    • 如果C是一个接口,则在C的超接口中递归调用该算法(如果存在)

  • 如果Class 对象声明了超过一个方法名和参数类型都相同的方法,则返回返回类型最为具体 2 的那个方法。

  • 如果这个Class对象表示一个数组类型,那么这个方法不会找到clone()方法。

  • 在由这个Class对象表示的类或接口的超接口中声明的静态方法不被认为是类或接口的成员。(超类中声明的静态方法是的)*

利用Field对象查看和修改实例域的值

通过getDelaredField()获取到Filed之后,如果想查看或修改某个类示例的域的话,可以调用
get(Object),getLong(Object),setInt(Object)等方法查看值
在这里插入图片描述
obj形参代表想查看的类实例。


set(Object,Object),setLong(Obejct,long),setInt(Object,int)等方法修改值
在这里插入图片描述
第一个形参代表想设置的实例,第二个形参代表修改后的值。

  • 若获取的Field是私有的,或在调用getDeclareField()时,该实例域不可见(不可访问),则需要先调用setAccessible(true)。
  • 反射过程中不会进行自动装箱。(因为自动拆箱,装箱是编译时完成的)
public class A {

    public static void main(String[] args) {
        try {
            B b = new B();
            Field f = B.class.getDeclaredField("i");
            f.setAccessible(true);
            f.setInt(b,30);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

    }
}

class B {
    private Integer i;
}
// 输出
Exception in thread "main" java.lang.IllegalArgumentException
  • 如果想修改非基本类型的实例域的值,一律用set()方法(即使是基本类型的包装类)
  • 对基本类型的域既可以调用setInt()方法也可以调用set()方法,但是调用set()方法的性能更差。
    分析见关于Java 反射中设置实例域值的相关探讨

利用Method对象调用方法

public Object invoke(Object obj,
                     Object... args)
              throws IllegalAccessException,
                     IllegalArgumentException,
                     InvocationTargetException
参数名称参数含义
obj调用方法的对象
args用于方法调用的参数
  • 如果方法是静态的,则会忽略指定的obj参数。它可以是null。
  • 如果方法的参数个数是0,则忽略args参数,也可以应该提供一个长度为0的数组或者null(不能提供值为nul的Object引用或者值为长度为0数组的引用)
		// mtt 代表某个实例,m代表某个无参数方法
  		m.invoke(mtt);                 // works
	
 		m.invoke(mtt, null);           // works (expect compiler warning)
		
		
		Object arg2 = null;
		m.invoke(mtt, arg2);           // IllegalArgumentException
		// 编译器将arg2视作一个值为null的Object对象,充当了一个参数
	
		m.invoke(mtt, new Object[0]);  // works

		Object arg4 = new Object[0];
		m.invoke(mtt, arg4);           // IllegalArgumentException
		// 编译器将arg4 视为一个值为空数组的Object对象,充当了一个参数
  • 如果方法是静态的,则声明该方法的类将被初始化(如果还没有初始化的话)

JVM启动时,不会一次性加载所有的.class文件,而是动态加载

  • 如果方法正常完成,它返回的值将返回给invoke的调用者;如果值是基本类型,则首先将其适当地包装在对象中(自动装箱)。但是,如果值的类型是由基本类型构成的数组,返回的是一个基本类型的数组。如果基础方法返回类型为void,则调用返回null。

创建新的类实例

有两个反射方法用于创建新的类实例java.lang.reflect.Constructor.newInstance() and Class.newInstance()。前一个方法使用的更多,因为:


public T newInstance(Object... initargs)
              throws InstantiationException,
                     IllegalAccessException,
                     IllegalArgumentException,
                     InvocationTargetException
参数名称参数类型
initargs作为参数传递给构造函数调用的对象数组;基本类型的值被包装在适当类型的包装器对象中(例如Float和floatt)

使用此Constructor对象表示的构造器,使用指定的初始化参数创建并初始化构造器的声明类的新实例。

  • 如果构造器的参数个数是0,则忽略args参数,也可以应该提供一个长度为0的数组或者null(不能提供值为nul的Object引用或者值为长度为0数组的引用)

  • 如果构造函数声明的类是非静态上下文中的内部类(非静态类),那么构造函数的第一个参数需要是外围实例; 参考section 15.9.3 of The Java™ Language Specification

public class A {

    public static void main(String[] args) {
        try {
            Class<B.C> cls = B.C.class;
            // 静态内部类的构造器需要一个外部类参数
            Constructor<B.C> ctor = cls.getDeclaredConstructor(B.class);
            B.C c = ctor.newInstance(new B());
            System.out.println(c);
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

}

class B {
    class C {
    }
}
  • 如果所需的访问和参数检查成功并且实例化将继续进行,那么构造函数声明的类将被初始化(如果它被初始化)

  1. 构造器是不可继承的。 ↩︎ ↩︎ ↩︎ ↩︎

  2. 子类比超类更具体。 ↩︎ ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值