Java 反射系列 —— 学习笔记

news2024/11/26 10:28:08

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的构造器。
  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> {
	@Override
	public void show(String value) {
 		System.out.println(value);
 	}
}
 
public class NumberShowImpl implements GenericInterface<Integer> {
 	@Override
	public 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.UnsupportedEncodingException
       GenericParameterType[0]: class java.io.OutputStream
       GenericParameterType[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.UnsupportedEncodingException
       GenericParameterType[0]: class java.lang.String
       GenericParameterType[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.UnsupportedEncodingException
       GenericParameterType[0]: class java.io.File
       GenericParameterType[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]: int
       GenericParameterType[1]: int
      *GenericParameterType[2]: class [C
public java.lang.String(char[],int,int)
      *GenericParameterType[0]: class [C
       GenericParameterType[1]: int
       GenericParameterType[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:
  Duke
  Fang

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: ConstructorTrouble
        at 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 constructor
        at 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);   // solution
            c.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 access
  a 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 present
    	   		if ((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)42
    at 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.Class getConstructor 方法

    • $ java MethodSpy java.lang.Class cast
      public T java.lang.Class.cast(java.lang.Object)
                    ReturnType: class java.lang.Object
             GenericReturnType: T
                 ParameterType: class java.lang.Object
          GenericParameterType: 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.Object
             GenericReturnType: T
                 ParameterType: class java.lang.Object
          GenericParameterType: 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.PrintStream
             GenericReturnType: class java.io.PrintStream
                 ParameterType: class java.util.Locale
          GenericParameterType: class java.util.Locale
                 ParameterType: class java.lang.String
          GenericParameterType: class java.lang.String
                 ParameterType: 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.PrintStream
             GenericReturnType: class java.io.PrintStream
                 ParameterType: class java.lang.String
          GenericParameterType: class java.lang.String
                 ParameterType: 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: 1

Constructor #1
public ExampleMethods()

Number of declared constructors: 1

Declared constructor #1
public ExampleMethods()

Number of methods: 4

Method #1
public boolean ExampleMethods.simpleMethod(java.lang.String,int)
             Return type: boolean
     Generic return type: boolean
         Parameter class: class java.lang.String
          Parameter name: stringParam
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false
         Parameter class: int
          Parameter name: intParam
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false

Method #2
public int ExampleMethods.varArgsMethod(java.lang.String...)
             Return type: int
     Generic return type: int
         Parameter class: class [Ljava.lang.String;
          Parameter name: manyStrings
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false

Method #3
public boolean ExampleMethods.methodWithList(java.util.List<java.lang.String>)
             Return type: boolean
     Generic return type: boolean
         Parameter class: interface java.util.List
          Parameter name: listParam
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false

Method #4
public <T> void ExampleMethods.genericMethod(T[],java.util.Collection<T>)
             Return type: void
     Generic return type: void
         Parameter class: class [Ljava.lang.Object;
          Parameter name: a
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false
         Parameter class: interface java.util.Collection
          Parameter name: c
               Modifiers: 0
            Is implicit?: false
        Is name present?: true
           Is synthetic?: false

不加参数的执行结果:

  Number of constructors: 1
public reflect.ExampleMethods()
    Number of parameters: 0
Number of declared constructors: 1
public reflect.ExampleMethods()
    Number of parameters: 0
       Number of methods: 4
public boolean reflect.ExampleMethods.simpleMethod(java.lang.String,int)
             Return type: boolean
     Generic return type: boolean
         Parameter class: class java.lang.String
          Parameter name: arg0
               Modifiers: 0
            Is implicit?: false
        Is name present?: false
           Is synthetic?: false
         Parameter class: int
          Parameter name: arg1
               Modifiers: 0
            Is implicit?: false
        Is name present?: false
           Is synthetic?: false
public int reflect.ExampleMethods.varArgsMethod(java.lang.String...)
             Return type: int
     Generic return type: int
         Parameter class: class [Ljava.lang.String;
          Parameter name: arg0
               Modifiers: 0
            Is implicit?: false
        Is name present?: false
           Is synthetic?: false
public boolean reflect.ExampleMethods.methodWithList(java.util.List<java.lang.String>)
             Return type: boolean
     Generic return type: boolean
         Parameter class: interface java.util.List
          Parameter name: arg0
               Modifiers: 0
            Is implicit?: false
        Is name present?: false
           Is synthetic?: false
public <T> void reflect.ExampleMethods.genericMethod(T[],java.util.Collection<T>)
             Return type: void
     Generic return type: void
         Parameter class: class [Ljava.lang.Object;
          Parameter name: arg0
               Modifiers: 0
            Is implicit?: false
        Is name present?: false
           Is synthetic?: false
         Parameter class: interface java.util.Collection
          Parameter name: arg1
               Modifiers: 0
            Is implicit?: false
        Is name present?: false
           Is 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.InterruptedException
  Modifiers:  public final
  [ synthetic=false var_args=false bridge=false ]
public final void java.lang.Object.wait(long,int)
  throws java.lang.InterruptedException
  Modifiers:  public final
  [ synthetic=false var_args=false bridge=false ]
public final native void java.lang.Object.wait(long)
  throws java.lang.InterruptedException
  Modifiers:  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.SecurityException
  Modifiers: 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.String

Class c = System.out.getClass();
System.out.println(c);//class java.io.PrintStream

byte[] bytes = new byte[1024];
Class c = bytes.getClass();
System.out.println(c);//class [B

Set<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;  // boolean

Class 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.MyLocaleServiceProvider
    at 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对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/42637.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

逆势涨薪3k!新媒体运营毅然转行测试,我的入行秘籍是什么?

不尝试永远都不会成功&#xff0c;勇敢的尝试是成功的一半。 大学毕业做运营&#xff0c;业务难精进&#xff0c;薪资难提升 “你大学专业是商务英语&#xff0c;为什么毕业后会选择做新媒体运营呢&#xff1f;” 其实我当时没有想那么多的&#xff0c;商务英语的就业方向一个…

苹果电容笔值得买吗?2022最新电容笔推荐

如今&#xff0c;许多人都喜欢用IPAD来学习记录&#xff0c;或是安静地作画。很多ipad的用户&#xff0c;都很重视它的实用性&#xff0c;因为他们发现&#xff0c;如果有一款功能不错的电容笔来搭配ipad&#xff0c;那么ipad的实用性就会得到极大的提高。事实上&#xff0c;如…

开发 Chrome 扩展程序的利弊

作为一名软件开发人员,您总是希望从事能够提高您的技术技能并赚钱的项目。有什么比开发现金流 chrome 扩展程序更好的方法呢? 在本文中,我将从软件开发人员的角度概述开发 chrome 扩展程序的一些优点和缺点。 开发 Chrome 扩展程序的好处 Chrome 扩展程序是软件开发人员接…

基于遗传算法与神经网络的测井预测(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

【序列召回推荐】(task4)多兴趣召回MIND模型

note Hinton在2011年提出的capsule network&#xff0c;通过EM期望值最大化算法&#xff0c;用动态路由代替反向传播进行更新参数&#xff0c;学习不同capsule之间的连接权重&#xff0c;实现比CNN更优秀的空间关系建模效果&#xff08;CNN可能对同一个图像的旋转版本识别错误…

Java笔记(十四)

文献种类&#xff1a;专题技术总结文献 开发工具与关键技术&#xff1a; IntelliJ IDEA、Java 语言 作者&#xff1a; 方建恒 年级&#xff1a; 2020 撰写时间&#xff1a; 2022 年 11 月 28 日 Java笔记(十四) 今天我给大家继续分享一下我的Java笔记&#xff0c; 我们继续来…

终于读完了阿里云p9专家分享云原生Kubernetes全栈架构师实战文档

都说程序员工资高、待遇好&#xff0c; 2022 金九银十到了&#xff0c;你的小目标是 30K、40K&#xff0c;还是 16薪的 20K&#xff1f;作为一名 Java 开发工程师&#xff0c;当能力可以满足公司业务需求时&#xff0c;拿到超预期的 Offer 并不算难。然而&#xff0c;提升 Java…

Linux便捷操作

1. Tab 这是你不能没有的 Linux 快捷键。它将节省你 Linux 命令行中的大量时间。 只需要输入一个命令&#xff0c;文件名&#xff0c;目录名甚至是命令选项的开头&#xff0c;并敲击 tab 键。它将自动完成你输入的内容&#xff0c;或为你显示全部可能的结果。 如果你只记一个…

中国住宅设施杂志中国住宅设施杂志社中国住宅设施编辑部2022年第9期目录

景观园林《中国住宅设施》投稿&#xff1a;cnqikantg126.com 市政园林景观工程施工项目管理的基本方法与措施 蒋伟;刘巍;张辉; 1-3 低成本风景园林设计与相关问题分析 魏小静; 4-6 城市文化公园景观设计探究——以临夏河州牡丹文化公园为例 姜丽; 7-9 建筑设计 …

一文读懂:低代码和无代码的演进历程、应用范围

低代码和无代码技术的演进发展 整个软件开发的演进路径大致可以分为四个阶段&#xff1a;第一代程序设计语言&#xff1b;第二代是汇编语言&#xff1b;第三代是现在常见的高级语言&#xff0c;比如 Python、Java 等&#xff1b;第四代就是低代码和无代码技术。低代码、无代码…

Pro_11丨跟踪+目标出场自适应切换

量化策略开发&#xff0c;高质量社群&#xff0c;交易思路分享等相关内容 『正文』 ˇ 大家好&#xff0c;今天我们分享第11期策略——跟踪目标出场自适应切换策略。本期策略是2022年度倒数第2期策略&#xff0c;2023年度松鼠俱乐部内容会更加丰富&#xff0c;12月出预告敬请…

【Vagrant】使用 Vagrant 快速创建多台 centos7 虚拟机

问题场景&#xff1a; 最近在学习数据库的主从复制&#xff0c;因此需要安装两个虚拟机&#xff0c;一个放主数据库&#xff0c;一个放从数据库&#xff08;不会用 Docker ,咱就多搭几个虚拟机吧 &#xff09;&#xff0c;因此记录使用 Vagrant 快速搭建两个 CentOS 7 的教程&a…

Python海龟turtle基础知识大全与画图集合

Turtle图形库 Turtle 库是 Python 内置的图形化模块&#xff0c;属于标准库之一&#xff0c;位于 Python 安装目录的 lib 文件夹下&#xff0c;常用函数有以下几种&#xff1a; 一.Turtle绘图的基础知识 画布是turtle用于绘图区域&#xff0c;我们可以设置它的大小和初始位置。…

使用支持向量机的基于异常的入侵检测系统

使用支持向量机的基于异常的入侵检测系统使用支持向量机的基于异常的入侵检测系统学习目标&#xff1a;学习内容&#xff1a;1.⼀种智能⼊侵检测系统第⼀阶段第⼆阶段&#xff1a;分类总结2.使用支持向量机的基于异常的入侵检测系统1.预处理入侵数据集2.基于信息增益的特征排名…

c++ CJsonObject 读写json

CJsonObject简介 CJsonObject是Bwar基于cJSON全新开发一个C版的JSON库&#xff0c;CJsonObject的最大优势是简单、轻量、跨平台&#xff0c;开发效率极高&#xff0c;尤其对多层嵌套json的读取和生成、修改极为方便。CJsonObject比cJSON简单易用得多&#xff0c;且只要不是有意…

Linux —— 文件操作

目录 1.内核提供的文件系统调用 1.1open和close 1.2write和read 2.文件描述 2.1文件描述符 2.2文件描述符分配规则 3.重定向 3.1最“挫”的重定向 3.2使用系统调用 3.3重定向原理 3.4让我们的"shell"支持重定向操作 4.一切皆文件 1.内核提供的文件系统调用…

什么是杜邦分析?杜邦分析法的公式及示例

什么是杜邦分析? 杜邦分析也称为杜邦恒等式、杜邦方程、杜邦框架、杜邦模型或杜邦方法&#xff0c;是一个多步骤的财务方程式&#xff0c;可以深入了解企业的基本绩效。杜邦模型对影响公司股本回报率 (ROE) 的关键指标进行了全面分析。杜邦分析的另一个术语是杜邦模型。这些名…

做app的测试,你大概率会用到这个命令,尤其是做monkey测试

1.普通命令 1.1 devices命令 语法格式 &#xff1a;adb devices [-l] # 作用 &#xff1a;返回已连接设备的信息 # 示例 &#xff1a;adb devices : 返回设备的信息adb devices -l : 返回设备的详细信息1.2 help命令 语法格式 &#xff1a;adb --help # 作用 &#xff1…

人工智能:人工神经网络的应用场景

❤️作者主页&#xff1a;IT技术分享社区 ❤️作者简介&#xff1a;大家好,我是IT技术分享社区的博主&#xff0c;从事C#、Java开发九年&#xff0c;对数据库、C#、Java、前端、运维、电脑技巧等经验丰富。 ❤️个人荣誉&#xff1a; 数据库领域优质创作者&#x1f3c6;&#x…

android接入微信API相关细节

细节1 想要接入微信&#xff0c;如接入分享微信功能、跳转小程序功能等&#xff1b;首先需要到微信开放平台申请AppId&#xff0c;如何申请在开放平台上的流程很清楚&#xff0c;就不赘述了 但有个细节就是应用包名签名&#xff0c;这个应用包名签名记得是以app有正式签名文件…