Java 反射系列

1. 类成员

为了更好的描述,我们做个约定个通配符 XXXX,

  • 如果是成员变量就代表 Field,
  • 如果是类方法就代表 Method,
  • 如果是构造器就代表 Constructor。

1.1 获取方法

那么怎么获取到这三类成员呢?

  • 获取单个的成员的方式用: getXXXX() 和 getDeclaredXXXX();
  • 列举多个成员的方式用: getXXXXs() 和 getDeclaredXXXXs();

1.2 getXXXX和getDeclared的区别

  • 普通的方式(不带Declared)获取类的公共(public)的成员,包括父类,
  • 带有Declared的方式获取类的所有申明的成员,即包括public、private和protected声明的成员,不包括父类的申明字段。

那么就有人疑问那怎么获取到父类的成员呢?当然是获取到父类的Class之后,通过父类的Class调用这两类方法获取。普通的方式是比较常用的方式,反射本身就破坏封装的一种方式,为了减少这种破坏,我们应该操作public成员即可。具体的区别如下:

1.2.1 获取成员变量

Class的API方法 是否可以列举 是否能列举继承类的成员 是否能列举私有成员
getDeclaredField()
getField()
getDeclaredFields()
getFields()

1.2.2 获取成员方法

Class的API方法 是否可以列举 是否能列举继承类的成员 是否能列举私有成员
getDeclaredMethod()
getMethod()
getDeclaredMethods()
getMethods()

1.2.3 获取构造器

Class的API方法 是否可以列举 是否能列举继承类的成员 是否能列举私有成员
getDeclaredConstructor()
getConstructor()
getDeclaredConstructors()
getConstructors()

1.3 类成员的Class

getXXXX() 和 getDeclaredXXXX() 获取到的类也就是类成员的Class,对应的Class如下:

  • 成员变量:java.lang.reflect.Field
  • 成员方法:java.lang.reflect.Method
  • 构造器方法:java.lang.reflect.Constructor

后面会分为三章分别解释一下对应的用法。

2. java.lang.reflect.Constructor

每个类都至少有一个构造器,因为一个类如果没有显示定义一个构造器,编译器自动会自动生成一个默认无参的构造器,构造器作为一个类的入口方法,在使用类的成员变量和方法之前,类的构造器必须被调用,生成一个实例,另外构造器不能被继承,如果子类的构造器没有显示的调用父类的构造器,执行器会默认的调用父类的构造器。

2.1 获取构造器的方法

和获取类的方法一样,在反射的包里,获取类的构造器也是有两个方法:

  1. Class.getDeclaredConstructors() :获取所有的构造函数(public,protected,default(package)access和private)。
  2. Class.getConstructors():获取public的构造器。

这两个方法的返回值都是java.lang.reflect.Constructor

public class ConstructorSift {public static void main(String... args) {try {Class<?> cArg = Class.forName(args[1]);Class<?> c = Class.forName(args[0]);Constructor[] allConstructors = c.getDeclaredConstructors();for (Constructor ctor : allConstructors) {Class<?>[] pType  = ctor.getParameterTypes();for (int i = 0; i < pType.length; i++) {if (pType[i].equals(cArg)) {out.format("%s%n", ctor.toGenericString());Type[] gpType = ctor.getGenericParameterTypes();for (int j = 0; j < gpType.length; j++) {char ch = (pType[j].equals(cArg) ? '*' : ' ');out.format("%7c%s[%d]: %s%n", ch, "GenericParameterType", j, gpType[j]);}break;}}}// production code should handle this exception more gracefully} catch (ClassNotFoundException x) {x.printStackTrace();}}
}

这个例子的功能是查找第一个输入参数的类具有第二个输入参数的构造方法的信息。执行几个例子:

2.1.0 getParameterTypes和getGenericParameterTypes区别

这两个方法都是用来获取方法形参类型的。

  • getGenericParameterTypes:返回 Type类型 的数组 Type[] 。
  • getParameterTypes:返回 Class类型 的数组: Class<?>[] 。
  • 其中 Type 是一个高级接口。

具体的说明来看一段网上的解释:Type 是所有类型的高级公共接口,当然也是 Class 的父类。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。

先来看一下Type 的用法:

  1. type是一种表示编程语言中的所有类型的类超级接口。

    • 如:int、Integer、String 这都是表示一编程语言的类型,而其中的 int.class、Integer.class、String.class 它们表示的是类型的实例。
  2. 我们以前学习的反射 Class c = Integer.class,Class相当于表示类型的类,而Integer.class 则是一种名为整形类型的类型实例。
  3. 理解了上面的那些,其理解 type就不难了,type 与 class 一样,不过 type是一种比Class 表示范围还要广的超级接口,它表示Java语言中类型的所有接口。
2.1.0.1 示例代码

首先假设有这么一个类:SampleClass

这个类有两个属性,一个String类型,一个泛型List<Integer>

import java.util.List;
public class SampleClass {private String sampleField;private List<Integer> ids;public String getSampleField() {return sampleField;}public void setSampleField(String sampleField) {this.sampleField = sampleField;}public List<Integer> getIds() {return ids;}public void setIds(List<Integer> ids) {this.ids = ids;}
}

然后写测试代码:

public static void main(String[] args) {Class sampleClassClass = SampleClass.class;Method[] methods = sampleClassClass.getMethods();for (Method method : methods) {System.out.println("------------------" + method.getName());Type[] genericParameterTypes = method.getGenericParameterTypes();Class<?>[] parameterTypes = method.getParameterTypes();for(Class parameterType: parameterTypes){System.out.println(parameterType + "====" + parameterType.getName());}for (int i = 0; i < genericParameterTypes.length; i++) {System.out.println(genericParameterTypes[i] + "=====" + genericParameterTypes[i].getTypeName());}}
}

输出结果

------------------main
class [Ljava.lang.String;====[Ljava.lang.String;
class [Ljava.lang.String;=====java.lang.String[]
------------------setSampleField
class java.lang.String====java.lang.String
class java.lang.String=====java.lang.String
------------------setIds
interface java.util.List====java.util.List
java.util.List<java.lang.Integer>=====java.util.List<java.lang.Integer>
------------------getIds
------------------getSampleField
------------------wait
long====long
int====int
long=====long
int=====int
------------------wait
------------------wait
long====long
long=====long
------------------equals
class java.lang.Object====java.lang.Object
class java.lang.Object=====java.lang.Object
------------------toString
------------------hashCode
------------------getClass
------------------notify
------------------notifyAll
  1. String类型,这两个方法返回的结果是一样的
  2. List<Integer> 类型
    • getParameterTypes 只返回了类型,泛型没有返回;
    • getGenericParameterTypes返回的是完整的泛型。
2.1.0.2 结论
  1. 如果方法参数不是参数化类型(泛型),那么 getParameterTypesgetGenericParameterTypes 返回的结果是一样的。
  2. 如果方法参数是泛型,这时就有区别了, getGenericParameterTypes 会返回完整的信息,而 getParameterTypes 只会返回参数类型,参数化类型无法得到。
2.1.0.3 获取参数化类型
// 将类型向参数化类型转换
ParameterizedType t = (ParameterizedType)genericParameterTypes[0];
// 可以得到参数化类型的参数实例
t.getActualTypeArguments()[0];
2.1.0.4 范型

泛型在java中有很重要的地位,无论是开源框架还是JDK源码都能看到它。毫不夸张的说,泛型是通用设计上必不可少的元素,所以真正理解与正确使用泛型,是一门必修课。

2.1.0.4.1 泛型本质

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。

泛型的本质是参数化类型,即给类型指定一个参数,然后在使用时再指定此参数具体的值,那样这个类型就可以在使用时决定了。这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

2.1.0.4.2 为什么使用泛型

泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

范型的作用:

  1. 安全性
  2. 消除转换
  3. 提高性能
  4. 重用性

1. 保证了类型的安全性

在没有泛型之前,从集合中读取到的每一个对象都必须进行类型转换,如果不小心插入了错误的类型对象,在运行时的转换处理就会出错。

比如,没有泛型的情况下使用集合:

public static void noGeneric() {ArrayList names = new ArrayList();names.add("mikechen的互联网架构");names.add(123); //编译正常
}

有泛型的情况下使用集合:

public static void useGeneric() {ArrayList<String> names = new ArrayList<>();names.add("mikechen的互联网架构");names.add(123); //编译不通过
}

有了泛型后,定义好的集合names在编译的时候 add(123) 就会编译不通过。

相当于告诉编译器每个集合接收的对象类型是什么,编译器在编译期就会做类型检查,告知是否插入了错误类型的对象,使得程序更加安全,增强了程序的健壮性。

2.消除强制转换

泛型的一个附带好处是,消除源代码中的许多强制类型转换,这使得代码更加可读,并且减少了出错机会。

还是举例说明,以下没有泛型的代码段需要强制转换:

List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);

当重写为使用泛型时,代码不需要强制转换:

List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0); // no cast

3. 避免了不必要的装箱、拆箱操作,提高程序的性能

在非泛型编程中,将筒单类型作为 Object 传递时会引起 Boxing(装箱)和 Unboxing(拆箱)操作,这两个过程都是具有很大开销的。引入泛型后,就不必进行 BoxingUnboxing 操作了,所以运行效率相对较高,特别在对集合操作非常频繁的系统中,这个特点带来的性能提升更加明显。泛型变量固定了类型,使用的时候就已经知道是值类型还是引用类型,避免了不必要的装箱、拆箱操作。

object a = 1;//由于是object类型,会自动进行装箱操作。
int b = (int)a;//强制转换,拆箱操作。这样一去一来,当次数多了以后会影响程序的运行效率。

使用泛型之后

public static T GetValue<T>(T a) {return a;
}public static void Main() {int b = GetValue<int>(1); //使用这个方法的时候已经指定了类型是int,所以不会有装箱和拆箱的操作。
}

4. 提高了代码的重用性

2.1.0.4.3 如何使用泛型

泛型有三种使用方式,分别为:泛型类、泛型接口和泛型方法。

1. 泛型类

泛型类:把泛型定义在类上。定义格式如下:

public class 类名 <泛型类型1,...> {  }

注意事项:泛型类型必须是引用类型(非基本数据类型)

定义泛型类,在类名后添加一对尖括号,并在尖括号中填写类型参数,参数可以有多个,多个参数使用逗号分隔:

public class GenericClass <ab, a, c> {}

当然,这个后面的参数类型也是有规范的,不能像上面一样随意,通常类型参数我们都使用大写的单个字母表示:

T:任意类型 type
E:集合中元素的类型 element
K:key-value形式 key
V:key-value形式 value

示例泛型类:

public class GenericClass<T> {private T value;public GenericClass(T value) {this.value = value;}public T getValue() {return value;}public void setValue(T value) {this.value = value;}
}

测试类:

//TODO 1:泛型类
GenericClass<String> name = new GenericClass<>("mikechen的互联网架构");
System.out.println(name.getValue());
GenericClass<Integer> number = new GenericClass<>(123);
System.out.println(number.getValue());

2. 泛型接口

泛型方法概述:把泛型定义在方法上

public <泛型类型> 返回类型 方法名(泛型类型 变量名) {}

方法声明中定义的形参只能在该方法里使用,而接口、类声明中定义的类型形参则可以在整个接口、类中使用。当调用 fun() 方法时,根据传入的实际对象,编译器就会判断出类型形参 T 所代表的实际类型。

public interface GenericInterface<T> {void show(T value);
}public class StringShowImpl implements GenericInterface<String> {@Overridepublic void show(String value) {System.out.println(value);}
}public class NumberShowImpl implements GenericInterface<Integer> {@Overridepublic void show(Integer value) {System.out.println(value);}
}

注意:使用泛型的时候,前后定义的泛型类型必须保持一致,否则会出现编译异常。

GenericInterface<String> genericInterface = new NumberShowImpl();//编译异常

或者干脆不指定类型,那么 new 什么类型都是可以的:

GenericInterface g1 = new NumberShowImpl();
GenericInterface g2 = new StringShowImpl();

3. 泛型方法

泛型方法,是在调用方法的时候指明泛型的具体类型 。

修饰符 <代表泛型的变量> 返回值类型 方法名(参数) {}

例如:

/**** @param t 传入泛型的参数* @param <T> 泛型的类型* @return T 返回值为T类型* 说明:*   1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。*   2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。*   3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。*   4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E等形式的参数常用于表示泛型。
**/
public <T> T genercMethod(T t){System.out.println(t.getClass());System.out.println(t);return t;
}public static void main(String[] args) {GenericsClassDemo<String> genericString  = new GenericsClassDemo("helloGeneric"); //这里的泛型跟下面调用的泛型方法可以不一样。String str = genericString.genercMethod("hello");//传入的是String类型,返回的也是String类型Integer i = genericString.genercMethod(123);//传入的是Integer类型,返回的也是Integer类型
}

输出:

class java.lang.String
hello
class java.lang.Integer
123

这里可以看出,泛型方法随着我们的传入参数类型不同,他得到的类型也不同。泛型方法能使方法独立于类而产生变化。

2.1.0.4.4 泛型通配符

Java泛型的通配符是用于解决泛型之间引用传递问题的特殊语法,主要有以下三类:

  1. <?> 无边界的通配符(Unbounded Wildcards),比如 List<?>

    • 无边界的通配符的主要作用就是让泛型能够接受未知类型的数据。
  2. <? extends E> 固定上边界的通配符(Upper Bounded Wildcards),
    • 使用固定上边界的通配符的泛型,就能够接受指定类及其子类类型的数据。
    • 要声明使用该类通配符,采用 <? extends E> 的形式,这里的E就是该泛型的上边界。
    • 注意:这里虽然用的是 extends 关键字,却不仅限于继承了父类 E 的子类,也可以代指显现了接口 E 的类。
  3. <? super E> 固定下边界的通配符
    • 使用固定下边界的通配符的泛型,就能够接受指定类及其父类类型的数据。
    • 要声明使用该类通配符,采用 <? super E> 的形式, 这里的E就是该泛型的下边界。
    • 注意:你可以为一个泛型指定上边界或下边界,但是不能同时指定上下边界。
// 1:表示类型参数可以是任何类型
public class Apple<?>{} // 2:表示类型参数必须是A或者是A的子类
public class Apple<T extends A>{} // 3: 表示类型参数必须是A或者是A的超类型
public class Apple<T supers A>{}
2.1.0.4.5 泛型中KTVE的含义

点开JDK中一些泛型类的源码,我们会看到下面这些代码:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{...
}public class HashMap<K,V> extends AbstractMap<K,V>    implements Map<K,V>, Cloneable, Serializable {...
}

上面这些泛型类定义中的泛型参数E、K和V都是什么意思呢?

其实这些参数名称是可以任意指定,就想方法的参数名一样可以任意指定,但是我们通常会起一个有意义的名称,让别人一看就知道是什么意思。

E:Element (在集合中使用,因为集合中存放的是元素)
T:Type(Java 类)
K:Key(键)
V:Value(值)
N:Number(数值类型)
?:表示不确定的java类型
2.1.0.4.6 泛型的实现原理

泛型本质是将数据类型参数化,它通过擦除的方式来实现,即编译器会在编译期间「擦除」泛型语法并相应的做出一些类型转换动作。看一个例子就应该清楚了,例如:

public class Caculate<T> {private T num;
}

我们定义了一个泛型类,定义了一个属性成员,该成员的类型是一个泛型类型,这个 T 具体是什么类型,我们也不知道,它只是用于限定类型的。

反编译一下这个 Caculate 类:

public class Caculate{public Caculate() {}private Object num;
}

发现编译器擦除 Caculate 类后面的两个尖括号,并且将 num 的类型定义为 Object 类型。

那么是不是所有的泛型类型都以 Object 进行擦除呢?大部分情况下,泛型类型都会以 Object 进行替换,而有一种情况则不是。那就是使用到了 extends 和 super 语法的有界类型,如:

public class Caculate<T extends String> {private T num;
}

这种情况的泛型类型,num 会被替换为 String 而不再是 Object。

这是一个类型限定的语法,它限定 T 是 String 或者 String 的子类,也就是你构建 Caculate 实例的时候只能限定 T 为 String 或者 String 的子类,所以无论你限定 T 为什么类型,String 都是父类,不会出现类型不匹配的问题,于是可以使用 String 进行类型擦除。

实际上编译器会正常的将使用泛型的地方编译并进行类型擦除,然后返回实例。但是除此之外的是,如果构建泛型实例时使用了泛型语法,那么编译器将标记该实例并关注该实例后续所有方法的调用,每次调用前都进行安全检查,非指定类型的方法都不能调用成功。

实际上编译器不仅关注一个泛型方法的调用,它还会为某些返回值为限定的泛型类型的方法进行强制类型转换,由于类型擦除,返回值为泛型类型的方法都会擦除成 Object 类型,当这些方法被调用后,编译器会额外插入一行 checkcast 指令用于强制类型转换,这一个过程就叫做『泛型翻译』。

2.1.1 查找普通类作为参数的类构造器

查找有 Locale参数 的 java.util.Formatter 的构造方法的信息:

$ java ConstructorSift java.util.Formatter java.util.Locale
public java.util.Formatter(java.io.OutputStream,java.lang.String,java.util.Locale)
throws java.io.UnsupportedEncodingExceptionGenericParameterType[0]: class java.io.OutputStreamGenericParameterType[1]: class java.lang.String*GenericParameterType[2]: class java.util.Locale
public java.util.Formatter(java.lang.String,java.lang.String,java.util.Locale)
throws java.io.FileNotFoundException,java.io.UnsupportedEncodingExceptionGenericParameterType[0]: class java.lang.StringGenericParameterType[1]: class java.lang.String*GenericParameterType[2]: class java.util.Locale
public java.util.Formatter(java.lang.Appendable,java.util.Locale)GenericParameterType[0]: interface java.lang.Appendable*GenericParameterType[1]: class java.util.Locale
public java.util.Formatter(java.util.Locale)*GenericParameterType[0]: class java.util.Locale
public java.util.Formatter(java.io.File,java.lang.String,java.util.Locale)
throws java.io.FileNotFoundException,java.io.UnsupportedEncodingExceptionGenericParameterType[0]: class java.io.FileGenericParameterType[1]: class java.lang.String*GenericParameterType[2]: class java.util.Locale

2.1.2 查找数组类作为参数的构造器

查找 String 的 char[] 构造方法。

$ java ConstructorSift java.lang.String "[C"
java.lang.String(int,int,char[])GenericParameterType[0]: intGenericParameterType[1]: int*GenericParameterType[2]: class [C
public java.lang.String(char[],int,int)*GenericParameterType[0]: class [CGenericParameterType[1]: intGenericParameterType[2]: int
public java.lang.String(char[])*GenericParameterType[0]: class [C

2.1.3 查找变长参数的构造器

ProcessBuilder 有一个构造器:public ProcessBuilder(String... command)

$ java ConstructorSift java.lang.ProcessBuilder "[Ljava.lang.String;"
public java.lang.ProcessBuilder(java.lang.String[])*GenericParameterType[0]: class [Ljava.lang.String;

2.1.4 查找泛型参数的构造器

java ConstructorSift java.util.HashMap java.util.Map
public java.util.HashMap(java.util.Map<? extends K, ? extends V>)*GenericParameterType[0]: java.util.Map<? extends K, ? extends V>

2.2 获取构造器的标识符

构造器和类的其他方法不一样,构造器只有以下几种标识符。

  1. 访问权限描述符:public, protected, and private。
  2. 注解。
public class ConstructorAccess {public static void main(String... args) {try {Class<?> c = Class.forName(args[0]);Constructor[] allConstructors = c.getDeclaredConstructors();for (Constructor ctor : allConstructors) {int searchMod = modifierFromString(args[1]);int mods = accessModifiers(ctor.getModifiers());if (searchMod == mods) {out.format("%s%n", ctor.toGenericString());out.format("  [ synthetic=%-5b var_args=%-5b ]%n", ctor.isSynthetic(), ctor.isVarArgs());}}// production code should handle this exception more gracefully} catch (ClassNotFoundException x) {x.printStackTrace();}}private static int accessModifiers(int m) {return m & (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED);}private static int modifierFromString(String s) {if ("public".equals(s))               return Modifier.PUBLIC;else if ("protected".equals(s))       return Modifier.PROTECTED;else if ("private".equals(s))         return Modifier.PRIVATE;else if ("package-private".equals(s)) return 0;else                               return -1;}
}

这个例子是获取第一个参数名的类具有第二参数类型的构造器。比如获取 File 类有 private 访问权限的构造器:

$ java ConstructorAccess java.io.File private
private java.io.File(java.lang.String,int)[ synthetic=false var_args=false ]
private java.io.File(java.lang.String,java.io.File)[ synthetic=false var_args=false ]

和获取类的注解一样,获取构造器的注解也是通过Constructor.getAnnotations获取。

2.3 反射调用类构造器

我们知道Class上有一个反射实例化一个类的方法: Class.newInstance(),现在又有了另外一种方式: java.lang.reflect.Constructor.newInstance() ,两者的调用是不同的:

  1. Class.newInstance() 只能调用无参构造器,有参构造器只能通过java.lang.reflect.Constructor.newInstance()来调用。
  2. Class.newInstance() 会抛出很多种异常,java.lang.reflect.Constructor.newInstance() 只会抛出 InvocationTargetException 。
  3. Class.newInstance() 只能调用当前调用者可见的构造器,java.lang.reflect.Constructor.newInstance() 可以调用 private 等当前调用者不可见的构造器。
class EmailAliases {private Set<String> aliases;private EmailAliases(HashMap<String, String> h) {aliases = h.keySet();}public void printKeys() {out.format("Mail keys:%n");for (String k : aliases)out.format("  %s%n", k);}
}public class RestoreAliases {private static Map<String, String> defaultAliases = new HashMap<String, String>();static {defaultAliases.put("Duke", "duke@i-love-java");defaultAliases.put("Fang", "fang@evil-jealous-twin");}public static void main(String... args) {try {Constructor ctor = EmailAliases.class.getDeclaredConstructor(HashMap.class);ctor.setAccessible(true);EmailAliases email = (EmailAliases)ctor.newInstance(defaultAliases);email.printKeys();// production code should handle these exceptions more gracefully} catch (InstantiationException x) {x.printStackTrace();} catch (IllegalAccessException x) {x.printStackTrace();} catch (InvocationTargetException x) {x.printStackTrace();} catch (NoSuchMethodException x) {x.printStackTrace();}}
}

本例通过反射调用一个Hash参数的类构造器。

$ java RestoreAliases
Mail keys:DukeFang

2.4 构造器经常遇到的异常

2.4.1 无默认构造器异常

上面我们讲了 Class.newInstance() 只能调用无参构造器,如果没有这个构造器就会抛出 InstantiationException。

public class ConstructorTrouble {private ConstructorTrouble(int i) {}public static void main(String... args){try {Class<?> c = Class.forName("ConstructorTrouble");Object o = c.newInstance();  // InstantiationException// production code should handle these exceptions more gracefully} catch (ClassNotFoundException x) {x.printStackTrace();} catch (InstantiationException x) {x.printStackTrace();} catch (IllegalAccessException x) {x.printStackTrace();}}
}

测试结果:

$ java ConstructorTrouble
java.lang.InstantiationException: ConstructorTroubleat java.lang.Class.newInstance0(Class.java:340)at java.lang.Class.newInstance(Class.java:308)at ConstructorTrouble.main(ConstructorTrouble.java:7)

2.4.2 构造器抛出异常

如果调用构造器时,构造器本身抛出异常,则我们的反射调用也会抛出 java.lang.RuntimeException。

public class ConstructorTroubleToo {public ConstructorTroubleToo() {throw new RuntimeException("exception in constructor");}public static void main(String... args) {try {Class<?> c = Class.forName("ConstructorTroubleToo");// Method propagetes any exception thrown by the constructor// (including checked exceptions).if (args.length > 0 && args[0].equals("class")) {Object o = c.newInstance();} else {Object o = c.getConstructor().newInstance();}// production code should handle these exceptions more gracefully} catch (ClassNotFoundException x) {x.printStackTrace();} catch (InstantiationException x) {x.printStackTrace();} catch (IllegalAccessException x) {x.printStackTrace();} catch (NoSuchMethodException x) {x.printStackTrace();} catch (InvocationTargetException x) {x.printStackTrace();err.format("%n%nCaught exception: %s%n", x.getCause());}}
}

测试结果:

$ java ConstructorTroubleToo class
Exception in thread "main" java.lang.RuntimeException: exception in constructorat ConstructorTroubleToo.<init>(ConstructorTroubleToo.java:6)at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)at java.lang.reflect.Constructor.newInstance(Constructor.java:513)at java.lang.Class.newInstance0(Class.java:355)at java.lang.Class.newInstance(Class.java:308)at ConstructorTroubleToo.main(ConstructorTroubleToo.java:15)

2.4.3 查找或者调用错误的构造器

查找或者调用错误的构造器会抛出NoSuchMethodException和IllegalArgumentException,这里就不举例子的,读者可以自己实现这个例子。

2.4.4 调用不可访问的构造器

构造器存在,但是对当前访问者不可见时会抛出: IllegalAccessException异常。

class Deny {private Deny() {System.out.format("Deny constructor%n");}
}public class ConstructorTroubleAccess {public static void main(String... args) {try {Constructor c = Deny.class.getDeclaredConstructor();
//          c.setAccessible(true);   // solutionc.newInstance();// production code should handle these exceptions more gracefully} catch (InvocationTargetException x) {x.printStackTrace();} catch (NoSuchMethodException x) {x.printStackTrace();} catch (InstantiationException x) {x.printStackTrace();} catch (IllegalAccessException x) {x.printStackTrace();}}
}

测试结果:

java.lang.IllegalAccessException: Class ConstructorTroubleAccess can not accessa member of class Deny with modifiers "private"at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)at java.lang.reflect.Constructor.newInstance(Constructor.java:505)at ConstructorTroubleAccess.main(ConstructorTroubleAccess.java:15)

3. java.lang.reflect.Field

Java 中 Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类字段或实例字段。Field 是成员变量的意思。Field 也是一个类,该类位于 java.lang.reflect 包下。

https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Field.html

  1. 获取变量的类型。

    • Field.getType():返回这个变量的类型。

    • Field.getGenericType():如果当前属性有签名属性类型就返回,否则就返回 Field.getType()。

    • isEnumConstant() : 判断这个属性是否是枚举类。

  2. 获取成员变量的修饰符。

    • Field.getModifiers() : 以整数形式返回由此 Field 对象表示的字段的 Java 语言修饰符。
  3. 获取和修改成员变量的值。

    • getName() : 获取属性的名字。

    • get(Object obj) : 返回指定对象obj上此 Field 表示的字段的值。

    • set(Object obj, Object value) : 将指定对象变量上此 Field 对象表示的字段设置为指定的新值。

3.1 获取field的类型

有两种方式可以获取到field的属性,Field.getType()Field.getGenericType(),其中 getGenericType 可以获取到泛型的标识符,如果这个field是泛型,则返回泛型的标识,如果不是泛型,这会转而调用 getType 获取到真正的类型,也就是 Object

这里可以提一下,Java 里的泛型是假泛型,从字节码到可以执行文件的时候,已经把泛型擦除了,变成真正的类型,但是 getType() 调用时,并没有真正的类型代入,所以会返回所有的类的父类 Object

我们举个例子:

public class FieldSpy<T> {public boolean[][] b = {{ false, false }, { true, true } };public String name  = "Alice";public List<Integer> list;public T val;public static void main(String[] args) {try {Class<?> c = Class.forName(args[0]);Field f = c.getField(args[1]);System.out.format("Type: %s%n", f.getType());System.out.format("GenericType: %s%n", f.getGenericType());} catch (ClassNotFoundException x) {x.printStackTrace();} catch (NoSuchFieldException x) {x.printStackTrace();}}
}

执行命令以及执行结果:

$ java FieldSpy FieldSpy b
Type: class [[Z
GenericType: class [[Z
$ java FieldSpy FieldSpy name
Type: class java.lang.String
GenericType: class java.lang.String
$ java FieldSpy FieldSpy list
Type: interface java.util.List
GenericType: java.util.List<java.lang.Integer>
$ java FieldSpy FieldSpy val
Type: class java.lang.Object
GenericType: T

3.2 检索和解析 Field 的修饰符

Field 的修饰符可以通过 public int getModifiers() 方法获取,这个方法返回的是int型,代表意义可以参见修饰符,如果要判断一个 field 是否具有某个修饰符,可以通过 位运算符& 判断,比如判断一个 field 的修饰符是否有 public 属性:

Field f = OneClass.getField("field");
int modify = f.getModifiers();
return modify&Modifier.PUBLIC == Modifier.PUBLIC

可以看一个官方的例子:

enum Spy { BLACK , WHITE }public class FieldModifierSpy {volatile int share;int instance;class Inner {}public static void main(String... args) {try {Class<?> c = Class.forName(args[0]);int searchMods = 0x0;for (int i = 1; i < args.length; i++) {searchMods |= modifierFromString(args[i]);}Field[] flds = c.getDeclaredFields();out.format("Fields in Class '%s' containing modifiers:  %s%n",c.getName(),Modifier.toString(searchMods));boolean found = false;for (Field f : flds) {int foundMods = f.getModifiers();// Require all of the requested modifiers to be presentif ((foundMods & searchMods) == searchMods) {out.format("%-8s [ synthetic=%-5b enum_constant=%-5b ]%n",f.getName(), f.isSynthetic(), f.isEnumConstant());found = true;}}if (!found) {out.format("No matching fields%n");}// production code should handle this exception more gracefully} catch (ClassNotFoundException x) {x.printStackTrace();}}private static int modifierFromString(String s) {int m = 0x0;if ("public".equals(s))           m |= Modifier.PUBLIC;else if ("protected".equals(s))   m |= Modifier.PROTECTED;else if ("private".equals(s))     m |= Modifier.PRIVATE;else if ("static".equals(s))      m |= Modifier.STATIC;else if ("final".equals(s))       m |= Modifier.FINAL;else if ("transient".equals(s))   m |= Modifier.TRANSIENT;else if ("volatile".equals(s))    m |= Modifier.VOLATILE;return m;}
}

这个例子的大致意思是查找输入类名是否具有输入的修饰符的成员变量,并把成员变量名,并且输出其是否是编译器生成的和是否输入枚举变量。

输入输出:

$ java FieldModifierSpy FieldModifierSpy volatile
Fields in Class 'FieldModifierSpy' containing modifiers:  volatile
share    [ synthetic=false enum_constant=false ]$ java FieldModifierSpy Spy public
Fields in Class 'Spy' containing modifiers:  public
BLACK    [ synthetic=false enum_constant=true  ]
WHITE    [ synthetic=false enum_constant=true  ]$ java FieldModifierSpy FieldModifierSpy\$Inner final
Fields in Class 'FieldModifierSpy$Inner' containing modifiers:  final
this$0   [ synthetic=true  enum_constant=false ]$ java FieldModifierSpy Spy private static final
Fields in Class 'Spy' containing modifiers:  private static final
$VALUES  [ synthetic=true  enum_constant=false ]
  • 是否是编译器生成可以通过方法 field.isSynthetic() 判断。
  • 是否是枚举变量可以通过方法 field.isEnumConstant() 判断。
  • 是否是编译器我想很多人都明白,什么是编译器生成的成员变量呢?
    • 比如枚举类型,每个枚举类型都有一个 VALUES 成员变量,这个变量我们并没有显式定义,但是可以通过它获取这个枚举类对应的所有没有常量,VALUES 就是编译器生成的。

3.2.1 Java 中冷门的 synthetic 关键字原理解读

看 JAVA 的反射时,看到有个 synthetic ,还有一个方法 isSynthetic() 很好奇,就了解了一下:

Any constructs introduced by a Java compiler that do not have a corresponding construct in the source code must be marked as synthetic, except for default constructors, the class initialization method, and the values and valueOf methods of the Enum class.

大意为:由 java 编译器生成的(除了像默认构造函数这一类的)方法,或者类

3.2.1.1 例子

既然知道 synthetic 方法synthetic类 是由编译器生成的,那到底编译器会怎么生成这些东西,又在什么情况下会生成这些东西呢?

先看一段代码:

import static java.lang.System.out;public final class DemonstrateSyntheticMethods
{public static void main(final String[] arguments){DemonstrateSyntheticMethods.NestedClass nested = new DemonstrateSyntheticMethods.NestedClass();out.println("String: " + nested.highlyConfidential);}private static final class NestedClass{private String highlyConfidential = "Don't tell anyone about me";private int highlyConfidentialInt = 42;private Calendar highlyConfidentialCalendar = Calendar.getInstance();private boolean highlyConfidentialBoolean = true;}
}

编译之后,可以看到三个文件:

其中,最下面的这个类文件很好解释,就是我们的主class,中间的文件,是我们的内部类,上面的文件,后面再讲,我们先看一下中间这个内部类

3.2.1.1.1 内部类的反编译结果

javap 反编译 DemonstrateSyntheticMethods$NestedClass.class ,得到如下结果:

javap DemonstrateSyntheticMethods$NestedClass.class
Compiled from "DemonstrateSyntheticMethods.java"
final class DemonstrateSyntheticMethods$NestedClass {DemonstrateSyntheticMethods$NestedClass(DemonstrateSyntheticMethods$1);static java.lang.String access$100(DemonstrateSyntheticMethods$NestedClass);
}

先把构造函数放一边,我们来看这个标黑的方法 access$100 这个是怎么回事呢?我们的源文件里找不到这个 access方法 啊?

3.2.1.1.2 synthetic方法

这个方法就是编译器生成的 synthetic方法,读者不信的话,可以用 method.isSynthetic() 去验证一下。

为何要生成这样一个方法呢?

可以看到,我们的 NestedClass类 中,highConfidential 是一个私有属性,而我们在外部类 DemonstrateSyntheticMethods 中,直接引用了这个属性。作为一个内部类,NestedClass 的属性被外部类引用,在语义上毫无问题,但是这却苦了编译器。

为了能让一个 private 的变量被引用到,编译器生成了一个 package scopeaccess 方法,这个方法就是一个 get 方法,在外部类使用 highConfidential 这个属性时,实际是使用了这个 access 方法。

javap 中可以看到直接的证据:

图中红框的位置,可以很清楚的看到 main 方法实际上调用了 access$100 这个方法。

所以,结论很清楚了,编译器为了方便内部类的私有成员被外部类引用,生成了一个 get 方法,这可以被理解为一个 trick ,绕开了 private 成员变量的限制。

3.2.1.1.3 synthetic类

定义已经提到,编译器不仅仅会生成方法,也会生成 synthetic类

我们回过头来看 2.1 提到的最后一个类 DemonstrateSyntheticMethods$1.class

这个类是一个完全的空类,反编译后是这个样子:

// $FF: synthetic class
class DemonstrateSyntheticMethods$1 {}

这个类只出场了一次,作为内部类 NestedClasspackage scope 的构造函数,如图所示:

那么,这个类的作用呢?笔者查了很多资料,都没有明确的说明这个类的用途,只能根据代码做推测如下:

NestedClass作为一个 private 类,其默认构造函数也是 private 的。那么,事实上,作为外部类的 DemonstrateSyntheticMethods类 ,没有办法new这个内部类的对象,而这和我们需要的语义相违背。

那么,为了实现语义,编译器又用了一个 trick,悄悄的生成了一个构造函数 NestedClass(DemonstrateSyntheticMethods$1 obj),这个构造函数是包可见的。

3.3 检索Field的注解

获取所有的注解可以用 field.getDeclaredAnnotations() 方式。

获取单个的可以用:

  • getAnnotatedType()
  • getAnnotation(Class annotationClass)
  • getAnnotationsByType(Class annotationClass)

实际上这几个方法都是从 class java.lang.reflect.AccessibleObject 继承而来的,这里就不做详细介绍了。

3.4 设置和获取Field的值

set(Object obj, Object value) 来设置 Field ,除了这个方式还有多种确定Field类型的方式,比如 void setDouble(Object obj, double d)

Object get(Object obj) 来获取 Field 的值,和 set方法 一直,get方法也有多种确定 Field类型 的方式,比如 double getDouble(Object obj)

以上方法都可能抛出 NoSuchFieldExceptionIllegalAccessException 异常。

官方文档上有一句话是这样说的:因为这种访问通常违反了该类的设计意图,因此应尽可能谨慎的使用它。前面就讲过,反射是破坏封装性的,违反的类的设计原则,所以能少用就少用。这里要提一下 setXXXX() 内部如果是基础类型时要小心,这个方法不会进行装箱和拆箱操作,因为装箱和拆箱操作是编译器做的,运行时,JVM 并不能做这个事情。比如下面的例子就会抛出异常。

public class FieldTrouble {public Integer val;public static void main(String... args) {FieldTrouble ft = new FieldTrouble();try {Class<?> c = ft.getClass();Field f = c.getDeclaredField("val");f.setInt(ft, 42);               // IllegalArgumentException} catch (NoSuchFieldException x) {x.printStackTrace();} catch (IllegalAccessException x) {x.printStackTrace();}}
}

执行结果:

Exception in thread "main" java.lang.IllegalArgumentException: Can not set java.lang.Integer field reflect.FieldTrouble.val to (int)42at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:191)at sun.reflect.UnsafeObjectFieldAccessorImpl.setInt(UnsafeObjectFieldAccessorImpl.java:114)at java.lang.reflect.Field.setInt(Field.java:949)at reflect.FieldTrouble.main(FieldTrouble.java:14)

做set方式之前可以通过 isAssignableFrom 方法来进行检测,检测之后再进行处理:

Integer.class.isAssignableFrom(int.class) == false;
int.class.isAssignableFrom(Integer.class) == false

另外,final 标识的成员变量是不能用set方法重新设置其值的,会抛出 IllegalAccessException 异常。

4 java.lang.reflect.Method

方法就是一段可执行的代码,可以是被继承而来的,也可以进行重载和重写,或者被编译器强制隐藏。但是相反的,反射代码是使方法选择被限制在一个特定的类中,而不考虑它的父类,虽然我们有办法查找到它的父类,但是这不是方法的反射能做到的,所以这里很容易引起问题。

4.1 获取Method的声明

方法的声明包括方法名称、描述符、参数、返回类型和异常表。类java.lang.reflect.Method 提供可以获取这些信息的方式。

  1. 获取方法的名称,String getName()
  2. 获取方法的描述符,int getModifiers() 返回值可以参见上一篇文章的介绍。
  3. 返回方法的返回值类型,Class<?> getReturnType()Type getGenericReturnType
  4. 返回方法的参数(列表),Class<?>[] getParameterTypes()Type[] getGenericParameterTypes()
  5. 返回方法的异常信息,Class<?>[] getExceptionTypes()Type[] getGenericExceptionTypes()

为什么获取方法的参数、返回值类型和异常表会有两个方法呢?因为带有Generic是返回声明的类型,即使这个声明的类型是泛型,也会返回泛型标识符,而不会返回真正的类型

例子如下:

public class MethodSpy {private static final String  fmt = "%24s: %s%n";// for the morbidly curious<E extends RuntimeException> void genericThrow() throws E {}public static void main(String... args) {try {Class<?> c = Class.forName(args[0]);Method[] allMethods = c.getDeclaredMethods();for (Method m : allMethods) {if (!m.getName().equals(args[1])) {continue;}out.format("%s%n", m.toGenericString());out.format(fmt, "ReturnType", m.getReturnType());out.format(fmt, "GenericReturnType", m.getGenericReturnType());Class<?>[] pType  = m.getParameterTypes();Type[] gpType = m.getGenericParameterTypes();for (int i = 0; i < pType.length; i++) {out.format(fmt,"ParameterType", pType[i]);out.format(fmt,"GenericParameterType", gpType[i]);}Class<?>[] xType  = m.getExceptionTypes();Type[] gxType = m.getGenericExceptionTypes();for (int i = 0; i < xType.length; i++) {out.format(fmt,"ExceptionType", xType[i]);out.format(fmt,"GenericExceptionType", gxType[i]);}}} catch (ClassNotFoundException x) {x.printStackTrace();}}
}

这个例子的大致意思是输入一个类,以及要获取方法信息的方法名。
输入结果

  1. java.lang.ClassgetConstructor 方法

    • $ java MethodSpy java.lang.Class cast
      public T java.lang.Class.cast(java.lang.Object)ReturnType: class java.lang.ObjectGenericReturnType: TParameterType: class java.lang.ObjectGenericParameterType: class java.lang.Object
      
  2. java.lang.Classcast 方法

    • $ java MethodSpy java.lang.Class cast
      public T java.lang.Class.cast(java.lang.Object)ReturnType: class java.lang.ObjectGenericReturnType: TParameterType: class java.lang.ObjectGenericParameterType: class java.lang.Object
      
    • cast 方法的返回值是就是泛型,标识符是 T,所以 getGenericReturnType() 方法会返回 T ,而 getReturnType() 则返回 java.lang.Object,也就是泛型擦除之后的类型。

  3. java.io.PrintStreamformat 方法

    • $ java MethodSpy java.io.PrintStream format
      public java.io.PrintStream java.io.PrintStream.format(java.util.Locale,java.lang.String,java.lang.Object[])ReturnType: class java.io.PrintStreamGenericReturnType: class java.io.PrintStreamParameterType: class java.util.LocaleGenericParameterType: class java.util.LocaleParameterType: class java.lang.StringGenericParameterType: class java.lang.StringParameterType: class [Ljava.lang.Object;GenericParameterType: class [Ljava.lang.Object;
      public java.io.PrintStream java.io.PrintStream.format(java.lang.String,java.lang.Object[])ReturnType: class java.io.PrintStreamGenericReturnType: class java.io.PrintStreamParameterType: class java.lang.StringGenericParameterType: class java.lang.StringParameterType: class [Ljava.lang.Object;GenericParameterType: class [Ljava.lang.Object;
      

4.2 获取参数的信息

我们单独拿出一章讲解获取参数的信息是因为参数比较特殊,为了安全和内存考虑,class的字节码文件里并不会存储参数的名称,比如一些参数名,如secret或password,可能会公开有关安全敏感方法的信息,比如很长的参数,存储其参数名会引起内存暴增。
当然我们执行时加上-parameters参数,可以强制存储其参数名称,默认情况下是不会存储的。

官方有一个打印参数的demo代码:

public class MethodParameterSpy {private static final String  fmt = "%24s: %s%n";// for the morbidly curious<E extends RuntimeException> void genericThrow() throws E {}public static void printClassConstructors(Class c) {Constructor[] allConstructors = c.getConstructors();out.format(fmt, "Number of constructors", allConstructors.length);for (Constructor currentConstructor : allConstructors) {printConstructor(currentConstructor);}  Constructor[] allDeclConst = c.getDeclaredConstructors();out.format(fmt, "Number of declared constructors",allDeclConst.length);for (Constructor currentDeclConst : allDeclConst) {printConstructor(currentDeclConst);}          }public static void printClassMethods(Class c) {Method[] allMethods = c.getDeclaredMethods();out.format(fmt, "Number of methods", allMethods.length);for (Method m : allMethods) {printMethod(m);}        }public static void printConstructor(Constructor c) {out.format("%s%n", c.toGenericString());Parameter[] params = c.getParameters();out.format(fmt, "Number of parameters", params.length);for (int i = 0; i < params.length; i++) {printParameter(params[i]);}}public static void printMethod(Method m) {out.format("%s%n", m.toGenericString());out.format(fmt, "Return type", m.getReturnType());out.format(fmt, "Generic return type", m.getGenericReturnType());Parameter[] params = m.getParameters();for (int i = 0; i < params.length; i++) {printParameter(params[i]);}}public static void printParameter(Parameter p) {out.format(fmt, "Parameter class", p.getType());out.format(fmt, "Parameter name", p.getName());out.format(fmt, "Modifiers", p.getModifiers());out.format(fmt, "Is implicit?", p.isImplicit());out.format(fmt, "Is name present?", p.isNamePresent());out.format(fmt, "Is synthetic?", p.isSynthetic());}public static void main(String... args) {        try {printClassConstructors(Class.forName(args[0]));printClassMethods(Class.forName(args[0]));} catch (ClassNotFoundException x) {x.printStackTrace();}}
}

获取到参数是Parameter,和Field一样,同样有这几种方法:

  • getType():参数类型
  • getName():参数名称,如果编译器加了参数-parameters,则会返回真正的参数名,如果没有加,则参数会是argN的形式,N是第几个参数。
  • getModifiers():参数标识符,不详细介绍了。
  • isImplicit():如果是隐式声明,则返回true。比如内部类会隐式声明parent成员变量和构造器。

我们的代码:

public class MethodParameterExamples {public class InnerClass { }
}

编译器真正生成的代码:

public class MethodParameterExamples {public class InnerClass {final MethodParameterExamples parent;InnerClass(final MethodParameterExamples this$0) {parent = this$0; }}
}
  • isNamePresent(): 名称是否可以同样跟-parameters有关。
  • isSynthetic(): 是否是编译器生成的。

比如用上面的代码获取下面类的方法信息:

public class ExampleMethods<T> {public boolean simpleMethod(String stringParam, int intParam) {System.out.println("String: " + stringParam + ", integer: " + intParam); return true;}public int varArgsMethod(String... manyStrings) {return manyStrings.length;}public boolean methodWithList(List<String> listParam) {return listParam.isEmpty();}public <T> void genericMethod(T[] a, Collection<T> c) {System.out.println("Length of array: " + a.length);System.out.println("Size of collection: " + c.size()); }}

编译器加 -parameters 的执行结果如下:

Number of constructors: 1Constructor #1
public ExampleMethods()Number of declared constructors: 1Declared constructor #1
public ExampleMethods()Number of methods: 4Method #1
public boolean ExampleMethods.simpleMethod(java.lang.String,int)Return type: booleanGeneric return type: booleanParameter class: class java.lang.StringParameter name: stringParamModifiers: 0Is implicit?: falseIs name present?: trueIs synthetic?: falseParameter class: intParameter name: intParamModifiers: 0Is implicit?: falseIs name present?: trueIs synthetic?: falseMethod #2
public int ExampleMethods.varArgsMethod(java.lang.String...)Return type: intGeneric return type: intParameter class: class [Ljava.lang.String;Parameter name: manyStringsModifiers: 0Is implicit?: falseIs name present?: trueIs synthetic?: falseMethod #3
public boolean ExampleMethods.methodWithList(java.util.List<java.lang.String>)Return type: booleanGeneric return type: booleanParameter class: interface java.util.ListParameter name: listParamModifiers: 0Is implicit?: falseIs name present?: trueIs synthetic?: falseMethod #4
public <T> void ExampleMethods.genericMethod(T[],java.util.Collection<T>)Return type: voidGeneric return type: voidParameter class: class [Ljava.lang.Object;Parameter name: aModifiers: 0Is implicit?: falseIs name present?: trueIs synthetic?: falseParameter class: interface java.util.CollectionParameter name: cModifiers: 0Is implicit?: falseIs name present?: trueIs synthetic?: false

不加参数的执行结果:

  Number of constructors: 1
public reflect.ExampleMethods()Number of parameters: 0
Number of declared constructors: 1
public reflect.ExampleMethods()Number of parameters: 0Number of methods: 4
public boolean reflect.ExampleMethods.simpleMethod(java.lang.String,int)Return type: booleanGeneric return type: booleanParameter class: class java.lang.StringParameter name: arg0Modifiers: 0Is implicit?: falseIs name present?: falseIs synthetic?: falseParameter class: intParameter name: arg1Modifiers: 0Is implicit?: falseIs name present?: falseIs synthetic?: false
public int reflect.ExampleMethods.varArgsMethod(java.lang.String...)Return type: intGeneric return type: intParameter class: class [Ljava.lang.String;Parameter name: arg0Modifiers: 0Is implicit?: falseIs name present?: falseIs synthetic?: false
public boolean reflect.ExampleMethods.methodWithList(java.util.List<java.lang.String>)Return type: booleanGeneric return type: booleanParameter class: interface java.util.ListParameter name: arg0Modifiers: 0Is implicit?: falseIs name present?: falseIs synthetic?: false
public <T> void reflect.ExampleMethods.genericMethod(T[],java.util.Collection<T>)Return type: voidGeneric return type: voidParameter class: class [Ljava.lang.Object;Parameter name: arg0Modifiers: 0Is implicit?: falseIs name present?: falseIs synthetic?: falseParameter class: interface java.util.CollectionParameter name: arg1Modifiers: 0Is implicit?: falseIs name present?: falseIs synthetic?: false

4.3 获取方法的标识符

int getModifiers获取标识符,这里举个例子就好,不做详细介绍。

public class MethodModifierSpy {private static int count;private static synchronized void inc() { count++; }private static synchronized int cnt() { return count; }public static void main(String... args) {try {Class<?> c = Class.forName(args[0]);Method[] allMethods = c.getDeclaredMethods();for (Method m : allMethods) {if (!m.getName().equals(args[1])) {continue;}out.format("%s%n", m.toGenericString());out.format("  Modifiers:  %s%n",Modifier.toString(m.getModifiers()));out.format("  [ synthetic=%-5b var_args=%-5b bridge=%-5b ]%n",m.isSynthetic(), m.isVarArgs(), m.isBridge());inc();}out.format("%d matching overload%s found%n", cnt(),(cnt() == 1 ? "" : "s"));} catch (ClassNotFoundException x) {x.printStackTrace();}}
}

输出结果:

$ java MethodModifierSpy java.lang.Object wait
public final void java.lang.Object.wait() throws java.lang.InterruptedExceptionModifiers:  public final[ synthetic=false var_args=false bridge=false ]
public final void java.lang.Object.wait(long,int)throws java.lang.InterruptedExceptionModifiers:  public final[ synthetic=false var_args=false bridge=false ]
public final native void java.lang.Object.wait(long)throws java.lang.InterruptedExceptionModifiers:  public final native[ synthetic=false var_args=false bridge=false ]
3 matching overloads found$ java MethodModifierSpy java.lang.StrictMath toRadians
public static double java.lang.StrictMath.toRadians(double)Modifiers:  public static strictfp[ synthetic=false var_args=false bridge=false ]
1 matching overload found$ java MethodModifierSpy MethodModifierSpy inc
private synchronized void MethodModifierSpy.inc()Modifiers: private synchronized[ synthetic=false var_args=false bridge=false ]
1 matching overload found$ java MethodModifierSpy java.lang.Class getConstructor
public java.lang.reflect.Constructor<T> java.lang.Class.getConstructor(java.lang.Class<T>[]) throws java.lang.NoSuchMethodException,java.lang.SecurityExceptionModifiers: public transient[ synthetic=false var_args=true bridge=false ]
1 matching overload found$ java MethodModifierSpy java.lang.String compareTo
public int java.lang.String.compareTo(java.lang.String)Modifiers: public[ synthetic=false var_args=false bridge=false ]
public int java.lang.String.compareTo(java.lang.Object)Modifiers: public volatile[ synthetic=true  var_args=false bridge=true  ]
2 matching overloads found

4.4 执行方法

使用反射执行方法(Invoking Methods)是很简单的事情,调用:

public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException

即可,obj 是拥有方法的 Class 的实例,args 是方法的参数。

注意方法调用可能抛出 IllegalAccessException、IllegalArgumentException、InvocationTargetException 异常。

4.5 反射之Method的一些注意事项

  1. 查找方法时c.getMethod(mName, cArg) 注意会抛出异常。
  2. 私有方法调用时会抛出IllegalAccessException,但是这个可以通过AccessibleObject.setAccessible()设置成功后可以调用成功。
  3. 方法调用时抛出IllegalArgumentException,抛出这个异常的原因是参数不合法。
  4. 方法调用时抛出InvocationTargetException,这个异常比较特殊,方法调用成功了,但是方法内部抛出了异常,所有invoke()会抛出这个异常,可以通过Throwable cause = IllegalArgumentException.getCause()和cause.getMessage获取到异常信息

5. 查找一个类

Java里面的类型是一个引用或者一个基本类型,类、枚举、或者数组都是继承于 java.lang.Object ,它们和接口一样都是引用类型,对于这些类型,JVM 提供了方式可以在运行中获取对象对应类型,也就是它属于哪个Class。java.lang.class 也提供了创建 Class 和 Class 对应的对象的方式。

本文讲一下目前有哪儿些方式可以获取到一个类(Class)。

所有的反射操作的切入点都是 java.lang.Class,这也印证了 Java 是面向对象编程语言,在包 java.lang.reflect 中,除了 java.lang.reflect.ReflectPermission 之外都没有 public 的构造方法,为了得到这些类,需要在 java.lang.Class 上调用对应的方法。

5.1 Object.getClass()

引用对象可以调用getClass()方法获取到它的类。

Class c = "foo".getClass();
System.out.println(c);//class java.lang.StringClass c = System.out.getClass();
System.out.println(c);//class java.io.PrintStreambyte[] bytes = new byte[1024];
Class c = bytes.getClass();
System.out.println(c);//class [BSet<String> s = new HashSet<String>();
Class c = s.getClass();
System.out.println(c);//class java.util.HashSet

这里要注意是的通过接口的引用调用其 getClass() 方法是返回引用对象的真正类,而不是接口的名称。数组对象的Class会以 [ 开头。

5.2 .class

.class的方式是使用Class本身调用.class获取。

比如:

boolean b;
Class c = b.getClass();   // compile-time error
Class c = boolean.class;  // booleanClass c = java.io.PrintStream.class;//class java.io.PrintStream
Class c = int[][][].class;//class [[[I

5.3 Class.forName()

forName()是声明异常类方法,如果找不到这个类会抛出java.lang.ClassNotFoundException异常。

比如:

Class c = Class.forName("com.duke.MyLocaleServiceProvider");
Exception in thread "main" java.lang.ClassNotFoundException: com.duke.MyLocaleServiceProviderat java.net.URLClassLoader.findClass(URLClassLoader.java:381)at java.lang.ClassLoader.loadClass(ClassLoader.java:424)at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)at java.lang.ClassLoader.loadClass(ClassLoader.java:357)at java.lang.Class.forName0(Native Method)at java.lang.Class.forName(Class.java:264)at reflect.Main.main(Main.java:8)

如果能找到类:

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

5.4 基础类型包装类的TYPE属性

基础类型的包装类都有一个TYPE属性,和包装类的Class是一样的。

Class c = Double.TYPE;
Class c = Void.TYPE;

5.5 其他方式

5.5.1 Class.getSuperclass()

获得当前类的父类。

5.5.2 Class.getClasses()

返回当前类的内部定义的类、接口、枚举类型。

5.5.3 Class.getDeclaredClasses()

返回当前类中显式声明的接口、枚举类型。

5.5.4 引申

Class.getDeclaringClass()方法可以引申出另外几个方法:

  • java.lang.reflect.Field.getDeclaringClass()
  • java.lang.reflect.Method.getDeclaringClass()

java.lang.reflect.Constructor.getDeclaringClass()
作用和Class.getDeclaredClasses()一样,只是作用于的类型不一样,分别是Field、Method、Constructor。

5.5.5 Class.getEnclosingClass()

返回当前类的封闭类,也就是包含这个类的类,比如:

Class c = Thread.State.class.getEnclosingClass();
System.out.println(c);//class java.lang.Thread

6. 关于Java中的Class和Object的区别和理解

在Java的世界里,一切皆是对象,所有的类都是继承于 Object 类,而记录对象的类型的信息是由Class类来完成的。

Object类和Class类没有直接的关系。

Object类是一切java类的父类,对于普通的java类,即便不声明,也是默认继承了Object类。典型的,可以使用Object类中的 toString() 方法。

Object obj = new Person();
Person person = new Person();
person.toString(); //使用父类的方法

Class类是用于java反射机制的,一切java类,都有一个对应的Class对象,他是一个final类。Class 类的实例表示,正在运行的 Java 应用程序中的类和接口。

在Java中,每个class都有一个相应的Class对象。也就是说,当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个Class对象。

Java 反射系列 —— 学习笔记相关推荐

  1. java 反射api_Java学习笔记--反射API

    反射API 1.反射API的介绍 通过反射API可以获取Java程序在运行时刻的内部结构.比如Java类中包含的构造方法.域和方法等元素,并可以与这些元素进行交换. 按照 一般地面向对象的设计思路,一 ...

  2. java/android 设计模式学习笔记(1)--- 单例模式

    前段时间公司一些同事在讨论单例模式(我是最渣的一个,都插不上嘴 T__T ),这个模式使用的频率很高,也可能是很多人最熟悉的设计模式,当然单例模式也算是最简单的设计模式之一吧,简单归简单,但是在实际使 ...

  3. 庄懂的技术美术入门课系列——学习笔记

    关AO的知识之前涉及到就# 庄懂的技术美术入门课系列--学习笔记 本系列旨记录看视频学习时的一些看个人的理解和思考 1.三色混合的环境光 基本思路: 物体的环境光可以想象成是在物体四周全方位向物体射出 ...

  4. 01.Java 编程入门学习笔记20210307

    Java 编程入门学习笔记-day01 第0章:编程入门 1.计算机的概述 计算机 = 硬件 + 软件 1.1硬件:冯诺依曼体系 CPU: CPU的衡量标准:速度的计量单位是赫兹(Hz),1Hz相当于 ...

  5. java/android 设计模式学习笔记(1)---单例模式

    前段时间公司一些同事在讨论单例模式(我是最渣的一个,都插不上嘴 T__T ),这个模式使用的频率很高,也可能是很多人最熟悉的设计模式,当然单例模式也算是最简单的设计模式之一吧,简单归简单,但是在实际使 ...

  6. java/android 设计模式学习笔记(3)---工厂方法模式

    这篇来介绍一下工厂方法模式(Factory Method Pattern),在实际开发过程中我们都习惯于直接使用 new 关键字用来创建一个对象,可是有时候对象的创造需要一系列的步骤:你可能需要计算或 ...

  7. [转]Java中文处理学习笔记——Hello Unicode

    Java中文处理学习笔记--Hello Unicode 作者: 车东 Email: chedongATbigfoot.com/chedongATchedong.com 写于:2002/07 最后更新: ...

  8. Java中文处理学习笔记——Hello Unicode (转)

    Java中文处理学习笔记--Hello Unicode (转)[@more@] Java中文处理学习笔记--Hello Unicode 作者: 车东 chedong@bigfoot.com 最后更新: ...

  9. Java中文处理学习笔记

    Java中文处理学习笔记--Hello Unicode 作者: 车东 Email: chedongATbigfoot.com/chedongATchedong.com 写于:2002/07 最后更新: ...

最新文章

  1. 关于JAVA的String类的一些方法
  2. mysql中length与char_length字符长度函数使用方法
  3. 实现SmartForms 连续打印
  4. MonkeyRunner——如何在实体机上启动一个程序
  5. 图文解说:Nginx+tomcat配置集群负载均衡
  6. 安卓手机与PC不得不说的那些事 之 篇一 网络分享
  7. 汽车电子专业知识篇(九)-charge pump的原理介绍
  8. 彻底理清重载函数匹配
  9. java rhino 运行 js_Mozilla Rhino :如何从Java调用JS函数
  10. linux启动大叶机制,DPDK-Suricata应用部署
  11. 解决Spring+Quartz无法自动注入bean问题
  12. 疑难杂症 | Win10解压文件后乱码
  13. 单容水箱液位pid控制实验报告_过程控制实验-单容水箱液位控制系统
  14. 如何将wmv视频格式快速转换成mp4视频呢
  15. Keil5下载及安装
  16. 微型计算机鼠标连接,解决无线鼠标重新对码问题
  17. 嗅探器c语言源码,C语言嗅探器带报告
  18. 计算机网络ip 地址怎么查询系统,怎么查看电脑的ip地址_查看ip地址命令介绍 - 驱动管家...
  19. 产品经理-自然资源行业4大产品线整理
  20. 孙宇晨真的有道歉吗?逐句解读孙宇晨避重就轻的“致歉信”

热门文章

  1. Time_Wait相关问题
  2. 幼儿园故事导入语案例_幼儿园经典教学导入语
  3. C#event EventHandler事件触发
  4. C#EventHandler用法
  5. 前几年写的几篇关于电影的东东:《英雄》人物
  6. python----计算邮资问题
  7. 修改最后一次commit提交信息
  8. Shell脚本利用ffmpeg批量处理视频文件
  9. Android 快速开发系列 打造万能的ListView GridView 适配器
  10. Activity启动流程(基于Android26),Android面试超详细知识点