面试题:
1. 懒汉单例模式为什么要加volaitle
2. 什么是反射?怎么获取一个类的成员变量、成员方法,注解信息?在项目中反射多吗?主要用来做什么?
1. 面试题解题
1.1 懒汉单例模式为什么要加volaitle
我以前写的一篇单例模式的文章:https://blog.csdn.net/xwh_1230/article/details/78198924
1.1.1 懒汉单例模式
使用时再创建实例。
/**
* DCL懒加载
* 1. 懒加载
* 2. 线程安全,性能较高
* 3. 易用、易理解
* Created by Administrator on 2017/10/10.
*/
public class SingleInstance {
private volatile static SingleInstance instance;
// 私有构造方法
private SingleInstance(){}
public static SingleInstance getInstance(){
// 延迟加载
if (instance == null){
// 第一重锁定
synchronized (SingleInstance.class){
//第二重锁定
if (instance == null){
instance = new SingleInstance();
}
}
}
return instance;
}
}
1.1.2 懒汉单例模式为什么要加volaitle
这个问题的关键在于:instance = new SingleInstance()
语句不是原子操作可以分解为:
- 给 instance 分配空间
- 调用构造方法初始化实例
- 将实例对象指向 instance分配空间
JVM中的及时编译存在指令重排序优化,所以说不能保证1-2-3顺序执行,最终也有可能是按照1-3-2执行。假如线程1执行的是1-3,线程2对instance做非空判断时结果是非null,则直接返回未初始化的instance,这个时候线程2就会报错。
添加volatile能禁止指令重排序特性,保证代码的有序性,它可以保证上述的步骤按照1-2-3的顺序执行。
1.2 什么是反射?怎么获取一个类的成员变量、成员方法,注解信息?在项目中反射多吗?主要用来做什么?
1.2.1 反射
运行时才知道要操作的类是什么,并能够调用它的任意方法和属性。这种动态获取对象,动态调用方法称之为反射。
1.2.2 关键Api
-
获取Class
- 知道类的全路径:
Class.forName(“java.lang.String”)
- String.class
知道要生成什么对象
- book.getClass()
已有对象,通过对象获取Class
- 知道类的全路径:
-
反射生成类对象
-
通过构造方法创建
// 2. 获取构造方法 Constructor<?> constructor = clazz.getConstructor(String.class); // 3. 通过构造方法创建对象,可以指定构造参数 Object book = constructor.newInstance("通过构造方法传入的book内容");
-
通过Class创建
// 3.1 通过Class创建对象,只能调用无参构造方法 Object book = clazz.newInstance();
-
-
获取构造方法、属性、普通方法
// 2. 获取构造方法 Constructor<?> constructor = clazz.getConstructor(String.class); // 2.1 获取私有构造方法 Constructor<?> privateConstructor = clazz.getDeclaredConstructor(); // 3.1 获取公有字段 Field bookContent = clazz.getField("bookContent"); // 3.2 获取私有字段 Field num = clazz.getDeclaredField("num"); // 4. 通过Class获取方法 Method setBookContent = clazz.getMethod("setBookContent", String.class); // 4.1 获取私有的方法 Method prvateSetNUm = clazz.getDeclaredMethod("setNum", Integer.class); //这几个是获取多个字段和方法的方式 clazz.getDeclaredField() clazz.getDeclaredFields() clazz.getMethods() clazz.getDeclaredMethods()
1.2.3 反射获取成员变量、成员方法、构造方法及注解信息
下面使用一个简单例子演示一下几种操作
// 反射示例的类
public class Book {
String bookContent;
public Book(){
}
public String getBookContent() {
return bookContent;
}
public void setBookContent(String bookContent) {
this.bookContent = bookContent;
}
@Override
public String toString() {
return "Book [bookContent=" + bookContent + "]";
}
}
try {
// 1. 首先获取Class对象
Class<?> clazz = Class.forName("tree.Book");
// 2. 获取构造方法
Constructor<?> constructor = clazz.getConstructor();
// 3. 反射的方式创建对象
Object book = constructor.newInstance();
// 4. 通过Class获取方法
Method setBookContent = clazz.getMethod("setBookContent", String.class);
// 5. 执行反射对象的对应方法
setBookContent.invoke(book, "这是我反射调用set方法,给book设置的内容");
Method toString = clazz.getMethod("toString");
System.out.println(toString.invoke(book));
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
1.2.3 项目中反射主要用来做什么?
- 反射调用系统方法或者sdk方法解决bug
比如有系统bug或者SDK的bug,我们无法改变其代码,只能使用反射的方式,对sdk代码或者系统代码进行hook,进行逻辑修复或者异常处理。
- 热修复等场景
使用DexClassLoader去加载未安装的apk或者jar中的方法,修复问题。