反射
1 为什么叫做反射?
通常写代码时,
非静态:都是先声明类,然后调用类的构造器创建对象。
静态:先声明类,然后根据“类名.”调用方法。
上面这样的话:
表示这个类型是已确定的,清楚的,存在的。否则无法继续。
当这个类本身还不确定,或者未知,暂时还不存在。但是我们要先完成后边的代码,创建对象,然后根据“对象.”调用方法,或者是,调用了静态方法。通常这些未知的类,都是实现类xxx接口,或者继承了xx父类的.
比如说:
(1)工具类,jdbc
这个工具类可以实现查询任意表格的数据。
(2)框架
框架为我们的JavaEE项目服务的,但是这个框架功能是提前写好的,它的内部包含了创建我们JavaEE项目中的某个类的对象,调用我们某个类的方法。
反射:在运行时才确定/获取“类",通过”类“再执行创建对象,调用方法等代码,再运行时获取的”类“是一个Class对象。
Class对象==>反过来查看类的信息,方法,成员.....
类==>类的信息,方法,成员.....
原来直接看类,现在看类的”镜像“(class对象)来获取类的信息,所以称为反射。
2 类的加载
Java中每一种类型(包括Class,interface,Enum等)都需要加载到JVM的方法区中。
每一种类型都对应一个class对象。
(1)过程
①加载(loading):把存储的.class读取到内存中
②连接(link):与内存中现有的其他类型建立联系。如果这个其他类存在,直接关联,否则就先去加载这个其他类。
Ⅰ 验证,例如:验证版本;验证.class是否是一个正确,有效的字节码文件。
JDK1.8编译的.class,在JDK1.7的运行环境中使用,不能反过来;JDK1.7编译的.class,在JDK1.8的运行环境中可以使用。
字节码是有一定规范的,例如:所有字节码开头都是cafebabe
Ⅱ准备
准备class对象的内存以及类中静态变量的内存。此处静态变量的内存中存储的时是默认值。
说明:如果静态的常量(final)会在这个时候直接把常量赋值
Ⅲ解析
代码中出现的所有类名、方法名等符号引用要解析为“地址引用”。
例如:代码中出现的String,这个类型的符号引用,用String类在方法区的Class对象的地址代替。
为什么做这一步?
下次执行时,不用现找,直接去String类中的Class中找String类相关信息。
③初始化
执行类的<clinit>()方法。
2 类初始化过程可能有延迟。
大多数情况下,类加载后直接就初始化了。
但是有个时候,类加载后,会延迟初始化。
(1)当我们通过子类调用父类的静态成员时,只需要完成父类的初始化,子类初始化被延迟了。
(2)当我们使用某个类的静态常量时,可以延迟类的初始化。
(3)但我们使用某个类声明并创建数组时,如果还未真正创建该类的对象时,可以延迟类的初始化
以上三种以外的,都会直接对类进行初始化。
(1)new对象
(2)调用某个类的静态变量
(3)通过反射的API调用该类成员等
(4)main方法所在的类,一定先初始化完成,再执行main方法的。
(5)当子类初始化时,如果父类还未初始化,会先初始化父类。
以上都会直接对类初始化。
4 类加载器:加载类并负责类的初始化
类加载器的类型是java.lang.classloder
(1)引导类加载器(Bootstrap Classloader),又称为跟类加载器,不是Java语言写的,是用C语言写的,所以我们不能获取它的对象。负责加载最最核心的类库,JRE核心类库的rt.jar。
(2)扩展类加载器(Extension ClassLoader),负责加载扩展库。JRE的lib中的ext目录下的扩展库。
(3)应用程序类加载器(Application Classloader),负责加载用户自定义的类型。在项目的classpath下的类。
(4)自定义类加载器。当我们的.class字节码文件需要加密时,可以用自定义的类加载器进行解密的。或者说,当我们需要加载JRE和用户的classpath以外的类时,可以自定义类加载器。
或者说,当我们需要加载JRE和用户的classpath以外的类时,可以自定义类加载器。或者说,类路径和默认的类路径不一样,例如:tomcat,可以自定义类加载器。
5 一个类被加载后,会记录它是被谁加载的。
那么我们如何获取”类“是被哪个类加载器加载的呢,我们可以通过它的Class对象.getClassLoader()方法获取。
6 系统类加载器:引导类加载器、应用程序类加载器的关系是什么?
双亲委托模式,:视为双亲,并不是extends。而是使用组合的方式来表达双亲,在应用程序类加载器中有一个属性叫做parent,然后它的值是扩展类加载器对象。 当我们在外面调用 应用程序类加载器.getParent()时,得到的是扩展类加载器对象
应用程序类加载器 把 扩展类加载器 视为双亲
扩展类加载器 把 引导类加载器 视为双亲
7 为什么要使用双亲委托模式?
目的:安全
避免用户定义的类 覆盖 核心类库中类。 例如:用户定义的java.lang.String覆盖真正的JRE核心类库中java.lang.String
否则就会发生安全问题。即自己定义的类代替系统的核心类库中的类,系统就崩溃了。
8 如何实现双亲委派机制?
当JVM接到一个”类的加载任务时“,AppClassLoader先相应,它会在当前方法区找找看,如果加载过了,就不会重复加载。
如果发现这个类没有加载过,它会将加载类的任务再次提交给”parent“,此时就是引导类加载器,他也会先找一下是不是被加载过了。加载过了就不再重复加载了。
如果引导类加载器发现这个类没有加载过,它就把加载类的任务提交给自己的”parent“,此时就是JRE核心类库的rt.jar,如果自己负责的目录下有,就正常加载,返回Class对象即可。
如果引导类加载器在自己负责的目录下找不到这个类,就会把任务传给ExtClassLoader,ExtClassLoader接下来也在自己负责的目录下尝试加载,此处时JRE的lib中的ext目录下的扩展库,如果自己负责的目录下有,就正常加载,返回Class对象即可。
如果AppClassLoader在自己负责的目录下还找不到这个类,就报异常ClassNotFoundException异常。
如果在前面的加载过程中,加载失败会报NoClassDefFoundError,ClassFormatError等。
9 应用
(1)反射的应用之一:获取类型的详细信息
java.lang.reflect.Modifier类:关于修饰符的描述 在Modifier类中,用一些常量来表示不同的修饰符。 public --> 1 private --> 2 ... 用2的n次方值来表示。因为2的n次方值正好是某一位是1,其余都是0。
public final
0000 0000 0000 0000 0000 0000 0000 0001 public
0000 0000 0000 0000 0000 0000 0001 0000 final
按位或 0000 0000 0000 0000 0000 0000 0001 0001 public final
public class TestClassInfo {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException {
Class<String> stringClass = String.class;
//获取String类的修饰符
int modifiers = stringClass.getModifiers();
System.out.println("String类的修饰符:" + modifiers); //17
System.out.println("String类的修饰符:" + Modifier.toString(modifiers));//public final
//获取String类名字
System.out.println("String类的名字:" + stringClass.getName());
//获取String类包名
System.out.println("String类的包名:" + stringClass.getPackage());
//获取String类父类
System.out.println("String类的父类:" + stringClass.getSuperclass());
//获取String类父接口
Class<?>[] interfaces = stringClass.getInterfaces();
for (Class<?> iter : interfaces) {
System.out.println("String类的父接口:" + iter);
}
//获取String类的成员:成员变量、成员方法、构造器、方法、代码块(代码块无法单独获取)
//获取成员变量
//(1)getField(String name):可以返回指定某个公共成员变量
//(2)getDeclaredField(String name):可以返回指定某个已声明成员变量
//(3)getFields():可以返回所有公共成员变量
//(4)getDeclaredFields():可以返回所有已声明的成员变量
Field value = stringClass.getDeclaredField("value");
System.out.println(value);
/*
Field:字段,它是类的成员变量,也称为属性
所有类的Field都有:修饰符、数据类型、字段名、字段值
都可以被set值,和被get值
类的定义:一类具有相同特性的事物的抽象描述。
*/
Field[] declaredFields = stringClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
System.out.println("它的修饰符:" + Modifier.toString(declaredField.getModifiers()));
System.out.println("它的名称是:" + declaredField.getName());
System.out.println("它的数据类型是:" +declaredField.getType());
declaredField.setAccessible(true);
System.out.println("它的值是:" + declaredField.get("hello"));
}
/*
构造器Constructor
所有类的构造器都有:修饰符、构造器名、形参列表
都可以用来创建对象
Constructor<T> getConstructor(Class<?>... parameterTypes):指定形参列表,我返回对应的公共构造器
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):指定形参列表,我返回对应的已声明构造器
Constructor<?>[] getConstructors() :所有公共构造器
Constructor<?>[] getDeclaredConstructors() :所有声明的构造器
*/
Constructor<String> constructor = stringClass.getConstructor();//获取无参构造
System.out.println(constructor);
//获取所有的构造器
Constructor<?>[] declaredConstructors = stringClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
/*
所有的方法:
都有修饰符、返回值类型、方法名、形参列表、抛出的异常列表
可以被调用
Method[] getMethods() :获取所有的公共的方法
Method[] getDeclaredMethods() :获取所有声明的方法
Method getDeclaredMethod(String name, Class<?>... parameterTypes):获取某个已声明方法,需要通过方法名和形参列表来唯一定位一个方法
Method getMethod(String name, Class<?>... parameterTypes) :获取某个公共的方法
*/
Method[] declaredMethods = stringClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod);
System.out.println("方法的修饰符:" +Modifier.toString(declaredMethod.getModifiers()));
System.out.println("方法的返回值类型:" + declaredMethod.getReturnType());
System.out.println("方法的名称:" + declaredMethod.getName());
System.out.println("方法的形参列表:" + declaredMethod.getParameterTypes());
System.out.println("方法的异常列表:" + declaredMethod.getExceptionTypes());
}
}
}
(2)反射的应用之二:通过反射来创建对象
结论:以后大家写Javabean都尽量保留无参构造。
(1)反射好操作
(2)继承时,子类也好处理,默认调用父类的无参构造
public class TestCreateObject {
@Test
public void test03()throws Exception{
Properties properties = new Properties();
properties.load(new FileInputStream("jdbc.properties"));
String className = properties.getProperty("className");
String paramTypes = properties.getProperty("paramTypes");
System.out.println(paramTypes);//String,String,int
String[] paramTypesArray = paramTypes.split(",");
Class[] paramTypesClassArray = new Class[paramTypesArray.length];
for (int i = 0; i < paramTypesArray.length; i++) {
switch (paramTypesArray[i]){
case "int":
paramTypesClassArray[i] = int.class;
break;
case "double":
paramTypesClassArray[i] = double.class;
break;
default:
paramTypesClassArray[i] = Class.forName(paramTypesArray[i]);
}
System.out.println(paramTypesClassArray[i]);
}
//根据类名称,通过Class.forName可以获取到它的Class对象
Class<?> aClass = Class.forName(className);
//获取有参构造
//Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(paramTypesClassArray);
Object obj = declaredConstructor.newInstance("1001", "张三", 85);//这个值是从数据库中查询的值
System.out.println(obj);
}
@Test
public void test02() throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
//读取jdbc.properties中的 (key,value)获取要创建的对象的类名
//可以使用FileInputStream,FileReader,或者按行读BufferedReader,Scanner
//我们还为Properties文件单独提供了一种方式
Properties properties = new Properties();
properties.load(new FileInputStream("jdbc.properties"));
String className = properties.getProperty("className");
//根据类名称,通过Class.forName可以获取到它的Class对象
Class<?> aClass = Class.forName(className);
//创建com.atguigu.bean.Student的对象
Object obj = aClass.newInstance(); //要求Student必须具有无参构造
System.out.println(obj);
}
@Test
public void test01(){
//原来
// Student stu = new Student();
}
}
(3)反射的应用之三:通过反射为对象的成员变量赋值
后期JDBC:从数据库查询出一些数据,自动通过反射创建对象,并且把查询的数据为成员变量赋值
public class TestField {
public static void main(String[] args) throws Exception {
//通过四种方式之一获取到Class对象
Class clazz = Class.forName("com.atguigu.bean.Student");
//获取它的Field
//得到某一个Field
Field nameField = clazz.getDeclaredField("name");
//如果是非静态的成员变量,那么需要先创建对象
Object stuObj = clazz.newInstance();//调用无参构造创建Student的对象
nameField.setAccessible(true);//如果该成员变量是私有的,跳过权限检查
nameField.set(stuObj, "张三");
System.out.println(stuObj);
}
}
(4)反射的应用之四:在运行时调用任意对象的方法。
public class TestMethod {
@Test
public void test01()throws Exception{
//想办法获取到这个类的Class对象
Class clazz = Class.forName("com.atguigu.reflect.Car");
//明确你要调用哪个方法,要获取这个方法的Method对象
Method methodFun = clazz.getDeclaredMethod("fun");//如果方法没有参数,只要告知方法名即可
//调用方法
//public Object invoke(Object obj, Object... args)
//前面的obj形参,表示需要传入一个调用方法的对象,例如这里就是要Car的对象 ,如果是静态方法,就不用传,写null
//args:代表要给你调用的方法的实参,如果是无参方法,就不用传了
methodFun.invoke(null);
Method methodDrive = clazz.getDeclaredMethod("drive");
//因为drive方法是非静态的,所以需要Car对象
Object obj = clazz.newInstance();
methodDrive.invoke(obj);
Method methodTest = clazz.getDeclaredMethod("test",String.class);//String.class是test方法的形参的类型
methodTest.invoke(obj, "尚硅谷");
}
}
(5)反射的应用之五:获取泛型父类信息
public class TestGeneric {
public static void main(String[] args) {
//获取Er类在继承Fu类时,给<T>指定的具体类是什么
//(1)获取Er类的Class对象
Class<Er> erClass = Er.class;
//(2)获取Er的父类
Class<? super Er> superclass = erClass.getSuperclass();//这个方法不行,只能得到父类,不能得到泛型信息
System.out.println(superclass);
Type genericSuperclass = erClass.getGenericSuperclass();//这个方法才能获取到带泛型的父类
/*
泛型这个概念是JDK1.5有的。
JDK1.5引入泛型后,类发生很大的变化,为了区别有泛型和无泛型的,就增加了更多的类型。
原来只有Class,现在还有GenericArrayType, ParameterizedType, TypeVariable<D>, WildcardType
ParameterizedType:Fu<String>,ArrayList<String>,HashMap<String,Integer>
TypeVariable<D>:<T>,<E>,<K,V>
WildcardType:ArrayList<?>, ArrayList<? extends 上限>,ArrayList<? super 下限>
GenericArrayType:T[]
*/
ParameterizedType p = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = p.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
class Fu<T,U>{
}
class Er extends Fu<String,Integer>{
}
(6)反射的应用之六:获取注解信息
几种注解:
@Override:方法重写
@SuppressWarnings:抑制警告
@Deprecated:已过时的
@Test:JUnit的测试方法
什么是元注解?
描述注解的注解,给注解加的注解。
什么是元数据?
描述数据的数据
注解由三部分组成:
(1)声明
(2)使用
(3)读取
补充:自定义注解
①语法
【修饰符】 @interface 注解名{
}
②如何限定注解可以使用的位置
可以使用元注解@Target来限定
@Target注解需要指定参数:ElementType枚举类型的常量对象
③如何限定注解的声明周期
声明周期:
SOURCE(源代码),CLASS(字节码阶段),RUNTIME(运行时阶段)只有RUNTIME阶段的注解才能被反射读取。
可以使用元注解@Retention来指定声明周期。
@Retention注解需要指定参数:Retention注解需要指定参数:RetentionPolicy枚举类型的常量对象
④如何通过反射读取某个类、方法上面的注解?
public class TestAnnotation {
public static void main(String[] args)throws Exception {
//(1)获取Sub类的Class对象
Class clazz = Sub.class;
//(2)获取method方法对象
Method method = clazz.getDeclaredMethod("method");
//(3)获取方法上面的注解
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
if(annotation instanceof MyAnnotation){
MyAnnotation my = (MyAnnotation) annotation;
System.out.println(my.value());
}
}
}
}
@MyAnnotation(value="尚硅谷")
class Demo{
@MyAnnotation("尚硅谷")
public void method(){
}
}
class Sub extends Demo{
@Override
@MyAnnotation("尚硅谷")
public void method(){
}
}
⑤为什么有的注解需要(参数),有的注解没有呢?
如果注解中声明的参数,在使用注解时,就要传递参数。
这里的参数其实就是注解的抽象方法。
使用注解时,传入的参数相当于是这个抽象方法的实现的返回值。
⑥另外还有两个元注解
@Inherited:表示该注解可以被子类继承;
@Documented:该注解可以被javadoc.exe识别,在API文档中体现。
(7)反射的应用之七:获取内部类和外部类的信息。
public class TestInnerOuter {
public static void main(String[] args) {
Class<Map> mapClass = Map.class;
//得到内部类
Class<?>[] declaredClasses = mapClass.getDeclaredClasses();
for (Class<?> declaredClass : declaredClasses) {
System.out.println(declaredClass);
}
Class<Map.Entry> entryClass = Map.Entry.class;
//得到外部类
Class<?> declaringClass = entryClass.getDeclaringClass();
System.out.println(declaringClass);
}
}
(8)反射的应用之八:动态创建和操作任意类型的数组。
public class TestArray {
public static void main(String[] args) throws Exception{
//通过反射创建数组
//先获取元素类型的Class对象
Properties properties = new Properties();
//因为stu.properties在src的里面 src是Java源代码目录,它编译后有对应class目录
//在src下的文件,最终都在class目录中
//class目录中的文件或类等,可以通过类加载器去加载
//ClassLoader.getSystemClassLoader()获取系统类加载器,
//getResourceAsStream获取资源,通过流来获取
properties.load(ClassLoader.getSystemClassLoader().getResourceAsStream("stu.properties"));
String className = properties.getProperty("className");
Class clazz = Class.forName(className);//clazz代表 com.atguigu.bean.Student类
//创建数组
//java.lang.reflect.Array
Object array = Array.newInstance(clazz, 5);//长度为5的Student数组
Array.set(array,0, clazz.newInstance());
Array.set(array,1, clazz.newInstance());
System.out.println(Array.get(array,0));
System.out.println(Array.get(array,1));
}
}