先附上java14的官方最新文档 ,不懂就查,方便快捷,一步到位。
一、反射 反射:框架设计的灵魂。
框架:半成品软件。可以在框架的基础上进行软件开发,简化编码。
在使用框架的时候,不会反射,没有问题;但如果要开发一个框架,就需要用到反射。
1.1 反射的概念 java代码在计算机中经历三个阶段:源代码阶段、类对象阶段、运行时阶段
上图中,在类对象阶段,将成员方法,封装成Constructor[]数组这种过程,这就是反射。
举个例子,就比如写代码时,定义的一个String变量,可以通过快捷键将其所有方法显示出来,这就是通过反射完成的。
概念: 将类的各个组成部分封装为其他对象,这个过程叫做反射
,也就是反射机制。
好处:
可以在程序运行过程中,操作这些对象。 可以解耦,提高程序的可扩展性。 很抽象,一会上案例。
1.2 获取Class对象 通过Class.forName(“全类名”) 如果此时处于源代码阶段
,那我们可以通过Class.forName("全类名")
这种方式,是将字节码文件加载进内存,返回class
对象。
全类名:包名.类名
1 2 3 4 5 6 7 public class Demo01Reflect { public static void main (String[] args) throws ClassNotFoundException { Class cls=Class.forName("demo44.Person" ); System.out.println(cls); } }
适用场景:
多用于配置文件,将类名定义在配置文件中。读取文件,加载类
通过类名.class 如果此时处于类对象阶段
,那我们可以通过类名的属性类名.class
获取class对象
1 2 3 4 5 6 7 public class Demo01Reflect { public static void main (String[] args) throws ClassNotFoundException { Class cls2=Person.class ; System.out.println(cls2); } }
适用场景:
多用于参数的传递
通过对象.getClass() 如果此时处于运行时阶段
,那我们可以通过对象名.getClass
来获取class对象。
getClass()是在Object类中定义的,所以所有的类,都有这个方法
1 2 3 4 5 6 7 8 public class Demo01Reflect { public static void main (String[] args) throws ClassNotFoundException { Person p=new Person(); Class cls3=p.getClass(); System.out.println(cls3); } }
适用场景:
多用于对象获取字节码的方式
一次运行只会加载一次字节码文件 将三个不同过程中,获取到的对象,进行地址值的比较,会发现都是同一个地址。
由此,我们可以得出结论:同一字节码文件(.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的class对象,都是同一个
1.3 使用Class对象的获取功能 以下面这个Person类
为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public class Person { private String name; private int age; public int sex; protected String intro; String education; 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; } public Person (String name, int age) { super (); this .name = name; this .age = age; } public Person () { super (); } @Override public String toString () { return "Person [name=" + name + ", age=" + age + ", sex=" + sex + ", intro=" + intro + ", education=" + education + "]" ; } public void say () { System.out.println(name + "最美!我爱" + name); } }
获取成员变量 常用方法 Field getField(String name) 返回一个Field对象,它反映此 Class对象所表示的类或接口的指定公共成员字段 。 Field[] getFields() 返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段 。 Field getDeclaredField(String name) 返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段 。 Field[] getDeclaredFields() 返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段 。 操作 获取值:Object get(Object obj) 返回指定对象上此 Field 表示的字段的值。 设置值:void set(Object obj, Object value) 将指定对象变量上此 Field 对象表示的字段设置为指定的新值。 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class Demo02Reflect { public static void main (String[] args) throws Exception { Class personClass=Person.class ; Field[] fields=personClass.getFields(); for (Field field:fields) { System.out.println(field); } Field sex=personClass.getField("sex" ); Person p=new Person(); Object value=sex.get(p); System.out.println(value); sex.set(p, 1 ); System.out.println(p); System.out.println("===============" ); Field[] declaredFields=personClass.getDeclaredFields(); for (Field declaredField:declaredFields) { System.out.println(declaredField); } Field name=personClass.getDeclaredField("name" ); name.setAccessible(true ); Object value2=name.get(p); System.out.println(value2); name.set(p, "胡列娜" ); System.out.println(p); } }
注意点:
一般地,私有的成员变量不能在类外面进行访问或设置;但是在反射中,私有成员变量就可以获取和设置。这就是反射一个很牛逼的点。 在访问非public的成员变量的时候,会抛异常IllegalAccessException
。这就需要忽略访问权限修饰符的安全性检查setAccessible(boolean flag)
,true
表示忽略,这也叫做暴力反射
获取构造方法 常用方法 ConstructorgetConstructor(Class<?>… parameterTypes) 返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。 Constructor<?>[] getConstructors() 返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。 ConstructorgetDeclaredConstructor(Class<?>… parameterTypes) 返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法。 Constructor<?>[] getDeclaredConstructors() 返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法。 操作 创建对象:T newInstance(Object… initargs) Uses the constructor represented by this Constructor object to create and initialize a new instance of the constructor’s declaring class, with the specified initialization parameters. 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class Demo03Reflect { public static void main (String[] args) throws Exception { Class personClass = Person.class ; Constructor c=personClass.getConstructor(String.class ,int .class ) ; System.out.println(c); Object person=c.newInstance("胡列娜" ,22 ); System.out.println(person); System.out.println("======================" ); Constructor c2=personClass.getConstructor(); System.out.println(c2); Object person2=c2.newInstance(); System.out.println(person2); System.out.println(personClass.newInstance()); } }
注意:
私有的构造方法,也是需要通过暴力反射来解决。同理,获取成员方法时也一样。
获取成员方法 常用方法 Method getMethod(String name, Class<?>… parameterTypes) 返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。 Method[] getMethods() 返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。 Method getDeclaredMethod(String name, Class<?>… parameterTypes) 返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。 Method[] getDeclaredMethods() 返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。 操作 执行方法:Object invoke(Object obj, Object… args) 在具有指定参数的指定对象上,调用此Method对象表示的基础方法 获取方法名:String getName() 以String形式返回此Method对象表示的方法的名称。 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Demo04Reflect { public static void main (String[] args) throws Exception { Class personClass = Person.class ; Method m=personClass.getMethod("say" ); m.invoke(new Person("胡列娜" ,20 )); System.out.println("===========" ); Method[] ms=personClass.getMethods(); for (Method method:ms) { System.out.println(method.getName()); } } }
下面放出一个坑
1 2 3 4 5 6 7 8 9 10 11 public class TestCircle { public double getArea (double r) { return Math.PI*r*r; } public static void main (String[] args) throws NoSuchMethodException, SecurityException { Class c=TestCircle.class ; Method m=c.getDeclaredMethod("getArea" , Double.class ) ; System.out.println(m); } }
仔细一看报错了吧。这是我老师上课写的,结果我下课研究的时候,整了两个小时才整明白,找到错误。
这个错,就是方法中变量的类型跟反射里面定义的类型不一样。
通俗点说,就是你的方法的变量类型是int,那么反射中获取方法时,变量的类型是int.class,同理,如果是Integer,那么反射中应是Integer。可以参照这篇文章
获取类名 常用方法 String getName() 以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。 代码 1 2 Class personClass = Person.class ; System.out.println(personClass.getName());
1.4 反射案例 需求 使用反射,写一个框架类,在不改变该类的任何代码的情况下,可以创建任意类的对象,并且执行其中任意的方法
实现:
配置文件 反射 步骤:
将需要创建的对象的全类名和需要执行的方法定义在配置文件中 在程序中加载读取配置文件 使用反射技术来加载类文件进内存 创建对象 执行方法 通过这样,我们以后如果修改的话,就只需要改配置文件,而不需要改代码了
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class ReflectTest { public static void main (String[] args) throws Exception { Properties pro = new Properties(); ClassLoader classLoader = ReflectTest.class .getClassLoader () ; InputStream is = classLoader.getResourceAsStream("pro.properties" ); pro.load(is); String className = pro.getProperty("className" ); String methodName = pro.getProperty("methodName" ); Class cls = Class.forName(className); Object obj = cls.newInstance(); Method met = cls.getMethod(methodName); System.out.println(cls.getName()); met.invoke(obj); } }
当类是GirlFriend时,执行marry方法
当类是Wife时,执行divorce方法
同样的代码,可以有不同的运行结果,这个过程只需要改配置文件。
1 2 3 4 5 className =demo44.domain.Wife methodName =divorce
好处 当项目比较庞大的时候,如果直接改代码,改完之后还需要进行测试,容易出问题。而通过反射,只需要改配置文件就可以了。
二、注解 2.1 注解的概念 注释:用文字描述程序 。比方说描述程序的功能,程序变量的含义之类的。注释给开发者看的 。
注解:说明程序的 。注解给计算机看的 。
概念描述
jdk1.5之后的新特性 说明程序的 使用注解:@注解名称 作用分类
编写文档:通过代码里标识的注解生成文档(通过javadoc 文件全名
生成doc文档) 代码分析:通过代码里标识的元数据对代码进行分析(使用反射) 编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查(Override) 2.2 jdk的内置注解 常用内置注解 @Override:检测被该注解标注的方法是否是继承自父类(接口)的
@Deprecated:将该注解标注的内容,已过时
@SuppressWarnings: 指示编译器去忽略注解中声明的警告。
了解更多的java注解 ,移步到菜鸟教程。
使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @SuppressWarnings ("all" )public class Demo01Annotation { @Override public String toString () { return "Demo01Annotation []" ; } @Deprecated public void play () { } public void play2 () { ArrayList list = new ArrayList(); } public void demo () { play(); } }
描述一下@SuppressWarning注解的使用。如果代码段报警告,可以通过@SuppressWarnings(“all”)标注在其方法上来忽略警告,不过一般地,会直接将其标注在类上。
2.3 自定义注解 格式 如何自定义注解?先来看一下内置注解的格式:
通过对两个内置注解的观察,我们可以发现,注解是由下面这两个部分组成的。
元注解 public @interface 注解名称{} 举个例子
1 2 public @interface Demo02Annotation {}
以上面这个为例,我们将其进行javac
编译,然后再通过javap
反编译,得到以下这个代码
1 2 public interface Demo02Annotation extends java .lang .annotation .Annotation {}
注解本质上就是一个接口,该接口默认继承了Annotation。接口里面可以定义什么,注解里面同样可以定义什么
属性 注解里的属性 ,可以认为是接口中的抽象方法 ,因为它可以在使用时,跟属性(成员变量)一样用
要求:
属性的返回值类型:基本数据类型、String、枚举、注解、以上类型的数组 定义了属性,在使用时,需要给属性赋值。如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值 如果只有一个属性需要赋值,并且属性的名称是value ,则value可以省略(可以参考@SuppressWarnings的源码) 数组赋值时,用{}包裹。如果其中只有一个值的时候,{}可以省略不写 使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 enum Person { Male, Female } @interface Anno {} public @interface Demo02Annotation { int value () ; String name () default "胡列娜" ; Person p () ; Anno a () ; String[] pers(); } @Demo 02Annotation(value=22 ,a = @Anno , p = Person.Male, pers = { "胡列娜" ,"江厌离" ,"邱若水" }) class Worker {}
元注解 元注解:用于描述注解的注解
常用的元注解:
@Target:描述注解能够作用的位置ElementType取值TYPE:可以作用在类上 METHOD:可以作用在方法上 FIELD:可以作用在成员变量上 @Retention:描述注解被保留的阶段@Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被jvm读取到。一般也只会用到这个 @Documented:描述注解会被抽取到api文档中 @Inherited:描述注解会被子类继承 对于@Retention,把源码截图放到这里,其中刚好对应java代码经过的三个阶段
像Documented,还有@Inherited就不多赘述了,演示过程也就不放上来了。通过命令javadoc 生成文档时,会发现区别。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Retention (RetentionPolicy.RUNTIME)@Target ({ ElementType.TYPE,ElementType.METHOD,ElementType.FIELD })public @interface Demo03Annotation {} @Demo 03Annotationclass Worker01 { @Demo 03Annotation public void show () { } }
2.4 使用(解析)注解 在程序中使用(解析)注解:获取注解中定义的属性值
由此,在大多数时候,注解是用来替换配置文件 的
步骤 获取注解定义的位置的对象 获取指定的注解 调用注解中的抽象方法,获取配置的属性值 代码 跟反射案例 一样,实现同样的功能。用注解替代配置文件 。
Pro.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Target (ElementType.TYPE)@Retention (RetentionPolicy.RUNTIME)public @interface Pro { String className () ; String methodName () ; } class ProImpl implements Pro { @Override public Class<? extends Annotation> annotationType() { return null ; } @Override public String className () { return null ; } @Override public String methodName () { return null ; } }
ReflectAnnotation.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Pro (className = "demo44.domain.GirlFriend" , methodName = "marry" )public class ReflectAnnotationTest { public static void main (String[] args) throws Exception { Class<ReflectAnnotationTest> c = ReflectAnnotationTest.class ; Pro an = c.getAnnotation(Pro.class ) ; String className = an.className(); String methodName = an.methodName(); Class cls = Class.forName(className); Object obj = cls.newInstance(); Method met = cls.getMethod(methodName); System.out.println(cls.getName()); met.invoke(obj); } }
2.5 实现简单的测试框架 Check.java
1 2 3 4 @Target (ElementType.METHOD)@Retention (RetentionPolicy.RUNTIME)public @interface Check {}
Calculator.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class Calculator { @Check public void add () { String str = null ; str.toString(); System.out.println("1 + 0 =" + (1 + 0 )); } @Check public void sub () { System.out.println(Integer.parseInt("hahah123" )); System.out.println("1 - 0 =" + (1 - 0 )); } @Check public void mul () { System.out.println("1 * 0 =" + (1 * 0 )); } @Check public void div () { System.out.println("1 / 0 =" + (1 / 0 )); } public void show () { System.out.println("永无bug..." ); } }
TestCheck.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class TestCheck { public static void main (String[] args) throws IOException { int count=0 ; BufferedWriter bw=new BufferedWriter(new FileWriter("bug.txt" )); Calculator c=new Calculator(); Class cls=c.getClass(); Method[] methods=cls.getMethods(); for (Method m:methods) { if (m.isAnnotationPresent(Check.class )) { try { m.invoke(c); } catch (Exception e) { count++; bw.write("异常的方法:" +m.getName()); bw.newLine(); bw.write("异常的名称:" +e.getCause().getClass().getSimpleName()); bw.newLine(); bw.write("异常的原因:" +e.getCause().getMessage()); bw.newLine(); bw.write("======================" ); bw.newLine(); } } } bw.write("本次测试一共出现 " +count+" 次异常" ); bw.flush(); bw.close(); } }
运行结果
这就实现了一个简单的测试框架。
同样的道理,像原来学过的Junit就算是一个测试框架了,里面的@Test就是注解。
总结:
PS:今天形式政策考试考了65,老师在群里让同学发成绩单,我的分数是最低的。这也算是我一段时间来学习态度的反映吧。像数学跟线代还有英语,这学期,上网课期间,我不是上课睡觉,就是在玩电脑,一点都没听,我也该收收心好好学学数学了。加油!奥利给!好好学习!