1.JDK与JRE区别?
JDK提供了java的开发环境和运行环境,而JRE提供了java的运行环境。具体来说就是JDK包含了JRE,还包含了编译java源码的编译器javac,如果你要运行java程序就安装JRE就行了,如果你要编写java程序就需要安装jdk
2.== 和 equals 的区别是什么?
==在基本类型和引用类型中的效果是不同的,在基本类型中比较的是值是否相同,而在引用类型中比较的是引用是否相同。equals 默认情况下是引用比较,只是很多类重写了 equals 方法,比如 String、Integer 等、把它从引用比较变成了值比较,所以一般情况下 equals 比较的是值是否相等。
3.两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?
不对,两个对象的 hashCode()相同,equals()不一定 true
4.为什么需要重写equals与hashCode方法?
重写 equals 和 hashCode 方法是因为它们是成对出现的,用于判断对象内容是否相等,它们需要协同工作,尤其是在使用哈希集合(如 HashSet 或 HashMap)时。如果重写了 equals 但没有重写 hashCode,就会导致同一个相等对象可能拥有不同的哈希码值,从而在哈希集合中被当作不同的元素存储,造成程序行为异常和效率低下。
- equals方法:用于判断两个对象的内容是否相等,而不是仅仅比较它们的内存地址。默认情况下,
Object类中的 equals 方法是比较对象的引用是否相同(即内存地址是否一样)。重写equals方法可以让开发者根据业务需求来定义对象的相等性,比如两个Person对象的id相同就认为它们相等。 - hashCode方法:用于计算对象的哈希值,这个值可以用来快速定位对象在哈希表(如
HashMap的桶)中的位置。 - 成对重写的原则:根据 Java 的约定,如果两个对象通过
equals方法比较是相等的(返回true),那么它们必须返回相同的hashCode值。 - 违反原则的后果:
- equals 为
true,hashCode 不同:当你在HashMap 中使用一个对象作为键,如果它与一个已存在的键通过equals判定相等,但它们的hashCode不同,HashMap会认为它们是两个不同的键,可能会创建新的条目,导致数据不一致或重复。 - 影响集合性能:
HashSet等集合会先根据hashCode找到可能的存储位置,然后再用equals进行确认。如果hashCode不一致,即使equals相等,也会导致对象没有被正确地添加到集合中,或者查询效率低下。
- equals 为
因此,在重写 equals 方法时,必须同时重写 hashCode 方法,以确保对象的正确性和一致性。
5.final, finally, finalize的区别?
final 是 Java 语言中的一个关键字,使用 final 修饰的对象不允许修改或替换其原始值或定义。
final 可以用来修饰:类、方法、变量和参数,其中可以用来修饰“参数”这一项,容易被人遗忘,这是 final 的 4 种用法。
final 用法说明
- 当 final 修饰
类时,此类不允许被继承,表示此类设计的很完美,不需要被修改和扩展。 - 当 final 修饰
方法时,此方法不允许任何从此类继承的类来重写此方法,表示此方法提供的功能已经满足当前要求,不需要进行扩展。 - 当 final 修饰
变量时,表示该变量一旦被初始化便不可以被修改。 - 当 final 修饰
参数时,表示此参数在整个方法内不允许被修改。
finally 则是 Java 中保证重点代码一定要被执行的一种机制。
我们可以使用 try-finally 或者 try-catch-finally 来进行类似关闭 JDBC 连接、保证释放锁等动作
finalize 是 Object 类中的一个基础方法,它的设计目的是保证对象在被垃圾收集前完成特定资源的回收,但在 JDK 9 中已经被标记为弃用的方法(deprecated)。
在实际开发中不推荐使用 finalize 方法,它虽然被创造出来,但无法保证 finalize 方法一定会被执行,所以不要依赖它释放任何资源,因为它的执行极不“稳定”。在 JDK 9 中将它废弃,也很好的证明了此观点。
6.String是基础数据类型吗?
String不属于基本数据类型,基本数据类型有8种,分别是byte short int long float double boolean char,String属于引用数据类型
7.java 中操作字符串都有哪些类?它们之间有什么区别?
有String、StringBuffer、StringBuilder
String和StringBuffer、StringBuilder的区别在于String声明的是不可变的字符序列,每次操作都会生成新的String对象,然后将指针指向新的String对象,而StringBuffer、StringBuilder可以在原有的对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用String
StringBuffer和StringBuilder的最大区别在于,StringBuffer是线程安全的,StringBuilder是线程不安全的,但StringBuilder的性能却高于StringBuffer,所以在单线程环境下推荐使用StringBuilder,多线程环境下使用StringBuffer
8. String str="i"与 String str=new String(“i”)一样吗?
不一样,内存分配方式不一样,Stirng str="i"的方式,java虚拟机会将其分配到字符串常量池中,而String str = new String("i")则会分配到堆内存中。类似于String str=new String(“i”)会创建两个对象:一个是在堆上创建的新的 String 对象,另一个是在字符串常量池中创建的字符串 "i"
9.如何将字符串反转?
使用StringBuilder 或者 StringBuffer的reverse() 方法
10.抽象类一定需要抽象方法吗?
不一定,抽象类不一定需要抽象方法
11.抽象类与普通类的区别?
普通类不能包含抽象方法,抽象类可以包含抽象方法。抽象类不能直接实例化new Xxx(),普通类可以直接实例化new Xxx()
12.抽象类与接口的区别?
修饰符不同,一个是abstract,一个是interface。抽象类中可以有不是抽象的方法,接口当中必须全是抽象方法(jdk1.8之前成立),jdk1.8还可以定义静态方法和默认方法。接口中变量全部默认使用public static final修饰的常量,抽象类中不限制 。抽象类只能够被单继承,接口可以有多实现。抽象类可以有构造函数;接口不能有。接口中的方法默认使用 public 修饰,抽象类中的方法可以是任意访问修饰符。
13.Java中IO分类?
按功能来分: 输入流、输出流 按类型来分: 字节流和字符流
字节流和字符流的区别是: 字节流按8位传输以字节为单位输入输出数据, 字符流按16位传输以字符为单位输入输出数据
14.BIO,NIO,AIO的区别?
BIO(Blocking I/O):采用阻塞式 I/O 模型,线程在执行 I/O 操作时被阻塞,无法处理其他任务,适用于连接数较少且稳定的场景。
NIO(New I/O 或 Non-blocking I/O):使用非阻塞 I/O 模型,线程在等待 I/O 时可执行其他任务,通过 Selector 监控多个 Channel 上的事件,提高性能和可伸缩性,适用于高并发场景。
AIO(Asynchronous I/O):采用异步 I/O 模型,线程发起 I/O 请求后立即返回,当 I/O 操作完成时通过回调函数通知线程,进一步提高了并发处理能力,适用于高吞吐量场景。
15.Files的常用方法都有哪些?
Files.exists(): 检测文件路径是否存在
Files.createFile(): 创建文件
Files.createDirectory(): 创建文件夹
Files.delete() : 删除一个文件或目录
Files.read(): 读取文件
Files.write(): 写入文件
15.Map与Collection?
Collection 和 Map 是 Java 集合框架的两个根接口,主要区别在于存储方式:Collection 存储一组对象(单列集合),而 Map 存储键值对(双列集合)。Collection 的子接口包括 List(有序、可重复)和 Set(无序、不可重复)等,而 Map 的键(key)是唯一的。
Collection
- 作用: 存储一组对象。
- 特性:
- 单列集合: 存储的是单个元素。
- 根接口: 是
List、Set、Queue等集合接口的父接口。 - 子接口:
List: 存储的元素有序且允许重复。Set: 存储的元素不重复。
Map
- 作用: 存储键值对(key-value)的映射关系。
- 特性:
- 双列集合: 存储的是键值对。
- 根接口: 与 Collection 接口同级,不继承自 Collection。
- 键(key)的唯一性: 每个键在 Map 中是唯一的。
- 值(value)的重复性: 允许重复的值。
16.ArrayList与LinkedList的区别?
ArrayList: ArrayList是Java集合框架中的一个类,它实现了List接口,底层基于数组实现。ArrayList的特点是支持动态数组,可以自动扩容,适合顺序访问和随机访问。
LinkedList: LinkedList也是Java集合框架中的一个类,同样实现了List接口,但底层基于链表实现。LinkedList的特点是支持高效的插入和删除操作,但随机访问的性能相对较差。
区别与优缺点对比 存储结构:ArrayList使用数组作为底层数据结构,数据在内存中是连续存储的,因此支持随机访问非常快速。LinkedList则使用链表作为底层数据结构,每个元素都包含指向前后元素的指针,插入和删除操作非常高效。
插入与删除操作:在ArrayList中,如果插入或删除元素,可能会导致数组元素的移动,从而影响性能。而LinkedList在插入和删除操作上具有明显优势,因为只需修改指针的指向,不需要移动大量元素。
随机访问性能:由于ArrayList的数组连续存储特性,它在随机访问上具有很好的性能。通过索引即可直接访问元素。而LinkedList需要从头或尾开始遍历链表,随机访问性能较差。
内存占用:由于LinkedList每个元素都需要存储前后指针,相对于ArrayList会占用更多的内存空间。如果需要存储大量数据,考虑内存占用也是一个重要因素。
迭代性能:在迭代(遍历)操作上,ArrayList由于连续存储的特性,性能通常较好。而LinkedList在迭代操作上由于需要通过指针跳转,性能相对较差。
如何选择?
- 使用ArrayList的场景: 需要频繁进行随机访问,例如根据索引获取元素。 数据集合相对固定,不需要频繁的插入和删除操作。 内存占用相对较少,不会造成严重的资源浪费。
- 使用LinkedList的场景: 需要频繁进行插入和删除操作,尤其是在中间位置。 不关心随机访问性能,而更关注插入和删除的效率。 可能需要更少的内存占用,尤其是在元素数量较少的情况下。
17.ArrayList与Vector的区别?
ArrayList和Vector的主要区别在于线程安全性和性能:Vector是线程安全的(同步的Vector的线程安全是通过在每个方法上都使用synchronized关键字来实现的),因此在多线程环境下使用;而ArrayList不是线程安全的(非同步的),性能更好,适合单线程环境。此外,在容量不足时,Vector默认将容量翻倍,而ArrayList默认增加容量的50%。
18.ArrayList如何转成成线程安全的List?
可以通过 Collections.synchronizedList()方法将 ArrayList 转换成线程安全的集合,或者使用线程不安全的 CopyOnWriteArrayList,Collections.synchronizedList() 方法会返回一个线程安全的 List,该列表中的所有方法都会进行同步,以保证在多线程环境下操作的安全性。
19.HashMap与HashTable的区别?
- HashMap不是线程安全的,HashTable是线程安全。
- HashMap允许空(null)的键和值(key),HashTable则不允许。
- HashMap性能优于Hashtable(Hashtable的线程安全是通过在每个方法上都使用
synchronized关键字来实现的)
20.synchronized锁的升级过程?
- 无锁 -> 偏向锁: 当一个线程首次执行同步代码块时,如果对象未上锁,
synchronized会将其偏向于该线程,此时升级为偏向锁。 - 偏向锁 -> 轻量级锁: 当有其他线程尝试竞争已经偏向某个线程的锁时,偏向锁会升级为轻量级锁。
- 轻量级锁 -> 重量级锁: 当轻量级锁发生竞争时,会通过 CAS 操作和自旋(自旋锁)来尝试获取锁。如果自旋一定次数后仍未获取到锁,则升级为重量级锁。
21.重载和重写的区别?
重载是发生在同一个类中,方法名相同,参数的类型、个数不同。重写发生在子类中,方法的名称、类型、返回值全部相同
22.Java 为什么要有包装类? 基本数据类型和对应包装类的区别?
因为当向ArrayList、HashMap中放东西时,int、double这种基本类型是放不进去的,因为容器都是装object的,这就需要有包装类了。基本数据类型不使用new关键字,而包装类需要使用new关键字在堆中分配存储空间;基本类型是将值存储在栈中,包装类型是将值放在堆中;
23.双亲委派机制可以打破吗?
可以打破。第一种是自定义类加载器去继承ClassLoader类重写loadClass()方法,第二种是线程上下文类加载器破坏双亲委派模型。
24.双亲委派机制是什么?
当一个类加载器收到了类加载的请求的时候,他不会直接去加载指定的类,而是把这个请求委托给自己的父加载器去加载。只有父加载器无法加载这个类的时候,才会由当前这个加载器来负责类的加载。 Java中提供的这四种类型的加载器,是有各自的职责的:
- Bootstrap ClassLoader ,主要负责加载Java核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。
- Extention ClassLoader,主要负责加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。
- Application ClassLoader ,主要负责加载当前应用的classpath下的所有类
- User ClassLoader , 用户自定义的类加载器,可加载指定路径的class文件 一个用户自定义的类,如com.hollis.ClassHollis 是无论如何也不会被ClassLoader和Extention ClassLoader加载的。
首先,通过委派的方式,可以避免类的重复加载,当父加载器已经加载过某一个类时,子加载器就不会再重新加载这个类。 另外,通过双亲委派的方式,还保证了安全性。因为Bootstrap ClassLoader在加载的时候,只会加载JAVA_HOME中的jar包里面的类,如java.lang.Integer,那么这个类是不会被随意替换的,除非有人跑到你的机器上, 破坏你的JDK。
双亲委派模型中,类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码的
25.throws与throw的区别?
throw是抛出一个异常,throws用来声明一个方法可能抛出的异常
26.Java中的Exception与Error是什么?
在 Java 中,Exception 和 Error 都继承自 Throwable 类。Exception 是程序在运行中可能遇到的可预料的意外情况,可以被捕获和处理。Error 指的是系统级或严重问题,通常是程序无法处理的,比如 JVM 内存溢出,需要恢复的严重错误。- Exception分类:
- 编译时异常(Checked Exception) : 在源代码中必须显式处理,否则程序无法编译通过。例如,文件未找到 (FileNotFoundException)。
- 运行时异常(Unchecked Exception) : 继承自 RuntimeException。它们在编译时不会强制检查,可以不捕获或抛出。通常是由于程序逻辑错误引起,如 NullPointerException、ArrayIndexOutOfBoundsException 等。常见的RuntimeException NullPointerException (空指针异常) NumberFormatException (数字格式化异常) ArrayIndexOutOfBoundsException (数组越界异常) StringIndexOutOfBoundsException (字符串越界异常) ClassCastException (类型转换异常) ArithmeticException (算术异常) IllegalArgumentException (非法参数异常)
27.JDK动态代理实现方法与实现步骤是怎样的?
实现方法:
- 在java的动态代理机制中,有两个重要的类或接口,一个是
InvocationHandler(Interface)、另一个则是Proxy(Class),这一个接口和类是实现我们动态代理所必须用到的
实现步骤:
- step 1:拿到被代理对象的引用,并且通过反射获取到它的所有的接口。
- step 2:通过JDK Proxy类重新生成一个新的类,同时新的类要实现被代理类所实现的所有的接口。
- step 3:动态生成 Java 代码,把新加的业务逻辑方法由一定的逻辑代码去调用。
- step 4:编译新生成的 Java 代码.class。
- step 5:将新生成的Class文件重新加载到 JVM 中运行。
28.JDK动态代理与CGLIB代理的区别?
JDK动态代理是通过重写被代理对象实现的接口中的方法来实现,而CGLIB是通过继承被代理对象来实现,和JDK动态代理需要实现指定接口一样,CGLIB也要求代理对象必须要实现MethodInterceptor接口,并重写其唯一的方法intercept。
CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。(利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理)
注意:因为CGLIB是通过继承目标类来重写其方法来实现的,故而如果是final和private方法则无法被重写,也就是无法被代理。
29.Java中创建对象的几种方式?
- new关键字
- Class.newInstance
- Constructor.newInstance
- Clone方法
- 反序列化
30.深度拷贝与浅度拷贝区别,如何实现深度拷贝?
区别:
浅度拷贝只复制对象的引用,而深度拷贝会为对象及其所有子对象在内存中创建独立的副本
实现方法:
- 实现
Cloneable接口并重写clone()方法 - 使用序列化和反序列化,这种方法通过将对象转换为字节流,然后从字节流中恢复为新的对象来创建深拷贝副本。
31.Java中的封装,继承,多态如何理解?
- 封装 (Encapsulation)
-
概念:将数据(属性)和操作数据的方法(函数)捆绑在一起,形成一个独立的单元——类。
-
作用:
- 信息隐藏:隐藏对象的内部状态和实现细节,外部只能通过公共接口来访问和修改数据。
- 提高安全性:防止外部代码不小心或恶意地修改对象内部数据,提高代码的健壮性。
- 提高可维护性:当内部实现发生变化时,只要公共接口不变,就不会影响到使用该类的其他代码。
- 继承 (Inheritance)
-
概念:子类(派生类)可以继承父类(基类)的属性和方法,从而获得父类的特性。
-
作用:
- 代码复用:避免重复编写相同的代码,子类可以直接复用父类的代码。
- 建立层次结构:形成类之间的“is-a”关系(例如,“狗”is-a“动物”),使代码结构更清晰。
-
Java 特性:Java是单继承语言,一个类只能直接继承一个父类。
- 多态 (Polymorphism)
-
概念:多态意味着同一个方法调用,在不同的对象上会有不同的行为表现。
-
实现方式:
- 父类引用指向子类对象:父类引用变量可以指向其子类的对象。
- 方法重写 (Overriding) :当子类和父类中定义了相同的方法签名时,子类可以重写父类的方法。
-
作用:
- 灵活性和通用性:允许编写更通用、更灵活的代码。例如,一个处理“动物”的方法,可以通过多态来处理“狗”、“猫”等不同类型的动物对象,而无需知道具体是哪种动物。
- 多种形态:一个接口或父类可以有多种不同的实现。
-
编译时多态与运行时多态:
- 编译时多态:通常指方法重载 (Overloading),编译器在编译时根据方法参数的类型和数量来确定调用哪个方法。
- 运行时多态:通常指方法重写 (Overriding),根据对象的实际类型在运行时决定调用哪个方法。
32.说说HashMap的底层实现
HashMap的底层是一个数组,存储着Node(或TreeNode)对象,当链表过长时会转为红黑树。之所以容量总是2的幂次方,是因为这允许在计算元素位置时使用高效的位运算(hash & (length - 1)),而非缓慢的取模运算。同时,当容量为2的幂次方时,扩容时只需将容量翻倍,并根据新容量计算元素的新位置,过程更简单高效,并能确保哈希值能更均匀地分布,从而减少哈希冲突。
底层结构
- 数组:
HashMap的底层是一个数组,每个元素都是一个链表或红黑树的起始节点。 - Node:
Node是链表的节点,包含键、值、哈希值以及指向下一个节点的引用。 - TreeNode:当链表中的节点数量达到一定阈值(在JDK 8中是8个)时,该链表会转换为一颗红黑树,以优化查找性能。
为什么容量为2的幂次方
-
效率更高的哈希计算:使用位运算能显著提高查找和插入的效率。
- 当容量为2的幂次方时,可以用
(capacity - 1) & hash来代替取模运算%来计算索引。 - 例如,如果
capacity是16,那么(capacity - 1)是15(二进制为1111)。此时,hash & 15的结果就是取模运算的等价结果,且效率远高于取模运算。
- 当容量为2的幂次方时,可以用
-
更均匀的哈希分布:长度为2的幂次方能帮助更均匀地分布哈希值,减少哈希冲突,避免过长的链表。
- 这使得
HashMap在多数情况下都能保持较快的查找速度。
- 这使得
-
更高效的扩容:当
HashMap需要扩容时,新容量通常是原容量的两倍。- 扩容时,原有的链表数据不需要重新计算每一个元素的索引,只需将原链表按照新容量的计算方式分成两部分(奇偶索引),然后直接复制到新数组的对应位置即可,这比重新计算所有元素的索引要高效得多。
33.ConcurrentHashMap是如何实现线程安全的?
-
分段锁(Java 1.7及之前) :
ConcurrentHashMap内部维护一个或多个Segment数组。- 每个
Segment包含一个独立的哈希表,并由一个独立的锁来保护。 - 当线程需要访问一个段时,只需要获取该段的锁,而不需要锁定整个哈希表。
- 这允许不同线程同时操作不同的段,显著提高了并发度。
-
更精细的锁和CAS(Java 1.8及之后) :
- 放弃分段锁: Java 8 版本不再使用分段锁,而是采用了一种更精细的锁机制。
- 节点锁: 在写入时,每个哈希桶(通常是链表或红黑树的头节点)会有一个锁。
- CAS: 对于某些更新操作,会使用 CAS(Compare and Swap)原子操作来尝试更新数据,避免了获取全局锁的开销。
volatile: 关键字段使用volatile关键字来保证线程之间的可见性,尤其是在读操作时,这允许无锁读取。
-
内部数据结构:
ConcurrentHashMap使用一个结合了 数组、链表和红黑树 的数据结构来处理哈希冲突。- 当链表长度超过一定阈值时,会将其转换为红黑树,以提高查找、插入和删除的效率。