Java基础面试题

(总结于小林coding,缩减了部分内容,仅供学习参考,正在更新中)

概念相关

Java的特点

  1. 平台无关性:Java编译器将源码编译为字节码,字节码可以在任何装有Java虚拟机的平台上运行,不需要重新编译。
  2. 面向对象:Java是一门严格面向对象的语言,几乎一切都是对象。面向对象编程(OOP)特性使得代码更易于维护和重用。
  3. 内存管理:Java有自己的垃圾回收机制,自动管理内存和不再使用的对象。开发者不用手动管理内存,减少了内存泄露和其他内存相关的问题。

Java为什么能跨平台

这主要依赖于Java虚拟机(JVM),开发者编写的Java源代码经编译后得到.class文件,即字节码文件。Java虚拟机可以将字节码翻译为特定平台下的机器码。

所以运行Java程序必须要有JVM支持,即使将Java程序打包为可执行文件(例如.exe),它也需要JVM的支持。

跨平台的是Java程序,而JVM是用C/C++编写的,不可跨平台,不同平台需要安装适合的JVM。

JVM,JRE,JDK的关系

  • JVM:略
  • JDK是Java开发工具包,包含了JVM,编译器,调试器等开发工具,以及一系列的类库(如Java标准库和开发工具库)。即JDK提供了开发、编译、调试和运行Java程序的所需的全部工具和环境。
  • JRE是Java运行时环境,是Java程序运行的最小环境,包含了JVM和Java标准库。JRE不包含开发工具,只包含运行Java程序所需的最小环境。

为什么Java解释和编译都有

  • 解释性:Java源代码被编译为字节码,JIT会把编译过的机器码保存下来下次使用。
  • 编译性:JVM中有方法调用计数器,当调用次数达到一定阈值时,JVM会使用JIT编译字节码为机器码,下次再调用时直接执行机器码,提高性能。否则就是用解释器进行解释执行。

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

  • 编译型语言:编译型语言在编译时将源代码编译为目标代码(如机器码),然后再运行目标代码。编译型语言在编译时进行优化,提高性能,但跨平台性差。
  • 解释型语言:解释型语言在运行时解释执行源代码,无需编译为目标代码。解释型语言在运行时进行优化,跨平台性好,但性能较低。

数据类型

八种基本的数据类型

Java支持的数据类分为两类:基本数据类型和引用数据类型

基本数据类型有8中,可分为3类:

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

引用数据类型有:类、接口、数组…

  • 注意:
    1. 1字节(byte,boolean),2字节(short,char),4字节(int,float),8字节(long,double)
    2. 浮点数的默认值为double,若需要float,需在末尾加上f或F。
    3. 整型默认为int,若需要byte,short,long,需在末尾加上b、s、l。
    4. 八种数据类型的包装类:除了char->Character、int->Integer,其他都是基本数据类型首字母大写。
    5. char类型是无符号的,不能为负,所以0开头

int和long之间的转换

  • int转long:long数据类型的范围比int大,因此long转int是安全的,可通过直接赋值和强制类型转换实现。
  • long转int:long转为int可能会出现数据丢失或溢出的问题,若转换的数据超出int类型的范围,转换的结果是阶段后低位部分。

数据类型转换方式

  • 自动类型转换(隐式转换):目标类型范围大于源类型时,Java会自动将源类型转换为目标类型
  • 强制类型转换(显式转换):目标类型范围小于源类型时,需要使用强制类型转换,这可能导致数据丢失或溢出
  • 字符串转换:Java提供了将字符串转换为其他数据类型的方法,如Integer.parseInt()、Double.parseDouble()等
  • 数值之间转换:Java提供了将数值转换为其他数据类型的方法,如Integer.valueOf()、Double.valueOf()等

类型互换可能会出现那些问题

  • 数据丢失:如果目标类型范围小于源类型范围,可能会出现数据丢失的情况
  • 数据溢出:如果目标类型范围大于源类型范围,可能会出现数据溢出的情况,转换的结果会额外填充高位部分,但源石数据爆粗不变
  • 精度损失:在进行浮点类型的转换时,可能发生精度损失
  • 类型不匹配:在进行类型转换时,需要确保源类型和目标类型是兼容的

为什么使用BigDecimal不用Double

double会出现精度丢失的问题,double执行的是二进制浮点运算,二进制在有些情况不能准确表示小数,就像十进制不能准确表示1/3,也就是说二进制表示小数只能表示(1/2)^n的任意组合

这就会导致可能出现0.6无法购买0.5和0.1的商品

而BigDecimal是使用字符串表示的,可以进行精确计算

装箱和拆箱是什么

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

赋值时,Java1.5以后,所有的装箱和拆箱都是有编译器完成,进行自动装箱和拆箱

1
2
Integer i = 10;
int j = i;//自动拆箱

方法调用时,可以直接传入原始数据或对象,编译器同样会进行转换

自动装箱的弊端

在一个循环中进行自动装箱,会产生多于的对象影响程序的性能

1
2
Integer sum = 0;
for(int i = 0; i < 10000; i++){ sum+=i; }

sum + i 中的 + 不适用于Integer对象,程序运行时会先将sum自动拆箱,再进行数值相加的操作,然后再进行自动装箱的操作转换为Integer对象,类似于

1
int result = sum.intValue() + i;Integer sum = new Integer(result);

故会在循环中产生大量无用的Integer对象,降低了程序的性能、加重了垃圾回收的工作量

Java为什么需要Integer

将int类型包装为Integer对象好处很多,可以把属性(也就是数据)和处理数据的方法结合在一起,例如parseInt()方法

还有个重要原因就是Java中绝大部分方法或类都是用来处理类类型对象的,例如ArrayList集合类只能以类作为它的存储对象

泛型中的应用:在Java中泛型只能使用引用类型

转换中的应用:Java中基本数据类型和引用类型不能直接进行转换,必须通过包装类实现

集合中的应用:在Java中集合类中的泛型只能是引用类型,不能是基本数据类型

int和Integer的区别

  • 基本数据类型和引用类型:int是基本数据类型,不需要实例化,不用额外分配内存;Integer是引用类型,对象的引用和对象是分开存储的

  • 自动装箱拆箱:略

  • 空指针异常:Integer对象可以表示null,int不能表示null。对被赋予null值的Integer对象进行操作会出现空指针异常

Integer缓存

Java的Integer类内部实现了一个静态缓存池,用于存储特定范围内的整数值对应的Integer对象

默认情况下,这个范围为-128至127,即在-128至127之间的整数值,都会被缓存在静态缓存池中,避免每次创建新的Integer对象,提高性能。可通过Integer.valueOf()方法来获取缓存池中的Integer对象,如果缓存池中不存在对应的Integer对象,则创建一个新的对象并放入缓存池中。

面向对象

如何理解面向对象,简单说说封装继承多态

面向对象是一种编程范式,它将现实中的事物抽象为对象,对象具有属性和方法。面向对象的设计思想是以对象为中心,通过对象之间的交互来完成程序的功能,具有灵活性和可扩展性,通过封装和继承可以更好地应对需求变化

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

  • 封装:封装是指将对象的属性和方法结合在一起,对外隐藏内部的细节,仅通过对象提供的接口与外界交互。增强了安全性并简化编程,使对象更加独立
  • 继承:继承是指子类可以继承父类的属性和方法,从而实现代码的重用。子类可以扩展父类的功能,也可以覆盖父类的方法,从而实现代码的复用。
  • 多态:多态是指一个对象在不同的情况下具有不同的行为,即对象的行为可以根据对象的状态或者类型而发生变化。多态性可分为编译时多态(重载)和运行时多态(重写)。多态使得对象的行为更加灵活和可扩展,从而提高程序的可维护性。

多态表现于:

  • 方法重载:
    • 方法重载是指在同一个类中定义多个同名方法,但参数列表不同(参数类型、个数、顺序),编译时根据参数列表选择对应的方法进行调用。
    • 示例:对于一个 add 方法,可以定义为 add(int a, int b) 和 add(double a, double b)
  • 方法重写:
    • 方法重写是指子类重写父类的方法,使得子类具有父类方法的功能,但可以扩展或修改父类方法的实现。在运行时,JVM会根据对象的实际类型来决定调用哪个方法。
    • 示例:对于一个 Animal 类,可以定义一个 eat 方法,子类 Cat 和 Dog 都可以重写这个方法,使得 Cat 和 Dog 的 eat 方法具有不同的实现。
  • 接口与实现:
    • 多个类可以实现同一个接口,并且用接口类型的引用来调用这些类的方法。这使得程序在面对不同的实现时,保持。一贯的调用方式
    • 示例:定义一个接口 Animal,包含一个 eat 方法,然后定义两个实现类 Cat 和 Dog,实现 Animal 接口,使得 Cat 和 Dog 具有 eat 方法。