参考:

JavaGuide[1]、《Java核心计数及面试指南》[2]

静态变量和实例变量的区别

实例字段在每个实例中都有自己的一个独立“空间”,但是静态字段只有一个共享“空间”,所有实例都会共享该字段。

==> 无论修改哪个实例的静态字段,所有实例的静态字段都被修改了,原因是静态字段并不属于实例[3]

静态方法和实例方法区别

  1. 调用方式

    调用静态方法可以无需创建对象

    • 静态方法: 类名.方法名/对象.方法名(不建议)
    • 实例方法: 对象.方法名
  2. 访问类成员是否存在限制

    静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员(即实例成员变量和实例方法)

    实例方法不存在这个限制。

静态方法为什么不能调用非静态成员

  1. 静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而

    非静态成员属于实例对象, 只有在对象实例化之后才存在, 需要通过类的实例对象去访问。

  2. 在类的非静态成员不存在的时候静态成员就已经存在了,此时调用在 内存中还不存在 的非静态成员,属于非法操作

重载(overload)和覆盖/重写(override)的差别

重载是多态的一种体现,表现形式是方法同名但参数不同,而覆盖是子类方法覆盖掉父类方法

区别点 重载方法 重写方法
发生范围 同一个类 子类
参数列表 必须修改 一定不能修改
返回类型 可修改 子类方法返回值类型应比父类方法返回值类型 更小或相等
异常 可修改 子类方法声明抛出的异常类应比父类方法声明抛出的异常类 更小或相等;
访问修饰符 可修改 一定不能做更严格的限制(可以降低限制)
发生阶段 编译期 运行期

请说出作用域public,private,protected,以及不写时的区别。

不写就是default(默认):同一包中的类可以访问,声明时没有加修饰符,认为是friendly。

image-20220310144341946

JVM、JDK和JRE

  • JVM: Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言 “一次编译,随处可以运行” 的关键所在。
  • JDK: Java Development Kit 缩写,它是功能齐全的 Java SDK。它拥有 JRE 所拥有的一切,还有编译器(javac)和工具(如 javadoc 和 jdb)。它能够创建和编译程序。
  • JRE: Java 运行时环境。它是 运行 已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。但是, 它不能用于创建新程序。**

什么是字节码?采用字节码的好处是什么?

在 Java 中,JVM 可以理解的代码就叫做字节码(即扩展名为 .class 的文件),它不面向任何特定的处理器,只面向虚拟机( 跨平台、解释性、可移植 )。

Java程序转变为机器代码的过程

  • Java 是编译与解释共存的语言
  • 编译为 .class->机器码 这一步。在这一步 JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。
  • 引进了 JIT(just-in-time compilation) 编译器,而 JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。

为什么说 Java 语言“编译与解释并存”?

Java 语言既具有编译型语言的特征,也具有解释型语言的特征。

  • 编译型 :一次性编译完毕。执行速度较快,开发效率较低。
  • 解释型 :将代码逐句解释(interpret)为机器代码后再执行。执行速度较慢,开发效率较高。(python\js\php等)

因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(.class 文件),这种字节码必须由 Java 解释器来解释执行

Java和C++区别

Java 和 C++ 都是面向对象的语言,都支持封装、继承和多态,但是,它们还是有挺多不相同的地方:

  • Java 不提供指针来直接访问内存,程序内存更加安全
  • Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。
  • Java 有自动内存管理垃圾回收机制(GC),不需要程序员手动释放无用内存。
  • C ++同时支持方法重载和操作符重载,但是 Java 只支持方法重载(操作符重载增加了复杂性,这与 Java 最初的设计思想不符)。

final

  • final修饰的方法可以阻止被覆写;

    final修饰的class可以阻止被继承;

    final修饰的field必须在创建对象时初始化,随后不可修改。

抽象类和接口

抽象类和接口的对比如下:

abstract class interface
理解 对概念逻辑的抽象 对功能的抽象
继承 只能extends一个class 可以implements多个interface
字段 可以定义实例字段 不能定义实例字段
抽象方法 可以定义抽象方法 可以定义抽象方法
非抽象方法 可以定义非抽象方法 可以定义default方法

共同点

  • 都不能被实例化。
  • 都可以包含抽象方法。
  • 都可以有默认实现的方法(Java 8 可以用 default 关键在接口中定义默认方法,抽象类的实现方法?)。

区别

  • 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系(比如说我们抽象了一个发送短信的抽象类,)。
  • 一个类只能继承一个类,但是可以实现多个接口。
  • 接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋

hashCode() 与 equals()

当你把对象加入 HashSet 时,

  • HashSet 会先计算对象的 hashCode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashCode 值作比较,如果没有相符的 hashCodeHashSet 会假设对象没有重复出现。
  • 但是如果发现有相同 hashCode 值的对象,这时会调用 equals() 方法来检查 hashCode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
  1. hashCode()方法要严格遵循以下规范:[4]
  • 如果两个对象相等,则两个对象的hashCode()必须相等;
  • 如果两个对象不相等,则两个对象的hashCode()尽量不要相等。

换而言之:

  • 如果两个对象的hashCode 值相等,那这两个对象不一定相等(哈希碰撞)。
  • 如果两个对象的hashCode 值相等并且equals()方法也返回 true,我们才认为这两个对象相等。
  • 如果两个对象的hashCode 值不相等,我们就可以直接认为这两个对象不相等。
  1. 编写equals()hashCode()遵循的原则是
  • equals()用到的用于比较的每一个字段,都必须在hashCode()中用于计算;
  • equals()中没有使用到的字段,绝不可放在hashCode()中计算。

可变长参数

方法重载时是会优先匹配固定参数的方法,因为固定参数的方法匹配度更高。

包装类型的常量池技术

Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在 [0,127] 范围的缓存数据,Boolean 直接返回 True or False

创建一个对象用什么运算符?对象实体与对象引用有何不同?

new 运算符,new 创建对象实例。

  • 对象实例在堆内存中
  • 对象引用指向对象实例,对象引用存放在栈内存中。

一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)。

面向对象的三大特征

  • 封装: 例如java bean 不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。
  • 继承: 通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。
  • 多态:
  • 多态,顾名思义,表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。
    • 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
    • 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
    • 多态不能调用“只在子类存在但在父类不存在”的方法;
    • 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。

深拷贝和浅拷贝区别了解吗?什么是引用拷贝?

关于深拷贝和浅拷贝区别,我这里先给结论:

  • 浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
  • 深拷贝 :深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
  • 引用拷贝 :简单来说,引用拷贝就是两个不同的引用指向同一个对象。

img

Java 泛型了解么?什么是类型擦除?介绍一下常用的通配符?

Java 泛型(generics) 是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

Java 的泛型是伪泛型,这是因为 Java 在运行期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除 。

  • 泛型类
  • 泛型接口
  • 泛型方法

反射

通过Class实例获取class信息的方法称为反射(Reflection)。反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。

反射机制优缺点

  • 优点 : 可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利
  • 缺点 :让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的

反射的应用场景

注解

Exception 和 Error 有什么区别?

  • Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
  • ErrorError 属于程序无法处理的错误 ,我们没办法通过 catch 来进行捕获 。例如Java 虚拟机运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
                     ┌───────────┐
│ Object │
└───────────┘


┌───────────┐
│ Throwable │
└───────────┘

┌─────────┴─────────┐
│ │
┌───────────┐ ┌───────────┐
│ Error │ │ Exception │
└───────────┘ └───────────┘
▲ ▲
┌───────┘ ┌────┴──────────┐
│ │ │
┌─────────────────┐ ┌─────────────────┐┌───────────┐
│OutOfMemoryError │... │RuntimeException ││IOException│...
└─────────────────┘ └─────────────────┘└───────────┘

┌───────────┴─────────────┐
│ │
┌─────────────────────┐ ┌─────────────────────────┐
│NullPointerException │ │IllegalArgumentException │...
└─────────────────────┘ └─────────────────────────┘

Checked Exception 和 Unchecked Exception 有什么区别?

  • Checked Exception 即受检查异常,Java 代码在编译过程中,如果受检查异常没有被 catch/throw 处理的话,就没办法通过编译 。除了RuntimeException及其子类以外,其他的Exception类及其子类都属于受检查异常 。

  • Unchecked Exception不受检查异常 ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。

RuntimeException 及其子类都统称为非受检查异常

什么是序列化?什么是反序列化?

  • 序列化: 将数据结构或对象转换成二进制字节流的过程
  • 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程

对于 Java 这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(Class)

序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。

对于不想进行序列化的变量,使用 transient 关键字修饰

Java 中 IO 流分为几种?

  • 按照流的流向分,可以分为输入流和输出流;
  • 按照操作单元划分,可以划分为字节流(音频文件、图片等媒体文件)和字符流(字符,涉及到编码);
  • 按照流的角色划分为节点流和处理流。

Java只有值传递

为什么 Java 中只有值传递? | JavaGuide

BigDecimal解决浮点数运算精度丢失问题

为了避免精度丢失,可以使用 BigDecimal 来进行浮点数的运算

BigDecimal解决浮点数运算精度丢失问题 | JavaGuide


  1. 主页 | JavaGuide ↩︎

  2. Java核心技术及面试指南 - 标签 - hsm_computer - 博客园 (cnblogs.com) ↩︎

  3. 9. 静态字段和静态方法 (gitee.io) ↩︎

  4. 5. 编写equals和hashCode (gitee.io) ↩︎