首页 > 想拿offer?必会这些高频Java基础面试题 ![分享]
头像
猿兄
编辑于 2021-02-01 11:24
+ 关注

想拿offer?必会这些高频Java基础面试题 ![分享]


分享一些面试必会的高频Java基础面试题。 正文接近万字,建议先收藏再看~



文章目录如下:






1. 权限修饰符

private 只能在同类中使用;

默认的 可以在同包中使用;

protected 可以在同包和不同包的子类中使用;

public 可以在同步和不同包中使用。

2. 方法重载与重写的区别?

重载:

  1. 在同一个类中,方法名相同,但 参数的类型,个数,或 顺序不同;

  2. 重载方法的返回值 和 权限修饰符可以相同也可以不同;

  3. 重载发生在编译时。

重写:

  1. 即指子类重写父类的方法,返回值类型、方法名、参数列表 都必须一样;

  2. 子类重写方法的 抛出异常的范围 必须小于等于 父类中被重写的方法;

  3. 子类重写方法的 权限修饰符范围 必须大于等于 父类中被重写的方法;

  4. 若父类中的方法的权限修饰符为 private,那子类就不能重写该方法;

注意: 构造器不能被重写,但是可以重载。

3. String和StringBuffer、StringBuilder的区别?( String为什么是不可变的?)

区别1: 可变性 String对象是不可变的,而StringBuilder和StringBuffer对象是可变的。原因在于,虽然三者的底层都是使用字符数组保存字符串,但是 String 中用了final修饰 (private final char value[] ),而 StringBuilder和StringBuffer 都是继承自 AbstractStringBuilder 类,该类中没用final修饰( char[] value) ;

String源码:
StringBuilder和StringBuffer源码:

区别2:线程安全性

  1. String 对象是不可变的,可以理解为常量,线程安全; StringBuilder和StringBuffer 都是继承自 AbstractStringBuilder 类,该类定义了一些字符串的基本操作,如append、insert、indexOf 等公共方法。

  2. StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以 线程安全;

  3. StringBuilder 没有给方法加同步锁,所以 线程不安全;

区别3:性能 String类型改变时,都要生成一个新的String对象,并将指针指向新的String对象,性能一般; StringBuffer类型改变时,会对StringBuffer对象本身进行操作,而不是去生成新的对象操作,所以性能较高; StringBuilder操作类型时,相同情况下,会比StringBuffer提高10%~15%的性能,但是要从承担多线程不安全的风险;

使用总结:

  1. 操作少量数据时,用 String 就可以了;

  2. 单线程操作 字符串缓冲区下的大量数据,用 StringBuilder;(单线程不存在线程不安全问题,所以不怕线程不安全;)

  3. 多线程操作 字符串缓冲区下的大量数据,用StringBuffer;(多线程可能存在线程不安全问题,所以用线程安全的StringBuffer)

4. 基本数据类型和包装类型及自动装箱和拆箱等

4.1 基本数据类型和包装类型

在 Java 中,基本数据类型没有方法和属性,不方便我们操作。而包装类就是将基本的数据类型以及相应的一些方法封装成一个类,这样我们就可以实例化它们,然后就可以和正常对象一样操作它们了。

4.2 基本数据类型与包装类型对应表:

4.3 自动装箱与拆箱以及缓存池

装箱: 将基本数据类型转换为对应的包装类型,使用了 valueof(xxx) 方法。

拆箱: 将包装类型转换为对应的基本数据类型,使用了 xxxValue() 方法。

示例:

4.4 缓存池

int 自动装箱成 Integer 其实是使用了 Integer.valueof(10); 那么相比于直接 new Integer(10)有什么区别呢?

验证一下:

然后Integer 缓存池默认大小为 -128 ~ 127。(上界 127 是可调的。) 意思也就是说,使用 Integer.valueof() 创建对象,并且数值范围在 -128~127 的,会直接使用缓存池中的对象。

5、 == 与 equals() 与 hashCode()

== 与 equals() 与 hashCode() 比较具体的相关内容可以看这里:== 与 equals() 与 hashCode()

下面是对一些常见问题的简述。

5.1 == 与 equals() 的区别与联系?

  • ==

    1. 对于基本数据类型, == 比较的是值。

    2. 对于引用数据类型,== 比较的是内存地址,也就是比较两个对象是否是同一个对象。

  • equals()

    1. equals() 是 Object 类提供的方法,用于比较两个对象的属性值是否相同。

    2. 默认的 equals() 方法,底层其实使用了 == 来比较,所以一般使用重写后的equals() 方法。

    3. 重写后的 equals() 方***比较两个对象的所有属性值是否相同。

5.2 重写了 hashCode() 方法后,散列码(或者称为:哈希值)一样的两个对象一定相等吗?

不一定相等。因为基于方法的实现,不同的对象经过计算之后得出的哈希值是可能相同的。但是,如果两个对象的哈希值不同,那么这两个对象一定不相等。

5.3 如果没有重写 hashCode() 方法,散列码(哈希值)一样的两个对象相等吗?

也不一定相等。因为默认的 hashCode() 方法也并不是直接将对象的内存地址值当做哈希值,而是将对象的内存地址值转换成一个整数作为哈希值并返回。同样,如果两个对象的哈希值不同,那么这两个对象一定不相等。

5.4 为什么重写了 equals() 方法后,还要重写 hashCode() 方法? 如果不重写会出现什么问题?

首先要知道:覆盖 equals() 方法时,也要覆盖 hashcode() 方法,使得如果 x.equals(y) 返回 true ,那么 x.hashCode() 与 y.hashCode() 就必须返回相同的值。也就是说,如果两个对象的所有属性值都相同,那么他们的哈希值也应该相同。

这样做的原因在于:Set, 比如HashSet,存储的是不可重复的对象,插入数据的时候,为了快速检查是否已有重复的对象,其中一个步骤就是检查两个对象的哈希值是否相同,不同则直接插入到HashSet中。

如果我们没有重写hashCode() 方法,就会出现在一个 Set 中插入了两个属性值完全一样的对象,也就是重复的对象。

6. 静态代码块及代码块的执行顺序

静态代码块相关的具体内容可以看这里:静态代码块相关问题

代码块执行顺序: 父类静态代码块 → 子类静态代码块 → 父类初始化块 → 父类构造函数 → 子类初始化块 → 子类构造函数

7. final、static、static final

7.1 final

final 主要用在3个地方:变量、方法、类。

  1. final 用在变量上 ① 这个变量必须在定义时初始化,或者声明之后,在构造函数或代码块中 对其赋值。 ③ 如果是基本数据类型的变量,那么这个变量的值一旦初始化之后就不能修改了,相当于是一个常量了。 ④ 如果是引用类型的变量,则其初始化之后,就不能修改它的引用了,不过引用上的对象本身的值还是可以修改的。

  2. final 用在方法上 ① 表明这个方法不能被子类重写,防止子类修改它的含义。 ② private 方***被隐式的指定为 final 。

  3. final 用在类上 ① 表明这个类不能被继承。 ② final 类中的所有成员方法都会被隐式的指定为 final 方法。

7.2 static

static 主要用在 5 个地方:静态变量,静态方法,静态代码块,静态内部类,静态导包。(static 不能修饰外部类。)

  1. 静态变量 静态变量也称为类变量,整个类只有一个这样的变量,该类的所有实例都共享这一个静态变量,可以通过类名来直接访问静态变量(类名.静态变量名)。每个静态变量在内存中只存一份。(对于非静态变量,每个对象实例都会有自己单独的一份,随着对象实例一起创建和销毁。)

  2. 静态方法 ① 静态方法是不在对象上执行的方法,或者说可以通过类名直接调用。比如 Math.pow(x, a); 这个调用不需要 Math 对象,或者说没有隐式参数。 ② 静态方法在类加载的时候就存在了,不依附于任何对象实例,静态方法中只能访问静态变量和静态方法。

  3. 静态代码块 静态代码块只在类加载的时候执行一次。 静态代码块及非静态代码块的执行顺序可以看这个:静态代码块相关问题

  4. 静态内部类 ① 静态内部类的创建不依赖于外部类。也就是说,创建普通内部类之前需要先创建外部类,而创建静态内部类之前不需要先创建外部类,可以直接创建静态内部类。 ② 静态内部类可以调用外部类的静态变量和静态方法,但是不能调用外部类的非静态变量和非静态方法。

  5. 静态导包 import static com.xxx.ClassName.xxx 静态导包可以简化静态方法和静态变量的使用,简化代码。 比如静态导包前: 写输出语句:System.out.println(...); 当我们先静态导包后: import static java.lang.System.out; 写输出语句语句:out.println(...);

7.3 static final

static final 用在变量和方法上。

  1. static final 用在变量上 相当于是一个“全局常量”,并且可以通过类名直接访问,而且整个类共享一个,这个变量必须在定义时就初始化,或者声明之后在静态代码块中赋值,一旦初始化或赋值之后,就不能修改了。

  2. static final 用在方法上 表明这个方法不能被子类继承修改,并且可以直接通过类名来调用这个方法。

  3. static final 与 final static 是一样的。

8. Object类的常用方法有哪些?

Object类是所有类的父类,所有类都默认继承了Object类。

Object类实现的方法:

  1. getClass 方法 final修饰,用于返回当前运行时对象的class对象。

  2. hashCode 方法 用于返回对象的哈希码,主要使用在哈希表中,如 JDK中的HashMap。

  3. equals 方法 用于比较两个对象的内存地址是否相等; 但一般都是使用重写后的equals方法,用于比较两个对象的值是否相等。

  4. clone 方法 用于创建并返回对象的一份拷贝(浅拷贝); 调用clone方法前需要先实现Cloneable接口并重写clone方法,否则会报CloneNotSupportedException异常。

  5. toString 方法 会返回 类的名字@实例的哈希码的16进制 的字符串; 一般都是使用重写后的toString方法。

  6. notify 方法 final修饰,用于唤醒在该对象上等待的线程,如果有多个线程在等待,则随机唤醒其中一个。

  7. notifyAll 方法 final修饰,用于唤醒在该对象上等待的所有线程。·

  8. wait 方法 (重载的3个) final修饰,用于暂停线程的执行,相比sleep方法,wait方***释放锁; 8.1 wait() 方***一直暂停线程执行; 8.2 wait(long timeout) 方***暂停线程,timeout 是超时时间(毫秒); 8.3 wait(long timeout,int nanos) 方法和 wait(long timeout) 一样,但是超时时间要加上nanos(毫微秒);

  9. finalize 方法 实例被垃圾回收器回收的时候触发的操作。对象被标记为垃圾之后,如果有必要,并且这个对象覆盖了finalize() 方法,那么这个对象可以执行 finalize() 方法来进行一次自救。(不过已经已经被官方明确声明为不推荐使用的语法,因为运行代价高昂,且不确定性大,无法保证各个对象的执行顺序。)

9.深拷贝和浅拷贝的区别?

  • 对于基本数据类型 深拷贝和浅拷贝都是进行值传递,也就是复制值(因为基本数据类型不是对象,也不能进行其他的传递啊。)

  • 对于引用数据类型 深拷贝:创建一个新的对象,复制被拷贝对象的内容。 浅拷贝:没有创建新的对象,只是传递了被拷贝对象的引用。

10. 键盘输入的常用获取方法?

1. Scanner

2. BufferedReader

11.接口和抽象类以及对比

11.1 抽象类

  1. 抽象类是用 abstract 关键字修饰的类,抽象方法是用 abstract 关键字修饰的方法。

  2. 一个抽象类中可以有非抽象的方法,但如果一个类中如果有了抽象方法,那么这个类必须是抽象类。另外,抽象类中可以没有抽象方法。

  3. 抽象类不能被实例化,只能被继承。

11.2 接口

  1. 接口是用 interface 关键字修饰的。

  2. 接口中的普通方法不能有具体的实现,实现类必须实现接口中的所有普通方法。

  3. 接口中用 default 关键字修饰的方法可以有默认的实现。

  4. 接口中可以定义静态方法,可以用接口名直接调用,不能用实现类或实现调用。

  5. 接口中的字段和方法默认都是 public 的,并且不允许定义成 private 或者 protected 。

  6. 接口中的字段默认且只能是 static final 的。

  7. 接口不能直接用 new 实例化,但是可以声明,不过必须引用一个实现该接口的对象。

  8. 如果一个类实现了两个接口,而这两个接口中有一样的默认方法,则必须重写该方法,否则会报错。

11.3 抽象类和接口的区别

  1. 一个类可以实现多个接口,但是只有继承一个抽象类(单继承,多实现)。

  2. 接口的字段默认且只能是 static final 的,而抽象类中的字段没有这种默认且也没有这种限制。

  3. 接口的字段和方法只能是 public 的, 而抽象类中的字段和方法对此没有限制。

  4. 接口的实现类必须实现接口中的所有普通方法;而抽象类的继承类如果也是一个抽象类,可以不需要实现抽象类中的所有抽象方法。

  5. 从设计层面上来看,抽象是对类的抽象,是一种模板设计,是一种 LIKE-A 关系;而接口是对行为的抽象,是一种行为的规范,是一种 IS-A 关系。

12. this 和 super 以及区别

12.1 this

含义: this 是自身的一个对象,代表对象本身,可以简单理解为 指向对象本身的一个指针。

用法: this主要有 3 种用法:

① 普通的直接引用 this 相当于是指向当前对象本身。

② 形参与成员名字相同,用 this 来区分

③ 引用本类的构造函数 (要作为构造函数中的第一条语句)

12.2 super

含义: super 可以理解为指向自己父类对象的一个指针,这个父类指的是离自己最近的一个父类。

用法: super 主要有 3 种用法:

① 普通的直接应用 super 相当于是指向当前父类的引用,这样就可以用 super.xxx 来引用父类中的成员。

② 子类中的成员变量或方法与父类中的成员变量或方法的名字相同时,用 super 进行区分。

③ 引用父类构造函数 (要作为构造函数中的第一条语句)

12.3 this 与 super 的区别

① this 代表当前对象名,super 相当于是指向自己父类对象的一个指针

② 在构造函数中使用时,this 和 super 都需要放在第一行;因此不能同时出现在同一个构造函数中。

③ this() 和 super() 都指的是对象,所有都不能在 static 环境中使用,比如静态代码块中,静态方法中。

13 异常

13.1 Throwable 类

  1. Throwable 是所有的异常的祖先;

  2. Throwable的两个重要子类:Exception(异常) 和 Error(错误);

  3. Throwable类的常用方法: ①getMessage() :返回异常发生时的详细处理。 ②toString() :返回异常发生时的简要描述。 ③getLocalizedMessage() :返回异常对象的本地化信息;使用Throwable的子类覆盖这个方法,可以声称本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与getMessage() 返回的结果相同。 ④printStackTrace() :在控制台上打印Throwable对象封装的异常信息。

13.2 Exception(异常) 和 Error(错误) 的区别?

  • Error(错误) Error是代码运行时系统的内部错误和资源耗尽错误,是程序无法处理的错误;

    比如如 Java虚拟机运行错误(VirtualMachineError)、类定义错误(NoClassDefFoundError) 等,这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了这样的错误,本质上也不应该试图去处理它们所引起的异常状况。

  • Exception(异常)

    Exception 是程序本身可以处理的异常。

    Exception 主要又分为 运行时异常(RuntimeException)非运行时异常(CheckedException)或者叫检查式异常

    RuntimeException异常由Java虚拟机抛出,如:空指针异常,数组越界异常,算术异常(如除数为0) 等。

    简单来说,Exception 与 Error 的区别在于:Exception 能被程序本身处理,而程序是无法处理 Error 的

13.3 运行时异常(RuntimeException)和 非运行时异常(或者叫检查式异常,CheckedException)的区别?

运行时异常: 运行时异常可以不去处理,不处理也能通过编译;当异常出现时,虚拟机会处理。

  • 常见的运行时异常:

    1. 数组越界异常:IndexOutOfBoundsException

    2. 空指针异常:NullPointerException

    3. 数据存储异常:ArraysStoreException

    4. 算术异常:ArithmeticException (比如除数为0时)

非运行时异常: 必须要去处理的异常,不然程序无法通过编译。

  • 常见的非运行时异常:

    1. 文件未找到异常:FileNotFoundException

    2. SQL运行异常:SQLException

    3. 输入流异常:IOException

13.4 处理异常的方法有哪些?

处理异常的方法有两种:

  1. 抛出异常: throws、throw

  2. 捕获异常: try...catch...finally...

13.5 抛出异常以及throw,throws的区别

抛出异常:一个方法不处理这个异常,而且继续将问题抛给调用者来处理,使用 throw 和 throws 。

throw 和 throws 的区别:

  1. 使用位置不同: throw 用在方法内,后面跟的是异常对象; throws 用在方法上,后面跟的是异常类(多个类型可用逗号隔开)。

  2. 功能不同: throws是用来声明可能发生的异常,并可给出预先的处理方法; throw是抛出具体的异常,执行了throw之后会跳转到方法调用者,并将异常抛给调用者。 (所以 如果throw语句独立存在时,后面就不要再定义其他语句了,因为执行不到后面。)

  3. throws 只是表示可能会出现某种异常,但不一定会出现这些异常; throw 是直接抛出了异常,所以如果执行了throw语句,那么一定会抛出异常;

  4. throws 和 throw 都是消极处理异常的方式,其实它们并没有去处理异常,而是将异常向上抛出,交由上层调用处理;

13.6 捕获异常(try...catch...finally...)

  1. tyr 块:用于捕获异常;其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。

  2. catch 块:用于处理try捕获到的异常。

  3. finally 块:无论前面是否捕获了或处理了异常,finally块里的语句最后都会被执行; 当在try块或catch块中遇到return语句时,finally块里的语句会在方法返回之前被执行。

  4. finally块不会被执行的4种特殊情况: ①在finally语句块中发生了异常; ②在前面的代码中使用了System.exit()退出程序; ③程序所在的线程死亡了; ④关闭了CPU。

14. 反射

14.1 什么是反射?

能够分析类能力的程序称为反射(reflective)。—— 《Java 核心技术 卷 I 》第11版。

反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内

部属性及方法。

简单来说就是:我们之前是 通过类的相关信息去实例化一个对象;而利用反射我们可以通过已经实例化好的对象去获取一个类的相关信息。

反射被视为动态语言的关键。

动态语言:在运行时代码可以根据某些条件改变自身结构。 比如:Object-C、C#、JavaScript、PHP、Python、Erlang。

静态语言:在运行时代码结构不可变。 比如:Java、C、C++。

Java 虽然不是动态语言,但 Java 可以利用反射机制、字节码操作获得类似动态语言的特性。Java的反射让编程的时候更加灵活。

14.2 反射的优缺点

  • 优点

    1. 使得 Java 虽然不是动态语言,却可以获得类似动态语言的特性

    2. 使得 Java 在编程的时候更灵活

    3. 可扩展性 :应用程序可以利用全限定名创建可扩展对象的实例,来使用来自外部的用户自定义类。

    4. 类浏览器和可视化开发环境 :一个类浏览器需要可以枚举类的成员。可视化开发环境(如 IDE)可以从利用反射中可用的类型信息中受益,以帮助程序员编写正确的代码。

    5. 调试器和测试工具 : 调试器需要能够检查一个类里的私有成员。测试工具可以利用反射来自动地调用类里定义的可被发现的 API 定义,以确保一组测试中有较高的代码覆盖率。

  • 缺点

    1. 性能开销 :反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。

    2. 安全限制 :使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。

    3. 内部暴露 :由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。

14.3 反射有哪些应用?

  1. 框架中大量使用了反射,比如Spring 的动态代理(AOP)就是利用了反射。

  2. 调试器和测试工具也是利用了反射。

16. 泛型

16.1 什么是泛型?有什么用?

泛型的本质是参数化类型,也就是所操作的数据类型被指定为一个参数

泛型提供了编译时类型安全监测机制,确保了我们只能把正确类型的对象放入集合中,无需我们去进行类型转换,避免了在运行时出现类型转换异常(ClassCastException)。

16.2 泛型可以用在哪里?

泛型常用在三个地方:

  1. 泛型类

  2. 泛型接口

  3. 泛型方法

16.3 泛型是如何实现的? 什么是类型擦除?

实现: 泛型主要是通过类型擦除来实现的。

类型擦除: 编译器在编译时擦除了所有类型相关的信息,在运行时不存在任何类型相关的信息;也就说在进入 JVM 之后,不会保留泛型相关的信息,会擦除掉。

17. 注解

Java 注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。简单可以理解为标签。

18. 面向对象的特征/特征

面向对象有三大特性:封装、继承、多态。

  • 封装: 将事物的属性封装成一个类,减少耦合,隐藏细节。外部对象不能直接访问内部信息,保留特定的接口与外界联系,外部不用知道具体的实现细节,当接口内部发生改变时,不会影响外部调用方。

  • 继承: 从一个已有类中派生出一个新的类 ,新类拥有已知类的行为和属性,可以通过重写修改这些行为和属性,也可以新增一些行为和属性。继承提高了代码的复用性、可维护性以及可扩展性。

  • 多态: 主要表现为 父类的引用指向子类的实例 。多态主要三种实现为:

    1. 子类重写父类方式;

    2. 同一类中的方法重载;

    3. 将子类对象作为父类对象来使用。

19. 什么是值传递?什么是引用传递?Java 是什么传递?

  • 值传递(pass by value): 在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

  • 引用传递(pass by reference): 在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

Java 中是值传递。(但是注意:这个值可以是对象的引用)



以上就是一些常见高频Java基础面试题的分享了。

后续还会分享更多相关内容:

看完之后,如果还有什么不懂的,可以在评论区留言,会及时回答更新。

点个关注,不再迷路

越努力,越幸运!祝大家早日上岸!

非常感谢各位牛油们能看到这里~

如果觉得有帮助的话,求点赞👍 求关注💗 求分享👬

注: 如果以上内容有任何错误和建议,欢迎大家留言,非常感谢!


全部评论

(0) 回帖
加载中...
话题 回帖

相关热帖

近期热帖

热门推荐