23_反射

反射

能够分析类能力的程序称为反射

  • 在运行中分析类的能力
  • 在运行中查看对象, 例如, 编写一个toString方法供所有类使用
  • 实现通用的数组操作代码
  • 利用Method对象, 这个对象很像C++中的函数指针

反射是一种功能强大且复杂的机制. 使用它的主要人员是工具构造者, 而不是应用程序员.

一. 什么是类对象

  • 类的对象: 基于某个类new出来的对象, 也称为实例对象
  • 类对象: 类加载的产物, 封装了一个类的所有信息(类名, 父类, 接口, 属性, 方法, 构造方法)

二. 获取类对象的方法

  1. 通过类的对象, 获取类对象
    • Student s = new Student();
    • Class c = s.getClass();
  2. 通过类名获取类对象
    • Class c = 类名.class;
  3. 通过静态方法获取类对象[推荐使用]
    • Class = Class.forName("包名.类名");
//显示类的加载过程, 在Run下的Edit Configurations下配置 -verbose:class: 
public class TestClass {

    public static void main(String[] args) throws Exception{

        getClazz();
    }
    public static void getClazz() throws Exception{
        //1. 通过类的对象, 获取类对象
        Person p1 = new Person();
        Class<?> class1 = p1.getClass();
        //System.out.println(class1.toString());
        System.out.println(class1.hashCode());

        //2. 通过类名获取类对象
        Class<?> class2 = Person.class;
        System.out.println(class2.hashCode());

        //3. 通过静态方法获取类对象[推荐使用]
        Class<?> class3 = Class.forName("com.reflex.demo01.Person");
        System.out.println(class3.hashCode());

    }
}

三. 常见操作

1. 常用方法

	public String getName()
	public Package getPackage()
	public Class<? super T> getSuperclass()
	public Class<?>[] getInterfaces()
	public Constructor<?>[] getConstructors()
	public T newInstance()
	public Method[] getMethods()
	public Field[] getField()

2. 常见操作

public class Demo01 {

    public static void main(String[] args) throws Exception{

        reflect1();
        reflect2();
        reflect3();

        Properties properties = new Properties();
        //properties.setProperty("name", "zhangsan");
        //System.out.println(properties.toString());
        invokeAny(properties, "setProperty", new Class[]{String.class, String.class}, "username", "张三");
        System.out.println(properties.toString());

        reflect4();
    }

    //1. 使用反射获取类的名字, 包名, 父类, 接口
    public static void reflect1() throws Exception{
        //(1) 获取类对象Person
        Class<?> class1 = Class.forName("com.reflex.demo01.Person");
        //getName() 获取类名
        System.out.println(class1.getName());//com.reflex.demo01.Person
        //getPackage() 获取包名
        System.out.println(class1.getPackage().getName());//com.reflex.demo01
        //getSuperClass() 获取父类
        System.out.println(class1.getSuperclass().getName());//java.lang.Object
        //getInterfaces() 获取接口
        Class<?>[] classes = class1.getInterfaces();
        System.out.println(Arrays.toString(classes));//[interface java.io.Serializable, interface java.lang.Cloneable]
        System.out.println(class1.getSimpleName());//Person 只获取类名,不要前缀
        System.out.println(class1.getTypeName());//com.reflex.demo01.Person 获取类名
    }

    //2. 使用反射获取类的构造方法, 创建对象
    public static void reflect2() throws Exception{
        //(1) 获取类的类对象
        Class<?> class2 = Class.forName("com.reflex.demo01.Person");
        //(2) 获取类的所有构造方法Constructor
//        Constructor<?>[] cons = class2.getConstructors();
//        for (Constructor<?> con : cons) {
//            System.out.println(con.toString());
//        }
        //(3) 获取类中无参构造方法
        Constructor<?> con1 = class2.getConstructor();
        Person zhangsan = (Person)con1.newInstance();
        System.out.println(zhangsan.toString());
        Person lisi = (Person)con1.newInstance();
        System.out.println(lisi.toString());
        //简便方法: 类对象.newInstance
        Person wangwu = (Person)class2.newInstance();
        System.out.println(wangwu.toString());

        //(4) 获取类中带参构造方法
        Constructor<?> con2 = class2.getConstructor(String.class, int.class);
        Person xiaoli = (Person)con2.newInstance("小李", 17);
        System.out.println(xiaoli.toString());
    }

    //3. 使用反射获取类中的方法, 并调用方法
    public static void reflect3() throws Exception{
        //(1) 获取类对象
        Class<?> class3 = Class.forName("com.reflex.demo01.Person");
        //(2) 获取方法Method对象
        //2.1 getMethods() 只能获取公开的方法, 包括从父类继承过来的方法
        //Method[] methods = class3.getMethods();
        //2.2 getDeclaredMethods() 获取类中所有的方法, 包括私有, 默认, 保护的, 不包含继承的方法
        Method[] methods = class3.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method.toString());
        }
        //(3) 获取单个方法
        //3.1 run 无参方法
        Method runMethod = class3.getMethod("run");
        //调用方法
        //正常调用 对象.run();
        Person lily = (Person) class3.newInstance();
        runMethod.invoke(lily);
        System.out.println("--------------------------");
        //3.2 toString 有返回值
        Method toStringMethod = class3.getMethod("toString");
        Object result = toStringMethod.invoke(lily);
        System.out.println(result);
        System.out.println("---------------------------");
        //3.3 eat 带参方法
        Method eatMethod = class3.getMethod("eat", String.class);
        eatMethod.invoke(lily, "苹果");
        System.out.println("--------------------------");

        //3.4 私有方法
        Method privateMethod = class3.getDeclaredMethod("privateMethod");
        //设置访问权限无效
        privateMethod.setAccessible(true);
        privateMethod.invoke(lily);
        System.out.println("--------------------------");

        //3.5 静态方法
        Method staticMethod = class3.getMethod("staticMethod");
        //正常调用 Person.staticMethod
        staticMethod.invoke(null);

    }

    //4. 使用反射实现一个可以调用任何对象方法的通用方法
    public static Object invokeAny(Object obj, String methodName, Class<?>[] types, Object...args) throws Exception{

        //1. 获取类对象
        Class<?> classAll = obj.getClass();
        //2. 获取方法
        Method method = classAll.getMethod(methodName, types);
        //3. 调用
        return method.invoke(obj, args);

    }

    //5. 使用反射获取类中的属性
    public static void reflect4() throws Exception{

        //(1) 获取类对象
        Class<?> class4 = Class.forName("com.reflex.demo01.Person");
        //(2) 获取属性(字段) 公开的字段, 父类继承的字段
        //Field[] fields = class4.getFields();//0 此方法无法获取字段
        //getDeclaredFields() 获取所有的属性, 包括私有, 默认, 保护的, 不包含继承的属性
//        Field[] fields = class4.getDeclaredFields();
//        System.out.println(fields.length);
//        for (Field field : fields) {
//            System.out.println(field.toString());
//        }
        //(3) 获取name属性
        Field nameField = class4.getDeclaredField("name");
        nameField.setAccessible(true);
        //(4) 赋值 正常调用是Person person = new Person(); person.name = "张三";
        Person person = (Person)class4.newInstance();
        nameField.set(person, "张三");// person.setName = "张三";
        //(5) 获取值
        System.out.println(nameField.get(person));// person.getName();

    }

}

四. 设计模式介绍

  • 什么是设计模式
    • 一套被反复使用, 多数人知晓的, 经过分类编目的, 代码设计经验的总结. 简单理解: 特定问题的固定解决方法
  • 好处:
    • 使用设计模式是为了可重用代码, 让代码更容易被他人理解, 保证代码可靠性, 重用性
  • 在Gof的<<设计模式>>书中描述了23种设计模式

五. 工厂设计模式

  • 工厂模式主要负责对象创建的问题
  • 开发中有一个非常重要的原则"开闭原则", 对拓展开放, 对修改关闭
  • 可通过反射进行工厂模式的设计, 完成动态的对象创建

下面是工厂模式的示例

示例说明: 假设有一个机器上有很多Usb接口, 外部设备通过插入Usb接口就可以运行,比如, 鼠标键盘电风扇等等, 用反射实现添加新的产品无需修改工厂类

  1. 首先创建一个Usb接口
public interface Usb {

    void service();
}
  1. 创建一个鼠标一个键盘类, 且都实现Usb接口
public class Mouse implements Usb{//鼠标类
    @Override
    public void service() {
        System.out.println("鼠标开始工作了...");
    }
}
public class KeyBoard implements Usb{//键盘类
    @Override
    public void service() {
        System.out.println("键盘开始工作了");
    }
}
  1. 创建工厂类
public class UsbFactory {
    public static Usb creatUsb(String type){
        Usb usb = null;
        Class<?> class1;
        try {
            class1 = Class.forName(type);// 获取类对象
            usb = (Usb)class1.newInstance();// 实例化类对象, 就可以调用接口内的方法了
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        return usb;
    }
}
  1. 创建usb.properties文件
1 = com.reflex.demo03.Mouse
2 = com.reflex.demo03.KeyBoard
  1. 创建测试文件
public class Demo {
    public static void main(String[] args) throws Exception{
        System.out.println("-----------请选择1鼠标, 2键盘--------");
        //输入要使用的设备
        Scanner input = new Scanner(System.in);
        String choice = input.next();
        //1 = com.reflex.demo03.Mouse
        //2 = com.reflex.demo03.KeyBoard
        // 我们需要把客户输入的数字1, 2转换为为其产品对应的类名
        //创建Properties对象
        Properties properties = new Properties();
        //创建文件字节流
        FileInputStream fis = new FileInputStream("D:\\JavaSE\\src\\com\\reflex\\demo03\\usb.properties");
        //通过properties加载文件(键值对)
        properties.load(fis);
        fis.close();

        //调用工厂类里的方法, 参数是properties通过键(1, 2)得到的值(com.reflex.demo03.Mouse, com.reflex.demo03.KeyBoard),也就是类名
        Usb usb = UsbFactory.creatUsb(properties.getProperty(choice));
        if (usb != null){
            System.out.println("接入成功");
            usb.service();// 调用方法
        }else{
            System.out.println("接入失败");
        }
    }
}

六. 单例设计模式

  • 单例(Singleton): 只允许创建一个该类的对象

方式1: 饿汉式(类加载时创建, 天生线程安全)

  • 优点: 线程安全
  • 缺点: 生命周期太长, 浪费内存空间
public class SingleTon {

    //1. 创建一个常量
    private static final SingleTon instance = new SingleTon();
    //2. 私有构造方法
    private SingleTon() {
    }
    //3. 通过公开方法返回这个对象
    public static SingleTon getInstance(){

        return instance;
    }

}

方式2: 懒汉式(使用时创建, 线程不安全, 加同步)

  • 优点: 生命周期短, 节省内存空间
  • 缺点: 有线程安全问题 解决办法: 使用同步方法或同步代码块
public class SingleTon2 {

    //1. 创造一个对象, 赋值为null
    private static SingleTon2 instance = null;
    //2.私有构造方法
    private SingleTon2() {}
    //3. 通过一个公开的方法, 返回这个对象, 并加同步
    public static SingleTon2 getInstance(){
        if (instance == null){//提高执行效率
            synchronized (SingleTon2.class){
                if (instance == null){
                    instance = new SingleTon2();
                }
            }
        }
        return instance;
    }
}

方式3: 懒汉式(使用时创建, 线程安全)

  • 使用静态内部类的方式
public class SingleTon3 {

    //1. 私有构造方法
    private SingleTon3() {}

    //2. 创建静态内部类(不使用时不执行)
    private static class Holder {
        static SingleTon3 instance = new SingleTon3();
    }

    //3.通过公开的方法, 返回静态内部类里的静态对象
    public static SingleTon3 getInstance() {
        return Holder.instance;
    }

}

枚举

  • 什么是枚举
    • 枚举是一个引用类型, 枚举是一个规定了取值范围的数据类型
  • 枚举变量不能使用其他的数据, 只能使用枚举中常量赋值, 提高程序安全性
  • 定义枚举使用enum关键字
  • 枚举的本质
    • 枚举是一个终止类, 并继承Enum抽象类
    • 枚举中常量是当前类型的静态常量
public enum Gender {//性别枚举
    MALE, FEMALE;

}
public enum Season {//季节枚举
    SPRING, SUMMER, AUTUMN, WINTER
}
public class TestGender {

    public static void main(String[] args) {

        Gender gender = Gender.MALE;
        System.out.println(gender.toString());//MALE

        Season season = Season.SPRING;
        System.out.println(season.toString());//SPRING
    }
}

枚举和switch语句的使用

public class demo01 {

    public static void main(String[] args) {

        //枚举和switch语句的使用
        Season season = Season.WINTER;
        switch (season) {// byte short int char string 枚举
            case SPRING:
                System.out.println("春天");
                break;
            case SUMMER:
                System.out.println("夏天");
                break;
            case AUTUMN:
                System.out.println("秋天");
                break;
            case WINTER:
                System.out.println("冬天");
                break;
            default:
                break;
        }
    }
}

注解

  • 什么是注解
    • 注解(Annotation)是代码里的特殊标记, 程序可以读取注解, 一般用于替代配置文件
  • 开发人员可以通过注解告诉类如何运行
    • 在Java技术里注解的典型应用是: 可以通过反射技术去得到类里面的注解, 以决定怎么去运行类
  • 常见注解: @Override, @Deprecated
  • 定义注解使用@interface关键字, 注解中只能包含属性
  • 注解的本质是接口

创建注解类型

public @interface MyAnnotation {
    //属性(类似方法)
    String name() default "张三";//默认值
    int age() default 20;//默认值
}
public class Person {

    @MyAnnotation
    public void show(){

    }
}
  • 注解属性类型
    • String
    • 基本数据类型
    • Class类型
    • 枚举类型
    • 注解类型
    • 以上类型的一维数组
public @interface MyAnnotation2 {
    //属性
    //字符串类型
    String value();
    //基本类型
    int num();
    //Class类型
    Class<?> class1();
    //枚举类型
    Gender GENDER();
    //注解类型
    MyAnnotation ANNOTATION();
    //ArrayList<String> list();
}
  • 元注解: 用来描述注解的注解
  • @Retention: 用于指定注解可以保留的域
    • RetentionPolicy.CLASS: 注解记录在class文件中, 运行Java程序时, JVM不会保留.这是默认值
    • RetentionPolicy.RUNTIME: 注解记录在class文件中, 运行Java程序时, JVM会保留, 程序可以通过反射获取该注解
    • RetentionPolicy.SOURCE: 编译时直接丢弃这种策略的注释
  • @Target: 指定注解用于修饰类的哪个成员
  1. 创建一个注解PersonInfo
@Retention(value = RetentionPolicy.RUNTIME)// 这样才能通过反射得到注解信息
@Target(value = {ElementType.METHOD})// 表示注解只能放在方法上面
public @interface PersonInfo {
    String name();
    int age();
    String sex();
}
  1. 创建一个Person类, 里面写一个show()方法, 方法上使用注解PersonInfo
public class Person {

    @PersonInfo(name = "张三", age = 19, sex = "男")
    public void show(String name, int age, String sex){
        System.out.println(name + "-----" + age + "-----" + sex);
    }


}
  1. 使用反射得到注解信息method.getAnnotation
public class Demo {

    public static void main(String[] args) throws Exception{

        //1. 获取类对象
        Class<?> class1 = Class.forName("com.reflex.demoAnnotation.Person");
        //2. 获取方法
        Method method = class1.getMethod("show", String.class, int.class, String.class);
        //3. 获取方法上面得注解信息
        PersonInfo personInfo = method.getAnnotation(PersonInfo.class);
        //4. 打印注解信息
        System.out.println(personInfo.name());// 张三
        System.out.println(personInfo.age());// 19
        System.out.println(personInfo.sex());// 男
        //5. 调用方法
        Person person = (Person)class1.newInstance();
        method.invoke(person, personInfo.name(), personInfo.age(), personInfo.sex());// 张三-----19-----男
    }
}
posted @ 2022-08-09 12:52  柒木木木  阅读(32)  评论(0编辑  收藏  举报