反射与注解

反射就是通过一个类名(字符串)对类进行加载,获取类的信息,并对该类创建对象调用方法等操作。

一、类对象

类对象是描述类的信息的对象。

包含类有哪些属性、方法、构造方法等。

有三种获取方式:

public class TestMain {
    public static void main(String[] args) {
        // 第一种,通过对象获取
        Student stu = new Student();
        Class c1 = stu.getClass();

        // 第二种,通过类名获取
        Class c2 = Student.class;

        // 第三种,通过Class.forName方法获取
        try {
            Class c3 = Class.forName("com.qf.day23.Student");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

二、反射的用法

当获得类对象后,可以获取该描述信息中的所有属性,方法,构造方法等信息,并且可以依据这些信息动态的创建对象,给属性赋值,调用方法。

2.1 反射操作属性
public class TestMain1 {
    public static void main(String[] args) {
        try {
            // 获取类对象信息
            Class c = Class.forName("com.qf.day23.Student");
            String name = c.getName();
            System.out.println(name); // 获取名称
            String simpleName = c.getSimpleName();
            System.out.println(simpleName); // 获取短名称
            System.out.println(c.getPackage()); // 获取包信息
            System.out.println(c.getSuperclass());// 获取父类信息
            System.out.println(Arrays.toString(c.getInterfaces()));// 获取接口信息
            // 通过无参构造方法创建对象
            Object obj = c.newInstance();
            // 获取所有属性
            Field[] fields = c.getDeclaredFields();
            for (Field field : fields) {
                System.out.println(field.getName()); // 打印属性的名称
                // 把属性的访问限制修改为允许访问
                field.setAccessible(true);
                // 给属性赋值
                // 如果属性的类型是String
                if(field.getType() == String.class) {
                    field.set(obj, "aaa");
                    // 如果属性类型是int
                }else if(field.getType() == int.class) {
                    field.set(obj, 20);
                }
            }
            // 获取单个属性
            Field field = c.getDeclaredField("age");
            System.out.println(field.getName());
            // 把属性的访问限制修改为允许访问
            field.setAccessible(true);
            // 得到该属性的值
            System.out.println(field.get(obj));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
2.2 反射操作方法
public class TestMain2 {
    public static void main(String[] args) {
        try {
            // 获取类对象信息
            Class c = Class.forName("com.qf.day23.Student");
            // 通过无参构造方法创建对象
            Object obj = c.newInstance();
            // 得到定义的所有方法
            Method[] methods = c.getDeclaredMethods();
            for (Method method : methods) {
                System.out.println(method.getName());
                // 判断是否以set开头
                if(method.getName().startsWith("set")) {
                    // 得到方法的参数
                    Class[] classes = method.getParameterTypes();
                    if(classes.length == 1 && classes[0] == String.class) {
                        // 调用方法
                        method.invoke(obj, "aaa");
                    }else if(classes.length == 1 && classes[0] == int.class) {
                        // 调用方法,指定参数值
                        method.invoke(obj, 18);
                    }
                }
            }
            // 得到某一个方法
            Method method = c.getDeclaredMethod("say"); // 得到方法say()
            method.invoke(obj);
            // 得到某一个方法,指定方法的参数类型
            Method method1 = c.getDeclaredMethod("say", String.class); // 得到方法say(String)
            method1.invoke(obj, "张三");
            Method method2 = c.getDeclaredMethod("say", String.class, int.class); // 得到方法say(String, int)
            method2.invoke(obj, "张三", 20);
            Method method3 = c.getDeclaredMethod("say", c); // 得到方法say(Student)
            method3.invoke(obj, obj);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
2.3 反射访问构造方法
public class TestMain3 {
    public static void main(String[] args) {
        try {
            // 获取类对象信息
            Class c = Class.forName("com.qf.day23.Student");
            // 只能调用系统无参构造方法(不推荐使用)
//            Object obj = c.newInstance();
//            System.out.println(obj);
            // 得到所有的构造方法
            Constructor[] constructors = c.getConstructors();
            for (Constructor constructor : constructors) {
                System.out.println(constructor.getParameterCount());
            }
            // 得到一个构造方法
            Constructor constructor = c.getConstructor(String.class, int.class, String.class);
            // 通过构造方法来创建对象
            Object object = constructor.newInstance("张三", 20, "男");
            System.out.println(object);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

三、设计模式

3.1 工厂模式

使用工厂来实现对象的创建。

3.1.1 简单工厂

工厂直接提供各种对象的创建。

public class CarFactory {
    // 创建汽车
    public static Car createCar() {
        Car car = new Car();
        car.setBrand("五菱宏光");
        car.setColor("蓝色");
        car.setId("1");
        return car;
    }

    // 提供其他对象的创建方法
}

public class TestMain {
    public static void main(String[] args) {
        Car car = CarFactory.createCar();
    }
}
3.1.2 抽象工厂

工厂类作为一个父类,定义了该工厂能够生产的对象,但是没有具体的实现,需要自己定义一个工厂的子类去生产相应的对象。

public abstract class AbstractFactory {
    // 生产汽车
    public abstract Car createCar();
    // 生产别的对象
}

public class MyFactory extends AbstractFactory{
    @Override
    public Car createCar() {
        Car car = new Car();
        car.setBrand("五菱宏光");
        car.setColor("蓝色");
        car.setId("1");
        return car;
    }
}

public class TestMain {
    public static void main(String[] args) {
        AbstractFactory factory = new MyFactory();
        Car car2 = factory.createCar();
    }
}
3.2 单例模式

Singleton,表示该类只能创建一个对象。例如上面的工厂对象,在项目中就只需要创建一个。

3.2.1 饿汉(eager)模式

一开始就创建对象(能创建对象就创建)。

缺点是加载时创建对象,比较慢,使用时比较快。

public class EagerSingleton {
    private static EagerSingleton instance = new EagerSingleton();

    // 构造方法私有
    private EagerSingleton() {
        System.out.println("创建对象");
    }

    public static EagerSingleton getInstance() {
        return instance;
    }
}
3.2.2 懒汉(lazy)模式

一开始不创建对象,到使用时才创建。

优点是加载时不创建对象,会比较快,第一次使用时创建对象,相对较慢。

public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {
        System.out.println("创建对象");
    }

    public static LazySingleton getInstance() {
        if(instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

上面的代码是有线程安全问题的,应该使用加锁的方式来解决线程安全问题。

public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {
        System.out.println("创建对象");
    }

    public static LazySingleton getInstance() {
        // 双重检测
        if(instance == null) {
            synchronized (LazySingleton.class) {
                if(instance == null) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

使用内部类来实现。

public class LazySingleton1 {
    private LazySingleton1() {
        System.out.println("创建对象");
    }

    // 只有加载内部类时才会创建对象,而getInstance方法中才去加载了内部类
    public static LazySingleton1 getInstance() {
        return Inner.instance;
    }

    private static class Inner{
        public static LazySingleton1 instance = new LazySingleton1();
    }
}

四、枚举

枚举是一个特殊的类,是 JDK1.5 之后才有,是 final 的,通常用来列举一些值。

4.1 枚举的基本使用

以下是一个使用常量的案例:

public interface HeroStatus {
    int BING_DONG = 1;
    int XUAN_YUN = 2;
    int CHEN_MO = 3;
}

public class Hero {
    public void setStatus(int status) {
        if(status == HeroStatus.BING_DONG) {
            System.out.println("英雄被冰冻");
        }else if(status == HeroStatus.CHEN_MO) {
            System.out.println("英雄被沉默");
        }else if(status == HeroStatus.XUAN_YUN) {
            System.out.println("英雄被眩晕");
        }
    }
}

public class TestMain {
    public static void main(String[] args) {
        Hero hero = new Hero();
        // 用户调用时,可以使用常量,但是用户也可以直接写数字1,甚至可以写数字5,都不会出现编译错误
        hero.setStatus(HeroStatus.BING_DONG);
    }
}

为了让用户调用时,如果不用指定的常量,会出现编译错误,推荐使用枚举。

public enum HeroStatus {
    BING_DONG,
    XUAN_YUN,
    CHEN_MO;
}

public class Hero {
    public void setStatus(HeroStatus status) {
        if(status == HeroStatus.BING_DONG) {
            System.out.println("英雄被冰冻");
        }else if(status == HeroStatus.CHEN_MO) {
            System.out.println("英雄被沉默");
        }else if(status == HeroStatus.XUAN_YUN) {
            System.out.println("英雄被眩晕");
        }
    }
}

public class TestMain {
    public static void main(String[] args) {
        Hero hero = new Hero();
        // 此时使用数字就会报错
        hero.setStatus(HeroStatus.BING_DONG);
    }
}
4.2 枚举的高级使用

枚举是一个特殊的类,所以也能定义属性方法等。

public enum HeroStatus {
    // 需要放到最前面
    BING_DONG("1", "冰冻"),  // 构造方法
    XUAN_YUN("2", "眩晕"),
    CHEN_MO("3", "沉默");

    private String name;
    private String desc;

    public void say() {
        System.out.println("name=" + name + ", desc=" + desc);
    }

    private HeroStatus(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }
}

public class Hero {
    public void setStatus(HeroStatus status) {
        status.say();
    }
}

public class TestMain {
    public static void main(String[] args) {
        Hero hero = new Hero();
        hero.setStatus(HeroStatus.CHEN_MO);
    }
}

五、注解

5.1 注解的概念

注解(Annotation),是在程序中的特殊标记,一般用来代替配置文件。

基本语法:

@类名,例如:@Override

定义方式:

public @interface 名称{

}

5.2 自定义注解

定义一个自动读取文件的注解。

工具类的定义:

@Target(ElementType.FIELD) // 表示该注解用在属性上
@Retention(RetentionPolicy.RUNTIME) // 表示该注解在运行时使用
public @interface ReadPath {
    // value表示默认属性,在赋值时,可以不写名称,如果是其他属性,赋值必须写名称
    String value() default "hello"; // 定义属性,给一个默认值
}


public class ReadFactory {
    public static Object createObject(String className) {
        try {
            Class c = Class.forName(className);
            Object obj = c.newInstance();
            // 读取txt中的属性
            Field[] fields = c.getDeclaredFields();
            for (Field field : fields) {
                // 判断属性上是否有注解@ReadPath
                if(field.isAnnotationPresent(ReadPath.class)) {
                    // 得到该注解
                    ReadPath readPath = field.getDeclaredAnnotation(ReadPath.class);
                    // 得到该注解的值
                    String path = readPath.value();
                    System.out.println(path);
                    // 判断值不为空
                    if(path != null && path.length() > 0) {
                        // 读取文件
                        String content = readContent(path);
                        field.setAccessible(true);
                        field.set(obj, content);
                    }
                }
            }
            return obj;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String readContent(String path) {
        try (
                BufferedReader br = new BufferedReader(new FileReader(path));
                ){
            StringBuilder builder = new StringBuilder();
            String str;
            while((str = br.readLine())!= null) {
                builder.append(str + "\n");
            }
            return builder.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

使用:

public class ReadTxt {
    @ReadPath("C:\\Users\\wangliang\\Desktop\\1.txt")
    private String content;

    public void read() {
        System.out.println(content);
    }
}


public class TestMain {
    public static void main(String[] args) {
        ReadTxt txt = (ReadTxt)ReadFactory.createObject("com.qf.day23.i.ReadTxt");
        txt.read();
    }
}
5.3 元注解

元注解:用在注解上的注解。

元数据:描述数据的数据。(一般指配置文件,xml 等)

自定义的注解上至少应该有以下元注解:

@Target(ElementType.METHOD):表示自定义的注解使用的位置(方法、属性、变量、类等)

@Retention(RetentionPolicy.SOURCE):表示自定义的注解使用的时机(源代码、运行时)

还有两个系统提供的元注解:

@Inherited:表示该注解是否可以继承(组合注解)

@Documented:表示该注解是否生成文档