Java基础+集合+多线程+JVM 面试题总结

Posted 脚丫先生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java基础+集合+多线程+JVM 面试题总结相关的知识,希望对你有一定的参考价值。

大家好,我是脚丫先生 (o^^o)

最近系统的总结了前辈们的各种面试题,站在巨人们的肩膀上真是看得远,我想只有对前辈们的知识进行自我的优化与吸收,才能形成适合自己的一份笔记。

文章目录

一、 Java 基础

1.1 面向对象和面向过程的区别

⾯向过程 :⾯向过程性能⽐⾯向对象⾼。 因为类调⽤时需要实例化,开销⽐较⼤,⽐较消耗资源,所以当性能是最重要的考量因素的时候,⽐如单⽚机、嵌⼊式开发、Linux/Unix 等⼀般采⽤⾯向过程开发。但是,⾯向过程没有⾯向对象易维护、易复⽤、易扩展。
⾯向对象 :⾯向对象易维护、易复⽤、易扩展。 因为⾯向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,⾯向对象性能⽐⾯向过程低。
⾯向过程性能⽐⾯向对象⾼ ?
Java 性能差的主要原因并不是因为它是⾯向对象语⾔,⽽是 Java 是半编译语⾔,最终的执⾏代码并不是可以直接被 CPU 执⾏的⼆进制机械码。⽽⾯向过程语⾔⼤多都是直接编译成机械码在电脑上执⾏,并且其它⼀些⾯向过程的脚本语⾔性能也并不⼀定⽐ Java 好。

1.2 Java 语⾔有哪些特点?

  1. 简单易学;
  2. ⾯向对象(封装,继承,多态);
  3. 平台⽆关性( Java 虚拟机实现平台⽆关性);
  4. 可靠性;
  5. 安全性;
  6. ⽀持多线程( C++ 语⾔没有内置的多线程机制,因此必须调⽤操作系统的多线程功能来进
    ⾏多线程程序设计,⽽ Java 语⾔却提供了多线程⽀持);
  7. ⽀持⽹络编程并且很⽅便( Java 语⾔诞⽣本身就是为简化⽹络编程设计的,因此 Java 语
    ⾔不仅⽀持⽹络编程⽽且很⽅便);
  8. 编译与解释并存;
    编译与解释:对于编译而言,它是一次性的把源程序翻译成目标代码,然后计算机读取的时候就可以直接以机器码进行执行,这样的话效率就会很高,但是对于解释则不同,解释的特点是只有在执行的时候才去翻译,也就是边翻译边执行。
    通俗理解:编译就是相当于提前做好了一桌子菜等着你吃,而解释就好比是吃火锅,边吃边下。

1.3 关于 JVM JDK 和 JRE 最详细通俗的

1.3.1 JVM

Java 虚拟机(JVM)是运⾏ Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),⽬的是使⽤相同的字节码,它们都会给出相同的结果。

Java虚拟机包括一个类加载器子系统(Class Loader SubSystem)、运行时数据区(Runtime Data Area)、执行引擎和本地接口库(Native Interface Library)。本地接口库通过调用本地方法库(Native Method Library)与操作系统交互,如图所示:

什么是字节码?采⽤字节码的好处是什么?
在 Java 中,JVM 可以理解的代码就叫做字节码(即扩展名为 .class 的⽂件),它不⾯向任何特定的处理器,只⾯向虚拟机。Java 语⾔通过字节码的⽅式,在⼀定程度上解决传统解释型语⾔执⾏效率低的问题,同时⼜保留了解释型语⾔可移植的特点。所以Java程序运⾏时⽐较⾼效,⽽且,由于字节码并不针对⼀种特定的机器,因此,Java程序⽆须重新编译便可在多种不同操作系统的计算机上运⾏。
Java 程序从源代码到运⾏⼀般有下⾯ 3 步:

Java程序的具体运行过程如下:
(1) Java源文件被编译器编译成字节码文件。
(2) JVM将字节码文件编译成相应操作系统的机器码。
(3) 机器码调用相应操作系统的本地方法库执行相应的方法。

1.3.2 JDK 和 JRE

  • JDK:Java Development Kit 的简称,Java 开发工具包,提供了 Java 的开发环境和运行环境。
  • JRE:Java Runtime Environment 的简称,Java 运行环境,为 Java 的运行提供了所需环境。

具体来说 JDK 其实包含了 JRE,同时还包含了编译 Java 源码的编译器 Javac,还包含了很多 Java 程序调试和分析的工具。简单来说:如果你需要运行 Java 程序,只需安装 JRE 就可以了如果你需要编写 Java 程序,需要安装 JDK。

1.4 Java 和 C++的区别?

我知道很多⼈没学过 C++,但是⾯试官就是没事喜欢拿咱们 Java 和 C++ ⽐呀!没办法!!!就
算没学过 C++,也要记下来!

都是⾯向对象的语⾔,都⽀持封装、继承和多态

  • Java 不提供指针来直接访问内存,程序内存更加安全
  • Java 的类是单继承的,C++ ⽀持多重继承;虽然 Java 的类不可以多继承,但是接⼝可以多
    继承。
  • Java 有⾃动内存管理机制,不需要程序员⼿动释放⽆⽤内存

1.5 字符型常量和字符串常量的区别?

  1. 形式上: 字符常量是单引号引起的⼀个字符; 字符串常量是双引号引起的若⼲个字符
  2. 含义上: 字符常量相当于⼀个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表⼀个地
    址值(该字符串在内存中存放位置)
  3. 占内存⼤⼩字符常量只占 2 个字节; 字符串常量占若⼲个字节 (注意: char 在 Java 中占两
    个字节)

1.6 构造器 Constructor 是否可被 override?

Constructor 不能被 override(重写),但是可以 overload(重载),所以你可以看到⼀个类中有多
个构造函数的情况。

1.7 重载和重写的区别

重载 :就是同样的⼀个⽅法能够根据输⼊数据的不同,做出不同的处理。并且发⽣在同⼀个类中,⽅法名必须相同,参数类型不同、个数不同、顺序不同,⽅法返回值和访问修饰符可以不同。
重写:就是当⼦类继承⾃⽗类的相同⽅法,输⼊数据⼀样,但要做出有别于⽗类的响应时,你就要覆盖⽗类⽅法。发⽣在运⾏期,是⼦类对⽗类的允许访问的⽅法的实现过程进⾏重新编写

综上

  • 重载就是同⼀个类中多个同名⽅法根据不同的传参来执⾏不同的逻辑处理。
  • 重写就是⼦类对⽗类⽅法的重新改造,外部样⼦不能改变,内部逻辑可以改变

1.8 Java ⾯向对象编程三⼤特性: 封装 继承 多态

1.8.1 封装

封装把⼀个对象的属性私有化,同时提供⼀些可以被外界访问的属性的⽅法,如果属性不想被外
界访问,我们⼤可不必提供⽅法给外界访问。但是如果⼀个类没有提供给外界访问的⽅法,那么
这个类也没有什么意义了。

1.8.2 继承

继承是使⽤已存在的类的定义作为基础建⽴新类的技术,新类的定义可以增加新的数据或新的功
能,也可以⽤⽗类的功能,但不能选择性地继承⽗类。通过使⽤继承我们能够⾮常⽅便地复⽤以
前的代码
注意

  1. ⼦类拥有⽗类对象所有的属性和⽅法(包括私有属性和私有⽅法),但是⽗类中的私有属性
    和⽅法⼦类是⽆法访问,只是拥有。
  2. ⼦类可以拥有⾃⼰属性和⽅法,即⼦类可以对⽗类进⾏扩展。
  3. ⼦类可以⽤⾃⼰的⽅式实现⽗类的⽅法。

1.8.3 多态

父类型的引用指向子类型的对象。用一句比较通俗的话:同一操作作用于不同的对象,可以产生不同的效果,这就是多态。
优点
1.应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性。
2.派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。

在 Java 中有两种形式可以实现多态:继承(多个⼦类对同⼀⽅法的重写)和接口(实现接口并覆盖接口中同⼀⽅法)

1.9 String StringBuffer 和 StringBuilder 的区别是什么?

操作字符串的类有:String、StringBuffer、StringBuilder。
String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。
StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。
对于三者使⽤的总结:

  1. 操作少量的数据: 适⽤ String
  2. 单线程操作字符串缓冲区下操作⼤量数据: 适⽤ StringBuilder
  3. 多线程操作字符串缓冲区下操作⼤量数据: 适⽤ StringBuffer

1.10 ⾃动装箱与拆箱

装箱:将基本类型⽤它们对应的引⽤类型包装起来;
拆箱:将包装类型转换为基本数据类型;

1.11 在⼀个静态⽅法内调⽤⼀个⾮静态成员为什么是⾮法的?

由于静态⽅法可以不通过对象进⾏调⽤,因此在静态⽅法⾥,不能调⽤其他⾮静态变量,也不可
以访问⾮静态变量成员。

1.12 在 Java 中定义⼀个不做事且没有参数的构造⽅法的作⽤?

Java 程序在执⾏⼦类的构造⽅法之前,如果没有⽤super() 来调⽤⽗类特定的构造⽅法,则会调⽤⽗类中“没有参数的构造⽅法”。因此,如果⽗类中只定义了有参数的构造⽅法,⽽在⼦类的构造⽅法中⼜没有⽤super() 来调⽤⽗类中特定的构造⽅法,则编译时将发⽣错误,因为 Java 程序在⽗类中找不到没有参数的构造⽅法可供执⾏。解决办法是在⽗类⾥加上⼀个不做事且没有参数的构造⽅法。

1.13 接⼝和抽象类的区别是什么?

实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
构造函数:抽象类可以有构造函数;接口不能有。
实现数量:类可以实现很多个接口;但是只能继承一个抽象类。
访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。

1.14 成员变量与局部变量的区别有哪些?

定义的位置上区别:
1. 成员变量是定义在方法之外,类之内的。
2. 局部变量是定义在方法之内。

作用上的区别:
    1. 成员变量的作用是用于描述一类事物的公共属性的。
    2. 局部变量的作用就是提供一个变量给方法内部使用而已。

生命周期区别:
    1. 随着对象的创建而存在,随着对象的消失而消失。
    2. 局部变量在调用了对应的方法时执行到了创建该变量的语句时存在,局部变量一旦出了自己的作用域
    那么马上从内存中消失。

初始值的区别:
    1. 成员变量是有默认的初始值。
            数据类型     默认的初始值
            int                0
            float              0.0f
            double              0.0
            boolean            false
            char                 ' '
            String(引用数据类型) null
    2. 局部变量是没有默认的初始值的,必须要先初始化才能使用。

1.15 创建⼀个对象⽤什么运算符?对象实体与对象引⽤有何不同?

new 运算符,new 创建对象实例(对象实例在堆内存中),对象引⽤指向对象实例(对象引⽤存
放在栈内存中)。⼀个对象引⽤可以指向 0 个或 1 个对象(⼀根绳⼦可以不系⽓球,也可以系⼀
个⽓球);⼀个对象可以有 n 个引⽤指向它(可以⽤ n 条绳⼦系住⼀个⽓球)。

1.16 ⼀个类的构造⽅法的作⽤是什么? 若⼀个类没有声明构造⽅法,该程序能正确执⾏吗? 为什么?

主要作⽤是完成对类对象的初始化⼯作。可以执⾏。因为⼀个类即使没有声明构造⽅法也会有默
认的不带参数的构造⽅法。

1.17 构造⽅法有哪些特性?

  1. 名字与类名相同。
  2. 没有返回值,但不能⽤ void 声明构造函数。
  3. ⽣成类的对象时⾃动执⾏,⽆需调⽤。

1.18 静态⽅法和实例⽅法有何不同 ?

  1. 在外部调⽤静态⽅法时,可以使⽤"类名.⽅法名"的⽅式,也可以使⽤"对象名.⽅法名"的⽅
    式。⽽实例⽅法只有后⾯这种⽅式。也就是说,调⽤静态⽅法可以⽆需创建对象。
  2. 静态⽅法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态⽅法),⽽不
    允许访问实例成员变量和实例⽅法;实例⽅法则⽆此限制。

1.19 对象的相等与指向他们的引⽤相等,两者有什么不同 ?

对象的相等,⽐的是内存中存放的内容是否相等。⽽引⽤相等,⽐较的是他们指向的内存地址是
否相等。

1.20 在调⽤⼦类构造⽅法之前会先调⽤⽗类没有参数的构造⽅法,其⽬的是?

帮助⼦类做初始化⼯作。

1.21 == 与 equals(重要)

== 解读
对于基本类型和引用类型 == 的作用效果是不同的,如下所示:
• 基本类型:比较的是值是否相同;
• 引用类型:比较的是引用是否相同;
equals 解读
equals 本质上就是 ,只不过 String 和 Integer 等重写了 equals 方法,把它变成了值比较。
总结 :
对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals 默认情况下是引用比较,只是很多类重新了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。

1.22 hashCode 与 equals

**hashCode()**的作⽤是获取哈希码,也称为散列码;它实际上是返回⼀个 int 整数。这个哈希码的作⽤是确定该对象在哈希表中的索引位置。 hashCode() 定义在 JDK 的Object类中,这就意味着 Java 中的任何类都包含有hashCode()函数。另外需要注意的是: Object的 hashcode ⽅法是本地⽅法,也就是⽤ c 语⾔或 c++ 实现的,该⽅法通常⽤来将对象的 内存地址 转换为整数之后返回。
equals与hashcode间的关系
1、如果两个对象相同(即用equals比较返回true),那么它们的hashCode值一定要相同。
2、如果两个对象的hashCode相同,它们并不一定相同(即用equals比较返回false)。
3、由于为了提高程序的效率才实现了hashcode方法,先进行hashcode的比较,如果不同,那没就不必在进行equals的比较了,这样就大大减少了equals比较的次数,这对比需要比较的数量很大的效率提高是很明显的。

1.23 什么是值传递和引用传递?

对象被值传递,意味着传递了对象的一个副本。因此,就算是改变了对象副本,也不会影响源对象的值。
对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象所做的改变会反映到所有的对象上。

1.24 简述线程、程序、进程的基本概念。以及他们之间关系是什么?

进程是cpu资源分配的最小单位,线程是cpu调度的最小单位。
进程之间不能共享资源,而线程共享所在进程的地址空间和其它资源。
一个进程内可拥有多个线程,进程可开启进程,也可开启线程。
一个线程只能属于一个进程,线程可直接使用同进程的资源,线程依赖于进程而存在。

线程有哪些基本状态?

线程的状态以及状态之间的相互转换:
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取 CPU 的使用权。
3、运行状态(Running):就绪状态的线程获取了 CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃 CPU 使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

(一)、等待阻塞:运行的线程执行 wait()方法,JVM 会把该线程放入等待池中。
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM 会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行 sleep()或 join()方法,或者发出了 I/O 请求时,JVM会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。

5、死亡状态(Dead):线程执行完了或者因异常退出了 run()方法,该线程结束生命周
期。
实现线程的两种方式:
是继承 Thread 类或实现 Runnable 接口,但不管怎样,当 new 了这个对象后,线程就已经进入了初始状态.

1.25 关于 final 关键字的⼀些总结

final 关键字主要⽤在三个地⽅:变量、⽅法、类。

  1. 对于⼀个 final 变量,如果是基本数据类型的变量,则其数值⼀旦在初始化之后便不能更
    改;如果是引⽤类型的变量,则在对其初始化之后便不能再让其指向另⼀个对象。
  2. 当⽤ final 修饰⼀个类时,表明这个类不能被继承。final 类中的所有成员⽅法都会被隐式地
    指定为 final ⽅法。
  3. 使⽤ final ⽅法的原因有两个。第⼀个原因是把⽅法锁定,以防任何继承类修改它的含义;
    第⼆个原因是效率。在早期的 Java 实现版本中,会将 final ⽅法转为内嵌调⽤。但是如果⽅
    法过于庞⼤,可能看不到内嵌调⽤带来的任何性能提升(现在的 Java 版本已经不需要使⽤
    final ⽅法进⾏这些优化了)。类中所有的 private ⽅法都隐式地指定为 final。
    总结:
    • final修饰的类叫最终类,该类不能被继承。
    • final修饰的方法不能被重写。
    • final修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。

1.26 Java 中的异常处理

1.26.1 Java 异常类层次结构图


在 Java 中,所有的异常都有⼀个共同的祖先java.lang 包中的Throwable类。 Throwable类有两个重要的⼦类Exception (异常)和Error (错误)。 Exception能被程序本身处理( try-catch ), Error是⽆法处理的(只能尽量避免)。Exception和Error⼆者都是 Java 异常处理的重要⼦类,各⾃都包含⼤量⼦类。
Exception: 程序本身可以处理的异常,可以通过catch来进⾏捕获。 Exception⼜可以为 受检查异常(必须处理) 和 不受检查异常(可以不处理)。
Error: Error属于程序⽆法处理的错误 ,我们没办法通过 catch来进⾏捕获 。例如,Java 虚拟机运⾏错误( Virtual MachineError )、虚拟机内存不够错误( OutOfMemoryError )、类定义错误( NoClassDefFoundError )等 。这些异常发⽣时,Java虚拟机(JVM)⼀般会选择线程终⽌。
受检查异常:
Java 代码在编译过程中,如果受检查异常没有被 catch / throw 处理的话,就没办法通过编译 。
除了 RuntimeException 及其⼦类以外,其他的 Exception 类及其⼦类都属于检查异常 。常⻅的受检查异常有: IO 相关的异常、 ClassNotFoundException 、 SQLException …。
不受检查异常:
Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。
RuntimeException 及其⼦类都统称为⾮受检查异常,例如: NullPointExecrption 、 NumberFormatException (字符串转换为数字)、 ArrayIndexOutOfBoundsException (数组越界)、 ClassCastException (类型转换错误)、 ArithmeticException (算术错误)等。

1.26.2 Throwable 类常⽤⽅法

public string getMessage() : 返回异常发⽣时的简要描述。
public string toString() : 返回异常发⽣时的详细信息。
public string getLocalizedMessage() : 返回异常对象的本地化信息。使⽤ Throwable 的⼦类覆盖这个⽅法,可以⽣成本地化信息。如果⼦类没有覆盖该⽅法,则该⽅法返回的信息与getMessage 返回的结果相同。
public void printStackTrace() :在控制台上打印 Throwable 对象封装的异常信息。

1.26.3 异常处理总结

try 块: ⽤于捕获异常。其后可接零个或多个 catch块,如果没有catch块,则必须跟⼀个finally块。
catch 块: ⽤于处理 try 捕获到的异常。
finally块: ⽆论是否捕获或处理异常,finally块⾥的语句都会被执⾏。当在try块或catch块中遇到return语句时,finally语句块将在⽅法返回之前被执⾏。
在以下 3 种特殊情况下, finally 块不会被执⾏:

  1. 在try或finally 块中⽤了System.exit(int) 退出程序。但是,如果System.exit(int)在异常语句之后, finally 还是会被执⾏
  2. 程序所在的线程死亡。
  3. 关闭 CPU。
    注意: 当 try 语句和 finally 语句中都有 return 语句时,在⽅法返回之前,finally 语句的内容将被
    执⾏,并且 finally 语句的返回值将会覆盖原始的返回值。

1.26.4 throw 和 throws 的区别?

• throw:是真实抛出一个异常。自定义的异常
• throws:是声明可能会抛出一个异常。

1.26.5 try-catch-finally 中哪个部分可以省略?

try-catch-finally 其中 catch 和 finally 都可以被省略,但是不能同时省略,也就是说有 try 的时候,必须后面跟一个 catch 或者 finally。

1.26.6 try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?

finally 一定会执行,即使是 catch 中 return 了,catch 中的 return 会等 finally 中的代码执行完之后,才会执行。

1.27 Java 序列化中如果有些字段不想进⾏序列化,怎么办?

对于不想进⾏序列化的变量,使⽤ transient 关键字修饰。

transient 关键字的作⽤是:阻⽌实例中那些⽤此关键字修饰的的变量序列化;当对象被反序列化
时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和⽅
法。

1.28 什么是内部类?内部类的作用

内部类可直接访问外部类的属性

Java中内部类主要分为成员内部类、局部内部类(嵌套在方法和作用域内)、匿名内部类(没构造方法)、静态内部类(static修饰的类,不能使用任何外围类的非static成员变量和方法, 不依赖外围类)

1.29 成员内部类、静态内部类、局部内部类和匿名内部类的理解

Java中内部类主要分为成员内部类、局部内部类(嵌套在方法和作用域内)、匿名内部类(没构造方法)、静态内部类(static修饰的类,不能使用任何外围类的非static成员变量和方法, 不依赖外围类)

使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。

因为Java不支持多继承,支持实现多个接口。但有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。

1.30 Java 中 IO 流

1.30.1 Java 中 IO 流分为⼏种?

按功能来分:输入流(input)、输出流(output)。
按类型来分:字节流和字符流。字节流针对英文,字符流针对中文
字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。

Java Io 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,⽽且彼此之间存在⾮常
紧密的联系, Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派⽣出来的。

InputStream/Reader: 所有的输⼊流的基类,前者是字节输⼊流,后者是字符输⼊流。
OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

按操作⽅式分类结构图

按操作对象分类结构图:

1.30.2 既然有了字节流,为什么还要有字符流?

问题本质想问:不管是⽂件读写还是⽹络发送接收,信息的最⼩存储单元都是字节,那为什么
I/O 流操作要分为字节流操作和字符流操作呢?

回答:字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是⾮常耗时,并
且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就⼲脆提供了⼀个直接操作
字符的接⼝,⽅便我们平时对字符进⾏流操作。如果⾳频⽂件、图⽚等媒体⽂件⽤字节流⽐较
好,如果涉及到字符的话使⽤字符流⽐较好。

1.31 对象拷贝

1.31.1 深拷⻉ vs 浅拷⻉

• 浅克隆:当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
• 深克隆:除了对象本身被复制外,对象所包含的所有成员变量也将复制。

1.31.2 为什么要使用克隆?

克隆的对象可能包含一些已经修改过的属性,而 new 出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠克隆方法了。

1.31.3 实现克隆的方式?

• 实现 Cloneable 接口并重写 Object 类中的 clone() 方法。
• 实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。

1.32 Java中类及方法的加载顺序

class A {
    private static int numA;
    private int numA2;
    
    static {
        System.out.println("A的静态字段 : " + numA);
        System.out.println("A的静态代码块");
    }
    
    {
        System.out.println("A的成员变量  : " + numA2);
        System.out.println("A的非静态代码块");
    }
 
    public A() {
        System.out.println("A的构造器");
    }
}
 
class B extends A {
    private static int numB;
    private int numB2;
 
    static {
        System.out.println("B的静态字段 : " + numB);
        System.out.println("B的静态代码块");
    }
    
    {
        System.out.println("B的成员变量 : " + numB2);
        System.out.println("B的非静态代码块");
    }
 
    public B() {
        System.out.println("B的构造器");
    }
}
 
public class Box {
    public static void main(String[] args) {
        A ab = new B();
        System.out.println("---");
        ab = new B();
    }
}

执行结果:

总结:由此,可以看出类中各成员初始化的顺序是:

父类的静态字段——>父类静态代码块——>子类静态字段——>子类静态代码块——>
父类成员变量(非静态字段)——>父类非静态代码块——>父类构造器——>子类成员变量——>子类非静态代码块——>子类构造器

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

• 普通类不能包含抽象方法,抽象类可以包含抽象方法。
• 抽象类不能直接实例化,普通类可以直接实例化。

1.34 抽象类能使用 final 修饰吗?

不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类,如下图所示,编辑器也会提示错误信息。

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

String 不属于基础类型,基础类型有 8 种:byte、boolean、char、short、int、float、long、double,而 String 属于对象。

1.36 Array 和 ArrayList 有何区别?

• Array 可以存储基本数据类型和对象,ArrayList 只能存储对象。
• Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。
• Array 内置方法没有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有 ArrayList 有。

巨人的肩膀

javaGuide哥

总结

未完待续…

以上是关于Java基础+集合+多线程+JVM 面试题总结的主要内容,如果未能解决你的问题,请参考以下文章

Java面试题⭐多线程篇⭐(万字总结,带答案,面试官问烂,跳槽必备,建议收藏)

Java面试题⭐多线程篇⭐(万字总结,带答案,面试官问烂,跳槽必备,建议收藏)

Java面试题⭐多线程篇⭐(万字总结,带答案,面试官问烂,跳槽必备,建议收藏)

Java开发基础面试题,成都比较好的java公司

2022年4月最新面经答案总结(Java基础数据库JVM计网计操集合多线程Spring)持续更新

3.5万字 JavaSE温故而知新!(结合jvm 基础+高级+多线程+面试题)