目录

  • 1.Java 程序设计概述
    • 1.1.Java 跨平台运行的原理是什么?
    • 1.2.Java 的安全性体现在哪些方面?
    • 1.3.面向对象和面向过程的区别是什么?
    • 1.4.面向对象的有哪些特征?
    • 1.5.Java 语言有哪些特点?
  • 2.Java 程序设计环境
    • 2.1.JDK 和 JRE 有什么区别?
  • 3.Java 的基本程序设计结构
    • 3.1.简单介绍 Java 中的 8 种基本数据类型以及它们的封装类。
    • 3.2.谈谈 Java 中的标识符。
    • 3.3.finally 语句块一定执行吗?
    • 3.4.& 和 && 的区别是什么?
    • 3.4.instanceof 关键字的作用是什么?
    • 3.5.final 关键字在 Java 中的用法有哪些?
    • 3.6.Java 中基本类型的转换规则有哪些?
    • 3.7.a = a + b 与 a += b 有什么区别吗?
    • 3.8.对于整型数据来说,存在 i 使得 i + 1 < i 吗?
    • 3.9.访问修饰符 public、private、protected、以及不写(默认)时的区别?
    • 3.10.Java 中有没有 goto 关键字?
    • 3.11.在 Java 中,如何跳出当前的多重嵌套循环?
    • 3.12.char 型变量中能否存储一个中文汉字,为什么?
    • 3.13.switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上?
    • 3.14.一个”.java”源文件中是否可以包含多个类(不是内部类)?有什么限制?
    • 3.15.hashCode() 与 equals() 的区别是什么?
  • 4.对象与类
    • 4.1.== 和 equals 的相同点和不同点是什么?
    • 4.2.什么是装箱?什么是拆箱?它们的执行过程分别是什么?有哪些注意事项?
    • 4.3.基本类型和包装类对象使用 == 和 equals 进行比较的区别是什么?
    • 4.4.return 与 finally 的执行顺序对返回值有什么影响?
    • 4.5.String、StringBuffer 和 StringBuilder 的区别是什么?
    • 4.6.Java 的四种引用方式分别是什么?
    • 4.7.Object 类有哪些常用的方法?
    • 4.8.Java 创建对象的方式有哪几种?
    • 4.9.浅拷贝和深拷贝的区别是什么?
    • 4.10.介绍一下 Java 中的可变参数。
    • 4.11.static 关键字的用法有哪些?
    • 4.12.静态变量和实例变量有什么区别?
    • 4.13.this 和 super 关键字的区别是什么?
    • 4.14.说说内存中的栈 (stack)、堆 (heap) 和静态存储区的用法。
    • 4.15.什么是注解?
  • 5.继承
    • 5.1.重写和重载的区别是什么?
    • 5.2.类加载器实例化时进行的操作步骤是什么?
    • 5.3.什么是反射?
    • 5.4.this 和 super 这两个关键字有什么区别?
  • 6.接口、Lambda 表达式与内部类
    • 6.1.抽象类 (abstract class) 和接口 (interface) 有什么区别?
    • 6.2.什么是 Lambda 表达式?
    • 6.3.什么是动态代理?其应用场景有哪些?
    • 6.4.什么是内部类?为什么需要使用内部类?
    • 6.5.匿名内部类可以继承类或实现接口吗?为什么?
  • 7.异常、断言和日志
    • 7.1.什么是内存泄漏和内存溢出?
    • 7.2.谈一谈 Java 中的异常?
    • 7.3.什么时候用 assert?如何使用 assert?
    • 7.4.throw 和 throws 的区别是什么?
    • 7.5.Error 和 Exception 有什么区别?
  • 8.泛型程序设计
    • 8.1.为什么要使用泛型程序设计?其优点是什么?
  • 9.集合
    • 9.1.Java 有哪些常用容器(集合)?
    • 9.2.Collection 和 Collections 有什么区别?
    • 9.3.List、Set、Map 之间的区别是什么?
    • 9.4.HashMap 的长度为什么是 2 的 N 次方?源码中是如何保证的?
    • 9.5.HashMap 和 Hashtable 的异同有哪些?
    • 9.6.HashMap 与 ConcurrentHashMap 的异同有哪些?
    • 9.7.poll() 方法和 remove() 方法有什么异同?
    • 9.8.ArrayList 和 LinkedList 有什么区别?
  • 10.其它常用类
    • 10.1.LocalDateTime & Calendar
      • 10.1.1.如何取当前的年、月、日、时、分、秒、毫秒?
      • 10.1.2.如何获取从 1970 年 1 月 1 日 0 时 0 分 0 秒到现在的毫秒数?
      • 10.1.3.如何获取某年某月的最后一天?
      • 10.1.4.打印昨天的当前时刻
      • 10.1.5.如何格式化日期?
    • 10.2.Runtime
      • 10.2.1.如何获取剩余的内存、总内存及最大堆内存?

① 以下 Java 基础面试题均基于Java 8,并且大部分题目来自网络。
② 由于本人水平有限,所以本文难免会出现一些错误或者不准确的地方,恳请读者在评论区指正。

1.Java 程序设计概述

1.1.Java 跨平台运行的原理是什么?

(1)Java 源文件要先编译成与操作系统无关的 .class 字节码文件,然后字节码文件再通过 Java 虚拟机解释成机器码运行。
(2).class 字节码文件面向虚拟机,不面向任何具体操作系统。
(3)不同平台的虚拟机是不同的,但它们给 JDK 提供了相同的接口。
(4)Java 的跨平台依赖于不同系统的 Java 虚拟机。

1.2.Java 的安全性体现在哪些方面?

(1)Java 使用引用取代了指针,指针的功能虽然强大,但是也容易造成错误,如数组越界问题。
(2)拥有一套异常处理机制,使用关键字 throw、throws、try、catch、finally。
(3)强制类型转换需要符合一定规则。
(4)字节码传输使用了加密机制。
(5)运行环境提供保障机制:字节码校验器 → 类装载器 → 运行时内存布局→ 文件访问限制。
(6)不用程序员显示控制内存释放,JVM 有垃圾回收机制。

1.3.面向对象和面向过程的区别是什么?

(1)面向过程: 是分析解决问题的步骤,然后用函数把这些步骤一步一步地实现,然后在使用的时候一一调用则可。性能较高,所以单片机、嵌入式开发等一般采用面向过程开发。
(2)面向对象: 是把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤,而是为了描述某个事物在解决整个问题的过程中所发生的行为。面向对象有封装、继承、多态的特性,所以易维护、易复用、易扩展。可以设计出低耦合的系统。 但是性能上来说,比面向过程要低。

1.4.面向对象的有哪些特征?

(1)抽象
抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。

(2)继承
继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类或基类);得到继承信息的类被称为子类(或派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。

(3)封装
通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口(可以想想普通洗衣机和全自动洗衣机的差别,明显全自动洗衣机封装更好,因此操作起来更简单;我们现在使用的智能手机也是封装得足够好的,因为几个按键就搞定了所有的事情)。

(4)多态
多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单地说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当 A 系统访问 B 系统提供的服务时,B 系统有多种提供服务的方式,但一切对 A 系统来说都是透明的(就像电动剃须刀是 A 系统,它的供电系统是 B 系统,B 系统可以使用电池供电或者用交流电, 甚至还有可能是太阳能,A 系统只会通过 B 类对象调用供电的方法,但并不知道供电系统的底层实现是什么,究竟通过何种方式获得了动力)。
① 方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override) 实现的是运行时的多态性(也称为后绑定)。
② 运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:1)方法重写。即子类继承父类并重写父类中已 有的或抽象的方法;2)对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。

1.5.Java 语言有哪些特点?

简单性 Java 语法是 C++ 语法的一个"纯净"版本,这里没有头文件、 指针运算(甚至指针语法)、结构、 联合、 操作符重载、 虚基类等。
面向对象 开发 Java 时面向对象技术已经相当成熟。 Java 的面向对象特性与 C++ 旗鼓相当。Java 与 C++ 的主要不同点在于多重继承,在 Java 中,取而代之的是更简单的接口概念。与 C++相比,Java 提供了更丰富的运行时自省功能。
分布式 Java 有一个丰富的例程库,用于处理像 HTTP 和 FIT 之类的 TCP/IP 协议。Java 应用程序能够通过 URL 打开和访问网络上的对象,其便捷程度就好像访问本地文件一样。
健壮性 Java 的设计目标之一在于使得 Java 编写的程序具有多方面的可靠性。Java 投入了大量的精力进行早期的问题检测、 后期动态的 (运行时)检测,并消除了容易出错的情况。Java 和 C++ 最大的不同在于 Java 采用的指针模型可以消除重写内存和损坏数据的可能性。Java 编译器能够检测许多在其他语言中仅在运行时才能够检测出来的问题。
安全性 Java 适用于网络 / 分布式环境。 为了达到这个目标,在安全方面投入了很大精力。使用 Java 可以构建防病毒、 防篡改的系统。从一开始,Java 就设计成能够防范各种攻击,其中包括:运行时堆栈溢出(如蠕虫和病毒常用的攻击手段)、破坏自己的进程空间之外的内存、未经授权读写文件等。
体系结构中立 编译器生成一个体系结构中立的目标文件格式,这是一种编译过的代码, 只要有 Java 运行时系统, 这些编译后的代码可以在许多处理器上运行。Java 编译器通过生成与特定的计算机体系结构无关的字节码指令来实现这一特性。 精心设计的字节码不仅可以很容易地在任何机器上解释执行,而且还可以动态地翻译成本地机器代码。
可移植性 与 C 和 C++ 不同,Java 规范中没有“ 依赖具体实现” 的地方基本教据类型的大小以及有关运算都做了明确的说明。作为系统组成部分的类库, 定义了可移植的接口例如,有一个抽象的 Window类, 并给出了在 UNIX、 Windows 和 Macintosh 环境下的不同实现。
解释性 Java 解释器可以在任何移植了解释器的机器上执行 Java 字节码。由于链接是一个增量式且轻量级的过程, 所以, 开发过程也变得更加快捷,更加具有探索性。
高性能 尽管对解释后的字节码性能已经比较满意,但在有些场合下还需要更加高效的性能。字节码可以(在运行时刻)动态地翻译成对应运行这个应用的特定 CPU 的机器码。
多线程 多线程可以带来更好的交互响应和实时行为。
动态性 从各种角度看, Java 与 C 或 C++ 相比更加具有动态性。它能够适应不断发展的环境库中可以自由地添加新方法和实例变量, 而对客户端却没有任何影响。在 Java 中找出运行时类型信息十分简单。

2.Java 程序设计环境

2.1.JDK 和 JRE 有什么区别?

在回答这个问题之前,先看下面这张图(来源于网络)。

JDK 和 JRE 的具体区别如下:

JDK JRE
英文全称 Java Development Kit,即 Java 开发工具包 Java Runtime Environment,即 Java 运行时环境
组成部分 包含 JRE、同时还包括 Java 源码的编译器 javac、监控工具 JConsole、分析工具 Java VisualVM 等 JRE 包含 Java 虚拟机,Java 基础类库等
面向人群 程序员 想运行 Java 程序的用户
使用场景 需要编写并运行 Java 程序 只需要运行 Java 程序

3.Java 的基本程序设计结构

3.1.简单介绍 Java 中的 8 种基本数据类型以及它们的封装类。

数据类型 关键字 字节数 取值范围 默认值 封装类
布尔型 boolean - {true, false} false Boolean
字节型 byte 1 -128 ~ 127 0 Byte
短整型 short 2 -215 ~ 215 - 1 0 Short
字符型 char 2 0 ~ 216 - 1 ‘\u0000’(空格) Character
整型 int 4 -231 ~ 231 - 1 0 Integer
长整型 long 8 -263 ~ 263 - 1 0 Long
单精度浮点型 float 4 1.4013E-45 ~ 3.4028E+38 0.0F Float
双精度浮点型 double 8 4.9E-324 ~ 1.7977E+308 0.0D Double

注意事项:
① int 是基本数据类型,Integer 是 int 的封装类,是引用类型。int 默认值是0,而 Integer 默认值是 null,所以 Integer 能区分出 0 和 null 的情况。一旦 Java 看到 null,就知道这个引用还没有指向某个对象,再任何引用使用前,必须为其指定一个对象,否则会报错

② 基本数据类型在声明时系统会自动给它分配空间,而引用类型声明时只是分配了引用空间,必须通过实例化开辟数据空间之后才可以赋值。数组对象也是一个引用对象,将一个数组赋值给另一个数组时只是复制了一个引用,所以通过某一个数组所做的修改在另一个数组中也看的见。

③ 虽然定义了 boolean 这种数据类型,但是只对它提供了非常有限的支持。在 Java 虚拟机中没有任何供 boolean 值专用的字节码指令,Java 语言表达式所操作的 boolean 值,在编译之后都使用 Java 虚拟机中的 int 数据类型来代替,而 boolean 数组将会被编码成 Java 虚拟机的 byte 数组,每个元素 boolean 元素占 8 位。Java 虚拟机规范提议:
1)如果 boolean 是 “单独使用”:boolean 被编译为 int 类型,占 4 个字节;
2)如果 boolean 是以 “boolean 数组” 的形式使用:boolean 占 1 个字节,Java 虚拟机直接支持 boolean 数组,通过 newarray 指令创建 boolean 数组,然后通过 byte 数组指令 baload 和 bastore 来访问和修改 boolean 数组

总之,boolean 占用 1 个字节、4 个字节都是有可能的,具体还要看虚拟机实现是否按照规范来。

3.2.谈谈 Java 中的标识符。

标识符的含义 指在程序中,我们自己定义的内容,例如类的名字,方法名称以及变量名称等等,都是标识符。
命名规则(硬性要求) 标识符只能包含英文字母,0-9的数字,$ 以及 _,但标识符不能以数字开头
命名规范(非硬性要求) 类名规范:首字符大写,后面每个单词首字母大写(大驼峰式)。 变量名规范:首字母小写,后面每个单词首字母大写(小驼峰式)。 方法名规范:同变量名。

3.3.finally 语句块一定执行吗?

finally 语句块不一定执行,因为存在一些特殊情况导致 finally 语句块不执行,例如:
① 直接返回未执行到 try-finally 语句块
② 抛出异常未执行到 try-finally 语句块
③ 系统退出未执行到 finally 语句块

3.4.& 和 && 的区别是什么?

(1)&&:逻辑与运算符。当运算符左右两边的表达式都为 true,才返回 true。同时具有短路性,如果第一个表达式为 false,则直接返回 false。

(2)&:逻辑与运算符、按位与运算符。
按位与运算符:用于二进制的计算,只有对应的两个二进位均为1时,结果位才为1 ,否则为0。
逻辑与运算符:& 在用于逻辑与时,和 && 的区别是不具有短路性。所在通常使用逻辑与运算符都会使用 &&,而 & 更多的适用于位运算。

3.4.instanceof 关键字的作用是什么?

instanceof 严格来说是 Java 中的一个双目运算符,用来测试一个对象是否为一个类的实例,具体用法如下:

boolean result = obj instanceof Class

其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回 false。此外,编译器会检查 obj 是否能转换成右边的 class 类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。

public class test {public static void main(String[] args) {A a = new A();AA aa = new AA();AAA aaa = new AAA();System.out.println(a instanceof A);     //trueSystem.out.println(a instanceof AA);    //falseSystem.out.println(aa instanceof AAA);  //falseSystem.out.println(aaa instanceof A);   //trueint i = 0;System.out.println(i instanceof Integer);  //编译不通过,i 必须是引用类型,不能是基本类型System.out.println(i instanceof Object); //编译不通过//在 JavaSE 规范中,对 instanceof 运算符的规定是:如果 obj 为 null,那么将返回 falseSystem.out.println(null instanceof Object);        //false}}class A {}class AA extends A {}class AAA extends AA {}

3.5.final 关键字在 Java 中的用法有哪些?

(1)被 final 修饰的不能够被继承,所有的方法不可以被重写,但其内的非 final 变量可以被修改;
(2)被 final 修饰的成员变量必须要初始化,赋初值后不能再重新赋值(可以调用对象方法修改属性值),对基本类型来说是其值不可变;
(3)对引用变量来说其引用不可变,即不能再指向其他的对象,但对象中的内容可以变化;
(4)被 final 修饰的方法不能重写,但 JVM 会尝试将其内联,以提高运行效率;

3.6.Java 中基本类型的转换规则有哪些?

(1)经常需要将一种数值类型转换为另一种数值类型。下图给出了数值类型之间的合法转换。
① 下图中的 6 个实心箭头,表示无信息丢失的转换;
② 下图中的 3 个虚心箭头,表示可能有精度损失的转换。
例如,123 456 789 是一个大整数, 它所包含的位数比 float 类型所能够表达的位数多。 当将这个整型数值转换为 float 类型时, 将会得到同样大小的结果,但却失去了一定的精度。

(2)6 条转换规则
① 8 种基本数据类型中,除了 boolean 类型不能转换,剩下七种类型之间都可以进行转换;
② 如果整数型字面量没有超过 byte、short、char 的取值范围,可以直接将其赋值给对应类型的变量;
③ 小容量向大容量转换称为自动类型转换,容量从小到大排序为:byte < short(char) < int < long < float < double,其中 short 和 char 都占用两个字节,但是 char 可以表示更大的正整数;
④ 大容量转换为小容量,称为强制类型转换,编写时必须添加“强制类型转换符”,但运行时可能会出现精度损失,须谨慎使用。
⑤ byte、short、char 类型混合运算时,先各自转换成 int 类型再做运算;
⑥ 多种数据类型混合运算时,各自先转换成容量最大的那一种再做运算。

3.7.a = a + b 与 a += b 有什么区别吗?

+= 操作符会进行隐式自动类型转换,此处 a += b 隐式地将加操作的结果类型强制转换为持有结果的类型,而 a = a + b 则不会自动进行类型转换。

byte a = 1;
byte b = 2;
b = a + b;        //报编译错误,不兼容的类型: 从 int 转换到 byte 可能会有损失
b += a;           //ok

同理,以下代码有也错误:

short s1= 1;
s1 = s1 + 1;  //报编译错误,不兼容的类型: 从 int 转换到 short 可能会有损失

short 类型在进行运算时会自动提升为 int 类型,也就是说 s1 + 1 的运算结果是 int 类型,而 s1 是 short 类型,从 int 转换到 short 可能会有精度损失,所以会报编译错误。正确写法如下:

short s1= 1;
s1 += 1;

+= 操作符会将右边的表达式结果强转为匹配左边的数据类型,所以没错。

其实,s1 += 1 相当于 s1 = (short)(s1 + 1),有兴趣的可以自己编译下这两行代码的字节码,你会发现是一样的。

3.8.对于整型数据来说,存在 i 使得 i + 1 < i 吗?

(1)存在,i = Integer.MAX_VALUE,即 i = 2147483647

(2)原因分析
计算机底层在进行加法运算时,会先将要相加的数转换为二进制补码,然后再将其相加。由于int 的取值范围是:-2147483648 ~ 2147483647 (-231 ~ 231 - 1)。当 i = Integer.MAX_VALUE,即 i = 2147483647时,其二进制补码如下所示,第一位的 0 是符号位,表示该数为正数。

01111111111111111111111111111111

从 int 的表示范围来看,i + 1 的结果显然超出了其表示范围。但是,在计算机底层进行计算时,i + 1 的补码是

10000000000000000000000000000000

该补码正好表示 Integer.MIN_VALUE,如果将 int 的表示范围看成一个环的话,当 i + 1 的结果大于 Integer.MAX_VALUE 时,那么从补码的角度来看,i + 1 会回到最小值,并且编译器不会提示报错。同理,当 i = Integer.MIN_VALUE时,i - 1 > i

(3)代码验证

public static void main(String[] args) {int i = Integer.MAX_VALUE;System.out.println(i);System.out.println(i + 1);System.out.println(i + 1 < i);//查看 i 和 i + 1 的二进制补码表示String strI = Integer.toBinaryString(i);String strIP1 = Integer.toBinaryString(-10);System.out.println("i 的二进制补码表示为:" + strI);System.out.println("i + 1 的二进制补码表示为:" + strIP1);
}

结果如下:

3.9.访问修饰符 public、private、protected、以及不写(默认)时的区别?

作用域 当前类 同包 子类 外部包
public
protected ×
default × ×
private × × ×

类的成员不写访问修饰时默认为 default。默认对于同一个包中的其他类相当于公开 (public),对于不是同一个包中的其他类相当于私有 (private)。受保护 (protected) 对子类相当于公开,对不是同一包中的没有父子关系的类相当于私有。

3.10.Java 中有没有 goto 关键字?

goto 是 Java 中的保留字,在目前版本的 Java 中没有使用。但是其实 goto 是 C 语言中的,goto 语句通常与条件语句配合使用,可用来实现条件转移, 构成循环,跳出循环体等功能。而在结构化程序语言中一般不主张使用 goto 语句,以免造成程序流程的混乱,使理解和调试程序都产生困难。但是在 Java 语言中,goto 只是作为了保留字,还没有使用,因为 Java 追求简单,方便。

3.11.在 Java 中,如何跳出当前的多重嵌套循环?

在要跳出的循环前加一个标记如 A,然后用 “break A” 可以跳出多重循环。Java 支持带标签的 break 和 continue 语句,作用有点类似于 C 和 C++中的 goto,但是就像要避免使用 goto 一样,应该避免使用带标签的 break 和 continue,因为它可能会造成程序流程的混乱。例子如下:

class Test {public static void main(String[] args) {A:for (int i = 0; i < 10; i++) {for (int j = 0; j < 10; j++) {System.out.println(j);if (j == 5) {break A;}}}}
}

结果如下:

0
1
2
3
4
5

3.12.char 型变量中能否存储一个中文汉字,为什么?

char 类型可以存储一个中文汉字,因为 Java 中使用的编码是 Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一个 char 类型占 2 个字节(16 比特),所以放一个中文是没问题的。

补充:使用 Unicode 意味着字符在 JVM 内部和外部有不同的表现形式,在 JVM 内部都是 Unicode,当这个字符被从 JVM 内部转移到外部时(例如存入文件系统中),需要进行编码转换。所以 Java 中有字节流字符流,以及在字符流和字节流之间进行转换的转换流,如 InputStreamReader 和 OutputStreamReader,这两个类是字节流和字符流之间的适配器类,承担了编码转换的任务;对于 C 程序员来说,要完成这样的编码转换恐怕要依赖于 union(联合体/共用体)共享内存的特征来实现。

3.13.switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上?

在 Java 5 以前,switch(expr)中,expr 只能是 byte、short、char、int。从 Java 5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型,从 Java 7 开始,expr 还可以是字符串 (String),但是长整型 (long) 在目前所有的版本中都是不可以的。

3.14.一个”.java”源文件中是否可以包含多个类(不是内部类)?有什么限制?

可以,但一个源文件中最多只能有一个公开类 (public class) 而且文件名必须和公开类的类名完全保持一致

3.15.hashCode() 与 equals() 的区别是什么?

见此文章。


4.对象与类

4.1.== 和 equals 的相同点和不同点是什么?

(1)相同点
① 都用于完成比较操作;
② 结果都返回布尔值;
③ 对于 Object 来说,== 和 equals() 比较的都是内存地址值,它们的作用相同;

(2)不同点
① == 是关系运算符,而 equals() 是方法

② 对于 == 来说:
如果比较对象都是基本类型,则比较它们的是否相等;
如果比较对象都是引用类型,则比较它们的内存地址值是否相等;
注意:不能比较没有父子关系的两个对象;

import java.util.*;public class Solution {public static void main(String[] args) {int a = 1;int b = 1;Solution s1 = new Solution();Solution s2 = new Solution();HashMap<Integer, Integer> hashMap = new HashMap<>();System.out.println(a == b);             // trueSystem.out.println(s1 == s2);           // falseSystem.out.println(s1 == hashMap);      // 提示报错}
}

③ 对于 equals 来说:
JDK 中的类一般已经重写了 equals(),比较的是内容;
自定义类如果没有重写 equals(),将调用父类(默认 Object 类)的 equals() 方法,Object 的 equals() 比较使用了 this == obj;
可以按照需求逻辑,重写对象的 equals() 方法,重写 equals() ,一般须重写 hashCode() ;

import java.util.*;public class Solution {public static void main(String[] args) {Solution s1 = new Solution();Solution s2 = new Solution();HashMap<Integer, Integer> hashMap = new HashMap<>();String str1 = "abc";String str2 = "abc";                    String str3 = new String("abc");         // 在堆中创建一个新的对象,str3 指向堆中的对象,而不是堆中的常量池System.out.println(s1 == s2);           // false,比较内存地址值System.out.println(s1.equals(s2));      // false,自定义类,未重写equals(),故通过 this == obj 比较内存地址值System.out.println(str1 == str2);       // true,str1 和 str2 都指向的是堆中的常量池中的同一个值,所以内存地址值一样System.out.println(str1.equals(str2));  // true,String为JDK中的类,故比较内容System.out.println(str1 == str3);       // false,str1 指向堆中的常量池中的"abc",str3 指向堆中的对象,内存地址值不一样System.out.println(str1.equals(str3));  // true,String为JDK中的类,故比较内容}
}

4.2.什么是装箱?什么是拆箱?它们的执行过程分别是什么?有哪些注意事项?

(1)装箱:基本类型转变为包装类型的过程。
(2)拆箱:包装类型转变为基本类型的过程。

//JDK1.5之前不支持自动装箱和自动拆箱,创建Integer对象必须按下面的方式进行
Integer i = new Integer(9);//JDK1.5开始提供了自动装箱的功能,可以按下面的方式创建Integer对象
Integer i = 9;//自动拆箱
int n = i;

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

(4)注意事项
① 整型的包装类 valueOf() 返回对象时,在常用的取值范围-128 ~ 127 内,会返回缓存对象。

Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1 == i2); //true
System.out.println(i3 == i4); //false

下面这段代码是 Integer 的 valueOf() 的具体实现:

public static Integer valueOf(int i) {if (i >= -128 && i <= IntegerCache.high) {return IntegerCache.cache[i + 128];} else {return new Integer(i);}
}

其中 IntegerCache 类的实现如下:

private static class IntegerCache {static final int low = -128;static final int high;static final Integer cache[];static {// high value may be configured by propertyint h = 127;String integerCacheHighPropValue =sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");if (integerCacheHighPropValue != null) {try {int i = parseInt(integerCacheHighPropValue);i = Math.max(i, 127);// Maximum array size is Integer.MAX_VALUEh = Math.min(i, Integer.MAX_VALUE - (-low) -1);} catch( NumberFormatException nfe) {// If the property cannot be parsed into an int, ignore it.}}high = h;cache = new Integer[(high - low) + 1];int j = low;for(int k = 0; k < cache.length; k++)cache[k] = new Integer(j++);// range [-128, 127] must be interned (JLS7 5.1.7)assert IntegerCache.high >= 127;}private IntegerCache() {}
}

从这两段代码可以看出,在通过 valueOf 方法创建 Integer 对象时,如果数值在 [-128, 127] 之间,便返回指向 IntegerCache.cache 中已经存在的对象的引用;否则创建一个新的 Integer 对象。上面的代码中 i1 和 i2 的数值为 100,因此会直接从 cache 中取已经存在的对象,所以 i1 和 i2 指向的是同一个对象,而 i3 和 i4 则是分别指向不同的对象。

② 浮点型的包装类 valueOf() 返回新的对象。
③ 布尔型的包装类 valueOf() 返回 Boolean 类的静态常量 TRUE 或 FALSE。

Double d1 = 100.0;
Double d2 = 100.0;
Double d3 = 200.0;
Double d4 = 200.0;
System.out.println(d1 == d2); //false
System.out.println(d3 == d4); //false
//解释:在某个范围内的整型数值的个数是有限的,而浮点数却不是Boolean b1 = false;
Boolean b2 = false;
Boolean b3 = true;
Boolean b4 = true;
System.out.println(b1 == b2); //true
System.out.println(b3 == b4); //true

④ 包含算术运算会触发自动拆箱。而如果存在大量自动装箱的过程,且装箱返回的包装对象不是从缓存中获取的,那么会创建很多新的对象,这样比较消耗内存以及运行时间。

public static void main(String[] args) {Integer s1 = 0;long t1 = System.currentTimeMillis();for (int i = 0; i < 1000 * 10000; i++) {s1 += i;}long t2 = System.currentTimeMillis();System.out.println("使用Integer,消耗了:" + (t2 - t1) + " ms");    // 44 msint s2 = 0;long t3 = System.currentTimeMillis();for (int i = 0; i < 1000 * 10000; i++) {s2 += i;}long t4 = System.currentTimeMillis();System.out.println("使用int,消耗了:" + (t4 - t3) + " ms");        // 4 ms
}

⑤ 基本类型的存在意义和包装类存在的意义是什么?
基本类型存储在栈里,因为栈的效率高,所以保留了基本类型。变量的值存储在栈中,方法执行时创建,结束时销毁,因此更加高效。

⑥ 有了基本类型为什么还要有包装类?
为了让基本类型也具有对象的特征,就出现了包装类型,它相当于将基本类型“包装起来”,使得其 Java 具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作,方便涉及到对象的操作。

4.3.基本类型和包装类对象使用 == 和 equals 进行比较的区别是什么?

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

(2)值相同
① 使用 == 比较
基本类型 - 基本类型、基本类型 - 包装对象,返回 true
包装对象 - 包装对象,非同一个对象(对象的内存地址不同)返回 false;对象的内存地址相同返回 true,如下面等于 100 的两个 Integer 对象(原因是 JVM 缓存部分基本类型常用的包装类对象,如 Integer -128 ~ 127 是被缓存的)

② 使用 equals() 比较
包装对象基本类型比较,返回 true;
包装对象包装对象比较,返回 true;

public static void main(String[] args) {Integer a = 100;Integer b = 100;Integer c = 200;Integer d = 200;int e = 100;int f = 200;System.out.println(a == b);         //trueSystem.out.println(c == d);         //falseSystem.out.println(a == e);         //trueSystem.out.println(c == f);         //trueSystem.out.println(a.equals(e));    //trueSystem.out.println(a.equals(b));    //trueSystem.out.println(c.equals(f));    //trueSystem.out.println(c.equals(d));    //true
}
public static String finallyTest1() {String str = "abc";int i = 0;if (i == 0) {//直接返回,未执行到 finally 语句块return str;}try {System.out.println("try...");return str;} finally {System.out.println("finally...");}
}public static String finallyTest2() {String str = "abc";int a = 0;//抛出异常,未执行到 finally 语句块int b = a / 0;try {System.out.println("try...");return str;} finally {System.out.println("finally...");}
}public static String finallyTest3() {String str = "abc";try {System.out.println("try...");//系统直接退出,未执行到 finally 语句块System.exit(0);return str;} finally {System.out.println("finally...");}
}

4.4.return 与 finally 的执行顺序对返回值有什么影响?

对于 try 和 finally 至少有一个语句块包含 return 语句的情况:
① 正常情况下(例如没有系统退出、出现异常等情况)finally 语句块会执行;
② 如果 finally 语句块中没有 return 语句,则 finally 对 return 语句中变量的重新赋值修改无效
③ 如果 try 和 finally 语句块中都包含 return 语句,则 return 值会以 finally 语句块中的 return 值为准

public class Test {public static void main(String args[]) {try {int a = 10 / 0;System.out.println(a);} catch (Exception e) {e.printStackTrace();//退出程序,finally 中的代码不会被执行System.exit(0);} finally {System.out.println("finally...");}}
}

结果如下:

java.lang.ArithmeticException: / by zeroat test.Test.main(Test.java:8)
public static void main(String[] args) {System.out.println(getInt());
}public static int getInt() {int a;try {a = 200;return a;} finally {System.out.println("finally...");a = 300;}
}

结果如下:

finally...
200
public static void main(String[] args) {System.out.println(getInt());
}public static int getInt() {int a;try {a = 200;return a;} finally {System.out.println("finally...");a = 300;return a;}
}

结果如下:

finally...
300

4.5.String、StringBuffer 和 StringBuilder 的区别是什么?

(1)String 是只读字符串,它并不是基本数据类型,而是一个对象。从底层源码来看是一个 final 类型的字符数组,所引用的字符串不能被改变,一经定义,无法再增删改。每次对 String 的操作都会生成新的 String 对象。

private final char value[];

每次 + 操作 : 隐式地在堆上 new 了一个跟原字符串相同的 StringBuilder 对象,再调用 append() 拼接后面的字符。

(2)StringBuffer 和 StringBuilder 都继承了 AbstractStringBuilder 抽象类,从 AbstractStringBuilder 抽象类中我们可以看到它们的底层都是可变的字符数组,所以在进行频繁的字符串操作时,建议使用 StringBuffer 和 StringBuilder 来进行操作。 另外 StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
性能:StringBuilder > StringBuffer > String

/**
* The value is used for character storage.
*/
char[] value;

(3)使用场景
当经常需要改变字符串内容时使用 StringBuffer 和 StringBuilder,一般情况下优先使用 StringBuilder,而在多线程使用共享变量时使用StringBuffer。

4.6.Java 的四种引用方式分别是什么?

(1)强引用 (Strong Reference)
强引用是平常中使用最多的引用,在程序内存不足时也不会被垃圾收集器回收。

//使用方式
Object obj=new Object()

(2)软引用 (Soft Reference)
① 软引用在程序内存不足时会被回收。
② 可用场景: 创建缓存的时候,创建的对象放进缓存中,当内存不足时,JVM 就会回收早先创建的对象。

/*注意:wrf 这个引用也是强引用,它是指向 SoftReference 这个对象的,这里的软引用指的是指向new String("str")的引用,也就是 SoftReference 类中T
*/
SoftReference<String> wrf = new SoftReference<String>(new String("str"));

(3)弱引用 (Weak Reference)
① 弱引用就是只要 JVM 垃圾回收器发现了它,就会将其回收。
② 可用场景: Java 源码中 java.util.WeakHashMap 中的 key 就是使用弱引用,一旦我不需要某个引用,JVM 会自动帮忙处理它,这样我们就不需要做其它操作。

//使用方式
WeakReference<String> wrf = new WeakReference<String>(str);

(4)虚引用 (Phantom Reference)
① 虚引用的回收机制跟弱引用差不多,但是它被回收之前,会被放入 ReferenceQueue 中。需要注意的是,其它引用是被 JVM 回收后才被传入 ReferenceQueue 中的。由于这个机制,所以虚引用大多被用于引用销毁前的处理工作。此外,虚引用创建的时候,必须带有ReferenceQueue。
② 可用场景: 对象销毁前的一些操作,比如说资源释放等。 Object.finalize() 虽然也可以做这类动作,但是这个方式即不安全又低效。

//使用方式
PhantomReference<String> prf = new PhantomReference<String>(new String("str"), new ReferenceQueue<>());

注意:上诉所说的几类引用,都是指对象本身的引用,而不是指 Reference 的四个子类的引用。

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

(1)Object 是所有类的根,是所有类的父类,所有对象包括数组都实现了 Object 的方法。Object 类结构如下图所示:

(2)各种方法介绍如下:
① clone()
保护方法,实现对象的浅复制,只有实现了 Cloneable 接口才可以调用该方法,否则抛出 CloneNotSupportedException 异常,深拷贝也需要实现 Cloneable,同时其成员变量为引用类型的也需要实现 Cloneable,然后重写 clone()。

② finalize()
该方法和垃圾收集器有关系,判断一个对象是否可以被回收的最后一步就是判断是否重写了此方法。

③ equals()
该方法使用频率非常高。equals 和 == 的区别见上面的4.1题,但是在 Object 中两者是一样的。子类一般都要重写这个方法。

④ hashCode()
该方法用于哈希查找,重写了 equals() 一般都要重写 hashCode(),这个方法在一些具有哈希功能的 Collection 中用到。

⑤ notify()
配合 synchronized 使用,该方法唤醒在该对象上等待队列中的某个线程(同步队列中的线程是给抢占 CPU 的线程,等待队列中的线程指的是等待唤醒的线程)。

⑥ notifyAll()
配合 synchronized 使用,该方法唤醒在该对象上等待队列中的所有线程。

⑦ wait()
配合 synchronized 使用,wait() 就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait() 一直等待,直到获得锁或者被中断。wait(long timeout) 设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生:

  1. 其他线程调用了该对象的 notify 方法;
  2. 其他线程调用了该对象的 notifyAll 方法;
  3. 其他线程调用了 interrupt 中断该线程;
  4. 时间间隔到了。此时该线程就可以被调度了,如果是被中断的话就抛出一个 InterruptedException 异常。

4.8.Java 创建对象的方式有哪几种?

(1)使用 new 关键字
这是最常见也是最简单的创建对象的方式,通过这种方式,我们可以调用任意的构造函数(无参的和有参的)。

public class Student {public static void main(String[] args) {Student student = new Student();}
}

(2)通过反射机制
① 使用 Class 类中的 newInstance 方法
使用 Class 类中的 newInstance 方法创建对象。这个 newInstance 方法调用无参的构造函数创建对象。

public class Student {public static void main(String[] args) throws Exception {//使用 Class 类中的 newInstance 方法创建对象,有以下两种方式Student student1 = (Student) Class.forName("test.Student").newInstance();Student student2 = Student.class.newInstance();System.out.println(student1 == student2);           //false}
}

② Constructor 类中的 newInstance 方法
和 Class 类中的 newInstance 方法很像, java.lang.reflect.Constructor 类里也有一个 newInstance 方法可以创建对象。我们可以通过这个newInstance 方法调用有参数的和私有的构造函数。

public class Student {public static void main(String[] args) throws Exception {Constructor<Student> constructor = Student.class.getConstructor();Student student = constructor.newInstance();}
}

(4)使用 clone 方法
无论何时我们调用一个对象的 clone(),JVM 就会创建一个新的对象,将前面对象的内容全部拷贝进去。用 clone() 创建对象并不会调用任何构造函数。要使用 clone(),我们需要先实现 Cloneable 接口并实现其定义的 clone()。

public class Student implements Cloneable {@Overrideprotected Object clone() throws CloneNotSupportedException {System.out.println("具体原型复制成功!");return (Student)super.clone();}public static void main(String[] args) throws CloneNotSupportedException {Student student1 = new Student();Student student2 = (Student)student1.clone();System.out.println(student1 == student2);       //false}
}

(5)序列化机制
序列化就是把对象通过流的方式存储到文件中,此对象要重写 Serializable 接口才能被序列化。
反序列化就是把文件中的内容读取出来,还原为 Java 对象,该过程也需要实现 Serializable 接口。
当我们序列化和反序列化一个对象,JVM 会给我们创建一个单独的对象。在反序列化时,JVM 创建对象并不会调用任何构造函数。

public class Student implements Serializable {private int age;public int getAge() {return age;}public void setAge(int age) {this.age = age;}public static void main(String[] args) throws Exception {ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("stu.txt"));Student student1 = new Student();student1.setAge(18);out.writeObject(student1);ObjectInputStream in = new ObjectInputStream(new FileInputStream("stu.txt"));Student student2 = (Student) in.readObject();System.out.println(student2.getAge());              //18System.out.println(student1 == student2);           //false}
}

4.9.浅拷贝和深拷贝的区别是什么?

(1)浅拷贝
浅拷贝是创建一个新对象,该对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,那么拷贝的就是内存地址 。

public class Address {int id;String addressName;public int getId() {return id;}public String getAddressName() {return addressName;}public Address(int id, String addressName) {this.id = id;this.addressName = addressName;}public void setId(int id) {this.id = id;}public void setAddressName(String addressName) {this.addressName = addressName;}@Overridepublic String toString() {return "Address{" +"id=" + id +", addressName='" + addressName + '\'' +'}';}
}
public class Person implements Cloneable {private int id;private String name;private Address address;public String getName() {return name;}public void setId(int id) {this.id = id;}public void setName(String name) {this.name = name;}public int getId() {return id;}public Address getAddress() {return address;}public void setAddress(Address address) {this.address = address;}public Person(int id, String name, Address address) {this.id = id;this.name = name;this.address = address;}@Overrideprotected Object clone() throws CloneNotSupportedException {return (Person) super.clone();}@Overridepublic String toString() {return "Person{" +"id=" + id +", name='" + name + '\'' +", address=" + address +'}';}
}
public class Test {public static void main(String[] args) throws CloneNotSupportedException {Person person1 = new Person(1, "小华", new Address(1, "北京"));//浅拷贝Person person2 = (Person) person1.clone();System.out.println("person1 == person2 的结果为:" + (person1 == person2));System.out.println("\n改变person1的属性值之前:");System.out.println("person1:" + person1);System.out.println("person2:" + person2);System.out.println("person1.getName() == person2.getName()的结果为:" + (person1.getName() == person2.getName()));System.out.println("person1.getAddress() == person2.getAddress()的结果为:" + (person1.getAddress() == person2.getAddress()));person1.setId(2);person1.setName("小明");person1.getAddress().setId(2);person1.getAddress().setAddressName("武汉");System.out.println("\n改变person1的属性值之后:");System.out.println("person1:" + person1);System.out.println("person2:" + person2);System.out.println("person1.getName() == person2.getName()的结果为:" + (person1.getName() == person2.getName()));System.out.println("person1.getAddress() == person2.getAddress()的结果为:" + (person1.getAddress() == person2.getAddress()));}
}

(2)深拷贝
深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象。

实现方式如下:
① 通过对象序列化实现深拷贝(推荐)
先将 Address 类和 Person 类实现 Serializable 接口,然后再修改Test.java中的代码即可。

package test;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;public class Test {public static void main(String[] args) throws Exception {Person person1 = new Person(1,"小华",new Address(1,"北京"));//创建对象输出流对象ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\testData\\a.txt"));//写对象oos.writeObject(person1);//释放资源oos.close();//创建对象输入流对象ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\testData\\a.txt"));//读取对象(深拷贝)Person person2 = (Person) ois.readObject();//释放资源ois.close();System.out.println("person1 == person2 的结果为:" + (person1 == person2));System.out.println("\n改变person1的属性值之前:");System.out.println("person1:" + person1);System.out.println("person2:" + person2);System.out.println("person1.getName() == person2.getName()的结果为:" + (person1.getName() == person2.getName()));System.out.println("person1.getAddress() == person2.getAddress()的结果为:" + (person1.getAddress() == person2.getAddress()));person1.setId(2);person1.setName("小明");person1.getAddress().setId(2);person1.getAddress().setAddressName("武汉");System.out.println("\n改变person1的属性值之后:");System.out.println("person1:" + person1);System.out.println("person2:" + person2);System.out.println("person1.getName() == person2.getName()的结果为:" + (person1.getName() == person2.getName()));System.out.println("person1.getAddress() == person2.getAddress()的结果为:" + (person1.getAddress() == person2.getAddress()));}
}


② 重写Person 类中的 clone() 方法来实现深拷贝

public class Address implements Cloneable {//...@Overridepublic Object clone() throws CloneNotSupportedException {return (Address) super.clone();}//...
}
public class Person implements Cloneable {//...@Overridepublic Object clone() throws CloneNotSupportedException {Person person = null;person = (Person)super.clone();//对引用数据类型单独处理person.name = new String(name);person.address = (Address)address.clone();return person;}//...
}

(3)区别
浅拷贝基本类型之前互不影响,引用类型其中一个对象改变了地址,就会影响另一个对象;
深拷贝改变新对象不会影响原对象,它们之前互不影响。

4.10.介绍一下 Java 中的可变参数。

(1)可变参数的作用是在不确定参数的个数时,可以使用可变参数。即 Java 允许将同一个类中多个同名同功能但参数个数不同的方法封装成一个方法。其基本语法如下:

访问修饰符 返回类型 方法名(数据类型... 形参名){//方法体
}

(2)案例
① 利用方法的重载,实现三个方法 sum,用于求2 ~ 4个数的和。

class Solution{// 两数之和public void sum(int a, int b){System.out.println("2数之和: " + (a + b));}// 三数之和public void sum(int a, int b, int c){System.out.println("3数之和: " + (a + b + c));}// 四数之和public void sum(int a, int b, int c, int d){System.out.println("4数之和: " + (a + b + c + d));}public static void main(String[] args) {Solution my = new Solution();my.sum(1,2);my.sum(1,2,3);my.sum(1,2,3,4);}
}

② 使用可变参数进行优化

class Solution{//n数之和public void sum(int... nums){// 可变参数可以当作数组使用int sum = 0;for (int i = 0; i < nums.length; i++) {sum += nums[i];}System.out.println(nums.length + "数之和为: " + sum);}public static void main(String[] args) {Solution my = new Solution();my.sum(1,2);my.sum(1,2,3);my.sum(1,2,3,4);}
}

(3)注意事项
① 可变参数的实参可以为 0个或任意多个;
② 可变参数的实参可以为数组;
③ 可变参数的本质是数组;
④ 一个形参列表只能出现一个可变参数;
⑤ 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后

4.11.static 关键字的用法有哪些?

(1)static 修饰变量
① 被 static 修饰的成员变量属于类,不属于某个对象(多个对象访问或修改 static 修饰的成员变量时,其中一个对象将 static 成员变量进行了修改,其他的对象的 static 成员变量值跟着改变),多个对象共享同一个 static 成员变量
② 被 static 修饰的成员,可以通过类名直接访问;
③ 在静态方法中,不能访问非静态的内容(变量),不能使用 this/super;非静态中,可以访问静态中的变量;

(2)static 修饰方法
① static 修饰的方法被称之为静态方法,也叫类方法
② 加 static 的方法,可以通过类名直接访问,也可以通过对象名访问,且不能再内部写this,因为直接用类名.方法的时候,没有当前对象。而不加 static 只能通过对象名访问;
③ 加了 static 的方法,不能在内部访问非 static 的属性和行为,因为在静态方法的内部无法确定非 static 的内容属于那个对象,它是通过类名调用。

(3)static 修饰代码块
① 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。
② 静态代码块只在第一次 new 执行一次,之后不再执行,而非静态代码块在每 new 一次就执行一次。非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。

(4)static 修饰类
① static 有一种特殊用法是修饰内部类(普通类是不允许声明为静态的,只有内部类才可以),被 static 修饰的内部类可以直接作为一个普通类来使用,而不需实例一个外部类;
② 当一个内部类没有使用 static 修饰的时候,不能直接使用内部类创建对象,需要先使用外部类对象+ . + new内部类对象。

4.12.静态变量和实例变量有什么区别?

(1)静态变量是被 static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一 个拷贝;
(2)实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。
(3)静态变量可以实现让多个对象共享内存。在 Java 开发中,上下文类和工 具类中通常会有大量的静态成员。

4.13.this 和 super 关键字的区别是什么?

(1)this
① 对象内部指代自身的引用
② 解决成员变量和局部变量同名问题
③ 可以调用成员变量
③ 不能调用局部变量
④ 可以调用成员方法
⑤ 在普通方法中可以省略 this
⑥ 在静态方法(也称为类方法)当中不允许出现 this 关键字

(2)super
① 代表对当前对象的直接父类对象的引用
② 可以调用父类的非 private 成员变量和方法
③ super() 可以调用父类的构造方法,只限构造方法中使用,且必须是第一条语句

4.14.说说内存中的栈 (stack)、堆 (heap) 和静态存储区的用法。

(1)通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用内存中的栈空间
(2)通过 new 关键字和构造器创建的对象放在堆空间
(3)程序中的字面量 (literal) 如直接书写的 100、“hello, world” 和常量都是放在静态存储区中。
(4)栈空间操作最快但是也很小,通常大量的对象都是放在堆空间,整个内存包括硬盘上的虚拟内存都可以被当成堆空间来使用。
(5)String str = new String(“hello”);
上面的语句中 str 放在栈中,用 new 创建出来的字符串对象放在堆中,而 “hello” 这个字面量放在静态存储区,一共创建了两个字符串对象。

4.15.什么是注解?

与注解有关的知识可以查看Java基础——注解这篇文章。


5.继承

5.1.重写和重载的区别是什么?

(1)重写 (Override)
从字面上看,重写就是重新写一遍的意思。其实就是在子类中把父类本身有的方法重新写一遍。子类继承了父类原有的方法,但有时子类并不想原封不动的继承父类中的某个方法, 此时可以对方法体进行修改或重写。但需要注意以下几点,简称两同两小加一大原则:
① 方法名、参数列表相同;
② 子类返回类型小于等于父类方法返回类型、重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常
② 子类函数的访问修饰权限要大于等于父类的 (public > protected > default > private)

public class Father {public void sayHello() {System.out.println("Hello, Father");  //Hello, Son}public static void main(String[] args) {Son s = new Son();s.sayHello();}
}class Son extends Father{@Overridepublic void sayHello() {// TODO Auto-generated method stubSystem.out.println("Hello, Son");}
}

(2)重载 (Overload)
在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载,它是一个类中多态性的一种表现。同时,重载对返回类型没有要求,可以相同也可以不同,但不能通过返回类型是否相同来判断重载。

public class Father {public static void main(String[] args) {// TODO Auto-generated method stubFather s = new Father();s.sayHello();s.sayHello("wintershii");}public void sayHello() {System.out.println("Hello");}public void sayHello(String name) {System.out.println("Hello" + " " + name);}
}

5.2.类加载器实例化时进行的操作步骤是什么?

其操作步骤是:加载 -> 连接 -> 初始化
先静态:父静态 > 子静态(静态只初始化一次)。
优先级:父类 > 子类 , 静态代码块 > 非静态代码块 > 构造函数。
① 代码书写顺序加载父类静态变量和父类静态代码块
② 代码书写顺序加载子类静态变量和子类静态代码块
③ 父类非静态变量(父类实例成员变量)
④ 父类非静态代码块
⑤ 父类构造函数
⑥ 子类非静态变量(子类实例成员变量)
⑦ 子类非静态代码块
⑧ 子类构造函数

class A {//静态代码块(只初始化一次)static {System.out.println("父类的静态代码块...");}//非静态代码块{System.out.println("父类的非静态代码块...");}//构造函数public A() {System.out.println("父类的构造函数...");}
}class B extends A {//静态代码块(只初始化一次)static {System.out.println("子类的静态代码块...");}//非静态代码块{System.out.println("子类的非静态代码块...");}//构造函数public B() {System.out.println("子类的构造函数...");}
}public class Solution {public static void main(String[] args) {A ab = new B();System.out.println("---------");ab = new B();}
}

上述代码的运行结果如下:

5.3.什么是反射?

与反射有关的知识可以查看Java基础——反射这篇文章。

5.4.this 和 super 这两个关键字有什么区别?

(1)this:

  • 可以近似看作对象内部指代自身的引用,但不能将 super 与 this 赋给另一个对象变量;
  • 解决成员变量和局部变量同名问题;
  • 可以调用成员变量、成员方法,但是不能调用局部变量;
  • this() 可以调用本类的构造器,但只限构造方法中使用,且必须是第一条语句
  • 在普通方法中可以省略 this,但在静态方法当中不允许出现 this 关键字;

(2)super:

  • 代表对当前对象的直接父类对象的引用;
  • 可以调用父类的非 private 成员变量和方法;
  • super() 可以调用父类的构造方法,只限构造方法中使用,且必须是第一条语句
class Demo {public Demo () {System.out.println("init Demo without arg...");}public Demo (int a) {System.out.println("init Demo with an arg...");}
}public class SubDemo extends Demo{public SubDemo() {super(1);System.out.println("init SubDemo without arg...");}public SubDemo(int a) {//调用本类的无参构造方法,但注意不能在无参构造方法的第一行调用 this(1),否则会出现构造方法循环调用this();System.out.println("init SubDemo with an arg...");}public SubDemo(int a, int b) {super();System.out.println("init SubDemo with two args...");}public static void main(String[] args) {SubDemo demo1 = new SubDemo(3);SubDemo demo2 = new SubDemo(3, 4);}
}
init Demo with an arg...
init SubDemo without arg...
init SubDemo with an arg...
init Demo without arg...
init SubDemo with two args...

6.接口、Lambda 表达式与内部类

6.1.抽象类 (abstract class) 和接口 (interface) 有什么区别?

(1)使用方式
① 抽象类只能继承一个,接口可以实现多个(单继承,多实现)
② 接口比抽象类更加抽象,因为抽象类中可以定义构造方法,可以有抽象方法和具体方法。而接口中不能定义构造方法,而且其中的方法全部都是抽象方法,默认且只能是 public abstract 的。
③ 抽象类中可以定义静态块,而接口中则不能。
④ 抽象类中可以定义各种成员变量,并且接口中定义的成员变量实际上都是常量,默认且只能是 public static final 的。

⑤ 有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法。

(2)设计目的
① 接口的设计目的,是对类的行为进行约束(更准确的说是一种"有"约束,因为接口不能规定类不可以有什么行为),也就是提供一种机制,可以强制要求不同的类具有相同的行为。它只约束了行为的有无,但不对如何实现行为进行限制。

② 抽象类的设计目的,是代码复用。当不同的类具有某些相同的行为(记为行为集合 A),且其中一部分行为的实现方式一致时(A的非真子集,记为B),可以让这些类都派生于一个抽象类。在这个抽象类中实现了 B,避免让所有的子类来实现 B,这就达到了代码复用的目的。而 A - B 的部分,留给各个子类自己实现。正是因为 A - B 在这里没有实现,所以抽象类不允许实例化出来(否则当调用到 A - B 时,无法执行)。

(3)设计思想
① 抽象类是对类本质的抽象(自下而上),表达的是 is xxx 的关系,比如: BMW is a car。抽象类包含并实现子类的通用特性,将子类存在差异化的特性进行抽象,交由子类去实现。
② 而接口是对行为的抽象(自上而下),表达的是 like xxx 的关系。比如:Bird like a Aircraft(像飞行器一样可以飞),但其本质上 is a Bird。接口的核心是定义行为,即实现类可以做什么,至于实现类主体是谁、是如何实现的,接口并不关心。

(4)使用场景
当关注事物的本质时,使用抽象类;当你关注事物的操作时,使用接口。

(5)复杂度
抽象类的功能要远超过接口,但是定义抽象类的代价比较高。因为对于Java来说,每个类只能继承一个类,在这个类中,你必须继承或编写出其所有子类的所有共性。虽然接口在功能上会弱化许多,但是它只是针对一个动作的描述。而且你可以在一个类中同时实现多个接口,在设计阶段会降低难度。

6.2.什么是 Lambda 表达式?

与 Lambda 表达式有关的知识可以查看Java基础——Lambda表达式这篇文章。

6.3.什么是动态代理?其应用场景有哪些?

(1)由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

(2)Java 中的代理按照代理类生成时机不同又分为静态代理动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在 Java 运行时动态生成。动态代理又有 JDK 代理和 CGLib 代理两种。
有关动态代理的具体使用可以参考详解23种设计模式(基于Java)—— 结构型模式(三 / 五)这篇文章中的代理模式。

(3)应用场景
① 统计每个 API 的请求耗时;
② 统一的日志输出;
③ 校验被调用的 API 是否已经登录和权限鉴定;
④ Spring 的 AOP 功能模块就是采用动态代理的机制来实现切面编程;

6.4.什么是内部类?为什么需要使用内部类?

(1)简单来说,内部类 (inner class) 是定义在另一个类中的类
(2)使用内部类的主要原因有以下三点:
① 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。
② 内部类可以对同一个包中的其他类隐藏起来。
③ 当想要定义一个回调函数且不想编写大量代码时,使用匿名 (anonymous) 内部类比较便捷。

6.5.匿名内部类可以继承类或实现接口吗?为什么?

(1)匿名内部类本质上是对父类方法的重写或对接口方法的实现。从语法角度看,匿名内部类创建处无法使用关键字继承类或实现接口
(2)原因如下:
① 匿名内部类没有名字,所以它没有构造函数。因为没有构造函数,所以它必须通过父类的构造函数来实例化。即匿名内部类完全把创建对象的任务交给了父类去完成。
② 匿名内部类里创建新的方法没有太大意义,新方法无法被调用。
③ 匿名内部类一般是用来覆盖父类的方法。
④ 匿名内部类没有名字,所以无法进行向下的强制类型转换,只能持有匿名内部类对象引用的变量类型的直接或间接父类。


7.异常、断言和日志

7.1.什么是内存泄漏和内存溢出?

(1)内存泄漏 (Memory Leak):指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

(2)内存溢出 (Out Of Memory,简称 OOM):指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存。常见的解决方案有:
① 修改JVM启动参数,直接增加内存。
② 检查错误日志,查看 “Out Of Memory” 错误前是否有其它异常或错误。
③ 对代码进行走查和分析,找出可能发生内存溢出的位置。
④ 使用内存查看工具动态查看内存使用情况。

(3)内存泄漏最终会导致内存溢出。

7.2.谈一谈 Java 中的异常?

(1)Java 中的异常层次结构

(2)在 Java 中, 所有的异常都是由 Throwable 继承而来(如果 Java 中内置的异常类不能够满足需求,用户可以创建自己的异常类),但在下一层立即分解为两个分支:Error 和 Exception
① Error 类层次结构描述了 Java 运行时系统的内部错误和资源耗尽错误。 应用程序不应该抛出这种类型的对象。 如果出现了这样的内部错误, 除了通告给用户,并尽力使程序安全地终止之外, 再也无能为力了。这种情况很少出现。
② 在设计 Java 程序时, 需要关注 Exception 层次结构。 这个层次结构又分解为两个分支:一个分支派生于 RuntimeException ; 另一个分支包含其他异常。划分两个分支的规则是:由程序错误导致的异常属于 RuntimeException;而程序本身没有问题, 但由于像 I/O 错误这类问题导致的异常属于其他异常

(3)“如果出现 RuntimeException 异常, 那么就一定是你的问题” 是一条相当有道理的规则。常见的 RuntimeException 有以下几种:
① ArithmeticException(算术异常)
② ClassCastException (类转换异常)
③ IllegalArgumentException (非法参数异常)
④ IndexOutOfBoundsException (下表越界异常)
⑤ NullPointerException (空指针异常)
⑥ SecurityException (安全异常)

7.3.什么时候用 assert?如何使用 assert?

(1)assertion(断言)在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。一般来说,assertion 用于保证程序最基本、关键的正确性。assertion 检查通常在开发和测试时开启
(2)为了提高性能,在软件发布后, assertion 检查通常是关闭的。在实现中,断言是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为 true;如果表达式计算为 false,那么系统会报告一个 AssertionError。
(3)断言用于调试目的:

assert(a > 0); // throws an AssertionError if a <= 0

(4)断言有以下两种形式:

assert Expr1;
assert Expr1 : Expr2;

Expr1 应该总是产生一个布尔值。 Expr2 可以是得出一个值的任意表达式;这个值用于生成显示更多调试信息的字符串消息。这两种表达形式本质是一样的,不同在于第二种方式中,可以指定输出错误的信息。

(5)断言在默认情况下是禁用的。
① 要在编译时启用断言,需使用 source 1.4 标记:

javac -source 1.4 Test.java

② 要在运行时启用断言,可使用 -ea 或者 -enableassertions 标记。
③ 要在运行时选择禁用断言,可使用 -da 或者 -disableassertions 标记。
④ 要在系统类中启用断言,可使用 -esa 或者 -dsa 标记。还可以在包的基础上启用或者禁用断言。

(6)可以在预计正常情况下不会到达的任何位置上放置断言。断言可以用于验证传递给私有方法的参数。不过,断言不应该用于验证传递给公有方法 的参数,因为不管是否启用了断言,公有方法都必须检查其参数。不过,既可以 在公有方法中,也可以在非公有方法中利用断言测试后置条件。另外,断言不应该以任何方式改变程序的状态。

7.4.throw 和 throws 的区别是什么?

(1)throw
① 表示方法内抛出某种异常对象(只能是一个);
② 用于程序员自行产生并抛出异常;
③ 位于方法体内部,可以作为单独语句使用;
④ 如果异常对象是非 RuntimeException,则需要在方法申明时加上该异常的抛出,即需要加上 throws 语句或者在方法体内使用 try catch 处理该异常,否则编译报错;
⑤ 执行到 throw 语句则后面的语句块不再执行;

(2)throws
① 方法的定义上使用 throws 表示这个方法可能抛出某些异常(可以有多个);
② 用于声明在该方法内抛出了异常;
③ 必须跟在方法参数列表的后面,不能单独使用;
④ 需要由方法的调用者进行异常处理;

package test;import java.io.IOException;class Solution {/*** 测试 throws 关键字* @throws NullPointerException*/public static void testThrows() throws NullPointerException {Integer i = null;System.out.println(i + 1);}/*** 测试 throw 关键字抛出运行时异常* @param i*/public static void testThrow(Integer i) {if (i == null) {//运行时异常不需要在方法上申明throw new NullPointerException();}}/*** 测试 throw 关键字抛出非运行时异常,需要方法体需要加 throws 异常抛出申明* @param filePath*/public static void testThrow(String filePath) throws IOException {if (filePath == null) {//非运行时异常,需要方法体需要加 throws 异常抛出申明throw new IOException();}}public static void main(String[] args) {testThrows();Integer i = null;testThrow(i);String filePath = null;try {testThrow(filePath);} catch (IOException e) {e.printStackTrace();}}
}

上述代码中三个方法对应的异常如下:



7.5.Error 和 Exception 有什么区别?

(1)Error 表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题;比如内存溢出,不可能指望程序能处理这样的情况;
(2)Exception 表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;也就是说,它表示如果程序运行正常,从不会发生的情况。


8.泛型程序设计

8.1.为什么要使用泛型程序设计?其优点是什么?

(1)泛型程序设计 (Generic programming) 意味着编写的代码可以被很多不同类型的对象所重用。例如, 我们并不希望为聚集 String 和 File 对象分别设计不同的类。实际上,也不需要这样做,因为一个 ArrayList 类可以聚集任何类型的对象。这是一个泛型程序设计的实例。泛型的本质是把参数的类型参数化,也就是所操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中。

(2)使用泛型编写的程序代码,要比使用 Object 变量再进行强制类型转换的代码具有更好的安全性和可读性。此外,多种数据类型执行相同的代码使用泛型可以复用代码。


9.集合

9.1.Java 有哪些常用容器(集合)?


(1)从上面的集合框架图可以看到,Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。Collection 接口又有 3 种子类型,ListSetQueue,再下面是一些抽象类,最后是具体实现类,常用的有 ArrayList、LinkedList、HashSet、LinkedHashSet、HashMap、LinkedHashMap 等等。


(2)集合框架体系如下图所示:

9.2.Collection 和 Collections 有什么区别?

(1)Collection 是 JDK 中集合层次结构中的最根本的接口,定义了集合类的基本方法。Collection 接口在 Java 类库中有很多具体的实现,其意义是为各种具体的集合提供了最大化的统一操作方式。
(2)Collections 是一个包装类,是 Collection 集合框架的工具类。它包含有各种有关集合操作的静态多态方法,不能实例化,比如排序方法: Collections. sort(list)。

9.3.List、Set、Map 之间的区别是什么?

(1)List:有序集合,元素可重复;
(2)Set:不重复集合,LinkedHashSet 按照插入排序,SortedSet 可排序,HashSet 无序;
(3)Map:键值对集合,存储键、值和之间的映射;Key无序且唯一;value 不要求有序,允许重复。

9.4.HashMap 的长度为什么是 2 的 N 次方?源码中是如何保证的?

(1)为了能让 HashMap 存数据和取数据的效率高,尽可能地减少 hash 值的碰撞,也就是说尽量把数据均匀地分配,每个链表或者红黑树的长度尽量相等。我们首先可能会想到通过取模操作来实现。取余 (%) 操作中如果除数是 2 的幂次,则等价于与其除数减一的与(&)操作,也就是说 hash % length == hash & (length - 1) 的前提是 length 是 2 的 N 次方。并且,采用二进制位操作 & ,相对于 % 能够提高运算效率。
注:HashMap 的初始长度是 16。

(2)在扩容迁移的时候不需要再重新通过哈希定位新的位置了。扩容后元素新的位置,要么在原脚标位,要么在原脚标位 + 扩容长度的位置
(3)HashMap 源码中的 tableSizeFor(int cap) 可以保证其长度永远是是 2 的 N 次方

/*** Returns a power of two size for the given target capacity.*/
static final int tableSizeFor(int cap) {int n = cap - 1;n |= n >>> 1;n |= n >>> 2;n |= n >>> 4;n |= n >>> 8;n |= n >>> 16;return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

具体分析可参考这篇文章。

9.5.HashMap 和 Hashtable 的异同有哪些?

(1)出现的版本不一样,Hashtable 出现于 Java 发布的第一版本 JDK 1.0,HashMap 出现于 JDK 1.2。
(2)都实现了 Map、Cloneable、Serializable(当前 JDK 版本 1.8)。
(3)HashMap 继承的是 AbstractMap,并且 AbstractMap 也实现了 Map 接口。Hashtable 继承Dictionary。
(4)Hashtable 中大部分 public 修饰普通方法都是 synchronized 字段修饰的,是线程安全的,HashMap 是非线程安全的。
(5)Hashtable 的 key 不能为 null,value 也不能为 null,这个可以从 Hashtable 源码中的 put 方法看到,判断如果 value 为 null 就直接抛出空指针异常,在 put 方法中计算 key 的 hash 值之前并没有判断 key 为 null 的情况,那说明,这时候如果 key 为空,照样会抛出空指针异常。
(6)HashMap 的 key 和 value 都可以为 null。在计算 hash 值的时候,有判断,如果 key==null ,则其 hash=0 ;至于 value 是否为 null,根本没有判断过。
(7)Hashtable 直接使用对象的 hash 值。hash 值是 JDK 根据对象的地址或者字符串或者数字算出来的 int 类型的数值。然后再使用除留余数法来获得最终的位置。然而除法运算是非常耗费时间的,效率很低。HashMap 为了提高计算效率,将哈希表的大小固定为了 2 的幂,这样在取模预算时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。
(8)Hashtable、HashMap 都使用了 Iterator。而由于历史原因,Hashtable 还使用了 Enumeration 的方式。
(9)默认情况下,初始容量不同,Hashtable 的初始长度是 11,之后每次扩充容量变为之前的 2 * n+1(n 为上一次的长度)而 HashMap 的初始长度为 16,之后每次扩充变为原来的两倍。

具体细节可参考这篇文章。

9.6.HashMap 与 ConcurrentHashMap 的异同有哪些?

(1)都是 key-value 形式的存储数据;
(2)HashMap 是线程不安全的,ConcurrentHashMap 是 JUC 下的线程安全的;
(3)HashMap 底层数据结构是数组 + 链表(JDK 1.8 之前)。JDK 1.8 之后是数组 + 链表 + 红黑树。当链表中元素个数达到 8 的时候,链表的查询速度不如红黑树快,链表会转为红黑树,红黑树查询速度快;
(4)HashMap 初始数组大小为 16(默认),当出现扩容的时候,变为原来的两倍;
(5)ConcurrentHashMap 在 JDK 1.8 之前是采用分段锁来现实的 Segment + HashEntry,
(6)Segment 数组大小默认是 16,2 的 n 次方;JDK 1.8 之后,采用 Node + CAS + Synchronized 来保证并发安全进行实现。

具体细节可参考这篇文章

9.7.poll() 方法和 remove() 方法有什么异同?

(1)相同点:poll() 和 remove() 都是从队列中取出一个元素
(2)不同点:poll() 在获取元素失败的时候会返回空,但 remove() 失败的时候会抛出异常

9.8.ArrayList 和 LinkedList 有什么区别?

(1)底层数据结构
ArrayList 是基于动态数组实现的,在一片连续的内存空间中存储数据。
LinkedList 是基于链表实现的,数据可以存储在分散的内存空间中。

(2)读取、插入、删除操作
由于底层数据结构的不同,读取、插入、删除操作的性能也会有所不同。
① 在读取方面 ArrayList 的性能比 LinkedList 高,ArrayList 支持通过索引访问(也称随机访问),可以在 O(1) 的时间复杂度内进行随机读取。而 LinkedList 需要从头开始遍历直到找到目标元素为止,其时间复杂度为 O(n)。
② 对于插入、删除操作,一般来说,LinkedList 的性能要比 ArrayList 高。ArrayList 在插入和删除元素时,可能需要进行元素移动甚至扩容操作;而 LinkedList 只需找到要插入的位置并实例化对象,然后修改节点指针即可。不过需要注意的是如果 ArrayList 使用尾插法并指定初始容量,这可以极大地提升性能,甚至超过 LinkedList。

(3)遍历操作
ArrayList 和 LinkedList 常见的遍历方式有 3 种:for(结合 get(i) 方法)、foreach、iterator。
① 在数据量比较小时,不同遍历方式的性能差别不大。
② 但是在数据量比较大时:
对于 ArrayList 来说,3 种遍历方式差距不是很大,其中 for 循环的效率最高,因为采用直接的下标运算。对于 LinkedList 来说,迭代器效率最高,因为其相当于维护一个当前状态指针,遍历只需要扫描一遍双向链表即可,而 for 效率最低,因为需要扫描 n 遍链表。不难看出,两种 List 中,foreach 的效率都比迭代器略低,因为其底层就是由迭代器实现的,只不过为了方便书写,做了简单的封装。

具体细节可参考 ArrayList 和 LinkedList 的三种遍历方式 这篇文章。

(4)内存空间利用率
一般来说,存储相同类型、相同大小的数据时,LinkedList 比 ArrayList 更占内存,其内存空间利用率更低,因为 LinkedList 为每一个节点存储了两个引用节点,一个指向前一个元素,另一个指向下一个元素。不过有时在 ArrayList 列表的结尾预留一定的容量空间,这也会造成一定的内存空间的浪费。

(5)扩容问题
ArrayList 使用动态数组实现,无参构造函数中默认初始化长度为 10,当需要扩容时会将原数组中的元素重新拷贝到长度为原数组的 1.5 倍的新数组中,扩容代价比较高;LinkedList 通过链表实现,所以不存在扩容问题,新增元素直接放到集合尾部,并修改相应的指针节点即可。

10.其它常用类

10.1.LocalDateTime & Calendar

10.1.1.如何取当前的年、月、日、时、分、秒、毫秒?

创建 java.util.Calendar 实例,调用其 get() 传入不同的参数即可获得参数所对应的值。

import java.util.Calendar;public class TestDateAndTime {public static void main(String[] args) {//获取当前的年、月、日、时、分、秒、毫秒Calendar calendar = Calendar.getInstance();//年System.out.println(calendar.get(Calendar.YEAR));//月,需要注意的是 Calendar.MONTH 是从 0 开始的System.out.println(calendar.get(Calendar.MONTH) + 1);//天System.out.println(calendar.get(Calendar.DATE));//时System.out.println(calendar.get(Calendar.HOUR));//分System.out.println(calendar.get(Calendar.MINUTE));//秒System.out.println(calendar.get(Calendar.SECOND));//毫秒System.out.println(calendar.get(Calendar.MILLISECOND));}
}

10.1.2.如何获取从 1970 年 1 月 1 日 0 时 0 分 0 秒到现在的毫秒数?

import java.util.Calendar;public class TestDateAndTime {public static void main(String[] args) {Calendar calendar = Calendar.getInstance();//以下 2 种方法均可以获取从 1970 年 1 月 1 日 0 时 0 分 0 秒到现在的毫秒数System.out.println(System.currentTimeMillis());System.out.println(Calendar.getInstance().getTimeInMillis());}
}

10.1.3.如何获取某年某月的最后一天?

import java.time.LocalDate;
import java.util.Calendar;public class TestDateAndTime {public static void main(String[] args) {Calendar calendar = Calendar.getInstance();//某月最后一天//2018-05月最后一天,6月1号往前一天calendar.set(Calendar.YEAR, 2018);calendar.set(Calendar.MONTH, 5);calendar.set(Calendar.DAY_OF_MONTH, 1);calendar.add(Calendar.DAY_OF_MONTH, -1);System.out.println(calendar.get(Calendar.YEAR) + "-" + (calendar.get(Calendar.MONTH) + 1) + "-" + calendar.get(Calendar.DAY_OF_MONTH));//JDK 1.8 java.time 包LocalDate date = LocalDate.of(2019, 6, 1).minusDays(1);System.out.println(date.getYear() + "-" + date.getMonthValue() + "-" + date.getDayOfMonth());}
}

10.1.4.打印昨天的当前时刻

import java.time.LocalDateTime;public class YesterdayCurrent {public static void main(String[] args) {LocalDateTime today = LocalDateTime.now();LocalDateTime yesterday = today.minusDays(1);System.out.println(yesterday);}
}

或者使用以下方式:

import java.util.Calendar;public class YesterdayCurrent {public static void main(String[] args) {Calendar cal = Calendar.getInstance();cal.add(Calendar.DATE, -1);System.out.println(cal.getTime());}
}

10.1.5.如何格式化日期?

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;public class TestDateAndTime {public static void main(String[] args) {Date date = new Date();SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//格式化日期System.out.println(simpleDateFormat.format(date));//JDK 1.8 java.time 包System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));}
}

10.2.Runtime

10.2.1.如何获取剩余的内存、总内存及最大堆内存?

可以通过 java.lang.Runtime 类中与内存相关方法来获取剩余的内存、总内存及最大堆内存。通过这些方法你也可以获取到堆使用的百分比及堆内存的剩余空间。

freeMemory() 方法返回剩余空间的字节数
totalMemory() 方法总内存的字节数
maxMemory() 返回最大内存的字节数
class Solution {public static void main(String[] args) {Runtime runtime = Runtime.getRuntime();//返回剩余空间的字节数long freeMemory = runtime.freeMemory();//返回最大内存的字节数long maxMemory = runtime.maxMemory();//返回总内存的字节数long totalMemory = runtime.totalMemory();System.out.println("freeMemory = " + freeMemory / (float) 1024 + "MB");System.out.println("maxMemory = " + maxMemory / (float) 1024 + "MB");System.out.println("totalMemory = " + totalMemory / (float) 1024 + "MB");}
}

Java 基础常见面试题(持续更新)相关推荐

  1. Java基础常见面试题(一)

    Java基础常见面试题(一) 1. 为什么说 Java 语言"编译与解释并存"? 我们可以将高级编程语言按照程序的执行方式分为两种: 编译型 :编译型语言会通过编译器将源代码一次性 ...

  2. java基础常见面试题

    125条常见的j ava面试笔试题大汇总 1.抽象: 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前 目标有关的方面.抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用 ...

  3. Java基础常见面试题总结

    基础概念与常识 Java 语言有哪些特点? 简单易学: 面向对象(封装,继承,多态): 平台无关性( Java 虚拟机实现平台无关性): 支持多线程( C++ 语言没有内置的多线程机制,因此必须调用操 ...

  4. Java开发常见面试题详解(LockSupport,AQS,Spring循环依赖,Redis)_3

    Java开发常见面试题详解(LockSupport,AQS,Spring循环依赖,Redis)_3 总览 问题 详解 String.intern()的作用 link LeetCode的Two Sum题 ...

  5. Java开发常见面试题详解(JVM)_2

    Java开发常见面试题详解(JVM)_2 JVM 问题 详解 JVM垃圾回收的时候如何确定垃圾?是否知道什么是GC Roots link 你说你做过JVM调优和参数配置,请问如何盘点查看JVM系统默认 ...

  6. Java工程师常见面试题集锦

    Java工程师常见面试题集锦(一)互联网人必看!(附答案及视频教程,持续更新) 2019年01月02日 14:01:14 CSDNedu 阅读数:653 大牛也怕面试题,尤其是基础题,在面试中如果出现 ...

  7. java陷阱常见面试题_Java常见陷阱

    java陷阱常见面试题 总览 Java是一种极简主义的语言,具有比其他语言故意更少的功能,尽管如此,Java仍然具有产生奇怪效果的边缘情况,甚至具有令人惊讶的效果的一些常见情况也会使您轻而易举. 如果 ...

  8. Java多线程常见面试题及答案汇总1000道(春招+秋招+社招)

    Java多线程面试题以及答案整理[最新版]Java多线程高级面试题大全(2021版),发现网上很多Java多线程面试题都没有答案,所以花了很长时间搜集,本套Java多线程面试题大全,汇总了大量经典的J ...

  9. Java虚拟机常见面试题

    2019独角兽企业重金招聘Python工程师标准>>> 1.java引用的四种状态 强引用.软引用.弱引用.虚引用. 强引用 new一个Object存放在堆内存,然后用一个引用指向它 ...

最新文章

  1. 软件测试开发:常见测试类型概念
  2. CentOS Linux 7 配置 nginx 支持 CGI
  3. BZOJ4590 [Shoi2015]自动刷题机
  4. java extend 和 implements 的区别
  5. Java洛谷P1149 火柴棒等式
  6. JavaScript---事件详解
  7. java实体类转map_十五道经典面试题-JAVA基础篇
  8. cwntos使用不了php,centos系统不能使用yum命令怎么解决
  9. 用matlab辨识系统,Matlab系统辨识工具箱
  10. Java使用POI导入excel教程
  11. 类似endnote_除了EndNote,竟还有如此强大的文献管理软件!重点是正版免费!
  12. html 如何清除历史记录,如何删除网页历史记录?
  13. Tslib的触摸屏5点校准算法原理和实现
  14. PS暂存盘已满怎么办
  15. window+mysql+免安装_mysql 5.7.18 免安装版window配置方法
  16. 常见音频编码格式总结
  17. java 一年有多少周_Java获取一年有多少周、某周的第一天和最后一天.
  18. java poi解析excel_Java 利用POI 解析Excel
  19. mysql 实现yyyyww,在java中有YYYYWW格式吗
  20. 宽依赖和窄依赖_Spark宽依赖和窄依赖深度剖析

热门文章

  1. 【034】翼辉信息获得“核高基”重大专项支持
  2. Apache ShardingSphere 5.0.0-alpha版本发布
  3. java long 随机 正数_java中如何产生随机正负数
  4. 支持向量机(SVM)学习小记
  5. 流畅的Python读书笔记-第八章-对象引用、可变性和垃圾回收
  6. Edgedetect 边沿检测(Verilog)
  7. 室内设计软件除了常用的几款还有7…
  8. 上海体育学院计算机课时,我国体育教育训练学专业硕士研究生课程的设置
  9. 麒麟960鸿蒙,麒麟710和麒麟960对比
  10. 大学生计算机装机配置作业,不愧是计算机专业的大学生,自己写配置来装机,万元电脑真霸气...