1、反射
1.1、概述
- 反射:加载类(通过反射将类的字节码文件加载到内存中),并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造器等)
- 反射需要掌握的内容:
1)记载类,获取类的字节码:Class对象
2)获取类的构造器:Constructor对象
3)获取类的成员变量:Field对象
4)获取类的成员方法:Method对象
1.2、获取类的字节码
- 获取Class对象可以通过如下三种方式:
1)Class c1 = 类名.class
2)调用Class类提供的forName方法:public static Class<?> forName(String className)
3)Object提供的getClass方法:public final native Class<?> getClass();
- 示例代码:
Class<Person> p1 = Person.class;
System.out.println(p1.getName()); // 类的全路径
System.out.println(p1.getSimpleName()); // 类简称: Person
Class<?> p2 = Class.forName("com.example.interviewStudy.reflect.Person"); // forName方法中的参数需要是类的全路径
System.out.println(p1 == p2); // true, p1和p2指向同一个对象
Person person = new Person();
Class p3 = person.getClass();
System.out.println(p1 == p3); // true
1.3、获取类的构造器
- 获取类构造器的作用:
- 示例代码:
Class p1 = Person.class;
Constructor<?>[] con1 = p1.getConstructors();
for (Constructor<?> c : con1) {
System.out.println(c.getName() +"<--->获取构造器参数总个数:" +c.getParameterCount());
}
System.out.println("获取所有构造器(包括私有的) ==>");
Constructor<?>[] con2 = p1.getDeclaredConstructors();
for (Constructor<?> c : con2) {
System.out.println(c.getName() +"<--->参数总个数:" +c.getParameterCount());
}
System.out.println("根据传入参数的类型 调用指定的构造器来创建对象 ==》");
Constructor publicPerson = p1.getConstructor(Long.class, String.class);
Person person1 = (Person) publicPerson.newInstance(10189L, "张三");
System.out.println(person1);
System.out.println("根据传入参数的类型 获取指定的 私有构造器 ==》");
Constructor<Person> privatePerson = p1.getDeclaredConstructor(String.class);
privatePerson.setAccessible(true); // 暴力反射,禁止检查访问权限,如果不禁止权限,则会报IllegalAccessException
// 调用无参构造器(将Person类中无参构造改为private修饰)
Person person2 = privatePerson.newInstance();
System.out.println(person2);
// 调用传入pname属性的构造器
Person person3 = privatePerson.newInstance("张小敬");
System.out.println(person3);
注意:如果不调用构造器对象的setAccessible,不禁止检查访问权限,就会报如下的
1.4、获取类的成员变量
为测试获取Person类的私有属性,需要将Person的pname属性改为private修饰
- 示例代码:
Class p1 = Person.class;
// p1.getConstructors(): 只能获取public修饰的属性
// p1.getDeclaredFields(): 能获取所有的属性
Field[] fields = p1.getDeclaredFields();
// 遍历Person类中的成员变量
for (Field f : fields) {
System.out.println(f.getName() + "<--->" + f.getType());
}
// p1.getField(): 只能指定获取public修饰的成员变量
Field pname = p1.getDeclaredField("pname");
Person person = new Person();
// 给private修饰的成员变量赋值,需要禁止检查访问权限
pname.setAccessible(true);
pname.set(person,"朱重八");
System.out.println("person = " + person);
// 获取成员变量
// 从person 对象中获取pname
String name = (String) pname.get(person);
System.out.println(name);
1.5、获取类的成员方法
- 示例代码:
Class p1 = Person.class;
Method[] methods = p1.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName() + "<-->" + method.getParameterCount() + "<-->访问修饰符:" + method.getModifiers());
}
Person person = new Person();
Method method1 = p1.getDeclaredMethod("printInfo", Long.class, String.class, Integer.class);
// 暴力反射
method1.setAccessible(true);
// 执行包含参数且有返回值的的成员方法,res1就是原本调用方法的返回值
Object res1 = method1.invoke(person, Long.valueOf(100897), "盛明兰", 26);
System.out.println(res1);
// 执行包含无参且没有返回值的成员方法,其原本方法的返回值就是null
Method method2 = p1.getDeclaredMethod("message");
method2.setAccessible(true);
Object res2 = method2.invoke(person);
System.out.println(res2);
1.6、反射的作用和应用场景
1)反射的作用
- 可以得到类全部的属性、构造器、成员变量、成员方法后进行其他操作
- 破坏封装性
- 适合实现Java框架,基本上主流的框架都会基于反射设计出一些通用的功能
2)反射的应用场景
- 需求:
实现一个简易版的持久化框架,对于任意一个对象,该框架都可以将对象的字段名和对应值,保存到txt格式的文本中
- 实现步骤:
1)定义一个方法,可以接收任意对象
2)每接收到一个对象,都通过反射获取到该对象的Class对象,根据Class对象来获取全部的成员变量
3)遍历所有的成员变量并获取到该变量在对象中的具体值
4)将变量和对应的值通过IO流的方式输出到文本中
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
public class ObjectFrame {
public static void saveEntity(Object entity) throws IllegalAccessException, FileNotFoundException {
// append追加文本的形式将数据输出data.txt文本中
PrintStream printStream = new PrintStream(new FileOutputStream("D:\\data1.txt",true));
Class clazz = entity.getClass();
String simpleName = clazz.getSimpleName();
printStream.println("=========" + simpleName + "==========");
// 获取entity对象所有的成员变量
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
String dataKey = field.getName();
String dataValue = field.get(entity) + "";
printStream.println(dataKey + " = " + dataValue);
}
printStream.close();
}
}
- 测试:
public static void main(String[] args) throws FileNotFoundException, IllegalAccessException {
Person p1 = new Person(1009876L,"张小敬",28);
Person p2 = new Person(2091823L,"雷东宝",32);
Person p3 = new Person(2091823L,"宋运辉",26);
ObjectFrame.saveEntity(p1);
ObjectFrame.saveEntity(p2);
ObjectFrame.saveEntity(p3);
Teacher t1 = new Teacher(101L,"李知恩","语文");
Teacher t2 = new Teacher(104L,"高启强","数学");
ObjectFrame.saveEntity(t1);
ObjectFrame.saveEntity(t2);
}
此外,反射在实际项目中还可以实现日志输出管理、公共字段/属性的自动填充(createUser、createTime、updateUser、updateTime)、事务、权限控制等,这需要结合AOP来实现
1.7、反射的优缺点
- 反射的优点:
- 在运行时可以获取任意类的属性、构造方法、成员方法,以及动态调用这些方法和修改属性
- 提高代码的复用率,如:在动态代理的场景中,使用动态生成的代理类来提升代码的复用性;
在Spring框架中,用反射来实例化Bean对象,用反射实现对象的属性拷贝的BeanUtils.copyProperties()方法;使用反射来自定义注解;
注意:使用BeanUtils.copyProperties()来完成对象属性的拷贝需要注意泛型擦除的问题
- 反射的缺点:
- 反射会涉及动态类型的解析,JVM无法对这些代码进行优化,导致性能比非反射调用低
- 反射可以绕过一些限制访问的属性和方法(如使用setAccessible方法来禁止检查访问权限),会破坏代码的封装性
2、注解
2.1、概述
2.2、自定义注解
2.3、注解的解析
2.4、应用场景
注解被用错位置在编译期间就会报错
- 元注解:指修饰注解的注解
3、单例模式
3.1、什么是单例模式
- 单例模式属于
创建型模式
,提供了一种创建对象的最佳方式,是指仅在内存中只创建一次对象的设计模式,在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象 - 单例模式的优点:提升使用对象的效率,又可以让类自行把控实例的实现细节,对外部业务代码不产生任何影响
单例模式的应用场景:HttpClient、Spring中的对象、打印日志的logger、数据库连接池对象
3.2、单例模式原则
- 构造器私有(防止类被通过常规方法实例化)
- 以静态方法或枚举返回实例(保证实例的唯一性)
- 确保实例只有一个,尤其是在多线程环境下(保证在创建实例时的线程安全)
- 确保反序列化时不会重新构建对象(在有序列化、反序列化的场景下防止单例被莫名破坏,造成未考虑到的后果)
3.3、Java实现单例模式有哪些方式
- 概括起来,实现单例需要关注如下几点:
- 构造函数需要是private访问权限,以此避免外部通过new创建实例
- 考虑是否支持延迟加载
- 考虑对象创建时的线程安全问题
- 考虑获取实例的getInstance()方法性能是否够高(是否加锁)
1)懒汉式单例
- 懒汉式单例:需要实例对象时,调用getInstance方法才去创建对象(适用于对象比较大的情况,不希望类被加载时就实例化从而拖慢程序的启动时长)
/***
* 懒汉模式
*/
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton(){ }
// 如果不加锁,则不能保证线程安全
// 锁的粒度较粗,同JDK1.0中的HashTable的设计,故HashTable不建议使用
public static synchronized LazySingleton getInstance(){
if (instance == null){
instance = new LazySingleton();
}
return instance;
}
}
2)饿汉式单例
- 饿汉式单例:可保证线程安全,不管需不需要单例对象instance ,都在类加载时进行初始化,会浪费内存资源,基于ClassLoader机制避免了多线程的同步问题
public class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){ }
public static HungrySingleton getInstance(){
return instance;
}
}
3)双重锁式单例
// DCL: Double Check Lock,双重锁校验
public class DCLSingleton {
// 基于volatile来保证可见性、有序性,禁止指令重排
private static volatile DCLSingleton instance;
private DCLSingleton() {
}
public static DCLSingleton getInstance() {
if (instance == null) { // 第一次检查
// 加锁保证线程安全
synchronized (DCLSingleton.class) {
if (instance == null) { // 再次检查防止同时进入synchronized
instance = new DCLSingleton();
}
}
}
return instance;
}
}
- 为什么双重锁式单例中instance要有volatile修饰?
在JVM执行过程中,instance实例的创建主要分为三步:
1)在堆中为对象分配内存空间
2)初始化对象(为属性赋值)
3)将实例对象指向堆内存中的内存空间
但由于JVM指令重排的优化,可能会出现
同时,volatile是轻量级的同步机制,可及时刷新内存,但volatile不能保证原子性,故需要配合synchronized来保证其可靠性
4)静态内部类式单例
- 基于延迟加载的方式,静态内部类实现单例模式:
/**
* 静态内部类实现单例,线程安全
* StaticInnerSingleton类被装载了,instance不一定被实例化,因为SingletonHolder类没有被主动使用
* 当执行getInstance时,才会显示地装载SingletonHolder类,实例化instance
*/
public class StaticInnerSingleton {
private StaticInnerSingleton() {
}
private static class SingletonHolder {
private static final StaticInnerSingleton instance = new StaticInnerSingleton();
}
public static final StaticInnerSingleton getInstance() {
return SingletonHolder.instance;
}
}
5)枚举式单例
public enum EnumSingleton {
INSTANCE;
// 执行业务代码
public void doSomeThing(){
System.out.println("执行业务逻辑....");
}
}
6)CAS式单例
import java.util.concurrent.atomic.AtomicReference;
public class Singleton {
priavte static final AtomicReference<Singleton> casInstance = new AtomicReference<>();
public static final Singleton getInstance() {
for (; ; ) {
Singleton singleton = casInstance.get();
if (singleton != null) return singleton;
// 如果casInstance为null,则创建单例对象并返回
casInstance.compareAndSet(null,new Singleton());
return casInstance.get();
}
}
}
3.4、关于设计模式
- 按照设计模式的应用目的进行分类,设计模式可以分为创建型模式、结构型模式、行为型模式
- 创建型模式:是对象在创建过程中各种问题和解决方案的总结,包括工厂模式、单例模式、原型模式、构建者模式
- 结构型模式:是针对软件设计结构的总结,关注于类、对象、组合方式的实践经验,常见的结构型模式有桥接模式、适配器模式、门面模式、装饰者模式、代理模式、享元模式等
- 行为型模式:是类或对象之间交互、职责划分等角度总结的设计模式,常见的行为型模式有策略模式、观察者模式、迭代器模式、适配器模式、模板方法模式、访问者模式等