大家好,我是栗筝i,从 2022 年 10 月份开始,我持续梳理出了全面的 Java 技术栈内容,一方面是对自己学习内容进行整合梳理,另一方面是希望对大家有所帮助,使我们一同进步。得到了很多读者的正面反馈。 而在 2023 年 10 月份开始,我将推出 Java 面试题/知识点系列内容,期望对大家有所助益,让我们一起提升。 本篇是对 Java 基础系列的面试题 / 知识点的总结的下篇
Java基础面试题问题(下篇)
解答:面向对象编程(Object-Oriented Programming,简称 OOP)是一种编程范式,它使用 “对象” 来设计软件和创建可重用的代码。
在 OOP 中,每个对象都是一个特定类的实例。类定义了对象的属性(也称为数据成员或字段)和方法(也称为成员函数或行为)。对象的属性是用来存储数据的,而方法则是用来执行任务的。
OOP 主要有以下四个基本特性:
面向对象编程的主要目标是提高软件的可重用性、灵活性和可维护性。
解答:在面向对象编程中,类和对象是核心概念。
简单来说,类是抽象的,它定义了一类事物的通用特性;对象是具体的,它是类的一个实例,具有类定义的结构和行为。
解答:封装、继承和多态是面向对象编程的三大基本特性。
这三个特性是面向对象编程的基础,它们使得我们可以更好地组织和管理代码,提高代码的可读性和可维护性。
解答:接口(Interface)和抽象类(Abstract Class)都是面向对象编程中的高级特性,它们都不能直接实例化,需要通过子类或实现类来实例化。
接口和抽象类的主要区别如下:
总的来说,接口更多地被用来定义行为(即方法),而抽象类既可以定义行为,也可以定义状态(即属性)。在设计类的层次结构时,我们通常会使用抽象类,而在定义一组相关的行为时,我们通常会使用接口。
解答:构造函数是一种特殊的方法,用于初始化新创建的对象。在 Java 中,构造函数的名称必须与类名相同,并且没有返回类型。
构造函数与普通方法的主要区别如下:
例如,以下是一个简单的类,其中包含一个构造函数和一个普通方法:
public class MyClass {
private int value;
// 构造函数
public MyClass(int value) {
this.value = value;
}
// 普通方法
public void displayValue() {
System.out.println("Value: " + value);
}
}在这个例子中,MyClass 的构造函数接受一个参数 value,并将其赋值给对象的 value 属性。displayValue 是一个普通方法,用于打印对象的 value 属性。
解答:方法重载和方法重写是 Java 中两种重要的特性。
方法重载和方法重写的主要区别如下:
总的来说,方法重载是静态的,它在编译时就已经确定了具体调用哪个方法;而方法重写是动态的,它在运行时才确定具体调用哪个方法。
解答:static 是 Java 中的一个关键字,它可以用来修饰类的成员(成员变量和成员方法),也可以用来创建静态代码块。
static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。 静态变量也被称为类变量。局部变量不能被声明为 static 变量。static 关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据,然后计算这些数据。static 关键字还可以形成静态代码块以优化程序性能。static 代码块在类加载的时候就运行了,而且只运行一次,同时运行时机是在构造函数之前。总的来说,static 关键字主要有以下几个作用:
解答:在 Java 中,final 是一个关键字,它可以用来修饰类、方法和变量。
final 类:如果一个类被声明为 final,那么它不能被继承。这意味着没有其他类可以是这个类的子类。final 类通常表示最终的、不可改变的实体,例如 String 类和 Integer 类都是 final 类。
final 方法:如果一个方法被声明为 final,那么它不能被子类重写。但是,子类可以继承 final 方法。
final 变量:如果一个变量被声明为 final,那么它的值就不能被修改,它就成为了一个常量。我们必须在声明 final 变量时或在构造函数中初始化它。
final 关键字提供了一种方式,可以明确表示某个实体不应该被改变。这对于创建不可变的对象和防止意外修改非常有用。
解答:this 和 super 是 Java 中的两个特殊关键字,它们在处理类和对象时非常有用。
this 关键字:this 是一个引用变量,它指向当前对象。在实例方法或构造函数中,this 通常用于引用当前对象的变量或方法。当类的成员变量与局部变量重名时,我们可以使用 this 来区分它们。此外,this 还可以用于在一个构造函数中调用另一个构造函数。
super 关键字:super 是一个引用变量,它指向当前对象的父类。我们可以使用 super 来访问父类的变量和方法。当子类需要调用父类的构造函数或者需要访问父类的方法时,我们可以使用 super。此外,如果子类重写了父类的方法,我们也可以通过 super 来调用父类的原始方法。
总的来说,this 和 super 都是用于在类的内部引用对象自身或其父类的特殊关键字。
解答:内部类,也称为嵌套类,是定义在另一个类中的类。根据内部类的位置和特性,我们可以将内部类分为四种:成员内部类、静态内部类、方法内部类和匿名内部类。
内部类有以下几个主要用途:
总的来说,内部类是一种高级特性,它可以使我们的代码更加整洁、灵活和易于维护。
解答:匿名内部类是一种没有名字的内部类,它通常用于只需要使用一次的场合。
匿名内部类通常用于以下两种类型的场合:
匿名内部类的语法格式如下:
new 父类名或接口名() {
// 方法重写
@Override
public void method() {
// 执行语句
}
}匿名内部类是一种简洁的语法,它可以让我们的代码更加简洁和易于阅读。但是,由于匿名内部类没有名字,所以它只能在定义的地方使用,不能在其他地方引用,这限制了它的使用范围。
解答:访问修饰符是 Java 中的关键字,用于设置类、方法和变量的访问权限。Java 提供了四种访问修饰符:public、private、protected 和默认(没有关键字,也称为 package-private)。
public:被 public 修饰的类、方法或变量可以在任何地方被访问。
private:被 private 修饰的类(只有内部类可以声明为 private)、方法或变量只能在其定义的类中被访问。
protected:被 protected 修饰的类(只有内部类可以声明为 protected)、方法或变量可以在同一个包中的任何类以及其他包中的子类中被访问。
访问修饰符是面向对象编程的重要特性,它们可以保护对象的状态,防止外部直接访问对象的内部数据,从而提高代码的安全性和可维护性。
解答:Java 中的异常机制是一种用于处理程序运行时可能出现的错误情况的机制。在 Java 中,异常是通过使用 try、catch 和 finally 关键字以及 throw 和 throws 语句来处理的。
当在代码中发生异常时,会创建一个异常对象,这个对象包含了关于异常的详细信息(例如异常类型和发生异常的地方)。然后,这个异常对象会被抛出,运行时系统会寻找合适的代码来处理这个异常。
Java 的异常可以分为两大类:Exception 和 Error。
Exception:这是程序可以处理的异常。它分为两种类型:检查型异常(Checked Exception)和运行时异常(Runtime Exception)。检查型异常是指编译器要求我们必须处理的异常,例如 IOException。运行时异常是编译器不要求我们处理的异常,例如 NullPointerException。
Error:这是程序通常无法处理的严重问题,如 OutOfMemoryError。这些问题在通常的情况下,程序无法恢复和处理。
总的来说,Java 的异常处理机制提供了一种结构化和易于管理的方式,用于处理程序运行时的错误情况。
解答:Java 中的异常主要分为两大类:Checked Exception 和 Unchecked Exception。
Checked Exception:这种类型的异常在编译时期就会被检查,也就是说,如果在代码中可能抛出的异常没有被捕获或者抛出,那么编译器将会报错。这种类型的异常通常是由外部错误引起的,比如文件不存在(FileNotFoundException)、网络连接失败(IOException)等,这些异常都需要程序员显式地进行处理,否则程序无法编译通过。
Unchecked Exception:这种类型的异常在编译时期不会被检查,也就是说,即使代码中可能抛出的异常没有被捕获或者抛出,编译器也不会报错。Unchecked Exception 又可以分为两种:Runtime Exception 和 Error。
Runtime Exception:这种异常通常是由程序逻辑错误引起的,比如空指针访问(NullPointerException)、下标越界(IndexOutOfBoundsException)等,这些异常是可以通过改进程序来避免的。
Error:这种异常通常是由严重的系统错误或者虚拟机错误引起的,比如内存溢出(OutOfMemoryError)、栈溢出(StackOverflowError)等,这些异常是程序无法处理的。
总的来说,Java 中的异常种类繁多,不同种类的异常需要采取不同的处理方式,理解这些异常的特性和分类,对于编写健壮的代码非常重要。
解答:Java 的异常层次结构主要由 java.lang.Throwable 类及其子类构成。Throwable 类是所有异常和错误的超类。它有两个主要的子类:Error 和 Exception。
Error:Error 类及其子类表示 Java 运行时系统的内部错误和资源耗尽错误。应用程序通常不会抛出这类异常,也不会去尝试捕获它。例如,OutOfMemoryError、StackOverflowError 等。
Exception:Exception 类及其子类表示程序可能会遇到的问题,需要程序员进行处理。Exception 又可以分为两类:Checked Exception 和 Unchecked Exception。
Checked Exception:这些异常在编译时会被检查,必须被显式捕获或者抛出。例如,IOException、SQLException 等。
Unchecked Exception:这些异常在编译时不会被检查,不需要显式捕获或者抛出。Unchecked Exception 主要包括 RuntimeException 及其子类,例如,NullPointerException、IndexOutOfBoundsException 等。
这种层次结构使得我们可以通过捕获异常的超类来捕获一类异常,也可以通过捕获具体的异常类来精确处理某个异常。
解答:throw 和 throws 是 Java 中用于处理异常的两个关键字,它们的用途和使用方式有所不同。
throw:throw 关键字用于在代码中显式地抛出一个异常。我们可以使用 throw 关键字抛出一个具体的异常对象,这个异常对象必须是 Throwable 类或其子类的实例。throw 语句后面必须立即跟着一个异常对象。
throw new Exception("This is an exception");throws:throws 关键字用于声明一个方法可能会抛出的异常。在方法签名的末尾使用 throws 关键字,后面跟着可能会抛出的异常类型。一个方法可以声明抛出多种类型的异常,多个异常类型之间用逗号分隔。
public void readFile() throws IOException, FileNotFoundException {
// method body
}总的来说,throw 是在代码中抛出一个具体的异常,而 throws 是在声明一个方法时,指明该方法可能会抛出的异常类型。
解答:在 Java 中,我们可以通过继承 Exception 类或其子类来自定义异常。以下是创建自定义异常的基本步骤:
Exception 类或其子类。如果这个异常需要被显式捕获,那么应该继承 Exception 类;如果这个异常是运行时异常,那么应该继承 RuntimeException 类。
String 参数的构造函数,这个 String 参数表示异常的详细信息。
以下是一个自定义异常的例子:
public class MyException extends Exception {
public MyException() {
super();
}
public MyException(String message) {
super(message);
}
}在这个例子中,我们创建了一个名为 MyException 的自定义异常类,它继承了 Exception 类,并提供了两个构造函数。当我们需要抛出这个异常时,可以使用 throw 关键字,如:throw new MyException("This is my exception");。
解答:try、catch 和 finally 是 Java 中用于处理异常的关键字。
try:try 块用于包含可能会抛出异常的代码。如果 try 块中的代码抛出了异常,那么 try 块后面的代码将不会被执行,程序会立即跳转到对应的 catch 块。
catch:catch 块用于捕获和处理异常。每个 try 块后面可以跟随一个或多个 catch 块。如果 try 块中的代码抛出了异常,那么程序会查找第一个能处理这种类型异常的 catch 块,然后执行这个 catch 块中的代码。
finally:finally 块包含的代码总是会被执行,无论 try 块中是否抛出了异常,无论 catch 块是否执行。finally 块通常用于放置清理代码,比如关闭文件、释放资源等。
解答:如果 try 块中有 return 语句,那么 finally 块的代码仍然会被执行。这是因为 finally 块的代码总是在 try 或 catch 块中的 return 语句之前执行。但是,如果 finally 块中也有 return 语句,那么这个 return 语句会覆盖 try 或 catch 块中的 return 语句,方法会返回 finally 块中的 return 语句的值。
解答:反射是 Java 提供的一种强大的工具,它允许运行中的 Java 程序对自身进行自我检查,并且可以操作类、方法、属性等元素。反射机制主要提供了以下功能:
反射的主要用途:
虽然反射非常强大,但是反射操作会比非反射操作慢很多,所以我们应该在必要的时候才使用反射。
Java 的反射机制是基于 Java 虚拟机(JVM)中的类信息(Class Information)实现的。
在 Java 中,当一个类被加载到 JVM 中时,JVM 会为这个类生成一个 Class 对象。这个 Class 对象包含了类的所有信息,包括类的名称、包、父类、接口、构造器、方法、字段等。这些信息在类被加载时从类的字节码文件中提取出来,并保存在 Class 对象中。
当我们使用反射去获取一个类的信息或操作一个类时,实际上是通过操作这个类对应的 Class 对象来实现的。例如,我们可以通过 Class 对象的 getMethod 方法获取类的方法,通过 newInstance 方法创建类的实例,通过 getField 方法获取类的字段等。
因此,Java 的反射机制的实现原理就是通过操作 Class 对象来操作类的信息。这也是为什么我们在使用反射时,首先需要获取类的 Class 对象。
解答:Java 反射的实现主要涉及 java.lang 和 java.lang.reflect 这两个包中的类。以下是一些主要的类及其作用:
java.lang.Class:这是反射的核心类,它代表了正在运行的 Java 应用程序中的类和接口。我们可以通过 Class 对象获取类的名称、父类、接口、构造器、方法、字段等信息,也可以通过 Class 对象创建类的实例。
java.lang.reflect.Constructor:这个类代表类的构造器。我们可以通过 Constructor 对象获取构造器的参数类型,也可以通过 Constructor 对象创建类的实例。
java.lang.reflect.Method:这个类代表类的方法。我们可以通过 Method 对象获取方法的名称、返回类型、参数类型等信息,也可以通过 Method 对象调用方法。
java.lang.reflect.Field:这个类代表类的字段。我们可以通过 Field 对象获取字段的名称、类型、修饰符等信息,也可以通过 Field 对象获取和设置字段的值。
java.lang.reflect.Modifier:这个类代表类、方法、字段的修饰符。我们可以通过 Modifier 类获取修饰符的字符串表示,也可以判断修饰符是否包含某个关键字(如 public、static 等)。
以上这些类提供了丰富的方法,使得我们可以通过反射获取和操作类的几乎所有信息。
解答:在 Java 中,我们可以通过 Class 类的 newInstance 方法或 Constructor 类的 newInstance 方法来通过反射创建对象。
以下是两种方法的示例:
Class 类的 newInstance 方法:try {
Class<?> cls = Class.forName("java.lang.String");
String str = (String) cls.newInstance();
} catch (Exception e) {
e.printStackTrace();
}在这个例子中,我们首先通过 Class.forName 方法获取了 String 类的 Class 对象,然后通过 Class 对象的 newInstance 方法创建了 String 类的实例。
Constructor 类的 newInstance 方法:try {
Class<?> cls = Class.forName("java.lang.String");
Constructor<?> constructor = cls.getConstructor(String.class);
String str = (String) constructor.newInstance("Hello, World!");
} catch (Exception e) {
e.printStackTrace();
}在这个例子中,我们首先获取了 String 类的 Class 对象,然后通过 Class 对象的 getConstructor 方法获取了 String 类的构造器,最后通过 Constructor 对象的 newInstance 方法创建了 String 类的实例。
需要注意的是,这两种方法都可能抛出异常,所以我们需要捕获或抛出这些异常。
解答:Java 反射创建对象和使用 new 关键字创建对象都可以用来实例化类,但是它们之间存在一些重要的区别:
new 关键字创建对象时,我们在编译时就知道要创建的类的类型。new 关键字创建对象的性能要比使用反射创建对象的性能高。这是因为反射操作需要在运行时解析类的信息,这会消耗更多的 CPU 和内存资源。new 关键字创建对象时,我们可以直接访问类的公有成员,但不能访问类的私有成员。new 关键字创建对象,因为这样更简单、更高效。解答:Spring 框架广泛地使用了 Java 的反射机制,主要用于以下几个方面:
Spring 通过反射,使得我们只需要进行简单的配置,就可以实现复杂的功能,大大提高了开发效率。