图标
文章摘要
LANZLZ.CN-GPT

|

反射与注解

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

一、类对象

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

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

有三种获取方式:

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
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 反射操作属性
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
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 反射操作方法
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
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 反射访问构造方法
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
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 简单工厂

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

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
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 抽象工厂

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

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
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)模式

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

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

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
public class EagerSingleton { private static EagerSingleton instance = new EagerSingleton(); // 构造方法私有 private EagerSingleton() { System.out.println("创建对象"); } public static EagerSingleton getInstance() { return instance; } }
3.2.2 懒汉(lazy)模式

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

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

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
public class LazySingleton { private static LazySingleton instance; private LazySingleton() { System.out.println("创建对象"); } public static LazySingleton getInstance() { if(instance == null) { instance = new LazySingleton(); } return instance; } }

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

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
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; } }

使用内部类来实现。

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
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 枚举的基本使用

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

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
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); } }

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

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
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 枚举的高级使用

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

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
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 自定义注解

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

工具类的定义:

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
@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; } }

使用:

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
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:表示该注解是否生成文档