2023Java高频必背面试题基础篇合集

时间:2022-11-11 01:24:03

1、Java 语⾔具有哪些特点?

(1)Java 为纯⾯向对象的语⾔。它能够直接反应现实⽣活中的对象。
(2)具有平台⽆关性。Java 利⽤ Java 虚拟机运⾏字节码,⽆论是在 Windows、Linux 还是 MacOS 等其它平台对 Java 程序进⾏编译,编译后的程序可在其它平台运⾏。
(3)很好的可移植性。Java 为解释型语⾔,编译器把 Java 代码编译成平台⽆关的中间代码,然后在 JVM 上解释运⾏,具有很好的可移植性。
(4)Java 提供了很多内置类库。如对多线程⽀持,对⽹络通信⽀持,最重要的⼀点是提供了垃圾回收器。
(5)Java 具有较好的安全性和健壮性。Java 提供了异常处理和垃圾回收机制,去除了 C++中难以理解的指针特性。

2、JDK 与 JRE 有什么区别?

JDK:Java 开发⼯具包(Java Development Kit),提供了 Java 的开发环境和运⾏环境
JRE:Java 运⾏环境(Java Runtime Environment),提供了 Java 运⾏所需的环境
JDK 包含了 JRE。如果只运⾏ Java 程序,安装 JRE 即可。要编写 Java 程序需安装 JDK。

3、简述 Java 基本数据类型。

byte: 占⽤ 1 个字节,取值范围-128 ~ 127;
short: 占⽤ 2 个字节,取值范围-215 ~ 215-1;
int:占⽤ 4 个字节,取值范围-231 ~ 231-1;
long:占⽤ 8 个字节;
float:占⽤ 4 个字节;
double:占⽤ 8 个字节;
char: 占⽤ 2 个字节;
boolean:占⽤⼤⼩根据实现虚拟机不同有所差异。

4、简述⾃动装箱拆箱。

对于 Java 基本数据类型,均对应⼀个包装类。
(1)装箱就是⾃动将基本数据类型转换为包装器类型,如 int->Integer;
(2)拆箱就是⾃动将包装器类型转换为基本数据类型,如 Integer->int。

5、简述 Java 访问修饰符。

default: 默认访问修饰符,在同⼀包内可⻅;
private: 在同⼀类内可⻅,不能修饰类;
protected : 对同⼀包内的类和所有⼦类可⻅,不能修饰类;
public: 对所有类可⻅。

6、构造⽅法、成员变量初始化以及静态成员变量三者的初始化顺序?

先后顺序:静态成员变量、成员变量、构造⽅法
详细的先后顺序:⽗类静态变量、⽗类静态代码块、⼦类静态变量、⼦类静态代码块、⽗类⾮静态变量、⽗类⾮静态代码块、⽗类构造函数、⼦类⾮静态变量、⼦类⾮静态代码块、⼦类构造函数。

7、Java 代码块执⾏顺序。

⽗类静态代码块(只执⾏⼀次);⼦类静态代码块(只执⾏⼀次);⽗类构造代码块;⽗类构造函数;⼦类构造代码块;⼦类构造函数;普通代码块。

8、⾯向对象的三⼤特性?

(1)继承:对象的⼀个新类可以从现有的类中派⽣,派⽣类可以从它的基类那继承⽅法和实例变量,且派⽣类可以修改或新增新的⽅法使之更适合特殊的需求;
(2)封装:将客观事物抽象成类,每个类可以把⾃身数据和⽅法只让可信的类或对象操作,对不可信的进⾏信息隐藏;
(3)多态:允许不同类的对象对同⼀消息作出响应。不同对象调⽤相同⽅法即使参数也相同,最终表现⾏为是不⼀样的。

9、为什么 Java 语⾔不⽀持多重继承?

为了程序的结构能够更加清晰从⽽便于维护。假设 Java 语⾔⽀持多重继承,类 C 继承⾃类 A 和类 B,如果类 A 和 B 都有⾃定义的成员⽅法 f() ,那么当代码中调⽤类 C 的 f() 会产⽣⼆义性。
Java 语⾔通过实现多个接⼝间接⽀持多重继承,接⼝由于只包含⽅法定义,不能有⽅法的实现,类 C 继承接⼝ A 与接⼝ B 时即使它们都有⽅法 f() ,也不能直接调⽤⽅法,需实现具体的 f() ⽅法才能调⽤,不会产⽣⼆义性。
多重继承会使类型转换、构造⽅法的调⽤顺序变得复杂,会影响到性能。

10、简述 Java 的多态。

Java 多态可以分为编译时多态运⾏时多态
编译时多态主要指⽅法的重载,即通过参数列表的不同来区分不同的⽅法。
运⾏时多态主要指继承⽗类和实现接⼝时,可使⽤⽗类引⽤指向⼦类对象
运⾏时多态的实现:主要依靠⽅法表,⽅法表中最先存放的是 Object 类的⽅法,接下来是该类的⽗类的⽅法,最后是该类本身的⽅法。如果⼦类改写了⽗类的⽅法,那么⼦类和⽗类的那些同名⽅法共享⼀个⽅法表项,都被认作是⽗类的⽅法。因此可以实现运⾏时多态。

11、Java 提供的多态机制?

Java 提供了两种⽤于多态的机制,分别是重载与覆盖。
重载:重载是指同⼀个类中有多个同名的⽅法,但这些⽅法有不同的参数,在编译期间就可以确定调⽤哪个
⽅法。
覆盖:覆盖是指派⽣类重写基类的⽅法,使⽤基类指向其⼦类的实例对象,或接⼝的引⽤变量指向其实现类
的实例对象,在程序调⽤的运⾏期根据引⽤变量所指的具体实例对象调⽤正在运⾏的那个对象的⽅法,即需
要到运⾏期才能确定调⽤哪个⽅法。

12、重载与覆盖的区别?

(1)覆盖是⽗类与⼦类之间的关系,是垂直关系;重载是同⼀类中⽅法之间的关系,是⽔平关系;
(2)覆盖只能由⼀个⽅法或⼀对⽅法产⽣关系;重载是多个⽅法之间的关系;
(3)覆盖要求参数列表相同;重载要求参数列表不同;
(4)覆盖中,调⽤⽅法体是根据对象的类型来决定的,⽽重载是根据调⽤时实参表与形参表来对应选择⽅法
体;
(5)重载⽅法可以改变返回值的类型,覆盖⽅法不能改变返回值的类型。

13、接⼝和抽象类的相同点和不同点?

相同点:
(1)都不能被实例化。
(2)接⼝的实现类或抽象类的⼦类需实现接⼝或抽象类中相应的⽅法才能被实例化。
不同点:
(1)接⼝只能有⽅法定义,不能有⽅法的实现,⽽抽象类可以有⽅法的定义与实现。
(2)实现接⼝的关键字为 implements,继承抽象类的关键字为 extends。⼀个类可以实现多个接⼝,只能
继承⼀个抽象类。
(3)当⼦类和⽗类之间存在逻辑上的层次结构,推荐使⽤抽象类,有利于功能的累积。当功能不需要,希望
⽀持差别较⼤的两个或更多对象间的特定交互⾏为,推荐使⽤接⼝。使⽤接⼝能降低软件系统的耦合
度,便于⽇后维护或添加删除⽅法。

14、简述抽象类与接⼝的区别

抽象类:体现的是 is-a 的关系,如对于 man is a person,就可以将 person 定义为抽象类。
接⼝:体现的是 can 的关系。是作为模板实现的。如设置接⼝ fly,plane 类和 bird 类均可实现该接⼝。
⼀个类只能继承⼀个抽象类,但可以实现多个接⼝。

15、简述内部类及其作⽤

成员内部类:作为成员对象的内部类。可以访问 private 及以上外部类的属性和⽅法。外部类想要访问内部类属性或⽅法时,必须要创建⼀个内部类对象,然后通过该对象访问内部类的属性或⽅法。外部类也可访问 private 修饰的内部类属性。
局部内部类:存在于⽅法中的内部类。访问权限类似局部变量,只能访问外部类的 final 变量。
匿名内部类:只能使⽤⼀次,没有类名,只能访问外部类的 final 变量。
静态内部类:类似类的静态成员变量。

16、Java 语⾔中关键字 static 的作⽤是什么?

static 的主要作⽤有两个:
(1)为某种特定数据类型或对象分配与创建对象个数⽆关的单⼀的存储空间。
(2)使得某个⽅法或属性与类⽽不是对象关联在⼀起,即在不创建对象的情况下可通过类直接调⽤⽅法或使⽤类的属性。
static 具体可分为 4 种使⽤⽅式:
(1)修饰成员变量。⽤ static 关键字修饰的静态变量在内存中只有⼀个副本。只要静态变量所在的类被加载,这个静态变量就会被分配空间,可以使⽤“类.静态变量”和“对象.静态变量”的⽅法使⽤。
(2)修饰成员⽅法。static 修饰的⽅法⽆需创建对象就可以被调⽤。static ⽅法中不能使⽤ this 和 super 关键字,不能调⽤⾮ static ⽅法,只能访问所属类的静态成员变量和静态成员⽅法。
(3)修饰代码块。JVM 在加载类的时候会执⾏ static 代码块。static 代码块常⽤于初始化静态变量。static代码块只会被执⾏⼀次。
(4)修饰内部类。static 内部类可以不依赖外部类实例对象⽽被实例化。静态内部类不能与外部类有相同的名字,不能访问普通成员变量,只能访问外部类中的静态成员和静态成员⽅法。

17、为什么要把 String 设计为不可变?

(1)节省空间:字符串常量存储在 JVM 的字符串池中可以被⽤户共享。
(2)提⾼效率:String 可以被不同线程共享,是线程安全的。在涉及多线程操作中不需要同步操作。
(3)安全:String 常被⽤于⽤户名、密码、⽂件名等使⽤,由于其不可变,可避免⿊客⾏为对其恶意修改。

18、简述 String/StringBuffer 与 StringBuilder。

(1)String 类采⽤利⽤ final 修饰的字符数组进⾏字符串保存,因此不可变。如果对 String 类型对象修改,需要新建对象,将⽼字符和新增加的字符⼀并存进去。
(2)StringBuffer,采⽤⽆ final 修饰的字符数组进⾏保存,可理解为实现线程安全的 StringBuilder。
(3)StringBuilder,采⽤⽆ final 修饰的字符数组进⾏保存,因此可变。但线程不安全。

19、判等运算符“==” 与equals的区别?

(1)“== ” ⽐较的是引⽤,equals ⽐较的是内容。
(2)如果变量是基础数据类型,“== ”⽤于⽐较其对应值是否相等。如果变量指向的是对象,“== ”⽤于⽐较两个对象是否指向同⼀块存储空间。
(3)equals 是 Object 类提供的⽅法之⼀,每个 Java 类都继承⾃ Object 类,所以每个对象都具有 equals 这个⽅法。Object 类中定义的 equals ⽅法内部是直接调⽤ == ⽐较对象的。但通过覆盖的⽅法可以让它不是⽐较引⽤⽽是⽐较数据内容。

20、简述 Object 类常⽤⽅法。

(1)hashCode:通过对象计算出的散列码。⽤于 map 型或 equals ⽅法。需要保证同⼀个对象多次调⽤该⽅法,总返回相同的整型值。
(2)equals:判断两个对象是否⼀致。需保证 equals ⽅法相同对应的对象 hashCode 也相同。
(3)toString: ⽤字符串表示该对象。
(4)clone:深拷⻉⼀个对象。

21、Java 中⼀维数组和⼆维数组的声明⽅式?

⼀维数组的声明⽅式:

type arrayName[]
type[] arrayName

⼆维数组的声明⽅式:

type arrayName[][]
type[][] arrayName
type[] arrayName[]

其中 type 为基本数据类型或类,arrayName 为数组名字。

22、简述 Java 异常的分类。

Java 异常分为 Error(程序⽆法处理的错误)和 Exception(程序本身可以处理的异常)。这两个类均继承 Throwable。
(1)Error 常⻅的有 *Error、OutOfMemoryError 等等。
(2)Exception 可分为运⾏时异常和⾮运⾏时异常。对于运⾏时异常,可以利⽤ try catch 的⽅式进⾏处理,也可以不处理。对于⾮运⾏时异常,必须处理,不处理的话程序⽆法通过编译。

23、简述 throw 与 throws 的区别。

(1)throw ⼀般是⽤在⽅法体的内部,由开发者定义当程序语句出现问题后主动抛出⼀个异常。
(2)throws ⼀般⽤于⽅法声明上,代表该⽅法可能会抛出的异常列表。

24、出现在 Java 程序中的 finally 代码块是否⼀定会执⾏?

(1)当遇到下⾯情况不会执:
当程序在进⼊ try 语句块之前就出现异常时会直接结束;
当程序在 try 块中强制退出时,如使⽤ System.exit(0),也不会执⾏ finally 块中的代码。
(2)其它情况下,在 try/catch/finally 语句执⾏的时候,try 块先执⾏,当有异常发⽣,catch 和 finally 进⾏处理后程序就结束了,当没有异常发⽣,在执⾏完 finally 中的代码后,后⾯代码会继续执⾏。值得注意的是,当 try/catch 语句块中有 return 时,finally 语句块中的代码会在 return 之前执⾏。如果try/catch/finally 块中都有 return 语句,finally 块中的 return 语句会覆盖 try/catch 模块中的 return 语句。

25、final、finally 和 finalize 的区别是什么?

(1)final ⽤于声明属性、⽅法和类,分别表示属性不可变、⽅法不可覆盖、类不可继承。
(2)finally 作为异常处理的⼀部分,只能在 try/catch 语句中使⽤,finally 附带⼀个语句块⽤来表示这个语句最终⼀定被执⾏,经常被⽤在需要释放资源的情况下。
(3)finalize 是 Object 类的⼀个⽅法,在垃圾收集器执⾏的时候会调⽤被回收对象的 finalize()⽅法。当垃圾回收器准备好释放对象占⽤空间时,⾸先会调⽤ finalize()⽅法,并在下⼀次垃圾回收动作发⽣时真正回收对象占⽤的内存。

26、简述泛型。

泛型,即“参数化类型”,解决不确定对象具体类型的问题。在编译阶段有效。在泛型使⽤过程中,操作的数据类型被指定为⼀个参数,这种参数类型在类中称为泛型类、接⼝中称为泛型接⼝和⽅法中称为泛型⽅法。

27、简述泛型擦除。

Java 编译器⽣成的字节码是不包涵泛型信息的,泛型类型信息将在编译处理是被擦除,这个过程被称为泛型擦除。

28、简述注解。

Java 注解⽤于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执⾏,但也有⼀些类型的注解实际上可以⽤于这⼀⽬的。
其可以⽤于提供信息给编译器,在编译阶段时给软件提供信息进⾏相关的处理,在运⾏时处理写相应代码,做对应操作。

29、简述元注解。

元注解可以理解为注解的注解,即在注解中使⽤,实现想要的功能。其具体分为:
@Retention: 表示注解存在阶段是保留在源码,还是在字节码(类加载)或者运⾏期(JVM 中运⾏)。
@Target:表示注解作⽤的范围。
@Documented:将注解中的元素包含到 Javadoc 中去。
@Inherited:⼀个被@Inherited 注解了的注解修饰了⼀个⽗类,如果他的⼦类没有被其他注解修饰,则它的⼦类也继承了⽗类的注解。
@Repeatable:被这个元注解修饰的注解可以同时作⽤⼀个对象多次,但是每次作⽤注解⼜可以代表不同的含义。

30、简述 Java 中 Class 对象。

java 中对象可以分为实例对象和 Class 对象,每⼀个类都有⼀个 Class 对象,其包含了与该类有关的信
息。
获取 Class 对象的⽅法:

Class.forName(“类的全限定名”)
实例对象.getClass()
类名.class

31、Java 反射机制是什么?

Java 反射机制是指在程序的运⾏过程中可以构造任意⼀个类的对象、获取任意⼀个类的成员变量和成员⽅法、获取任意⼀个对象所属的类信息、调⽤任意⼀个对象的属性和⽅法。反射机制使得 Java 具有动态获取程序信息和动态调⽤对象⽅法的能⼒。可以通过以下类调⽤反射 API。
(1)Class 类:可获得类属性⽅法;
(2)Field 类:获得类的成员变量;
(3)Method 类:获取类的⽅法信息;
(4)Construct 类:获取类的构造⽅法等信息。

32、序列化是什么?

序列化是⼀种将对象转换成字节序列的过程,⽤于解决在对对象流进⾏读写操作时所引发的问题。序列化可以将对象的状态写在流⾥进⾏⽹络传输,或者保存到⽂件、数据库等系统⾥,并在需要的时候把该流读取出来重新构造成⼀个相同的对象。

33、简述 Java 序列化与反序列化的实现。

(1)序列化:将 java 对象转化为字节序列,由此可以通过⽹络对象进⾏传输。
(2)反序列化:将字节序列转化为 java 对象。
(3)具体实现:实现 Serializable 接⼝,或实现 Externalizable 接⼝中的 writeExternal()与 readExternal()⽅法。

34、简述 Java 的 List。

List 是⼀个有序队列,在 Java 中有两种实现⽅式:
(1)ArrayList 使⽤数组实现,是容量可变的⾮线程安全列表,随机访问快,集合扩容时会创建更⼤的数组,把原有数组复制到新数组。
(2)LinkedList 本质是双向链表,与 ArrayList 相⽐插⼊和删除速度更快,但随机访问元素很慢。

35、Java 中线程安全的基本数据结构有哪些?

(1)HashTable: 哈希表的线程安全版,效率低;
(2)ConcurrentHashMap:哈希表的线程安全版,效率⾼,⽤于替代 HashTable;
(3)Vector:线程安全版 Arraylist;
(4)Stack:线程安全版栈;
(5)BlockingQueue 及其⼦类:线程安全版队列。

36、简述 Java 的 Set。

Set 即集合,该数据结构不允许元素重复且⽆序。Java 对 Set 有三种实现⽅式:
(1)HashSet 通过 HashMap 实现,HashMap 的 Key 即 HashSet 存储的元素,Value 系统⾃定义⼀个名为PRESENT 的 Object 类型常量。判断元素是否相同时,先⽐较 hashCode,相同后再利⽤ equals ⽐较,查询 O(1);
(2)LinkedHashSet 继承⾃ HashSet,通过 LinkedHashMap 实现,使⽤双向链表维护元素插⼊顺序;
(3)TreeSet 通过 TreeMap 实现的,底层数据结构是红⿊树,添加元素到集合时按照⽐较规则将其插⼊合适的位置,保证插⼊后的集合仍然有序。查询 O(logn)。

37、简述 Java 的 HashMap。

(1)JDK8 之前底层实现是数组 + 链表,JDK8 改为数组 + 链表/红⿊树。主要成员变量包括存储数据的 table 数组、元素数量 size、加载因⼦ loadFactor。HashMap 中数据以键值对的形式存在,键对应的 hash 值⽤来计算数组下标,如果两个元素 key 的 hash 值⼀样,就会发⽣哈希冲突,被放到同⼀个链表上。
(2)table 数组记录 HashMap 的数据,每个下标对应⼀条链表,所有哈希冲突的数据都会被存放到同⼀条链表,Node/Entry 节点包含四个成员变量:key、value、next 指针和 hash 值。在 JDK8 后链表超过 8 会转化为红⿊树。
(3)若当前数据/总数据容量>负载因⼦,Hashmap 将执⾏扩容操作。默认初始化容量为 16,扩容容量必须是 2的幂次⽅、最⼤容量为 1<< 30 、默认加载因⼦为 0.75。

38、为何 HashMap 线程不安全?

(1)在 JDK1.7 中,HashMap 采⽤头插法插⼊元素,因此并发情况下会导致环形链表,产⽣死循环。
(2)虽然 JDK1.8 采⽤了尾插法解决了这个问题,但是并发下的 put 操作也会使前⼀个 key 被后⼀个 key 覆盖。
(3)由于 HashMap 有扩容机制存在,也存在 A 线程进⾏扩容后,B 线程执⾏ get ⽅法出现失误的情况。

39、简述 Java 的 TreeMap。

TreeMap 是底层利⽤红⿊树实现的 Map 结构,底层实现是⼀棵平衡的排序⼆叉树,由于红⿊树的插⼊、删除、遍历时间复杂度都为 O(logN),所以性能上低于哈希表。但是哈希表⽆法提供键值对的有序输出,红⿊树可以按照键的值的⼤⼩有序输出。

40、ArrayList、Vector 和 LinkedList 有什么共同点与区别?

(1)ArrayList、Vector 和 LinkedList 都是可伸缩的数组,即可以动态改变⻓度的数组。
(2)ArrayList 和 Vector 都是基于存储元素的 Object[] array 来实现的,它们会在内存中开辟⼀块连续的空间来存储,⽀持下标、索引访问。但在涉及插⼊元素时可能需要移动容器中的元素,插⼊效率较低。当存储元素超过容器的初始化容量⼤⼩,ArrayList 与 Vector 均会进⾏扩容。
(3)Vector 是线程安全的,其⼤部分⽅法是直接或间接同步的。ArrayList 不是线程安全的,其⽅法不具有同步性质。LinkedList 也不是线程安全的。
(4)LinkedList 采⽤双向列表实现,对数据索引需要从头开始遍历,因此随机访问效率较低,但在插⼊元素的时候不需要对数据进⾏移动,插⼊效率较⾼。

41、HashMap 和 Hashtable 有什么区别?

(1)HashMap 是 Hashtable 的轻量级实现,HashMap 允许 key 和 value 为 null,但最多允许⼀条记录的key 为 null.⽽ HashTable 不允许。
(2)HashTable 中的⽅法是线程安全的,⽽ HashMap 不是。在多线程访问 HashMap 需要提供额外的同步机制。
(3)Hashtable 使⽤ Enumeration 进⾏遍历,HashMap 使⽤ Iterator 进⾏遍历。

42、如何决定使⽤ HashMap 还是 TreeMap?

如果对 Map 进⾏插⼊、删除或定位⼀个元素的操作更频繁,HashMap 是更好的选择。如果需要对 key 集合进⾏有序的遍历,TreeMap 是更好的选择。

43、HashSet 中,equals 与 hashCode 之间的关系?

equals 和 hashCode 这两个⽅法都是从 object 类中继承过来的,equals 主要⽤于判断对象的内存地址引⽤是否是同⼀个地址;hashCode 根据定义的哈希规则将对象的内存地址转换为⼀个哈希码。HashSet 中存储的元素是不能重复的,主要通过 hashCode 与 equals 两个⽅法来判断存储的对象是否相同:
(1)如果两个对象的 hashCode 值不同,说明两个对象不相同;
(2)如果两个对象的 hashCode 值相同,接着会调⽤对象的 equals ⽅法,如果 equlas ⽅法的返回结果为true,那么说明两个对象相同,否则不相同。

44、fail-fast 和 fail-safe 迭代器的区别是什么?

(1)fail-fast 直接在容器上进⾏,在遍历过程中,⼀旦发现容器中的数据被修改,就会⽴刻抛出
ConcurrentModificationException 异常从⽽导致遍历失败。常⻅的使⽤ fail-fast ⽅式的容器有HashMap 和 ArrayList 等。
(2)fail-safe 这种遍历基于容器的⼀个克隆。因此对容器中的内容修改不影响遍历。常⻅的使⽤ fail-safe ⽅式遍历的容器有 ConcurrentHashMap 和 CopyOnWriteArrayList。

45、Collection 和 Collections 有什么区别?

(1)Collection 是⼀个集合接⼝,它提供了对集合对象进⾏基本操作的通⽤接⼝⽅法,所有集合都是它的⼦类,⽐如 List、Set 等。
(2)Collections 是⼀个包装类,包含了很多静态⽅法、不能被实例化,⽽是作为⼯具类使⽤,⽐如提供的排序⽅法:Collections.sort(list);提供的反转⽅法:Collections.reverse(list)。

46、形参与实参区别。

(1)实参(argument)
全称为"实际参数"是在调用时传递给函数的参数. 实参可以是常量、变量、表达式、函数等, 无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。 因此应预先用赋值,输入等办法使实参获得确定值。
(2)形参(parameter)
全称为"形式参数" 由于它不是实际存在变量,所以又称虚拟变量。是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数.在调用函数时,实参将赋值给形参。因而,必须注意实参的个数,类型应与形参一一对应,并且实参必须要有确定的值。