前面我们介绍了Java反射机制的一些预备知识,详细说明了下类的加载过程,Java四大类加载器,以及双亲委托模式。下面我们将继续对Java反射机制的其他知识作以介绍。
详解Java反射机制(二)
1.使用类加载器来加载资源文件
ClassLoader类基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节码文件,然后从字节码中定义一个Java类,即java.lang.Class类的一个实例。
除此之外,ClassLoader还可以加载Java应用(src下)所需的资源,如加载配置文件等。
1.1当配置文件在src下,使用类加载器进行加载
- 加载类路径下(src下)jdbc.properties配置文件的代码案例。下图为jdbc.properties配置文件中的内容。
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class TestLoaderFile {
public static void main(String[] args) throws IOException {
Properties properties = new Properties();//创建集合,此集合为map集合(key-value)
//创建类的对象
Class<TestLoaderFile> aClass = TestLoaderFile.class;
//获取类加载器对象
ClassLoader classLoader = aClass.getClassLoader();
InputStream in = classLoader.getResourceAsStream("jdbc.properties");
properties.load(in);
System.out.println(properties);
}
}
运行后的结果为:
1.2 当配置文件在src下具体的包中,使用类加载器进行加载
- 当配置文件在src下具体的包中,使用类加载器进行加载。下图中config.properties配置文件位于src下org.westos.demo6包中,通过类加载器进行加载。config.properties配置文件具体内容如下图。
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class TestLoaderFile2 {
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
//获取类的对象
Class<TestLoaderFile2> clazz = TestLoaderFile2.class;
//获取类的加载器对象
ClassLoader classLoader = clazz.getClassLoader();
//需要注意的是:当配置文件在src下具体的包下时,在加载时需要加上具体包下的路径
InputStream in = classLoader.getResourceAsStream("org/westos/demo6/config.properties");
properties.load(in);
System.out.println(properties);
}
}
运行后的结果为:
1.3 当配置文件在项目根路径下,此时可以使用FileInputStream读取
- 当配置文件在项目根路径下,可以使用FileInputStream读取配置文件。下图中的out.properties文件在项目根路径下,其内容如下。
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class TestLoaderFile3 {
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
//使用FileInputStream来读取根目录下配置文件
properties.load(new FileInputStream("out.properties"));
System.out.println(properties);
}
}
运行后的结果为:
2.通过反射来查看类信息
Java程序中,所有的对象都有两种类型:编译时类型和运行时类型,而很多时候对象的编译时类型和运行时类型不一致。
例如:在程序中某些变量或者形参的类型是Object类型,但是在程序却需要调用对象运行时类型的方法,该方法不是Object中方法,该如何解决?
为了解决这些问题,程序需要在运行时发现对象和类的真实信息,常采用两种方法:
- 第1种是在编译和运行时都完全知道类型的具体信息,我们可以先使用instanceof运算符进行判断,再利用强制类型转换符将其转换成运行时类型的变量即可。
- 第2种是在编译时根本无法预知该对象和类的真实信息,程序只能依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射。
类加载器加载完类之后,在堆内存的方法区就产生一个Class类型的对象(注意:一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。可以通过这个对象看到类的结构,这个对象就像一面镜子,透过这个镜子就能看到类的结构,我们形象地称之为:反射。
2.1 java.lang.Class(Class类型对象)
所谓的java.lang.Class类型:所有的java类型(包含基本数据类型、引用数据类型、void)被加载到内存后,或者是编译器自动编译成class字节码,最终都会用一个Class对象来表示。即所有的Java类型,在内存中都表示一个Class对象。
2.2 获取Class对象的方式
Java程序中可以通过下面四种方式获得Class对象。
获取Class对象的四种方式 |
---|
1.类型名.class:适用于编译期间已知的任意类型; |
2.调用任意对象的getClass()方法,可以获取该对象的运行时类型的Class对象; |
3.使用Class类的forName(String name)静态方法,该方法需要传入一个字符串参数,该值是某个类的全限定名(全限定的意思为完整的包.类型名),该方法适用于除过数组之外的任意引用数据类型; |
4.调用类加载对象的loadClass(String name)该方法需要一个字符串参数,该值是某个类的全限定类型。 |
方式1只适用于编译器期间已知的类型,如果某个类型编译期间是已知的,优先考虑这种方式,另外基本数据类型也只能通过此种方式获得Class对象;如果某个类型编译期间未知,我们只能通过某种方式获取该类型的全名称的字符串形式,那么就需要使用3和4了,当方法在运行期间仍然无法加载该类时,会报ClassNotFoundException。
import java.io.Serializable;
import java.lang.annotation.ElementType;
/***
* 1.java.lang.Class类型:
* 所有的java类型(包括基本数据类型、引用数据类型、void)被加载到内存后,
* 或者是编译器自动编译生成的class字节码,最终都会用一个Class对象来表示。
* 即,所有的Java类型,在内存中都表示为一个Class对象。
*
*
****2.如何获取Class对象? 4种 重点
* (1)类型名.class
* 优点:最简洁
* 缺点:要求编译期这个类型就要存在
* (2)对象.getClass()
* 这个方法在java.lang.Object类型中声明的,返回对象的运行时类型。
*适用于:你的先具有对象
*(3)Class.forName("类型全名称")
*类型全名称:包.类名
* 优势:这个类型可以在编译期间未知,这个类名可以在代码中出现,
* 也可以出现在配置文件中,或者键盘输入等方式来进行指定。
*
*(4)使用类加载器对象.loadClass("类型全名称")
* 一般都是在自定义类加载器对象去加载指定路径下的类
*
*
* 这4种方式:都可以选择,就要看当前的情况,哪种能用,就用哪种,如果都能用,就用就简便的。内存后
*
*/
public class MyTest {
public static void main(String[] args) throws ClassNotFoundException {
test02();
test03();
test04();
}
public static void test01(){
Class<Integer> c1 = int.class;//基本数据类型
Class<Void> c2 = void.class;//特殊的空类型
Class<String> c3 = String.class;//系统定义的类类型
Class<MyTest> c4 = MyTest.class;//自己定义的类类型
Class<Serializable> c5 = Serializable.class;//接口类型
Class<ElementType> c6 = ElementType.class;//枚举类型
Class<Override> c7 = Override.class;//注解类型
Class<int[]> c8 = int[].class;//数组类型
//Class<Student> c9 = Student.class;//错误的,因为编译期间不存在
}
public static void test02(){
Class c1 = "".getClass();
Class c2 = String.class;
System.out.println(c1==c2);//返回true
}
public static void test03() throws ClassNotFoundException {
Class c1 = "".getClass();
Class c2 = String.class;
Class c3 = Class.forName("java.lang.String");
System.out.println(c1 == c2);//true
System.out.println(c1 == c3);//true
}
public static void test04() throws ClassNotFoundException {
Class c = MyTest.class;
ClassLoader loader = c.getClassLoader();
Class c2 = loader.loadClass("org.westos.demo3.User");
Class c3 = User.class;
System.out.println(c2 == c3);//true
}
}
3.反射的应用
关于反射的概念我们前面有所了解,下面我们对反射的应用作以详细阐述。
反射的作用 |
---|
1.在运行时能够获取任意类型的详细信息 |
2.在运行时能够创建任意引用数据类型的对象 |
3.在运行时可以为任意对象的任意属性值赋值,或者获取任意对象的任意属性的值 |
4.在运行时可以调用任意对象的任意方法 |
5.在运行时读取某个注解信息 |
6.在运行时读取某个类的泛型形参 |
下面对反射的各个应用进行学习。
3.1 应用1:运行期间获取任意类型的详细信息
利用反射在运行期间获取任意类型的详细信息的步骤:
- (1)获取这个类的Class对象
- (2)获取类的详细信息,其中包含:包名、类名、修饰符、直接父类、父接口们、属性以及构造器。
/***
*1.在运行期间去获取任意类型的详细信息
*
* 后期的时候,在框架中,例如:spring框架,会帮我们管理很多的类,
* 而这些类不是spring写的,而是我们写的,然后在运行期间spring去加载获取的。
*
*
* 步骤:
* (1)获取这个类的Class对象
* (2)获取类的详细信息:
* 其中包含:包含包名,类名,修饰符,直接父类,父接口们,属性,构造器
*
*
*
* 一切皆对象:
* (1)所有类型在内存中都是Class对象
* (2)所有的属性都是Field对象
* 例如:private int age;
*属性类型:Filed类型
*
* 类的概念:一类具有相同特性的事物的抽象描述。
* 所有的属性,有没有相同特征:
* 都是有修饰符、数据类型、名称
* 都有相同的行为操作:get获取值/set设置值
*
*所以把属性抽象为Field类,那麽一个属性被加载到内存后,是用一个Filed对象表示的。
*
*(3)所有的构造器都是Constructor的对象
* 所有的构造器都有:
* 修饰符,构造器名称,形参列表
* 都能new对象
* 所有的构造器抽象为Constructor类,那麽一个构造器被加载到内存后,是用一个Constructor对象表示。
*
* (4)所有的方法都是Method对象
* 所有的方法都有:修饰符,返回值类型,方法名,形参列表,抛出的异常列表
* 方法都能被调用(invoke)
*
*
*/
public class MyTest2 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//如果这个类名在配置文件中,先获取类名
Properties pro = new Properties();
pro.load(MyTest2.class.getClassLoader().getResourceAsStream("config.properties"));
String className = pro.getProperty("className");//key就是配置文件中=左边的属性名
//(1)获取这个类的Class对象
Class aclass = Class.forName(className);
System.out.println(aclass);
//(2)获取包的详细信息
//aClass代表com.test.demo.User这个类
//获取包名
Package pkg = aclass.getPackage();
System.out.println("包名:"+pkg.getName());
//获取类名
System.out.println("类名:"+aclass.getName());
//(3)类的修饰符
int mod = aclass.getModifiers();
System.out.println("修饰符的值:"+mod);
System.out.println("修饰符为:"+ Modifier.toString(mod));
int mod2 = String.class.getModifiers();
System.out.println("String类型的修饰符为:"+Modifier.toString(mod2));
//类的直接父类
Class superclass = aclass.getSuperclass();
System.out.println("父类的名称为:"+superclass.getName());
//类的父接口
Class[] interfaces = aclass.getInterfaces();
System.out.println("父接口们:");
for (Class inter : interfaces) {
System.out.println(inter);
}
//类的属性
/**
*(1)Fileds[] getFileds() 得到所有的公共的属性
*(2)Fileds[] getDeclaredFileds() 得到所有的声明的属性
*/
//Field[] fields = aclass.getFields();
Field[] fields = aclass.getDeclaredFields();
int count=0;
for (Field field : fields) {
count++;
int fmod = field.getModifiers();
System.out.println(count+"属性的修饰符:"+Modifier.toString(mod));
System.out.println(count+"属性的数据类型:"+field.getType().getName());
System.out.println(count+"属性的名称:"+field.getName());
}
//类的构造器
/***
* Constructor[] getConstructors():得到所有的公共构造器
* Constructor[] getDeclaredConstructors():得到所有声明的构造器
*/
//Constructor[] constructors = aclass.getConstructors();
Constructor[] constructors = aclass.getDeclaredConstructors();
int count1=0;
for (Constructor constructor : constructors) {
count1++;
int cmod = constructor.getModifiers();
System.out.println(count1+"构造器的修饰符:"+Modifier.toString(cmod));
System.out.println(count1+"构造器的名称:"+constructor.getName());
Class[] parameterTypes = constructor.getParameterTypes();
System.out.println(count1+"构造器的行参列表:"+ Arrays.toString(parameterTypes));
}
/***
* Method[] getMethods():得到所有的公共方法
*Constructor[] getDeclaredMethods():得到所有声明的方法
*
*/
count=0;
Method[] methods = aclass.getDeclaredMethods();
for (Method method : methods) {
count++;
int mmod = method.getModifiers();
System.out.println(count+"方法的修饰符:"+Modifier.toString(mod));
System.out.println(count+"方法的名称:"+method.getName());
Class[] parameterTypes = method.getParameterTypes();
System.out.println(count+"方法的行参列表:"+ Arrays.toString(parameterTypes));
System.out.println(count+"方法的返回值类型:"+method.getReturnType());
Class<?>[] exceptionTypes = method.getExceptionTypes();
System.out.println(count+"抛出的异常类型们:"+Arrays.toString(exceptionTypes));
}
}
}
运行后的结果为:
class com.test.demo.User
包名:com.test.demo
类名:com.test.demo.User
修饰符的值:1
修饰符为:public
String类型的修饰符为:public final
父类的名称为:java.lang.Object
父接口们:
interface java.io.Serializable
1属性的修饰符:public
1属性的数据类型:java.lang.String
1属性的名称:name
2属性的修饰符:public
2属性的数据类型:int
2属性的名称:age
1构造器的修饰符:public
1构造器的名称:com.test.demo.User
1构造器的行参列表:[]
2构造器的修饰符:public
2构造器的名称:com.test.demo.User
2构造器的行参列表:[class java.lang.String, int]
1方法的修饰符:public
1方法的名称:toString
1方法的行参列表:[]
1方法的返回值类型:class java.lang.String
1抛出的异常类型们:[]
2方法的修饰符:public
2方法的名称:getName
2方法的行参列表:[]
2方法的返回值类型:class java.lang.String
2抛出的异常类型们:[]
3方法的修饰符:public
3方法的名称:setName
3方法的行参列表:[class java.lang.String]
3方法的返回值类型:void
3抛出的异常类型们:[]
4方法的修饰符:public
4方法的名称:setAge
4方法的行参列表:[int]
4方法的返回值类型:void
4抛出的异常类型们:[]
5方法的修饰符:public
5方法的名称:getAge
5方法的行参列表:[]
5方法的返回值类型:int
5抛出的异常类型们:[]
3.2 应用2:运行期间可以创建任意引用数据类型的对象
在运行时可以创建任意引用数据类型的对象,通常有两种方式。
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/***
*2.在运行时可以创建任意引用数据类型的对象
*
* 方式一:使用Class对象直接new对象
* 步骤:
* (1)获取某个类的Class对象
* (2)通过Class对象来创建这个Class所代表的类型的对象
*
* 当UserTest没有无参构造时,会报java.lang.InstantiationException
*
*
* 方式二:通过Class对象先获取有参构造,然后再创建对象
* 步骤:
* (1)获取某个类的Class对象
* (2)通过Class对象来获取Constructor对象
* (3)通过Constructor对象来创建这个Class所代表的类型的对象。
*/
public class MyTest3 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//(1)获取某个类的Class对象
Class<?> aClass = Class.forName("com.test.ext.demo.UserTest");
//(2)创建对象
//obj的编译时类型是Object类型
//obj的运行时类型是UserTest类型
Object obj = aClass.newInstance();//这里的newInstance()没有参数,因为它是用无参构造创建实例的。
System.out.println(obj);
test02();
}
public static void test02() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//(1)获取某个类的对象
Class<?> aClass = Class.forName("com.test.ext.demo.UserTest2");
/***
* (1)Constructor aClass.getConstructor(parameterTypes):某个公共的构造器
* (2)Constructor aClass.getConstructor(parameterTypes):某个声明的构造器
*
* 一个类中可能存在多个构造器,但是多个构造器重载的话,形参列表一定不一样,
* 所以通过形参列表可以定义带一个唯一的构造器。
* 如果Class<?>...parameterTypes,一个都不传,即获取无参构造
*
*
*/
//(2)获取有参构造对象
Constructor<?> constructor = aClass.getDeclaredConstructor(String.class, int.class);
//当构造器私有时,仍然想通过构造器来实现创建对象,可以设置访问性
constructor.setAccessible(true);
//(3)通过Constructor对象来创建实例对象
Object obj = constructor.newInstance("张三", 23);
//这里的newInstance(实参列表),因为用的是有参构造来创建对象的
System.out.println(obj);
}
}
运行后的结果为:
3.3 应用3:运行期间可以为任意属性赋值,或者获取任意对象的任意属性的值
我们在平常编写类时注意保存无参构造,原因在于:
- 1.创建对象方便。
- 2.继承时比较方便。因为子构造器默认调用父类的无参构造。
- 3.反射创建对象方便。
运行期间为任意属性赋值或获取任意对象的任意属性的值,操作的步骤 |
---|
1.获取某个类型的Class对象 |
2.创建实例对象 |
3.为某个属性赋值,其中需要先获取某个属性Field对象 |
public class MyTest4 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException, NoSuchFieldException {
test01();
}
public static void test01() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
//(1)获取某个类的Class对象
Class<?> aClass = Class.forName("com.test.ext.demo.UserTest");
//(2)创建对象
//obj的编译时类型是Object类型
//obj的运行时类型是UserTest类型
Object obj = aClass.newInstance();
System.out.println(obj);
//(3)获取name属性的Filed对象
/***
* Field aClass.getFiled(name):获取公共的某个属性
* Filed Class.getDeclaredField(name):获取声明的某个属性
*
*
*/
Field nameField = aClass.getDeclaredField("name");
//因为name属性为私有的,所以需要设置下可访问性
nameField.setAccessible(true);
/**
* 之前,如何为属性进行赋值
* 通过对象.属性名=值;
* 属性的特点:(1)每一对象都是独立的(2)属性有默认值
* 所以在为属性赋值时,要说为哪个对象的属性赋值。
*
*/
// nameField.set(obj,value);//obj代表UserTest的对象,value代表值
nameField.set(obj,"张三");
System.out.println(obj);
//(4)为年龄属性赋值
Field ageFiled = aClass.getDeclaredField("age");
ageFiled.setAccessible(true);
ageFiled.set(obj,23);
System.out.println(obj);
//(5)获取name属性的值
//先获取name属性的Filed对象
Field nameFiled2 = aClass.getDeclaredField("name");
nameFiled2.setAccessible(true);
/**
*之前,获取属性的值
* 变量=对象名.属性名
*/
Object value = nameFiled2.get(obj);
System.out.println(value);
}
}
运行后的结果为:
3.4 应用4:运行期间可以调用任意对象的任意方法
运行期间可以调用任意对象的任意方法,操作步骤 |
---|
1.获取某个类的Class对象 |
2.得到方法Method对象,进而进行调用:invoke(对象,实参列表) |
/**
* 4.在运行时可以调用任意对象的任意方法
* Method:
* invoke(对象,实参列表)
*
*
* 步骤:
* (1)获取某个类的Class对象
* (2)得到方法Method对象
*
*
*/
public class MyTest5 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
test01();
}
public static void test01() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
//(1)获取某个类的Class对象:四种方式之一
Class<?> aClass = Class.forName("com.test.ext.demo.UserTest");
//(2)得到方法Method对象
//例如:得到setName(String name)方法
/***
* Method aClass.getMethod(name,parameterTypes):得到公共的方法
* Method aClass.getDeclaredMethod(name,parameterTypes):得到声明的方法
*一个类中方法是可能重载的,如何定位到某一个方法?
* 通过方法名+形参列表
*
*/
Method method = aClass.getDeclaredMethod("setName", String.class);
//调用方法
/***
*静态方法:
* 类名.方法([实参列表])
*非静态方法
* 对象名.方法([实参列表])
*/
//创建对象
Object obj = aClass.newInstance();
//调用方法
method.invoke(obj, "陈奕迅");
System.out.println(obj);
//获取public static oid test()方法并进行调用
Method method1 = aClass.getDeclaredMethod("test", int.class);
//调用方法
method1.invoke(null,10);//注意:obj位置上传入null,表示调用静态方法
}
}
运行后的结果为:
3.5 应用5:运行期间读取某个注解信息
首先一个完整的注解,有三个要素:(1)声明(2)使用(3)读取。
- @override,@SuppressWarnings,@Deprecated等这些是JRE声明的,也是由编译器读取的
- @Test,@Before…这些注解是Junit声明和读取的
- @author,@parm…等这些注解也是JRE中声明的,由javadoc.exe读取的
注意:如果我们自定义的注解,那么声明和读取我们就要自己编写了。
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.TYPE;
/***
*5.在运行时读取某个注解信息
*
*获取类上面注解步骤:
* (1)获取Class对象
* (2)获取注解对象
* (3)获取注解的配置参数的值
*
*获取属性上的注解步骤:
* (1)获取Class对象
* (2)获取属性对象Filed对象
* (2)获取注解对象Annotation对象
* (3)获取注解的配置参数的值
*
*/
//声明注解
@Retention(RetentionPolicy.RUNTIME)//注意:只有生命周期是运行时,才可以被反射读取
@Target({TYPE,FIELD})
@interface MyAnnotation{
String value();//如果配置参数只有一个,名称是value,在使用时,可以省略赋值"value="
}
//使用注解
@MyAnnotation("陈奕迅")
class MyClass{
@MyAnnotation("陈奕迅")
private String info;
}
//读取注解
public class MyTest6 {
public static void main(String[] args) throws NoSuchFieldException {
//(1)获取Class对象
Class<MyClass> clazz = MyClass.class;//四种方式之一即可
//(2)获取注解对象
MyAnnotation annotation =clazz.getAnnotation(MyAnnotation.class);
//(3)获取类上注解的配置参数的值
String value = annotation.value();
System.out.println("value="+value);
test01();
}
public static void test01() throws NoSuchFieldException {
//(1)获取Class对象
Class<MyClass> clazz = MyClass.class;//四种方式之一即可
//(2)获取属性对象
Field info = clazz.getDeclaredField("info");
//(3)获取注解对象
Annotation annotation1 = info.getAnnotation(MyAnnotation.class);
//(3)获取属性上注解的配置参数的值
System.out.println(annotation1.toString());
}
}
运行后的结果为:
3.6 应用6:运行期间读取某个类的泛型实参
/***
*6.在运行时读取某个类的泛型实参
* 步骤:
* (1)获取Class对象
* (2)获取泛型父类
* ParameterizedType type = (ParameterizedType)clazz.getGenericSuperclass();
* (3)获取类型实参
*
* Type:代表Java所有类型
* (1)Class:代表的是普通的类型,没有泛型信息的
* (2)ParameterizedType:参数化类型 例如:Father<String,Integer>
* (3)GenericArrayType:泛型数组类型 例如:T[]
* (4)TypeVariable:类型变量 例如:T
* (5)WildcardType:带?通配符的泛型的类型 例如:ArrayList<?>
* ArrayList<? super 下限> ArrayList<? extends 上限>
*/
public class MyTest7 {
@Test
public void test01(){
//获取Son类的泛型父类的类型实参
//(1)获取Class对象
Class<Son> clazz = Son.class;
//(2)获取泛型父类
/* 此种方式是获取普通父类
Class<? super Son> fu = clazz.getSuperclass();
System.out.println(fu);*/
ParameterizedType type = (ParameterizedType)clazz.getGenericSuperclass();
//(3)获取类型参数
Type[] types = type.getActualTypeArguments();
for (Type t : types) {
System.out.println(t);
}
}
@Test
public void test02(){
MyTools myTools = new MyTools();
myTools.test();
}
}
//泛型类型形参:<T,U>
class Father<T,U>{
}
//泛型类型实参:
class Son extends Father<String,Integer>{
}
/**
* 泛型类、接口的类型形参,什么时候才能确定具体的类型
* (1)创建它的对象
* (2)继承泛型类
* (3)实现泛型接口
*
*
*/
class Tools<T>{
private Class type;
//构造方法
public Tools(){
//在创建子类对象时,来确定type的代表类型
//(1)获取正在new的对象的类型的Class对象
Class<? extends Tools> clazz = this.getClass();
//(2)获取泛型父类的信息
ParameterizedType t= (ParameterizedType)clazz.getGenericSuperclass();
//(3)获取类型实参
type= (Class) t.getActualTypeArguments()[0];
}
public void test(){
//这个方法中需要用到T的类型对象,即T的Class对象
System.out.println(type);
}
}
class MyTools extends Tools<String>{
}
运行后的结果为:
总结
本节首先学习了类加载器加载资源文件,需要注意的是当资源文件放置的位置不同,加载资源文件的方式也有所不同。其次学习了利用反射来查看类信息,最后着重学习反射在我们以后学习工作中的应用。