文章目录
如何理解泛型?
Java 泛型(generics) 是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型
。
Java 使用的是一种类型擦除方式实现的泛型。
因为 Java 在运行期间,所有的泛型信息都会被擦掉。
泛型的理解:JVM 中的编译与优化
如何理解反射?
赋予了我们在 运行时分析类以及执行类中方法的能力。
通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和改变属性。
注解
- 编译期直接扫描 :编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用@Override 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。
- 运行期通过反射处理 :像框架中自带的注解(比如 Spring 框架的 @Value 、@Component) 都是通过反射来进行处理的。
异常
Java List 不能如下使用
实际上他的长度是0,不过可以使用 fill 函数去填充实际长度
Java 到底是值传递还是引用传递?
java中方法参数传递方式是按值传递。
- 如果参数是基本类型,传递的是基本类型的字面量值的拷贝。
- 如果参数是引用类型,传递的是该参量所引用的对象在堆中地址值的拷贝。
为什么String要设计成不可变的?
Java 中的 ==, equals 与 hashCode 的区别与联系
==
分两种:
- 若是基本数据类型比较,是比较值,
- 若是引用类型,则比较的是他们在内存中存放的地址。
对象是放在堆中,栈中存放的对象的引用,所以==是对栈中的值进行比较,若返回 true ,代表变量的内存地址相等。
equals()
equals是Object类中的方法,默认比较的还是Object类的equals方法用于判断对象的内存地址引用是不是同一个地址。若是类中覆盖了equals方法(比如String类),就要根据具体代码来确定,一般覆盖后都是通对象的内容是否相等来判断对象是否相等。
hashCode
计算出对象实例的哈希码,在对象进行散列时作为key存入。之所以有hashCode方法,因为在批量的对象比较中,hashCode比较要比equals快。在添加新元素时,先调用这个元素的 hashCode方法,这样很快就能定位到它的物理位置,若该位置没有元素,可直接存储;若该位置有元素,就调用它的equals方法与新元素进行比较。用于确定该对象在哈希表中的索引位置,它实际上是一个int型整数。
推荐阅读:https://blog.csdn.net/justloveyou_/article/details/52464440
涉及到知识点:享元模式
String、StringBuffer、StringBuilder区别
https://blog.csdn.net/u011541946/article/details/79865160
String
- 不可变的,Immutable 类 ,类似拼接、裁剪字符串等动作,都会产生新的String对象。由于字符串操作的普遍性,所以相关操作的效率往往对应用性能有明显影响。
- 2.String 对象赋值之后就会在字符串常量池中缓存,如果下次创建会判定常量池是否已经有缓存对象,如果有的话直接返回该引用给创建者。
StringBuffer
StringBuffer 本质是一个线程安全的可修改字符序列,它保证了线程安全,也随之带来了额外的性能开销(方法全部使用了synchronized修饰),所以除非有线程安全的需要,不然还是推荐使用它的后继者,也就是 StringBuilder 。
StringBuilder:在修改字符串操作比较多的时候用StringBuilder或StringBuffer.
不要求线程安全时使用。
int 与 Integer区别
(Java 中为什么还需要基本数据类型?)
Java 两种数据类型
- 基本数据类型,分为boolean、byte、int、char、long、short、double、float;
- 引用数据类型 ,分为数组、类、接口。
为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每 一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
基本数据类型: boolean,char,byte,short,int,long,float,double 封装类类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
建议避免无意中的装箱、拆箱行为,尤其是在性能敏感的场合,创建10万个Java对象和10万个整数的开销可不是一个数量级的,不管是内存使用还是处理速度,光是对象头的空间占用就已经是数量级的差距了。
享元模式
- 自动装箱:将基本数据类型重新转化为对象
- 自动拆箱:将对象重新转化为基本数据类型
加大对简单数字的重利用,Java定义在自动装箱时对于值从–128到127之间的值,它们被装箱为Integer对象后,会存在内存中被重用,始终只存在一个对象。而如果超过了从–128到127之间的值,被装箱后的Integer对象并不会被重用,即相当于每次装箱时都新建一个 Integer对象。
静态代理与动态代理?
静态代理与JDK自带动态代理
见:代理模式
CGLib动态代理
cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。
底层实现
JDK动态代理
public Object createProxy(Object proxiedObject) {
Class[] interfaces = proxiedObject.getClass().getInterfaces();
DynamicProxyHandler handler = new DynamicProxyHandler(proxiedObject);
return Proxy.newProxyInstance(proxiedObject.getClass().getClassLoader(),
interfaces, handler);
}
主要实现在:Proxy.newProxyInstance 上。
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
Objects.requireNonNull(h);
final Class<?> caller = System.getSecurityManager() == null
? null
: Reflection.getCallerClass();
/*
* Look up or generate the designated proxy class and its constructor.
* 生成接口的代理类的字节码文件
*/
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
return newProxyInstance(caller, cons, h);
}
private static Constructor<?> getProxyConstructor(Class<?> caller,
ClassLoader loader,
Class<?>... interfaces)
{
// optimization for single interface
if (interfaces.length == 1) {
Class<?> intf = interfaces[0];
if (caller != null) {
checkProxyAccess(caller, loader, intf);
}
// 如果在缓存中有对应的代理类,那么直接返回
// 否则代理类将由 ProxyBuilder 来创建
return proxyCache.sub(intf).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
} else {
// interfaces cloned
final Class<?>[] intfsArray = interfaces.clone();
if (caller != null) {
checkProxyAccess(caller, loader, intfsArray);
}
final List<Class<?>> intfs = Arrays.asList(intfsArray);
return proxyCache.sub(intfs).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
}
}
接下来看 build方法是如何处理的:
Constructor<?> build() {
Class<?> proxyClass = defineProxyClass(module, interfaces);
final Constructor<?> cons;
try {
cons = proxyClass.getConstructor(constructorParams);
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
return cons;
}
在 defineProxyClass
方法中就有著名的
/*
* Generate the specified proxy class.
* 生成指定代理类的字节码文件
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
// ProxyGenerator.generateProxyClass 方法的详情是:
static byte[] generateProxyClass(final String name,
Class<?>[] interfaces,
int accessFlags)
{
ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
// 真正用来生成代理类字节码文件的方法在这里
final byte[] classFile = gen.generateClassFile();
if (saveGeneratedFiles) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
try {
int i = name.lastIndexOf('.');
Path path;
if (i > 0) {
Path dir = Path.of(name.substring(0, i).replace('.', File.separatorChar));
Files.createDirectories(dir);
path = dir.resolve(name.substring(i+1, name.length()) + ".class");
} else {
path = Path.of(name + ".class");
}
Files.write(path, classFile);
return null;
} catch (IOException e) {
throw new InternalError(
"I/O exception saving generated file: " + e);
}
}
});
}
return classFile;
}
gen.generateClassFile(); 真正用来生成代理类字节码文件的方法:
private byte[] generateClassFile() {
//下面一系列的addProxyMethod方法是将接口中的方法和Object中的方法添加到代理方法中(proxyMethod)
this.addProxyMethod(hashCodeMethod, Object.class);
this.addProxyMethod(equalsMethod, Object.class);
this.addProxyMethod(toStringMethod, Object.class);
Class[] var1 = this.interfaces;
int var2 = var1.length;
int var3;
Class var4;
//获得接口中所有方法并添加到代理方法中
for(var3 = 0; var3 < var2; ++var3) {
var4 = var1[var3];
Method[] var5 = var4.getMethods();
int var6 = var5.length;
for(int var7 = 0; var7 < var6; ++var7) {
Method var8 = var5[var7];
this.addProxyMethod(var8, var4);
}
}
Iterator var11 = this.proxyMethods.values().iterator();
//验证具有相同方法签名的方法的返回类型是否一致
List var12;
while(var11.hasNext()) {
var12 = (List)var11.next();
checkReturnTypes(var12);
}
//后面一系列的步骤用于写代理类Class文件
Iterator var15;
try {
//生成代理类的构造函数
this.methods.add(this.generateConstructor());
var11 = this.proxyMethods.values().iterator();
while(var11.hasNext()) {
var12 = (List)var11.next();
var15 = var12.iterator();
while(var15.hasNext()) {
ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod)var15.next();
//将代理类字段声明为Method,并且字段修饰符为 private static.
//因为 10 是 ACC_PRIVATE和ACC_STATIC的与运算 故代理类的字段都是 private static Method ***
this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName,
"Ljava/lang/reflect/Method;", 10));
//生成代理类的方法
this.methods.add(var16.generateMethod());
}
}
//为代理类生成静态代码块对某些字段进行初始化
this.methods.add(this.generateStaticInitializer());
} catch (IOException var10) {
throw new InternalError("unexpected I/O Exception", var10);
}
if(this.methods.size() > '\uffff') {
//代理类中的方法数量超过65535就抛异常
throw new IllegalArgumentException("method limit exceeded");
} else if(this.fields.size() > '\uffff') {
// 代理类中字段数量超过65535也抛异常
throw new IllegalArgumentException("field limit exceeded");
} else {
// 后面是对文件进行处理的过程
this.cp.getClass(dotToSlash(this.className));
this.cp.getClass("java/lang/reflect/Proxy");
var1 = this.interfaces;
var2 = var1.length;
for(var3 = 0; var3 < var2; ++var3) {
var4 = var1[var3];
this.cp.getClass(dotToSlash(var4.getName()));
}
this.cp.setReadOnly();
ByteArrayOutputStream var13 = new ByteArrayOutputStream();
DataOutputStream var14 = new DataOutputStream(var13);
try {
var14.writeInt(-889275714);
var14.writeShort(0);
var14.writeShort(49);
this.cp.write(var14);
var14.writeShort(this.accessFlags);
var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
var14.writeShort(this.interfaces.length);
Class[] var17 = this.interfaces;
int var18 = var17.length;
for(int var19 = 0; var19 < var18; ++var19) {
Class var22 = var17[var19];
var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));
}
var14.writeShort(this.fields.size());
var15 = this.fields.iterator();
while(var15.hasNext()) {
ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next();
var20.write(var14);
}
var14.writeShort(this.methods.size());
var15 = this.methods.iterator();
while(var15.hasNext()) {
ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();
var21.write(var14);
}
var14.writeShort(0);
return var13.toByteArray();
} catch (IOException var9) {
throw new InternalError("unexpected I/O Exception", var9);
}
}
}
CGLib动态代理
具体项目中的使用
参考:Java中的代理模式-静态代理与动态代理/
1、静态代理,动态代理
简单描述区别, 然后可以引出 jdk动态代理和cglib 的底层实现原理(Proxy 和 InvocationHandler).
再引入 Spring AOP 在不同情况下采用的代理实现方式
最后举例项目中动态代理的使用场景(常见的日志打印)
Java 中的集合与数据结构
- 数据结构:List 列表、Queue 队列、Deque 双端队列、Set 集合、Map 映射
- 比较器:Comparator比较器、Comparable 排序接口
- 算法:Collections常用算法类、Arrays静态数组的排序、查找算法
- 迭代器:Iterator通用迭代器、ListIterator针对 List 特化的迭代器
List 列表
- 元素可重复
- List 主要实现类:ArrayList、LinkedList、Vector、Stack。
线程不安全的List
ArrayList
- 内部使用数组实现,线程不安全,需要线程安全可以使用
Collections.synchronizedList
进行包装。 - 默认容量10,每次扩容 50%
List<Object> list =Collections.synchronizedList(new ArrayList<Object>());
LinkedList
内部使用链表实现,线程不安全
线程安全的 List
CopyOnWriteArrayList
内部维护了一个数组,读的时候是读这个数组的值,迭代器 Iterator 遍历的也是这个数组。如果在遍历 array 的同时,还有一个写操作,例如增加元素,CopyOnWriteArrayList 会将 array 复制一份,然后在新复制处理的数组上执行增加元素的操作,执行完之后再将 array 指向这个新的数组。因此保证了线程安全。
- 仅适用于写操作非常少的场景,而且能够容忍读写的短暂不一致
- 迭代器是只读的,不支持增删改。因为迭代器遍历的仅仅是一个快照,而对快照进行增删改是没有意义的。
Vector(项目中几乎不使用)
一个元老级别的类,早在 jdk1.1 就引入进来类,之后在 jdk1.2 里引进 ArrayList,ArrayList 大部分的方法和 Vector 比较相似,两者是不同的,Vector 是允许同步访问的,Vector 中的操作是线程安全的,但是效率低。因为读写加锁,强一致
Queue 队列
- Queue主要实现类:ArrayDeque、LinkedList、PriorityQueue。
- LinkedList 同时实现了stack、Queue、PriorityQueue,Deque 的所有功能。
- 优先队列的使用见:算法-栈和队列(Java实现)
Map 映射
- Map 主要实现类:HashMap、LinkedHashMap、TreeMap、IdentityHashMap、WeakHashMap、Hashtable、Properties。
HashMap
无序,哈希表实现,默认容量16,负载因子 0.75,扩容机制是超过负载因子后扩容两倍。复杂度:O(1)
LinkedHashMap
内部使用 哈希表和链表 共同实现,特点是有序,输出序列与输入序列相同。复杂度:O(1)
TreeMap
按照键的顺序存放,使用红黑树实现,复杂度:O(logN)
ConcurrentHashMap
Set 集合
- Set主要实现类:HashSet、LinkedHashSet 和 TreeSet。
在 Set 集合的实现上,基本都是基于 Map 中的键做文章,使用 Map 中键不能重复、无序的特性;所以,我们只需要重点关注 Map 的实现即可!