反射与注解
反射与注解
反射就是通过一个类名(字符串)对类进行加载,获取类的信息,并对该类创建对象调用方法等操作。
一、类对象
类对象是描述类的信息的对象。
包含类有哪些属性、方法、构造方法等。
有三种获取方式:
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
:表示该注解是否生成文档
- 感谢你赐予我前进的力量