什么是多态?多态的实现方式?
多态是同一个行为具有不同的表现形式或形态的能力
实现方式:
- 方法重写
- 方法重载
- 抽象类和接口
反射的优缺点
优点:能够在运行时动态获取类的实例,提高系统的灵活性和扩展性,如动态代理技术
缺点:性能较差;破坏了类的封装性,安全性较差;
反射为什么慢?
- 需要进行动态类型检查
- JIT优化受限
- 需要使用字符串名称识别类型和成员,字符串搜索不区分大小写,速度较慢
- 需要进行类型安全检查
为什么重写equals()就要同时重写hashcode()方法?
因为set和Map集合在判断对象是否相等时不仅依赖equals方法,还依赖于hashcode()方法返回的哈希码
泛型擦除的缺点
- 泛型参数不支持基本类型只支持引用类型,因为泛型会被最终擦除为具体类型,而Object不能存储基本类型的值
- 运行时只能对原始类型进行类型检测,无法判断带泛型的类型
- 不能实例化类型参数
- 不能实例化泛型数组
如何通过反射创建对象
- 使用.class语法
如果在编译时已经知道要操作的类,可以直接使用.class
语法来获取Class
对象。这种方式不需要类的全路径名,只需要类名即可。例如:
java
Class clazz2 = MyClass.class;
- 使用Class.forName()
这种方式需要你知道类的完整路径名。通过调用Class.forName()
静态方法并传入类的全路径名,可以获取到对应的Class
对象。例如:
java
Class clazz1 = Class.forName("com.example.MyClass");
- 使用getClass()方法
如果已经有了一个类的实例,可以通过调用该实例的getClass()
方法来获取Class
对象。这种方式适用于已经存在的对象实例。例如:
java
MyClass myObject = new MyClass();
Class clazz3 = myObject.getClass();
获取到类后后使用newInstance()方法创建新的实例:
java
Object obj = clazz3.newInstance();
权限分类?枚举类权限?
枚举类的构造器只能使用 private 权限修饰符
自定义异常实现
自定义异常类的创建涉及以下几个步骤:
继承Exception类:自定义的异常类需要继承Java的Exception类。
定义异常信息:可以在自定义异常类中定义一个字符串变量来存储异常信息。
构造函数:自定义异常类应该包含一个构造函数,它接收一个字符串参数作为异常信息,并将其传递给父类的构造函数。
Constructor.newInstance()与Class.newInstance()的区别
Class.newInstance() 只能够调用无参的构造函数,即默认的构造函数; Constructor.newInstance() 可以根据传入的参数,调用任意构造构造函数。
Class.newInstance() 抛出所有由被调用构造函数抛出的异常。
Class.newInstance() 要求被调用的构造函数是可见的,也即必须是public类型的; Constructor.newInstance() 在特定的情况下,可以调用私有的构造函数。
ArrayList的扩容机制?
当添加的元素数量超过当前数组的长度时,ArrayList 会进行扩容,ArrayList 的初始容量为10 新的容量是旧容量的1.5倍左右。这是通过将旧容量右移一位(即除以2)然后加上旧容量来实现的,但这并不是严格的1.5倍,因为如果旧容量是奇数,则会丢失小数部分。
ConcurrentHashMap线程安全体现在哪些方面?
在 JDK 1.8 中,添加元素时首先会判断容器是否为空,如果为空则使用 volatile 加 CAS 来初始化。如果容器不为空则根据存储的元素计算该位置是否为空,如果为空则利用 CAS 设置该节点;如果不为空则使用 synchronize 加锁,遍历桶中的数据,替换或新增节点到桶中, 最后再判断是否需要转为红黑树,这样就能保证并发访问时的线程安全了。
我们把上述流程简化一下,我们可以简单的认为在 JDK 1.8 中,ConcurrentHashMap 是在头节点加锁来保证线程安全的,锁的粒度相比 Segment 来说更小了,发生冲突和加锁的频率降低了,并发操作的性能就提高了。而且 JDK 1.8 使用的是红黑树优化了之前的固定链表,那么当数据量比较大的时候,查询性能也得到了很大的提升,从之前的 O(n) 优化到了 O(logn) 的时间复杂度
Integer缓存?
Java的Integer类内部实现了一个静态缓存池,用于存储特定范围内的整数值对应的Integer对象。默认情况下,这个范围是-128至127。当通过Integer.valueOf(int)方法创建一个在这个范围内的整数对象时,并不会每次都生成新的对象实例,而是复用缓存中的现有对象。
何时会出现InterruptedException?
当一个线程处于阻塞状态下(例如休眠)的情况下,调用了该线程的interrupt()方法,则会出现InterruptedException。
什么是intern()方法
String.intern()是一个Native方法,它的作用是:如果字符常量池中已经包含一个等于此String对象的字符串,则返回常量池中字符串的引用,否则,将新的字符串放入常量池,并返回新字符串的引用'
BIO、NIO、AIO
项目 | BIO | NIO | AIO |
---|---|---|---|
异步/阻塞 | 同步阻塞 | 同步阻塞/非阻塞 | 异步非阻塞 |
线程模型 | 一个连接一个线程,线程会一直阻塞,知道IO完成 | 一个请求一个线程,通过IO多路复用轮询连接状态 | 一个有效请求一个线程发起IO后立刻返回,等通知 |
使用场景 | 适用简单的连接数少的场景,维护成本高 | 适用于高并发,大量连接,适合长连接的应用如Netty | 应用较少,但应用于对IO要求高的场景,如高性能服务器 |
forname和classloader的区别
面向对象和面向过程的区别?
面向对象和面向过程都是一种开发思想。
面向过程就是根据解决问题所需要的步骤,具体化的一步一步的去实现。
面向对象就是把数据及对数据的操作方法放在一起,作为一个整体,也就是对象,若干个这样的整体组成一个系统去解决实际问题。
面向过程只用函数实现,性能比较快因为不需要进行实例化,但是不容易扩展、维护,复用
面向对象通过类去实现功能模块,代码安全性高,容易扩展和复用,比较灵活且便于维护
面向对象有哪些特性?
面向对象有三大特性:封装,继承和多态
- 封装就是将类信息隐藏在类内部,不允许外部程序直接访问,而是通过调用类的方法去访问,良好的封装能减少耦合,增加安全性。
- 继承就是从已有的类派生出新的类,新的类继承父类的属性和方法,并扩展新的能力,极大提高了代码的复用行和易维护性。
- 多态是指同一个行为具有多种形式的表现,具体体现在继承,重写,重载,父类引用指向子类对象等,多态体现出面向对象的灵活性。
Finally代码块中的内容一定会执行吗?
finally 块中的代码确实会执行,但在系统崩溃、无限循环、强制终止程序和守护线程等极端情况下,finally
块中的代码可能不会执行。在实际编程中,finally 块通常用于释放资源、清理状态或执行其他必要的收尾工作
String是如何实现不可变的
- final类和final字段
String类被声明为final,这意味着它不能被继承。此外,String类中的字段(如value数组)也被声明为final,这意味着它们在初始化后不能被修改。- 字符数组的私有性
String类中的value数组是私有的,这意味着外部代码无法直接访问和修改它。这确保了String对象的值不会被外部代码修改。- 方法的返回值
String类中的方法(如substring、concat等)不会修改原始String对象,而是返回一个新的String对象。这确保了原始String对象的值不会被改变。- 字符串常量池
Java通过字符串常量池(String Constant Pool)机制进一步增强了String的不可变性。字符串常量池是一个特殊的内存区域,用于存储字符串字面量和字符串对象的引用。当创建一个新的字符串字面量时,Java首先检查字符串常量池中是否已经存在相同值的字符串,如果存在,则返回该字符串的引用,而不是创建一个新的字符串对象。
Error可以捕获吗
在 Java 中,Error 是继承自 Throwable 的一个子类,因此从技术上讲,它可以被 try-catch 块捕获。
链表和红黑树互相转换
一、链表长度超过 8,数组大小小于 64
如果链表长度超过了长度阈值 8。
如果数组大小等于16,小于最小树化容量64。
在这种情况下,即使链表长度超过了8,由于总容量小于64,链表不会转为红黑树,而是会进行扩容操作。
二、链表长度没有超过 8,数组大小大于 64如果链表长度等于 5,没有超过长度阈值 8。
如果数组大小等于80,大于最小书画容量 64。
链表会在其长度达到 8 时才会转换为红黑树。因此,如果链表长度等于 5,那么它不会转为红黑树,无论数组(即HashMap的桶数组)大小是多少。
三、红黑树转换为链表在HashMap实现中,红黑树会在一定条件下转换回链表。这主要是为了在删除元素后,保持合适的数据结构以优化性能和空间使用。红黑树转换为链表的条件如下:
树形化的红黑树节点数量小于等于 6:当红黑树节点的数量减少到6或更少时,红黑树会被转换回链表。这是因为在少量节点的情况下,链表的插入和删除操作比红黑树更高效。
最小树化容量:这是一个辅助条件,用于确保只有在HashMap的容量(桶数组大小)足够大时,才会执行链表到红黑树的转换和反转换。默认情况下,这个值是64。但是,转回链表的主要依据还是节点数量。
StringBuilder
StringBuilder的底层是一个char [ ]。
通过无参构造,append()添加元素时。数组的默认长度为16,每次扩容为数组原长度的2倍+2(value.length<<1+2)。
通过含参构造添加元素时。
①直接添加一个长度。数组的初始长度为该长度
②添加一个字符串。数组的初始长度为该字符串的长度+16(str.length+16)。每次扩容为数组原长度的2倍+2(和上述append()方法相同,因为也是通过append()添加元素的)
Java反射破坏了封装性吗?
一、用户通过反射机制获取了所使用的类中私有成员的存取权限,这是毫无意义的,因为我们上面分析过了,大多数的这些成员是依附于其它public方法而存在的,基本上都是为了服务这些public成员的。
二、紧接着上面的来说,就算用户拿到了private成员的存取权限,而且还"恶意"的修改类的私有成员,那么这么做的目的何在呢?这将大概率导致类的功能无法正常提供
HashMap和HashTable的区别
- HashMap是非线程安全的,在多线程环境下,HashMap会产生线程安全问题;而HashTable中的大部分方法都使用synchronized关键字来确保线程同步,因此HashTable是线程安全的,不过性能要比HashMap低一些
- HashMap的key可以使用null(但只能有一个),value可以为null,而HashTable都不允许存储key和value值为空的元素
- HashMap继承了AbstractMap,HashTable继承了Dictionary抽象类,两者都实现了Map接口
- HashMap的初始容量为16,- HashTable的默认容量大小为11,负载因子为0.75
- HashMap的扩容机制为扩容两倍,而HashTable的扩容机制为两倍-1
- HashTable不会转换为红黑树
- HashTable采用头插法的方式迁移数组,相比较HashMap的尾插法来说效率更高