day14-反射、注解

YangeIT大约 27 分钟JavaSe反射注解

day14-反射、注解

跳转到页底(每日作业)[1]

今日目标

  • 反射
    • 获取字节码方式 ❤️
    • 获取成员变量 🍐 成 员、构造、方法获取方式非常相似
    • 获取构造方法 🍐
    • 获取普通方法 🍐
  • 注解
    • 注解的作用 🍐
    • 解析注解 🚀

前置知识

  1. 知道.java文件通过javac命令 能编译成.class文件
  2. 知道类一般由成员变量、构造方法、普通方法组成
  3. 能理解配置文件的作用

一、反射 🚩 🍐 理解

学前交代

  1. 反射、动态代理、注解等知识点,在以后开发中极少用到。
  2. 这些技术都是以后学习框架、或者做框架的底层源码的。
  3. 讲解的目的是为了以后我们理解框架、或者自己开发框架架构师给别人用作铺垫的
  4. 接下来的学习的节奏是:先认识,在了解作用和场景

1️⃣ 1.1 反射入门案例之获得字节码对象和构造 🍐 ✏️

反射入门案例

反射技术 指的是加载类的字节码内存,并以编程的方法解刨出类中各个成分(成员变量、方法、构造器等)

  1. Java中反射技术用到的类在java.lang.reflect包中 1668575265599
  2. 用IDEA开发程序时,用对象调用方法,IDEA会有代码提示,idea会将这个对象能调用的方法都给你列举出来,供你选择。这个场景就用到了反射技术1668575796295
  3. 学习反射,主要学习通过反射技术获得: 1668576426355

由于Java的设计原则是万物皆对象,获取到的类其实也是以对象的形式体现的,叫做字节码对象

  • 字节码对象,用Class类来表示
  • 构造器对象,用Constructor类的对象来表示
  • 成员变量对象,用Field类的对象来表示
  • 成员方法对象,用Method类的对象来表示

代码操作

准备案例代码Cat类,接下来通过方法获得他的成员变量,成员方法,以及构造方法对象


public class Cat{
//    私有的成员变量
    private String name;
    private int age;

//    公开的成员变量
    public int sex;

    
//    公开的无参构造
    public Cat(){
        System.out.println("空参数构造方法执行了");
    }

//    私有的有参构造
    private Cat(String name, int age){
        System.out.println("有参数构造方法执行了");
        this.name=name;
        this.age=age;
    }

    //私有的无参普通方法
    private void run(){
        System.out.println("(>^ω^<)喵跑得贼快~~");
    }

    //公开的无参普通方法
    public void eat(){
        System.out.println("(>^ω^<)喵爱吃猫粮~");
    }

    //私有的有参普通方法
    private String eat(String name){
        return "(>^ω^<)喵爱吃:"+name;
    }

//  get 和set 以及toString方法省略...自行实现

}

总结

课堂作业

  1. 反射主学什么?🎤 点击查看open in new window

2️⃣ 1.2 反射入门案例之获得成员变量 🍐 ✏️

反射入门案例之获得成员变量

获取类的成员变量

反射的第三步:通过字节码文件对象获得成员变量对象

其实套路是一样的,在Class类中提供了获取成员变量的方法,如下图所示。 1668579517323

方法记忆技巧

  • getXX:获取什么
  • Constructor: 构造方法的意思
  • Declared:获得任意的,包含private的,没有这个单词表示只能获取一个public修饰的
  • 后缀s: 表示可以获取多个,没有后缀s只能获取一个

代码操作

获得成员变量对象的示例代码

public class Test3 {
    public static void main(String[] args) throws Exception {
//获得字节码文件
        Class<Cat> catClass = Cat.class;
//        通过字节码文件 获得全部的成员变量
        Field[] declaredFields = catClass.getDeclaredFields();
        System.out.println(Arrays.toString(declaredFields));

//     通过字节码文件 获得public成员变量
        Field[] fields = catClass.getFields();
        System.out.println(Arrays.toString(fields));
        
    }
}

总结

课堂作业

  1. 私有化成员变量可以修改值吗? 需要注意什么?🎤

3️⃣ 1.3 反射入门案例之获得成员方法 🍐 ✏️

反射入门案例之获得成员方法

获取类的成员方法

反射的第四步:通过字节码文件对象获得成员方法对象

在Java中反射包中,每一个成员方法用Method对象来表示,通过Class类提供的方法可以获取类中的成员方法对象。如下下图所示

1668580761089
1668580761089

方法记忆技巧

  • getXX:获取什么
  • Constructor: 构造方法的意思
  • Declared:获得任意的,包含private的,没有这个单词表示只能获取一个public修饰的
  • 后缀s: 表示可以获取多个,没有后缀s只能获取一个

代码操作

获得成员方法的示例代码 👇

public class Test4 {
    public static void main(String[] args) throws Exception {
//获得字节码文件
        Class<Cat> catClass = Cat.class;
//        通过字节码文件 获得指定的 无参 成员方法
         Method run = catClass.getDeclaredMethod("run");
//        通过字节码文件 获得指定的 有参 成员方法
        Method eat = catClass.getDeclaredMethod("eat",String.class);

//        调用方法需要对象,创建对象
         Cat cat = catClass.newInstance();

//         注意:2个方法都是private 需要取消安全检查
        run.setAccessible(true);
        eat.setAccessible(true);
//         调用方法
        run.invoke(cat);
        
//        eat方法有返回值
         Object ret = eat.invoke(cat, "欧力给");
        System.out.println(ret);


    }
}

运行截图:

总结

课堂作业

  1. 日志中有哪些信息?🎤

4️⃣ 1.4 反射应用 🍐 ✏️

反射应用

知识前提

  1. 能获得字节码文件对象
  2. 能通过字节码对象获得普通方法对象和私有方法(Method)对象
  3. 能通过字节码对象获得普通成员对象和私有成员(Field)对象
  4. 理解反射可以获得运行状态下的类的成员变量和成员方法,并可以调用成员方法和修改成员变量的值

温馨提示

各位小伙伴,按照前面我们学习反射的套路,我们已经充分认识了:

  1. 什么是反射,
  2. 反射的核心作用是用来获取类的各个组成部分并执行他们。
    • 但是由于小伙伴的经验有限,对于反射的具体应用场景还是很难感受到的(这个目前没有太好的办法,只能慢慢积累,等经验积累到一定程度,就会豁然开朗了)。
      • 下面会提供2个案例,一起来巩固下

代码操作

让我们写一个框架,能够将任意一个对象的属性名和属性值写到文件中去。不管这个对象有多少个属性,也不管这个对象的属性名是否相同。

1668583255686
1668583255686

需求分析

1.先写好两个类,一个Student类和Teacher2.写一个ObjectFrame类代表框本架
	在ObjectFrame类中定义一个saveObject(Object obj)方法,用于将任意对象存到文件中去
	参数:Object obj: 就表示要存入文件中的对象
	
3.编写方法内部的代码,往文件中存储对象的属性名和属性值
	1)参数obj对象中有哪些属性,属性名是什么实现值是什么,中有对象自己最清楚。
	2)接着就通过反射获取类的成员变量信息了(变量名、变量值)
	3)把变量名和变量值写到文件中去

1️⃣ 写一个ObjectFrame表示自己设计的框架,代码如下图所示

public class ObjectFrame{
    public static void saveObject(Object obj) throws Exception{
        //
        PrintStream ps = new PrintStream(new FileOutputStream("绝对地址\\data.txt",true));
        //1)参数obj对象中有哪些属性,属性名是什么实现值是什么,中有对象自己最清楚。
		//2)接着就通过反射获取类的成员变量信息了(变量名、变量值)
        Class c = obj.getClass(); //获取字节码
        ps.println("---------"+class.getSimpleName()+"---------");
        
        Field[] fields = c.getDeclaredFields(); //获取所有成员变量
		//3)把变量名和变量值写到文件中去
        for(Field field : fields){
            String name = field.getName();
            Object value = field.get(obj)+"";
            ps.println(name);
        }
        ps.close();
    }
}

使用自己设计的框架,往文件中写入Student对象的信息和Teacher对象的信息。

2️⃣ 先准备好Student类和Teacher类

public class Student{
    private String name;
    private int age;
    private char sex;
    private double height;
    private String hobby;
}
public class Teacher{
    private String name;
    private double salary;
}

3️⃣ 创建一个测试类,在测试中类创建一个Student对象,创建一个Teacher对象,用ObjectFrame的方法把这两个对象所有的属性名和属性值写到文件中去。

public class Test5Frame{
    @Test
    public void save() throws Exception{
        Student s1 = new Student("黑马吴彦祖",45, '男', 185.3, "篮球,冰球,阅读");
        Teacher s2 = new Teacher("播妞",999.9);
        
        ObjectFrame.save(s1);
        ObjectFrame.save(s2);
    }
}

4️⃣ 打开data.txt文件,内容如下图所示,就说明我们这个框架的功能已经实现了

1668584556229
1668584556229

课堂作业

🚩 1. 将上述的2个案例都完成以下

二、注解 🚩

学习目的

注解和反射一样,都是用来做框架的,我们这里学习注解的目的其实是为了以后学习框架或者做框架做铺垫的。

学习技巧:先认识注解,掌握注解的定义和使用格式,然后学习应用场景

1️⃣ 2.1 认识注解&定义注解

认识注解&定义注解

一、注解的定义和使用

注释:给程序员看的,了解程序的情况

注解:程序的标记,程序获取其中的信息,根据信息执行程序

  1. Java注解是代码中的特殊标记,比如@Override、等,作用是:让其他程序根据注解信息决定怎么执行该程序。 👈

  2. 注解不光可以用在方法上,还可以用在类上、变量上、构造器上等位置。 👈

用过的注解:

再比如:@Override注解可以用在方法上,用来标记这个方法是重写方法,被@Override注解标记的方法能够被IDEA识别进行语法检查。

image
image

自定义注解的格式如下图所示

1669604102185
1669604102185
  1. 格式属性类型
    • 基本数据类型
    • String
    • Class 字节码文件对象
    • 注解
    • 枚举 Enum
    • 以上类型的一维数组

注意:如果只有一个属性需要赋值,并且属性的名称是 value,则 value 可以省略,直接定义值即可(如下图)

代码操作

比如:现在我们自定义一个MyTest注解

public @interface MyTest{
    String aaa();
    boolean bbb() default true;	//default true 表示默认值为true,使用时可以不赋值。
    String[] ccc();
}

定义好MyTest注解之后,我们可以使用MyTest注解在类上、方法上等位置做标记。注意使用注解时需要加@符号,如下

@MyTest(aaa="牛魔王",ccc={"HTML","Java"})
public class AnnotationTest1{
    @MyTest(aaa="铁扇公主",bbb=false, ccc={"Python","前端","Java"})
    public void test1(){
        
    }
}

总结

课堂作业

  1. 注解的作用是什么吗?🎤
  2. 注解的本质是什么?🎤

2️⃣ 3.2 元注解 🍐

元注解

元注解是修饰注解的注解。这句话虽然有一点饶,但是非常准确。我们看一个例子

1669605746113
1669605746113

接下来分别看一下@Target注解和@Retention注解有什么作用,如下所示

  • @Target是用来声明注解只能用在那些位置,比如:类上、方法上、成员变量上等
  • @Retetion是用来声明注解保留周期,比如:源代码时期、字节码时期、运行时期
1669605786028
1669605786028

代码操作

  • @Target元注解的使用:比如定义一个MyTest3注解,并添加@Target注解用来声明MyTest3的使用位置
@Target(ElementType.TYPE)	//声明@MyTest3注解只能用在类上
public @interface MyTest3{
    
}

接下来,我们把@MyTest3用来类上观察是否有错,再把@MyTest3用在方法上、变量上再观察是否有错

1669606261919
1669606261919

如果我们定义MyTest3注解时,使用@Target注解属性值写成下面样子

//声明@MyTest3注解只能用在类上和方法上
@Target({ElementType.TYPE,ElementType.METHOD})	
public @interface MyTest3{
    
}

此时再观察,@MyTest用在类上、方法上、变量上是否有错

1669606451308
1669606451308

到这里@Target元注解的使用就演示完毕了。

总结

课堂作业

  1. 元注解有什么作用?🎤

3️⃣ 3.3 解析注解以应用 ✏️

解析注解以应用

知识储备

  1. 能够自己定义注解
  2. 能够把自己定义的注解标记在类上或者方法上等位置
  3. 也理解元注解的作用
  4. 也知道注解能给程序做标记,但是缺乏实战

解析注解:获取类上、方法上、变量上等位置注解及注解属性值

解析注解套路如下:

  1. 如果注解在类上,先获取类的字节码对象,再获取类上的注解
  2. 如果注解在方法上,先获取方法对象,再获取方法上的注解
  3. 如果注解在成员变量上,先获取成员变量对象,再获取变量上的注解

总之:注解在谁身上,就先获取谁,再用谁获取谁身上的注解

用到的方法:

1669607820853
1669607820853

代码操作

案例1 解析注解

解析来看一个案例,来演示解析注解的代码编写

1669607882128
1669607882128

先定义一个MyTest4注解

//声明@MyTest4注解只能用在类上和方法上
@Target({ElementType.TYPE,ElementType.METHOD})	
//控制使用了@MyTest4注解的代码中,@MyTest4保留到运行时期
@Retetion(RetetionPloicy.RUNTIME)
public @interface MyTest4{
    String value();
    double aaa() default 100;
    String[] bbb();
}

定义有一个类Demo

@MyTest4(value="蜘蛛侠",aaa=99.9, bbb={"至尊宝","黑马"})
public class Demo{
    @MyTest4(value="孙悟空",aaa=199.9, bbb={"紫霞","牛夫人"})
    public void test1(){
        
    }
}

2. 解析注解

③ 写一个测试类AnnotationTest3解析Demo类上的MyTest4注解

public class AnnotationTest3{
@Test
public void parseClass(){
    //1.先获取Class对象
    Class c = Demo.class;
    
    //2.解析Demo类上的注解
    if(c.isAnnotationPresent(MyTest4.class)){
        //获取类上的MyTest4注解
        MyTest4 myTest4 = (MyTest4)c.getDeclaredAnnotation(MyTest4.class);
        //获取MyTests4注解的属性值
        System.out.println(myTest4.value());
        System.out.println(myTest4.aaa());
        System.out.println(myTest4.bbb());
    }
}
    
@Test
public void parseMethods(){
    //1.先获取Class对象
    Class c = Demo.class;
    
    //2.解析Demo类中test1方法上的注解MyTest4注解
    Method m = c.getDeclaredMethod("test1");
    if(m.isAnnotationPresent(MyTest4.class)){
        //获取方法上的MyTest4注解
        MyTest4 myTest4 = (MyTest4)m.getDeclaredAnnotation(MyTest4.class);
        //获取MyTests4注解的属性值
        System.out.println(myTest4.value());
        System.out.println(myTest4.aaa());
        System.out.println(myTest4.bbb());
    }
}
}

课堂作业

🚩 1. 安装上述代码,完整的练习一遍,理解反射如何获得注解中的值?

学习代理,看这里!!!

代理见这里:

回顾整个javase阶段的重要知识

可以从问卷网的卷子入手open in new window

web阶段知识点

web阶段知识预告


  1. 页底标记 ↩︎