反射
一、反射的概念
通过一个类名或者对象去获取该类中的所有描述信息。
然后根据这些描述信息,去创建对象,调用方法,操作属性等。
二、类对象
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());
}
}