day26【反射

    技术2023-08-16  88

    零.类的加载时机

    一.类加载器

    1.概述: 在jvm中,负责将本地上的class文件加载到内存的对象 2.分类: - BootstrapClassLoader 根类加载器-->C语言写的,我们获取不到 ​ 也被称为引导类加载器,负责Java核心类的加载 ​ 比如System,String等。 jre/lib/rt.jar下的类都是核心类 --------------------------------- - ExtClassLoader 扩展类加载器 ​ 负责JRE的扩展目录中jar包的加载。 ​ 在JDK中JRE的lib目录下ext目录 - AppClassLoader 系统类加载器 ​ 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包(第三方jar包)和类路径。或者自定义的类 ---------------------------------------------------- AppClassLoader的父加载器是ExtClassLoader ExtClassLoader的父加载器是BootstrapClassLoader 但是他们不是子父类继承关系,他们有一个共同的爹-->ClassLoader ---------------------------------------------------- 3.获取加载器对象 类名.class.getClassLoader 4.双亲委派(全盘负责委托机制) 谁用谁加载 a.Person中有一个String Person本身是AppClassLoader加载 String是BootstrapClassLoader b.加载顺序: Person本身是AppClassLoader加载,String本来按理来说是AppClassLoader 但是AppClassLoader加载String的时候,会去问一问ExtClassLoader,,ext,你加载吗? ext说:偶no,我不负责加载核心类,我负责的是扩展类,所以你别急,我给你找人(BootstrapClassLoader) ext说:boot,你加载String吗? boot:,正好我加载核心类,行吧,我加载吧 ----------------------------------------------------------- 假如:ext和boot都不加载,app才会自己加载 class Test{ new Person() } a.AppClassLoader负责加载Test,然后按道理来讲Person也是由AppClassLoader加载到内存 但是App先不加载,先去找ExtClassLoader,Ext不负责加载自定义类,Ext就去找Boot 但是Boot只负责加载核心类,所以Boot也不加载 b.最后App看两个双亲不加载,自己就加载了Person ----------------------------------------------------------- c.类加载器的cache(缓存)机制:如果cache中保存了这个类就直接返回它,如果没有才加载这个类,然后存入cache中,下一次如果有其他类在使用的时候就不会在加载了,直接去cache缓存拿即可。这就是为什么每个类只加载一次,内存只有一份的原因。 举例:还是上述代码中,当第一次使用System类的时候,那么System类就会被加载了,那么System类就会存储到内存中了,当下面代码中我们再一次使用System类的时候,由于内存中已经有了,那么就不会在去加载了,这时会直接拿过来用即可。 因此方法区中每一个类的字节码文件只有一份的原因由全盘负责、委托机制和类加载器的cache(缓存)机制共同决定。也称为双亲委派机制 5.双亲委派作用:(一个类在内存中加载几次呢?->1) 能够让Class类加载一次 创建一个Class对象 /* 类名.class.getClassLorder()这个类的加载器 ClassLorder类的方法 getParent()返回父加载器 */ public class ClassLoader { @Test public void classLorder(){ //三个类加载器的关系 //父子关系 ClassLoader c1 = ClassLoader.class.getClassLoader(); System.out.println(c1); ClassLoader c2 = c1.getParent(); System.out.println(c2); ClassLoader c3 = c2.getParent(); System.out.println(c3); } //获取AppClassLoader 系统类加载器 @Test public void app(){ //ClassLoader为本类类名 ClassLoader c1 = ClassLoader.class.getClassLoader(); System.out.println(c1); } //获取ExtClassLoader 扩展类加载器 @Test public void ext(){ ClassLoader c1 = DNSNameService.class.getClassLoader(); System.out.println(c1); } //BootstrapClassLoader 根类加载器 @Test public void boot(){ //引导类加载器,不是类,JVM内部,返回值null ClassLoader cl = String.class.getClassLoader(); System.out.println(cl); } }

    二.反射

    1.概述:根据Class对象操作Class对象中的成员

    三.反射之获取Class对象

    获取Class对象的方式: 1.new对象 调用 getClass()->Object中的方法 2.类名.class->class->每个数据类型,不管是基本的还是引用的,jvm都赋予了他们一个静态属性 名字就叫做class 3.Class中方法->forName(String className) 注意: class就加载一次,Class对象也就一个,3中方式获取出来的Class是同一个 ------------------------------------------------------------ 问题: 3种获取Class对象的方式,哪个在开发中最常用:forName 使用forName(Stirng className)->扩展性更好,灵活性更高 因为:参数是一个字符串,将来我们可以将类的全限定名放在配置文件中,然后 用io流读取,读取出来的字符串(全限定名)可以当做参数放在forName中 这样,我们想获取不同的Class对象,直接改文件名就可以了,不用修改 可以代码实现一下: a.在模块下创建prop.properties className=cn.itcast.day21.e_fanshe04.Student b.创建测试类: public class Demo02_ForName { public static void main(String[] args) throws Exception { //创建Properties集合 Properties properties = new Properties(); FileInputStream fis = new FileInputStream("day21\\pro.properties"); properties.load(fis); //根据className获取对应的value String className = properties.getProperty("className"); //获取value的class对象 Class person = Class.forName(className); System.out.println(person); } } public class Person { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } private Person(String name){ this.name = name; System.out.println("我是私有的构造"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return name+"..."+age; } } //测试类 public class Test01 { public static void main(String[] args) throws ClassNotFoundException { //每个数据类型,不管基本还是引用数据类型,jvm都赋予了这个类型一个静态属性,名字叫class Class aClass = Person.class; Class aClass1 = Class.forName("cn.itcast.day15.class02.Person"); //Object类中的getClass方法 Person person = new Person(); Class aClass2 = person.getClass(); System.out.println(aClass==aClass1); System.out.println(aClass==aClass2); } }

    四.获取Class对象中的构造方法

    一.获取所有public的构造方法

    反射通用的使用方式: 1.获取要操作类的class对象 2.利用Class类中的方法获取类中的成员(构造,变量,方法) 3.运行获取出来的成员 获取Class对象中的构造方法: Constructor<?>[] getConstructors()->获取所有的构造方法(public修饰的) public class Demo01_Constructor { public static void main(String[] args) throws Exception{ //获取Person类的Class对象 Class pClass = Class.forName("cn.itcast.day20.fanshe02.Person"); Constructor[] constructors = pClass.getConstructors(); //遍历数组 for (Constructor constructor : constructors) { System.out.println(constructor); } } }

    二.获取空参构造

    1.获取指定的构造方法: Constructor<T> getConstructor(Class<?>... parameterTypes):获取指定public的构造 parameterTypes:需要传递参数类型的class对象 如果获取的是无参构造,参数不写 2.Constructor类中的方法 T newInstance(Object...initargs)-->创建对象->new Person("柳岩",1) 如果使用此方法创建的是无参构造,参数不用写 此方法相当于:new Person() 或者 new Person("柳岩",1) private static void method01() throws Exception { //获取Person类的Class对象 Class pClass = Class.forName("cn.itcast.day20.fanshe02.Person"); //获取无参构造 Constructor constructor = pClass.getConstructor(); Object o = constructor.newInstance();//Person p = new Person() System.out.println(o); }

    三.利用空参构造创建对象的快捷方式

    1.利用空参构造创建对象的快捷方式 直接调用Class类中的newInstance方法 前提:被反射的类,必须具有public权限的无参构造 private static void method02()throws Exception { //获取Person类的Class对象 Class pClass = Class.forName("cn.itcast.day20.fanshe02.Person"); Object o = pClass.newInstance(); System.out.println(o); }

    四.利用反射获取有参构造并创建对象

    1.获取指定的构造方法: Constructor<T> getConstructor(Class<?>... parameterTypes) parameterTypes:需要传递参数类型的class对象 如果获取的是无参构造,参数不写 2.Constructor类中的方法 T newInstance(Object...initargs)-->创建对象->new Person("柳岩",1) 如果使用此方法创建的是无参构造,参数不用写 public class Demo03_Constructor { public static void main(String[] args) throws Exception{ //获取Person类的Class对象 Class pClass = Class.forName("cn.itcast.day20.fanshe02.Person"); //获取有参构造 Constructor constructor = pClass.getConstructor(String.class, int.class); System.out.println(constructor); //创建对象 //Person p = new Person("柳岩", 36) Object o = constructor.newInstance("柳岩", 36); System.out.println(o); } }

    五.利用反射获取私有构造(暴力反射)

    获取私有的构造(扩展): Constructor<?>[] getDeclaredConstructors()->获取所有的构造,包括私有的 Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) ->获取指定的构造 AccessibleObject类中的方法-->暴力反射 void setAccessible(boolean flag) flag:false->代表不能访问私有的成员 flag:true->解除私有权限 public class Demo04_Constructor { public static void main(String[] args) throws Exception{ //获取Person类的Class对象 Class pClass = Class.forName("cn.itcast.day20.fanshe02.Person"); //method01(pClass); method02(pClass); } private static void method02(Class pClass)throws Exception { //获取私有的构造方法 Constructor dds = pClass.getDeclaredConstructor(String.class); //解除私有权限 dds.setAccessible(true); //创建对象 Object o = dds.newInstance("郭磊"); System.out.println(o); } /* 获取所有的构造->包括私有 */ public static void method01(Class pClass){ Constructor[] dds = pClass.getDeclaredConstructors(); //遍历数组 for (Constructor dd : dds) { System.out.println(dd); } } }

    六.利用反射获取所有成员方法

    利用反射获取类中的方法: Method[] getMethods()获取所有的方法,public public class Demo05_Method { public static void main(String[] args)throws Exception { Class c = ClassUtils.getC();//抽出来的工具类可以不抽取 Method[] methods = c.getMethods(); //遍历数组 for (Method method : methods) { System.out.println(method); } } }

    七.反射之获取方法(有参,无参)

    利用反射获取类中的方法: Method[] getMethods()获取所有的方法,public Method getMethod(String name,Class<?>...parameterTypes) name:获取的方法名 parameterTypes:该方法的参数类型 Method中有一个方法 Object invoke(Object obj, Object... args) obj:反射的类的对象 args:运行方法传递的实参 如果执行的方法是void,调用invoke方法没必要必须用返回值接收 如果执行的方法没有参数,那么args不用写 public class Demo06_Method { public static void main(String[] args)throws Exception { Class c = ClassUtils.getC(); System.out.println("---------获取setName方法-------------"); //获取set方法 Method setName = c.getMethod("setName", String.class); //利用空参构造创建Person对象 Object o = c.newInstance();//Person o = new Person() //调用方法 setName.invoke(o, "柳岩"); System.out.println(o); System.out.println("---------获取getName方法-------------"); Method getName = c.getMethod("getName"); //执行getName方法,有返回值的 Object invoke = getName.invoke(o); System.out.println(invoke); } }

    五.反射练习(编写一个小框架)

    利用反射,解析配置文件中的信息 文件中配置的信息是 类的全限定名 className=cn.itcast.day20.fanshe03_test.Person 类中的某一个方法名 methodName=eat 步骤: 1.创建配置文件->properties 存的信息键值对的形式 问题1:配置文件放在哪里?放在src下->切记 问题2:项目开发完,交给用户使用,给用户的是编译后的class文件,而out目录存放的就是class文件 问题3:如果我们将配置文件放在模块下,out目录下是没有配置文件的,那么代码运行需要读配置文件的信息 所以给了用户,用户一执行,,报错了,因为没读到配置文件的信息 解 决:如果将配置文件放在src下面,idea生成的out目录中这个配置文件会显示在out的项目路径下 注意的是:src 存放的是源代码 编译后产生的class文件,是同步的,但是生成的out路径,下没有src这个目录,因为src存放源代码的 问题:如何读取src目录下的文件? 直接new FileInputStream(模块名\\src\\配置文件名)是不行的 因为这样写,而我们给的用户是out下的资源,而out下存放的class文件路径是没有src 所以这样写,给了用户,用户一使用,直接就读不到这个配置文件了 解决: 使用类的加载器 ClassLoader类中的方法 InputStream getResourceAsStream("直接写文件名")返回一个字节输入流 此流会自动扫描src下的配置文件 2.利用IO流读取配置文件 读到Properties集合中 3.获取Properties中对应的值 获取类的全限定名 方法名 4.利用反射获取类的Class对象 利用反射去指定方法 //在src下创建一个pro.properties文件 className=cn.itcast.day15.class02.Person methodName=eat public class Test { public static void main(String[] args)throws Exception{ //获取类加载器 ClassLoader classLoader = Test.class.getClassLoader(); //使用类加载器的输入流读取配置文件 InputStream in = classLoader.getResourceAsStream("config.properties"); //创建Properties集合 Properties properties = new Properties(); //调用load方法,将流中的配置文件读取到Properties集合中 properties.load(in); //获取配置文件中的value String className = properties.getProperty("className");//cn.itcast.day20.fanshe03_test.Person String methodName = properties.getProperty("methodName");//eat //根据获取出来的className创建Person的Class对象 Class aClass = Class.forName(className); //根据从配置文件中解析出来的方法名去获取Person中的方法 Method method = aClass.getMethod(methodName); //创建对象 Object o = aClass.newInstance(); //执行方法 method.invoke(o); } }

    六.注解

    一.注解的介绍

    1.jdk1.5版本的新特性->一个引用数据类型 和类,接口,枚举是同一个层次的 2.作用: 说明:对代码进行说明,生成doc文档(API文档)(不会用) 检查:检查代码是否有错误 @Override(会用) 分析:对代码进行分析,起到了代替配置文件的作用(会用) 3.JDK中的注解: @Override -> 检测此方法是否为重写方法 jdk1.5版本,支持父类的方法重写 jdk1.6版本,支持接口的方法重写 @Deprecated -> 方法已经过时,不推荐使用 调用方法的时候,方法上会有横线,但是能用 @SuppressWarnings->消除警告 @SuppressWarnings("all")

    二.注解的定义以及属性的定义格式

    1.自定义注解格式: 修饰符 @interface 注解名{ 属性 } 2.属性的定义格式:为了提高注解作用 - 格式1:数据类型 属性名();-->没有默认值的-->需要后面赋值 - 格式2:数据类型 属性名() default 默认值;-->可以改变的 3.注解中能够定义什么样的属性 - 八种基本数据类型(int,float,boolean,byte,double,char,long,short)- String类型,Class类型,枚举类型,注解类型。 - 以上所有类型的一维数组。int[][] arr = {{1,2},{3,4}} [0][0] [1,0]->3 public @interface Book { //书名 String bookName(); //价格 double price(); //作者 String[] author(); //数量 //Integer count();错误,不能使用包装类 //int count() default 10;这个是可以的,定义属性的时候可以指定默认值 }

    三.注解的使用

    //注解的使用 /* 使用注解,就是为注解的属性赋值 格式: @注解名(属性名=值,属性名=值) 如果属性是数组 数组的赋值-->属性名 = {"曹雪芹","高鹗"} 注解,可以写在类上,方法上,参数位置都行 */ @Book(booName = "红楼梦",price = 200.9,author = {"曹雪芹","高鹗"}) pubilc class BookShelf{ } 注解注意事项: 1.空注解可以直接使用 2.一个对象中不能连续使用同一个注解多次,但是一个对象中可以使用多个不同的注解 (不同的位置可以使用一样的注解,但是同样的位置不能使用一样的注解) 3.使用注解时,如果此注解中有属性,注解中的属性一定要赋值,如果有多个属性,,隔开 如果注解中的属性有数组,那么如果数组只有一个元素值,那么{}不用写,反之用写 4.如果注解中的属性值有默认值,那么我们不必要写,也不用重新赋值,反之必须写上 5.如果注解中只有一个属性,并且属性名叫value,那么使用注解的时候,属性名不用写

    四.注解解析的方法

    注解解析:获取注解中的属性值 接口:AnnotatedElement接口中的方法(用于解析注解的接口) boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 判断当前class对象是否有指定的注解 返回值:boolean 返回的是true,证明该class对象上有注解;返回的是false,证明该class对象没有注解 Class<? extends Annotation> annotationClass 传递的是class对象, 传递的其实就是该class对象上对应注解的class对象 <T extends Annotation> getAnnotation(Class<T> annotationClass)->获得当前class对象上指定的注解对象。 解释: 参数传递的是注解的class对象 传递哪个注解类型,返回的就是哪个注解对象 AnnotatedElement毕竟是个接口,你有实现类 实现类:Class类 Constructor构造方法 Field成员变量 Method成员方法 结论:注解的解析,和哪个技术密切相关--> 反射 需求:获取BookShelf1类上的注解Book的属性值 ===================================================== 注解的解析思想: 1.反射带有注解的类 2.判断这个类上是否有注解 3.获取这个注解 4.获取注解中的属性值 ------------------ 假如我们要是解析方法上的注解属性值 1.反射带有注解的类 2.反射方法->getMethod-->Method 3.判断方法上是否有注解 4.获取这个注解 5.获取注解的属性值 @Book(booName = "红楼梦",price = 200.9,author = {"曹雪芹","高额"}) pubilc class BookShelf{ } /* 运行之后发现没有输出,而且c.isAnnotationPresent(Book.class)判断为false, 明明类上有注解,但是却返回false,为啥呢?那么我们接下来学习一下元注解 */ public class Test01 { public static void main(String[] args)throws Exception { //1.反射带有注解得类 Class c = Class.forName("cn.itcast.day21.zhujie02.BookShelf01"); //2.判断这个类上是否有注解 boolean b = c.isAnnotationPresent(Book.class); System.out.println(b); if (b){ //如果有注解,获取注解 //Annotation annotation = c.getAnnotation(Book.class); Book book = (Book) c.getAnnotation(Book.class); //获取注解中的属性值 System.out.println("书名:"+book.bookName()); System.out.println("价格:"+book.price()); System.out.println("作者:"+ Arrays.toString(book.author())); } } }

    七.元注解

    ** * 自定义的注解 Book * 定义属性 * * JDK的元注解,比喻注解的总管 * 管理其他的注解 * * 元注解对我们的注解进行控制 * 1: 控制我们的注解,可以写在哪里,(,方法,变量上,...) * 2: 控制我们的注解的生命周期 * * JDK的2个元注解 * * @Target 指示其他注解,出现的位置->点进Target底层 * ElementType[] value(); 数组,可以赋值多个->点ElementType底层 * ElementType是数据类型,是枚举 * 枚举的属性,都是静态修饰,直接类名调用 * TYPE, 其他注解可以写在类上 * FIELD,其他注解可以写在成员变量 * METHOD,其他注解可以写在方法上 * PARAMETER,其他注解可以写在方法参数上 * CONSTRUCTOR,其他注解可以写在构造方法上 * * @Retention 指示其他注解的生命周期->点到Retention底层 * RetentionPolicy value(); 不是数组,赋值一个->点到RetentionPolicy底层 * RetentionPolicy数据类型 * 枚举的属性,都是静态修饰,直接类名调用 * SOURCE(默认级别) 注解仅存在于源码中java文件中(不在class文件中,也不在方法区中)->@Override只是检 测方法是否为重写方法 * CLASS 注解存在于编译后的class文件中->Class文件中出现了,方法区中没有 * RUNTIME 运行时期的内存中-->方法区中出现了,一旦在方法区中出现了,我们才能利用反射获取到注解 所以当我们在注解上写SOURCE 运行上面的Test案例判断类上有没有注解,才会返回false */ @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Book { //书名 String bookName(); //价格 double price() ; //作者 String[] author(); } ------------------------------------ @Book(booName = "红楼梦",price = 200.9,author = {"曹雪芹","高额"}) pubilc class BookShelf{ @Book(booName = "红楼梦",price = 200.9,author = {"曹雪芹","高额"}) public void lookBook(){ System.out.println("看书"); } }

    八.注解再次解析

    @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Book { String bookName(); double price(); String[] author(); } @Book(booName = "红楼梦",price = 200.9,author = {"曹雪芹","高鹗"}) pubilc class BookShelf{ } ================================================================= public class Test01 { public static void main(String[] args)throws Exception { //1.反射带有注解得类 Class c = Class.forName("cn.itcast.day21.zhujie03.BookShelf"); //2.判断这个类上是否有注解 boolean b = c.isAnnotationPresent(Book.class); System.out.println(b); if (b){ //如果有注解,获取注解 //Annotation annotation = c.getAnnotation(Book.class); Book book = (Book) c.getAnnotation(Book.class); //获取注解中的属性值 System.out.println("书名:"+book.bookName()); System.out.println("价格:"+book.price()); System.out.println("作者:"+ Arrays.toString(book.author())); } } }

    九.模拟Junit练习

    @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyTest { } /* 使用@MyTest注解 哪个方法上有@MyTest我们就让哪个方法执行 */ public class Test01 { @MyTest public void method01(){ System.out.println("我是method01"); } public void method02(){ System.out.println("我是method02"); } @MyTest public void method03(){ System.out.println("我是method03"); } /* 1.反射带有注解的类 2.反射方法->getMethod-->Method 3.判断方法上是否有注解 4.获取这个注解 5.获取注解的属性值 */ public static void main(String[] args) throws Exception{ //1.获取本类的Class对象 Class aClass = Test01.class; //创建对象 Object o = aClass.newInstance(); //2.获取方法 Method[] methods = aClass.getMethods(); // 3.判断方法上是否有注解 for (Method method : methods) { boolean b = method.isAnnotationPresent(MyTest.class); if (b){ //如果方法上有注解,直接invoke执行 method.invoke(o); } } } }

    十.Lombok(day28讲解)

    使用的注解有: @Data->包含get/set,toString,hashCode,equals,无参构造方法 @Getter->生成get方法 @Setter->生成set方法 @NoArgsConstructor@AllArgsConstructor - @NoArgsConstructor:无参数构造方法。 - @AllArgsConstructor:满参数构造方法。 @@EqualsAndHashCode->生成hashCode和Equals
    Processed: 0.015, SQL: 9