一、反射的概念
1、什么是反射?为什么要用反射?
咱们之前写代码时,先写类,然后用类创建对象,通过类名或对象名调用方法等成员。
==> 在编译期间就要确定这个类的名字,成员等。
比如:
public class Student{
private int id;
private String name;
public Student(){
}
public Student(int id, String name){
this.id = id;
this.name = name;
}
public void setId(int id){
this.id = id;
}
public int getId(){
return id;
}
//...
}
public class TestStudent{
public static void main(String[] args){
Student stu1 = new Student();
Student stu2 = new Student(1,"张三");
System.out.println(stu1.getId());
}
}
现在我们在开发过程中,会遇到这样的情况,编译期间,这个类型未知 或者 编译期间不存在,
但是我们要先把 new对象,调用方法等代码先完成。需要在运行时,才能确定类型等。
类 --> 类的信息
类的Class对象 --> 类的信息
类的Class对象是类的镜像,把这个过程比喻反射。
2、反射的根源:Class类型的对象
public class TestReflect {
public static void main(String[] args) {
//先写创建对象的代码
Properties pro = new Properties();//Map系列,有key,value,key,value都是String类型
try {
pro.load(TestReflect.class.getClassLoader().getResourceAsStream("info.properties"));
} catch (IOException e) {
e.printStackTrace();
}
try {
Class<?> clazz = Class.forName(pro.getProperty("className"));//"className"是info.properties文件中的某个key
//pro.getProperty("className")得到但是info.properties文件中key为className的value值,即className=右边的值
//new对象
Object obj = clazz.newInstance();
System.out.println(obj);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
3.properties文件到底放哪里?
(1)放在src的根目录下
(2)放到某个包中
(3)src外面,模块的根目录下
public class TestLoaderProperties {
public static void main(String[] args)throws Exception {
Properties pro = new Properties();
ClassLoader classLoader = TestLoaderProperties.class.getClassLoader();
pro.load(classLoader.getResourceAsStream("info.properties"));//src根目录下
System.out.println(pro);
System.out.println("---------------------------");
Properties pro2 = new Properties();
ClassLoader classLoader2 = TestLoaderProperties.class.getClassLoader();
pro2.load(classLoader2.getResourceAsStream("com/atguigu/reflect/pkg.properties"));//com.atguigu.reflect包下
System.out.println(pro2);
System.out.println("----------------------");
Properties pro3 = new Properties();
//如果是main方法中运行,那么就要加模块名,main的相对路径是相对于project
pro3.load(new FileInputStream("day0831_teacher_code/out.properties"));//src外,模块根目录下
System.out.println(pro3);
}
@Test
public void test() throws Exception {
//JUnit的相对路径是相对于模块
Properties pro3 = new Properties();
pro3.load(new FileInputStream("out.properties"));//src外,模块根目录下
System.out.println(pro3);
}
}
二、类加载的过程
3、Class对象怎么来的?
Java程序运行时,会把需要的类的.class文件等类信息先“加载”到内存中,
每一个Java的类型被“加载”后,会用一个Class对象来存储和表示。
4、类的加载,分为三个阶段:(了解)
(1)加载(load):先把.class的文件的数据读取到内存中
(2)连接(link)
A:检验合法性
比如检验字节码文件的格式,包括版本等
字节码文件的必须以cafebabe
B:准备相应的内存
准备new Class对象,
类的静态变量数据存储内存(方法区) 此时是默认值,还未初始化
类的静态的常量,此时会直接初始化。 public static final int MAX_VALUE = 100;
C:解析
把字节码文件中的符号引用,替换为地址引用
例如:Student类
public class Student {
private int id;
private String name;
。。。。
}
比如:String,int都是符号引用,
String和int也是Java的数据类型,在内存中也有他们的Class对象,此时要把String和int换成这两个类型在内存中Class对象的首地址。
为什么要换成地址引用?
这样的话,在运行时,不用现找了,直接根据地址取相应的信息。
经过前两步的话,Class对象有了,但是这个类静态变量还未初始化。
(3)初始化
运行这个类<clinit>方法
经过前3步,Class对象有了,静态变量也初始化了。
5、类的加载是谁来完成的?
类加载由类加载器ClassLoader来完成。底层有专门的线程来负责类的加载。
类加载器又分为四种:
(1)引导类加载器:负责核心类库中的类,rt.jar下类
说明:引导类加载器是C语言写的,所以Java层面得不到它的对象
(2)扩展类加载器(ExtClassLoader):负责JRE下lib目录下ext扩展目录中的jar
(3)应用程序类加载器(AppClassLoader):负责程序员自己写的类
(4)自定义类加载器:也是加载程序员自己写的类等,加载一些特殊的类,特殊目录的类
比如:字节码如果需要加密,那么在运行时,必须用对应的类加载器先解密才能正常加载。
比如:字节码文件不是放在普通类路径下,例如IDEA的类路径是out。
tomcat服务器等的类路径就比较特殊,那么就不能用普通的类加载器加载,就需要自定义类加载器加载,tomcat写了自定义类加载器。
6、如何获取类加载器?
类的Class对象可以调用getClassLoader()方法得到类加载器对象。
7、类加载的协作模式:双亲委托模式,这样的模式设计是为了安全。
双亲:父母双亲
双亲:
应用程序类加载器会把扩展类加载器视为双亲,但是不是继承。在应用程序类加载器中有一个parent(父母)的成员变量,来记录扩展类加载器。
扩展类加载器会把引导类加载器视为双亲,但是不是继承。在扩展类加载器中有一个parent(父母)的成员变量,来记录引导类加载器。
过程描述:
(1)当我们的应用程序类加载器接到加载任务时,先看一下内存中是否已经加载过这个类,如果加载过,就不重复加载了,直接返回Class对象。
如果没有加载过,先把任务交给“双亲”,即扩展类加载器。
例如:应用程序类加载器接到加载“java.lang.String”的任务,如果这个类没加载过,把任务给扩展类加载器
例如:应用程序类加载器接到加载“java.lang.Atguigu”的任务,如果这个类没加载过,把任务给扩展类加载器
(2)扩展类加载器接到加载任务后,也是先看一下内存中是否已经加载过这个类,如果加载过,就不重复加载了,直接返回Class对象。
如果没有加载过,再把任务交给“双亲”,即引导类加载器。
例如:扩展类加载器接到加载“java.lang.String”的任务,如果这个类没加载过,把任务给引导类加载器
例如:扩展类加载器接到加载“java.lang.Atguigu”的任务,如果这个类没加载过,把任务给引导类加载器
(3)引导类加载器接到加载任务后,也是先看一下内存中是否已经加载过这个类,如果加载过,就不重复加载了,直接返回Class对象。
如果没有加载过,尝试在它负责的目录下搜索这个类,
例如:引导类加载器接到加载“java.lang.String”的任务,在它负责的目录下加载,rt.jar中找,如果找到了,就返回Class对象。
例如:引导类加载器接到加载“java.lang.Atguigu”的任务,在它负责的目录下加载,rt.jar中找,如果找到了,就返回Class对象。
如果没找到,把任务往回传,交给扩展类加载器
(4)扩展类加载器再次接到引导类回传的任务,它就会在它负责的目录下搜索这个类, 如果找到了,就返回Class对象。
如果没找到,把任务再回传,交给应用程序类加载器
(5)应用程序类加载器再次接到扩展类加载器 回传的任务,它就会在它负责的目录下搜索这个类, 如果找到了,就返回Class对象。
如果没找到,就报错ClassNotFoundException
总结:
类加载的过程是类加载器来完成,它有四种(通常是3种),工作模式是双亲委托模式,目的是为了安全。
类加载的结果是得到一个类的Class对象。
public class TestClassLoad {
public static void main(String[] args) {
System.out.println(MyClass.MAX_VALUE);
System.out.println(MyClass.a);
System.out.println("-------------------------------");
//String.class得到的是String类的Class对象
ClassLoader classLoader = String.class.getClassLoader();
System.out.println("加载String类类加载器:" + classLoader);//null
//TestClassLoad.class得到的是TestClassLoad类的Class对象
ClassLoader loader = TestClassLoad.class.getClassLoader();
System.out.println("程序员自定义类TestClassLoad类的类加载器对象:" + loader);//sun.misc.Launcher$AppClassLoader@18b4aac2
ClassLoader parent = loader.getParent();
System.out.println("app类加载器的parent:" + parent);
System.out.println("-------------------------------");
try {
//Class.forName("com.atguigu.loader.TestLoader")得到TestLoader的Class对象
Class clazz = Class.forName("com.atguigu.loader.TestLoader");
System.out.println("在JRE/lib/ext目录下的TestLoader类的类加载器:" + clazz.getClassLoader()); //sun.misc.Launcher$ExtClassLoader@45ee12a7
ClassLoader ext = clazz.getClassLoader();
System.out.println("ext扩展类加载器的的parent:"+ ext.getParent());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try {
//Class.forName("java.lang.Atguigu")得到Atguigu的Class对象
Class clazz = Class.forName("java.lang.Atguigu");//ClassNotFoundException:
System.out.println("Atguigu类的Class对象:" + clazz);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class MyClass{
public static final int MAX_VALUE = 100; //类加载的准备阶段直接赋值
public static int a = 100;//类加载的初始化阶段完成赋值
static{
System.out.println("静态代码块");//类初始化执行
}
}
三、反射的根源类Class
1、为什么会有Class类型?
Java种的数据类型,特别是引用数据类型,比如:类,
它们有很多共同特征:
以类为例
都有类名、修饰符,父类、父接口,成员变量、成员方法、构造器....
可以创建对象,调用方法等操作
Java的类是用来对一类具有相同特性的事物的抽象描述。
Class这个类是对Java的数据类型的抽象描述。
Class这个类的对象,就是代表一个具体的Java的数据类型。
java.lang.String类型,在Java的内存中有一个Class对象来表示它,并且这个对象是唯一的。
java.lang.Integer类型,在Java的内存中也有一个Class对象来表示它,并且这个对象也是唯一的。
如果两个变量的类型是相同的,那么它们使用的Class对象是同一个。
2、哪些Java类型有Class对象?
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。
枚举是一种类,注解是一种接口。
每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。
基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。
结论:所有Java数据类型都有Class对象。
3、如何获取Class对象?
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。
获取的方式有四种:(必须掌握)
(1)类型名.class
适用于编译器就已知并存在的类型
(2)对象.getClass()
Object类中有这个方法获取对象的运行时类型
适用于多态引用时获取对象的运行时类型
(3)Class.forName("类型的全名称")
适用于编译期间未知或不存在的类型
(4)类加载器对象.loadClass("类型的全名称")
public class TestClass {
@Test
public void test06(){
//TestClass.class得到TestClass的Class对象
//TestClass.class.getClassLoader()得到加载TestClass的类加载器对象
ClassLoader classLoader = TestClass.class.getClassLoader();
try {
//用这个类加载器对象,去加载别的类
Class<?> c1 = classLoader.loadClass("com.atguigu.bean.Teacher");
Class c2 = Teacher.class;
System.out.println(c1 == c2);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
@Test
public void test05(){
try {
Class<?> c1 = Class.forName("java.lang.String");
Object obj = "hello";//多态引用
Class<?> c2 = obj.getClass();
Class<?> c3 = String.class;
System.out.println(c1 == c2);
System.out.println(c1 == c3);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
@Test
public void test04(){
int[] arr1 = {1,2,3,4,5};
int[] arr2 = new int[10];
Class c1 = arr1.getClass();
Class c2 = arr2.getClass();
System.out.println(c1 == c2);//true 只看元素的类型和维度,不看元素的值和数组的长度
}
@Test
public void test03(){
Object obj = "hello";//多态引用
Class<?> c1 = obj.getClass(); //获取的是obj的运行时类型,是String类型的Class对象
Class stringClass = String.class;
System.out.println(c1 == stringClass);//true
}
@Test
public void test02(){
Class c1 = "hello".getClass();//表示"hello"这个对象的运行时类型String的Class对象
Class stringClass = String.class; //得到的是表示String类型的Class对象
//比较对象的地址,是同一个对象,才会返回true
System.out.println(c1 == stringClass);//true 它们都是java.lang.String,在内存中,只有唯一的一个Class对象来表示String类型
}
//演示所有的Java的数据类型都要自己的Class对象
@Test
public void test01(){
//类
Class stringClass = String.class;
Class integerClass = Integer.class;
//基本数据类型和void
Class intClass = int.class;
Class voidClass = void.class;
System.out.println(stringClass == integerClass) ;//false
System.out.println(intClass == integerClass) ;//false
Class intArrayClass = int[].class;
Class intArray2Class = int[][].class;
System.out.println(intArrayClass == intArray2Class);//false
Class stringArrayClass = String[].class;
System.out.println(intArrayClass == stringArrayClass);//false
System.out.println(stringClass == stringArrayClass);//false
//接口
Class s1 = Serializable.class;
//注解
Class o1 = Override.class;
//枚举
Class m1 = Month.class;
}
}
四、反射的应用
1、获取类的信息
拿到了某个类的Class对象,就能把这个类看透。
public class TestClassInfo {
public static void main(String[] args)throws Exception {
Class<?> clazz = Class.forName("java.lang.String");//这里拿String来演示,可以是任意的其他类型
//通过Class对象获取类的信息
//(1)获取包
Package pkg = clazz.getPackage();
System.out.println("pkg = " + pkg);
//(2)获取类型名称
String name = clazz.getName();
System.out.println("name = " + name); //全名称 = 包 + 类名
//(3)修饰符
int modifiers = clazz.getModifiers();
System.out.println("modifiers = " + modifiers);//17
/*
java.lang.reflect包下Modifier,关于所有JAVA的修饰符的描述类
十六进制 十进制 二进制
public static final int PUBLIC = 0x00000001; 1 1
public static final int PRIVATE = 0x00000002; 2 10
public static final int PROTECTED = 0x00000004; 4 100
public static final int STATIC = 0x00000008; 8 1000
public static final int FINAL = 0x00000010; 16 10000
每一个修饰符是都是2的n次方,它们的二进制的特点是,其中一位是1,其他的都是0
public: 00000000 00000000 00000000 00000001
final : 00000000 00000000 00000000 00010000
|按位或: 00000000 00000000 00000000 00010001 17
17的二进制:00000000 00000000 00000000 00010001
&
public: 00000000 00000000 00000000 00000001
00000000 00000000 00000000 00000001 非0,说明有public
17的二进制:00000000 00000000 00000000 00010001
&
private: 00000000 00000000 00000000 00000010
00000000 00000000 00000000 00000000 0,说明没有private
*/
System.out.println(Modifier.toString(modifiers));//public final
//(4)父类
Class<?> superclass = clazz.getSuperclass();
System.out.println("父类:" + superclass);
//(5)父接口们
System.out.println("父接口们有:");
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println(anInterface);
}
//(6)成员变量们
/*
Field getField(String name):根据属性名获取一个公共的属性
Field getDeclaredField(String name):根据属性名获取一个声明的属性
Field[] getFields() :获取所有公共的属性
Field[] getDeclaredFields() :获取所有声明的属性
*/
System.out.println("获取所有的属性:");
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
//获取其中一个属性
Field valueField = clazz.getDeclaredField("value");
int mod = valueField.getModifiers();
System.out.println("mod = " + mod);//18
System.out.println("value属性的修饰符:" + Modifier.toString(mod));//private final
System.out.println("value的名称:" + valueField.getName());
System.out.println("value的数据类型:" + valueField.getType());//[C [表示一维数组,C表示char类型
//(7)获取所有的构造器
/*
Constructor<T> getConstructor(Class<?>... parameterTypes):获取某一个公共的构造器
Constructor<?>[] getConstructors():所有所有公共的构造器
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) :获取某个声明的构造器
Constructor<?>[] getDeclaredConstructors() :获取某个所有声明的构造器
*/
System.out.println("所有的构造器:");
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
//获取某一个构造器
//Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) :获取某个声明的构造器
//(Class<?>... parameterTypes),可变参数,因为构造器的形参的个数是有0~n的可能
//构造器可以重载,靠构造器的形参列表区分,所以要获取某个构造器,需要指定件构造器形参列表的类型
//public java.lang.String(byte[])
Constructor c = clazz.getDeclaredConstructor(byte[].class);//得到形参为byte[]类型String类的构造器
System.out.println("形参是byte[]类型的构造器:" + c);
//(8)所有的方法
/*
Method getDeclaredMethod(String name, Class<?>... parameterTypes):获取某个声明的方法,通过指定方法的名称和方法的形参列表来确定一个方法
Method[] getDeclaredMethods():获取所有已声明的方法
Method getMethod(String name, Class<?>... parameterTypes):获取某个公共的方法,通过指定方法的名称和方法的形参列表来确定一个方法
Method[] getMethods():获取所有已公共的方法
*/
System.out.println("所有的方法们:");
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod);
}
}
}
2、在运行时创建任意类型的对象
任意类型:不包括基本数据类型、void、抽象类、接口等
任意类型:是指能创建对象的类型。
简单的描述一下:
后面大家会写JavaEE的项目。
(1)后面的写的服务器端的代码,是运行在web服务器(例如:tomcat)中,
服务器端会有很多的类,其中一些类是由web服务器帮我们创建对象的。例如:servlet类(代表jsp页面)
(2)后面的写的项目会用到spring,mybatis等框架,我们类也会交给spring框架帮我们管理,
我们的类对象由spring框架帮我们创建。
web服务器和spring,mybatis等框架代码已经写好了,我们的类是后来写的。
在web服务器和spring,mybatis等框架中提前写好了new对象的代码,具体new什么类的对象,是后来我们通过
xx.xml文件告诉服务器或框架,然后他们读取这个配置文件,就可以获取到“类名”,就可以获取到对应的Class对象,
然后new对象。
方式一:直接使用Class类的newInstance()方法创建对象
Class类中有一个使用频率比较高的方法:
T newInstance() :创建此 Class 对象所表示的类的一个新实例。
如果此Class对象代表的是String类型,那么 newInstance()创建的就是String的对象
如果此Class对象代表的是Student类型,那么 newInstance()创建的就是Student的对象
如果此Class对象代表的是Date类型,那么 newInstance()创建的就是Date的对象
要通过这种方式创建实例对象,要求这个类型必须有 公共的无参构造,否则会报IllegalAccessException, InstantiationException异常。
报IllegalAccessException:构造器的权限修饰符有问题
InstantiationException异常:没有无参构造
方式二:先获取这个类的构造器对象,然后通过构造器对象的newInstance方法创建对象
这种方式,构造器可以是无参,可以是有参,可以是任意一种权限修饰的情况都可以。
public class TestNewInstance {
@Test
public void test3() throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
/*
假设框架的配置文件叫做: info.properties
里面有key=value等各种配置信息,其中一个key是className,它的value是你要创建对象的类型名称
例如:className=java.util.Date
*/
//(1)第一步,先读取配置文件
//如果info.properties文件在src的根目录下
//src是源代码文件,它里面的所有的东西都会被编译器编译到类路径下(out目录下) 即src下的配置文件会和.class文件在一起
//.class文件是可以通过“应用程序类加载器”加载的,那么src下的配置文件也可以通过“应用程序类加载器”加载
Properties properties = new Properties();
/*
TestNewInstance类是程序员自己写的类,所以由“应用程序类加载器”加载
TestNewInstance.class得到它的Class对象
TestNewInstance.class.getClassLoader()得到类加载器对象,即“应用程序类加载器”对象
*/
ClassLoader classLoader = TestNewInstance.class.getClassLoader();//获取应用程序类加载器对象
properties.load(classLoader.getResourceAsStream("info.properties"));
//经过上面的代码,"info.properties"中的数据就被存到properties的map中,以(key,value)形式存储
//(2)第二步:根据key获取value,来得到要加载的类的名称
String className = properties.getProperty("className");
//(3)第三步:在内存中获取它的Class对象
Class clazz = Class.forName(className);//如果这个类没在内存中,应用程序类加载器会尝试加载,如果找不到会报ClassNotFoundException
//(4)获取某个构造器对象
//例如:我要得到public Teacher(String name,double salary)构造器
Constructor c = clazz.getDeclaredConstructor(String.class,double.class);//String.class代表形参的类型是String类型
//(5)通过构造器的对象调用
//java.lang.reflect.Constructor类T newInstance(Object... initargs)
//newInstance(Object... initargs)的参数最终就是传给你调用的构造器的实参
//c.newInstance("张三",15000); 这里的“张三”相当于你 new Teacher("张三",15000)
//如果被调用的构造器是因为权限修饰符的可见性问题,无法创建对象,可以这么做
c.setAccessible(true);//跳过了权限修饰符的检查
Object obj = c.newInstance("张三",15000);
System.out.println(obj);
}
@Test
public void test2() throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
/*
假设框架的配置文件叫做: info.properties
里面有key=value等各种配置信息,其中一个key是className,它的value是你要创建对象的类型名称
例如:className=java.util.Date
*/
//(1)第一步,先读取配置文件
//如果info.properties文件在src的根目录下
//src是源代码文件,它里面的所有的东西都会被编译器编译到类路径下(out目录下) 即src下的配置文件会和.class文件在一起
//.class文件是可以通过“应用程序类加载器”加载的,那么src下的配置文件也可以通过“应用程序类加载器”加载
Properties properties = new Properties();
/*
TestNewInstance类是程序员自己写的类,所以由“应用程序类加载器”加载
TestNewInstance.class得到它的Class对象
TestNewInstance.class.getClassLoader()得到类加载器对象,即“应用程序类加载器”对象
*/
ClassLoader classLoader = TestNewInstance.class.getClassLoader();//获取应用程序类加载器对象
properties.load(classLoader.getResourceAsStream("info.properties"));
//经过上面的代码,"info.properties"中的数据就被存到properties的map中,以(key,value)形式存储
//(2)第二步:根据key获取value,来得到要加载的类的名称
String className = properties.getProperty("className");
//(3)第三步:在内存中获取它的Class对象
Class clazz = Class.forName(className);//如果这个类没在内存中,应用程序类加载器会尝试加载,如果找不到会报ClassNotFoundException
//(4)获取某个构造器对象
//例如:我要得到Teacher类的public Teacher(String name)构造器
Constructor c = clazz.getDeclaredConstructor(String.class);//String.class代表形参的类型是String类型
//(5)通过构造器的对象调用
//java.lang.reflect.Constructor类T newInstance(Object... initargs)
//newInstance(Object... initargs)的参数最终就是传给你调用的构造器的实参
//c.newInstance("张三"); 这里的“张三”相当于你 new Teacher("张三")
Object obj = c.newInstance("张三");
System.out.println(obj);
}
@Test
public void test() throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
/*
假设框架的配置文件叫做: info.properties
里面有key=value等各种配置信息,其中一个key是className,它的value是你要创建对象的类型名称
例如:className=java.util.Date
*/
//(1)第一步,先读取配置文件
//如果info.properties文件在src的根目录下
//src是源代码文件,它里面的所有的东西都会被编译器编译到类路径下(out目录下) 即src下的配置文件会和.class文件在一起
//.class文件是可以通过“应用程序类加载器”加载的,那么src下的配置文件也可以通过“应用程序类加载器”加载
Properties properties = new Properties();
/*
TestNewInstance类是程序员自己写的类,所以由“应用程序类加载器”加载
TestNewInstance.class得到它的Class对象
TestNewInstance.class.getClassLoader()得到类加载器对象,即“应用程序类加载器”对象
*/
ClassLoader classLoader = TestNewInstance.class.getClassLoader();//获取应用程序类加载器对象
properties.load(classLoader.getResourceAsStream("info.properties"));
//经过上面的代码,"info.properties"中的数据就被存到properties的map中,以(key,value)形式存储
//(2)第二步:根据key获取value,来得到要加载的类的名称
String className = properties.getProperty("className");
//(3)第三步:在内存中获取它的Class对象
Class clazz = Class.forName(className);//如果这个类没在内存中,应用程序类加载器会尝试加载,如果找不到会报ClassNotFoundException
//(4)第四步:创建对象
// 通过这个类的Class对象,调用newInstance()方法创建对象
Object obj = clazz.newInstance();
System.out.println(obj);
}
}
3、在运行时可以访问/操作任意对象的属性
步骤:
(1)先获取到Class对象
(2)要得到你要操作/访问的属性的Field对象
(3)如果你要访问的是静态变量,那么调用Field对象的set/get方法就可以访问该静态变量的值
如果你要访问的是非静态变量,那么还需要创建这个类的对象,然后再Field对象的set/get方法就可以访问该非静态变量的值
public class TestAccessField {
@Test
public void test03() throws NoSuchFieldException, IllegalAccessException {
String str = "hello";
//修改str的字符串内容,假设把e修改为a
Class<? extends String> stringClass = str.getClass();//得到String类的Class对象
Field valueField = stringClass.getDeclaredField("value");//得到String类中的private final char value[];
//因为value属性在String类中是私有的,需要跳过权限修饰符检查
valueField.setAccessible(true);
Object o = valueField.get(str);//得到str这个字符串的value数组
char[] arr = (char[]) o;//强制转换为char[]类型
arr[1] = 'a';
System.out.println(str);//hallo
}
@Test
public void test02() throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException {
//(1)第一步,先读取配置文件
Properties properties = new Properties();
ClassLoader classLoader = TestNewInstance.class.getClassLoader();//获取应用程序类加载器对象
properties.load(classLoader.getResourceAsStream("info.properties"));
//(2)第二步:根据key获取value,来得到要加载的类的名称
String className = properties.getProperty("className");
//(3)第三步:在内存中获取它的Class对象
Class clazz = Class.forName(className);//如果这个类没在内存中,应用程序类加载器会尝试加载,如果找不到会报ClassNotFoundException
//(4)第四步:获取要操作的属性的Field对象
Field companyField = clazz.getDeclaredField("company");
//因为company属性是静态的,所以不需要创建Teacher的对象
//(5)调用Field对象的set和get方法操作company属性
//如果属性是私有的,可以通过下面的操作,避免权限修饰符检查
companyField.setAccessible(true);
companyField.set(null,"尚硅谷");
Object value = companyField.get(null);
System.out.println("value = " + value);
}
@Test
public void test01() throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException {
//(1)第一步,先读取配置文件
Properties properties = new Properties();
ClassLoader classLoader = TestNewInstance.class.getClassLoader();//获取应用程序类加载器对象
properties.load(classLoader.getResourceAsStream("info.properties"));
//(2)第二步:根据key获取value,来得到要加载的类的名称
String className = properties.getProperty("className");
//(3)第三步:在内存中获取它的Class对象
Class clazz = Class.forName(className);//如果这个类没在内存中,应用程序类加载器会尝试加载,如果找不到会报ClassNotFoundException
//(4)第四步:获取要操作的属性的Field对象
Field nameField = clazz.getDeclaredField("name");
//(5)因为name属性是非静态的,所以要创建Teacher的对象才能操作它
//这里Teacher有 公共的无参构造,
Object teacher = clazz.newInstance();
//(6)调用Field对象的set和get方法操作name属性
//如果属性是私有的,可以通过下面的操作,避免权限修饰符检查
nameField.setAccessible(true);
nameField.set(teacher,"李四");
Object value = nameField.get(teacher);
System.out.println("value = " + value);
System.out.println(teacher);
}
}
4、在运行时调用任意方法
演示通过反射调用com.atguigu.test.Father类中的各个方法
public class TestInvokeMethod {
@Test
public void test4() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
//(1)第一步,通过我们讲的四种方式之一,获取到Father类的Class对象
Class clazz = Class.forName("com.atguigu.test.Father");
//(2)第二步:获取到你要调用的方法的Method对象
// public int sum(int a, int b)是一个非静态方法
Method sumMethod = clazz.getDeclaredMethod("sum",int.class,int.class);
//(3)第三步,如果是非静态方法,那么需要先创建Father类的对象
Object obj = clazz.newInstance();//创建Father类的对象,因为clazz是代表Father类,要求Father类有公共的无参构造
Object value = sumMethod.invoke(obj, 1, 5);//这里obj的意思是通过Father对象obj调用fun方法,等价于 ((Father)obj).fun();
//如果sum方法有返回值,通过invoke调用,返回值就传回来了
System.out.println("value = " + value);
}
@Test
public void test3() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
//(1)第一步,通过我们讲的四种方式之一,获取到Father类的Class对象
Class clazz = Class.forName("com.atguigu.test.Father");
//(2)第二步:获取到你要调用的方法的Method对象
// public void fun()是一个非静态方法
Method funMethod = clazz.getDeclaredMethod("fun");
//(3)第三步,如果是非静态方法,那么需要先创建Father类的对象
Object obj = clazz.newInstance();//创建Father类的对象,因为clazz是代表Father类,要求Father类有公共的无参构造
funMethod.invoke(obj);//这里obj的意思是通过Father对象obj调用fun方法,等价于 ((Father)obj).fun();
}
@Test
public void test2() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//(1)第一步,通过我们讲的四种方式之一,获取到Father类的Class对象
Class clazz = Class.forName("com.atguigu.test.Father");
//(2)第二步:获取到你要调用的方法的Method对象
// public static void test(int a)是一个静态方法
Method testMethod = clazz.getDeclaredMethod("test",int.class); //int.class表示要获取有一个int类型的形参的test方法
//(3)第三步,如果是静态方法,那么可以直接调用
testMethod.invoke(null, 100);//这里null的意思是不需要Father类的对象
//这里的100是给test方法的形参赋值的100
}
@Test
public void test1() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//(1)第一步,通过我们讲的四种方式之一,获取到Father类的Class对象
Class clazz = Class.forName("com.atguigu.test.Father");
//(2)第二步:获取到你要调用的方法的Method对象
// public static void test()是一个静态方法
Method testMethod = clazz.getDeclaredMethod("test");
//(3)第三步,如果是静态方法,那么可以直接调用
testMethod.invoke(null);//这里null的意思是不需要Father类的对象
}
}
5、获取泛型父类的信息
演示获取子类Sub的泛型父类Father的泛型信息,子类Sub在继承父类Father时,指定了泛型信息Father<String,Integer>
public class TestGenericFather {
public static void main(String[] args)throws Exception {
//(1)第一步,获取Sub子类的Class对象
Class clazz = Class.forName("com.atguigu.reflect.kuo.Sub");
//(2)第二步:获取泛型父类的Type对象
Class superclass = clazz.getSuperclass();
System.out.println(superclass);//这种方式得不到泛型信息
Type genericSuperclass = clazz.getGenericSuperclass();
/*
JDK1.5引入了泛型,原来所有的类型都用Class对象表示,但是因为有了泛型之后,
有些类型就不能用Class对象表示,例如:ArrayList<String>,ArrayList<?>等,
JDK就新增了Type接口等类型,来表示各种具有泛型的类型。
Type 是 Java 编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。
Type的实现类有Class,还有一些子接口:GenericArrayType, ParameterizedType, TypeVariable<D>, WildcardType
GenericArrayType:代表T[]泛型数组
ParameterizedType:代表ArrayList<String>参数化类型
TypeVariable<D>:代表T等泛型类型
WildcardType:代表带通配符的泛型类型 ArrayList<?>,Arraylist<? extends 上限>,ArrayList<? super 下限>
Father<String,Integer>属于ParameterizedType参数化类型
*/
ParameterizedType pt = (ParameterizedType) genericSuperclass;//向下转型,目的是调用子接口的getActualTypeArguments方法
Type[] actualTypeArguments = pt.getActualTypeArguments();//获取泛型的具体类型参数,也就是<>中的具体类型
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
System.out.println("-------------------------");
Type[] genericInterfaces = clazz.getGenericInterfaces();
for (Type genericInterface : genericInterfaces) {
if(genericInterface instanceof ParameterizedType){
ParameterizedType p = (ParameterizedType) genericInterface;
Type[] arr = p.getActualTypeArguments();
System.out.println(Arrays.toString(arr));
}
}
}
}
class Father<T,U>{
}
class Sub extends Father<ArrayList<String>,Integer> implements Comparable<Sub>, Serializable {
@Override
public int compareTo(Sub o) {
return 0;
}
}
6、获取内部类和外部类的信息
演示获取Outer类的内部类信息
Class类:
Class<?>[] getClasses():获取所有公共的内部类和内部接口
Class<?>[] getDeclaredClasses() :获取所有声明的内部类和内部接口
Class<?> getEnclosingClass() :获取外部类或外部接口的信息
public class TestInnerOuter {
public static void main(String[] args) {
//第一步:得到Outer类的Class对象
Class clazz = Outer.class;
//第二步:获取它的内部类信息
Class[] arr = clazz.getDeclaredClasses();
for (Class c : arr) {
System.out.println(c);
}
System.out.println("-----------------------");
//获取HashMap的所有内部类
Class hashMapClass = HashMap.class;
//获取HashMap的内部类
Class[] declaredClasses = hashMapClass.getDeclaredClasses();
for (Class declaredClass : declaredClasses) {
System.out.println(declaredClass);
}
System.out.println("------------------------");
//演示获取Inner类的外部类信息
//第一步:获取Inner的Class对象
Class innerClass = Outer.Inner.class;
//第二步:得到外部类新
Class enclosingClass = innerClass.getEnclosingClass();
System.out.println(enclosingClass);
System.out.println("------------------------");
List<Integer> list = Arrays.asList(1, 2, 3, 4);
Class<? extends List> listClass = list.getClass();
Class<?> c = listClass.getEnclosingClass();
System.out.println("listClass=" + listClass);
System.out.println("listClass的外部类=" + c);
}
}
class Outer{
class Inner{
}
static class NeiClass{
}
}
7、反射操作数组
在java.lang.reflect包 下Array 类提供了动态创建和访问 Java 数组的方法。
static Object newInstance(Class<?> componentType, int length)
Class<?> componentType:数组元素的类型
int length:数组的长度
public class TestArray {
public static void main(String[] args) {
MyArrayList<String> list = new MyArrayList<>(String.class,5);
list.add("hello");
list.add("world");
list.add("java");
list.add("mysql");
list.add("atguigu");
list.add("chai");
String str = list.get(1);
System.out.println(str);
}
@Test
public void test(){
Object arr = Array.newInstance(int.class, 5);
Array.set(arr,0,10);
Array.set(arr,1,20);
System.out.println(Array.get(arr,0));
System.out.println(Array.get(arr,1));
System.out.println(Array.get(arr,2));
}
}
import java.lang.reflect.Array;
import java.util.Arrays;
public class MyArrayList<T> {
private T[] arr;
private int total;
public MyArrayList(Class tClassType,int length){
arr = (T[]) Array.newInstance(tClassType, 5);
}
public void add(T element){
if(total>=arr.length){
arr = Arrays.copyOf(arr, arr.length*2);
}
arr[total++] = element;
}
public T get(int index){
return arr[index];
}
}
8、反射与自定义注解的配合使用
(1)自定义注解
语法格式:
【修饰符】 @interface 注解名{
}
语法格式:
【修饰符】 @interface 注解名{
抽象方法;
}
语法格式:
【修饰符】 @interface 注解名{
返回值类型 方法名() default 默认返回值;
}
说明:返回值类型有要求,8种基本数据类型,String类型,Class类型,注解类型,或者它们的数组,其他类型不可以
(2)使用自定义注解
如果使用的注解没有抽象方法,或者抽象方法有默认返回值,那么在使用注解时,就不用为抽象方法指定返回值
否则,如果使用的注解有抽象方法,并且抽象方法没有指定默认返回值,就必须在注解名后面的()中为没有抽象方法指定返回值。
说明:如果抽象方法名是value,在使用注解时,指定方法的返回值时,可以省略value=。
但是如果方法名是其他的单词,或者抽象方法是多个,就不能省略 方法名=
(3)看一下系统的注解的声明
@Override:java.lang包
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
//没有抽象方法
}
@SuppressWarnings
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value(); //抽象方法
}
(4)元注解
加在注解上面的注解,称为元注解。
一共有四个元注解:
@Target:指明该注解使用的 位置
它的位置由ElementType枚举类的10个常量对象来指定
@Retention:指明该注解的生命周期
它的生命周期由RetentionPolicy枚举类的3个常量对象来指定
SOURCE(源代码)
CLASS(字节码)
RUNTIME(运行时) 只有活到运行时的注解才能被反射读取。
@Documented:注解是否可以被javadoc.exe识别读取到API文档中
@Inherited:是否可以被子类继承
public class TestAnnotation {
@SuppressWarnings(value="unused")
public static void main(String[] args) {
int a = 1;
//尝试获取MyClass类上面的注解
//第一步:先得到MyClass类的Class对象
Class clazz = MyClass.class;
//第二步:获取类上的注解
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
if(annotation instanceof MyAnnotation){
MyAnnotation my = (MyAnnotation) annotation;//向下转型,为了调用MyAnnotation中声明的抽象方法
System.out.println(my.info());
System.out.println(my.value());
}
}
System.out.println("------------------------");
//演示MySub子类继承了父类的MyAnnotation注解
Class<MySub> mySubClass = MySub.class;
System.out.println(mySubClass.getAnnotation(MyAnnotation.class));
}
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited //可以被子类继承
@interface MyAnnotation{
String value();
String info() default "atguigu";
}
@interface OtherAnnotation{
}
@OtherAnnotation
@MyAnnotation(value="尚硅谷",info="北京尚硅谷")
class MyClass{
}
class MySub extends MyClass{
}
class Base{
public void method(){
System.out.println("父类的方法");
}
}
class Son extends Base{
@Override //因为Override这个注解没有抽象方法,所以直接使用@Override ,不需要加(),指定抽象方法的返回值
public void method() {
super.method();
}
}