15道大厂Java高频面试题(一)
15道大厂Java高频面试题(一)
😎 作者介绍:我是程序员洲洲,一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主。
🤓 同时欢迎大家关注其他专栏,我将分享Web前后端开发、人工智能、机器学习、深度学习从0到1系列文章。
🌼 同时洲洲已经建立了程序员技术交流群,如果您感兴趣,可以私信我加入社群,可以直接vx联系(文末有名片)
🖥 随时欢迎您跟我沟通,一起交流,一起成长、进步!
给大家推荐一个非常好用的求职招聘小程序,秋招、春招、国企、银行都能用:万码优才。
本文目录
1、JVM、JRE、JDK之间的关系
- JVM(Java Virtual Machine)
- 定义:JVM 是 Java 虚拟机,它是 Java 程序能够在不同平台上运行的基础。JVM 的主要功能是执行 Java 字节码,将 Java 字节码转换成可以在具体操作系统上执行的机器码。
- 作用:
- 跨平台性:通过 JVM,Java 程序实现了“一次编写,到处运行”的特性。
- 内存管理:JVM 负责垃圾回收、堆栈管理等内存操作。
- 安全性:JVM 提供沙箱机制,防止恶意代码的运行。
- JVM 是独立于操作系统的,每种操作系统有对应的 JVM 实现。
-
JRE(Java Runtime Environment)
- 定义:JRE 是 Java 运行时环境,它包括了 JVM 和运行 Java 程序所需要的库和文件。JRE 提供了一个支持 Java 程序运行的环境。
- 组成:
- JVM:用于执行 Java 字节码。
- 类库:包含了 Java 程序运行所需要的类和 API,如 java.lang、java.util 等。
- 其他支持文件:如配置文件、支持 Java 程序运行的其他资源等。
- 作用:JRE 是 Java 程序运行时所需要的环境,用户可以通过 JRE 执行 Java 应用程序,但不能用于开发。
-
JDK(Java Development Kit)
- 定义:JDK 是 Java 开发工具包,是开发 Java 应用程序所必需的工具集。它包含了 JRE 和一些用于开发的工具,如编译器(javac)、调试器(jdb)、文档生成器等。
- 组成:
- JRE:包括了 JVM 和 Java 类库,提供 Java 程序的运行环境。
- 开发工具:包括 Java 编译器、调试器、打包工具等,用于开发、测试和部署 Java 应用程序。
- 其他工具:如 Java 文档工具、JVM 调试工具等。
- 作用:JDK 是用于 Java 开发的完整环境,包括了运行和开发所需要的全部工具。只有安装 JDK,才能进行 Java 编程和开发。
总结它们的关系:
- JVM 是 Java 程序运行的核心组件,负责执行 Java 字节码。
- JRE 是提供 Java 程序运行所需环境的套件,其中包含了 JVM 和类库等。
- JDK 是用于开发 Java 程序的完整工具包,包含了 JRE 和开发所需的其他工具。
因此,JDK 包含 JRE,JRE 包含 JVM,JVM 则负责执行 Java 字节码,确保 Java 程序能够跨平台运行。
2、Java有⼏种数据类型
Java 中有 8 种基本数据类型:
- byte:8 位整数,范围 -128 到 127。
- short:16 位整数,范围 -32,768 到 32,767。
- int:32 位整数,范围 -2^31 到 2^31-1。
- long:64 位整数,范围 -2^63 到 2^63-1。
- float:32 位浮点数,符合 IEEE 754 标准。
- double:64 位浮点数,符合 IEEE 754 标准。
- char:16 位字符,表示单个 Unicode 字符。
- boolean:表示
true
或false
。
此外,Java 还有引用数据类型,如数组、类、接口等。
3、Java基础数据类型的转换⽅式
Java 中基本数据类型的转换分为自动类型转换和强制类型转换。以下是它们的详细说明:
-
自动类型转换(隐式转换)
自动类型转换发生在赋值或表达式中,当源数据类型的范围可以容纳目标数据类型的值时,Java 会自动执行转换。例如,从小范围类型转换到大范围类型时,会自动进行转换。- 规则:小范围类型转换到大范围类型。
byte
→short
→int
→long
→float
→double
char
→int
→long
→float
→double
- 示例:
int i = 10; long l = i; // 自动转换为 long 类型
- 规则:小范围类型转换到大范围类型。
-
强制类型转换(显式转换)
强制类型转换发生在从大范围类型转换到小范围类型时,这时必须显式地进行转换,因为这种转换可能会丢失数据或出现精度问题。- 规则:大范围类型转换到小范围类型需要强制转换。
double
→float
→long
→int
→short
→byte
- 示例:
double d = 10.5; int i = (int) d; // 强制转换为 int 类型,结果是 10,丢失小数部分
- 规则:大范围类型转换到小范围类型需要强制转换。
-
包装类与基本类型的转换
Java 提供了自动装箱和拆箱的功能。自动装箱是指将基本数据类型自动转换为对应的包装类,自动拆箱则是将包装类转换为对应的基本数据类型。- 自动装箱:将基本数据类型自动转换为对应的包装类。
int i = 10; Integer integer = i; // 自动装箱
- 自动拆箱:将包装类转换为对应的基本数据类型。
Integer integer = 10; int i = integer; // 自动拆箱
- 自动装箱:将基本数据类型自动转换为对应的包装类。
以上就是 Java 基本数据类型转换的几种方式。
4、基本类型和包装类型的区别
Java 中的基本类型和包装类型有以下区别:
-
存储方式
- 基本类型直接存储值,如
int
存储整数值。 - 包装类型是基本类型的对象包装,存储的是对基本类型值的引用。比如,
Integer
存储的是一个int
类型的值。
- 基本类型直接存储值,如
-
内存占用
- 基本类型占用固定的内存空间,例如:
int
占用 4 字节。 - 包装类型则由于是对象类型,除了存储值外,还需要额外的内存开销用于存储对象的元数据。
- 基本类型占用固定的内存空间,例如:
-
性能
- 基本类型访问和操作速度较快,因为它直接存储值。
- 包装类型操作相对较慢,因为需要通过对象引用访问值,且涉及到装箱和拆箱操作。
-
使用场景
- 基本类型适用于需要高性能和内存优化的场景,特别是在大量数值计算时。
- 包装类型则主要用于对象化的场景,比如在集合类(如
ArrayList
)中,因为集合只能存储对象类型。
-
默认值
- 基本类型有默认值,例如
int
的默认值为 0,boolean
的默认值为false
。 - 包装类型的默认值为
null
,因为它们是对象类型,未初始化时没有值。
- 基本类型有默认值,例如
-
自动装箱与拆箱
- 基本类型与包装类型之间可以自动转换,称为自动装箱(基本类型转包装类型)和自动拆箱(包装类型转基本类型)。
- 例如:
Integer integer = 10; // 自动装箱 int i = integer; // 自动拆箱
基本类型和包装类型各有优缺点,选择时需要根据实际需求权衡性能和功能。
5、什么是⾃动装箱和⾃动拆箱
自动装箱和自动拆箱是 Java 中基本类型与包装类型之间的自动转换机制。
-
自动装箱
自动装箱是指将基本数据类型自动转换为对应的包装类。例如,将int
类型的值赋给Integer
类型的对象时,Java 会自动进行转换。- 示例:
int i = 10; Integer integer = i; // 自动装箱,将基本类型 int 转换为包装类 Integer
- 示例:
-
自动拆箱
自动拆箱是指将包装类自动转换为对应的基本数据类型。例如,将Integer
类型的对象赋给int
类型的变量时,Java 会自动进行转换。- 示例:
Integer integer = 10; int i = integer; // 自动拆箱,将包装类 Integer 转换为基本类型 int
- 示例:
自动装箱和自动拆箱使得 Java 开发更加简洁,程序员无需显式地进行类型转换,编译器会自动处理这些转换。
6、成员变量和局部变量的区别
成员变量和局部变量在 Java 中有以下几个区别:
-
定义位置
- 成员变量定义在类中,但在方法、构造函数或块外面。
- 局部变量定义在方法、构造函数或块内,生命周期仅限于方法执行期间。
-
生命周期
- 成员变量的生命周期与对象的生命周期相同,随着对象的创建而分配内存,随着对象的销毁而释放内存。
- 局部变量的生命周期仅在方法执行期间,方法执行完毕后,局部变量所占内存会被释放。
-
默认值
- 成员变量在未显式初始化时会自动赋予默认值,如
int
类型默认值为0
,boolean
默认值为false
。 - 局部变量如果未初始化,编译时会报错,必须显式初始化后才能使用。
- 成员变量在未显式初始化时会自动赋予默认值,如
-
作用范围
- 成员变量的作用范围是整个类的方法,可以被该类的所有方法访问。
- 局部变量的作用范围仅限于声明它的方法、构造函数或代码块内。
-
访问修饰符
- 成员变量可以使用访问修饰符(如
public
、private
、protected
、default
)来控制访问权限。 - 局部变量不能使用访问修饰符,它们的访问权限仅限于声明它们的代码块。
- 成员变量可以使用访问修饰符(如
-
存储位置
- 成员变量存储在堆内存中(对于实例变量),静态成员变量存储在方法区(静态变量)。
- 局部变量存储在栈内存中,每次方法调用时都会分配新的内存空间。
这些区别影响着变量的生命周期、作用范围以及如何在程序中使用。
7、静态变量是什么?
静态变量是使用 static
关键字定义的变量,属于类而不是类的实例。其主要特点如下:
-
存储位置
- 静态变量存储在方法区(内存的常驻区域),而不是对象的堆内存中。
-
生命周期
- 静态变量的生命周期从类加载到 JVM 中开始,直到程序终止为止。它只会初始化一次,且所有实例共享同一份数据。
-
共享性
- 所有该类的实例共享同一个静态变量,无论创建多少个实例,静态变量始终只有一份内存。
-
访问方式
- 静态变量可以通过类名直接访问,也可以通过对象访问。推荐使用类名来访问静态变量,避免误用。
- 示例:
MyClass.staticVariable = 10; // 使用类名访问 MyClass obj = new MyClass(); obj.staticVariable = 10; // 也可以通过对象访问,但不推荐
-
默认值
- 静态变量如果没有显式初始化,会自动赋予默认值,例如
int
类型的默认值为0
,boolean
为false
。
- 静态变量如果没有显式初始化,会自动赋予默认值,例如
-
访问修饰符
- 静态变量可以使用
public
、private
、protected
或默认访问修饰符来控制其访问权限。
- 静态变量可以使用
-
用途
- 静态变量常用于需要所有实例共享的常量或计数器等场景。例如,可以用来计数创建的对象数目。
静态变量是类的一部分,不依赖于任何对象实例,因此它们对于管理跨多个实例共享的数据非常有用。
8、值传递和引⽤传递的区别
在 Java 中,方法参数的传递有两种方式:值传递和引用传递。它们的区别如下:
-
值传递
- 在值传递中,方法接收到的是实际参数值的副本,任何对参数的修改不会影响原始数据。
- 示例:
public class ValuePass { public static void main(String[] args) { int num = 10; modifyValue(num); System.out.println("num after modifyValue: " + num); // 输出 10 } public static void modifyValue(int num) { num = 20; // 修改的是 num 的副本 } }
- 在上述代码中,虽然
modifyValue
方法内部修改了num
,但原始的num
变量值没有变化,因为num
是以值传递的方式传入的。
-
引用传递
- 在引用传递中,方法接收到的是实际参数的引用(地址),因此方法内部对参数的修改会影响原始数据。
- 示例:
public class ReferencePass { public static void main(String[] args) { MyClass obj = new MyClass(10); modifyReference(obj); System.out.println("obj.value after modifyReference: " + obj.value); // 输出 20 } public static void modifyReference(MyClass obj) { obj.value = 20; // 修改的是 obj 的引用所指向的对象 } } class MyClass { int value; MyClass(int value) { this.value = value; } }
- 在上述代码中,
obj
是通过引用传递到modifyReference
方法的,因此对obj.value
的修改会影响原始对象的值。
-
适用场景
- 值传递适用于基本数据类型(如
int
、float
、char
等),因为它传递的是数据副本。 - 引用传递适用于对象类型(如类实例),因为它传递的是对象的引用(即内存地址)。
- 值传递适用于基本数据类型(如
总结:
- 值传递:传递的是值的副本,方法内修改参数不会影响原始值。
- 引用传递:传递的是对象的引用,方法内修改参数会影响原始对象。
9、⾯向对象和⾯向过程的区别
面向对象和面向过程是两种不同的编程范式。它们的主要区别如下:
-
基本概念
- 面向过程:强调通过函数或过程来操作数据,主要关注过程、步骤和函数。
- 面向对象:强调通过对象和类来组织代码,将数据和操作数据的方法封装在一起。
-
数据和功能的组织方式
- 面向过程:数据和功能是分开组织的,数据由全局变量表示,函数通过参数和返回值操作这些数据。
- 面向对象:数据和功能被封装在对象内部,对象通过方法与外部交互。
-
代码复用
- 面向过程:复用通常通过函数调用来实现,不同的过程可以通过共享全局数据来复用。
- 面向对象:复用主要通过继承和多态来实现,子类可以继承父类的属性和方法,还可以重写方法来定制行为。
-
模块化
- 面向过程:模块化通过函数或过程实现,每个函数完成特定任务。
- 面向对象:模块化通过类和对象实现,每个类表示一个独立的模块,类内部封装了数据和操作。
-
代码示例
-
面向过程:
public class ProceduralExample { public static void main(String[] args) { int a = 10, b = 20; int result = add(a, b); System.out.println("Sum: " + result); } public static int add(int x, int y) { return x + y; } }
在面向过程的例子中,数据(
a
和b
)和操作(add
函数)是分开的,程序执行时通过函数调用进行操作。 -
面向对象:
public class ObjectOrientedExample { public static void main(String[] args) { Calculator calc = new Calculator(); int result = calc.add(10, 20); System.out.println("Sum: " + result); } } class Calculator { public int add(int x, int y) { return x + y; } }
在面向对象的例子中,
Calculator
类封装了操作和数据,add
方法是类的一部分,数据通过对象(calc
)传递。
-
-
灵活性和可维护性
- 面向过程:代码修改和扩展可能会影响整个系统,特别是当系统变得复杂时,管理全局数据和函数之间的依赖关系变得困难。
- 面向对象:通过封装、继承和多态,面向对象的程序设计更易于扩展和维护,每个类和对象独立性强,修改一部分代码通常不会影响其他部分。
总结:
- 面向过程:注重过程,操作函数和全局数据分离,适用于程序简单的情况。
- 面向对象:注重对象,数据和行为封装在对象内,更加注重模块化和复用,适用于复杂系统开发。
10、⾯向对象的三⼤特征
面向对象的三大特征是封装、继承和多态。它们分别代表了对象如何组织数据、如何实现代码复用以及如何实现动态行为。具体解释如下:
-
封装
- 封装是指将数据(属性)和操作数据的方法(函数)绑定在一起,并隐藏内部的实现细节,只暴露必要的接口。
- 封装的核心是通过访问修饰符(如
private
、public
)控制对数据的访问。 - 示例:
在这个例子中,public class Person { private String name; private int age; // 通过 getter 和 setter 方法访问数据 public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { if (age > 0) { this.age = age; } } } public class Test { public static void main(String[] args) { Person p = new Person(); p.setName("Alice"); p.setAge(30); System.out.println("Name: " + p.getName() + ", Age: " + p.getAge()); } }
name
和age
是私有的,只能通过公共的getter
和setter
方法访问,从而实现封装。
-
继承
- 继承是指子类可以继承父类的属性和方法,子类不仅可以使用父类的已有功能,还可以根据需要扩展或重写父类的功能。
- 继承提高了代码的复用性,使得相似类之间可以共享公共代码。
- 示例:
在这个例子中,class Animal { public void makeSound() { System.out.println("Animal makes a sound"); } } class Dog extends Animal { public void makeSound() { System.out.println("Dog barks"); } } public class Test { public static void main(String[] args) { Animal animal = new Dog(); animal.makeSound(); // 输出 Dog barks } }
Dog
类继承自Animal
类,并重写了makeSound
方法。
-
多态
- 多态是指同一个方法或操作在不同的对象上表现出不同的行为。多态有两种形式:方法重载和方法重写。
- 多态使得对象能够动态地决定调用哪个方法,提高了程序的灵活性和可扩展性。
- 示例:
在这个例子中,class Animal { public void makeSound() { System.out.println("Animal makes a sound"); } } class Dog extends Animal { public void makeSound() { System.out.println("Dog barks"); } } class Cat extends Animal { public void makeSound() { System.out.println("Cat meows"); } } public class Test { public static void main(String[] args) { Animal animal1 = new Dog(); Animal animal2 = new Cat(); animal1.makeSound(); // 输出 Dog barks animal2.makeSound(); // 输出 Cat meows } }
animal1
和animal2
都是Animal
类型的引用,但它们实际指向Dog
和Cat
对象,因此调用makeSound
方法时会有不同的输出,这是多态的体现。
总结:
- 封装:通过访问控制,隐藏实现细节,提供对外接口。
- 继承:子类继承父类的属性和方法,增强代码复用性。
- 多态:同一方法在不同对象上表现出不同的行为,提高代码灵活性。
11、说⼀说你对多态的理解
多态是面向对象编程中的一项重要特性,指的是同一个方法或操作在不同的对象上表现出不同的行为。多态使得程序更加灵活和可扩展。具体来说,多态可以分为以下几种类型:
-
方法重写(Override)
- 方法重写是指子类重写父类的方法,在子类中提供新的实现,覆盖父类的方法。这是实现多态的一个重要机制。
- 当父类的引用指向子类对象时,调用方法时会执行子类的版本,而不是父类的版本。
- 示例:
在这个例子中,class Animal { public void sound() { System.out.println("Animal makes a sound"); } } class Dog extends Animal { @Override public void sound() { System.out.println("Dog barks"); } } class Cat extends Animal { @Override public void sound() { System.out.println("Cat meows"); } } public class Test { public static void main(String[] args) { Animal myAnimal = new Animal(); Animal myDog = new Dog(); Animal myCat = new Cat(); myAnimal.sound(); // 输出 "Animal makes a sound" myDog.sound(); // 输出 "Dog barks" myCat.sound(); // 输出 "Cat meows" } }
myDog
和myCat
都是Animal
类型的引用,但它们分别指向Dog
和Cat
对象,因此调用sound()
方法时会表现出不同的行为。
-
方法重载(Overload)
- 方法重载是指同一个类中可以有多个相同名称但参数不同的方法。方法重载不是真正的多态,因为它是在编译时决定调用哪个方法,但它也是一种通过相同方法名实现不同操作的方式。
- 示例:
在这个例子中,class Calculator { public int add(int a, int b) { return a + b; } public double add(double a, double b) { return a + b; } } public class Test { public static void main(String[] args) { Calculator calc = new Calculator(); System.out.println(calc.add(10, 20)); // 输出 30 System.out.println(calc.add(10.5, 20.5)); // 输出 31.0 } }
add
方法根据参数类型的不同,执行不同的操作,这是方法重载。
-
运行时多态和编译时多态
- 编译时多态(方法重载):通过方法的参数类型、数量、顺序不同来实现的多态,调用的哪个方法由编译器在编译时决定。
- 运行时多态(方法重写):通过继承和方法重写来实现的多态,调用的哪个方法由 JVM 在运行时决定。
-
多态的优势
- 多态增强了代码的灵活性和扩展性,使得同一个方法在不同的对象上可以有不同的行为。
- 通过多态,可以通过父类类型的引用来操作子类对象,这样就可以编写更加通用和可扩展的代码。
总结:
- 多态使得程序在运行时更加灵活,不同对象可以表现出不同的行为,增强了代码的复用性和可维护性。
- 多态通过方法重写和方法重载实现,方法重载是编译时多态,而方法重写是运行时多态。
12、接⼝和抽象类的区别
接口和抽象类是 Java 中两种用于实现抽象行为的机制,尽管它们有一些相似之处,但它们也有很多区别。以下是它们的主要区别:
-
定义和继承方式
-
接口:接口是用
interface
关键字定义的,它只能包含抽象方法(JDK 8 以后可以包含默认方法和静态方法)。一个类可以实现多个接口,接口支持多重继承。 -
抽象类:抽象类是用
abstract class
关键字定义的,它可以包含抽象方法和非抽象方法。一个类只能继承一个抽象类,抽象类支持单继承。 -
示例:
// 接口定义 interface Animal { void sound(); } // 抽象类定义 abstract class Animal { abstract void sound(); void sleep() { System.out.println("Sleeping..."); } }
-
-
实现的方式
-
接口:一个类通过
implements
关键字实现接口,并且必须实现接口中的所有抽象方法。 -
抽象类:一个类通过
extends
关键字继承抽象类,并且可以选择实现抽象类中的某些抽象方法,或者留给子类去实现。 -
示例:
// 接口实现 class Dog implements Animal { @Override public void sound() { System.out.println("Bark"); } } // 抽象类继承 class Dog extends Animal { @Override void sound() { System.out.println("Bark"); } }
-
-
构造方法
- 接口:接口不能有构造方法,因为接口不能直接实例化。
- 抽象类:抽象类可以有构造方法,子类通过继承抽象类时可以调用父类的构造方法。
-
字段和成员
-
接口:接口中的字段默认为
public static final
,必须初始化。接口中的方法默认为public abstract
,并且不能包含实现代码(除非是默认方法或静态方法)。 -
抽象类:抽象类可以有实例字段,且这些字段不需要是
final
,可以通过构造方法或方法来修改。 -
示例:
interface Animal { int age = 10; // 接口中的字段默认为 public static final void sound(); } abstract class Animal { int age; // 抽象类中的字段可以是实例字段 abstract void sound(); void sleep() { System.out.println("Sleeping..."); } }
-
-
多重继承
- 接口:一个类可以实现多个接口,接口支持多重继承。
- 抽象类:一个类只能继承一个抽象类,Java 不支持多重继承。
-
使用场景
- 接口:接口适用于需要定义一组行为但不关心具体实现的场景。比如定义 API 接口、回调函数等。
- 抽象类:抽象类适用于有共同基础功能但不同实现的类层次结构中。比如不同类型的动物都可以继承抽象类
Animal
,并实现各自的sound
方法。
总结:
- 接口:适用于行为规范的定义,支持多重继承,不能有构造方法和实例字段。
- 抽象类:适用于定义有部分实现的类,支持单继承,可以有构造方法和实例字段。
13、Java访问权限
在 Java 中,访问权限决定了类、方法、变量等在不同类或包中的可见性。Java 提供了四种访问权限级别:public
、protected
、default
(包私有)和 private
。每种权限的适用范围和访问规则如下:
-
public
public
访问权限表示该成员可以被任何其他类访问,无论该类在同一包中还是在其他包中。- 示例:
public class MyClass { public int value; public void display() { System.out.println("Public method"); } }
-
protected
protected
访问权限表示该成员可以在同一包中的其他类或不同包中的子类中访问。protected
不允许不同包中的非子类访问。- 示例:
public class MyClass { protected int value; protected void display() { System.out.println("Protected method"); } }
-
default(包私有)
- 默认访问权限,也称为包私有访问权限。没有显式声明访问修饰符时,默认为包私有。包私有的成员只能在同一包中的其他类中访问,不能跨包访问。
- 示例:
class MyClass { int value; // 默认访问权限 void display() { // 默认访问权限 System.out.println("Default method"); } }
-
private
private
访问权限表示该成员只能在定义它的类内部访问,其他类不能访问该成员。- 示例:
public class MyClass { private int value; private void display() { System.out.println("Private method"); } }
总结:
- public:成员可以被任何类访问,跨包、跨类都可以访问。
- protected:成员可以被同包中的类以及不同包中的子类访问,不能被其他类直接访问。
- default(包私有):成员只能在同一包中访问,无法跨包访问。
- private:成员只能在定义它的类内部访问,不能被其他类访问。
14、 static和final有什么区别
在 Java 中,static
和 final
都是用于修饰类成员的关键字,它们有不同的用途和特性。具体区别如下:
-
static 关键字
static
用于表示类级别的成员,即该成员属于类本身,而不是类的实例。静态成员可以通过类名访问,不需要创建类的实例。static
可以修饰变量、方法、代码块和嵌套类。静态变量和方法是类级别的,共享给所有对象。- 示例:
class Counter { static int count = 0; // 静态变量 static void increment() { // 静态方法 count++; } } public class Test { public static void main(String[] args) { Counter.increment(); System.out.println(Counter.count); // 输出 1 } }
- 在这个例子中,
count
是静态变量,通过类名Counter
来访问,而无需创建Counter
的实例。
-
final 关键字
final
用于声明常量、方法和类,表示不可修改。- 常量:当
final
用于变量时,表示该变量为常量,初始化后不可修改。 - 方法:当
final
用于方法时,表示该方法不能被子类重写。 - 类:当
final
用于类时,表示该类不能被继承。 - 示例:
class MyClass { final int MAX_VALUE = 100; // 常量 final void display() { // 不能被子类重写 System.out.println("Final method"); } } public class Test { public static void main(String[] args) { MyClass obj = new MyClass(); obj.display(); // 输出 "Final method" } }
- 在这个例子中,
MAX_VALUE
是常量,不能修改;display
方法是final
的,不能被子类重写。
-
常见组合
static final
:常常用于声明常量,表示该常量是类级别的,并且不可修改。- 示例:
class MyClass { static final int MAX_VALUE = 100; // 静态常量 } public class Test { public static void main(String[] args) { System.out.println(MyClass.MAX_VALUE); // 输出 100 } }
- 在这个例子中,
MAX_VALUE
是静态常量,可以通过类名直接访问,并且其值不可改变。
总结:
- static:表示成员属于类本身,而不是实例。静态成员共享给所有类的实例,且可以通过类名访问。
- final:表示常量、不能重写的方法或不能继承的类。
final
用于限制修改,确保稳定性。
15、final、finally、finalize的区别
在 Java 中,final
、finally
和 finalize
看起来类似,但它们各自有不同的含义和用途。具体区别如下:
-
final
final
是一个修饰符,用于声明常量、方法和类。- 常量:当
final
用于变量时,表示该变量的值一旦被赋值后就不可更改。 - 方法:当
final
用于方法时,表示该方法不能被子类重写。 - 类:当
final
用于类时,表示该类不能被继承。 - 示例:
class MyClass { final int MAX_VALUE = 100; // 常量 final void display() { // 不能被子类重写 System.out.println("Final method"); } }
- 在这个例子中,
MAX_VALUE
是常量,且不可修改;display
方法是final
的,不能被子类重写。
-
finally
finally
是用于异常处理中的一个关键字,它定义在try-catch
语句块之后,表示无论是否发生异常,finally
块中的代码都会被执行。finally
用于执行清理操作,如关闭文件流、释放资源等,确保这些操作不受异常影响。- 示例:
try { int result = 10 / 0; } catch (ArithmeticException e) { System.out.println("Error: " + e.getMessage()); } finally { System.out.println("Finally block executed"); }
- 在这个例子中,
finally
块会在try
或catch
块执行后始终执行,确保程序的清理工作。
-
finalize
finalize
是Object
类中的一个方法,它在垃圾回收器准备回收对象时被调用。finalize
方法允许对象在被销毁前执行一些清理操作,但这并不是必须的。finalize
方法已经被标记为过时(deprecated),并且不推荐使用,因为垃圾回收器的行为是非确定性的,不能保证它何时会调用finalize
方法。- 示例:
class MyClass { @Override protected void finalize() throws Throwable { System.out.println("Object is being garbage collected"); } } public class Test { public static void main(String[] args) { MyClass obj = new MyClass(); obj = null; // 使对象变为可回收 System.gc(); // 强制进行垃圾回收 } }
- 在这个例子中,当
obj
被垃圾回收时,finalize
方法会被调用,但不能确定何时调用。
总结:
- final:用于声明常量、不能重写的方法或不能继承的类。
- finally:用于异常处理,确保在异常发生与否的情况下执行的清理代码。
- finalize:
Object
类中的方法,在垃圾回收时调用,但已不推荐使用。
更多推荐
所有评论(0)