【java基础】反射

136 阅读8分钟

反射

一、反射的概念

通过一个类名或者对象去获取该类中的所有描述信息。

然后根据这些描述信息,去创建对象,调用方法,操作属性等。

二、类对象

2.1 概念

类在加载时,得到的对象,该对象包含类的基本信息(父类、接口、构造方法、属性、方法等)

2.2 获取方式

1、使用对象获取

Student stu = new Student();
// 通过对象获取类的信息(类对象)
Class c = stu.getClass();

2、直接通过类获取


// 直接通过类获取类对象
Class c1 = Student.class;

3、通过类的完全限定名(字符串)获取,一般此方案称为使用反射的方式获取。


// 通过完全限定名获取
try {
    Class c2 = Class.forName("com.exercise.Student");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}
2.3 基本使用

获得描述信息:


public class Test1 {
    public static void main(String[] args) {
        try {
            // 通过完全限定名获取
            Class c = Class.forName("com.exercise.Student");
            // 得到当前类名
            System.out.println(c.getName());
            // 得到包名
            System.out.println(c.getPackage().getName());
            // 得到简单类名
            System.out.println(c.getSimpleName());
            // 获得父类
            Class superclass = c.getSuperclass();
            System.out.println(superclass.getName());
            // 获得接口
            Class[] interfaces = c.getInterfaces();
            for (Class class1 : interfaces) {
                System.out.println(class1.getName());
            }
            // 获得构造方法
            Constructor[] constructors = c.getDeclaredConstructors();
            for (Constructor constructor : constructors) {
                // 构造方法名称
                System.out.println(constructor.getName());
                // 访问修饰符
                System.out.println(constructor.getModifiers());
                // 参数数量
                System.out.println("参数数量为:" + constructor.getParameterCount());
                // 参数类型
                Class[] parameterTypes = constructor.getParameterTypes();
                for (Class c2 : parameterTypes) {
                    System.out.println("参数类型:" + c2.getName());
                }
            }
            // 得到所有属性
            Field[] fields = c.getDeclaredFields();
            for (Field field : fields) {
                System.out.println("属性的名称:" + field.getName());
                System.out.println("属性的类型:" + field.getType().getName());
                System.out.println("属性的访问修饰符:" + field.getModifiers());
            }
            // 得到所有方法:
            Method[] methods = c.getDeclaredMethods();
            for (Method method : methods) {
                System.out.println("方法的名称:" + method.getName());
                System.out.println("方法的返回值类型:" + method.getReturnType().getName());
                System.out.println("方法的访问修饰符:" + method.getModifiers());
                // 参数数量
                System.out.println("方法参数数量为:" + method.getParameterCount());
                // 参数类型
                Class[] parameterTypes = method.getParameterTypes();
                for (Class c2 : parameterTypes) {
                    System.out.println("方法参数类型:" + c2.getName());
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

注意:getFields和getConstructors方法只能获得公有的方法和属性,如果要获得私有的,需要使用getDeclaredFields和getDeclaredConstructors。

操作:创建对象,设置属性,调用方法

创建对象:


public class Test2 {
    public static void main(String[] args) {
        try {
            // 通过完全限定名获取
            Class c = Class.forName("com.exercise.Student");
            // 创建对象(要求必须有无参构造方法)
            // 1、通过无参构造
            Object obj = c.newInstance();
            System.out.println(obj);
            // 2、使用无参构造方法
            Constructor constructor = c.getDeclaredConstructor();
            Object obj1 = constructor.newInstance();
            System.out.println(obj1);
            // 3、使用有参构造方法
            Constructor constructor1 = c.getDeclaredConstructor(String.class, String.class, int.class);
            Object obj2 = constructor1.newInstance("1001", "张三", 18);
            System.out.println(obj2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

操作属性:


public class Test3 {
    public static void main(String[] args) {
        try {
            // 通过完全限定名获取
            Class c = Class.forName("com.exercise.Student");
            // 创建对象(要求必须有无参构造方法)
            // 通过无参构造
            Object obj = c.newInstance();
            // 给属性设置值
            // 得到id属性
            Field field1 = c.getDeclaredField("id");
            // 临时获取访问权限(此方式不推荐,违背封装原理,建议调用set方法)
            field1.setAccessible(true);
            // 设置id属性
            field1.set(obj, "1001");
            // 得到name属性
            Field field2 = c.getDeclaredField("name");
            // 临时获取访问权限(此方式不推荐,违背封装原理,建议调用set方法)
            field2.setAccessible(true);
            // 设置name属性
            field2.set(obj, "张三");
            // 得到age属性
            Field field3 = c.getDeclaredField("age");
            // 设置age属性
            field3.set(obj, 20);
            System.out.println(obj);
            
            // 获取属性值
            System.out.println(field1.get(obj));
            System.out.println(field2.get(obj));
            System.out.println(field3.get(obj));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

操作方法:


public class Test4 {
    public static void main(String[] args) {
        try {
            Class c = Class.forName("com.exercise.Person");
            // 创建对象
            Object obj = c.newInstance();
            // 根据方法名称得到方法
            Method method1 = c.getMethod("m1");
            // 调用无参方法
            method1.invoke(obj);
            
            // 根据方法名称和参数类型得到方法
            Method method2 = c.getMethod("m2", String.class);
            // 调用有参方法
            method2.invoke(obj, "张三");
            
            // 根据方法名称和参数类型得到方法
            Method method3 = c.getMethod("m2", String.class, int.class);
            // 调用有参方法
            method3.invoke(obj, "张三", 20);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

三、设计模式

3.1 工厂模式

简化对象的创建过程。

例如:一个对象的创建,如果需要10个步骤,将其封装成一个方法,避免每次创建对象时,都需要写10个步骤。

3.1.1 工厂方法

定义一个工厂类,在其中定义一些静态的方法,用来创建不同的对象,简化创建对象的过程。


public class MyFactory {
    public static Car createCar() {
        Car car = new Car();
        car.setId("1");
        car.setName("比亚迪");
        // 省略创建的其他初始化的步骤
        return car;
    }
    
    // 还可以创建其他的对象
}
3.1.2 抽象工厂

定义一个抽象工厂类,具体实现交给使用者。


public abstract class MyAbstractFactory {
    public abstract Car createCar();
    
    static {
        // 启动时需要实现的代码
    }
}

public class MyCarFactory extends MyAbstractFactory{
    @Override
    public Car createCar() {
        Car car = new Car();
        car.setId("1");
        car.setName("比亚迪");
        // 省略创建的其他初始化的步骤
        return car;
    }
}
3.2 单例模式【重点】

当一个类只需要创建一个对象时,可以使用单例模式。

  • 构造方法私有
  • 在类的内部创建一个对象

单例模式分为饿汉(eager)模式和懒汉(lazy)模式。

3.2.1 饿汉模式

public class MySingleton {
    // 在内容创建一个对象
    private static MySingleton instance = new MySingleton();
    
    // 构造方法私有
    private MySingleton() {
    }
    
    // 提供一个访问方法
    public static MySingleton getInstance() {
        return instance;
    }
}
3.2.2 懒汉模式

简单的懒汉模式:


// 懒汉模式
public class MySingleton1 {
    // 在内容创建一个对象
    private static MySingleton1 instance;
    
    // 构造方法私有
    private MySingleton1() {
        
    }
    
    // 提供一个访问方法
    public static MySingleton1 getInstance() {
        if(instance == null) {
            instance = new MySingleton1();
        }
        return instance;
    }
}

上面的懒汉模式存在线程安全问题,下面的代码解决了线程安全问题:


// 懒汉模式
public class MySingleton1 {
    // 在内容创建一个对象
    private static MySingleton1 instance;
    
    // 构造方法私有
    private MySingleton1() {
        
    }
    
    // 提供一个访问方法
    public static MySingleton1 getInstance() {
        // 双重检测锁
        if(instance == null) {
            synchronized(MySingleton1.class) {
                if(instance == null) {
                    instance = new MySingleton1();
                }
            }
        }
        return instance;
    }
}

上面的代码不优美,可以使用内部类实现:


// 懒汉模式
public class MySingleton2 {
    // 构造方法私有
    private MySingleton2() {
        System.out.println("对象创建");
    }
    
    // 提供一个访问方法
    public static MySingleton2 getInstance() {
        return Inner.instance;
    }
    
    private static class Inner{
        // 在内容创建一个对象
        private static MySingleton2 instance = new MySingleton2();
    }
}
3.3 装饰模式

装饰模式是指对一个类的功能进行修饰,实现该功能的增强。


// 绘制
public abstract class Drawable {
    public abstract void draw();
}

// 实际画圆的操作类
public class CircleDraw extends Drawable{
    @Override
    public void draw() {
        System.out.println("画圆");
    }
}
// 对画圆类进行装饰的类,增强了画圆的方法
public class ColorCircleDraw extends Drawable{
    private Drawable drawable;
​
    public ColorCircleDraw(Drawable drawable) {
        super();
        this.drawable = drawable;
    }
​
    @Override
    public void draw() {
        drawable.draw();
        System.out.println("绘制一个彩色边框");
    }
}

public class Test1 {
    public static void main(String[] args) {
        // 调用装饰类
        ColorCircleDraw ccd = new ColorCircleDraw(new CircleDraw());
        ccd.draw();
    }
}

四、枚举

当项目中需要使用多种状态时,可以使用常量来定义。


public interface Constants {
    int DIAN_RAN = 1; // 点燃
    int BING_DONG = 1 << 1; // 冰冻
    int ZHONG_DU = 1 << 2; // 中毒
}

public class Test {
    public static void main(String[] args) {
        checkStatus(Constants.DIAN_RAN | Constants.BING_DONG);
    }
    
    public static void checkStatus(int status) {
        switch (status) {
        case Constants.DIAN_RAN:
            System.out.println("角色被点燃");
            break;
        case Constants.BING_DONG:
            System.out.println("角色被冰冻");
            break;
        case Constants.ZHONG_DU:
            System.out.println("角色被中毒");
            break;
        case Constants.DIAN_RAN | Constants.BING_DONG:
            System.out.println("角色被点燃、冰冻");
            break;
        default:
            break;
        }
    }
}

小技巧:当状态之间可以重叠时,可以在设计状态值时,使用1、2、4、8...方式来设计,利用二进制的|运算来实现。

使用常量时,由于没有编译检查,所以直接写数字,或者超出范围的数字,导致程序无法有效识别。例如:


public static void main(String[] args) {
    checkStatus(2); 
    checkStatus(8);
}
4.1 枚举的基本用法

此时,可以使用枚举来解决此问题。


public enum MyStatus {
    DIAN_RAN, BING_DONG, ZHONG_DU
}

public class Test1 {
    public static void main(String[] args) {
        checkStatus(MyStatus.DIAN_RAN); 
        checkStatus(MyStatus.BING_DONG);
    }
    
    public static void checkStatus(MyStatus status) {
        switch (status) {
        case DIAN_RAN:
            System.out.println("角色被点燃");
            break;
        case BING_DONG:
            System.out.println("角色被冰冻");
            break;
        case ZHONG_DU:
            System.out.println("角色被中毒");
            break;
        default:
            break;
        }
    }
}

此时,如果写数字,会直接报错,强制要求必须使用合法的枚举值。

4.2 枚举的高级用法

枚举是一个特殊的类,所以可以定义属性和方法。

public enum MyStatus {
    // 列举值
    DIAN_RAN(1, "DR", "被点燃"), 
    BING_DONG(2, "BD", "被冰冻"), 
    ZHONG_DU(3, "ZD", "中毒");
    
    // 属性
    private int id; // 编号
    private String name; // 名称
    private String desc; // 描述
    
    private MyStatus(int id, String name, String desc) {
        this.id = id;
        this.name = name;
        this.desc = desc;
    }
    
    public void desc() {
        System.out.println("id=" + id + ",name= " + name + ",desc=" + desc);
    }
}

public class Test1 {
    public static void main(String[] args) {
        checkStatus(MyStatus.ZHONG_DU); 
    }
    
    public static void checkStatus(MyStatus status) {
        if(status == MyStatus.ZHONG_DU) {
            status.desc();
        }
    }
}

五、注解

5.1 概念

在类、方法、属性、参数等上方添加的格式为@Override之类的特殊标记,称为注解,作用是用来提供相应的配置信息,代替复杂的配置文件。

常见注解:@Override,配置当前方法是重写父类或接口中的方法。

5.2 定义

使用@interface定义。注解中只能包含属性。


// 必须声明注解的使用方式和使用位置
@Retention(RetentionPolicy.RUNTIME) // 在运行时起作用,SOURCE是表示编译(源码)时起作用
@Target({ElementType.METHOD, ElementType.TYPE, 
    ElementType.FIELD, ElementType.PARAMETER}) // 在方法、类、属性、参数上使用
public @interface MyInterface {
    String value(); // 默认属性,当使用时,如果只使用此属性,可以不写名字
    String name() default "hello"; // 定义属性,默认值为hello
    String url() default "";
    String driver() default "";
    String username() default "";
    String password() default "";
}

@MyInterface(value = "hello", 
    url="jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf-8", 
    password="123456", username="admin", driver="com.mysql.jdbc.Driver")
public class MyClass {
    @MyInterface("aaa")
    private String aaa;
    
    @MyInterface("test")
    public void test() {
    }
    
    public void test1(@MyInterface("bbb") String m) {
    }
}
5.3 元注解

元数据:描述数据的数据。

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

常用元注解默认有4个:

@Documented :会生成javadoc@Retention:该注解使用的时机,是运行时还是源码(编译)@Target:该注解使用的目标,即使用在何处,类、方法、参数、属性等@Inherited:表示该注解可以继承

5.4 使用注解读文件

核心代码:


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD) 
public @interface MyReader {
    String value();
}

public class MyFactory {
    public static Object createObj(String type) {
        try {
            // 得到类对象
            Class c = Class.forName(type);
            // 创建对象
            Object obj = c.newInstance();
            // 检查该对象的所有属性,是否具有@MyReader注解,如果有,就读取文件
            // 得到所有属性
            Field[] fields = c.getDeclaredFields();
            for (Field field : fields) {
                // 判断属性上是否有注解
                if(field.isAnnotationPresent(MyReader.class)) {
                    // 得到该注解
                    MyReader reader = field.getDeclaredAnnotation(MyReader.class);
                    String value = reader.value();
                    // 当value不为空
                    if(value != null && !value.trim().equals("")) {
                        // 读文件
                        try (
                                BufferedReader br = new BufferedReader(
                                        new FileReader(value))
                                )
                        {
                            String str;
                            StringBuilder sb = new StringBuilder();
                            while((str = br.readLine()) != null) {
                                sb.append(str);
                            }
                            // 将读取的文件设置到对应的属性中
                            field.setAccessible(true);
                            field.set(obj, sb.toString());
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            return obj;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

项目中的应用:


public class Person {
    @MyReader("C:\Users\Desktop\1.txt")
    private String content;
​
    public String getContent() {
        return content;
    }
​
    public void setContent(String content) {
        this.content = content;
    }
}

public class TestMain {
    public static void main(String[] args) {
        Person person = (Person)MyFactory.createObj("com.exercise.demo.Person");
        System.out.println(person.getContent());
    }
}