Java基础八股

本文最后更新于 2024年8月12日 中午

Java基础八股

概念

说一下Java的特点

  • 平台无关性:Java编译器将源码编译成字节码,该字节码可以在任何安装了Java虚拟机(JVM)的系统上运行。
  • 面向对象:在Java中几乎一切都是对象。面向对象编程特性使得代码易于维护和重用,包括类、对象、继承、多态、抽象和封装。
  • 内存管理:Java有自己的垃圾回收机制,自动管理内存和回收不再使用的对象,开发者不再需要手动管理内存,从而减少内存泄漏和其他内存相关的问题。

Java为什么是跨平台的

Java能支持跨平台,主要依赖于JVM,编译器会将Java文件编译成.class文件,称为字节码文件,Java虚拟机负责将字节码文件翻译成特定平台下的机器码然后运行。也就是说,在不同平台上安装对应的JVM,就可以运行字节码文件,运行我们编写的Java程序,实现了一次编译,到处运行的目的。

JVM、JDK、JRE三者的关系

  • JVM是Java虚拟机,是Java程序运行的环境。它负责将Java字节码(由Java编译器生成)解释或编译成机器码,并执行程序。JVM提供了内存管理、垃圾回收、安全性等功能,使得Java程序具备跨平台性。
  • JDK是Java开发工具包,是开发Java程序所需的工具集合。它包含了JVM、编译器(javac)、调试器(jdb)等开发工具,以及一系列的类库(如Java标准库和开发工具库)。JDK提供了开发、编译、调试和运行Java程序所需的全部工具和环境。
  • JRE是Java运行时环境,是Java程序运行所需的最小环境。它包含了JVM和一组Java类库,用于支持Java程序的执行。JRE不包含开发工具,只提供Java程序运行所需的运行环境。

JVM是什么

JVM是java 虚拟机,主要工作是解释自己的指令集(即字节码)并映射到本地的CPU指令集和OS的系统调用。

JVM屏蔽了与操作系统平台相关的信息,使得Java程序只需要生成在Java虚拟机上运行的目标代码(字节码),就可在多种平台上不加修改的运行,这也是Java能够“一次编译,到处运行的“原因。

编译型和解释型语言的区别

  • 编译型语言:程序执行之前,整个源代码会被编译成机器码或者字节码,生成可执行文件。执行时直接运行编译后的代码,速度快,但跨平台性较差。
  • 解释型语言:在程序执行时,逐行解释执行源代码,不生成独立的可执行文件。通常由解释器动态解释并执行代码,跨平台性好,但执行速度相对较慢。

数据类型

八种基本的数据类型

  • 数值型
    • 整数类型:byte、short、int、long
    • 浮点类型:float、double
  • 字符型:char
  • 布尔型:boolean

long和int可以互转吗

可以的,Java中的 long 和 int 可以相互转换。由于 long 类型的范围比 int 类型大,因此将 int 转换为 long 是安全的,而将 long 转换为 int 可能会导致数据丢失或溢出。

数据类型转换方式你知道哪些

  • **自动类型转换(隐式转换)**:当目标类型的范围大于源类型时,Java会自动将源类型转换为目标类型不需要显式的类型转换。例如,将 int 转换为 long、将 float 转换为 double 等。
  • **强制类型转换(显式转换)**:当目标类型的范围小于源类型时,需要使用强制类型转换将源类型转换为目标类型。这可能导致数据丢失或溢出。例如,将 long转换为 int 、将 double 转换为 int 等。

类型转换会出现什么问题

  • 数据丢失:当将一个范围较大的数据类型转换为一个范围较小的数据类型时,可能会发生数据丢失。
  • 数据溢出:与数据丢失相反,当将一个范围较小的数据类型转换为一个范围较大的数据类型时,可能会发生数据溢出。
  • 精度损失:在进行浮点数类型的转换时,可能会发生精度损失。由于浮点数的表示方式不同,将一个单精度浮点数( float )转换为双精度浮点数( double )时,精度可能会损失。
  • 类型不匹配导致的错误:在进行类型转换时,需要确保源类型和目标类型是兼容的。如果两者不兼容会导致编译错误或运行时错误。、

为什么用bigDecimal不用double

在Java中进行浮点数运算的时候,会出现丢失精度的问题。使用BigDecimal可以确保精确的十进制数值计算,避免了使用double可能出现的舍入误差。

装箱和拆箱是什么

装箱(Boxing)和拆箱(Unboxing)是将基本数据类型和对应的包装类之间进行转换的过程

自动装箱:将基本类型转化为相应的包装类类型。

自动拆箱:将包装类类型转化为相应的基本类型

自动装箱主要发生在两种情况:

  • 赋值时
  • 方法调用时

Java中为什么要有包装类

  • 把基本的数据类型封装成Object对象,对象封装有很多好处,可以把属性也就是数据跟处理这些数据的方法结合在一起,比如Integer就有parseInt(等方法来专门处理int型相关的数据。
  • 在Java中绝大部分方法或类都是用来处理类类型对象的,如ArrayList集合类就只能以类作为他的存储对象,而这时如果想把一个int型的数据存入ist是不可能的,必须把它包装成类也就是Integer才能被List所接受。

面向对象

如何理解面向对象

面向对象是一种编程范式,它将现实世界中的事物抽象为对象,对象具有属性(称为字段或属性)和行为(称为方法)。

面向对象编程的设计思想是以对象为中心,通过对象之间的交互来完成程序的功能,具有灵活性和可扩展性,通过封装和继承可以更好地应对需求变化。

简单说说封装继承多态

Java面向对象的三大特性是什么:封装、继承、多态。

  • 封装:封装是指将对象的属性(数据)和行为(方法)结合在一起,对外隐藏对象的内部细节,仅通过对象提供的接口与外界交互。封装的目的是增强安全性和简化编程,使得对象更加独立。
  • 继承:继承是一种可以使得子类自动共享父类数据结构和方法的机制。它是代码复用的重要手段,通过继承可以建立类与类之间的层次关系,使得结构更加清晰。
  • 多态:多态是指允许不同类的对象对同一消息作出响应,即同一个接口,使用不同的实例而执行不同操作。多态性可以分为编译时多态(重载)和运行时多态(重写)。它使得程序具有良好的灵活性和扩展性。

多态解决了什么问题

多态是指子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。多态可以提高代码的扩展性和复用性,是很多设计模式、设计原则、编程技巧的代码实现基础。

面向对象的设计原则你知道哪些

面向对象编程的六大原则

重载和重写有什么区别

  • 重载是指在同一个类中,可以有多个同名的方法,它们具有不同的参数列表,编译器根据调用时的参数类型来决定调用哪个方法。
  • 重写是指子类可以重新定义父类中的方法,方法名、参数列表和返回类型必须与父类中的方法一致。

重载是指同一个类中定义多个同名的方法,而重写是指子类重新定义父类中的方法。

抽象类和普通类有什么区别

  • 实例化:普通类可以直接实例化对象,而抽象类不能被实例化,只能被继承。
  • 方法实现:普通类中的方法可以有具体的实现,而抽象类中的方法可以有实现也可以没有实现。
  • 继承:一个类可以继承一个普通类,而且可以继承多个接口;而一个类只能继承一个抽象类,但可以同时实现多个接口。
  • 实现限制:普通类可以被其他类继承和使用,而抽象类一般用于作为基类,被其他类继承和扩展使用。

抽象类和接口有什么区别

抽象类用于描述类的共同特性和行为,可以有成员变量、构造方法和具体方法。适用于有明显继承关系的场景。

接口用于定义行为规范,可以多实现,只能有常量和抽象方法(Java8以后可以有默认方法和静态方法)。适用于定义类的能力或功能。

  • 实现方式:实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。
  • 方法方式:接口只有定义,不能有方法的实现,而抽象类可以有定义与实现,方法可在抽象类中实现。
  • 访问修饰符:接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。
  • 变量:抽象类中可以包含实例变量和静态变量,而接口中只能包含常量

抽象类能加final修饰吗

不能。Java中的抽象类是用来被继承的,而final修饰符用于禁止类被继承或方法被重写,因此,抽象类和final修饰符是互斥的,不能同时使用。

接口中可以定义哪些方法

  • 抽象方法:抽象方法是接口的核心部分,所有实现接口的类都必须实现这些方法。抽象方法默认是 public和abstract,这些修饰符可以省略。
  • 默认方法:在 Java8 中引入的,允许接口提供具体实现。实现类可以选择重写默认方法。
  • 静态方法:在 Java8 中引入的,它们属于接口本身,可以通过接口名直接调用,而不需要实现类的对象。
  • 私有方法:在Java9 中引入的,用于在接口中为默认方法或其他私有方法提供辅助功能。这些方法不能被实现类访问,只能在接口内部使用。

抽象类可以被实例化吗

在Java中抽象类本身不能被实例化。
这意味着不能使用 new 关键字直接创建一个抽象类的对象。抽象类的存在主要是为了被继承,它通常包含一个或多个抽象方法(由 abstract 关键字修饰且无方法体的方法),这些方法需要在子类中被实现。

接口可以包含构造函数吗

在接口中,不可以有构造方法,在接口里写入构造方法时,编译器提示:Interfaces cannot haveconstructors,因为接口不会有自己的实例的,所以不需要有构造函数。

深拷贝和浅拷贝

深拷贝和浅拷贝的区别

  • 拷贝对象和原始对象的引用类型引用同一个对象。浅拷贝只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化,这就是浅拷贝。
  • 拷贝对象和原始对象的引用类型引用不同对象。深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变,这就是深拷贝

实现深拷贝的三钟方法是什么

  • 实现 Cloneable 接口并重写 clone() 方法

    对象及其所有引用类型字段都实现 Cloneable 接口,并且重写 clone0 方法。在 clone0 方法中,通过递归克隆引用类型字段来实现深拷贝。

    实现Cloneable接口并重写clone()方法

  • 使用序列化和反序列化

    通过将对象序列化为字节流,再从字节流反序列化为对象来实现深拷贝。要求对象及其所有引用类型字段都实现 Serializable 接囗。

    使用序列化和反序列化

  • 手动递归复制

    针对特定对象结构,手动递归复制对象及其引用类型字段。适用于对象结构复杂度不高的情况

    手动递归复制

泛型

什么是泛型

泛型是 Java 编程语言中的一个重要特性,它允许类、接口和方法在定义时使用一个或多个类型参数,这些类型参数在使用时可以被指定为具体的类型。

为什么需要泛型

  • 适用于多种数据类型执行相同的代码
  • 泛型中的类型在使用时指定,不需要强制类型转换

对象

Java创建对象的方式有哪些

  • 使用new关键字:通过new关键字直接调用类的构造方法来创建对象。
  • 使用Class类的newInstance()方法:通过反射机制,可以使用Class类的newInstance()方法创建对象。
  • 使用Constructor类的newInstance()方法:同样是通过反射机制,可以使用Constructor类的newInstance()方法创建对象。
  • 使用clone()方法:如果类实现了Cloneable接口,可以使用clone()方法复制对象。
  • 使用反序列化:通过将对象序列化到文件或流中,然后再进行反序列化来创建对象。

new出的对象什么时候回收

通过关键字new创建的对象,由Java的垃圾回收器(Garbage Collector)负责回收。垃圾回收器的工作是在程序运行过程中自动进行的,它会周期性地检测不再被引用的对象,并将其回收释放内存。

  • 引用计数法:某个对象的引用计数为0时,表示该对象不再被引用,可以被回收。
  • 可达性分析算法:从根对象(如方法区中的类静态属性、方法中的局部变量等)出发,通过对象之间的引用链进行遍历,如果存在一条引用链到达某个对象,则说明该对象是可达的,反之不可达,不可达的对象将被回收。
  • **终结器(Finalizer)**:如果对象重写了 finalize()方法,垃圾回收器会在回收该对象之前调用finalize()方法,对象可以在 finalize()方法中进行一些清理操作。

反射

什么是反射

Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java 语言的反射机制。

反射有哪些特性

  • 运行时类信息访问:反射机制允许程序在运行时获取类的完整结构信息,包括类名、包名、父类、实现的接口、构造函数、方法和字段等。
  • 动态对象创建:可以使用反射API动态地创建对象实例,即使在编译时不知道具体的类名。这是通过Class类的newInstance0)方法或Constructor对象的newInstance()方法实现的。
  • 动态方法调用:可以在运行时动态地调用对象的方法,包括私有方法。这通过Method类的invoke()方法实现,允许你传入对象实例和参数值来执行方法。
  • 访问和修改字段值:反射还允许程序在运行时访问和修改对象的字段值,即使是私有的。这是通过Field类的get()和set()方法完成的。

注解

讲一讲Java的注解原理

Java注解的作用域有哪些

  • 类级别作用域:用于描述类的注解,通常放置在类定义的上面,可以用来指定类的一些属性,如类的访问级别、继承关系、注释等。
  • 方法级别作用域:用于描述方法的注解,通常放置在方法定义的上面,可以用来指定方法的一些属性如方法的访问级别、返回值类型、异常类型、注释等。
  • 字段级别作用域:用于描述字段的注解,通常放置在字段定义的上面,可以用来指定字段的一些属性如字段的访问级别、默认值、注释等。

异常

介绍一下Java的异常

Java的异常体系主要基于两大类:Throwable类及其子类。Throwable有两个重要的子类:Error和Exception,它们分别代表了不同类型的异常情况。

  • Error(错误):表示运行时环境的错误。错误是程序无法处理的严重问题,如系统崩溃、虚拟机错误、动态链接失败等。通常,程序不应该尝试捕获这类错误。

  • Exception(异常):标识程序本身可以处理的异常条件。

    • 非运行时异常:这类异常在编译时期就必须被捕获或者声明抛出看,如文件不存在、类未找到等。
    • 运行时异常:一般由程序错误导致,如空指针访问、数组越界等。

Java异常处理有哪些

  • try-catch语句块:用于捕获并处理可能抛出的异常。try块中包含可能抛出异常的代码,catch块用于捕获并处理特定类型的异常。可以有多个catch块来处理不同类型的异常。
  • throw语句:用于手动抛出异常。可以根据需要在代码中使用throw语句主动抛出特定类型的异常。
  • throws关键字:用于在方法声明中声明可能抛出的异常类型。如果一个方法可能抛出异常,但不想在方法内部进行处理,可以使用throws关键字将异常传递给调用者来处理。
  • finally块:用于定义无论是否发生异常都会执行的代码块。通常用于释放资源,确保资源的正确关闭。

try-catch中的语句运行情况是怎么样的

try块中的代码将按顺序执行,如果抛出异常,将在catch块中进行匹配和处理,然后程序将继续执行catch块之后的代码。如果没有匹配的catch块,异常将被传递给上一层调用的方法。

Object

==与equals有什么区别

对于字符串变量来说,使用”==“和”equals”比较字符串时,其比较方法不同。,”==“比较两个变量本身的值,即两个对象在内存中的首地址,“equals”比较字符串包含内容是否相同。
对于非字符串变量来说,如果没有对equals()进行重写的话,”==”和”equals”方法的作用是相同的,都是用来比较对象在堆内存中的首地址,即用来比较两个引用变量是否指向同一个对象。

StringBuffer和StringBuild的区别是什么

  • StringBuffer 就是为了解决大量拼接字符串时产生很多中间对象问题而提供的一个类。它提供了append 和 add 方法,可以将字符串添加到已有序列的末尾或指定位置,它的本质是一个线程安全的可修改的字符序列
  • StringBuilder 是 JDK1.5 发布的,它和 StringBuffer 本质上没什么区别,就是去掉了保证线程安全的那部分,减少了开销。

String、StringBuffer、StringBuild三者的比较

序列化

怎么把一个对象从一个jvm转移到另一个jvm

  • 使用序列化和反序列化:将对象序列化为字节流,并将其发送到另一个JVM,然后在另一个 JVM 中反序列化字节流恢复对象。这可以通过Java 的 ObjectOutputStream 和 ObjectlnputStream 来实现。
  • 使用消息传递机制:利用消息传递机制,比如使用消息队列(如 RabbitMQ、Kafka)或者通过网络套接字进行通信,将对象从一个M 发送到另一个,这需要自定义协议来序列化对象并在另一个JVM中反序列化。
  • **使用远程方法调用(RPC)**:可以使用远程方法调用框架,如 gRPC,来实现对象在不同JVM 之间的传输。远程方法调用可以让你在分布式系统中调用远程 JVM 上的对象的方法。
  • 使用共享数据库或缓存:将对象存储在共享数据库(如 MySQL、PostgreSQL)或共享缓存(如Redis)中,让不同的 JVM可以访问这些共享数据。这种方法适用于需要共享数据但不需要直接传输对象的场景。

设计模式

volatile和sychronized如何实现单例模式

在这种实现中,通过将 instance 声明为 volatile,确保了多线程环境下对变量的可见性。在 getInstance() 方法中,首先检查 instance 是否已经被实例化,如果没有,则进入同步块并再次检查 instance 是否为 null,然后再实例化对象。这种方式实现了懒加载并保证了线程安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton {
private static volatile Singleton instance;

private Singleton() {}

public static Singleton getInstance() {
if (instance == null) {
//同步代码块
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

代理模式和适配器模式有什么区别

  • 目的不同:代理模式主要关注控制对对象的访问,而适配器模式则用于接口转换,使不兼容的类能够-起工作。
  • 结构不同:代理模式一般包含抽象主题、真实主题和代理三个角色,适配器模式包含目标接口、适配器和被适配者三个角色。
  • 应用场景不同:代理模式常用于添加额外功能或控制对对象的访问,适配器模式常用于让不兼容的接口协同工作。

I/O

BIO、NIO、AIO区别是什么

  • BIO(blocking IO):就是传统的 java.io 包,它是基于流模型实现的,交互的方式是==同步、阻塞方式==,也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。
  • NIO(non-blocking IO) :Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、==同步非阻塞 IO 程序==,同时提供了更接近操作系统底层高性能的数据操作方式。
  • AIO(Asynchronous IO) :是 Java 1.7 之后引入的包,是 NIO 的升级版本,提供了==异步非堵塞的 IO 操作方式==,所以人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

NIO是如何实现的

NIO是一种==同步非阻塞==的IO模型,所以也可以叫NON-BLOCKINGIO。同步是指线程不断轮询IO事件是否就绪,非阻塞是指线程在等待IO的时候,可以同时做其他任务。

同步的核心就是Selector(I/O多路复用),Selector代替了线程本身轮询IO事件,避免了阻塞同时减少了不必要的线程消耗;

非阻塞的核心就是通道和缓冲区,当IO事件就绪时,可以通过写到缓冲区,保证IO的成功,而无需线程阻塞式地等待。

NIO由一个专门的线程处理所有IO事件,并负责分发。事件驱动机制,事件到来的时候触发操作,不需要阻塞的监视事件。

NIO主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector。传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。

Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。


Java基础八股
https://love-enough.github.io/2024/08/03/Java基础八股/
作者
GuoZihan
发布于
2024年8月3日
更新于
2024年8月12日
许可协议