[Java]Java基础面试题(1)

1. Java语言特点

  1. 简单易学;
  2. 面向对象(封装,继承,多态);
  3. 平台无关性( Java 虚拟机实现平台无关性);
  4. 可靠性;
  5. 安全性;
  6. 支持多线程( C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持);
  7. 支持网络编程并且很方便( Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便);

  8. 编译与解释并存;

2. JDK JRE JVM

JDK(Java 开发工具包)是一个软件开发工具包,其中包含了开发和运行 Java 程序所需的一切,包括 Java 编译器、运行环境和库。它被开发人员用来创建Java应用程序。

JRE(Java 运行时环境)是一个软件包,提供了 Java 虚拟机(JVM)和 Java 核心类库。它被用来运行 Java 程序,而不需要进行任何开发。

JVM(Java 虚拟机)是一个在计算机上执行 Java 字节码的虚拟机。它是 Java 平台的核心组件之一,允许 Java 代码在不同的计算机和操作系统上运行。

3. Oracle JDK和Open JDK

Oracle JDK 和 Open JDK 都是 Java 开发工具包,它们都提供了 Java 编译器、运行环境和库。两者的主要区别在于其来源和许可证。

Oracle JDK 是由 Oracle 公司提供的 Java 开发工具包,它是 Java 的官方实现之一。Oracle JDK 中包含了一些商业特性和工具,需要付费许可证才能使用。而且 Oracle JDK 发布的版本更新不如 Open JDK 频繁。

Open JDK 是一个由社区维护和开发的 Java 开发工具包。它是 Java 的开源实现之一,完全免费,并且它的版本更新比 Oracle JDK 更加频繁。Open JDK 中没有商业特性和工具,因此它在某些方面可能不如 Oracle JDK 强大。

总之,如果您只需要使用 Java 的基本功能,或者希望免费使用 Java 开发工具包,那么 Open JDK 是一个很好的选择。如果您需要一些商业特性或工具,并且愿意为此付费,则可以考虑使用 Oracle JDK。

4. 什么是Java程序的主类应用程序

在 Java 程序中,主类应用程序是程序的入口点,也就是 Java 虚拟机(JVM)开始执行程序的地方。主类应用程序必须包含一个特定的方法,即 public static void main(String[] args) 方法,它是程序的起点,当 JVM 启动时,会自动调用该方法。

在编写 Java 应用程序时,必须指定一个主类,这个类包含了 main 方法。在运行 Java 应用程序时,使用 java 命令启动 JVM,并指定要执行的主类。例如,假设我们有一个名为 MyProgram 的 Java 应用程序,它的主类为 MainClass,则可以使用以下命令来运行它:

shell java MainClass

这将启动 JVM 并执行 MainClass 类中的 main 方法。在 main 方法中,我们可以编写任何需要执行的代码,包括调用其他类的方法,处理输入和输出等等。因此,主类应用程序是编写 Java 应用程序的基础。

Java 应用程序和小程序 Applet 之间有何区别?

Java 应用程序和小程序 Applet 都是 Java 程序,但是它们有几个关键的区别。

Java 应用程序是一种独立的程序,可以在计算机上直接运行。它通常使用图形用户界面(GUI)来与用户交互,并使用标准输入和输出来与操作系统交互。Java 应用程序通常是通过在命令行上运行 Java 解释器来执行的,也可以通过打包为可执行 JAR 文件来进行分发和部署。

小程序 Applet 是一种嵌入在网页中的小型 Java 应用程序,它运行在客户端的浏览器中。Applet 通常使用用户界面控件来与用户交互,并可以与网页内容进行交互。Applet 运行在一个沙箱环境中,受到安全限制,不能访问计算机的文件系统或网络端口,只能通过网络与服务器进行通信。

因此,Java 应用程序和小程序 Applet 的主要区别在于它们的部署方式和运行环境。Java 应用程序是独立的程序,需要在计算机上直接安装和运行,而 Applet 是嵌入在网页中的小型程序,通过浏览器运行。同时,由于安全限制,Applet 功能比 Java 应用程序受到了一些限制。

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

  1. 形式上:字符常量是单引号引起的一个字符,字符串常量是双引号引起的若干个字符。
  2. 含义上:字符常量相当于一个整形值(ASCII 值),可以参加表达式运算,字符串常量代表一个地址值(该字符串在内存中存放位置)。
  3. 占内存大小:字符常量只占 2 个字节 字符串常量占若干个字节(至少一个字符结束标志)(注意: char Java 中占两个字节)。

6. 构造方法是否可被重写

父类的私有属性和构造方法并不能被继承,所以构造方法也就不能被重写,但是可以重载,所以你可以看到一个类中有多个构造函数的情况。

7. 重载和重写区别

重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。

重写: 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。

8. String、StringBuffer、StringBuilder

说说 String、StringBuffer、StringBuilder 三者的区别?

  1. 可变性:

简单的来说:String 类中使用 final 关键字字符数组保存字符串,private final char[] value,所以 String 对象是不可变的。而 StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串 char[]value 但是没有用 final 关键字修饰,所以这两种对象都是可变的。

StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的。

```java abstract class AbstractStringBuilder implements Appendable, CharSequence { /** * The value is used for character storage. */ char[] value;

/**
 * The count is the number of characters used.
 */
int count;

/**
 * This no-arg constructor is necessary for serialization of subclasses.
 */
AbstractStringBuilder() {
}

/**
 * Creates an AbstractStringBuilder of the specified capacity.
 */
AbstractStringBuilder(int capacity) {
    value = new char[capacity];
}
// ...

} ```

  1. 线程安全性:

String 中的对象是不可变的,也就可以理解为常量,所以是线程安全的。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。

```java public final class StringBuffer extends AbstractStringBuilder implements Serializable, CharSequence {

/**
 * A cache of the last value returned by toString. Cleared
 * whenever the StringBuffer is modified.
 */
private transient char[] toStringCache;

/** use serialVersionUID from JDK 1.0.2 for interoperability */
static final long serialVersionUID = 3388685877147921107L;

/**
 * Constructs a string buffer with no characters in it and an
 * initial capacity of 16 characters.
 */
public StringBuffer() {
    super(16);
}

/**
 * Constructs a string buffer with no characters in it and
 * the specified initial capacity.
 *
 * @param      capacity  the initial capacity.
 * @exception  NegativeArraySizeException  if the {@code capacity}
 *               argument is less than {@code 0}.
 */
public StringBuffer(int capacity) {
    super(capacity);
}

/**
 * Constructs a string buffer initialized to the contents of the
 * specified string. The initial capacity of the string buffer is
 * {@code 16} plus the length of the string argument.
 *
 * @param   str   the initial contents of the buffer.
 */
public StringBuffer(String str) {
    super(str.length() + 16);
    append(str);
}

/**
 * Constructs a string buffer that contains the same characters
 * as the specified {@code CharSequence}. The initial capacity of
 * the string buffer is {@code 16} plus the length of the
 * {@code CharSequence} argument.
 * <p>
 * If the length of the specified {@code CharSequence} is
 * less than or equal to zero, then an empty buffer of capacity
 * {@code 16} is returned.
 *
 * @param      seq   the sequence to copy.
 * @since 1.5
 */
public StringBuffer(CharSequence seq) {
    this(seq.length() + 16);
    append(seq);
}

@Override
public synchronized int length() {
    return count;
}

@Override
public synchronized int capacity() {
    return value.length;
}
// ...

} ```

  1. 性能:

每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuilder 和 StringBuffer 每次都会对可变字符串对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10% ~ 15% 左右的性能提升,但却要冒多线程不安全的风险。

  1. 总结:
  • 操作少量的数据:使用 String。
  • 单线程操作字符串缓冲区下操作大量数据:使用 StringBuilder。
  • 多线程操作字符串缓冲区下操作大量数据:StringBuffer。

9. 自动装箱和拆箱

Java 的自动装箱拆箱是指基本数据类型和对应的包装类之间的转换。

Java 中的基本数据类型包括 boolean、byte、char、short、int、long、float 和 double。这些基本数据类型没有方法或属性,不能像对象一样进行操作。为了让基本数据类型也能够具备对象的一些特性,Java 提供了对应的包装类,分别为 Boolean、Byte、Character、Short、Integer、Long、Float 和 Double。这些包装类都是对象,可以调用方法和属性,具有更多的操作和功能。

在 Java 中,自动装箱和拆箱是指自动将基本数据类型转换为对应的包装类对象,或者将包装类对象转换为基本数据类型,使得程序员能够更方便地在基本数据类型和包装类之间进行转换。例如,下面的代码演示了自动装箱和拆箱的用法:

java int i = 10; Integer integer = i; // 自动装箱,将基本数据类型 int 转换为 Integer 对象 int j = integer; // 自动拆箱,将 Integer 对象转换为基本数据类型 int

在这个例子中,将基本数据类型 int 赋值给 Integer 对象时,自动进行了装箱操作。同样地,将 Integer 对象赋值给基本数据类型 int 时,自动进行了拆箱操作。自动装箱和拆箱使得程序员能够更方便地在基本数据类型和包装类之间进行转换,减少了代码的冗余和复杂度。

10. 静态方法内部调用成员方法

为什么在一个类的静态方法内部调用一个成员方法是非法的?

在 Java 中,在一个类的静态方法内部调用一个成员方法是非法的,因为静态方法和成员方法有本质的区别。

静态方法属于类,它不依赖于类的实例,可以在没有创建类的实例的情况下被调用。在静态方法内部,无法访问类的实例变量或实例方法,因为静态方法不属于任何实例,而是属于整个类的。因此,在静态方法内部调用一个成员方法是非法的,因为成员方法必须依赖于类的实例,需要通过实例变量来访问或修改对象的状态。

另外,静态方法是在类加载时就被加载到内存中的,而成员方法是在类的实例化时才会被加载到内存中。因此,在静态方法内部调用一个成员方法可能会出现空指针异常等错误,因为此时类的实例可能还没有被创建。

总之,在静态方法内部调用一个成员方法是非法的,应该将成员方法改为静态方法或者创建类的实例后再调用成员方法。

11. 空构造方法的作用

在 Java 的类中,定义一个空构造方法但是不做任何事情有什么作用?

在 Java 的类中,定义一个空构造方法但是不做任何事情,通常被称为默认构造方法。如果一个类没有显式地定义构造方法,Java 编译器会自动生成一个默认构造方法,以便创建对象时能够正常地初始化对象的状态。

如果一个类中没有任何构造方法,Java 编译器会默认生成一个无参构造方法,这个构造方法不做任何事情,但是确保对象能够被正常地创建。如果一个类中定义了构造方法,但是没有定义默认构造方法,那么在创建对象时如果没有指定参数,就无法创建对象。

Java 程序在执行子类的构造方法之前,如果没有用 super() 来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。

另外,有些框架或库在使用反射时需要默认构造方法来创建对象。如果一个类没有默认构造方法,那么在使用反射创建对象时可能会出现问题,因此在这种情况下,定义一个空构造方法但是不做任何事情是必要的。

12. java和javax包有什么区别

java 和 javax 包都是 Java 标准库中的包,它们之间的区别在于它们的作用范围和使用场景。

java 包是 Java 标准库的核心包之一,包含了 Java 语言的基础类库和核心 API,例如 java.lang、java.io、java.util、java.net 等。这些类库提供了 Java 语言的基础功能,例如字符串处理、文件操作、集合框架、网络编程等。Java 包是 Java 语言的基础,任何 Java 应用程序都需要使用 Java 包中的类库。

javax 包是 Java 扩展包,它包含了 Java 标准库之外的一些 API,例如 JavaEE、JavaME、JavaCard 等,主要是为了支持 Java 的企业应用、移动应用和智能卡应用等。与 Java 包不同的是,javax 包中的类库不是 Java 语言的基础库,而是针对特定的应用场景进行扩展。通常,javax 包需要单独下载和安装,或者是通过第三方框架或库进行引用和使用。

总之,java 包是 Java 语言的基础类库和核心 API,任何 Java 应用程序都需要使用;javax 包是 Java 扩展包,包含了 Java 标准库之外的一些 API,主要是为了支持特定的应用场景。

13. 抽象类和接口区别

抽象类和接口都是 Java 语言中实现多态和封装的重要手段,它们有以下几个区别:

  1. 实现方式不同:抽象类使用关键字 abstract 来定义,可以包含抽象方法和非抽象方法;接口使用关键字 interface 来定义,只能包含抽象方法和常量。

  2. 方法实现方式不同:抽象类可以包含非抽象方法,这些方法有默认的实现,可以被子类继承和重写;接口只能包含抽象方法,这些方法没有默认的实现,必须由实现接口的类提供具体实现。

  3. 继承关系不同:一个类只能继承一个抽象类,但是可以实现多个接口;接口可以继承多个接口,也可以通过默认方法提供共享实现。

  4. 构造方法不同:抽象类可以定义构造方法,但是不能直接实例化;接口不能定义构造方法,也不能直接实例化。

  5. 访问权限不同:抽象类的成员变量和成员方法可以有 public、protected、default 和 private 四种访问权限;接口的成员变量默认为 public static final,成员方法默认为 public abstract。

  6. 从 Java 8 开始,为了兼容 Lambda 表达式,引入了默认方法,而且接口内部也可以直接声明静态方法,直接通过接口调用。Java 9 开始,接口内部可以定义私有方法,但是只能在默认方法中调用。

综上所述,抽象类更适合表示一种 is-a 的关系,即子类是一种特殊的父类;而接口更适合表示一种 has-a 的关系,即实现类具有某种行为或能力。在使用时,应根据具体的业务场景和设计需求进行选择和使用。

14. 成员变量和局部变量区别

在 Java 中,成员变量和局部变量是两种不同类型的变量,它们有以下几个区别:

  1. 定义位置不同:成员变量定义在类体中,作为类的一部分,可以被类的所有方法访问;局部变量定义在方法体中,作为方法的一部分,只能在该方法内部访问。

  2. 生命周期不同:成员变量的生命周期与对象的生命周期相同,即在对象创建时被初始化,在对象被销毁时被销毁;局部变量的生命周期与方法的生命周期相同,即在方法执行时被初始化,在方法执行结束时被销毁。

  3. 默认值不同:成员变量会自动初始化为默认值,例如整数类型默认值为 0,布尔类型默认值为 false,引用类型默认值为 null;局部变量不会自动初始化,必须显式地赋值后才能使用。

  4. 访问权限不同:成员变量可以有 public、protected、default 和 private 四种访问权限,可以在类的内部和外部进行访问;局部变量只能在方法的内部访问,无需访问修饰符。

  5. 内存分配方式不同:成员变量在对象创建时分配内存,占据对象的一部分空间;局部变量在方法栈中分配内存,不占据对象空间。

综上所述,成员变量和局部变量是两种不同类型的变量,具有不同的定义位置、生命周期、默认值、访问权限和内存分配方式。在使用时,应根据具体的业务需求和设计原则进行选择和使用。

15. ==符号和equals方法区别

==:它的作用是判断基本数据类型的两个值是否相等或者判断两个对象的地址是不是相等,即判断两个对象是不是同一个对象。

equals 方法:它的作用也是判断两个对象是否相等。但它一般有两种使用情况:

  • 情况 1:类没有覆盖 equals 方法,则通过 equals 方法比较两个对象时,等价于通过 == 比较这两个对象,也就是判断内存地址是否相等。
  • 情况 2:类覆盖了 equals 方法。一般,我们都覆盖 equals 方法来判断两个对象的内容相等。若它们的内容相等,则返回 true。

16. hashCode和equals方法

为什么一个类重写 equals 方法的同时也要重写 hashCode 方法?

在 Java 中,equals 方法和 hashCode 方法是一对重要的方法,它们在实现对象的比较和散列表(如 HashMap、HashSet)中起到了重要的作用。当一个类重写 equals 方法时,也应该重写 hashCode 方法,这是因为:

每个对象都应该有一个唯一的哈希码:hashCode 方法返回一个 int 类型的哈希码,用于在散列表中确定对象的存储位置。在散列表中,对象的哈希码用于快速定位存储位置,以提高访问效率。如果 equals 方法被重写,但是 hashCode 方法没有被重写,那么对象的哈希码就是堆内存的十六进制内存地址,这样会导致散列表无法正确地定位对象,而且单纯使用 equals 方法比较两个对象是否相等是十分低效的,因为两个对象 hashCode 不等,一定不相等,那么散列表也就失去了性能优势。

因此,当一个类重写 equals 方法时,也应该重写 hashCode 方法,以确保在使用散列表等需要使用哈希码的场合,能够正确地比较对象的相等性和定位存储位置。另外,在重写 hashCode 方法时,也应该遵循一定的规则,例如同一对象多次调用hashCode方法应该返回相同的值,不同对象的哈希码应该尽可能地不同等等。

hashCode 和 equals 方法规定:

  1. 如果两个对象相等,则 hashCode 一定也是相同的。

  2. 两个对象相等,对两个对象分别调用 equals 方法都返回 true。

  3. 两个对象有相同的 hashCode 值,它们也不一定是相等的。因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖。

  4. hashCode 方法的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode 方法,则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。

17. 为什么Java只有值传递

在 Java 中,只有值传递,这是因为 Java 使用的是基于栈的内存管理模型。在基于栈的内存管理模型中,方法中的参数和局部变量都存储在栈中,它们都是值传递。因此,当一个参数被传递到一个方法中时,实际上传递的是参数的副本,而不是参数本身。这意味着,如果在方法中修改参数的值,不会影响原始值的值。

例如:

```java public void changeValue(int x) { x = x + 1; }

int a = 1; changeValue(a); System.out.println(a); // 输出 1,因为a的值没有被修改 ```

在这个例子中,虽然在 changeValue 方法中修改了 x 的值,但是由于 Java 只有值传递,因此 a 的值没有被修改。

需要注意的是,当一个对象作为参数传递给方法时,传递的是对象的引用(地址),而不是对象本身。因此,当在方法中修改对象的属性时,会影响原始对象的属性,因为它们引用的是同一个对象。

例如:

```java public void changeName(Person p) { p.setName("Alice"); }

Person person = new Person("Bob"); changeName(person); System.out.println(person.getName()); // 输出 "Alice",因为person引用的对象的属性被修改了 ```

在这个例子中,虽然在 changeName 方法中没有修改 p 的引用,但是通过引用修改了对象的属性,因此输出的是修改后的属性值。

总之,Java 只有值传递,但是当对象作为参数传递时,传递的是对象的引用(地址),而不是对象本身,因此对于对象的属性的修改会影响原始对象。

18. 进程、线程、协程

进程、线程、协程都是计算机并发编程中的重要概念。

  • 进程(Process)是操作系统中进行资源分配和调度的基本单位,每个进程都有独立的内存空间和系统资源,是程序的一次执行过程。进程可以包含多个线程。

  • 线程(Thread)是操作系统中进行调度的最小单位,线程是进程中的一个执行单元,一个进程可以包含多个线程,它们共享进程的内存和系统资源,但是有各自的线程栈和寄存器等。线程的创建和销毁比进程要快,而且线程之间的切换也比进程要快,因此在多线程编程中可以提高程序的性能。

  • 协程(Coroutine)是一种用户态的轻量级线程,也称为协作式多任务,不需要操作系统的支持,由程序员自己控制。在协程中,不同任务之间可以通过协程调度器(Coroutine Scheduler)自由切换,协程之间的切换不需要进入内核态,因此比线程的上下文切换更快。协程通常用于 IO 密集型操作或者在单线程环境中进行并发编程。

总之,进程、线程和协程都是计算机并发编程中的重要概念,每个都有各自的优缺点和适用场景。在实际应用中,需要根据具体情况选择合适的并发编程模型。

19. 线程的状态

Java 的线程有哪些状态?

Java 的线程有以下几种状态:

  1. 新建状态(New):当线程对象被创建但还没有调用 start() 方法时,线程处于新建状态。

  2. 运行状态(Runnable):当线程被 start() 方法启动后,线程处于运行状态。在运行状态中,线程可以进入阻塞状态或者就绪状态。

  3. 阻塞状态(Blocked):当线程等待某个条件时,线程进入阻塞状态。比如线程调用了 sleep() 方法、等待某个锁、等待某个输入输出操作完成等情况。

  4. 就绪状态(Ready):当线程等待 CPU 时间片时,线程进入就绪状态,等待系统调度运行。

  5. 等待状态(Wait):当线程等待某个条件时,线程进入等待状态。等待状态和阻塞状态不同之处在于,等待状态需要等待其他线程的通知才能继续运行。

  6. 超时等待状态(Timed Waiting):当线程等待某个条件一定时间后,线程进入超时等待状态。比如线程调用了 sleep() 方法或者等待某个输入输出操作完成,但是等待时间已经超过了指定时间。

  7. 终止状态(Terminated):当线程的 run() 方法执行完毕或者调用了 stop() 方法时,线程进入终止状态。

总之,Java 的线程有多种状态,不同状态之间可以相互转换,线程状态的转换是由 JVM 和操作系统控制的。在编写多线程程序时,需要注意不同状态之间的转换,以及如何正确地使用各种线程同步和互斥机制。

20. final关键字

final 关键字有哪些用法?

  1. 对于一个 final 变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

  2. 当用 final 修饰一个类时,表明这个类不能被继承。final 类中的所有成员方法都会被隐式地指定为 final 方法。

  3. 使用 final 方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的 Java 实现版本中,会将final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的 Java 版本已经不需要使用 final 方法进行这些优化了)。类中所有的 private 方法都隐式地指定为 final。

21. 异常体系结构

Java 的异常体系结构是 Java 语言中用于处理程序运行过程中出现异常情况的一种机制。Java 中的异常分为两类:受检异常(checked exceptions)和非受检异常(unchecked exceptions)。

  1. 受检异常:是指在编译期间就可以被发现的异常,必须在代码中显式地进行处理。受检异常一般表示程序运行时可能发生的一些可预测的异常情况,比如文件不存在、网络连接中断等。受检异常都继承自 Exception 类,一般需要用 try-catch 语句进行捕获和处理。
  2. 非受检异常:是指在编译期间无法被发现的异常,一般是由程序中的逻辑错误或者系统错误引起的。非受检异常都继承自RuntimeException 类,可以用 try-catch 语句进行捕获和处理,但也可以通过在方法签名中声明抛出异常来告诉调用者需要注意处理该异常。

Throwable 类是所有异常类的根类,它有两个子类:Error 和 Exception。Error 表示 Java 虚拟机发生的一些严重错误,一般无法恢复;而 Exception 表示程序运行时可能发生的一些可处理的异常情况。Exception 又有两个子类:RuntimeException 和 IOException,其中 RuntimeException 表示程序中的逻辑错误引起的异常,IOException 表示与输入输出相关的异常。以上是异常体系结构的主要分类,具体使用中可能会根据具体情况自定义异常类。

22. finally代码块

在什么情况下,finally 代码块中不会执行?

在以下几种情况下,finally代码块中的代码不会执行:

  1. 在执行 try 或 catch 块之前,线程被中断(比如调用了 Thread.interrupt() 方法),从而导致 try 或 catch 块无法正常执行完成。
  2. 在执行 try 或 catch 块之前,虚拟机停止运行(比如调用了 System.exit() 方法),从而导致 try 或 catch 块无法正常执行完成。
  3. 在执行 try 或 catch 块期间,发生了无法处理的错误(比如栈溢出、内存溢出等),导致程序异常终止。
  4. 在执行 try 或 catch 块期间,执行了 Runtime.getRuntime().halt() 方法,从而导致程序直接退出,finally 块无法执行。

需要注意的是,尽管 finally 块中的代码可能不会被执行,但是在使用 try-catch-finally 语句时,finally 块中的代码通常用于释放资源或执行一些必要的清理工作,因此在编写代码时应该注意避免出现上述情况,以确保 finally 块中的代码能够得到执行。

23. 序列化忽略字段

Java 如何忽略不想序列化的字段?

在 Java 中,如果不想序列化某些字段,可以使用 transient 关键字来修饰这些字段。transient 关键字用于告诉 Java 虚拟机,在序列化对象时不需要将该字段的值保存到序列化流中,即忽略该字段。

下面是一个示例:

```java import java.io.*;

class Person implements Serializable { private String name; private transient String password;

public Person(String name, String password) {
    this.name = name;
    this.password = password;
}

public String getName() {
    return name;
}

public String getPassword() {
    return password;
}

private void writeObject(ObjectOutputStream out) throws IOException {
    out.defaultWriteObject(); // 调用默认的序列化方法
    // password字段不会被序列化
}

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    in.defaultReadObject(); // 调用默认的反序列化方法
    // password字段在反序列化时不会从流中读取,因此需要手动初始化
    password = "";
}

}

public class SerializationTest { public static void main(String[] args) throws Exception { Person p1 = new Person("Alice", "123456"); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser")); out.writeObject(p1); out.close();

ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser"));
    Person p2 = (Person) in.readObject();
    in.close();

    System.out.println(p2.getName()); // 输出:Alice
    System.out.println(p2.getPassword()); // 输出:(空字符串)
}

} ```

在上面的示例中,Person 类中的 password 字段被使用 transient 关键字修饰,因此在序列化时不会将其保存到序列化流中。在自定义的 writeObject 方法中,只调用了默认的序列化方法,因此 password 字段不会被序列化。在自定义的 readObject 方法中,只调用了默认的反序列化方法,并手动将 password 字段初始化为一个空字符串。

24. 键盘输入一行内容

Java 命令行模式如何使用键盘输入内容?

方法 1:通过 Scanner

java Scanner input = new Scanner(System.in); String s = input.nextLine(); input.close();

方法 2:通过 BufferedReader

java BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); String s = input.readLine();

猜你喜欢

转载自blog.csdn.net/weixin_45254062/article/details/130319167