java基础第12期——反射、注解

一. 反射

反射: 将类的各个组成部分封装为其他对象.

1.1 获取class对象的方式

Class.forName("全类名"):
将字节码文件加载进内存,返回class对象
多用于配置文件,将类名定义在配置文件中,读取文件,加载类

类名.class: 
通过类名的属性class获取
多用于参数的传递

对象.getClass():
多用于对象的获取字节码的方式

注意:
以上三种方法获得的字节码文件地址相同
同一个字节码文件在一次程序运行中只会加载一次.

1.1.1 代码演示

示例person:

package reflecting;

public class demo00_person {

    public String name;
    protected String name1;
    String name2;
    private String name3;

    //成员方法
    public static void method1(){
        System.out.println("我是空参方法");
    }
    public static void method11(String s){
        System.out.println("我是有参方法"+s);
    }


    public demo00_person() {
    }

    public demo00_person(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "demo00_person{" +
                "name='" + name + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

使用:

public static void main(String[] args) throws ClassNotFoundException {//异常
        Class cl1 = Class.forName("reflecting.demo00_person");//从包名开始
        Class cl2 = demo00_person.class;
        Class cl3 = demo00_person.class;

        System.out.println(cl1 ==cl2);//true
        System.out.println(cl1==cl3);//true
   }

1.2 class对象的功能

1.2.1 获取

1.成员变量:
Filed[] getFields():  获取public修饰的成员变量
Filed getFiled(String name):  获取public修饰的指定名称的成员变量
Filed[] getDeclaredFields()
Filed getDeclaredField(String name)

2.构造方法:
Constructor<?>[] getConstructors()
Constructor<T> getConstructor(类<?>... parameterTypes)
Constructor<?>[] getDeclaredConstructors()
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)

3.成员方法:
Method getMethod(String name, 类<?>... parameterTypes),方法名和按声明顺序标识该方法形参类型
Method[] getMethods()
Method getDeclaredMethod(String name, 类<?>... parameterTypes)
method[] getDeclaredMethods()

4.类名
String getName()

1.2.2 使用

一.成员变量:Field对象操作:
1.设置值:
void set(Object obj, Object value)
2.获取值:
Object get(Object obj)
3.忽略访问权限修饰符的安全检查:
SetAccessible(true): 暴力反射

二. 构造方法Constructor对象
1.创建对象:
T newInstance(Object... initargs)
使用此Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。
2.如果创建空参数构造器,可以使用:
class.getDeclaredConstructor().newInstance()
getDeclaredConstructor()根据他的参数对该类的构造函数进行搜索并返回对应的构造函数,
没有参数就返回该类的无参构造函数,然后再通过newInstance进行实例化。
同样有SetAccessible(true)暴力反射.

三. 成员方法Method对象
1. 获取成员方法后,执行方法:
Object invoke(Object obj, Object... args), 传入调用该方法的类的对象和实参
同样有SetAccessible(true).
2. 获取方法名
String getName()

1.2.3 代码演示

public static void main(String[] args) throws Exception {
        //获取person的Class对象
        Class<demo00_person> p1 = demo00_person.class;
        //获取成员变量
        Field[] fields = p1.getFields();
        for (Field field : fields) {
            System.out.println(field);//person写了四个权限,只获取到public修饰的
        }
        //获取Field对象:名为name的成员变量
        Field name = p1.getField("name");
        //获取值,传入person对象
        demo00_person person = new demo00_person();
        Object value = name.get(person);//获得值,字符串默认值null

        //设置值
        name.set(person,"bronya");
        System.out.println(person);//打印结果name="bronya"

        System.out.println("---------");

        //忽略访问权限的安全检查
        Field dc = p1.getDeclaredField("name3");
        dc.setAccessible(true);//忽略权限
        Object o = dc.get(person);
        System.out.println(o);//null

        System.out.println("---获取构造方法---");
        Constructor<demo00_person> constructor = p1.getConstructor(String.class);
        System.out.println("构造器:"+constructor);
        //T newInstance(Object... initargs)方法创建对象
        demo00_person person1 = constructor.newInstance("板鸭");
        System.out.println("创建的对象:"+person);
        //创建空参数构造器
        demo00_person person2 = p1.getDeclaredConstructor().newInstance();
        System.out.println("空参数:"+person2);

        //获取成员方法,传入指定方法名,和形参类型
        Method method1 = p1.getMethod("method1");//空参的
        Method method11 = p1.getMethod("method11", String.class);//有参写法
        //执行方法,传入类对象和实参
        method1.invoke(p1);
        method11.invoke(p1,"666");
        //获取所有public修饰的成员方法
        Method[] methods = p1.getMethods();
        for (Method method : methods) {
            System.out.println(method);//还包括Object类的方法
            System.out.println(
                method.getName()
            );
        }
    }

1.2.4 框架应用

写个框架,创建任意类的对象,执行其中任意方法.
1.将要创建对象的全类名和需要执行的方法定义在配置文件中
2.在程序中加载配置文件
3.用反射技术加载类文件进内存
4.创建对象, 执行方法

src目录下新建一个properties配置文件:

className = reflecting.demo00_person
methodName = method11

使用:

public class demo03_Test {
    public static void main(String[] args) throws Exception {
        //加载配置文件,先创建Properties对象,用load方法加载
        Properties properties = new Properties();
        //获取该字节码文件对应的类加载器,
        ClassLoader classLoader = demo03_Test.class.getClassLoader();
        //调用getResourceAsStream()传入配置文件名称,获取字节输入流
        InputStream ras = classLoader.getResourceAsStream("pro.properties");
        //字节输入流给属性集合
        properties.load(ras);
        //获取配置文件中定义的数据
        String classname = properties.getProperty("className");
        String method = properties.getProperty("methodName");
        //加载该类进内存
        Class<?> aClass = Class.forName(classname);
        //创建类对象
        Object obj = aClass.getDeclaredConstructor().newInstance();
        //获取方法对象,传入方法名和参数
        Method method1 = aClass.getMethod(method,String.class);
        //执行方法,传入类对象和实参
        method1.invoke(obj,"111");
    }
}

二. 注解

2.1 定义

注解(Annotation),也叫元数据。一种代码级别的说明。
它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。
它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
作用分类:
①编写文档:通过代码里标识的元数据生成文档【生成文档doc文档】java doc命令
②代码分析:通过代码里标识的元数据对代码进行分析【使用反射】
③编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【Override】

2.2 jdk内置的注解

@Override: 检查该方法是否是继承自父类的
@Deprecated: 该注解标注的内容已经过时(但仍能使用)
@SuppressWarnings: 告诉编译器忽略指定的警告,不用在编译完成后出现警告信息。参数传入all表示全部

2.3 自定义注解

2.3.1 格式

元注解
public @interface 名称{}

注解本质是接口,继承java.lang.annotation.Annotation接口.

2.3.2 元注解

元注解是用于描述注解的注解

@Target: 表示注解能够作用的位置
        参数ElementType.
                TYPE: 表示注解可以作用到类上
                METHOD: 表示可以作用到方法
                FIELD: 可以作用于成员变量上
@Retention: 描述注解被保留的阶段
        参数RetentionPolicy.
                SOURCE: 不会保留到Class字节码文件中
                CLASS: 会保留到Class字节码文件中
          (常用) RUNTIME: 当前被描述的注解会保留到Class字节码文件中并被JVM读取到
@Documented: 描述注解是否被抽取到api文档中
@Inherited: 描述注解是否被子类继承

2.3.3 注解属性

注解里的抽象方法称作注解属性,

注解属性的要求:

1.返回值类型必须为之一: 基本数据类型\String\枚举\注解\以上类型的数组
2.使用时需要赋值, 或者定义属性时用default初始化默认值

2.3.4 代码演示

定义一个枚举类:

public enum meiju {
    p1,p2,p3;
}

定义一个注解类:

public @interface an0 {
}

自定义注解, 比如用到了枚举类和注解类:

@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
@Documented
@Inherited
public @interface an1 {
    //定义一个注解里的抽象方法,称注解的属性
    String mt3() default "111";
    int mt4();//基本数据类型
    String mt5();//String类型
    meiju mt6();//枚举类型
    an0 mt7();//注解类型
    String[] mt8();//数组类型
}

给注解赋值:

//压制该类全部警告
@SuppressWarnings("all")
public class demo00 {
	//表示已经过时
    @Deprecated
    public void mt1(){
        System.out.println("1");
    }
    //注意如何赋值:基本数据类型\String\枚举\注解\以上类型的数组
    @an1(mt4 = 1,mt5 = "1",mt6 = meiju.p1,mt7 = @an0,mt8 = {"1","2","a"})
    public void mt2(){
        mt1();
    }
}

2.4 解析注解

在程序中使用(解析)注解: 用来替换配置文件

步骤:
1. 获取注解定义的位置的对象
2. 获取指定的注解: getAnnotation(class) 方法
3. 调用注解的抽象方法获取配置的属性值

首先自定义一个注解:

//该注解用来描述需要执行的类名和方法名
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface demo01 {
    String className();
    String methodName();
}

我们要使用的类Annotation.demo00, 假如我们要使用其中的mt1()

//压制该类全部警告
@SuppressWarnings("all")
public class demo00 {
	//表示已经过时
    @Deprecated
    public void mt1(){
        System.out.println("1");
    }
    //注意如何赋值:基本数据类型\String\枚举\注解\以上类型的数组
    @an1(mt4 = 1,mt5 = "1",mt6 = meiju.p1,mt7 = @an0,mt8 = {"1","2","a"})
    public void mt2(){
        mt1();
    }
}

最后的具体操作:

@demo01(className = "Annotation.demo00",methodName = "mt1")
public class demo011 {
    public static void main(String[] args) throws Exception {
        //解析注解
        //获取该类字节码文件对象
        Class<demo011> cls = demo011.class;
        //获取注释对象
        demo01 an = cls.getAnnotation(demo01.class);//在内存中生成了该注解接口的子类实现对象
        //调用注解对象中定义的抽象方法获取返回值
        String className = an.className();
        String methodName = an.methodName();

        //加载该类进内存
        Class<?> aClass = Class.forName(className);
        //创建类对象
        Object obj = aClass.getDeclaredConstructor().newInstance();
        //获取方法对象,传入方法名和参数
        Method method1 = aClass.getMethod(methodName);
        //执行方法,传入类对象和实参
        method1.invoke(obj);
    }
}
posted @ 2020-12-10 12:30  崩天的勾玉  阅读(114)  评论(0编辑  收藏  举报