Java:反射 / 注解 / package / JUnit详解

更多
2021年08月23日 20点52分 作者:叶飞 修改

复习:J&C:反射和特性(注释)

说明:以下演示大量使用F3和Ctrl+T理清继承结构


Class类

两种获取方式

  • .getClass():通过实例(在运行时)获取
  • .class:通过类型(在编译时)获取
以及:通过字符串类名获得(复习)
Class<? extends Person> ci = Person.class;

@想一想@:为什么是<? extends Person>,说明什么?

//假如有:Class<Person> ciByObject = null;
Class<? extends Person> ciByObject = Teacher.class;		
Person p = ciByObject.newInstance();

类成员

都可以通过Class对象获得:

  • getConstructors():返回构造函数
  • getField():返回字段
    Field name = Person.class.getField("name");
    //读写字段
    name.set(fg, "大飞哥");
    System.out.println(name.get(fg));
    
  • getMethod():返回方法,可使用invoke()调用该方法
    Method eat = Person.class.getMethod("eat", Double.class);
    //调用方法
    eat.invoke(new Person("飞哥"), 18.6);

Java对其进行了很好的整理(值得借鉴):

  • interface Member:类成员
    • final class Field:字段(不可执行),可调用set()/get()方法赋值取值
    • abstract class Executable:可执行的
      • final class Method:方法
      • final class Constructor<T>:构造函数

PS:Class实现的是interface Type,Type下面还有四个和泛型相关的接口(仅供了解)

所有反射相关的类,都放在两个package里面:

package java.lang;
package java.lang.reflect;

突破访问限制

以调用私有方法为例:

Method eat =Person.class.getDeclaredMethod("eat");
eat.setAccessible(true);
eat.invoke(new Person());

关键:

  • getDeclaredMethod():获取所有的,而不仅仅是私有的方法
  • setAccessible():重新设置访问权限

继承和多态

继承和多态,在反射中仍然起作用。

首先,子类类型信息对象,就直接继承(包含)着她父类成员:

//Teacher中并没有声明eat()方法,eat()来自基类
Teacher.class.getMethod("eat").invoke(new Teacher());    

其次,也可以由子类对象调用其父类成员:

//Teacher对象可以调用基类Person的eat()方法
Person.class.getMethod("eat").invoke(new Teacher());  

比较复杂一点的是如果子类覆盖(override)了父类方法:

//类似于:new Person().eat(),OK
Person.class.getMethod("eat").invoke(new Person());
//类似于:new Teacher().eat(),OK
Person.class.getMethod("eat").invoke(new Teacher());

//运行时错误:Person对象不能调用Teacher中的eat()方法
Teacher.class.getMethod("eat").invoke(new Person());


注解

自定义声明

我们可以使用@interface自己定义一个注解。

很像一个接口,嗯……,里面还可以有方法声明一样的东东(正式名称attribute:属性),比如:

@interface guru{ //注意这个@符号
	int level();
}

这样,标记时就需要指定level的值:

@guru(level = 2)
class Person {
如果level命名为value的话,value=可以省略,比如:

反射获取

要能通过反射获取到注解,需要在注解上添加一个
@Retention(RetentionPolicy.RUNTIME)

标记该注解会在一直保留(retention)到运行时(runtime)

然后,通过AnnotatedElement接口实例(比如:Class<T>/Member/Field)的:

  • getAnnotations():获取其上标记的所有注解:
    Annotation[] annotations = Person.class.getAnnotations();
    for (int i = 0; i < annotations.length; i++) {
    	System.out.println(annotations[i]);
    }
  • getAnnotation(Class<T>)方法,获取Class<T>指定的一个注解:
    Guru annotation = Person.class.getAnnotation(Guru.class);
    System.out.println(annotation.value());

内置注解

记JDK/JRE中自带的注解,简单分类:

代码用的:

  • @Override - 标记该方法重写父类方法。如果发现其父类中并没有该方法,会报编译错误。
  • @Deprecated - 标记过时成员。如果使用该成员,会报编译警告。
  • @SuppressWarnings - 指示编译器去忽略注解中声明的警告,带字符串value属性,常用的有:all、unused、unchecked……

注解用的:

  • @Retention - 标识这个注解怎么保存,带一个RetentionPolicy类型(枚举)value,可选:
    • SOURCE:只在代码中存在,编译时丢弃
    • CLASS(默认):编译后保存在类文件中,但运行时会被JVM丢弃
    • RUNTIME:一直保留,在运行时可以通过反射访问。
  • @Documented - 标记这些注解是否包含在用户文档中。
  • @Target - 标记这个注解应该是哪种 Java 成员。

其他:

  • @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。


package:包

复习java基础eclipse)+讲解+控制台演示

  • .java文件的编译,需要使用到JDK中的javac.exe工具,编译完成,生成包含二进制的、字节码的.java文件。
  • .java文件,可以直接用java.exe运行

所以,单个.class文件是可以执行的,但是,项目中通常都会有多个.java源代码文件,编译后就会形成多个.class文件,这时候咋办

通常我们会把它们打包(package)成.jar(Java Archive)文件,打包实际上包含两个过程:

  • 压缩:以流行的 ZIP 文件格式为基础(演示,使用7zip直接解压.jar文件)
  • 插入一些配置文件,比如:/META-INF/MANIFEST.MF,演示:
    • 记事本打开
    • 解压其他更复杂的jar包
PS:除了.jar,还有.war(Web),.ear(Enterprise)等

class loader

通过Class可以直接获取package:
Main.class.getPackage();

但不能从这个Package中得到所有的类,为啥呢?

JVM 运行并不是一次性加载所需要的全部类的,它是按需加载(又被称之为延迟加载):在程序在运行的过程中,需要用什么类,就加载什么类。

所谓加载,就是把Class 的字节码形式( *.class)转换成内存形式的 Class 对象,这个过程是通过 ClassLoader 来完成的。

F3演示:forName()调用ClassLoader……


JUnit

演示:eclipse上新建一个JUnit项目

官方定义JUnit5由三部分组成:

  • Platform:位于架构的最底层,是JVM上执行单元测试的基础平台,对接了各种IDE(例如IDEA、eclipse),定义了引擎层对接的API;
  • Jupiter:位于引擎层,支持5版本的编程模型、扩展模型;
  • Vintage:位于引擎层,用于执行低版本的测试用例

尝试着理解:Platform定义接口(API),所以是开放的

有用的注解

测试方法:

  • @Test:表示方法是测试方法。
  • @Disabled:表示测试类或测试方法不执行,类似于JUnit4中的@Ignore

测试环境:

  • @BeforeAll :表示在所有单元测试之前执行
  • @AfterAll :表示在所有单元测试之后执行
    注意:上述两个注解只能是在静态方法上标记
  • @BeforeEach :表示在每个单元测试之前执行
  • @AfterEach :表示在每个单元测试之后执行

断点演示:执行顺序


作业

  1. 写一段代码演示.class和.getClass()的区别,以及他们和forName()的区别。
  2. 将当前项目打包成.jar文件,用命令行运行
  3. 用内置注解完善之前的作业
  4. 封装方法:
    1. GetPrivateField(),可以获得某对象的某私有字段值
    2. SetPrivateField(),可以修改某对象的某私有字段值
    提示:能不能用使用泛型?
  5. 自定义一个注解/特性HelpMoneyChanged(帮帮币变化):
    1. 该特性只能用于方法
    2. 默认可以接受一个int类型的参数amount,表示帮帮币变化的数量
    3. 有一个string类型的Message属性,记录帮帮币变化的原因
  6. 将HelpMoneyChanged应用于Publish()方法
  7. 用反射获取Publish()上的特性实例,输出其中包含的信息
  8. 用JUnit重写之前的双向链表单元测试。


Java 反射 注解
赞: 0 踩: 0

打赏
已收到打赏的 帮帮币

你的 打赏 非常重要!
为了保证文章的质量,每一篇文章的发布,都已经消耗了作者 1 枚 帮帮币
没有“帮帮币”,作者无法发布新的文章。

全系列阅读
评论 / 0

后台开发


其他:WebForm和WebApi

其他ASP.NET框架,如WebForm、WebApi……

RazorPages(Core)

微软推荐的、最新的、基于Razor页面和.NET core的新一代Web项目开发技术,包括Razor Tag Helper、Model绑定和Validation、Session/Cookie、内置依赖注入等……

MVC(Framework)

过去两年间最流行的、基于.NET Framework和MVC模式的ASP.NET MVC框架,主要用于讲解安全、性能、架构和各种实战功能演示……

C#语法

从入门的变量赋值、分支循环、到面向对象,以及更先进的语言特性,如:泛型、Lambda、Linq、异步方法等…………

Java语法

面向过程的变量赋值、分支循环和函数封装;面向对象的封装、继承和多态;以及更高阶的常用类库(集合/IO/多线程……)、lambda等

Java Web开发

SpringMVC

分层架构和综合实战

J&C

Java和C#共有的语法

全部
关键字



帮助

反馈