Java 基础面试题

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

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

  1. 面向过程是一种以过程为中心的编程思想,简单来说就是分析出解决问题所需要的步骤,然后按照步骤一步步编程实现。面向过程以实现功能的函数为主,如果需要实现某个功能,只需要编写对应的函数即可。其优点是性能比面向对象高,但是不易复用和扩展。
  2. 面向对象是一种以建立对象为基础的编程思想,简单来说就是分析构成问题的各个部分,将其拆分成独立的对象,用于描述某个部分在解决问题过程中所需要执行的行为,面向对象的特性是封装,继承,多态。面向对象以实现功能的对象为主,如果需要实现某个功能,需要先创建对应的对象,然后让对象去执行行为。其优点是能够降低系统的耦合,提高系统的可维护性,但是性能比面向过程低。

Java有几种基本数据类型

在Java中,基本数据类型又称为原始数据类型,共有 8 种。这些基本数据类型可以分为以下几类:

  1. 整数类型
    • byte:8位,有符号,表示范围为 -128 到 127 的整数。
    • short:16位,有符号,表示范围为 -32,768 到 32,767 的整数。
    • int:32位,有符号,表示范围为 -2^31 到 2^31 - 1 的整数(约 -2.1亿到 2.1 亿)。
    • long:64位,有符号,表示范围为 -2^63 到 2^63 - 1 的整数。
  2. 浮点数类型
    • float:32位,单精度浮点数,用于表示小数,精度约为 7 位有效数字。
    • double:64位,双精度浮点数,更精确,用于表示小数,精度约为 15-16 位有效数字。
  3. 字符类型
    • char:16位,用于表示单个字符,例如字母、数字或符号。
  4. 布尔类型
    • boolean:表示 true 或 false 值。

基本类型和引用类型?他们的区别

Java 为了面向对象操作的一致性,每种基本数据类型都有对应的包装类,使它们具有对象的特性。其区别如下:

  1. 存储方式
    • 基本类型:基本类型的变量直接存储数据值,它们的值是直接存储在内存中的栈内存中的位置。这意味着基本类型的变量包含了实际的数据值。
    • 引用类型:引用类型的变量存储的是一个引用或地址,指向实际数据存储在堆内存中的位置。这意味着引用类型的变量本身不包含实际数据,而是指向数据的位置。
  2. 初始化
    • 基本类型:基本类型的变量在声明时会有默认初始值,例如,int 类型的变量默认初始化为0,boolean 类型的变量默认初始化为false。
    • 引用类型:引用类型的变量在声明时默认初始化为null,表示它们不引用任何对象。
  3. 内存管理
    • 基本类型:基本类型的内存分配和释放是自动的,由Java虚拟机(JVM)自动管理。它们的生命周期与变量的作用域密切相关。
    • 引用类型:引用类型需要显式地创建对象,并且需要手动管理对象的生命周期。当不再需要引用对象时,需要确保引用类型的变量不再指向对象,以便垃圾回收器可以回收不再使用的内存。
  4. 比较
    • 基本类型:基本类型的比较是通过值进行的,即比较它们的实际数据值。
    • 引用类型:引用类型的比较是通过引用进行的,即比较它们指向的对象是否是同一个对象,而不是对象的内容。
  5. 传递方式
    • 基本类型:基本类型的值在方法之间传递时是按值传递的,即传递的是实际数据的副本。
    • 引用类型:引用类型的变量在方法之间传递时传递的是引用的副本,因此多个变量可以指向同一个对象。

Integer a= 127 与 Integer b = 127相等吗?

在Java中,整数包装类 Integer 中的一些常用整数值会被缓存,以提高性能和节省内存。范围在 -128 到 127 之间的整数常量会被缓存到 Integer 对象池中。因此,当你创建一个 Integer 对象并赋值为这个范围内的整数时,它们会引用相同的对象,因此它们相等。

所以,Integer a = 127 和 Integer b = 127 是相等的,它们引用了相同的 Integer 对象。这是因为它们的值在缓存范围内(-128 到 127),超出这个范围的整数则不会被缓存,每次都会创建一个新的对象。

示例:

Integer a = 127;
Integer b = 127;

System.out.println(a == b); // 输出 true,它们引用相同的对象

Integer x = 128;
Integer y = 128;

System.out.println(x == y); // 输出 false,超出了缓存范围,每次创建新的对象

需要注意的是,这种自动装箱(Autoboxing)和对象池的行为只适用于 Integer 类型和特定范围内的整数值,对于其他整数包装类(如 Long、Short、Byte 等)以及超出范围的整数值,不会有对象缓存,每次都会创建新的对象。因此,在比较整数对象时,最好使用 .equals() 方法而不是 == 运算符,以确保正确的比较。例如:a.equals(b)

访问修饰符 public,private,protected,以及不写(默认)时的区别?

  1. public:
    • public 访问修饰符表示成员对任何类都是可见的,无论这些类是否属于同一包。
    • public 成员可以在任何地方被访问,包括不同包的类。
  2. private:
    • private 访问修饰符表示成员只能被同一类中的其他成员访问,外部类无法访问。
    • private 成员通常用于隐藏内部实现细节,确保数据的封装性。
  3. protected:
    • protected 访问修饰符表示成员对同一包内的类和子类可见。
    • protected 成员允许在子类中继承并访问,但在不同包的非子类中无法直接访问。
  4. 默认(不写访问修饰符):
    • 如果不写访问修饰符,则成员具有包级私有(package-private)的访问权限。
    • 默认访问权限表示成员对同一包内的类可见,但对于不同包的类是不可见的。

总结:

  • public:对所有类可见。
  • private:只对同一类内部可见。
  • protected:对同一包内的类和子类可见。
  • 默认(不写访问修饰符):对同一包内的类可见,但对于不同包的类是不可见的。

重载和重写的区别

在面向对象编程中,重载(Overloading)和重写(Overriding)是两个不同的概念,用于实现多态性和代码复用。它们之间的主要区别包括以下几个方面:

定义:

  • 重载:重载是指在同一个类中,可以有多个具有相同名称的方法,但这些方法的参数列表不同(参数类型、参数个数、参数顺序等)。编译器根据方法的参数列表来决定调用哪个方法。
  • 重写:重写是指子类可以在继承自父类的方法的基础上重新定义方法体,以适应子类的特定需求。重写的方法名称、返回类型和参数列表必须与父类中的方法一致。

发生地点

  • 重载:重载发生在同一个类中,通常是在一个类中定义多个具有相同名称但不同参数的方法。
  • 重写:重写发生在继承关系中的子类和父类之间,子类重写了父类中的方法。

运行时行为

  • 重载:重载的方法在编译时就确定了,具体调用哪个方法取决于参数的匹配。
  • 重写:重写的方法在运行时动态确定,调用哪个方法取决于对象的实际类型。这是多态性的一种体现。

返回类型

  • 重载:可以有相同名称但不同返回类型的重载方法。
  • 重写:重写的方法必须具有与父类方法相同的返回类型或其子类型。

目的

  • 重载:重载主要用于实现同一功能的不同变种,通过不同的参数来区分它们,提高代码的灵活性和可读性。
  • 重写:重写主要用于子类对父类方法的定制化实现,以实现多态性和特定行为。

构造器是否可被重写

在Java中,构造器不能被重写。

构造器是用于创建对象的特殊方法,它们不是普通的类方法,因此不遵循普通方法的继承和重写规则。

在Java中,子类可以调用父类的构造器,但不能覆盖(override)它。

Java 面向对象编程三大特性?

Java 面向对象编程的三大特性是封装、继承和多态。

  1. 封装: 封装是指将数据(属性)和操作数据的方法(方法)封装在一个类中,以保护数据不被直接访问或修改。通过使用访问修饰符(如 private、protected、public)来控制属性的可见性,以及提供公共方法来访问或修改属性,可以实现封装。这有助于维护代码的安全性和一致性,同时也提高了代码的可维护性和可复用性。
  2. 继承: 继承是一种机制,允许一个类(子类或派生类)继承另一个类(父类或基类)的属性和方法。子类可以继承父类的特性,并且可以扩展或修改这些特性,以满足特定的需求。继承有助于代码的重用和组织,同时也建立了类之间的层次结构。
  3. 多态: 多态是指同一种操作或方法可以根据上下文以不同的方式执行。在 Java 中,多态性通常通过方法的重写(Override)和方法的重载(Overload)来实现。重写允许子类覆盖父类的方法,以实现特定的行为,而重载允许在同一类中定义多个同名方法,但参数列表不同。多态性提高了代码的灵活性和可扩展性,允许在运行时根据对象的实际类型调用适当的方法。

接口和抽象类的区别

接口(Interface)和抽象类(Abstract Class)是 Java 中用于实现抽象类型的两种不同机制,它们有一些关键区别:

  1. 定义和用途:
    • 抽象类:抽象类是一个类,可以包含抽象方法和具体方法。抽象类用于表示一种通用的基类,它可能包含一些通用的属性和方法,但无法被实例化。子类必须继承抽象类并实现其中的抽象方法,可以选择性地覆盖具体方法。
    • 接口:接口是一种特殊类型,它定义了一组抽象方法,但不包含任何具体的方法实现(Java 8 以后提供了 default 方法)。接口用于定义一种契约或协议,规定了类必须实现的方法,但不提供具体实现。类可以实现多个接口,从而实现多重继承。
  2. 多继承:
    • 抽象类:Java 中只支持单继承,一个类只能继承一个抽象类。
    • 接口:一个类可以实现多个接口,从而具备多重继承的能力。这使得接口在定义多个相关但不同继承层次结构的行为时非常有用。
  3. 构造方法:
    • 抽象类:抽象类可以有构造方法,可以被子类继承和调用。构造方法通常用于初始化抽象类的属性。
    • 接口:接口不能有构造方法,因为接口不能被实例化。它们只包含抽象方法的声明和常量的定义。
  4. 变量:
    • 抽象类:可以包含实例变量(字段),这些变量可以具有不同的访问修饰符,例如 privateprotectedpublic
    • 接口:只能包含常量(public static final)和抽象方法,不能包含实例变量。
  5. 使用场景:
    • 抽象类:通常用于建立类的层次结构,表示一组相关类之间的通用特性,包括部分共享的代码。
    • 接口:用于定义一组相关但不同类之间的契约或协议,强调实现类必须提供的方法。

总之,抽象类和接口都是用于实现抽象类型的机制,但它们在定义和用途上有明显的区别。在设计中,你需要考虑到类之间的关系和需求,选择使用抽象类还是接口,或者它们的组合,以满足设计的目标。通常来说,如果你需要建立一种通用的基类并提供一些默认的实现,可以选择抽象类。如果你需要定义一种契约或协议,强调实现类必须提供的方法,可以选择接口。

final 在 java 中有什么作用?

在 Java 中,final 是一个关键字,用于给类、方法、变量等声明添加特定的行为和语义。final 的作用取决于它被应用的上下文,以下是 final

在不同上下文中的作用:

final 类:

如果你将一个类声明为 final,则意味着这个类不能被继承,即不能有子类。这是为了防止其他类继承并修改该类的行为,通常用于确保某个类的实现不会被改变。

final 方法:

当你将一个方法声明为 final 时,表示这个方法不能被子类重写(覆盖)。这用于确保特定行为在所有子类中保持一致性,通常在父类中定义关键的算法或逻辑。

final 变量:

当你将一个变量声明为 final 时,表示这个变量的值不能被修改,即它成为了一个常量。一旦被赋予初值,就不能再改变。

final 参数:

在方法的参数列表中使用 final 关键字表示该参数是只读的,不能在方法内部被修改。这有助于确保方法不会无意中改变参数的值。

总的来说,final 用于增加代码的安全性、可维护性和可预测性。它可以应用在类、方法、变量和参数上,根据需要来限制或保护它们的行为。

抽象类能使用 final 修饰吗?

不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承, 这样彼此就会产生矛盾,所以 final 不能修饰抽象类

final、finally、finalize 有什么区别?

  1. final
    • final 是一个关键字,可以用于类、方法和变量。
    • 当用于类时,final 表示这个类不能被继承,即它是不可扩展的。
    • 当用于方法时,final 表示该方法不能被子类重写(覆盖)。
    • 当用于变量时,final 表示该变量是一个常量,一旦被赋值后就不能再次修改。
  2. finally
    • finally 是一个关键字,用于异常处理中的 try-catch-finally 语句块。
    • 无论是否发生异常,finally 块中的代码都会被执行。
    • finally 常用于确保资源的释放,例如关闭文件、数据库连接等。
  3. finalize
    • finalize 是一个方法名,它是 java.lang.Object 类中的一个方法。
    • finalize 方法是 Java 垃圾回收器(Garbage Collector)用于回收对象资源的机制。
    • 当对象不再被引用时,垃圾回收器会在对象被销毁前调用 finalize 方法,你可以在该方法中进行一些资源释放的操作。
    • 注意: 在Java 中,不建议使用 finalize(),因为它的行为不稳定,而且垃圾回收器的工作通常足够处理资源回收。

String 、StringBuffer 和 StringBuilder 的区别是什么?

StringStringBuffer 和 StringBuilder 是 Java 中用于处理字符串的三个不同类,它们之间的主要区别在于其性能、可变性和线程安全性。

  1. String
    • String 是不可变的字符串类。一旦创建了一个 String 对象,它的内容不能被修改。
    • 当你对一个 String 对象进行修改(例如连接字符串),实际上是创建了一个新的 String 对象,原始的对象保持不变。
    • 由于不可变性,String 对象在多线程环境中是线程安全的,不需要额外的同步措施。
    • String 适用于保存常量字符串,例如配置信息、消息等。
  2. StringBuffer
    • StringBuffer 是可变的字符串类,用于处理可变的字符序列。
    • 你可以使用 StringBuffer 的方法来添加、删除、修改字符序列,而不会创建新的对象。
    • StringBuffer 是线程安全的,适用于多线程环境,因为它的方法都被同步了。
    • 但在单线程环境中,它的性能相对较差,因为同步操作会带来一定的开销。
  3. StringBuilder
    • StringBuilder 与 StringBuffer 类似,也是可变的字符串类,用于处理可变的字符序列。
    • 不同的是,StringBuilder 不是线程安全的,因此在多线程环境中使用时需要额外的同步措施。
    • 但在单线程环境中,StringBuilder 的性能通常比 StringBuffer 更好,因为没有同步开销。

总结:

  • String 是不可变的,适用于常量字符串。
  • StringBuffer 是可变的且线程安全的。
  • StringBuilder 是可变的但不是线程安全的,在单线程环境中性能更好。根据需求选择适当的类来处理字符串,平衡性能和线程安全。

String 为什么设置为不可变?

  1. **安全性:**不可变的字符串提供了更高的安全性。因为字符串的内容无法被修改,所以不会出现在多个地方同时修改一个字符串的情况,这有助于避免潜在的竞态条件和错误。
  2. **线程安全:**由于字符串不可变,多个线程可以同时访问字符串对象而无需担心线程安全问题。这简化了多线程编程,不需要额外的同步措施。
  3. **缓存和性能优化:**不可变字符串允许字符串常量池(String Pool)的存在。相同的字符串字面值在常量池中只有一份实例,这有助于节省内存和提高性能。重复使用相同的字符串对象减少了对象创建和销毁的开销。
  4. **安全哈希码:**字符串的哈希码(HashCode)可以在创建时计算并缓存,因为字符串不可变。这意味着在整个字符串的生命周期内,其哈希码保持不变,这对于哈希表等数据结构的使用非常有益。
  5. **字符串连接的优化:**Java 编译器可以对字符串连接操作进行优化,将多个连接操作合并为一个,以减少不可变字符串的创建。这种优化提高了性能。

== 和 equals 的区别

  1. ==
    • == 是用来比较两个对象的引用是否相同,即比较两个对象是否是同一个对象。
    • 当使用 == 运算符比较基本数据类型(如 intchar 等)时,它比较的是值。
    • 当用于比较引用类型(如对象)时,它比较的是对象的内存地址是否相同,即两个引用是否指向同一个对象。
  2. equals
    • equals 是一个方法,定义在 java.lang.Object 类中,可以被子类覆盖以提供自定义的比较行为。
    • 默认情况下,equals 方法与 == 一样,比较两个对象的引用是否相同(即是否指向同一个对象)。
    • 通常,开发者需要在自定义类中重写 equals 方法,以比较对象的内容而不是引用。这样,可以根据对象的属性来判断它们是否相等。

示例:

String str1 = new String("https://www.skjava.com");
String str2 = new String("https://www.skjava.com");

// 使用 == 比较引用,结果为 false,因为它们是不同的对象
boolean result1 = (str1 == str2);

// 使用 equals 比较内容,结果为 true,因为它们的内容相同
boolean result2 = str1.equals(str2);

hashCode() 与 equals ()

hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。

  1. hashCode 和 equals 的一致性:
    • 如果两个对象使用 equals 方法比较相等,它们的 hashCode 值必须相等。
    • 但是,两个哈希码相等的对象不一定相等,因此在重写 equals 方法时,需要确保相等的对象具有相等的哈希码。

hashCode 用于生成对象的哈希码,以便在散列集合中快速定位对象。equals 用于比较两个对象的内容是否相等。在自定义类中,需要重写这两个方法,确保它们的行为与对象的相等性定义一致。

hashCode()与equals() 的相关规定

一致性规定:

  • 如果两个对象通过 equals() 比较相等(即 obj1.equals(obj2) 返回 true),那么它们的 hashCode() 值必须相等。
  • 如果两个对象的 hashCode() 值相等,它们不一定要通过 equals() 比较相等,即相等的哈希码不一定表示相等的对象。

equals()** 方法规定:**

  • 自反性:对于任何非空引用 xx.equals(x) 应该返回 true
  • 对称性:对于任何非空引用 x 和 y,如果 x.equals(y) 返回 true,那么 y.equals(x) 也应该返回 true
  • 传递性:对于任何非空引用 xy 和 z,如果 x.equals(y) 返回 true 且 y.equals(z) 返回 true,那么 x.equals(z) 也应该返回 true
  • 一致性:对于任何非空引用 x 和 y,只要 x 和 y 的属性没有改变,多次调用 x.equals(y) 应该返回相同的结果。
  • 对于任何非空引用 xx.equals(null) 应该返回 false

hashCode()** 方法规定:**

  • 如果两个对象通过 equals() 方法比较相等,那么它们的 hashCode() 值必须相等。
  • 如果两个对象的 hashCode() 值相等,它们不一定要通过 equals() 方法比较相等,即相等的哈希码不一定表示相等的对象。
  • 对于不同的对象,它们的 hashCode() 值可以相同,但最好是通过散列算法来减小哈希冲突的概率。

hashCode() 和 equals() 方法在 Java 中是非常重要的,因为它们影响了对象的相等性和在散列集合中的行为。遵守这些规定有助于保证程序的正确性和性能。

为什么要重写 hashcode( ) 还要重写 equals( ) ?

重写 hashCode() 和 equals() 方法是为了确保在使用散列集合(如 HashMapHashSet)时,对象的相等性和哈希码的处理是正确的。这两个方法的关系是紧密相连的,以下是为什么需要同时重写它们的主要原因:

  1. 维护哈希集合的一致性:
    • 哈希集合(如 HashMapHashSet)使用对象的哈希码来确定对象在内部数据结构中的存储位置。
    • 如果两个对象相等,它们的哈希码必须相等。这是为了确保当你在哈希集合中添加一个对象并尝试查找它时,可以正确地找到它,而不会导致哈希集合中的对象丢失或重复。
  2. 重写 equals() 方法以比较对象的内容:
    • 默认情况下,equals() 方法在 Object 类中是比较对象的引用,即它只会返回 true 当且仅当两个引用指向同一个对象。
    • 通常,你希望根据对象的属性来确定它们是否相等,而不是仅仅基于引用比较。因此,你需要在自定义类中重写 equals() 方法,以比较对象的内容。
  3. 保证哈希码一致性:
    • 当你重写 equals() 方法以比较对象的内容时,你必须确保相等的对象具有相等的哈希码,否则它们将无法在哈希集合中正确定位。
    • 如果两个相等的对象具有不同的哈希码,那么当你尝试在哈希集合中查找这些对象时,哈希集合会认为它们是不同的对象,从而导致错误的行为。

有没有可能两个不相等的对象有相同的hashcode

有可能。

在产生hash冲突时,两个不相等的对象就会有相同的 hashcode 值。当hash冲突产生时,一般有以下几种方式来处理:

  1. 拉链法:每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向链表进行存储.
  2. 开放定址法:一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入
  3. 再哈希:又叫双哈希法,有多个不同的Hash函数.当发生冲突时,使用第二个,第三个….等哈希函数计算地址,直到无冲突.

instanceof 关键字的作用

instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例,用法为:

boolean result = obj instanceof Class

其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。

注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。

int i = 0;
System.out.println(i instanceof Integer);//编译不通过  i必须是引用类型,不能是基本类型System.out.println(i instanceof Object);//编译不通过

Integer integer = new Integer(1);
System.out.println(integer instanceof  Integer);//true

//false   ,在 JavaSE规范 中对 instanceof 运算符的规定就是:如果 obj 为 null,那么将返回 false。
System.out.println(null instanceof Object);

Java自动装箱与拆箱

自动装箱(Autoboxing):

  • 自动装箱是指将基本数据类型自动转换为其对应的包装类型。
  • 在需要使用包装类型的地方,可以直接使用基本数据类型,编译器会自动将其转换为包装类型。
  • 例如,你可以这样写:Integer num = 10;,其中 10 是 int 类型,但编译器会自动将其装箱为 Integer 对象。
Integer num = 42; // 自动装箱

自动拆箱(Unboxing):

  • 自动拆箱是指将包装类型自动转换为其对应的基本数据类型。
  • 在需要使用基本数据类型的地方,可以直接使用包装类型,编译器会自动将其拆箱为基本数据类型。
  • 例如,你可以这样写:int value = num;,其中 num 是 Integer 对象,但编译器会自动将其拆箱为 int 类型。
Integer num = 42;
int value = num; // 自动拆箱

这些自动转换机制简化了代码,使得在基本数据类型和包装类型之间进行转换更加方便,减少了手动进行类型转换的需要。但需要注意,在进行自动拆箱时,如果包装类型对象为 null,会抛出 NullPointerException 异常。因此,在自动拆箱之前需要确保包装类型对象不为 null

深拷贝和浅拷贝的区别是什么?

深拷贝和浅拷贝是两种不同的对象复制方式,它们有以下主要区别:

  1. 复制对象的深度:
    • 浅拷贝:在浅拷贝中,只复制对象本身以及对象中的基本数据类型字段的值。对于对象中的引用类型字段,只复制引用而不复制引用指向的对象。这意味着原对象和浅拷贝对象共享相同的引用对象。
    • 深拷贝:在深拷贝中,不仅复制对象本身和基本数据类型字段的值,还递归复制对象中的引用类型字段所引用的对象及其内部的所有对象。这样,原对象和深拷贝对象不共享任何引用对象,它们是完全独立的。
  2. 复制的方式:
    • 浅拷贝:通常可以通过对象的克隆方法或拷贝构造函数来执行浅拷贝。
    • 深拷贝:深拷贝通常需要自定义实现,以确保所有引用类型字段都被递归复制。
  3. 对象之间的独立性:
    • 浅拷贝:原对象和浅拷贝对象共享相同的引用对象,因此对其中一个对象进行修改可能会影响另一个对象。
    • 深拷贝:原对象和深拷贝对象是完全独立的,对其中一个对象的修改不会影响另一个对象。
  4. 应用场景:
    • 浅拷贝适用于需要共享一部分数据,但仍然需要一些独立性的情况,或者在性能和内存占用方面有限制的情况。
    • 深拷贝适用于需要完全独立的对象副本,以避免对象之间的互相影响。

3 * 0.1 == 0.3返回值是什么

在 Java 中,3 * 0.1 == 0.3 返回false。这是因为浮点数的精度问题可能导致计算结果与预期值不完全相等。

这是一个常见的浮点数陷阱,因为0.1在二进制中是一个无限循环小数,无法精确表示。因此,计算机在进行浮点数计算时可能会出现舍入误差。

static都有哪些用法?

静态成员变量(静态字段):

  • 使用 static 修饰的成员变量属于类,而不是实例。所有类的实例共享同一个静态成员变量。
  • 静态成员变量在类加载时被初始化,只会被初始化一次。
  • 静态成员变量可以通过类名访问,例如 ClassName.staticVariable

静态方法:

  • 使用 static 修饰的方法是类级别的方法,不依赖于类的实例。
  • 静态方法可以通过类名调用,不需要创建类的实例。
  • 静态方法常用于工具方法、工厂方法等不依赖于对象状态的操作。

静态代码块:

  • 静态代码块是一个特殊的代码块,用于在类加载时执行初始化操作。
  • 静态代码块只在类加载时执行一次,并且在静态成员变量初始化之前执行。

静态内部类:

  • 静态内部类是定义在另一个类内部的类,但使用 static 修饰。
  • 静态内部类不依赖于外部类的实例,可以独立存在。
  • 静态内部类可以通过外部类名直接访问,例如 OuterClass.StaticInnerClass

静态导入:

  • 静态导入允许在类中直接引用静态成员,而无需使用类名。
  • 静态导入通过 import static 语句实现,例如 import static java.lang.Math.*; 可以直接使用 sqrt 而不需要写成 Math.sqrt

a=a+b与a+=b有什么区别吗?

+= 操作符会进行隐式自动类型转换,此处a+=b隐式的将加操作的结果类型强制转换为持有结果的类型,而a=a+b则不会自动进行类型转换.如:

byte a = 127;
byte b = 127;
b = a + b; // 报编译错误:cannot convert from int to byte b += a; 

以下代码是否有错,有的话怎么改?

short s1= 1; 
s1 = s1 + 1;

有错误。short类型在进行运算时会自动提升为int类型,也就是说s1+1 的运算结果是int类型,而s1是short类型,此时编译器会报错。正确写法:

short s1= 1; 
s1 += 1; 

+=操作符会对右边的表达式结果强转匹配左边的数据类型,所以没错。

try catch finally,try里有return,finally还执行么?

执行,并且finally的执行早于try里面的return。

  1. 不管有木有出现异常,finally块中代码都会执行;
  2. 当try和catch中有return时,finally仍然会执行;
  3. finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;
  4. finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。

Java 序列化中如果有些字段不想进行序列化,怎么办?

对于不想进行序列化的变量,使用 transient 关键字修饰。

transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。

在Java中,构造器不能被重写(override)。构造器是用于创建对象的特殊方法,它们不是普通的类方法,因此不遵循普通方法的继承和重写规则。

在Java中,子类可以调用父类的构造器,但不能覆盖(override)它。

局部内部类和匿名内部类访问局部变量的时候,为什么变量必须 要加上final?

因为生命周期不一致, 局部变量直接存储在 栈中,当方法执行结束后,非final的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,如果局部内部类要调用局部变量时,就会出错。加了final, 可以确保局部内部类使用的变量与外层的局部变量区分开,解决了这个问题。

字符型常量和字符串常量的区别

  1. 数据类型:
    • 字符型常量(Character Constants):表示单个字符,通常用单引号括起来,例如 'A'
    • 字符串常量(String Constants):表示一个字符序列(多个字符组成的文本),通常用双引号括起来,例如 "Hello, World"
  2. 存储方式:
    • 字符型常量只包含一个字符,通常使用字符编码方式(如ASCII码)存储。
    • 字符串常量是一个字符序列,会存储多个字符,每个字符都会以特定的编码方式存储,通常以字符数组的形式存在。
  3. 可变性:
    • 字符型常量是不可变的,一旦定义就不能被修改。
    • 字符串常量也是不可变的,一旦创建就不能被修改。在许多编程语言中,字符串是不可变的,如果需要修改字符串,通常会创建一个新的字符串。
  4. 使用场景:
    • 字符型常量通常用于表示单个字符,例如字符操作,字符比较等。
    • 字符串常量用于表示文本、消息、文件内容等包含多个字符的信息

什么是字符串常量池?

字符串常量池(String Pool),也称为字符串池或字符串缓存,是一种存储字符串对象的特殊区域或数据结构,通常位于Java堆内存中。字符串常量池的主要目的是节省内存和提高性能,它确保相同的字符串文字(文字常量)在内存中只有一个实例,即使它们被多次引用。

在Java中,字符串是不可变的,这意味着一旦创建了一个字符串,它的内容就不能被修改。由于字符串常量池的存在,当你创建一个新的字符串时,JVM 首先检查字符串常量池,如果池中已经存在相同内容的字符串,那么就不会创建新的对象,而是返回已存在的对象的引用。这个过程称为字符串的驻留(String Interning)。

这个特性有以下好处:

  1. 节省内存:由于相同的字符串只需要在内存中存储一次,因此减少了内存消耗。
  2. 提高性能:比较字符串时可以直接比较引用,而不需要比较字符串内容,这可以提高比较的速度。

示例:

String s1 = "Hello"; // 创建一个字符串,存储在字符串常量池中
String s2 = "Hello"; // 直接引用字符串常量池中的对象,不创建新的对象

System.out.println(s1 == s2); // 输出 true,因为它们引用了同一个字符串对象

需要注意的是,字符串常量池只存储字符串文字(使用双引号括起来的字符串),而不包括通过new操作符创建的字符串对象,这些对象会在堆内存中独立存储,不会加入字符串常量池。要将通过new创建的字符串对象加入字符串常量池,可以使用intern()方法。

String s3 = new String("Hello"); // 创建一个新的字符串对象,存储在堆内存中
String s4 = s3.intern(); // 将s3加入字符串常量池,并返回常量池中的引用

System.out.println(s1 == s4); // 输出 true,因为它们引用了同一个字符串对象(字符串常量池中的对象)

throw 和 throws 的区别是什么?

throw

  • throw 是一个关键字,用于在代码块内部抛出一个异常对象。
  • 它通常用于方法体内,用于手动创建并抛出一个异常,将控制权交给异常处理机制。
  • throw 后面通常跟着一个异常对象,例如:throw new Exception("这是一个异常信息");
  • 当程序执行到 throw 语句时,会立即退出当前代码块,并开始查找适当的异常处理器(catch 块或者方法的 throws 声明)来处理抛出的异常。

throws

  • throws 是方法签名中的一部分,用于声明方法可能抛出的异常类型。
  • 它用于告诉编译器和调用者,该方法可能会抛出指定类型的异常,以便调用者可以选择捕获并处理这些异常。
  • throws 后面通常列出了一个或多个异常类型,用逗号分隔,例如:
public void doSomething() throws IOException, SQLException {
    // 方法体可能抛出 IOException 或 SQLException 异常
}

  • 调用一个声明了 throws 的方法时,调用者必须处理这些异常,要么使用 try-catch 块捕获异常,要么继续将异常传递给上层调用者。

Java 中有哪些常见的关键字?

分类关键字
访问控制privateprotectedpublic
类,方法和变量修饰符abstractclassextendsfinalimplementsinterfacenative
newstaticstrictfpsynchronizedtransientvolatile
程序控制breakcontinuereturndowhileifelse
forinstanceofswitchcasedefault
错误处理trycatchthrowthrowsfinally
包相关importpackage
基本类型booleanbytechardoublefloatintlong
shortnulltruefalse
变量引用superthisvoid
保留字gotoconst

continue、break 和 return 的区别是什么?

continue

  • continue 是用于循环语句中的关键字。
  • 当执行 continue 语句时,它会立即跳过当前循环迭代的剩余部分,并进入下一次循环迭代。
  • 主要用于在循环中根据某些条件跳过某次迭代,但继续执行后续的迭代。

break

  • break 也是用于循环语句中的关键字,也可以用于 switch 语句中。
  • 当执行 break 语句时,它会立即退出当前循环或 switch 语句,继续执行循环或语句之后的代码。
  • 主要用于在满足某些条件时提前结束循环或跳出 switch 语句。

return

  • return 用于方法中,它用于从方法中返回一个值,并终止方法的执行。
  • 当执行 return 语句时,方法的执行立即结束,控制权返回到调用方法的地方,并将指定的值传递给调用者。
  • 主要用于从方法中返回结果或在满足某些条件时提前结束方法的执行。
  • 28
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值