原因

  1. 焦虑。每次去面试更高的职位时候,内心总是担忧着那些面试题怎么解答。
  2. 很多问题在实际工作中并不会遇到,没有实际的解决问题经验,看过也记不住。
  3. 让你的Java知识功底更加强悍,后面的框架,微服务也好理解。

学习目标

  1. 收集常见经典的 Java 面试题。
  2. 做出答案,分享出去,和博友们进行讨论,不断进步。
  3. 扩大知识图谱,扎实基础,梳理知识脉络。
  4. 锻炼自己写文章以及排版的能力。
  5. 避免下次面试找工作的时候抓瞎。


Java 常见面试题

1. JDK、JRE 和 JVM 的区别

JRE:Java Runtime Environment(java运行时环境)。即java程序的运行时环境,包含了java虚拟机,java基础类库。

JDK:Java Development Kit(java开发工具包)。即java语言编写的程序所需的开发工具包。JDK包含了JRE,同时还包括java源码的编译器javac、监控工具jconsole、分析工具jvisualvm等。

JVM:全称Java Virtual Machine,JVM就是我们常说的java虚拟机,它是整个Java实现跨平台的最核心的部分,所有的Java程序会首先被编译为.class的类文件,这种类文件可以在虚拟机上执行,也就是说class并不直接与机器的操作系统相对应,而是经过虚拟机间接与操作系统交互,由虚拟机将程序解释给本地系统执行。

2. == 和 equals 的区别

==可以使用在基本数据类型和引用数据类型变量中。
如果比较的是基本数据类型变量,比较两个变量保存的数据是否相等(不一定类型要相同)。
如果比较的是引用数据类型变量,比较两个变量的地址值是否相等。即两个引用是否指向同一个对象实体。

equals()只能适用引用数据类型。默认情况下,比较内存地址值是否相等。
Object类中equals的定义:

public boolean equals(Object obj) {
	return (this == obj);
}

通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。
那么我们就需要对Object类中的equals()进行重写。

//手动实现
public boolean equals(Object obj) {
	if (this == obj){
		return true;
	}
	
	if(obj instanceof Customer){
		Customer cust = (Customer)obj;
		//比较两个对象的每个属性是否相同
//		if(this.age == cust.age && this.name.equals(cust.name)){
//			return true;
//		}else{
//			return false;
//		}

		//或
		return this.age == cust.age && this.name.equals(cust.name);
	}else{
		return false;
	}
}
//自动生成的
右键生成

3. 基本类型和包装类对象使用 == 和 equals进行比较的结果?

1)值不同:使用 = = 和equals() 比较都返回false。

2)值相同:

使用 = = 比较:

  • 基本类型-基本类型、基本类型-包装对象返回true
  • 包装对象-包装对象返回false
  • 缓存中去的包装对象比较返回true(JVM 缓存部分基本类型常用的包装类对象,如 Integer 缓存 -128 ~ 127 )
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j);//false

//Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],
//保存了从-128到+127范围的整数。如果我们使用自动装箱的方式,
//给Integer赋值的范围在-128到+127范围内时,可以直接使用数组中元素,不用再去new了。

Integer m = 1;
Integer n = 1;
System.out.println(m == n);//true

Integer x = 128;
Integer y = 128;
System.out.println(x == y);//false

使用equals() 比较:

  • 包装对象-基本类型返回true
  • 包装对象-包装对象返回true
//以int和Integer为例:
@Test
public void test1(){
	int i1 = 10000;
    Integer i2 = new Integer(10000);
    Integer i3 = new Integer(10000);
    System.out.println("Integer 基本类型和包装对象使用 == 比较 : " + (i1 == i2));//Integer 基本类型和包装对象使用 == 比较 : true
    System.out.println("Integer 基本类型和包装对象使用 equals 比较 : " + i2.equals(i1));//Integer 基本类型和包装对象使用 equals 比较 : true
    System.out.println("Integer 包装对象和包装对象使用 == 比较 : " + (i2 == i3));//Integer 包装对象和包装对象使用 == 比较 : false
    System.out.println("Integer 包装对象和包装对象使用 equals 比较 : " + i2.equals(i3));//Integer 包装对象和包装对象使用 equals 比较 : true
}

4. static存在的主要意义

static的主要意义是在于创建独立于具体对象的域变量或者方法。以致于即使没有创建对象,也能使用属性和调用方法。

5. Java语言有哪些特点

  • 跨平台性

所谓的跨平台性,是指软件可以不受计算机硬件和操作系统的约束而在任意计算机环境下正常运行。这是软件发展的趋势和编程人员追求的目标。之所以这样说,是因为计算机硬件的种类繁多,操作系统也各不相同,不同的用户和公司有自己不同的计算机环境偏好,而软件为了能在这些不同的环境里正常运行,就需要独立于这些平台。

而在Java语言中, Java自带的虚拟机很好地实现了跨平台性。 Java源程序代码经过编译后生成二进制的字节码是与平台无关的,但是可被Java虚拟机识别的一种机器码指令。 Java虚拟机提供了一个字节码到底层硬件平台及操作系统的屏障,使得Java语言具备跨平台性。

  • 面向对象

面向对象是指以对象为基本粒度,其下包含属性和方法。对象的说明用属性表达,而通过使用方法来操作这个对象。面向对象技术使得应用程序的开发变得简单易用,节省代码。Java是一种面向对象的语言,也继承了面向对象的诸多好处,如代码扩展、代码复用等。

  • 安全性

安全性可以分为四个层面,即语言级安全性、编译时安全性、运行时安全性、可执行代码安全性。语言级安全性指Java的数据结构是完整的对象,这些封装过的数据类型具有安全性。编译时要进行Java语言和语义的检查,保证每个变量对应一个相应的值,编译后生成Java类。运行时Java类需要类加载器载入www jxzxmr net,并经由字节码校验器校验之后才可以运行。 Java类在网络上使用时,对它的权限进行了设置,保证了被访问用户的安全性。

  • 多线程

多线程在操作系统中已得到了最成功的应用。多线程是指允许一个应用程序同时存在两个或两个以上的线程,用于支持事务并发和多任务处理。 Java除了内置的多线程技术之外,还定义了一些类、方法等来建立和管理用户定义的多线程。

  • 简单易用

Java源代码的书写不拘泥于特定的环境,可以用记事本、文本编辑器等编辑软件来实现,然后将源文件进行编译,编译通过后可直接运行,通过调试则可得到想要的结果。

6. 什么是自动装箱?什么是自动拆箱?装箱和拆箱的执行过程?

JDK 5.0新特性:自动装箱与自动拆箱
自动装箱:基本数据类型 —> 包装类
自动拆箱:包装类 —> 基本数据类型

int num1=10;
Integer num2=num1;//自动装箱
    
Integer a=10;
int b=a;//自动拆箱

装箱是通过调用包装器类的 valueOf 方法实现的
拆箱是通过调用包装器类的 xxxValue 方法实现的,xxx代表对应的基本数据类型。
如int装箱的时候自动调用Integer的valueOf(int)方法;Integer拆箱的时候自动调用Integer的intValue方法。

7. 两个对象hashCode()相同,equals() 也一定为 true吗?

不一定。
类的hashCode方法和equals方法都可以重写,返回的值完全在于自己定义。
hashCode()返回该对象的哈希值;equals()返回两个对象是否相等。

8. final、finally和finalize()区别

final 表示最终的、不可改变的。用于修饰类、方法和变量。
用final修饰的类不能被其他类所继承。
用final修饰的方法不能被重写。
修饰属性:可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化。
修饰局部变量:尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。

finally 异常处理的一部分,它只能用在try/catch语句中,finally中声明的是一定会被执行的代码。即便catch中又出现异常了,try中有return语句,catch中有return语句等情况。

finalize()是在java.lang.Object里定义的,Object的finalize方法什么都不做,对象被回收时finalized方法会被调用。
特殊情况下,可重写finalize方法,当对象被回收的时候释放一些资源。但注意,要调用super.finalize()。

9. final 与 static 的区别

都可以修饰类、方法、成员变量。

static可以修饰类的代码块,final不可以。
static不可以修饰方法内的局部变量,final可以。

static修饰表示静态或全局,被修饰的属性和方法属于类,可以用类名.静态属性 / 方法名 访问
static修饰的代码块表示静态代码块,当Java虚拟机(JVM)加载类时,就会执行该代码块,只会被执行一次
static静态变量的加载早于对象的创建。
static修饰的属性,也就是类变量,是在类加载时被创建并进行初始化,只会被创建一次。
static修饰的变量可以重新赋值
static方法中不能用this和super关键字
static方法必须被实现,而不能是抽象的abstract
static方法只能被static方法覆盖

final修饰表示常量、一旦创建不可改变。
final标记的成员变量必须在声明的同时赋值,或在该类的构造方法中赋值,不可以重新赋值。
final方法不能被子类重写。
final类不能被继承,没有子类,final类中的方法默认是final的。
final不能用于修饰构造方法。
private类型的方法默认是final类型的。

10. String对象中的replace和replaceAll的区别?

replace方法:支持字符和字符串的替换。
public String replace(char oldChar, char newChar)
public String replace(CharSequence target, CharSequence replacement)

replaceAll方法:基于正则表达式的字符串替换。
public String replaceAll(String regex, String replacement)

代码:

public static void main(String[] args) {
    String str = "Hello Java. Java is a language.";
    System.out.println(str.replace("Java.", "c++"));//打印 Hello c++ Java is a language.
    System.out.println(str.replaceAll("Java.", "c++"));//打印 Hello c++ c++is a language.
}

11. Math.round(-1.5) 等于多少?

结果是:-1。

扩展JDK中的java.lang.Math类
round:返回四舍五入,负.5小数返回较大整数,如-1.5返回-1。
ceil:返回小数所在两整数间的较大值,如-1.5返回-1。
tail:返回小数所在两整数间的较小值,如-1.5返回-2。

代码:

	Math.round(1.4)=1
	Math.round(-1.4)=-1
	Math.round(1.5)=2
	Math.round(-1.5)=-1
	Math.round(1.6)=2
	Math.round(-1.6)=-2
	
	Math.ceil(1.4)=2.0
	Math.ceil(-1.4)=-1.0
	Math.ceil(1.5)=2.0
	Math.ceil(-1.5)=-1.0
	Math.ceil(1.6)=2.0
	Math.ceil(-1.6)=-1.0
	
	Math.floor(1.4)=1.0
	Math.floor(-1.4)=-2.0
	Math.floor(1.5)=1.0
	Math.floor(-1.5)=-2.0
	Math.floor(1.6)=1.0
	Math.floor(-1.6)=-2.0

12. String 属于基础的数据类型吗?

不属于。
Java8种基础的数据类型:byte、short、char、int、long、float、double、boolean。

13. String、StringBuffer和StringBuilder的区别?

String :不可变的字符序列;底层使用char[]存储。
StringBuffer : 可变的字符序列;线程安全的,效率低;底层使用char[]存储。
StringBuilder : 可变的字符序列; jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储。
注意:作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder会改变其值。

源码分析:
String str = new String();//char[] value = new char[0];
String str1 = new String(“abc”);//char[] value = new char[]{‘a’,‘b’,‘c’};

StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];//底层创建了一个长度是16的数组
System.out.println(sb1.length());//0
sb1.append(‘a’);//value[0] = ‘a’;
sb1.append(‘b’);//value[1] = ‘b’;

StringBuffer sb2 = new StringBuffer(“abc”)😕/char[] value =new char[“abc”.length() +16];
System.out.println(sb2. Length());//3

扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。
默认情况下,扩容为原来容量的2倍 + 2,同时将原有数组中的元素复制到新的数组中。

14. 普通类和抽象类有哪些区别?

  • 抽象类不能被实例化。
  • 抽象类可以有抽象方法,抽象方法只需申明,无需实现。
  • 含有抽象方法的类必须申明为抽象类。
  • 抽象的子类必须实现抽象类中所有抽象方法,否则这个子类也是抽象类。
  • 抽象方法不能被声明为静态。
  • 抽象方法不能用private修饰。
  • 抽象方法不能用final修饰。

15. 抽象类与接口的区别?

相同点:
不能实例化;都可以包含抽象方法的。

不同点:

  • 抽象类一定有构造方法,创建子类对象时被调用;接口中不能有构造方法。
  • 抽象类通过extends来继承,只能继承一个抽象类(单继承);接口通过implements来实现,允许实现多个接口(多继承),用逗号隔开。
  • 抽象类中可以有普通成员变量;接口中没有普通成员变量。
  • 抽象类中可以包含非抽象普通方法;接口中的所有方法必须都是抽象的。
  • 抽象类中的抽象方法的访问权限可以是 public、protected 和 default;接口中的抽象方法只能是 public 类型的,并且默认即为 public abstract 类型。
  • 抽象类中可以包含静态方法;JDK1.8 前接口中不能包含静态方法,JDK1.8 及以后可以包含已实现的静态方法。
  • 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量可以是任意访问权限;接口中变量默认且只能是 public static final 类型。
  • 接口不可以实现接口,但可以继承接口,并且可以继承多个接口,用逗号隔开。

17. Java4种访问修饰符权限的区别?

在这里插入图片描述

18. Java中的 << << >>> 是什么?

<<  表示左移,不分正负数,低位补0
>>  表示右移,如果该数为正,则高位补0,若为负数,则高位补1
>>> 表示无符号右移,也叫逻辑右移,即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0

代码:

	16 << 1 : 32
	16 >> 3 : 2
	16 >> 10 : 0
	1 >> 1 : 0
	16 >>> 2 : 4
	-16 >> 2 : -4
	-16 << 2 : -64
	-16 >>> 2 : 1073741820

19. throw 和 throws 的区别?

throw:

  • 表示方法内抛出某种异常对象。
  • 如果异常对象是非 RuntimeException 则需要在方法申明时加上该异常的抛出 即需要加上 throws 语句 或者 在方法体内 try catch 处理该异常,否则编译报错。
  • 执行到 throw 语句则后面的语句块不再执行。

throws:

  • 方法的定义上使用 throws 表示这个方法可能抛出某种异常。
  • 需要由方法的调用者进行异常处理。

20. 常见的异常类有哪些?

java.lang.Throwable
	|-----java.lang.Error:一般不编写针对性代码进行处理
	|-----java.lang,Exception:可以进行异常的处理
		|-----编译时异常(checked)
			|-----IOException
				|-----FileNotFoundException
			|-----ClassNotFoundException
		|-----运行时异常(unchecked、RuntimeException)
			|-----NullPointException:空指针异常
			|-----ArrayIndexOutOfBoundsException:数组角标越界
			|-----ClassCastException:类型转换异常
			|-----NumberFormatException
			|-----InputMismatchException
			|-----ArithmeticException:算数异常

21. 重载和重写有什么区别?

  • 从定义上:

重载:在同一个类中声明多个方法名相同形参列表不同的方法,彼此之间构成了重载,构造器也可以重载。
重写:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作。

  • 具体规则:

重载的规则:
①必须具有不同的参数列表。
②可以有不同的访问修饰符。
③可以抛出不同的异常。

重写方法的规则:
①参数列表必须完全与被重写的方法相同,否则不能称其为重写而是重载。
②返回的类型必须一直与被重写的方法的返回类型相同,否则不能称其为重写而是重载。
③访问修饰符的限制一定要大于被重写方法的访问修饰符。
④重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常。

  • 类的关系上:

重写是子类和父类之间的关系,是垂直关系。
重载是同一个类中方法之间的关系,是水平关系。

  • 形态上:

重载:不表现为多态性。
重写:表现为多态性。

22. 如何解决线程安全问题, 有几种方式?分别是什么?

4种
1.继承Thread类
2.实现Runnable接口
3.实现Callable接口
4.使用线程池

23. synchronized 与 Lock的异同?

相同:

  • 二者都可以解决线程安全问题。

不同:

  • Lock是一个接口,而synchronized是java中的关键字,synchronized是内置的语言实现;
  • synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unlock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
  • Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
  • 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到;
  • Lock可以提高多个线程进行读操作的效率;
  • synchronized能锁住类、方法和代码块,而Lock是块范围内的。

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

24. sleep() 和wait()的异同?

相同点:

  • 一旦执行方法,都可以使得当前的线程进入阻塞状态。

不同点:

  • 两个方法声明的位置不同: Thread类 中声明sleep(),object类 中声明wait()。
  • 调用的要求不同: sleep()可以在任何需要的场景下调用。 wait()必须使用在同步方法或同步块中。
  • 关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait() 会释放锁。

25. wait()、notify()和notifyAll()的异同?

相同点:

  • 三个方法必须使用在同步代码块或同步方法中。
  • 三个方法的调用者必须是同步代码块或同步方法中的同步监视器中的,否则,会出现IlLegaLMonitorStateException异常。
  • 三个方法是定义在java.lang.object类中。

不同点:

  • wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
  • notify():一旦执行此方法,就会唤醒被wait的一个线程。 如果有多个线程被wait,就唤醒优先级高的那个。
  • notifyALl():一旦执行此方法,就会唤醒所有被wait的线程。

26. 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?

  • call() 可以有返回值的。
  • call() 可以抛出异常,被外面的操作捕获,获取异常的信息。
  • Callable是支持泛型的。

27. 比较ArrayList、LinkedList、Vector三者的异同?

相同:

  • 三个类都实现了list接口,存储数据的特点相同,有序,可重复。

不同:

  • ArrayList:作为List接口的主要实现类;jdk1.2,线程不安全的,效率高;底层使用Object[] elementData存储。
  • LinkedList:jdk1.2:底层使用双向链表存储,对于频繁的插入、删除操作比ArrayList的效率。
  • Vector:作为List接口的古老的实现类;jdk1.0,线程安全的,效率低;底层使用Object[] elementData存储。

如果有收获!!! 希望老铁们来个三连,点赞、收藏、转发
创作不易,别忘点个赞,可以让更多的人看到这篇文章,顺便鼓励我写出更好的博客
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐