当前位置: 代码迷 >> 综合 >> 单元测试、注解、枚举、反射(5)JavaSE
  详细解决方案

单元测试、注解、枚举、反射(5)JavaSE

热度:57   发布时间:2023-09-30 05:41:38.0

1 JUnit单元测试

1.1 Junit的介绍

Junit属于白盒测试(白盒测试是对软件的过程性细节做细致的检查。这种方法是把测试对象看做一个打开的盒子,它允许测试人员利用程序内部的逻辑结构及有关信息,设计或选择测试用例,对程序的所有逻辑路径进行测试,通过在不同点检查程序状态,确定实际状态是否与预期的状态一致。因此白盒测试又称为结构测试)。
使用JUnit的原因是因为在没有使用Junit的时候,有以下缺点:
(1)测试一定走main方法,是程序的入口,main方法的格式必须不能写错。
(2)要是在同一个main方法中测试的话,那么不需要测试的东西必须注释掉。
(3)测试逻辑如果分开的话,需要定义多个测试类,麻烦。
(4)业务逻辑和测试代码,都混淆了。
而使用Junit就可以在同一个类中定义多个方法,并且增加更多强大的功能。

1.2 Junit的使用

(1)一般测试和业务做一个分离,分离为不同的包,以后测试类就单独放在这个包下。(建议起名:公司域名倒着写+test)
(2)测试类的名字:****Test (见名知意)
(3)测试方法的定义:这个方法可以独立运行,不依托于main方法。(建议:名字见名知意:testAdd() ,testSub()。参数:无参。返回值:void)。
(4)测试方法定义完成后,还不能直接独立运行,必须在方法上加一个注解:@Test,并且导入JUnit包后,才可以独立运行。
(5)判定结果是绿色:正常结果。红色:出现异常。
(6)即使出现绿色效果,也不意味着你的测试就通过了,因为代码中逻辑也可能出现问题,这种情况怎么解决呢?加入断言。同理绿色通过,红色不通过。
EG:做一个对两个方法(加法和减法)的测试类

import org.junit.Test;
import com.jayden.Calculator//对计算器Calculator类定义一个测试类
class CalculatorTest{
    //对计算器Calculator类中的add加法方法进行测试@Testpublic static void testAdd(){
    System.out.println("测试add方法");Calculator cal = new Calculator();int result = cal.add(10,30);//加入**断言**:预测一下结果,判断一下我预测的结果和 实际的结果是否一致://第一个参数:预测结果 第二个参数:实际结果Assert.assertEquals(40,result);}//对计算器Calculator类中的sub减法方法进行测试public static void testSub(){
    System.out.println("测试减法方法");Calculator cal = new Calculator();int result = cal.sub(30,10);System.out.println("result");}
}

(7)如果需要对一个类中的很多方法进行测试的过程中,这些方法都需要写一些同样的代码,我们如果每一个方法中都写入这些代码,就会造成很大的冗余。遇到这种情况就可以使用JUnit中两个注解@Before和@After。
某一个方法中,加入@Before注解以后,那么这个方法中的功能会在测试方法执行前先执行。一般会在@Beforer修饰的那个方法中加入:加入一些申请资源的代码:申请数据库资源,申请IO资源,申请网络资源等等。
加入了@After注解以后,那么这个方法中的功能会在测试方法执行后先执行。一般会在@After修饰的那个方法中加入:加入释放资源的代码:释放数据库资源,释放IO资源,释放网络资源等等。

2 注解

2.1 注解的介绍

  • 注解(Annotation,也称元数据)是在JDK5.0新增的。
  • 它是代码里面的特殊标记。使用注解时要在其前面增加@符号,并把该注解当成一个修饰符使用。用于修饰它支持的程序元素。这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过使用注解,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。
  • Annotation 可用于修饰包,类,构造器,方法,成员变量,参数,局部变量,这些信息被保存在Annotation的"name=value"对中。在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/ArIdroid中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。未来的开发模式都是基于注解的,JPA(java的持久化API)是基于注解的,Spring2.5以. E都是基于注解的,Hibernate3.x以后也是基于注解的,现在的Struts2有一部分也是基于注解的了,注解是一种趋势,一定程度上可以说 :框架=注解+反射+设计模式

2.2 注解的使用

2.2.1 Junit的注解

@Test @Before @After 见1.2

2.2.2 文档相关的注解

文档注释(/** … **/)允许你在程序中嵌入关于程序的信息。需要配合javadoc工具软件来生成信息,并且显示在HTML文件中。文档注解(@author)一般就使用在文档注释之中。
javadoc工具一般识别的标签有
单元测试、注解、枚举、反射(5)JavaSE
注意:
? @param @return和@exception这三个标记都是只用于方法的。
? @param的格式要求: @param 形参名 形参类型 形参说明
? @return的格式要求: @return 返回值类型返回值说明,如果方法的返回值类型是void就不能写
? @exception的格式要求: @exception 异常类型异常说明
? @param和@exception可以并列多个

EG:生成一个Person的文档注释HTML
/*** @author : jayden* @version : 1.0*/
public class Person {
    /*** 下面是eat方法,实现了XXX功能* @param num1 就餐人数* @param num2 点了几个菜*/public void eat (int num1,int num2){
     }/*** 下面是睡觉方法* @param age 年龄* @return int* @exception RuntimeException 当年龄过大时* @exception IndexOutOfBoundsException 当年龄过小时* @see Student*/public int sleep(int age){
    new Student();if(age>100){
    throw new RuntimeException();}if(age<0){
    throw new IndexOutOfBoundsException();}return 10;}
}

IDEA中javadoc的使用:
单元测试、注解、枚举、反射(5)JavaSE
单元测试、注解、枚举、反射(5)JavaSE
在指定的目录输出后会有很多html文件,index.html就是我们要查看的注解文档。
注意:防止乱码:
单元测试、注解、枚举、反射(5)JavaSE

2.2.3 JDK内置的3个注解

  • @Override: 限定重写父类方法,该注解只能用于方法,只要重写方法在父类中有问题,就会有错误提示。
  • @Deprecated: 用于表示所修饰的元素(类,方法,构造器,属性等)已过时。通常是因为所修饰的结构危险或存在更好的选择。
  • @SuppressWarnnings: 抑制编译器警告

EG:

class Person{
    public void eat(){
    System.out.println("父类eat...");}
}public class Student extends Person {
    /*@Override的作用:限定重写的方法,只要重写方法有问题,就有错误提示。*/@Overridepublic void eat(){
    System.out.println("子类eat..");}/*在方法前加入@Deprecated,这个方法就会变成一个废弃方法/过期方法/过时方法*/@Deprecatedpublic void study(){
    System.out.println("学习。。");}
}class Test{
    public static void main(String[] args){
    Student stu = new Student();/*过期方法的用画横线提示*/stu.study();@SuppressWarnings("unused")int age = 10; //原本是暗色,变成亮色int num = 10;System.out.println(num); //只要被使用过就会变亮//rwatypes:泛型@SuppressWarnings({
    "unused","rwatypes"})ArrayList al = new ArrayList();}
}

2.2.4 代替配置文件的注解

在servlet3.0之前的配置:
需要专门用一个xml配置文件来设置该Servlet所对应的访问地址

package com.bjsxt.servlet;import javax.servlet.*;
import java.io.IOException;public class HelloServlet implements Servlet {
    @Overridepublic void init(ServletConfig servletConfig) throws ServletException {
    }@Overridepublic ServletConfig getServletConfig() {
    return null;}   @Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    System.out.println("service方法被调用了...");}@Overridepublic String getServletInfo() {
    return null;}@Overridepublic void destroy() {
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><!--配置Servlet--><!--配置Servlet的信息--><servlet><servlet-name>HelloServlet</servlet-name><servlet-class>com.bjsxt.servlet.HelloServlet</servlet-class></servlet><!--配置Servlet的映射路径--><servlet-mapping><servlet-name>HelloServlet</servlet-name><!--http://localhost:8080/01-hello-servlet/hello--><url-pattern>/hello</url-pattern></servlet-mapping>
</web-app>

在servlet3.0之后使用注解:替代配置文件。
只需要一个@WebServlet注解指定地址即可。

package com.bjsxt.servlet;import javax.servlet.*;
import java.io.IOException;@WebServlet("/hello")
public class HelloServlet implements Servlet {
    @Overridepublic void init(ServletConfig servletConfig) throws ServletException {
    }@Overridepublic ServletConfig getServletConfig() {
    return null;}/*** 用于提供服务, 接收请求, 处理响应** @param servletRequest* @param servletResponse* @throws ServletException* @throws IOException*/@Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    System.out.println("service方法被调用了...");}@Overridepublic String getServletInfo() {
    return null;}@Overridepublic void destroy() {
    }
}

2.2.5 自定义注解

1.自定义注解使用很少,一般情况下都是用现成的注解。

EG:接口的定义
public @interface MyAnnotation {
    String[] value();
}

2.发现定义的注解的声明使用的关键字:@interface,跟接口没有一点关系。
3.这value是属性看上去是无参数方法,实际上理解为一个成员变量,一个属性
无参数方法名字看成是成员变量的名字,无参数方法的返回值看成是成员变量的类型。这个参数叫 配置参数
4.无参数方法的类型:基本数据类型(八种),String,枚举,注解类型,还可以是以上类型对应的数组。

EG:接口的使用

1)使用注解的话,如果你定义了配置参数,就必须给配置参数进行赋值操作:

@MyAnnotation(value={
    "abc","ccc"})
public class Test {
    
}

2)如果只有一个参数,并且这个参数的名字为value的话,那么value=可以省略不写。

@MyAnnotation({
    "abc","ccc"})
public class Test {
    
}

3)如果你给配置参数设置默认的值了,那么使用的时候可以无需传值:

public @interface MyAnnotation {
    String[] value() default {
    "abc","hello"};
}
@MyAnnotation()
public class Test {
    
}

4)注解可以叠加使用

@MyAnnotation()
@MyAnnotation2
public class Test {
    
}

5)一个注解的内部是可以不定义配置参数的。内部没有定义配置参数的注解叫做标记。内部定义配置参数的注解叫做元数据

public @interface MyAnnotation2 {
    
}

2.2.6 元注解

元注解是用于修饰其它注解的注解。
JDK5.0提供了四种元注解:Retention, Target, Documented, Inherited

EG:元注解使用的例子
@Target({
    TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

1)@Retention
@Retention:用于修饰注解,用于指定被修饰注解的生命周期,@Rentention包含一个RetentionPolicy枚举类型的成员变量,使用@Rentention时必须为该value成员变量指定值。

  • RetentionPolicy.SOURCE: 只在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释,在.class文件中不会保留注解信息(具体表现,反编译查看字节码文件:发现字节码文件中没有MyAnnotation这个注解。)
  • RetentionPolicy.CLASS: 在class文件中有效(即class保留),保留在.class文件中,但是当运行Java程序时,他就不会继续加载了,不会保留在内存中,JVM不会保留注解。如果注解没有加Retention元注解,那么相当于默认的注解就是这种状态。(反编译看字节码文件,字节码文件中带有MyAnnotation注解。)
  • RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行 Java程序时,JVM会保留注释,加载在内存中了,那么程序可以通过反射获取该注释。

2)@Target
用于修饰注解的注解,用于指定被修饰的注解能用于修饰哪些程序元素。@Target也包含一个名为value的成员变量。

@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

ElementType变量是一个枚举数组,里面包含数据类型,方法,类等。如果要使用@Target标记过的注解@test,那么@test首先就要指定@Target指定的范围如@Target({TYPE,METHOD}),然后再使用@test注解后就只能再指定类型上使用这个注解

public enum ElementType {
    TYPE,//类FIELD,//属性METHOD,//方法PARAMETER,//参数CONSTRUCTOR,//构造器LOCAL_VARIABLE,ANNOTATION_TYPE,//注解类型PACKAGE,//包TYPE_PARAMETER,TYPE_USE
}
EG:
@Target({
    TYPE})
public @interface MyAnnotation {
    
}
@MyAnnotation() //因为@Target指定了TYPE,所以可以作用再类Class上
public class Test {
    @MyAnnotation() //不能使用int a;@MyAnnotation() //不能使用public static void testOne(){
    }
}

3)@Documented
用于指定被该元注解修饰的注解类将被javadoc工具提取成文档。默认情况下,javadoc是 不包括注解的,但是加上了这个注解生成的文档中就会带着注解了。
4)@Inherited
被它修饰的Annotation将具有继承性。如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解。

3 枚举

在java中,类的对象是有限个,确定的。这个类我们可以定义为枚举类。
举例:
星期:一二三四五六日
性别:男女
季节:春夏秋冬
构造枚举类的特点:
1.属性是私有的,而且不可改变
2.构造器也是私有的,不可以随意构造枚举类
3.在类中定义枚举,提供给外界的构造方法是有限的。

3.2 枚举的构造

在JDK1.5之前,得自定义枚举类。EG:
public class Season {
    //属性是私有的,而且不可改变private final String seasonName;private final String seasonDesc;//构造器也是私有的,不可以随意构造Season类private Season(String seasonName,String seasonDesc){
    this.seasonName = seasonName;this.seasonDesc = seasonDesc;}//枚举,提供给外界的构造方法是有限的。public static final Season SPRING = new Season("春天","春暖花开");public static final Season SUMMER = new Season("夏天","烈日炎炎");public static final Season AUTUMN = new Season("秋天","硕果累累");public static final Season WINTER = new Season("冬天","冰天雪地");//额外:public String getSeasonName(){
    return this.seasonName;}public String getSeasonDesc(){
    return this.seasonDesc;}@Overridepublic String toString() {
    return "Season{" +"seasonName='" + seasonName + '\'' +", seasonDesc='" + seasonDesc + '\'' +'}';}
}
class TestSeason {
    //这是一个main方法,是程序的入口:public static void main(String[] args) {
    Season summer = Season.SUMMER;System.out.println(summer/*.toString()*/);System.out.println(summer.getSeasonName());}
}
在JDK1.5之后,枚举类enum出现,可以简化很多,对应自定义的修改。EG:
public enum  Season {
    //定义枚举,必须放在最前面//提供枚举类的有限的 确定的对象:--->enum枚举类要求对象(常量)必须放在最开始位置//多个对象之间用逗号,进行连接,最后一个对象后面用分号;结束SPRING ("春天","春暖花开"),SUMMER ("夏天","烈日炎炎"),AUTUMN("秋天","硕果累累"),WINTER("冬天","冰天雪地");//属性是私有的,而且不可改变private final String seasonName;private final String seasonDesc;//构造器也是私有的,不可以随意构造Season类private Season(String seasonName,String seasonDesc){
    this.seasonName = seasonName;this.seasonDesc = seasonDesc;}//额外:public String getSeasonName(){
    return this.seasonName;}public String getSeasonDesc(){
    return this.seasonDesc;}@Overridepublic String toString() {
    return "Season{" +"seasonName='" + seasonName + '\'' +", seasonDesc='" + seasonDesc + '\'' +'}';}

3.2 枚举的使用以及常用的方法:

public class TestSeason {
    public static void main(String[] args) {
    Season winter = Season.WINTER;System.out.println(winter);//enum关键字对应的枚举类的上层父类是 :java.lang.Enum//但是我们自定义的枚举类的上层父类:ObjectSystem.out.println(Season.class.getSuperclass().getName());//java.lang.Enum}
}
public class TestSeason {
    public static void main(String[] args) {
    //enum关键字对应的枚举类的上层父类是 :java.lang.Enum//但是我们自定义的枚举类的上层父类:ObjectSeason winter = Season.WINTER;System.out.println(winter);System.out.println(Season.class.getSuperclass().getName());//java.lang.Enum//用enum关键字创建的Season枚举类上面的父类是:java.lang.Enum,常用方法子类Season可以直接拿过来使用://toString();--->获取对象的名字Season autumn = Season.AUTUMN;System.out.println(autumn/*.toString()*/);//AUTUMNSystem.out.println("--------------------");//values:返回枚举类对象的数组Season[] values = Season.values();for(Season s:values){
    System.out.println(s/*.toString()*/);}System.out.println("--------------------");//valueOf:通过对象名字获取这个枚举对象//注意:对象的名字必须传正确,否则抛出异常Season autumn1 = Season.valueOf("AUTUMN");System.out.println(autumn1);}
}

注意:
1.为什么在很多源码中看到别人定义的枚举类形态特别简单:因为这个枚举类底层没有属性,构造器,toString,get方法都删掉不写了,然后案例应该写为:SPRING() 现在连括号()也可以省略,就变成SPRING。看到的形态就剩:常量名(对象名)

public enum Season {
    SPRING,SUMMER,AUTUMN,WINTER;
}

3.3 枚举的应用

定义一个Person类,类中又一个属性是性别Sex,那么这个sex属性由于只有男和女两种选择而不能随意,那么就可以用枚举类来限定Person的set方法。

class Person {
    //属性:private int age;private String name;private Gender sex;public Gender getSex() {
    return sex;}public void setSex(Gender sex) {
    this.sex = sex;}
}
enum Gender {
    ,;
}
public class Test {
    //这是一个main方法,是程序的入口:public static void main(String[] args) {
    Person p = new Person();p.setAge(19);p.setName("lili");p.setSex(Gender.);//传入枚举类Gender的对象:-->在入口处对参数进行了限制System.out.println(p);//还可以通过枚举结合switch处理:Gender sex = Gender.;//switch后面的()中可以传入枚举类型//switch后面的():int,short,byte,char,String ,枚举switch (sex){
    case:System.out.println("是个女孩");break;case:System.out.println("是个男孩");break;}}
}

4 反射

4.1 为什么要用反射

案例引入:美团外卖付款实现(要么用微信支付 要么用支付宝支付 )

//接口的制定方:美团外卖
public interface Mtwm {
    //在线支付功能:void payOnline();
}
class WeChat implements Mtwm{
    @Overridepublic void payOnline() {
    //具体实现微信支付的功能:System.out.println("我已经点了外卖,正在使用微信支付");}
}
class AliPay implements Mtwm {
    @Overridepublic void payOnline() {
    //具体的支付宝支付:System.out.println("我已经点了外卖,我正在使用支付宝进行支付");}
}

最一般的实现

public class Test {
    public static void main(String[] args) {
    //定义一个字符串,用来模拟前台的支付方式:String str = "微信";if("微信".equals(str)){
    //str.equals("微信")---?避免空指针异常//微信支付://new WeChat().payOnline();pay(new WeChat());}if("支付宝".equals(str)){
    //支付宝支付://new AliPay().payOnline();pay(new AliPay());}if("招商银行".equals(str)){
    pay(new BankCard());}}//微信支付public static void pay(WeChat wc){
    wc.payOnline();}//支付宝支付public static void pay(AliPay ap){
    ap.payOnline();}//招商银行支付public static void pay(BankCard bc){
    bc.payOnline();}
}

发现要写的方法很多,代码冗余。这个时候可以用多态来简化

public class Test {
    public static void main(String[] args) {
    //定义一个字符串,用来模拟前台的支付方式:String str = "微信";if("微信".equals(str)){
    //str.equals("微信")---?避免空指针异常//微信支付:pay(new WeChat());}if("支付宝".equals(str)){
    //支付宝支付:pay(new AliPay());}if("招商银行".equals(str)){
    pay(new BankCard());}}//方法形参是接口,具体传入的是接口的实现类的对象--->多态的一种形式public static void pay(Mtwm m){
    m.payOnline();}
}

多态确实可以提高代码的扩展性,但是:扩展性没有达到最好。
怎么没有达到最好:上面的if分支,还是需要手动的删除或者添加,如果假如同时来了50个付款方式,那就要加50个if。
解决办法:反射机制
利用反射实现上述功能:

public class Demo {
    public static void main(String[] args) throws Exception {
    //定义一个字符串,用来模拟前台的支付方式:String str = "com.jayden.test01.AliPay";  //字符串:实际上:就是微信类的全限定路径//下面的代码就是利用反射:Class  cls = Class.forName(str);//cls-->Class类具体的对象--》AliPay字节码信息Object o = cls.newInstance();Method method = cls.getMethod("payOnline");method.invoke(o);}
}

上述代码不管又多少家银行来了,这个类中的逻辑代码都不用变了,要变的只有一个字符串接口。

4.2 反射的概念

  • JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制
  • 在编译后产生字节码文件的时候,类加载器子系统通过二进制字节流,负责从文件系统加载class文件。在执行程序(java.exe)时候,将字节码class文件读入JVM中(即类的加载)。这个时候JVM会在内存中对应创建一个java.lang.Class对象,这个对象也会被放入字节码信息中,这个Class对象,就对应加载那个字节码信息,这个对象将被作为程序访问方法区中的这个类的各种数据的外部接口。
    所以:我们可以通过这个对象看到类的结构,这个对象就好像是一面镜子,透过镜子看到类的各种信息,我们形象的称之为反射。
  • 这种“看透”class的能力(the ability of the program to examine itself)被称为introspection(内省、内观、反省)。Reflection和introspection是常被并提的两个术语。

说明:在运行期间,如果我们要产生某个类的对象,Java虚拟机(JVM)会检查该类型的Class对象是否已被加载。
如果没有被加载,JVM会根据类的名称找到.class文件并加载它。一旦某个类型的Class对象已被加载到内存,就可以用它来产生该类型的所有对象。

补充:动态语膏vs静态语言
1、动态语言
是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。
主要动态语言: Object-C、 C#、JavaScript、 PHP、 Python、 Erlang 。
2、静态语言
与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。
所以Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。Java的动态性让编程的时候更加灵活!
单元测试、注解、枚举、反射(5)JavaSE

4.3 获取字节码信息的4种方式

//举例要用的类Person
class Person {
    //属性private int age;public String name;//方法private void eat(){
    System.out.println("Person---eat");}public void sleep(){
    System.out.println("Person---sleep");}
}
public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
    //案例:以Person的字节码信息为案例//方式1:通过getClass()方法获取Person p = new Person();Class c1 = p.getClass();System.out.println(c1);//方式2:通过内置class属性:Class c2 = Person.class;System.out.println(c2);System.out.println(c1==c2);//注意:方式1和方式2不常用,因为既然都已经又Person类了,就可以直接用Person类的对象来使用里面的属性和方法,没有必要多此一举。//方式3:用的最多:调用Class类提供的静态方法forNameClass c3 = Class.forName("com.zhaoss.test02.Person");//方式4:利用类的加载器(了解技能点)ClassLoader loader = Test.class.getClassLoader();Class c4 = loader.loadClass("com.zhaoss.test02.Person");}
}

4.4 Class类的具体的实例

  • Calss类的具体实例可以有:外部类,内部类、接口、注解、数组、基本数据类型、void。
public class Demo {
    public static void main(String[] args) {
    /*Class类的具体的实例:(1)类:外部类,内部类(2)接口(3)注解(4)数组(5)基本数据类型(6)void*/Class c1 = Person.class;Class c2 = Comparable.class;Class c3 = Override.class;int[] arr1 = {
    1,2,3};Class c4 = arr1.getClass();int[] arr2 = {
    5,6,7};Class c5 = arr2.getClass();System.out.println(c4==c5);//结果:true .同一个维度,同一个元素类型,得到的字节码就是同一个Class c6 = int.class;Class c7 = void.class;}
}

4.5 通过Class类来获取运行时类的完整对象

举例要用的类:

//作为一个父类
public class Person implements Serializable {
    //属性private int age;public String name;//方法private void eat(){
    System.out.println("Person---eat");}public void sleep(){
    System.out.println("Person---sleep");}
}
//Student作为子类
@MyAnnotation(value="hello")
public class Student extends Person implements MyInterface{
    //属性:private int sno;//学号double height;//身高protected double weight;//体重public double score;//成绩//方法:@MyAnnotation(value="himethod")public String showInfo(){
    return "我是一名三好学生";}public String showInfo(int a,int b){
    return "重载方法====我是一名三好学生";}private void work(){
    System.out.println("我以后会找工作--》成为码农 程序员 程序猿 程序媛");}void happy(){
    System.out.println("做人最重要的就是开心每一天");}protected int getSno(){
    return sno;}//构造器public Student(){
    System.out.println("空参构造器");}private Student(int sno){
    this.sno = sno;}Student(int sno,double weight){
    this.sno = sno;this.weight = weight;}protected Student(int sno,double height,double weight){
    this.sno = sno;}@Override@MyAnnotation(value="hellomyMethod")public void myMethod() {
    System.out.println("我重写了myMethod方法。。");}@Overridepublic String toString() {
    return "Student{" +"sno=" + sno +", height=" + height +", weight=" + weight +", score=" + score +'}';}
}/* @Target:定义当前注解能够修饰程序中的哪些元素 @Retention:定义注解的声明周期*/
@Target({
    TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value();//属性
}public interface MyInterface {
    //自定义的接口//随便定义一个抽象方法:void myMethod();
}

1)获取构造器和创建对象

public class Test {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    //获取字节码信息:Class cls = Student.class;//通过字节码信息可以获取构造器://getConstructors只能获取当前运行时类的被public修饰的构造器Constructor[] c1 = cls.getConstructors();for(Constructor c:c1){
    System.out.println(c);}System.out.println("-------------------");//getDeclaredConstructors:获取运行时类的全部修饰符的构造器Constructor[] c2 = cls.getDeclaredConstructors();for(Constructor c:c2){
    System.out.println(c);}System.out.println("-------------------");//获取指定的构造器://得到空构造器Constructor con1 = cls.getConstructor();System.out.println(con1);//得到两个参数的有参构造器:Constructor con2 = cls.getConstructor(double.class, double.class);System.out.println(con2);//得到一个参数的有参构造器:并且是private修饰的Constructor con3 = cls.getDeclaredConstructor(int.class);System.out.println(con3);//有了构造器以后我就可以创建对象:Object o1 = con1.newInstance();System.out.println(o1);Object o2 = con2.newInstance(180.5, 170.6);System.out.println(o2);}
}

2)获取属性和对属性进行赋值

public class Test {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
    //获取运行时类的字节码信息:Class cls = Student.class;//获取属性://getFields:获取运行时类和父类中被public修饰的属性Field[] fields = cls.getFields();for(Field f:fields){
    System.out.println(f);}System.out.println("---------------------");//getDeclaredFields:获取运行时类中的所有属性Field[] declaredFields = cls.getDeclaredFields();for(Field f:declaredFields){
    System.out.println(f);}System.out.println("---------------------");//获取指定的属性:Field score = cls.getField("score");System.out.println(score);Field sno = cls.getDeclaredField("sno");System.out.println(sno);System.out.println("---------------------");//属性的具体结构://获取修饰符,返回的是一个int类型的数,打开modifier源码后可以看到数字对应的字符串,如private就是2,static是8,那么修饰为private static就是10。然后它的ToString方法可以将这个数字转化为对应修饰符,所以我们可以利用这个方法获得对应属性的修饰符/*int modifiers = sno.getModifiers();System.out.println(modifiers);System.out.println(Modifier.toString(modifiers));*/System.out.println(Modifier.toString(sno.getModifiers()));//获取属性的数据类型:Class clazz = sno.getType();System.out.println(clazz.getName());//获取属性的名字:String name = sno.getName();System.out.println(name);System.out.println("-------------------------------");//给属性赋值:(给属性设置值,必须要有对象)Field sco = cls.getField("score");Object obj = cls.newInstance();sco.set(obj,98);//给obj这个对象的score属性设置具体的值,这个值为98System.out.println(obj);}
}

3)获取方法和调用方法

public class Test {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
    //获取字节码信息:Class cls = Student.class;//获取方法://getMethods:获取运行时类的方法还有所有父类中的方法(被public修饰)Method[] methods = cls.getMethods();for(Method m:methods){
    System.out.println(m);}System.out.println("-----------------------");//getDeclaredMethods:获取运行时类中的所有方法:Method[] declaredMethods = cls.getDeclaredMethods();for(Method m:declaredMethods){
    System.out.println(m);}System.out.println("-----------------------");//获取指定的方法:Method showInfo1 = cls.getMethod("showInfo");System.out.println(showInfo1);Method showInfo2 = cls.getMethod("showInfo", int.class, int.class);System.out.println(showInfo2);Method work = cls.getDeclaredMethod("work",int.class);System.out.println(work);System.out.println("-----------------------");//获取方法的具体结构:/*@注解修饰符 返回值类型 方法名(参数列表) throws XXXXX{}*///名字:System.out.println(work.getName());//修饰符:int modifiers = work.getModifiers();System.out.println(Modifier.toString(modifiers));//返回值:System.out.println(work.getReturnType());//参数列表:Class[] parameterTypes = work.getParameterTypes();for(Class c:parameterTypes){
    System.out.println(c);}//获取注解:Method myMethod = cls.getMethod("myMethod");Annotation[] annotations = myMethod.getAnnotations();for(Annotation a:annotations){
    System.out.println(a);}//获取异常:Class[] exceptionTypes = myMethod.getExceptionTypes();for(Class c:exceptionTypes){
    System.out.println(c);}//调用方法:Object o = cls.newInstance();myMethod.invoke(o);//调用o对象的mymethod方法System.out.println(showInfo2.invoke(o,12,45));;}
}

4)获取类的接口,所在包,注解

public class Test {
    public static void main(String[] args) {
    //获取字节码信息:Class cls = Student.class;//获取运行时类的接口:Class[] interfaces = cls.getInterfaces();for(Class c:interfaces){
    System.out.println(c);}//得到父类的接口://先得到父类的字节码信息:Class superclass = cls.getSuperclass();//得到接口:Class[] interfaces1 = superclass.getInterfaces();for(Class c:interfaces1){
    System.out.println(c);}//获取运行时类所在的包:Package aPackage = cls.getPackage();System.out.println(aPackage);System.out.println(aPackage.getName());//获取运行类的注解:Annotation[] annotations = cls.getAnnotations();for(Annotation a:annotations){
    System.out.println(a);}}
}

4.6 反射面试题

  • 创建Person的对象,以后用new Person()创建,还是用反射创建?
    如果某个对象,只有在程序运行时才可以动态的知道具体时哪一个对象时,这个时候就用反射。
  • 反射是否破坏了面向对象的封装性?
    封装性和反射各自解决的是不同的问题,事实上,反射确实是具有可以访问private修饰的属性的,但是这仅仅是提供了这种方法。不建议使用。举个例子:在厕所上有男生女生的标识,但是如果真有变态,那也就是可以进入与自己性别不符的门。但不能因为有变态的存在,直接不在厕所上贴男生女生的标识。