Java面试必须掌握的基础
一.Java的数据类型
基本数据类型
整数类型:byte,short,int,long
浮点类型:float,double
字符类型:char
布尔类型:boolean
引用数据类型
类(class),接口(interface),数组(array),空(null),枚举(Enumeration)
二.&和&&的区别
&和&&都表示与的意思,既表达式俩边都成立,结果才成立。
&&是逻辑运算符,&&有短路作用,既当表达式左边为假时,不需要计算右边,整个的结果直接为假;&没有
&是位运算符,&的左右俩边可以是布尔类型,也可以是数值;&&俩边只能是布尔类型
三.Integer与int的区别
int是八大基本数据类型之一,Integer是int的封装类。
int的默认值是0,Integer的默认值是null,此时的0代表这个数赋值0,而null代表没接收到这个值
Integer提供了与整数相关的操作,int没有
四.==与equals的区别
对象类型不同
1、equals():是超类Object中的方法。从它的源码可以看出,里面使用的就是 == 比较,所以这种情况下比较的就是它们在内存中的存放地址。
//超类中equals的源码
public boolean equals(Object obj) {
return (this == obj);
}
2、==:是操作符。
比较的对象不同
1、equals():当两者的内存地址不同时,equals会检测两个对象是否相等,即两个对象的内容是否相等。
2、==:当应用于基本数据类型时,比较的是两者的值是否相等;
当应用于引用类型时,用于比较两者的引用地址是否相等,也可以理解为用于判断两者是否为同一个对象。
五.break、continue、return的区别及作用?
break:打破当前循环,结束循环,跳出循环,不再执行。
continue:跳出本次循环,进行下一次循环。(结束正在执行的循环条件,进入到下一个循环条件)
return:程序返回,后面的代码都不再进行,(结束当前的方法,直接返回)
六.final、finally、finalize的区别?
final: final 修饰变量:变量变为常量,不可以改变,(其中不可变分为引用不可变和对象不可变,final指的是引用不可变),因为不可 变,final修饰的变量必须初始化。
final 修饰方法 :方法不允许被任何子类重写,但子类可以使用该方法。
final 修饰类:被修饰的类不可以被继承,类中的所有方法都不能被重写。
finally:作为处理异常处理的一部分,只能在try/catch语句中,并且附带一个语句块表示这段语句最终一定会被执行(无论是否抛出异常),(经常用于释放资源) System.exit(0)可以阻断finally的执行
finalize:是在java.lang.Object里面定义的方法,因为是Object类,也就是说每一个对象都有这么一个方法,该方法在 gc 启动,该对象被回收的时候被调用。
七.static 关键字?
通常在new创建一个对象的时候,才会分配数据存储空间,方法才供外界调用。用上static就会单独分配一个存储空间,可以不用创建对象就可以使用方法,在静态区域里面。
static关键字,表示一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。
Java中static方法不能被覆盖,因为方法覆盖是基于运行时绑定,而static方法是编译时静态绑定的,static方法跟类的任何实例都不相关。
八.是否可以在static环境中访问非static变量?
static变量在Java中是属于类的,它所有的实例中的值都是一样的,当类被Java虚拟机载入的时候,会对static变量进行初始化,如果你的代码尝试不用实例来访问非static的变量,因为这些变量还没有被创建出来,还没有跟任何实例关联上。
九.static静态方法里面可不可以引用静态资源?
可以,因为都是类在初始化的时候加载的,大家都一样,互相认识。
十.非静态方法里面能不能引用静态资源?
可以,非静态方法就是实例方法,那是new之后才产生的,那么属于类的内容它都认识。
十一.面向对象的三大特性
封装、继承、多态
封装:将数据(属性) 和操作数据的方式捆绑,给属性加上private 修饰,提供对应的set get方法
继承:继承只允许继承一个类,子类拥有父类中所有没有封装的属性,简化代码,提高代码的复用率,
子类还可以重写父类的方法,从而实现多态
多态:多态就是对同一消息,做出响应,但是表现出不同的行为。
多态的条件:①子类继承父类,②子类重写父类中的方法,③父类引用指向子类对象。
十二.override(重写)和overload(重载)的区别
重载是在一个类中,方法名相同,参数列表不同(参数类型和参数个数)的一种现象
1.重载与返回值类型无关
2.不能通过访问修饰符进行重载
重写是在父子类中,子类重写父类的方法,要求方法名与参数列表,返回值类型完全相同。子类重写父类规范要在子类的方法前加注解@Override
1.重写的返回值类型可以改,但只能是父子类
2.重写的访问修饰符得大于等于原来的
3.不能重写私有方法
4.参数列表的顺序与类型必须一样,变量名可不同
十三.抽象类(abstract class)和接口(interface)有什么区别?
1.接口的方法默认是public,所有方法在接口中都不能有实现(Java8中接口的方法可以有默认实现)而抽象类可以有非抽象的方法。
2.接口中除了static、final变量,不能有其他变量,而抽象类中则不一定。
3.一个类可以实现多个接口,但是只能实现一个抽象类,接口自己本身,可以通过extends关键字扩展多个接口。
4.接口方法默认都是public,抽象方法可以有public、protected、和default这些修饰符(抽象方法就是为了被重写,所以不能被private关键字修饰!)
5.从设计层面来说,抽象是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为的规范。
十四.深拷贝和浅拷贝?
浅拷贝:仅拷贝被拷贝对象的成员变量的值,也就是基本数据类型变量的值,和引用数据类型变量的地址值。而对于引用数据类型,变量指向堆中的对象不会拷贝。
深拷贝:完全拷贝一个对象,拷贝 被拷贝对象的成员变量的值,堆中的对象也会拷贝一份。
因此深拷贝是安全的,浅拷贝的话如果有引用类型,那么拷贝后对象,引用类型变量修改,会影响原对象。
浅拷贝如何实现呢?
Object 类提供的 clone()方法可以非常简单地实现对象的浅拷贝。
深拷贝如何实现呢?
重写克隆方法:重写克隆方法,引用类型变量单独克隆,这里可能会涉及多层递归。
序列化:可以先将原对象序列化,再反序列化成拷贝对象。
十五. Java创建对象的几种方式?
Java中创建对象有以下四种方式:
new创建一个新对象
通过反射机制
采用clone机制
通过序列化机制
(前两者都需要显式地调用构造方法。对于 clone 机制,需要注意浅拷贝和深拷贝的区别,对于序列化机制需要明确其实现原理,在 Java 中序列化可以通过实现 Externalizable 或者 Serializable 来实现。)
十六. Java序列化和反序列化
序列化
是将对象转换为字节流的过程,这样对象可以通过网络传输、持久化存储或者缓存,Java 提供了 java.io.Serializable 接口来支持序列化,只要类实现了这个接口,就可以将该类的对象进行序列化。
反序列化
是将字节流重新转换为对象的过程,自即从存储中读取数据并重新创建对象。
十七.Java 中 wait()和 sleep()的区别?
wait()
在同步代码块里面调用,并且依赖于对象锁来管理线程的等待和唤醒,wait(1000)等待超过1s被唤醒 或 wait() 一直等待需要通过notify或者notifyAll进行唤醒 是属于object类,
sleep()
不需要对象锁,可以在任何上下文中调用,调用后线程会进入休眠状态,不会释放他持有的任何锁,sleep(1000)等待超过1s被唤醒属于Thread类
十八.HashMap面试必备?
1.HashMap的原理
hashmap原理流程图.png1.通过put方法存入key,value键值对
2.通过hashcode计算key计算出hash值
3.在通过hash & (数据的长度length - 1) 算出key在数组下标索引的位置 默认初始化长度16
4.判断索引的节点是否为树节点
5.,如果是树节点,执行红黑树的putTreeVal操作,否则就遍历链表
6.执行红黑树时判断树中是否有相同的key,如果有就替换掉对应key的value值,
7.没有就插入一个新的树节点,然后在判断是否需要扩容,如果插入的元素达到了扩容因子0.75*数组的长度 ,就进行扩容操作
8.如果遍历链表的话,一个个查找是否有相等的key,如果有就替换,没有就在链表的尾部插入一个新的节点(1.7及以前是头插法,1.8开始是尾插法)
9.链表插入新的节点后判断链表的长度是否>=8并且数组的长度>=64时,链表转化为红黑树;如果链表的长度是否>=8并且数组的长度<64则进行扩容操作,如果链表的长度是否<8就需要判断是否需要扩容
2.核心
HashMap 是基于哈希表的数据结构,用于存储键值对,其核心是将键的哈希值映射到数组索引位置,通过数组+链表(在 Java8 及之后是数组+链表+红黑树)来处理哈希冲突。
HashMap 使用键的 hashcode()方法计算哈希值,并通过 indexFor 方法确定元素在数组中的存储位置。哈希值是经过一定扰动处理的,防止哈希值分布不均匀,从而减少冲突。
HashMap 的默认初始容量为 16,负载因子为 0.75。也就是说,当存储的元素数量超过16x0.75=12 个时, HashMap 会触发扩容操作,容量x2并重新分配元素位置。这种扩容是比较耗时的操作,频繁扩容会影响性能。
3.为什么链表长度达到 8 就转成红黑树,而当长度降到 6 就转换链表
这体现了时间(链表)和空间(红黑树)平衡的思想,
一般链表的长度也不会很长,那么红黑树也不会带来明显的查询时间上的优势,反而会增加空间负担,所以通常情况下,并没有必要转为红黑树,所以就选择了概率非常小,小于千万分之一概率,也就是长度为 8 的概率,把长度 8 作为转化的默认阈值。
长度降到 6转链表是因为 转化比较耗性能,避免移除添加重复执行转化过程,所以取了6为默认阈值。有个缓冲过程
十九.Java 中有哪些集合类?
1.set
HashSet、TreeSet
2.list
ArrayList、LinkedList、Vector
3.Map
HashMap、HashTable、TreeMap
二十.Collection 和 Collections 有什么区别?
1.Collection是最基本的集合接口,Collection派生了两个子接口list和set,分别定义了两种不同的存储方式。
2.Collections是一个包装类,它包含各种有关集合操作的静态方法(对集合的搜索、排序、线程安全化等)。此类不能实例化,就像一个工具类,服务于Collection框架。
二十一.HashMap 和 Hashtable 有什么区别?
1.线程安全性:HashMap 是非线程安全的,而 Hashtable 是线程安全的。
2.性能:HashMap 性能更好,适合单线程或不需要线程安全的环境;Hashtable 由于同步的开销,性能较低。
3.null 键和值:HashMap 允许 null 键和 null 值,而 Hashtable 不允许。
4.遍历方式:HashMap 使用快速失败的 Iterator,而 Hashtable 使用的是非快速失败的 Enumeration 和 Iterator。
二十二.HashMap 和 Hashtable 有什么区别?
HashMap 性能更好。这是因为 HashMap 没有同步机制,在单线程环境中没有额外的锁竞争开销。而 Hashtable 所有方法都使用了 synchronized,导致多线程时锁竞争和上下文切换,性能下降。
二十三.为什么 HashMap 允许 null 键和值,而 Hashtable 不允许?
HashMap 的实现允许 null 键,因为它将 null 视为一个特殊的键处理,存储在索引为 0 的位置上。而 Hashtable 出于早期设计考虑,认为 null 键和 null 值在实际使用中无意义,并且在存取时可能引发歧义,因此直接抛出 NullPointerException。
二十四.在多线程环境下为什么不推荐使用 Hashtable,而推荐 ConcurrentHashMap?
Hashtable 的所有方法都进行了同步,会导致较大的锁竞争,影响性能。相对而言,ConcurrentHashMap 通过分段锁(Segmented Locking)机制,只对部分数据进行同步,大大降低了锁的争用,提高了并发性能。
二十五.HashMap 的快速失败机制是什么?Hashtable 有类似的机制吗?
- HashMap 使用快速失败的 Iterator,在遍历过程中如果发现集合被结构性修改(如增删元素),会抛出 ConcurrentModificationException。这是为了防止在并发修改时可能引起的数据不一致问题。
- Hashtable 没有这种快速失败机制,其 Enumeration 是非快速失败的,但并不保证遍历时的一致性。
二十六.如何实现一个线程安全的 HashMap?
可以通过以下几种方式实现线程安全的 HashMap:
- 使用 Collections.synchronizedMap(new HashMap<>()) 来获取一个同步的 HashMap,所有方法都会加锁。
- 使用 ConcurrentHashMap,它是一个高效的线程安全 HashMap 替代品,推荐在并发环境中使用。
二十七.当 Hashtable 和 HashMap 中的键值对数量超过负载因子时会发生什么?
- 当 HashMap 或 Hashtable 中的元素数量超过负载因子设定的阈值时,会进行扩容操作。扩容时,哈希表的容量会翻倍,元素会重新进行哈希分配,以减少冲突并提升查找效率。
- 默认情况下,HashMap 的初始容量是 16,负载因子为 0.75;Hashtable 的初始容量是 11,负载因子同样为 0.75。
二十八.如何防止 HashMap 出现并发修改问题?
- 可以通过以下几种方式防止并发修改问题:
1.使用 Collections.synchronizedMap() 将 HashMap 包装成线程安全的版本。
2.使用 ConcurrentHashMap,它可以在多线程环境下安全地执行读写操作。
3.避免在遍历时对 HashMap 进行结构性修改。如果需要在遍历时修改,可以使用 Iterator 提供的 remove() 方法。
二十九.List 和 Set 有什么区别?
- List:允许重复元素,并且保证元素的插入顺序。可以通过索引访问元素。常用的实现类有 ArrayList 和 LinkedList。
- Set:不允许重复元素,元素的顺序通常不保证(除 LinkedHashSet 外)。常用的实现类有 HashSet、LinkedHashSet 和 TreeSet。
二十九.ArrayList 和 LinkedList 有什么区别?
ArrayList:
- 基于动态数组实现,支持快速随机访问(通过索引)。
- 增加或删除元素时,若涉及到数组扩容或数组元素移动,性能较低。
- 适合查询操作较多的场景。
LinkedList:
- 基于双向链表实现,随机访问性能较差。
- 在插入和删除操作时表现更优,特别是在集合中间插入或删除元素时。
- 适合频繁插入、删除操作的场景。
三十. HashSet、LinkedHashSet 和 TreeSet 的区别?
- HashSet:基于 HashMap 实现,不保证元素顺序,插入、删除和查找操作的时间复杂度是 O(1)。
- LinkedHashSet:是 HashSet 的有序版本,维护了元素的插入顺序,性能稍逊于 HashSet,但仍提供 O(1) 的查找时间。
- TreeSet:基于红黑树(自平衡二叉树)实现,元素按自然顺序或自定义比较器排序,插入、删除、查找操作的时间复杂度是 O(log n)。
三十一. HashSet、LinkedHashSet 和 TreeSet 的区别?
Iterator:
- 可以用于遍历 Set、List 和其他 Collection。
- 只能单向遍历集合。
- 不能修改元素,只能删除元素(通过 remove() 方法)
ListIterator:
- 只能用于遍历 List。
- 可以双向遍历集合。
- 可以修改、添加和删除元素。
三十二. ArrayList 的扩容机制是怎样的?
ArrayList 的默认初始容量为 10,当集合的元素数量超过容量时,会扩展为原来容量的 1.5 倍。
三十三. Java 中实现线程安全的集合
可以使用以下方式实现线程安全的集合:
- 使用同步包装类:如 Collections.synchronizedList(new ArrayList<>())。
- 使用并发集合类:如 ConcurrentHashMap、CopyOnWriteArrayList、BlockingQueue。
- 自行实现同步:在访问集合时使用 synchronized 关键字
三十四. Iterator 和 ListIterator 的区别是什么?
Iterator:只能单向遍历集合,并且只能删除元素,不能修改。
ListIterator:可以双向遍历 List,并且可以添加、删除和修改元素。
三十五. Java 中引用有哪些?
强引用:
概述:强引用是最常见的引用类型,当一个对象被强引用所引用时,垃圾回收器将不会回收该对象,即使系统内存空间不足。
特点:只要引用存在,对象就不会被垃圾回收器回收。
场景:常用于需要长期保持对象存在的场景。
示例代码:Object obj = new Object();
软引用:
概述:软引用用于描述一些可能有用但非必需的对象。
特点:在系统将要发生内存溢出异常前,会被垃圾回收器回收。
场景:适用于实现内存敏感的缓存机制,如网页缓存、图片缓存等。
示例代码:SoftReference<Object> softRef = new SoftReference<>(new Object());
弱引用:
概述:弱引用也用于描述非必需的对象,但在垃圾回收时会被立即回收。
特点:只要发现弱引用,不管内存是否充足,都会回收对象。
场景:适用于临时数据存储,如ThreadLocal。
示例代码:WeakReference<Object> weakRef = new WeakReference<>(new Object());
虚引用:
概述:虚引用主要用于跟踪对象被垃圾回收的状态,但不能通过虚引用来获取对象的实例。
特点:在任何时候都可能被垃圾回收器回收。
场景:主要用于配合ReferenceQueue使用,跟踪对象被回收的状态。
示例代码:PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), refQueue);
网友评论