写在前面
- 温习一下毕业以来学习的东西。准备做成一个系列。所以对于每一部分技术点进行一个笔记整理。更多详见 java面试的一些总结
- 笔记主要是以网上开源的一本
《Java核心面试知识整理》面试笔记为原型,结合工作中学习的知识。《Effective Java》、《编写高质量代码(改善Java程序的151个建议)》这两本书为方向进行整理。 - 笔记立足
DevOps。开发+运维+测试三个方向 ,面向对JAVA有一定了解的小伙伴用于温习,因为理论较多一点。在不断更新,博文内容理解不足之处请小伙伴留言指正。
傍晚时分,你坐在屋檐下,看着天慢慢地黑下去,心里寂寞而凄凉,感到自己的生命被剥夺了。当时我是个年轻人,但我害怕这样生活下去,衰老下去。在我看来,这是比死亡更可怕的事。--------王小波
二、JAVA 反射
动态语言
动态语言,是指程序在运行时可以改变其结构:新的函数可以引进,已有的函数可以被删除等结构上的变化。比如常见的 JavaScript 就是动态语言,除此之外 Ruby,Python 等也属于动态语言,而 C、C++则不属于动态语言。从反射角度说 JAVA 属于半动态语言。
反射机制概念(运行状态中知道类所有的属性和方法)
反射(Reflection)机制允许程序在运行时借助Reflection API取得任何类的内部信息,并不能直接操作对象的内部属性及方法。反射被视为动态语言的关键。
白话讲解:在 Java 中的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能成为Java 语言的反射机制。
JAVA 反射 API
如果面试被问到,对于反射API了你了解多少?怎么回答。
Reflection API提供了Constructor,Field和Method类,这三个类定义在java.lang.reflect包中,分别用于描述类的构造方法,属性和方法。JAVA8之后在在java.lang.reflect包中添加了Executable抽象类,parameterl类这两个API,Executable描述类的执行对象,parameter描述方法参数信息。
当然,获取这些反射APl的实例信息,还需要一个最重要对象元类Class,java.lang.Class类封装一个对象和接口运行时的状态。当类加载时Class类型的对象自动创建,Class没有公共构造方法,其对象是JVM在类加载时通过类加载器中的defineClass()方法自动构造的,不能显示的实例化一个class对象。
如果面试被问到你刚才说了类加载,你可以讲讲类加载么?
所谓类加载,就是指将类的class文件读入内存,并为之创建一个Java.lang.class对象。即当线程使用任何一个类时,系统都会为之创建一个java.lang.Class对象。java可以使用使用forName动态加载类文件,动态加载(Dynamic Loading)是指在程序运行时加载需要的类库文件,对Java程序来说,一般情况下,一个类文件在启动时或首次初始化时会被加载到内存中,而反射则可以在运行时决定是否要加载一个类,一个类文件只有在被加载到内存中才可能生成实例对象,即加载到内存中,生成Class对象,通过new关键字生成实例对象。
每个类被加载之后,会生成一个Class对象,通过Class对象可以访问JVM中该类的信息,一旦类被载入JVM中,同一个类将不会被再次载入,被载入的类都有一个唯一的标识,是该类得到全限定类名(包括包名和类名)。
Class
Class常用方法:
如果面试被问到你对Class对象了解多少,说几个常用方法,怎么回答?
Class常用方法 | 描述 |
| 返回 |
| 调用默认的构造方法,返回该Class对象的 |
| 返回 |
构造器相关方法 | 描述 |
| 返回 |
| 返回Class对象所对应的类的指定参数列表的 |
| 返回 |
| 返回 与 构造,需要开启访问权限 设置通过反射访问该成员变量时取消访问权限检查。 |
方法相关方法 | 描述 |
| 返回 |
| 返回 |
| 返回 |
| 返回 |
属性相关方法 | 描述 |
| 返回 |
| 返回 |
| 返回 |
| 返回 |
这里需要注意的是(Method、Constructor的也一样):
- 调用
getDeclaredFields()方法可以获取包括私有和受保护的所有属性,但不包括父类的属性; - 调用
getField()方法可以获得所有的public属性。包括从父类继承的。
注解相关方法 | 描述 |
| 返回 |
| 返回 |
Class自身信息相关方法 | 描述 |
| 返回 |
| 返回 |
| 返回 |
| 返回 |
| 返回 |
| 返回该类的 |
包相关方法 | 描述 |
| 返回 |
Class谓词相关方法 | 描述 |
| 判断 |
| 判断 |
| 判断 |
| 判断 |
| 返回 |
获取Class对象的四种方式:
如果面试被问到如何获取Class对象,怎么回答?
- 使用
Class类的forName(String classNmae)静态方法,参数class代表所需要类的全限定类名。forName()方法声明抛出ClassNotFoundException受检异常,调用必须捕获或抛出异常。
Class<?> reflection_demoClass = Class.forName("com.liruilong.Reflection.Reflection_Demo");- 调用某个类的
class属性来获取该类对应的Class对象,对象.class;类的Class属性获得该类所对应的Class对象,会始代码更安全。程序性更好。string类型的字符串不能使用String.class方式。需要使用Class.forName(“java.lang.String”),Object类的.class文件默认是不包含参数信息的。
Class<Reflection_Demo> reflection_demoClass = Reflection_Demo.class;
- 调用某个类的
getclass()方法来获取该类对应的class对象,该方法是Object类中的一个方法
Class<? extends Reflection_Demo> aClass = new Reflection_Demo().getClass();
- 调用元类Class对应的
getClassLoader()获取类加载器,ClassLoader,这个有点牵强,姑且算一种吧,嘻嘻。。
ClassLoader classLoader = Reflection_Demo.class.getClassLoader();
Class<?> bClass = classLoader.loadClass("com.liruilong.Reflection.Reflection_Demo");
下面我们看看剩下的API吧!
Executable
Executable抽象类:JAVA8在java.lang.reflect包下新增了一个Executable抽象类,代表可执行的类成员。Executable抽象类派生了Constructor和Method两个子类。Executable抽象类提供了大量方法来获取参数,修饰符或注解等信息。
方法 | 描述 |
| 获取 |
| 获取 |
| 获取 |
| 判断是否包含数量 |
Constructor
Constructor类:用于表示类的构造方法。通过Class的getConstructor()方法来获取构造方法的集合。
方法 | 描述 |
| 返回构造器的名称 |
| 返回当前构造方法的参数类型 |
| 返回 |
Method
Method类:用于封装方法的信息,调用Class对象的getMethods()方法或getMethod()可以获取当前类的所有方法或指定的方法。
常用方法 | 功能描述 |
| 返回方法的 |
| 返回方法的 |
| 返回 |
| 返回当前方法的 |
Field
Field类:用于封装属性信息,调用Class对象的getFields()或getField()方法可以获取当前类的所有属性或指定属性。
常用方法 | 描述 |
| 获取属性的名称 |
| 返回修饰符的整型标识 |
| 获取属性的值,此处的Xxx对应的java8中的基本类型,如果属性是引用类型,直接使用get(Object obj)方法 |
| 设置属性的值,此处的Xxx对应Java8中的基本类型,如果属性是引用类型,直接使用set(Object obj,Object val)方法 |
| 返回当前属性的类型 |
parameter
parameter类:是JAVA8中新增的API,每个paramtete 对象代表一个参数。Parameter类提供许多方法来获取参数信息
方法 | 功能 |
| 获取参数的 |
| 获取参数的 |
| 获取 |
| 获取 |
| 判断该参数是否为 |
| 判断 |
利用反射创建对象的的两种方式:
如果面试被问到使用反射如何创建对象,怎么回答?
-
Class 对象的 newInstance(): 使用 Class对象的 newInstance()方法来创建该Class 对象对应类的实例,但是这种方法要求该 Class对象对应的类有默认的空构造器。
Class<?> reflection_demoClass = Class.forName("com.liruilong.Reflection.Reflection_Demo");
Reflection_Demo o = (Reflection_Demo) reflection_demoClass.newInstance();
System.out.println(o);// com.liruilong.Reflection.Reflection_Demo@677327b6- 调用
Constructor对象的newInstance(): 先使用 Class对象获取指定的 Constructor对象,再调用Constructor对象的newInstance()方法来创建 Class 对象对应类的实例,通过这种方法可以选定构造方法创建实例。
Class<?> reflection_demoClass = Class.forName("com.liruilong.Reflection.Reflection_Demo");
Constructor<?> constructors = reflection_demoClass.getConstructor(null);
Reflection_Demo o1 = (Reflection_Demo) constructors.newInstance(null);
System.out.println(o1); // com.liruilong.Reflection.Reflection_Demo@14ae5a5关于反射的一些其他编码经验:
如果面试问关于反射,平常开发中有哪些经验,要怎么回答?
- 注意
Class类本身的特殊性:Java语言把Java源文件编译为后缀为class的字节码文件,然后通过ClassLocale机制把类文件加载到内存中,最后生成实例执行,Java使用元类(MetaClass)来描述加载到内存中的类数据,即Class类,描述类的类对象,需要注意Class的一些特性。
-
无构造函数,不能主动实例化,Class对象在加载时由java虚拟机通过类加载器中的defineClass自动构造。 -
可以描述基本类型 Class as=int.class;8个基本类型执行JVM中并不是一个对象,一般存在于栈中,通过Class可以描述它们,可以使用int.calss描述int类型的类对象。 -
Class对象都是单例模式,一个Class对象描述一个类,只描述一个类,即一个类只有一个Class对象。Class是java 的反射入口,只有在获得一个类的动态描述时才能动态的加载调用。
- 适时选择
getDeclaredXXX和getXXX:getDeclaredMethod方法获得的是所有public访问级别的方法,包括从父类继承来的方法,而getDeclareMethod获得自身类的所有方法,包括公有的(public),私有(private),方法等,不受访问权限限制。如果需要列出所有继承自父类的方法,可以先获得父类,然后使用getDeclareMethods,之后持续递归。 - 反射访问
属性或方法时将Accessible设置为true,java中通过反射执行方法的步骤,获取一个对象的方法,然后根据isAccessible返回值确定是否能执行,如果返回false,则需要调用setAccessible(true),在调用invoke执行方法。
-
Access并不是语法层次理解的访问权限,而是指是否更容易获得,是否进行安全检查。动态的修改一个类或方法或执行方法时都会受到Java安全体系的制约,而安全处理非常消耗资源,所以对于运行期要执行的方法或修改的属性就提供了Accessible可选项,由开发者决定是否要逃避安全体系的检查。 -
AccessibleObject是field,Method,constructor的父类,决定其是否可以快速访问而不进行访问控制检查,AccessobleObject类中是以override变量保存该值的。
-
Accessible属性只是用来判断是否需要进行安全检查的,如果不需要则直接执行,可以提升系统性能. AccessibleObject的其他两个子类field和 constructor也相似,所以要设置Accessible为true。
Method method= genericDemo.class.getMethod("toArray");
if(!method.isAccessible())
method.setAccessible(true);
method.invoke(obj, args);
public class Foo {
public final void doStuff() {
System.out.println("Do liruilong ___$# ^_^");
}
public static void main(String[] args) throws Exception, Throwable {
Method method = Foo.class.getMethod("doStuff");
System.out.println("可以访问吗!!"+method.isAccessible());
method.invoke(new Foo());
}- 动态加载不适合数组,当使用forName加载一个类时,8个基本类型排除,它不是一个具体的类,还要具有可追索的类路径,否则包ClassNotFoundException异常。数组虽然是一个类,但没有定义类路径,可以加载编译后的对象动态动态加载一个对象数组,但是没有意义。在java中数组是定长的,没有长度的数组是不允许存在的。可以使用Array数组反射类来动态加载一个数组。
//动态创建一个数组
String [] strs = (String[]) Array.newInstance(String.class,8);
int[][] ints = (int [][])Array.newInstance(int.class,2,3);
元素类型 | 编译后的类型 |
byte[] | [B |
char[] | [C |
Double[] | [D |
Float[] | [F |
Int[] | [I |
Long[] | [J |
Short[] | [S |
Boolean | [Z |
引用类型(如String) | [L引用类型 |
- 动态可以让代理模式更灵活,java的反射框架提供了动态代理(Dynamic Proxy)机制,允许在运行期对目标对象生成代理,
静态代理:通过代理主题角色和具体主题角色共同实现抽象主题角色的逻辑的,只是代理主题角色把相关的执行逻辑委托给了具体主题角色而已。
静态代理:
interface subject{
public void request();
}
class RealSubject implements subject{
public void request(){
}
}
class Proxy implements subject{
private subject subjects = null;
public Proxy(){
subjects = new RealSubject();
}
public Proxy(subject subjects){
this.subjects =subjects;
}
public void request(){
befoer();
subjects.request();
afert();
}
public void befoer(){}
public void afert(){}
}java基于java.lang.reflect.Proxy用于实现动态代理,使SubjectHandler作为主要的逻辑委托对象,invoke是必须要实现的,完成对真实方法的调用。即通过InvocationHandler接口的实现类来实现,所有被代理的方法都是由InvocationHandler接管实际的处理任务。
动态代理
interface subject{
public void request();
}
class RealSubject implements subject{
public void request(){
}
}
class SubjectHandler implements InvocationHandler{
private subject subjects;
private SubjectHandler(subject subjects) {
this.subjects = subjects;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("预处理");
Object obj = method.invoke(subjects,args);
System.out.println("后处理");
return obj;
}
}
//场景类
public static void main(String[] args) {
subject subjects = new RealSubject();
InvocationHandler handler = new SubjectHandler(subjects);
//当前加载器
ClassLoader cl = subjects.getClass().getClassLoader();
//动态代理
subject proxy = (subject) Proxy.newProxyInstance(cl,subjects.getClass().getInterfaces().request(),handler);
//执行具体的角色方法
proxy.request();
}- 使用反射增加
装饰模式的普遍性,装饰模式:动态的给一个对象添加一些额外的职责。使用动态代理可以实现装饰模式。
//动物
interface Animal{
public void doStuff();
}
//老鼠
class Rat implements Animal{
@Override
public void doStuff(){
System.out.println("Jetty Tom");
}
}
//定义某种能力
interface Featuer{
public void load();
}
class FlyFeatuer implements Featuer{
public void load(){
System.out.println("增加 一支翅膀");
}
}
class DigFeatuer implements Featuer{
public void load(){
System.out.println("增加钻地能力!");
}
}
class DecorateAnimal implements Animal{
private Animal animal;
private Class<? extends Featuer> clz;
public DecorateAnimal(Animal animal,Class<? extends Featuer> clz){
this.animal = animal;
this.clz = clz;
}
@Override
public void doStuff(){
InvocationHandler handler = new InvocationHandler() {
//具体的包装行为
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object obj = null;
//设置包装
// public class Modifier
// extends ObjectModifier类提供了static方法和常量来解码类和成员访问修饰符。 修饰符集合被表示为具有表示不同修饰符的不同位位置的整数。
//method.getModifiers()返回由该对象表示的可执行文件的Java语言modifiers 。
if (Modifier.isPublic(method.getModifiers())){
obj = method.invoke(clz.newInstance(),args);
}
animal.doStuff();
return obj;
}
};
ClassLoader cl = getClass().getClassLoader();
Featuer Proxy = (Featuer) java.lang.reflect.Proxy.newProxyInstance(cl,clz.getInterfaces(),handler);
Proxy.load();
}
}
public class Demo {
public static void main(String[] args) {
//定义Jerry老树
Animal Jerry = new Rat();
Jerry = new DecorateAnimal(Jerry,FlyFeatuer.class);
Jerry = new DecorateAnimal(Jerry,DigFeatuer.class);
Jerry.doStuff();
}
}
- 反射让模板方法模式更强大,模板方法模式:定义一个操作中的算法骨架,将一些步骤延迟到子类中,使子类不改变一个算法的结构即可定义该算法的某些特定的步骤,即父类定义抽象模板为骨架,其中包括基本方法(由子类实现的方法,并且在模板方法被调用)和模板方法(实现对基本方法的调用,完成固定的逻辑)
public abstract class AbsPopulator{
public final void dataInitialing() throws Exception{
doInit();
}
protected abstract void doInit();
}
public class UserPopulator extends AbsPopulator{
protected void doInit(){}
/*初始化用户表,如创建,加载数据等*/
}基于反射增强的模板方法
public abstract class AbsPopulator{
public final void dataInitialing() throws Exception{
//获得所有public方法
Method[] methods = getClass().getMethods();
for(Method m:methods){
//判断是否是数据初始化方法
if(isIinitDataMethod(m)){
m.invoke(this);
}
}
}
private boolean isIinitDataMethod(Method m) {
return m.getName().startsWith("init")&&//init开始
Modifier.isPublic(m.getModifiers())&&//公开的方法
m.getReturnType().equals("Void.Type")&&//返回类型为void
!m.isVarArgs()&&//输入参数不为空
!Modifier.isAbstract(m.getModifiers());//不能为抽象方法
}
}
public class UserPopulator extends AbsPopulator{
public void Inituser(){}
public void InitPassword(){}
public void InitJobz(){}
public void Inituser(){}
/*初始化用户表,如创建,加载数据等*/
}使用反射后,不需要定义任何抽象方法,只需要定义一个基本的方法鉴别器,即可加载否和规则的基本方法,模板方法根据鉴别器返回执行相应的方法。
- 不需要太多的关注反射效率,获得一个泛型类的实际泛型类型。这个Demo有点问题,以后再研究
class Utils{
//获得一个泛型类的实际泛型类型
public static <T> Class<T> getGenricClassType(Class clz) {
//返回 Type表示此所表示的实体(类,接口,基本类型或void)的直接超类 类
Type type = clz.getGenericSuperclass();
//ParameterizedType表示一个参数化类型,如Collection <String>。
if(type instanceof ParameterizedType){
ParameterizedType pt = (ParameterizedType){
ParameterizedType pt = (ParameterizedType)type;
Type[] types = pt.getActualTypeArguments();
if (types.length>0&&types[0] instanceof Class){
return (Class)types[0];
}
}
return (Class)Object.class;
}
}
}










