Java常见面试题--后端——JavaSE后篇

承接前篇

Java常见面试题--后端——JavaSE前篇-CSDN博客

为什么String不可变

String 在 Java 中被设计为不可变的,这是出于多方面的考虑:

  1. 安全性:不可变的字符串是线程安全的。在多线程环境下,如果字符串是可变的,多个线程可能同时修改字符串,导致不确定的行为。通过使字符串不可变,可以避免这种情况。

  2. 缓存:由于字符串不可变,可以被缓存,例如字符串常量池。多个字符串变量如果指向相同的字符串常量,它们可以共享相同的内存地址,节约内存空间。

  3. 优化:字符串不可变使得编译器可以进行优化,例如在拼接字符串时可以使用常量折叠,直接合并为一个新的字符串。

  4. 安全性和哈希码:由于字符串不可变,它们的哈希码在创建后就不会改变。这使得字符串适合用作 Map 的键,保证了哈希码的一致性。

  5. 参数传递:字符串作为方法参数传递时,不可变性确保了传递的参数不会被改变。

因此,不可变的特性带来了安全性、线程安全性、性能优化以及更简单的代码编写和维护。

判断两个String对象相等应该怎么写?

在 Java 中判断两个 String 对象相等,应该使用 equals() 方法而不是 == 操作符。equals() 方法用于比较两个字符串对象的内容是否相同,而 == 操作符用于比较两个对象的引用是否相同(即是否指向同一个内存地址)。

示例代码如下:

String str1 = "Hello";
String str2 = "Hello";
String str3 = new String("Hello");
​
// 使用 equals() 方法比较字符串内容是否相等
boolean isEqual1 = str1.equals(str2); // true
boolean isEqual2 = str1.equals(str3); // true
​
// 使用 == 操作符比较字符串对象的引用是否相同
boolean isSameReference = str1 == str2; // true
boolean isSameReference2 = str1 == str3; // false

在上面的例子中,equals() 方法比较了字符串对象的内容,而 == 操作符比较了对象的引用。通常情况下,我们想要比较两个字符串的内容是否相等,所以应该使用 equals() 方法。

判断两个Double类型的数相等应该怎么写?

在 Java 中,由于浮点数的精度问题,直接使用 == 操作符来比较两个 Double 类型的数可能不够准确。通常建议使用以下方法来判断两个 Double 类型的数是否相等:

  1. 使用 Double 类的 equals 方法:

Double num1 = 0.1 + 0.2;
Double num2 = 0.3;
​
boolean isEqual = num1.equals(num2); // 使用 Double 的 equals 方法比较
  1. 使用比较的误差范围(epsilon):

由于浮点数计算可能存在精度问题,可以定义一个很小的误差范围(通常称为 epsilon),在比较两个浮点数时,判断它们的差值是否小于这个误差范围。

double num1 = 0.1 + 0.2;
double num2 = 0.3;
​
double epsilon = 0.0000001; // 定义一个很小的误差范围
​
boolean isEqual = Math.abs(num1 - num2) < epsilon; // 比较差值是否小于误差范围

第二种方法更常用,因为它允许您指定比较的精度范围,尤其是在涉及浮点数计算时。

Java中Stringbuilder与Stringbuffer区别及应用场景

StringBuilderStringBuffer 是用于处理可变字符串的类,它们之间的主要区别在于线程安全性和性能:

  1. 线程安全性:

    • StringBuilder 不是线程安全的,因此在多线程环境下使用时需要注意同步问题。

    • StringBuffer 是线程安全的,所有公共方法都是同步的,可以在多线程环境下安全使用。

  2. 性能:

    • 由于 StringBuffer 的所有方法都是同步的(使用了 synchronized 关键字),在单线程环境下其性能比 StringBuilder 差一些。

    • StringBuilder 没有线程安全的保证,但因为不需要进行同步,所以在单线程环境下其性能更好。

应用场景:

  • 如果在单线程环境下进行字符串操作,并且对性能有较高要求,可以使用 StringBuilder

  • 如果在多线程环境下进行字符串操作,或者需要线程安全性,可以使用 StringBuffer

一般情况下,由于现代应用程序更倾向于使用不可变对象和线程安全的集合类,StringBuilder 在大多数情况下是更常用的选择,因为它在单线程环境下提供了更好的性能。只有在需要线程安全性时才应该使用 StringBuffer

判断输出结果

Integer x = new Integer(123);
Integer y = new Integer(123);
System.out.println(x == y);
输出的结果,为什么?
Integer z = Integer.valueOf(123);
Integer k = Integer.valueOf(123);
System.out.println(z == k);
输出的结果,为什么?

在 Java 中,对于 Integer 类型的对象,范围在 -128 到 127 之间的整数对象会被缓存,以提高性能和节省内存。所以在这个范围内,两个对象指向同一个缓存对象,超出这个范围则会创建新的对象。

对于第一个问题:

Integer x = new Integer(123);
Integer y = new Integer(123);
System.out.println(x == y);

输出结果是 false。这是因为 new Integer(123) 每次都会创建一个新的对象,所以 xy 指向不同的对象,即使它们的值相同。

对于第二个问题:

Integer z = Integer.valueOf(123);
Integer k = Integer.valueOf(123);
System.out.println(z == k);

输出结果是 true。这是因为在使用 Integer.valueOf() 方法时,会先检查范围,如果在 -128 到 127 之间,则会从缓存中返回已有的对象,因此 zk 实际上指向了同一个缓存对象,所以 == 比较返回 true

equals重写了什么算法

在 Java 中,equals() 方法是用于比较两个对象的内容是否相等的方法,它的默认实现是比较对象的引用是否相等(即比较对象的地址)。然而,大部分类都会重写 equals() 方法以根据自身的需求进行对象内容的比较。

在重写 equals() 方法时,需要遵循以下原则:

  1. 自反性:对于任何非空引用值 xx.equals(x) 应返回 true

  2. 对称性:对于任何非空引用值 xy,如果 x.equals(y) 返回 true,那么 y.equals(x) 也应返回 true

  3. 传递性:对于任何非空引用值 xyz,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 也应返回 true

  4. 一致性:如果两个对象的内容没有发生变化,多次调用 equals() 应该始终返回相同的结果。

  5. 非空性:对于任何非空引用值 xx.equals(null) 应返回 false

对于实现 equals() 方法的算法,通常会检查对象的类型、比较对象的字段或属性值是否相等,以确定对象是否相等。例如,在比较自定义类的对象时,常见的做法是逐个比较对象的属性值。需要注意的是,重写 equals() 方法时需要同时重写 hashCode() 方法,以确保对象在放入哈希集合(如 HashMapHashSet)等集合类时能够正确地定位对象。

面向对象的特性(oop特性

面向对象编程(OOP)的特性包括以下几个方面:

  1. 封装(Encapsulation)

    • 将数据和操作数据的方法封装在对象内部,隐藏对象的内部实现细节,只暴露必要的接口给外部使用。

  2. 继承(Inheritance)

    • 允许一个类(子类)继承另一个类(父类)的属性和方法,子类可以重用父类的特性,提高代码的复用性和扩展性。

  3. 多态(Polymorphism)

    • 允许不同类的对象对同一消息做出响应,即同样的方法调用可以在不同的对象上产生不同的行为。包括方法重载和方法重写。

  4. 抽象(Abstraction)

    • 将复杂的现实世界抽象为类和对象,只关注对象的行为和特性,隐藏不必要的细节,简化复杂性。

这些特性共同构成了面向对象编程的核心理念,并提供了一种有效的方法来组织和管理复杂的软件系统,使得代码更易于理解、扩展和维护。

重写和重载的区别

重写(Override)和重载(Overload)是面向对象编程中的两个概念,它们指的是不同类型的方法操作。

  1. 重载(Overload)

    • 重载发生在同一个类中,指的是在同一个类中定义多个方法,它们具有相同的名称但参数列表不同(参数类型、参数个数或参数顺序不同)。

    • 重载的目的是为了提供多种方法来处理相似的操作,便于程序员记忆和使用。

    示例:

    public class Calculator {
        public int add(int a, int b) {
            return a + b;
        }
    ​
        public double add(double a, double b) {
            return a + b;
        }
    }

  2. 重写(Override)

    • 重写发生在子类和父类之间,指的是子类定义了一个与父类中某个方法名称、返回类型和参数列表完全相同的方法。

    • 重写的目的是改变父类方法的实现,以适应子类的需求,实现多态性。

    示例:

    class Animal {
        public void makeSound() {
            System.out.println("Animal is making a sound");
        }
    }
    ​
    class Dog extends Animal {
        @Override
        public void makeSound() {
            System.out.println("Dog is barking");
        }
    }

总结:

  • 重载是在同一个类中,方法名称相同但参数列表不同;

  • 重写是在子类和父类之间,子类重新定义了父类的方法,方法名称、返回类型和参数列表完全相同。

在 Java 中,编译器根据方法的参数列表来区分重载的方法,而根据方法的签名(名称、参数列表、返回类型)来确定是否重写父类的方法。

try. catch、finally执行顺序

在 Java 中,try-catch-finally 是用于处理异常的结构。其执行顺序如下:

  1. try 块:包含可能会抛出异常的代码块。当异常被抛出时,会立即跳出 try 块,并将异常抛给相应的 catch 块。

  2. catch 块:用于捕获和处理 try 块中抛出的异常。当 try 块中的代码抛出异常时,会在对应的 catch 块中进行异常处理。如果捕获到异常,则执行 catch 块中的代码,然后执行 finally 块。

  3. finally 块:不管是否发生异常,都会执行的代码块。finally 块通常用于执行清理工作或确保资源的释放。即使在 try 块中有 return 语句,finally 块也会在 return 之前执行。

执行顺序如下:

  • 如果没有异常抛出:

    • 先执行 try 块中的代码。

    • 然后执行 finally 块中的代码。

  • 如果发生异常:

    • 先执行 try 块中抛出异常前的代码。

    • 找到匹配的 catch 块处理异常,执行 catch 块中的代码。

    • 最后执行 finally 块中的代码。

无论是否发生异常,finally 块中的代码都会被执行。如果有 return 语句,会在 finally 块执行后才返回。

为什么使用接口,接口的好处

接口在面向对象编程中起着重要作用,其主要好处包括:

  1. 实现多态性(Polymorphism):

    • 接口允许类通过实现接口来定义行为,一个类可以实现多个接口。这种多态性使得对象能够被视为多种类型,提高了灵活性和扩展性。

  2. 解耦合(Decoupling):

    • 接口定义了类与类之间的契约,而不是具体的实现细节。这种分离允许代码模块化,降低了代码的耦合度,使得代码更易于维护和修改。

  3. 实现代码重用(Code Reusability):

    • 接口定义了一组方法规范,多个类可以通过实现同一个接口来重用接口定义的方法,避免了重复编写相似的代码。

  4. 实现面向对象设计原则:

    • 接口有助于实现面向对象编程中的抽象、封装、继承和多态等基本原则。它们促进了良好的设计实践和模式的应用。

  5. 提供规范和约束:

    • 接口定义了类应该具有的方法和行为,为开发者提供了一种规范和约束,使得代码更易于理解和协作开发。

总的来说,接口是一种重要的抽象工具,能够提供灵活性、可扩展性和代码重用性,有助于实现良好的面向对象设计。通过接口,程序员可以定义统一的契约和规范,使得系统更易于扩展和维护。

谈一谈对反射的了解

反射(Reflection)是指程序在运行时能够检查、获取和修改其自身状态或行为的能力。在 Java 中,反射允许程序在运行时检查和操作类、对象、方法、字段等元数据信息。

一些关于反射的基本概念和用法包括:

  1. 获取 Class 对象

    • 可以通过对象的 getClass() 方法或者类名的 .class 属性来获取对应类的 Class 对象。

    • 也可以使用 Class.forName("ClassName") 方法来根据类的全限定名获取 Class 对象。

  2. 获取类的信息

    • 可以使用 Class 类的方法来获取类的信息,如获取类的名称、方法、字段、构造器等。

    • 通过 Class 对象可以获取类的构造方法、成员方法、字段等信息,并可以动态调用它们。

  3. 创建对象

    • 可以使用 Class 类的 newInstance() 方法创建类的实例。在 Java 9 之后,推荐使用 Class.getDeclaredConstructor().newInstance() 方法。

  4. 调用方法和操作字段

    • 可以使用反射机制动态调用类的方法,通过 Method.invoke() 实现方法的调用。

    • 也可以使用反射机制操作类的字段,通过 Field.get()Field.set() 获取和设置字段的值。

  5. 处理权限和安全性

    • 反射允许绕过访问权限的限制,可以访问私有方法、字段等。这种能力需要小心使用,可能会降低代码的安全性。

反射是一种强大的特性,它允许程序在运行时动态地获取和操作类的信息,但同时也增加了代码的复杂性和运行时性能开销。因此,在使用反射时需要慎重考虑,合理利用其功能,并注意性能和安全方面的问题。

有什么办法可以把方法去内存撑爆

在 Java 中,方法的内存溢出通常是指方法区(Method Area)的内存溢出,这里存储着类的信息、静态变量、常量池等。撑爆方法区的几种方式包括:

  1. 大量动态生成类:

    • 如果在程序运行过程中大量动态生成类(比如使用 CGLIB、动态代理等),可能会导致方法区内存不足。

  2. 大量字符串常量:

    • 大量字符串常量的生成也会占用方法区内存。特别是使用大量字符串常量的场景,例如大量的类加载、大量的动态字符串拼接等。

  3. 无限递归调用:

    • 在方法中进行无限递归调用,导致方法调用栈无限增长,最终导致栈溢出异常(StackOverflowError)。

  4. 类加载器泄漏:

    • 如果自定义类加载器在加载类时没有正确地进行垃圾回收,可能会导致类加载器本身及其加载的类无法被回收,导致方法区内存溢出。

避免方法区内存溢出可以采取以下措施:

  • 避免过多的动态生成类和字符串常量。

  • 注意递归调用的结束条件,避免无限递归。

  • 合理设计程序结构,确保自定义类加载器能够被垃圾回收。

需要注意的是,方法区内存溢出相对比较少见,而更常见的是堆内存溢出(Heap Overflow)或栈内存溢出(Stack Overflow),针对不同类型的内存溢出,解决方案也不同。

如何查看gc日志,主要关注哪些内容?

要查看 JVM 的 GC(垃圾回收)日志,你可以使用以下方法:

  1. 启用 GC 日志:

    • 在启动 Java 应用程序时,可以使用以下参数启用 GC 日志记录:

      -Xloggc:<file-path>

      这将把 GC 日志输出到指定文件中。

  2. 选择日志级别:

    • 可以选择不同的日志级别,如:

      • -XX:+PrintGCDetails:打印详细的 GC 信息。

      • -XX:+PrintGCDateStamps:打印 GC 发生的日期时间。

      • -XX:+PrintHeapAtGC:在每次 GC 时打印堆信息。

  3. 日志文件分析:

    • 分析 GC 日志时,关注以下内容:

      • GC 的类型(Minor GC、Major GC、Full GC)。

      • GC 发生的时间点和频率。

      • 内存的使用情况(堆内存、永久代/元空间等)。

      • GC 的停顿时间(Pause Time)。

      • 内存回收的效果(回收了多少内存)。

  4. 使用分析工具:

    • 你也可以使用一些专门的分析工具来分析 GC 日志,比如 jstatjvisualvmVisualGC 等。

GC 日志的分析有助于了解程序的内存使用情况、垃圾回收的频率和效率等信息,有助于优化程序性能和内存的使用。

  • 29
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值