提示

Java 面试宝典。@ermo

# 1 Java 基础

# 1.1 语法基础

# 什么是面向对象?

面向对象编程是以对象为核心,每个对象内含有自己的属性和行为,然后多个对象组成一个功能,这种编程方式易扩展,降低耦合。

面向过程编程是以事件为核心,将一个个步骤编写成函数,然后通过控制代码进行顺序执行,从而完成一个功能。

# 面向对象的特性是什么?

面向对象的三大特性是继承、封装、多态,详细了解可以参考:面向对象 章节。

封装,将类的属性私有化(private 修饰符),通过该类提供的方法来访问私有信息。使用者无需关心该类的内部实现细节。

就像用户想吃饭就去找厨师(类),而不需要关心食材、厨具以及厨艺(隐藏的属性)。

封装的好处:

  • 隐藏内部实现细节,提高程序安全性
  • 降低耦合:降低程序的修改成本,便于对属性的统一管理与测试类内部结构可以自由修改
  • 对成员进行精确的控制:通过方法访问成员提高代码灵活度,不只是简单的赋值操作

继承,继承实现了 IS-A 的关系,使用继承可以将一类事务的相同属性和行为放到父类中,子类只需要关注一些独有的特点即可。

继承遵循里氏替换原则,子类对象必须能够替换掉所有父类对象。

多态,一个行为(方法)具有多种不同的表现形式,就是多态。

多态的好处是:

  • 可扩展性,一个方法或接口可以让不同的类调用,如果要新增一种新的类型,无需修改原有代码
  • 降低耦合,当前行为无需知道类的具体调用类型,只知道该类有这个方法即可

实现多态的三个必要条件是:继承、重写、向上转型。

# 简述 JDK、JRE、JVM 三者的区别?

JVM 就是 Java 虚拟机,之所以称为“虚拟”机就是因为它是不存在的,定义了可以执行 Java 字节码的运行环境所具备的必要条件。JVM 是依赖操作系统的,不同的操作系统都要有不同的 JVM 实现,Java 语言本身是不依赖于操作系统的,这也是早期 Java 火起来的一个原因

JRE 通常称为 Java 运行环境,JRE 一般面向的是 Java 程序的使用者,而不是开发者。只安装 JRE 的系统只能运行编译好的 Java 程序,不能编译和调试 Java 程序。JRE 与 JVM 的区别是,前者真正存在的,包含 JVM 的标准实现和 Java 核心类库。

JDK 通常称为 Java 开发工具集或 Java 开发工具包,提供了 Java 开发环境和运行环境。JDK 是面向开发人员的,JDK 除了包括 JRE 中 JVM 实现和核心类库外,还包含例如 javac 这样的开发工具。JDK 是 Java 语言的核心,开发人员在 Oracle 官网下载的通常就是 JDK 工具包。

# == 和 equals() 的区别?

== 作用于基本类型的时候,比较的是值是否相等。

== 作用于引用类型的时候,比较的内存引用地址是否相等。

equals 是 Object 的方法,所以基本类型是不能使用 equals 方法的,只有引用类型才可以使用此方法。在开发过程中,一般要覆写 equals 类,比较对象中具体的存储值,而不是内存地址值,下面是 Object.equals 方法的源代码。

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

# 对 hashCode() 和 equals() 的理解?

首先 hashCode() 和 equals() 都是 Object 类的方法,hashCode 称为哈希值,也叫做散列码,就是一个整数。

调用 hashCode 方法生成的散列码主要用于比较2个对象是否重复。比如在 HashSet 类中的 add 方法中,就会先调用 hashCode() 判断对象是否重复。HashSet 底层数据结构是使用到了 HashMap 中的 key,value 是一个固定值。

在添加方法中先调用 hashCode() 判断散列表中是否存在当前散列值,如果不存在,程序就认为当前 HashSet 中没有这个对象。如果散列值存在,会再调用对象的 equals() 方法比较对象中具体的值是否相同,如果不相同就会散列到其他位置,如果相同就认为 HashSet 中存在当前对象,不会重复添加。通过 hashCode() 大幅度提高系统的执行效率,减少 equals() 的执行次数。

所以,如果2个对象的 hashCode 相同,也不能完全代表2个对象相同,需要再次用 equals() 去比较具体的值。如果2个对象相同,那么二者之间的 hashCode 一定相同。

开发过程中,如果 equals() 方法被覆写,hashCode() 方法也一定要被覆写。对象的默认 hashCode() 方法是对堆上的对象产生独特的值,如果不覆写 hashCode(),当前类的2个对象一定不会相等。

# final、finalize 和 finally 的不同?

final 是一个修饰符,可以作用到变量、方法和类上:

  • 被 final 修饰的基础变量不可更改,可以将 final 作用到成员变量上,这样的变量必须在构建对象的时候初始化,并且一经初始化后不可更改
  • 被 final 修饰的方法不能被重写
  • 被 final 修饰的类不可变,同时也不能被继承,JDK 中基础类对应的包装类全部使用 final 修饰,比如 Integer、Float 和 String 等

finally 在 try/catch 语句块后使用并且附带一个代码块,在 finally 代码块中的程序一定会被执行。try 不能单独存在,try 可以 catch 或者 finally 之一一起使用,一般是三者一起结合使用。

finalize() 是 Object 类中的一个成员方法,这个方法在 gc 启动,该对象被回收的时候被调用,系统在对象被 gc 回收前做必要的清理工作。

# 简述 String、StringBuffer、StringBuilder 的区别?

三者都是字符串相关的操作类。String 类被 final 修饰是不可变的,每次对 String 的操作都会生成一个新的对象,StringBuffer 和 StringBuilder 的操作是当前对象的行为,不会重新创建新的对象,所以如果有频繁操作字符串的场景应该使用后面2个类。

从线程安全角度来看,String 每次都是创建新的对象,不会有多线程安全问题。StringBuffer 的方法上都有 synchronized 修饰,是线程安全的,StringBuilder 是线程不安全的。三者的性能从快到慢依次是 StringBuilder、StringBuffer、String。

具体字符串的不可变可以参考:字符串的不可变

# 简述重载和重写的区别?

重载定义在同一个类中,方法名相同,形参不同,方法返回值和访问修饰符可以不同,发生在编译时。

重写定义在父类和子类中,方法名和形参相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类。父类方法使用 private 和 final 修饰符不能被重写。

# 简述接口和抽象类的区别?

类只能继承一个抽象类,却可以实现多个接口。

抽象类可以有普通成员变量,接口没有普通成员变量。

抽象类可以有构造方法,接口没有构造方法。

抽象类中的静态成员变量可以是任意访问类型,接口只能是(public static final)。

抽象类可以不定义抽象方法,同时可以有普通方法;接口中都是抽象方法,JDK8 之后可以有 default 方法,JDK9 之后可以有 private 方法。

抽象类可以有静态方法;接口在 JDK8 之前不能有静态方法,JDK8 之后可以有静态方法,静态方法只能被接口调用。

抽象类子类可以继承,表达的是 IS-A 的关系,比如 Cat is Animal;实现类可以继承接口,表达的是 LIKE-A 的关系,比如 Bird like Plane。

# switch case 支持哪些数据类型?

Java7 之前,switch 支持 byte、short、int、long 和枚举类。

Java7 之后,switch 支持 String 字符串,这是一个语法糖,内部实现是使用字符串的 hash code 值。

    public static void main(String[] args) {
        String type = "order";
        switch (type) {
            case "order":
                System.out.println("order");
                break;
            case "gift":
                System.out.println("gift");
                break;
            default:
                System.out.println("default");
        }
    }

# 赋值 = 和 += 的区别是什么?

+= 操作本质上分为2个步骤,先将2个数相加,然后将结果赋值给变量。+= 操作会将2个类型强制转换为结果类型,然后再进行相加操作。

  byte i = 127;
  byte j = 127;
  i = i + j; // 编译错误
  i += j; // 通过
  System.out.println(i);

上例中 i = i + j 在相加之前,程序将 ij 转换为 int 类型,再赋值给 byte 的变量 i 编译就会出错。

# 1.2 泛型

# 1.3 注解

# 1.4 异常

# 1.5 反射

# 1.6 SPI 机制

# 1.7 集合

# List 和 Set 的区别?

# ArrayList 和 LinkedList 的区别?

# HashMap 和 HashTable 有什么区别?底层实现原理是什么?

123123