目录
1、什么是反射?
反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
反射的优缺点?
优点:能够运行时动态获取类的实例,提高灵活性;可与动态编译结合。
Class.forName('com.mysql.jdbc.Driver.class'); // 加载MySQL的驱动类。
缺点:使用反射性能较低,需要解析字节码,将内存中的对象进行解析。
其解决方案是:通过setAccessible(true) ,关闭JDK的安全检查来提升反射速度;多次创建一个类的实例时,有缓存会快很多;ReflflectASM工具类,通过字节码生成的方式加快反射速度。
怎样获取反射中的 Class 对象?
1、Class.forName(“类的路径”);当知道该类的全路径名时,可以使用该方法获取 Class 类对象。
Class clz = Class.forName("java.lang.String");
2、类名.class。这种方法只适合在编译前就知道操作的 Class。
Class clz = String.class;
3、对象名.getClass()
String str = new String("Hello");
Class clz = str.getClass();
4、如果是基本类型的包装类,可以调用包装类的Type属性来获得该包装类的Class对象。
反射 API 有几类?
反射 API 用来生成 JVM 中的类、接口或则对象的信息。
- Class 类:反射的核心类,可以获取类的属性,方法等信息。
- Field 类:Java.lang.reflect 包中的类,表示类的成员变量,可以用来获取和设置类中的属性值。
- Method 类:Java.lang.reflect 包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。
- Constructor 类:Java.lang.reflect 包中的类,表示类的构造方法。
为什么引入反射?
- 可以通过外部类的全路径名创建对象,并使用这些类,实现一些扩展的功能。
- 可以枚举出类的全部成员,包括构造函数、属性、方法。
- 测试时可以利用反射 API 访问类的私有成员,以保证测试代码覆盖率。
2、序列化、反序列化是什么?
序列化:序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。核心作用是对象状态的保存与重建。Java对象是保存在JVM的堆内存中的,也就是说,如果JVM堆不存在了,那么对象也就跟着消失了。 而序列化提供了一种方案,可以让你在即使JVM停机的情况下也能把对象保存下来的方案。就像我们平时用的U盘一样。把Java对象序列化成可存储或传输的形式(如二进制流),比如保存在文件中。这样,当再次需要这个对象的时候,从文件中读取出二进制流,再从二进制流中反序列化出对象。
反序列化:客户端从文件中或网络上获得序列化后的对象字节流,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。
3、HashMap 原理?
HashMap通过哈希表、哈希函数、链表和红黑树等数据结构和算法,实现了高效的键值对存储和查找功能。HashMap的原理主要基于哈希表(Hash Table)的实现,它使用键(Key)的哈希值来计算在数组中的索引位置,从而存储键值对(Key-Value Pair)。以下是HashMap原理的详细解释:
- 初始化和哈希桶:HashMap在创建时,会初始化一个数组,这个数组被称为“哈希桶”(Hash Bucket)。在Java的HashMap实现中,这个数组的长度默认为16,但也可以根据需要调整。
- 计算哈希值:当向HashMap中添加一个键值对时,会首先计算键的哈希值。这个哈希值是通过一个哈希函数(Hash Function)计算得出的,哈希函数将键转换为一个整数。
- 确定索引位置:得到哈希值后,HashMap会使用这个哈希值来确定键值对在哈希桶数组中的索引位置。通常,这个索引位置是通过哈希值对数组长度取模运算得到的。
- 处理哈希冲突:由于哈希函数的输出范围有限,而输入(即键)的范围可能非常大,因此有可能出现多个键的哈希值相同的情况,这就是所谓的“哈希冲突”(Hash Collision)。为了处理哈希冲突,HashMap采用了“链表法”(Chaining)或“开放寻址法”(Open Addressing)等策略。在Java的HashMap实现中,采用的是“链表法”。当哈希冲突发生时,会将具有相同哈希值的键值对存储在一个链表中,这个链表被称为“桶”(Bucket)。
- 存储键值对:确定了索引位置和链表后,就可以将键值对存储到相应的位置上了。如果链表已经存在,则将其添加到链表的末尾;如果链表不存在(即该位置上没有其他键值对),则创建一个新的链表并将键值对添加到其中。
- 动态扩容:随着键值对的增加,哈希桶数组可能会变得拥挤,这会导致哈希冲突的增加和性能的下降。为了解决这个问题,HashMap会动态地调整数组的大小。当数组的使用量达到一定的阈值(通常是数组容量的75%)时,HashMap会自动进行扩容,将数组长度加倍,并重新计算所有键值对的哈希值并将它们放入新的哈希桶中。这个过程被称为“再哈希”(Rehashing)。
从Java 8开始,HashMap在解决哈希冲突时引入了红黑树(Red-Black Tree)来优化性能。当链表长度超过一定阈值(默认为8)时,HashMap会将链表转换为红黑树,以提高查找性能。同样地,当树的节点数量减少到一定程度(默认为6)时,HashMap会将树转换回链表以节省内存。
4、ArrayList 和 LinkedList 区别?
Arraylist
- 非线程安全
- 底层采用数组存储
- 插入、删除元素,时间复杂度受位置影响。默认是添加在列表的末尾,如果在位置 k 插入或删除一个元素,需要将k后面的元素后移或前移一位。
- 支持随机访问,根据索引下标序号,可以快速定位元素
- 需要连续的内存空间,中间不能有碎片
LinkedList
- 非线程安全
- 底层采用双向循环链表存储
- 插入、删除元素,时间复杂度不受位置影响,只需要更改位置 k的前后指针地址,时间复杂度为 O(1)
- 不支持高效的随机访问
- 不需要连续的内存空间
5、什么是 serialVersionUID ?
serialVersionUID 用来表明类的不同版本间的兼容性。 Java 的序列化机制是通过在运行时判断类的 serialVersionUID 来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的 serialVersionUID 与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。
为什么要显示指定 serialVersionUID 值?
如果不显示指定serialVersionUID,JVM在序列化时会根据属性自动生成一个serialVersionUID, 然后与属性一起序列化, 再进行持久化或网络传输。在反序列化时,JVM会再根据属性自动生成一个新版serialVersionUID,然后将这个新版serialVersionUID与序列化时生成的旧版 serialVersionUID 进行比较,如果相同则反序列化成功,否则报错。
如果显示指定了,JVM在序列化和反序列化时仍然都会生成一个serialVersionUID,但值为我们显示指定的值,这样在反序列化时新旧版本的serialVersionUID就一致了。
在实际开发中,不显示指定serialVersionUID的情况会导致什么问题? 如果我们的类写完后不再修改,那当然不会有问题,但这在实际开发中是不可能的,我们的类会不断迭代, 一旦类被修改了, 那旧对象反序列化就会报错。所以在实际开发中, 我们都会显示指定一个serialVersionUID,值是多少无所谓, 只要不变就行。
6、Java有哪些锁?
在Java中,有多种类型的锁用于并发编程,以确保线程安全地访问共享资源。
- 内置锁(synchronized):这是Java中最基本的锁机制,通过
synchronized
关键字实现。它可以用于方法或代码块,以确保同一时间只有一个线程可以执行该段代码。 - 可重入锁(ReentrantLock):
ReentrantLock
是java.util.concurrent.locks
包中的一个类,实现了与内置锁相同的语义,但提供了更广泛的锁定和更多功能,例如中断等待的线程、尝试非阻塞地获取锁等。 - 读写锁(ReadWriteLock):读写锁是一种特殊的锁,允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。Java中的
ReentrantReadWriteLock
是实现读写锁的一个类。 - 乐观锁:乐观锁假定多个事务之间很少发生冲突,操作不加锁。它通常使用版本号或时间戳来检查数据是否在读取和写入之间被其他事务修改。
- 悲观锁:悲观锁假定冲突可能频繁发生,因此先加锁,阻止其他事务发生,操作后释放锁。
synchronized
和ReentrantLock
都可以被视为悲观锁的实现。 - 自旋锁(SpinLock):自旋锁是一种特殊的互斥锁,当线程尝试获取锁但失败时,它会一直循环(自旋)等待,直到获取到锁为止。它适用于等待时间较短的情况,以避免线程挂起和恢复的开销。
- 公平锁(FairLock):公平锁保证线程获取锁的顺序与线程请求锁的顺序相同。如果存在等待队列,那么等待时间最长的线程将获得锁。
- 信号量(Semaphore):信号量是一种同步工具,用于控制对共享资源的访问。它允许多个线程同时访问共享资源,但限制了同时访问该资源的线程数量。
- StampedLock:这是Java 8引入的一个锁,它支持读/写锁语义,并提供了一种乐观读锁机制,这使得它在某些情况下比
ReentrantReadWriteLock
更加高效。 - Condition锁:
Condition
是与ReentrantLock
一起使用的条件对象,可以在特定条件下暂停和恢复线程执行。它允许线程在等待某个条件满足之前阻塞。 - CountDownLatch:这是一个同步辅助工具,用于等待一组线程完成操作。它允许一个或多个线程等待其他线程完成一组操作。