Java面试题汇总(持续更新…..)

作者 : admin 本文共5430个字,预计阅读时间需要14分钟 发布时间: 2024-06-14 共1人阅读

Java面试题

1. JVM & JDK & JRE

Java虚拟机(JVM)是运行Java字节码的虚拟机,JVM有针对不同系统的特定实现,目的是使用相同的字节码,他们都会给出相同的结果。字节码和不同系统的JVM实现是Java语言“一次编译、随处可以运行”的关键。

Java面试题汇总(持续更新…..)插图

JDK和JRE

JDK是功能齐全的Java SDK,提供给开发者使用,能够创建和编译Java程序的开发套件。包含了JRE,同时还包含了编译java源码的编译器javac、调试器jdb、等一系列工具。

JRE是Java运行时环境,他是运行已编译Java程序所需的所有内容的集合,主要包括Java虚拟机(JVM)、Java基础类库(Class Library)。

JRE是Java运行时环境,仅包含Java应用程序运行时环境和必要的类库。JDK包含JRE

Java面试题汇总(持续更新…..)插图(1)

2. 什么是字节码?采用字节码有什么好处?

字节码就是(.class文件),他不面向任何特定的处理器,只面向虚拟机。Java语言通过字节码的形式,在一定程度上解决了传统解释型语言执行效率低的问题。同时保留了解释型语言可移植特性。所以,Java程序运行时相对高效(但是和C、C++、Go相比仍有差距)。而且,由于字节码并不针对一种特定的机器,因此Java程序无需重新编译便可在多种 不同操作系统上运行。

3. JIT(just-in-time compliation)编译器

Java程序从源代码到运行的过程
Java面试题汇总(持续更新…..)插图(2)
在 .class->机器码这一步。JVM类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会比较慢,而且,有些方法和代码块是经常需要被调用的(即热点代码),所以引进了JIT编译器,JIT属于运行时编译,当JIT编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。 机器码的运行效率肯定是高于Java编译器的,因此Java是编译与解释共存的语言
Java面试题汇总(持续更新…..)插图(3)

HotSpot采用了惰性评估的做饭,根据二八定律,消耗大部分系统资源的只有一小部分代码(热点代码),而这正是JIT所需要编译的部分 。

4. JDK、JRE、JVM、JIT的关系

Java面试题汇总(持续更新…..)插图(4)

5. Java位运算符

Java提供了一系列位运算符,这些运算符主要用于操作整数类型(int、long、short、byte),使用位运算符转换成的指令码运行起来更加高效。

  • << : 左移运算符,高位丢弃,低位补零。x<<1 相当于x*2(不溢出的情况下)。
int value = 0b0001;  // 1
int result = value << 2;  // 左移2位,结果为 0b0100,即4
  • .>>:带符号右移,向右移动若干位,高位补符号位,低位丢弃。正数高位补0,负数高位补1.x>>1 相当于 x/2
int value = 0b0100;  // 4
int result = value >> 2;  // 结果为 0b0001,即1
  • .>>>:无符号右移,将位向右移动指定位数,左边空出的位用0填充,无论正负。
int value = -1;  // 所有位为1
int result = value >>> 1;  // 结果为将最高位的1转换为0,其余位保持1
  • & :按位与:对两个位进行运算,只有在两个相应位都是1时,结果位1.
int flags = 0b11010;  // 二进制表示 0b表示二进制数
int mask = 0b11000;   // 要保留的位
int result = flags & mask;  // 结果为 0b11000
  • ^ : 按位异或 :对两个位进行运算,如果两个相应位一个位1一个为0,则结果为1.
int flags = 0b11010;
int toggle = 0b10101;
int result = flags ^ toggle;  // 结果为 0b01111
  • |:按位或:对两个位进行运算,如果两个位至少有一个为1,则结果位1.
int flags = 0b11010;
int mask = 0b00101;
int result = flags | mask;  // 结果为 0b11111

  • ~:按位取反:对位进行单操作,将1变成0,将0变成1.
int flags = 0b11010;
int result = ~flags;  // 结果为二进制的反码 0b00101

6. 如果移位的位数超过数值所占有的位数会怎么样

当 int 类型 左移/右移 大于等于32位时,会先求余(%)后在进行相应操作。也就是说 左移/右移32位相当于不进行移位操作(32%32=0), 左移/右移 42位相当于 左移/右移10位。当long类型进行 左移/右移操作时,由于long对应的二进制是64位,因此求余操作的基数也变成了64.

7. 基本类型和包装类型的区别

Java面试题汇总(持续更新…..)插图(5)

  • 用途:除了定义一些常量和局部变量之外,我们在其他地方比如方法参数、对象属性中很少会使用基本类型来定义变量。并且,包装类型可用于泛型,而基本类型不可以

  • 存储方式:基本数据类型的局部变量存放在Java虚拟机栈中的局部变量表,基本数据类型的成员变量(未被static修饰)存放在Java虚拟机的堆中。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中。

  • 占用空间: 相比于包装类型,基本数据类型占用的空间往往非常小。

  • 默认值:成员变量包装类型不赋值就是null,而基本类型有默认值且不是null。

  • 比较方式:基本数据类型采用 == 比较的是值,对于包装类型 == 比较的是内存地址。所有整型包装类对象之间值的比较,全部使用equals()方法。

!注意基本数据类型存放在栈中是一个常见的误区!基本数据类型的存储位置取决于他们的作用域和声明方式,如果他们是局部变量,那么他们会存放在栈中,如果他们是成员变量,那么他们会存放在堆中

public class Test {
    // 成员变量,存放在堆中
    int a = 10;
    // 被 static 修饰,也存放在堆中,但属于类,不属于对象
    // JDK1.7 静态变量从永久代移动了 Java 堆中
    static int b = 20;
    public void method() {
        // 局部变量,存放在栈中
        int c = 30;
        static int d = 40; // 编译错误,不能在方法中使用 static 修饰局部变量
    }
}

8. 包装类型的缓存机制

Java基本数据类型的包装类型大部分都利用到了缓存机制来提升性能

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

Integer 缓存源码

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static {
        // high value may be configured by property
        int h = 127;
    }
}

Character缓存源码

public static Character valueOf(char c) {
    if (c <= 127) { // must cache
      return CharacterCache.cache[(int)c];
    }
    return new Character(c);
}

private static class CharacterCache {
    private CharacterCache(){}
    static final Character cache[] = new Character[127 + 1];
    static {
        for (int i = 0; i < cache.length; i++)
            cache[i] = new Character((char)i);
    }

}

Boolean 缓存源码

public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

例:

Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1==i2);  //输出结果为false

Integer i1 = 40;这一行代码会发生装箱,等价于Integer.valueOf(40), 因此 i1直接使用的是缓存中的对象,而Integer i2 = new Integer(40);会直接创建对象,因此两者地址比较为false。

强制:所有整型包装类对象之间值的比较,全部使用equals方法比较。
说明:对于Integer var = ? 在-128至127之间的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以使用 == 进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,因此,推荐所有的整形包装类对象都使用equals方法进行判断。

9. 自动装箱和拆箱的原理

什么是自动装箱拆箱

  • 装箱:将基本类型用他们对应的引用类型包装起来。
  • 拆箱:将包装类型转换为基本数据类型。

举例

Integer i = 10;  //装箱
int n = i;   //拆箱

原理

  • Integer i = 10; 等价于 Integer i = Integer.valueOf(10)
  • int n = i; 等价于 int n = i.intValue();

注意:如果频繁拆装箱的话,也会严重影响系统性能。我们应该尽量避免不必要的拆装箱操作。

private static long sum() {
    // 应该使用 long 而不是 Long
    Long sum = 0L;
    for (long i = 0; i <= Integer.MAX_VALUE; i++)
        sum += i;
    return sum;
}

这里 sum是引用类型,i是基本类型,每次执行sum+i会将sum转换为基本类型进行操作,操作完成后在将sum转换为引用类型,因此需要将sum修改为long类型

10. 为什么浮点数运算的时候有精度丢失的风险

浮点数精度丢失演示:

float a = 2.0f - 1.9f;
float b = 1.8f - 1.7f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999905
System.out.println(a == b);// false

问题的原因和计算机保存浮点数的机制有很大关系,计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机中,只能被截断,所以会导致小数精度损失的情况。

11. 如何解决浮点数运算精度丢失问题

BigDecimal可以实现对浮点数的运算,不会造成精度丢失。

BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");

BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);

System.out.println(x.compareTo(y));// 0

强制:禁止使用构造方法BigDecimal(double) 的方式把double值转化为BigDecimal对象。
说明:BigDecimal(double) 存在精度丢失风险,在精确计算或者值比较的场景中可能会导致业务逻辑异常。正例 new BigDecimal(“0.1”)

12. 超过long整形的数据应该如何表示

BigInteger内部使用 int[] 数组来存储任意大小的整型数据。

13. 成员变量和局部变量有什么区别

Java面试题汇总(持续更新…..)插图(6)

  • 语法形式:成员变量是属于类的,局部变量是属于方法的。成员变量可以被private、static等修饰,局部变量不可以。
  • 存储方式:从变量在内存中的存储方式看,如果成员变量被static修饰,那么这个成员变量属于类。如果没有,那么这个成员变量属于实例。成员变量存在于堆内存,而局部变量中的基本数据类型存储于栈内存。
  • 生存时间:成员变量是对象的一部分,随着对象的创建而存在。而成员变量如果被static修饰更是随着类的存在而存在,而局部变量随着方法的调用产生,调用的结束消亡。
  • 默认值:成员变量如果没有赋初值,则会自动以类型的默认值而赋值(除非final修饰的成员变量必须显示的赋值),而局部变量不会自动赋值。

14. 静态变量有什么作用

静态变量也就是被static修饰的变量,他可以被类的所有实例共享,无论一个类创建了多少个对象,他们都共享同一份静态变量。也就是说,静态变量只会分配一次内存,即使创建多个对象,这样可以节省内存。

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

  • 静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态成员属于实例对象,只有在对象创建的时候才会存在,需要通过类的实例对象访问。
  • 在类的非静态成员不存在的时候静态方法就已经存在了,此时调用在内存中不存在的静态成员,属于非法操作

16. 静态方法和实例方法有何不同

  • 调用方式:调用静态方法无需创建对象。
  • 访问类成员是否存在限制:静态方法在访问类的成员变量时只能访问静态成员,不允许访问实例成员。而实例方法不存在

17. 重载和重写的区别

  • 重载是同一个方法根据输入参数的不同,做出不同的处理。
  • 重写是子类继承父类,重写父类的方法。

计算机网络面试题

本站无任何商业行为
个人在线分享 » Java面试题汇总(持续更新…..)
E-->