【Java基础】常见面试题汇总 共94题

发布于:2024-10-11 ⋅ 阅读:(10) ⋅ 点赞:(0)

文章目录


1. Java为什么被称为平台无关性语言?

Java被称为平台无关性语言是因为它的程序在编译后生成字节码(Bytecode),而不是依赖具体操作系统的机器码。这个字节码可以通过Java虚拟机(JVM)在任何支持Java的设备或操作系统上运行,无需修改源代码。因此,Java具备“编写一次,到处运行”的特性。

2. 解释下什么是面向对象?面向对象和面向过程的区别?

面向对象是一种编程方式,通过对象和类来组织代码,核心是封装、继承、多态等概念。

面向对象与面向过程的区别

  • 面向对象:通过对象来封装数据和行为,注重代码的复用和扩展。
  • 面向过程:以函数为中心,依次执行操作,关注过程和逻辑。

简而言之,面向对象更适合复杂系统,面向过程适合简单任务。

3. 面向对象的三大特性?分别解释下?

面向对象的三大特性

  1. 封装:隐藏内部细节,只暴露必要的接口。
  2. 继承:子类继承父类的属性和方法,减少重复代码。
  3. 多态:相同方法在不同对象上表现出不同的行为,提高灵活性。

简单来说,封装保护数据,继承复用代码,多态增强灵活性。

4. Java 中的参数传递时传值呢?还是传引用?

Java 中的参数传递是传值。具体表现为:

  • 对于基本数据类型,传递的是变量的值,函数内对该值的修改不会影响原变量。
  • 对于引用类型(对象),传递的是对象引用的副本,即引用的值。虽然传递的是引用,但修改的是引用指向的对象内容,而不是引用本身。函数内对对象属性的修改会影响原对象,但如果改变引用指向的对象,则不会影响原引用。

总结:Java 中一律是传值,但对于对象,传递的是引用的值。

5. JDK、JRE、JVM 三者之间的关系?

JDK(Java Development Kit)JRE(Java Runtime Environment)、**JVM(Java Virtual Machine)**三者关系如下:

  1. JVM:是运行Java字节码的虚拟机,负责将字节码转换为机器码,执行Java程序,保证平台无关性。

  2. JRE:是Java运行环境,包含了JVM和一些运行Java程序所需的库和类文件,是Java程序的执行环境。

  3. JDK:是Java开发工具包,包含了JRE、JVM以及编译器(如javac)等开发工具,用于开发和调试Java程序。

总结:JDK包含JRE,而JRE包含JVM,JDK用于开发,JRE用于运行,JVM负责执行Java字节码。

6. ==和equals有什么区别?

在Java中,==equals() 的区别主要在于:

  1. == 是比较引用:对于引用类型,== 比较的是两个对象在内存中的地址,简单来说,就是看它们是不是同一个对象。如果是基本数据类型,== 比较的就是它们的值。

  2. equals() 是比较内容equals() 方法默认也比较的是对象的引用(如果没有重写的话)。不过像 StringInteger 这些类都重写了 equals(),用来比较对象的实际内容。

举个例子:如果你有两个 String 对象,虽然它们的值一样,但是 == 比较时,它们指向的内存地址不同,结果是 false。而 equals() 会返回 true,因为它们的内容是一样的。

7. equals()与hashcode(),什么时候重写,为什么重写,怎么重写?

1. 什么时候重写 equals()hashCode()

  • 当你想要比较两个对象的内容是否相同时,需要重写 equals()
  • 当你使用对象作为哈希相关的集合(如 HashMapHashSet)的键时,必须重写 hashCode(),以确保相等的对象具有相同的哈希值。

2. 为什么要一起重写?

  • 如果两个对象根据 equals() 方法是相等的,那么它们必须返回相同的 hashCode(),否则会导致在哈希表中的不一致行为(比如无法正确存取对象)。

3. 怎么重写?

  • 重写 equals() 时,要先判断对象的引用,然后逐个比较实际的字段。
  • 重写 hashCode() 时,使用对象中参与 equals() 比较的字段来生成哈希值,通常借助 Objects.hash() 方法来简化。

例子:

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null || getClass() != obj.getClass()) return false;
    MyClass other = (MyClass) obj;
    return Objects.equals(field1, other.field1) && Objects.equals(field2, other.field2);
}

@Override
public int hashCode() {
    return Objects.hash(field1, field2);
}

8. 为什么重写 equals()就一定要重写 hashCode()方法?

简单来说,equals() 定义了两个对象是否相等,而 hashCode() 用来生成哈希码。如果两个对象通过 equals() 被认为是相等的,它们的哈希码也必须一样。否则,在哈希表中会出现问题,比如你存进去的对象找不到了,因为它们的哈希码不一致。

所以,equals() 相等时,hashCode() 必须相同,这是为了保证在哈希集合中的正确性和一致性。

9. 两个对象的 hashCode()相同,则 equals() 也一定为 true 吗?

不一定。两个对象的 hashCode() 相同,并不意味着它们的 equals() 也一定为 true

hashCode() 相同表示这两个对象可能落在哈希表的同一个位置,但它们的内容可能不同。hashCode() 只是一个哈希算法的结果,可能会发生哈希碰撞(也就是不同的对象生成相同的哈希码)。只有在 equals() 返回 true 时,才能确认两个对象真正相等。

举个例子,假设你有两个不同的对象,它们的 hashCode() 刚好碰撞了,但 equals() 比较内容时会发现它们并不相等。

所以,hashCode() 相同只是可能相等,而 equals() 才是判断两个对象是否真的相等的标准。

10. 说一说你对异常的理解?

异常是程序运行中出现的错误,Java 提供了异常处理机制,让我们能捕获这些错误,避免程序崩溃。异常分为检查异常(必须处理,如文件未找到)和非检查异常(运行时异常,如空指针)。通过 try-catch 可以捕获异常,finally 用来做资源释放。合理的异常处理能提升程序的健壮性和稳定性。

11. 异常有哪些种类,可以举几个例子吗?

异常有两大类:

  1. 检查异常(Checked Exception):必须处理,比如 IOExceptionSQLException
  2. 非检查异常(Unchecked Exception):也叫运行时异常,比如 NullPointerExceptionArrayIndexOutOfBoundsException

另外,还有错误(Error),如 OutOfMemoryError,通常是系统级问题。

12. throw 和 throws 有啥区别?直接 try catch 不好吗,为啥还要抛呢?

throw:用于在方法内部主动抛出异常。比如当你发现一个问题时,手动用 throw 抛出异常。

throw new IllegalArgumentException("Invalid argument");

throws:用于声明方法可能抛出的异常,告诉调用者这个方法可能会抛出某些类型的异常,由调用者来处理。

public void myMethod() throws IOException {
    // code
}

直接用 try-catch 是可以的,但有时一个方法不知道该如何处理异常,或者异常的处理应该交给调用者去解决。这时,用 throws 抛出异常,交给上层代码去处理,会更灵活,代码也更清晰。

总结:throw 是手动抛出异常,throws 是声明可能抛出哪些异常。用 throws 是为了灵活处理不同层次的异常,不总是局限在当前方法里。

13. try catch会影响性能吗?为什么抛出异常的时候会影响性能?

try-catch 本身对性能影响很小,只有在抛出异常时性能才会受到影响,因为要创建异常对象和处理调用栈信息,这些操作比较耗资源。因此,频繁抛出异常会影响性能。

14. try-catch-finally中,如果 catch 中 return了,finally 还会执行吗?

会执行。即使 catch 中有 returnfinally 块仍然会在 return 之前执行,这是 Java 保证的一种机制。

15. 谈一谈你对 final 关键字的理解?什么时候需要用这个关键字来定义呢?

final 关键字有三个主要用途:

  1. 修饰变量:表示这个变量一旦赋值后就不能再改变,比如常量。
  2. 修饰方法:防止方法被子类重写。
  3. 修饰类:防止类被继承,比如 String 类就是 final 的。

可以用 final 来确保数据的不可变性、避免方法被误改,或者不希望类被扩展时使用它。

16. String 为什么要设计为不可变类?

  1. 安全性String 被广泛用于类加载、网络操作等核心系统功能,不可变性确保字符串的值不会被恶意或意外修改,提升了安全性。

  2. 线程安全String 不可变,使得它在多线程环境中无需同步,可以安全地共享和使用。

  3. 性能优化:由于不可变,String 的实例可以被缓存和重用(比如字符串常量池),减少了内存占用和对象创建的开销。

总结:不可变设计提升了 String 的安全性、线程安全性,并优化了性能。

17. String a= new String(“aa”)+”bb””这句话创建了多少个对象?为什么?

共创建了 4 个 String 对象,第一个是常量池中的对象 “aa”,如果常量池中有 “aa” 就直接返回,没有就创建并添加进常量池中。第二个是 new 出来的以 “aa” 为初始值创建的 String 对象,第三个 “bb” 同 “aa”,第四个是通过 “+” 拼接前两个对象,创建出的新 String 对象。

18. String 对象最多可以存放多少个字符(长度)?

String 对象最多可以存储大约 21 亿个字符,这是因为它的内部用 char[] 数组来存储数据,最大长度受限于 int 的最大值(2^31 - 1)。不过,实际能存多少还取决于你系统的内存大小,通常来说,内存是主要限制因素。

19. 字符串常量池是放在堆中吗?

字符串常量池在 Java 7 之前是放在方法区(也叫永久代)中的,但从 Java 7 开始,字符串常量池被移到了堆内存中。这是为了更好地利用堆的垃圾回收机制,提升性能和管理效率。

所以,Java 7 之后,字符串常量池是在堆中

20. String中“+”和 StringBufer 中的 append 会有性能上的差别吗?

String + 每次拼接都会创建新对象,性能较低。StringBuffer.append() 直接修改原对象,不创建新对象,性能更好,特别是大量拼接时。

21. String、StringBuilder、StringBuffer 的区别?

  1. String:不可变对象,修改会创建新对象,适用于少量字符串操作。
  2. StringBuilder:可变对象,线程不安全,但性能高,适合单线程下大量字符串操作。
  3. StringBuffer:可变对象,线程安全,性能略低于 StringBuilder,适合多线程环境下的字符串操作。

22. String 字符串修改实现的原理?

在 Java 中,String不可变的,修改 String 实际上不会改变原对象,而是创建一个新的 String 对象。

原理:
每次对 String 执行修改操作(如拼接、替换等),都会创建一个新的字符串对象并返回,原来的字符串对象保持不变。这个过程涉及创建新的 char[] 数组来存储修改后的内容。

总结:String 的修改本质上是通过创建一个新的 String 对象实现的,原对象不会改变。

23. String str=“i”与 String str = new String(“i”)一样吗?

不一样。

  1. String str = "i":使用的是字符串常量池,"i" 会放入常量池中,重复使用相同的字符串不会创建新对象。
  2. String str = new String("i"):每次都会在堆内存中创建一个新的 String 对象,即使内容相同,也不会从常量池中复用。

总结:前者复用常量池中的对象,后者每次创建新对象。

24. String 类的常用方法都有那些?

String 类的常用方法有:

  1. length():获取字符串长度。
  2. charAt():返回指定索引的字符。
  3. substring():截取字符串的一部分。
  4. indexOf():查找字符或子串的位置。
  5. toLowerCase()/toUpperCase():转换大小写。
  6. trim():去除前后空格。
  7. equals()/equalsIgnoreCase():比较字符串是否相等。
  8. split():根据分隔符拆分字符串。
  9. replace():替换字符或子串。
  10. contains():判断是否包含子串。

25. final 修饰 StringBuffer 后还可以 append 吗?

final 修饰 StringBuffer 后,可以继续使用 append() 方法。final 修饰的对象引用是不可变的,也就是说,StringBuffer 变量不能指向其他对象,但它本身的内容(即内部的字符序列)是可以修改的。所以,你可以继续调用 append() 方法修改 StringBuffer 的内容。

26. 字节和字符的区别?

字节(byte)是8位二进制数据,用于存储原始数据;字符(char)是16位,用于表示文本中的单个字符。字节处理二进制文件,字符处理文本,二者需通过编码转换。

27. Java 中的IO流的分类?说出几个你熟悉的实现类?

Java中的IO流主要分为两大类:

  1. 按方向分

    • 输入流(InputStream/Reader):用于读取数据。
    • 输出流(OutputStream/Writer):用于写出数据。
  2. 按处理数据类型分

    • 字节流:处理字节数据,如InputStreamOutputStream
    • 字符流:处理字符数据,如ReaderWriter

常见的实现类有:

  • 字节流:FileInputStreamFileOutputStream
  • 字符流:FileReaderFileWriter
  • 缓冲流:BufferedReaderBufferedWriter

28. 字节流和字符流有什么区别?

字节流和字符流的区别主要在于:

  1. 处理的数据类型

    • 字节流:处理原始字节数据,适用于所有类型的文件(如图片、音频、视频等)。
    • 字符流:处理字符数据,专门用于文本文件,能够自动处理字符编码(如UTF-8、UTF-16)。
  2. 基本单位

    • 字节流:以**字节(8位)**为单位。
    • 字符流:以**字符(16位,UTF-16)**为单位。
  3. 使用的类

    • 字节流InputStreamOutputStream及其子类。
    • 字符流ReaderWriter及其子类。

总结:字节流适合处理所有类型的数据,而字符流专门用于文本处理。

29. BIO、NIO、AIO 有什么区别?

BIO、NIO、AIO的区别主要在于IO处理模式:

  1. BIO(阻塞IO):每个请求都会阻塞,适合连接数较少的场景,简单但性能低。

  2. NIO(非阻塞IO):支持多路复用,非阻塞方式处理大量连接,适合高并发,性能较好。

  3. AIO(异步IO):完全异步非阻塞,操作完成后通知应用程序,适合超高并发场景。

总结:BIO简单但低效,NIO适合高并发,AIO效率最高但复杂。

30. 如何实现对象的克隆?

  1. 实现Cloneable接口并重写Object类的clone()方法:

    • 调用super.clone()实现浅拷贝。
  2. 通过序列化实现深拷贝:

    • 将对象序列化为字节流,再反序列化为新对象。

31. 深克隆和浅克隆的区别?

浅克隆:复制对象时,引用类型的属性与原对象共享,修改会相互影响。

深克隆:复制对象时,引用类型也会被复制,克隆对象与原对象完全独立,不相互影响。

32. 装箱和拆箱的区别?

装箱:将基本数据类型转换为对应的包装类对象(如intInteger)。

拆箱:将包装类对象转换为对应的基本数据类型(如Integerint)。

装箱是把基本类型放入对象中,拆箱是从对象中取出基本类型。

33. Integer 和 int 的区别?

int 是Java的基本数据类型,表示原始的整型数,占用4字节,默认值为0。

Integerint的包装类,是对象类型,用于更复杂的操作,如集合框架,允许null值。

总结:int是基本类型,性能高;Integer是对象类型,功能更强但占用更多内存。

34. short s1=1;s1=s1+1;有什么错?那么 short s1=1; s1 +=1;呢?有没有错误?

代码 short s1=1; s1=s1+1; 会报错。原因是:

  • 表达式 s1 + 1 的结果会被提升为 int 类型,无法直接赋值给 short 类型的变量 s1,需要显式类型转换。

而代码 short s1=1; s1 +=1; 不会报错。原因是:

  • s1 += 1 是一种复合赋值运算符,编译器会自动处理类型转换,不需要手动进行类型转换。

35. switch 语句能否作用在 byte 上,能否作用在 long 上,能否作用在 String 上?

  1. switch 语句可以作用于 byte 类型,因为 byte 是整数类型,switch 支持整型数据。

  2. switch 不能作用于 long 类型,因为 switch 仅支持 int 范围内的整数类型(如 byteshortintchar),long 超出了这个范围。

  3. switch 可以作用于 String 类型,从 Java 7 开始,switch 语句支持 String

36. Java 中的 Math.round(-1.5)等于多少?

Math.round() 方法根据四舍五入的规则对参数进行取整:

  • 对于正数,四舍五入到最近的整数。
  • 对于负数,如果小数部分是0.5,它会向更接近0的整数舍入。

因此,Math.round(-1.5) 四舍五入为 -1。

37. 两个二进制数的异或结果是什么?

两个二进制数异或结果是这两个二进制数差的绝对值。表达式如下:a^b = |a-b|。

两个二进制 a 与 b 异或,即 a 和 b 两个数按位进行运算。如果对应的位相同,则为 0(相当于对应的算术相减),如果不同即为 1(相当于对应的算术相加)。由于二进制每个位只有两种状态,要么是 0,要么是 1,则按位异或操作可表达为按位相减取值相对值,再按位累加。

38. 介绍一下 Object 常见方法?

Object 类是 Java 所有类的父类,常见方法包括:

  1. equals():判断两个对象是否相等。
  2. hashCode():返回对象的哈希码值,常与equals()一起重写。
  3. toString():返回对象的字符串表示,通常被重写以提供自定义信息。
  4. clone():创建对象的副本,需实现Cloneable接口。
  5. getClass():返回对象的运行时类信息。
  6. finalize():在垃圾回收时调用,做资源释放。
  7. wait()notify()notifyAll():用于线程间的同步和通信。

39. 构造方法有哪些特性?

  1. 与类同名:构造方法的名称必须与类名相同。

  2. 无返回值:构造方法没有返回值类型,甚至不能写void,仅用于初始化对象。

  3. 自动调用:在创建对象时,构造方法自动被调用,无需手动调用。

  4. 可重载:可以根据参数的不同定义多个构造方法(构造方法重载)。

  5. 隐式默认构造方法:如果没有定义构造方法,编译器会提供一个默认的无参构造方法。

  6. 不能被继承:构造方法不会被子类继承,但可以通过super()调用父类构造方法。

这些特性使得构造方法用于对象的初始化操作。

40. 在 Java 中定义一个不做事且没有参数的构造方法有什么作用?

在Java中,如果子类的构造方法没有显式调用父类的构造方法(使用 super()),Java会自动调用父类的无参构造方法。如果父类中没有定义无参构造方法,并且只有带参构造方法,则编译时会报错,因为找不到可以默认调用的无参构造方法。

解决办法就是:

  • 在父类中手动定义一个无参构造方法,即使它什么也不做,这样可以确保子类构造时父类能够正常初始化。

例如:

class Parent {
    public Parent(int x) {
        // 带参构造方法
    }

    // 解决方法:手动定义一个无参构造方法
    public Parent() {
        // 不做事的无参构造方法
    }
}

class Child extends Parent {
    public Child() {
        // 子类构造方法没有调用 super(),Java默认调用父类无参构造方法
    }
}

这样可以避免编译错误,确保子类能正确构造。

41. Java 中创建对象的几种方式?

  1. 使用new关键字:最常见的方式,通过构造方法创建对象,例如 MyClass obj = new MyClass();

  2. 通过反射:使用Class.forName()Class.newInstance(),如 MyClass obj = (MyClass) Class.forName("MyClass").newInstance();

  3. 使用克隆:通过clone()方法复制一个已有对象,前提是类实现了Cloneable接口,例如 MyClass obj = originalObj.clone();

  4. 通过序列化与反序列化:将对象序列化到流中,再通过反序列化创建对象,例如 ObjectInputStream ois = new ObjectInputStream(inputStream); MyClass obj = (MyClass) ois.readObject();

  5. 通过工厂方法:使用某些工厂类提供的静态方法创建对象,如 Integer obj = Integer.valueOf(5);

42. 静态变量和实例变量的区别?

静态变量

  • 属于类,所有对象共享,用类名访问。
  • 在类加载时分配内存,生命周期随类存在。

实例变量

  • 属于对象,每个对象有自己的副本,用对象访问。
  • 在对象创建时分配内存,随对象销毁。

总结:静态变量共享,实例变量独立。

43. super 关键字的作用?

super 关键字的作用有以下几点:

  1. 调用父类构造方法:用于调用父类的构造方法,必须在子类构造方法的第一行,如 super()super(args)

  2. 调用父类方法:用于调用父类的非私有方法,避免子类中被覆盖的同名方法,如 super.methodName()

  3. 访问父类成员变量:用于访问父类的成员变量,避免与子类同名变量冲突,如 super.variableName

总结:super 用于在子类中访问父类的构造方法、方法或成员变量。

44. transient 关键字的作用?

transient 关键字的作用是:防止成员变量被序列化。当一个变量被声明为 transient 时,序列化对象时该变量的值不会被保存。

45. static 关键字的作用?

static 关键字的作用包括:

  1. 静态变量:用于声明类变量,所有对象共享该变量。
  2. 静态方法:用于声明类方法,可以直接通过类名调用,无需创建对象。
  3. 静态代码块:用于初始化类,在类加载时执行。
  4. 静态内部类:声明静态内部类,可以不依赖外部类实例。

总结:static 使得成员属于类本身,而不是实例。

46. 抽象类和接口有什么区别?

抽象类接口的主要区别如下:

  1. 方法定义

    • 抽象类可以有抽象方法和具体方法
    • 接口只能有抽象方法(Java 8后可以有默认方法和静态方法)。
  2. 多继承

    • 抽象类不支持多继承,一个类只能继承一个抽象类。
    • 接口支持多实现,一个类可以实现多个接口。
  3. 成员变量

    • 抽象类可以有实例变量常量
    • 接口中的变量默认是public static final的常量。
  4. 构造方法

    • 抽象类可以有构造方法
    • 接口没有构造方法

总结:抽象类是部分实现的类,接口是行为规范,支持多实现。

47. Java 中是否可以重写一个 private 或者 static 方法?

在Java中,不能重写privatestatic方法,原因如下:

  1. private方法:由于private方法只能在类内部访问,子类无法访问该方法,因此不能重写。你可以在子类中定义一个同名方法,但这实际上是一个新的方法,并不是对父类方法的重写。

  2. static方法static方法属于类本身而不是实例,因此不能通过子类进行重写。如果在子类中定义了同名的static方法,这是对父类方法的隐藏,而不是重写。

总结:private方法和static方法都不能被重写。

48. 为什么Java不支持多继承?

Java 不支持多继承,主要原因是:

  1. 避免菱形继承问题:多继承会导致方法冲突,无法确定调用哪个父类的方法。

  2. 简化设计:多继承增加复杂性,难以维护。

  3. 接口替代:Java通过接口实现多态,避免了多继承的问题。

总结:Java通过接口提供多态性,避免了多继承带来的复杂性和冲突。

49. 聊一聊你对多态的理解?

多态是指同一方法在不同对象上表现出不同行为。Java中的多态有两种:

  1. 编译时多态(重载):根据参数不同选择方法。
  2. 运行时多态(重写):父类引用调用子类重写的方法。

多态让代码更灵活、解耦,提高扩展性和维护性。

50. 讲一下重载和重写的区别?

重载:同类中方法名相同,参数不同。

重写:子类修改父类方法,方法签名相同。

区别:

  • 重载在同类中,重写在父子类间。
  • 重载方法参数不同,重写方法参数和返回类型必须相同。

51. Java 的泛型是如何工作的 ?什么是类型擦除 ?

Java的泛型通过在类、接口、方法中引入类型参数,提供编译时的类型检查,确保类型安全,避免强制类型转换。

类型擦除:Java在编译时会移除泛型的类型信息,转换成原始类型(如List<T>变为List),这样在运行时不保留泛型类型信息,只保证编译时的类型安全。

52. 什么是泛型中的限定通配符和非限定通配符?

泛型中的通配符

  1. 限定通配符

    • <? extends T>:表示类型必须是TT的子类,适合读取数据。
    • <? super T>:表示类型必须是TT的父类,适合写入数据。
  2. 非限定通配符

    • <?>:表示可以接受任何类型,既不限制上界也不限制下界,适合灵活处理任意类型的数据。

总结:限定通配符限制类型上下界,非限定通配符接受任意类型。

53. List和 List之间有什么区别 ?

List<?>List<Object> 的区别在于它们的通配符和类型约束:

  1. List<?>(非限定通配符):表示可以持有任何类型List,但不能往里面添加元素(除了null),因为具体类型未知,只能读取元素。

  2. List<Object>:表示可以持有**Object类型或其子类**的List,可以添加Object或其子类的元素。

总结:List<?>适合处理任何类型的List,但限制插入;List<Object>则明确可以插入Object类型。

54. 什么是序列化?什么情况下需要序列号?序列号在Java中是怎么实现的?

序列化是指将对象转换为字节流,以便保存到文件或通过网络传输。

需要序列化的情况

  • 对象需要持久化到存储设备中(如保存到文件或数据库)。
  • 对象需要通过网络传输

Java中序列化的实现

  • 通过实现Serializable接口,Java对象可以被序列化。
  • 使用ObjectOutputStream进行序列化,ObjectInputStream进行反序列化。

总结:序列化用于对象的持久化和传输,Java通过Serializable接口实现。

55. Java 中的反射是什么意思?有哪些应用场景?有哪些优缺点?

反射是Java中的一种机制,允许在运行时动态获取类的信息(如类名、方法、属性等)并调用方法或操作对象。

应用场景

  • 框架设计:如Spring、Hibernate等,依赖反射来实例化类、调用方法。
  • 动态代理:通过反射生成动态代理类。
  • 调试工具:如对象分析、类结构查看等。

优点

  • 灵活性高:可以在运行时动态操作类和对象。
  • 解耦性强:通过反射实现与具体类的松耦合。

缺点

  • 性能开销:反射较慢,影响性能。
  • 安全性低:可能绕过权限控制,带来安全风险。

总结:反射用于动态操作类,灵活但性能较低,有安全隐患。

56. 什么是动态代理?有什么用?Java中可以怎么样实现动态代理?

动态代理是指在运行时创建代理对象,动态处理方法调用。

用途

  • 主要用于AOP(面向切面编程)权限控制日志记录等,避免手动编写重复代码。

Java中的实现

  • JDK动态代理:使用Proxy类和InvocationHandler接口,适用于接口代理。
  • CGLIB代理:通过字节码生成子类,适用于代理没有接口的类。

总结:动态代理在运行时创建代理对象,用于简化代码,Java通过JDK或CGLIB实现。

57. final、finally、finalize的区别

  1. final:修饰类、方法、变量,表示不可继承、不可重写、不可更改。

  2. finally:异常处理中保证代码一定执行。

  3. finalize:垃圾回收前的清理方法(已不推荐使用)。

58. &和&&的区别?

&:按位与运算符,或逻辑与运算符,两边的表达式都会执行

&&:逻辑与运算符,具有短路特性,如果左边为false,右边不会执行。

总结:&既能用于按位操作也能用于逻辑运算,&&用于逻辑运算且有短路特性。

59. 请阐述Java和C++两种编程语言的主要差异,以及它们各自的优点和缺点是什么?

主要差异

  1. 内存管理

    • Java:有自动垃圾回收,不需要手动管理内存。
    • C++:手动管理内存,需注意释放资源(delete)。
  2. 平台独立性

    • Java:通过JVM实现“写一次,运行到处”。
    • C++:编译后生成与平台相关的可执行文件。
  3. 多继承

    • Java:不支持类的多继承,使用接口解决。
    • C++:支持类的多继承,但可能带来复杂性。
  4. 指针

    • Java:不支持指针,提供更安全的内存访问。
    • C++:支持指针,灵活但容易出错。

优缺点

  • Java优点

    • 自动内存管理,跨平台,社区支持强大。
  • Java缺点

    • 性能较低,依赖JVM,系统级操作能力有限。
  • C++优点

    • 性能高,灵活,适合系统级编程和高性能应用。
  • C++缺点

    • 手动内存管理复杂,容易出现内存泄漏。

60. 在Java和C中,函数或方法的参数传递有哪些重要区别?请具体说明。

JavaC在参数传递方面的主要区别如下:

  1. 参数传递方式

    • Java:只有值传递,即将参数的副本传递给方法。对于基本类型,传递的是值的副本;对于引用类型,传递的是对象引用的副本(指向同一对象)。
    • C:支持值传递指针传递。通过指针传递,函数可以直接操作传入的变量的内存地址,改变其值。
  2. 指针操作

    • Java:不支持指针,无法直接操作内存地址,这提高了安全性。
    • C:支持指针,可以通过指针传递来修改参数的值,但需要开发者自行管理内存,容易出错。
  3. 对象的影响

    • Java:对于对象传递,虽然传递的是对象引用的副本,但可以通过该引用修改对象的内部状态,但不能更改对象引用本身指向新的对象。
    • C:通过指针传递参数,函数可以修改传入变量的值或对象。

总结

  • Java只有值传递,但对象的引用可以修改对象内部状态。
  • C支持值和指针传递,指针传递允许直接修改参数值。

61. 能否详细解释面向对象编程的五大基本原则是什么?这些原则在编程实践中的重要性如何体现?

面向对象编程的五大基本原则(SOLID):

  1. 单一职责原则(SRP)

    • 一个类应只有一个明确的职责。
    • 重要性:增强代码可读性和可维护性,避免复杂类。
  2. 开放封闭原则(OCP)

    • 软件实体应对扩展开放,对修改封闭。
    • 重要性:通过扩展新功能而非修改已有代码,降低变更风险。
  3. 里氏替换原则(LSP)

    • 子类应能替代父类使用,且不改变功能。
    • 重要性:保证子类和父类兼容,确保多态性正确实现。
  4. 接口隔离原则(ISP)

    • 接口应小而精,不应强迫类实现不需要的方法。
    • 重要性:避免臃肿的接口,提高模块的灵活性。
  5. 依赖倒置原则(DIP)

    • 高层模块不应依赖低层模块,二者都应依赖于抽象。
    • 重要性:减少模块间的耦合,增强系统可扩展性和灵活性。

总结:这些原则在编程实践中通过降低耦合、增强扩展性和可维护性,确保代码清晰、灵活、可复用。

62. Java语言中已经有了基本数据类型,为什么还需要引入对应的包装类?包装类的存在有何意义?

Java引入包装类的意义:

  1. 兼容集合框架:集合只能存储对象,包装类使基本类型可以存入集合。
  2. 提供方法:包装类提供基本类型没有的实用方法(如Integer的数值转换)。
  3. 支持泛型:泛型只支持对象,包装类使基本类型能与泛型结合使用。
  4. 自动装箱与拆箱:简化基本类型与对象类型的相互转换。

总结:包装类扩展了基本类型的功能,并增强了与集合、泛型等特性的兼容性。

63. 谈谈你对Java中自动拆箱和装箱的理解,这两个过程在编程中起到了什么作用?

自动装箱:将基本数据类型自动转换为对应的包装类对象。

自动拆箱:将包装类对象自动转换为基本数据类型。

作用

  1. 简化代码书写,无需手动进行类型转换。
  2. 提高基本类型与对象类型的兼容性,特别是在集合、泛型等场景中使用。

总结:自动装箱和拆箱使得基本类型与包装类之间的转换更加方便,简化了编程操作。

64. 为什么我们通常不建议使用浮点数来表示金钱等需要精确计算的数值?请说明原因。

  1. 浮点数的近似表示:浮点数在计算机内部是以二进制存储的,而许多十进制小数(如0.1)在二进制中无法精确表示,导致存储的只是近似值。

  2. 舍入误差:由于这种近似表示,在进行数学运算时,浮点数的结果可能会有微小的误差,这在金融等精确计算的场景中是不可接受的。

  3. 解决方法:常见的替代方法是使用整数(如以分为单位)或使用BigDecimal类,这样可以确保精确的数值表示和运算。

总结:浮点数因其精度问题导致计算误差,不适合用于处理金钱等需要精确计算的场景。

65. 在使用Java的BigDecimal类进行数值比较时,为什么不应该直接使用equals方法来做等值比较?请解释原因。

在使用Java的BigDecimal类进行数值比较时,不应该直接使用equals方法,因为equals不仅比较数值,还比较精度。例如,new BigDecimal("1.0").equals(new BigDecimal("1"))会返回false,即使它们数值相等,因为它们的小数位数不同

应使用compareTo()方法来比较数值大小,它只比较数值本身,而忽略精度差异。

66. 对Java中的负数取绝对值结果一定是正数吗,为什么?

在Java中,对负数取绝对值的结果不一定总是正数。这是因为Math.abs()方法在极少数情况下会返回负值。

具体原因是:对于int类型的最小值Integer.MIN_VALUE(-2^31,即 -2147483648),其绝对值超出了int类型的正数范围(最大为Integer.MAX_VALUE,即 2147483647),所以Math.abs(Integer.MIN_VALUE)仍然会返回-2147483648

类似地,对于long类型,Math.abs(Long.MIN_VALUE)也会返回Long.MIN_VALUE

总结:在极少数情况下(如Integer.MIN_VALUELong.MIN_VALUE),绝对值的结果仍然是负数。

67. 考虑以下Java代码片段:’Sting a=”ab”; Stingb=”a”+”b”:请问在这里,a和b是否相等(使用’==`运算符进行比较)?为什么?

在这个Java代码片段中:

String a = "ab";
String b = "a" + "b";

a == b 会返回 true,原因如下:

  • 在Java中,字符串字面量(如"ab")会放入字符串常量池。
  • 表达式 "a" + "b" 是编译期常量,编译器会优化这个表达式,直接将其结果视为 "ab",而不是在运行时进行字符串拼接。
  • 因此,ab实际上都指向字符串常量池中的同一个对象 "ab",所以a == btrue

如果是通过变量拼接字符串(如String b = someVariable + "b";),则结果会不同,因为那时b指向的是一个新的对象。

68. Java中的String类型是否有长度限制?如果有,那么这个限制是多少?

Java中的String类型有长度限制,但它主要受限于JVM内存和**int类型的最大值**。String的长度是通过int类型表示的,因此最大长度为 2^31 - 1(即约21亿字符)。不过,实际使用中会受到可用内存的限制。

69. 在RPC接口的返回值中,我们应该选择基本数据类型还是它们的包装类?

在RPC接口的返回值中,建议选择包装类而不是基本数据类型。原因是:

  1. 包装类可以表示null,用于区分未赋值和默认值的情况,基本数据类型则不支持null
  2. 包装类具有更多方法和功能,方便处理和扩展。

因此,包装类在RPC接口中更灵活,适应性更强。

70. 列举并解释一些常见的字符编码方式以及它们之间的主要差异。

  1. ASCII:最基本的字符编码,用7位表示128个字符,包括英文字母、数字和一些符号。它只适用于英语,不支持其他语言字符。

  2. ISO-8859-1:扩展的ASCII,用8位表示字符,能支持西欧语言的特殊符号,比如法语、西班牙语中的字符,但对亚洲语言不友好。

  3. UTF-8:一种通用的字符编码方式,变长编码。英文字符用1字节,其他字符根据需要用2到4个字节。UTF-8兼容ASCII,并且能表示全球绝大部分语言,是网络传输中常用的编码方式。

  4. UTF-16:也是一种通用编码方式,通常用2个字节表示字符,但对于一些稀有字符,可能需要4个字节。适合处理大量亚洲字符。

  5. Unicode:一种标准,它定义了全球所有字符的唯一编码,像UTF-8和UTF-16都是Unicode的具体实现方式。

主要差异在于:编码范围、支持的字符集以及字节长度的不同。UTF-8最灵活,适合多语言场景;ASCII最简单,只适合英语。

71. 能列举几种编程中常用的语法糖吗?

常用的编程语法糖有:

  1. 增强for循环

    • 简化遍历数组或集合的代码,替代传统的for循环。
    • 例如:for (String item : list)
  2. 自动装箱/拆箱

    • Java自动将基本数据类型与其对应的包装类进行转换。
    • 例如:int a = 5; Integer b = a; // 自动装箱
  3. Lambda表达式

    • 简化匿名类的实现,使代码更简洁。
    • 例如:list.forEach(item -> System.out.println(item));
  4. 三元运算符

    • 通过?:表达式简化if-else的判断。
    • 例如:int max = (a > b) ? a : b;
  5. 字符串拼接

    • 使用+号拼接字符串,而不是手动调用StringBuilder
    • 例如:String result = "Hello " + "World";

这些语法糖让代码更加简洁易读,减少了样板代码的编写。

72. Lambda表达式的底层实现机制是怎样的?

Lambda表达式在Java的底层实现机制基于匿名内部类和 invokedynamic 字节码指令

  1. 编译时:Lambda表达式并没有直接编译为匿名内部类,而是由编译器生成一条特殊的invokedynamic指令,指向一个方法句柄

  2. 运行时invokedynamic指令在JVM中由LambdaMetafactory生成实际的实现类,这个类是一个与目标函数式接口匹配的实例,从而实现Lambda的功能。

总结:Lambda表达式通过invokedynamic指令在运行时动态生成实现类,避免了创建冗余的匿名类实例。

73. 描述一下泛型是什么,以及使用它有哪些优势?

泛型是Java的一种机制,允许在类、接口和方法中使用类型参数,使代码可以处理不同类型的数据,而不需要具体指定类型。

优势

  1. 类型安全:在编译时检查类型,避免类型转换错误。
  2. 代码重用:同一泛型类或方法可以处理不同类型的数据,减少重复代码。
  3. 可读性和维护性:无需显式类型转换,代码更简洁、易读。

总结:泛型通过类型参数提高了代码的安全性、重用性和可维护性。

74. 解释一下类型擦除?

类型擦除是Java泛型的机制,编译时会移除泛型类型信息,将泛型替换为原始类型(如Object)。这样确保了泛型与非泛型代码的兼容性。

结果

  • 运行时无法获取泛型类型(如List<String>List<Integer>在运行时都是List)。
  • 泛型的类型检查仅在编译时有效,运行时类型信息被擦除。

总结:类型擦除保证兼容性,但在运行时泛型类型信息丢失。

75. 在泛型声明中,K、T、V、E、Object等通常代表什么意义?

  1. T:表示Type(类型),常用于任意类型的泛型参数。
  2. K:表示Key,常用于键值对中的键类型。
  3. V:表示Value,常用于键值对中的值类型。
  4. E:表示Element,通常用于集合(如List, Set)中,代表集合中的元素类型。
  5. Object:指的是非泛型类型,表示任意对象类型。

总结:这些字母是泛型中的惯用符号,用于表达不同的泛型角色。

76. 泛型中的上界限定符extends和下界限定符super各自的作用是什么,它们之间有何不同?

在泛型中:

  1. extends(上界限定符):指定泛型的上限,表示泛型类型必须是某个类或其子类,适用于读取数据。

    • 例:<? extends Number> 表示类型必须是Number或其子类。
  2. super(下界限定符):指定泛型的下限,表示泛型类型必须是某个类或其父类,适用于写入数据。

    • 例:<? super Integer> 表示类型必须是Integer或其父类。

区别extends适用于读取,super适用于写入。

77. SPI是什么,它与API有什么主要区别?

SPI(Service Provider Interface)用于动态扩展,定义接口由服务提供者实现,框架通过它发现和加载服务。

API(Application Programming Interface)提供接口,供应用程序调用功能。

区别

  • API:应用调用接口。
  • SPI:框架加载实现。

总结:API用于调用功能,SPI用于扩展和服务发现。

78. Java注解的主要用途是什么?

Java注解的主要用途包括:

  1. 提供元数据:为代码提供信息,供编译器或工具使用(如@Override)。
  2. 编译时检查:如@Deprecated提醒不推荐使用的代码。
  3. 运行时处理:通过反射机制,在运行时动态处理注解(如@Autowired用于依赖注入)。
  4. 代码生成和配置:用于框架配置(如Spring、JUnit中的注解)。

总结:注解用于提供元数据、编译检查、运行时处理和配置功能。

79. serialVersionUlD在Java序列化中起什么作用?如果不定义它,可能会遇到什么问题?

serialVersionUID在Java序列化中用于标识类的版本,它确保在序列化和反序列化时类的兼容性。

作用

  • 它帮助JVM确认反序列化时,序列化对象与当前类是否版本一致。

如果不定义serialVersionUID

  • JVM会根据类的结构自动生成一个serialVersionUID,但这可能在类的修改后发生变化。
  • 如果类在反序列化时结构改变且没有匹配的serialVersionUID,会抛出InvalidClassException,导致反序列化失败。

总结:不定义serialVersionUID可能导致版本不匹配而无法反序列化。

80. 是否了解fastjson库中的反序列化漏洞?请简述其影响。

Fastjson反序列化漏洞是指Fastjson在处理不可信数据时,可能导致反序列化攻击,即攻击者通过精心构造的JSON数据,触发任意代码执行或系统命令。

影响

  • 攻击者可以利用漏洞执行恶意代码,导致信息泄露、系统崩溃远程代码执行,对系统安全造成严重威胁。

总结:Fastjson反序列化漏洞可能导致任意代码执行,影响系统安全,必须谨慎处理不可信数据并及时更新库版本。

81. finally块中的代码是否总是会被执行?有没有例外情况?

finally块的代码通常总是会执行,无论是否抛出异常。它用于释放资源或执行收尾工作。

例外情况

  1. System.exit():如果在trycatch块中调用System.exit(),程序终止,finally不会执行。
  2. JVM崩溃或死锁:如果JVM异常终止(如崩溃或死锁),finally也不会执行。
  3. 线程被强制中断:如果线程被强制停止,可能导致finally不执行。

总结:一般情况下finally会执行,但在程序终止、崩溃等极端情况时可能不会。

82. Java中的枚举类型有哪些独特之处,使用它们有什么好处?

Java枚举的独特之处

  1. 类型安全:定义了一组固定常量,避免错误。
  2. 内置方法:如values()name()等,方便使用。
  3. 可扩展:枚举可以包含方法和字段,甚至实现接口。

好处

  • 提高代码可读性安全性
  • 灵活扩展功能,便于操作常量。

总结:枚举类型安全、易用、功能强大。

83. SimpleDateFormat在多线程环境中是否安全?使用时需要特别注意什么?

SimpleDateFormat在多线程环境中不安全,可能导致数据错乱或异常。

注意:在多线程环境下,使用时要避免共享实例,可以通过局部变量同步或使用ThreadLocal来确保线程安全。

总结:SimpleDateFormat不适合多线程共享,需特别处理线程安全问题。

84. 目前JDK的最新版本号是什么?

目前,Java的最新版本是 JDK23,它于2024年9月发布。这个版本带来了许多新功能和改进,包括对模式匹配、并发编程和垃圾回收机制的增强。

85. 在JDK的最新版本中,引入了哪些新的特性或改进?

在JDK最新版本(JDK23)中,引入了以下新的特性和改进:

  1. 原始类型模式匹配扩展:增强了instanceofswitch语句的能力,使它们可以处理原始类型的模式匹配。

  2. 模块导入声明:简化了模块的导入,帮助开发者更轻松地使用第三方库和Java类。

  3. 隐式声明类和main方法:简化了初学者编写Java程序的体验,允许更简单的程序结构。

  4. 结构化并发:通过新API简化了多线程编程,提升了代码的可维护性和可靠性。

  5. ZGC垃圾收集器改进:默认启用了ZGC的代际模式,提升了垃圾回收的效率。

这些特性提高了Java的灵活性、性能和开发效率。

86. UUID是什么,它能否保证在全局范围内的唯一性?

UUID(Universally Unique Identifier)是一种标准格式,用于生成全球唯一的标识符,通常由128位的数字组成。UUID的格式保证在极大概率下生成的值在全球范围内是唯一的。

能否保证全局唯一性

  • UUID的设计能在绝大多数情况下确保唯一性,但它并不绝对保证唯一性(理论上有碰撞的可能性)。然而,由于128位的长度和算法设计,这种碰撞的概率非常小,可以忽略不计。

总结:UUID非常适合在全球范围内生成唯一标识,虽然不能100%绝对保证唯一性,但其碰撞几率极低。

87. char类型能否直接存储中文字符?

char类型可以直接存储中文字符。在Java中,char类型使用UTF-16编码,占用两个字节(16位),因此能够表示Unicode字符集中的大多数字符,包括中文字符。

中文字符的Unicode编码范围内,Java的char能够支持,因此可以存储和处理中文字符。

总结:char类型可以存储中文字符,因为它采用16位编码,支持Unicode字符集。

88. 从性能角度来看,while(true)和for(;;)哪个更优?

从性能角度来看,**while(true)for(;;)**在Java中几乎是等价的,两者在编译后的字节码是相同的,性能上没有明显差别。

  • while(true):基于条件检查进行循环,条件一直为true
  • for(;;):是一个没有初始化、条件和递增部分的循环,相当于无限循环。

由于编译器会将两者优化为相同的底层指令,从性能角度来看,while(true)for(;;)几乎完全相同,选择哪个更多取决于代码风格和可读性。

89. ClassNotFoundExceptionNoClassDefFoundError在Java中分别代表什么,它们之间有何区别?

ClassNotFoundException:表示在动态加载类(如通过Class.forName())时,未找到类。它是受检异常,需要处理。

NoClassDefFoundError:表示类在编译时存在,但运行时丢失。它是错误(Error),通常由JVM抛出。

区别

  • ClassNotFoundException:发生在动态加载时。
  • NoClassDefFoundError:发生在类缺失时,通常在执行代码时出现。

总结:一个是异常(动态加载时),一个是错误(类缺失)。

90. 在JDK 9中,为什么String类的内部实现从char改为了byte[]这样做有什么好处?

在JDK 9中,String类的内部实现从char[]改为byte[],主要目的是节省内存

好处

  1. 内存占用减少char占2字节,而大多数字符串只包含单字节的Latin-1字符,使用byte[]可以减少内存占用。
  2. 提高性能:减少了垃圾回收和内存压力,从而提升了整体性能。

总结:这一更改优化了内存效率,尤其是对于包含大量Latin-1字符的字符串。

91. Arrays.sort方法在Java中使用了哪种排序算法?

Java中Arrays.sort()使用了**双轴快速排序(Dual-Pivot Quicksort)**算法,适用于基本数据类型。这种算法是对经典快速排序的优化,通常在实际数据下表现良好。

对于对象类型,Arrays.sort()使用的是归并排序(Timsort),一种稳定的排序算法,基于归并排序和插入排序的结合。

总结:Arrays.sort()使用双轴快速排序(基本类型)和Timsort(对象类型)。

92. Java中的String类是如何实现其不可变性的?

Java中的String类通过以下方式实现了不可变性

  1. 内部使用final修饰的char[]byte[]数组:存储字符串的字符数据是一个final字段,不能修改其引用。

  2. 没有提供修改内容的方法String类的所有方法(如substringreplace)返回的是新的String对象,而不是修改当前对象。

  3. 确保安全性和性能:不可变性避免了字符串在多个线程中共享时的数据冲突,提升了线程安全性和效率。

总结:通过使用final字段和返回新对象,Java保证了String对象的不可变性。

93. 字符串常量在什么时候会被加入到Java的字符串常量池中?

字符串常量在Java中会在编译时加入到字符串常量池中。当编译器遇到字符串字面量时,它会将其添加到常量池中,以便复用相同内容的字符串对象,避免重复创建。

此外,使用String.intern()方法也可以将字符串显式地加入到常量池中。

总结:字符串常量在编译时自动加入字符串常量池,也可以通过intern()方法显式加入。

94. 解释String类中的intern方法的工作原理。

String.intern()方法的作用是将字符串放入字符串常量池中。如果常量池中已经存在一个与该字符串值相同的对象,则返回常量池中的对象引用;否则,会将当前字符串添加到常量池并返回该引用。

总结:intern()方法确保相同内容的字符串在内存中只存在一个实例,提升内存效率。