1. JVM、JDK、JRE之间的关系
JDK(Java Development Kit),JRE(Java Runtime Environment)和JVM(Java Virtual Machine)是 Java 平台的三个关键组成部分,它们之间的关系如下:
-
JVM(Java虚拟机): JVM 是 Java 平台的核心组件之一。它是一个虚拟计算机,用于执行 Java 字节码(即编译后的 Java 代码)。JVM 负责将字节码翻译成底层操作系统和硬件可以理解的指令。它提供了内存管理、垃圾回收、线程管理等功能,以确保 Java 程序的跨平台性和可移植性。
-
JRE(Java运行时环境): JRE 是运行 Java 应用程序所需的最小环境。它包含了 JVM 以及 Java 标准类库(Java API)和其他运行时所需的文件。JRE 可以用于执行已经编译的 Java 应用程序,但无法进行 Java 开发或编译。如果只需运行 Java 应用程序而不进行开发,通常只需安装 JRE。
-
JDK(Java开发工具包): JDK 是用于 Java 开发的完整工具包。它包含了 JRE,以及开发人员所需的编译器(javac)和其他开发工具(如调试器、构建工具等)。JDK 提供了开发和调试 Java 应用程序所需的一切工具和资源。如果需要进行 Java 应用程序的开发,需要安装 JDK。
2. 单精度和双精度有什么区别?flaot num = 0.1对吗?
单精度(float)使用32位(4字节)内存来存储浮点数,而双精度(double)使用64位(8字节)内存来存储浮点数。由于双精度使用更多的内存空间,它可以表示比单精度更大范围的数值,并且具有更高的精度。具体来说,单精度可以表示大约7位有效数字的浮点数,而双精度可以表示大约15位有效数字的浮点数。
flaot num = 0.1不对,浮点数字面量默认被解释为双精度(double)类型,正确表示为
flaot num = 0.1f
// 或者
float num = 0.1F
3. 属性和变量的区别?
属性(Property)和变量(Variable)是两个相关但不完全相同的概念。属性是面向对象编程中与类或对象关联的特征或状态,具有访问控制和特定的访问方式;变量是通用的数据存储位置,可以在程序中用于存储和操作数据。
4. 基础数据类型与引用数据类型
基础数据类型(Primitive Data Types)和引用数据类型(Reference Data Types)是两种不同的数据类型。
基础数据类型包括以下8种:
- 整数类型:byte、short、int、long
- 浮点类型:float、double
- 字符类型:char
- 布尔类型:boolean
引用数据类型包括以下几种:
- 类(Class)
- 接口(Interface)
- 数组(Array)
区别如下:
-
存储方式:
- 基础数据类型直接存储在栈(Stack)中,它们的值直接存储在变量中。
- 引用数据类型存储在堆(Heap)中,而变量中存储的是对堆中对象的引用(内存地址)。
-
内存管理:
- 基础数据类型由Java虚拟机直接分配和管理内存,它们的内存占用是固定的,不会发生垃圾回收。
- 引用数据类型则需要手动创建和销毁对象,垃圾回收器负责回收无用的对象的内存。
-
默认值:
- 基础数据类型在声明时会有一个默认值,如int默认为0,boolean默认为false。
- 引用数据类型的默认值为null,表示不指向任何对象。
-
传递方式:
- 基础数据类型以值传递方式进行传递。方法的参数接收到的是实际的值的副本,对参数的修改不会影响原始值。
- 引用数据类型以引用传递方式进行传递。方法的参数接收到的是对象引用的副本,对引用的修改会影响原始对象。
-
方法调用:
- 基础数据类型的方法调用是直接操作值的操作。
- 引用数据类型的方法调用是通过对象引用来操作对象的。
5. 什么时候使用静态属性和静态方法?什么是静态代码块?
静态属性(Static Property)和静态方法(Static Method)是在类级别上定义的属性和方法,它们与类本身相关联,而不是与类的实例(对象)相关联。以下是关于何时使用静态属性和静态方法的一些建议:
-
静态属性的使用场景:
- 当某个属性的值对于类的所有实例来说是共享的,并且在整个类的生命周期内保持不变时,可以使用静态属性。例如,一个类可以有一个静态计数器属性,用于统计创建的对象数量。
- 当需要在不创建类的实例的情况下访问属性时,可以使用静态属性。静态属性可以直接通过类名访问,而无需创建对象。
-
静态方法的使用场景:
- 当某个方法不依赖于类的实例状态,只与类的静态属性或其他静态方法相关时,可以使用静态方法。静态方法可以通过类名直接调用,无需实例化对象。
- 当某个方法提供了一些实用功能或服务,与具体的实例无关时,可以使用静态方法。比如,数学类中的数学函数方法,如计算平方根、求绝对值等。
需要注意的是,使用静态属性和静态方法应慎重,因为它们破坏了面向对象的封装性和可测试性。静态属性和静态方法的共享性和全局性可能导致代码的耦合度增加和可维护性降低。因此,应该在确保使用静态的合适性的同时,考虑其潜在的副作用,并根据具体情况进行权衡和设计。
静态代码块 static { } 中的代码会在类的信息加载完成后,自动调用。如调用类中的方法、new一个对象。
6. 构造函数是什么?
构造函数(Constructor)是一种特殊的方法,用于创建对象并初始化对象的成员变量。构造函数具有与类相同的名称,但没有返回类型,甚至没有void
关键字。构造函数在使用new
关键字创建对象时被自动调用,用于初始化对象的状态。
如果在类中没有显式定义构造函数,JVM会自动提供一个无参数的默认构造函数。但如果类中定义了至少一个构造函数,JVM将不会提供默认构造函数。
构造函数也可以使用访问修饰符来控制其可见性,例如public
、private
、protected
等,用于限制构造函数的访问权限。
public class MyClass {
public MyClass() {
// 无参数构造函数
}
public MyClass(int value) {
// 带一个参数的构造函数
}
public MyClass(String name, int age) {
// 带多个参数的构造函数
}
}
7. 调用 new 关键字的过程发生了什么?
当使用new
关键字创建一个对象时,Java虚拟机(JVM)会按照以下步骤执行对象的创建过程:
-
分配内存:首先,JVM会在堆(Heap)内存中为对象分配内存空间。内存的分配是在运行时动态进行的。JVM会根据对象的大小来决定分配的内存空间。
-
初始化零值:在分配内存后,JVM会将分配的内存空间初始化为默认的零值,即将对象的所有成员变量初始化为其对应类型的默认值(例如,整数类型初始化为0,布尔类型初始化为false,引用类型初始化为null等)。
-
执行构造函数:接下来,JVM会根据对象的类型找到相应的构造函数,并执行构造函数中的代码。构造函数负责对对象进行进一步的初始化,可以设置成员变量的初始值,执行必要的操作等。
-
返回对象引用:在构造函数执行完成后,JVM会返回一个指向新创建对象的引用。通过这个引用,我们可以操作和访问对象的成员变量和方法。
需要注意的是,对象的创建过程是在运行时动态发生的,而不是在编译时确定的。因此,每次使用new
关键字创建对象时,都会在堆内存中为对象分配独立的内存空间,并执行相应的初始化和构造过程。每个对象都有自己独立的状态和数据。
另外,Java中的垃圾回收(Garbage Collection)机制会负责回收不再使用的对象的内存空间,以避免内存泄漏和提供自动内存管理。当对象不再被引用时,垃圾回收器会自动回收对象所占用的内存空间,并释放资源。
8. 什么是继承?
继承(Inheritance)是一种面向对象编程的概念,用于创建新的类从现有类继承属性和方法。通过继承,子类可以使用父类已有的功能,并可以在此基础上添加新的功能或修改已有的功能。
在Java中,使用关键字extends
来实现类的继承关系。子类可以继承父类的非私有成员变量和方法,包括字段、方法和构造函数。
Java中的继承是单继承的,一个类只能继承一个父类。但是,Java提供了接口(Interface)来实现多继承的效果,一个类可以实现多个接口。
继承的主要优势包括:
- 代码重用:子类可以继承父类的属性和方法,避免重复编写相同的代码。
- 扩展性:子类可以在父类的基础上添加新的功能或修改已有的功能,以满足特定需求。
- 统一性和一致性:继承创建了一个层次结构,使得类与类之间具有一定的关联性和一致性。
// 父类
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(name + " is eating.");
}
}
// 子类
class Dog extends Animal {
public Dog(String name) {
super(name);
}
public void bark() {
System.out.println(name + " is barking.");
}
}
9. super 和 this
super
和this
都是关键字,在Java中用于访问类的不同成员或调用不同的构造函数。
-
super
关键字:- 在子类中,可以使用
super
关键字来访问父类的成员变量和方法。 - 使用
super
时,可以调用父类的构造函数,以便在子类的构造函数中执行父类的初始化操作。 super
的用法有两种:- 访问父类的成员变量或方法:
super.成员名
,例如:super.name
表示访问父类的名为name
的成员变量。 - 调用父类的构造函数:
super(参数列表)
,例如:super(name, age)
表示调用父类带有name
和age
参数的构造函数。
- 访问父类的成员变量或方法:
- 在子类中,可以使用
-
this
关键字:this
关键字指代当前对象的引用,即调用当前方法或访问当前对象的成员变量。- 在实例方法中,可以使用
this
关键字来访问当前对象的成员变量或调用其他实例方法。 - 在构造函数中,可以使用
this
关键字来调用当前类的其他构造函数。这种用法被称为构造函数的重载,用于复用代码和提供不同的构造方式。 this
的用法有两种:- 访问当前对象的成员变量或方法:
this.成员名
,例如:this.name
表示访问当前对象的名为name
的成员变量。 - 调用当前类的其他构造函数:
this(参数列表)
,例如:this(name)
表示调用当前类带有name
参数的构造函数。
- 访问当前对象的成员变量或方法:
需要注意的是,super
和this
关键字不能在静态方法(static
方法)中使用,因为静态方法属于类本身,没有实例化对象。它们只能在实例方法和构造函数中使用。
10. 什么是多态?
多态(Polymorphism)是面向对象编程的重要概念之一,它允许使用父类的引用变量来引用子类的对象,从而实现同一操作在不同对象上的多种形态。
多态的实现依赖于以下两个关键概念:
-
继承(Inheritance):通过继承,子类可以继承父类的属性和方法。子类对象可以被赋值给父类引用变量。
-
方法重写(Method Overriding):子类可以重写父类的方法,提供自己的实现。方法重写要求子类方法具有与父类方法相同的名称、参数列表和返回类型。
通过多态,可以实现以下特性:
1. 向上转型(Upcasting):子类对象可以赋值给父类引用变量。这样做可以将子类对象视为父类对象,从而使用父类的方法和属性。这种转型是隐式的,不需要显式的类型转换。
ParentClass obj = new ChildClass();
2. 方法的动态绑定(Dynamic Binding):当通过父类引用调用被子类重写的方法时,实际执行的是子类的方法。在运行时,JVM会根据对象的实际类型来决定调用哪个方法。
ParentClass obj = new ChildClass();
obj.method(); // 子类的method()方法被调用
多态的优势和应用场景包括:
-
代码的灵活性和扩展性:多态允许在父类引用的基础上处理不同的子类对象,使代码更灵活和可扩展。可以通过修改父类引用的指向来适应不同的对象类型。
-
统一的接口和封装:多态通过共享父类的接口,使得不同的对象可以以一致的方式进行访问和操作。这提供了更好的封装和抽象能力。
-
代码的可读性和可维护性:多态可以使代码更简洁、可读性更高,减少重复的代码和条件判断。
需要注意的是,多态只适用于针对父类的方法和属性。在多态的情况下,子类特有的方法和属性无法通过父类引用访问。如果需要访问子类特有的方法或属性,可以使用类型转换操作将父类引用转换为子类引用,以便调用子类特有的成员。
11. 访问权限
在Java中,访问权限用于控制类、成员变量、方法和构造函数对其他类和代码的可见性和可访问性。Java中有四种访问权限修饰符:
-
public
:使用public
修饰的类、成员变量、方法和构造函数可以被任何其他类访问。 -
protected
:被protected
修饰的成员变量、方法和构造函数可以在同一包内的其他类中被访问,以及在子类中被访问。 -
默认(包级)访问权限:如果没有指定访问权限修饰符,那么默认的访问权限会应用。默认访问权限限制了只有同一包内的其他类可以访问。
-
private
:私有访问权限是最严格的权限级别。使用private
修饰的成员变量、方法和构造函数只能在所属类内部访问,其他任何类都无法直接访问。
访问权限 | 同一类 | 同一包 | 子类 | 其他包 |
---|---|---|---|---|
public | ✓ | ✓ | ✓ | ✓ |
protected | ✓ | ✓ | ✓ | ✕ |
默认(包级) | ✓ | ✓ | ✕ | ✕ |
private | ✓ | ✕ | ✕ | ✕ |
12. 什么是抽象?
抽象(Abstraction)是一种面向对象编程的重要概念,它允许我们从具体的实现中提取出通用的概念和行为,并将其表示为抽象类或接口。抽象提供了一种封装和抽象化的方式,使得我们能够以更高层次的抽象思维来设计和组织代码。
-
抽象类(Abstract Class):
- 抽象类是一个被
abstract
关键字修饰的类,无法被实例化。 - 抽象类可以包含抽象方法和非抽象方法。
- 抽象方法是没有具体实现的方法,只有方法签名,使用
abstract
关键字修饰。 - 非抽象方法是具有具体实现的方法。
- 抽象类可以被子类继承,子类必须实现抽象类中的抽象方法。
- 抽象类常用于定义通用的行为和属性,提供一个模板供子类实现。
- 抽象类是一个被
-
接口(Interface):
- 接口是一种完全抽象的类,使用
interface
关键字定义。 - 接口中只能包含抽象方法和常量,不能包含非抽象方法和实例变量。
- 接口定义了一组要被实现的方法,而不关心具体的实现。
- 类可以实现一个或多个接口,通过
implements
关键字实现接口。 - 实现接口的类必须实现接口中定义的所有方法。
- 接口常用于定义规范、定义回调方法和实现多态。
- 接口是一种完全抽象的类,使用
抽象的优点和应用场景包括:
- 封装和抽象化:抽象可以将复杂的实现细节隐藏起来,提供更简洁的接口。
- 可扩展性和灵活性:抽象类和接口提供了一种扩展和定制的方式,允许子类实现自己的特定行为。
- 继承和多态:抽象类和接口支持继承和多态特性,提高代码的可重用性和可维护性。
- 规范和约束:接口可以定义规范和约束,使得代码具有一致性和可互操作性。
13. 什么是Bean规范?
Bean规范是Java语言中用于描述可重用组件的一种规范。它定义了一种用于封装数据和功能的标准化编程模型,称为JavaBean。JavaBean是一种符合特定规范的普通Java类,具有以下特征:
-
公共无参构造函数:JavaBean类必须提供一个公共的无参构造函数,用于实例化对象。
-
私有成员变量:JavaBean类的属性应该是私有的,并通过公共的访问方法(getter和setter)来访问和修改属性的值。
-
公共访问方法:JavaBean类应该提供公共的访问方法(getter和setter)来获取和设置属性的值。
-
序列化支持:JavaBean类可以实现
java.io.Serializable
接口,以支持对象的序列化和反序列化。 -
符合命名规范:JavaBean类的命名应该符合标识符命名规范,属性名以小写字母开头,遵循驼峰命名法。
JavaBean规范的设计目的是提供一种通用的编程模型,使得Java类可以被简单地组织、配置和重用。JavaBean可用于各种应用领域,例如图形界面编程、Web开发和企业级应用开发等。
14. 说说作用域
作用域是指变量、方法、类等程序元素的可见范围,即它们可以被访问的代码范围。Java中有四种主要的作用域:
-
类级别作用域(Class-level Scope):
- 类级别的作用域指的是在类内部声明的成员变量(类变量)和成员方法(类方法)的范围。
- 类变量和类方法可以被类中的任何其他成员方法直接访问,无需创建类的实例。
- 类变量和类方法使用
static
关键字来修饰。
-
对象级别作用域(Object-level Scope):
- 对象级别的作用域指的是在类内部声明的实例变量(对象变量)和实例方法(对象方法)的范围。
- 实例变量和实例方法只能在类的实例(对象)上调用和访问。
- 每个对象都有自己的实例变量,各个对象之间的实例变量互相独立,互不影响。
-
方法级别作用域(Method-level Scope):
- 方法级别的作用域指的是在方法内部声明的局部变量的范围。
- 局部变量只能在声明它的方法内部使用,其它方法无法访问该局部变量。
- 当方法执行结束后,局部变量的作用域也随之结束,其内存空间会被回收。
-
块级别作用域(Block-level Scope):
- 块级别的作用域指的是在代码块(由花括号
{}
包围的一段代码)内部声明的变量的范围。 - 块级作用域的变量只能在该代码块内部使用,超出该代码块范围后将不再可见。
- 块级作用域常见于条件语句、循环语句和方法体内部。
- 块级别的作用域指的是在代码块(由花括号
15. 说说java中的字符串操作
-
字符串的创建:
- 使用字符串字面量创建字符串:
String str = "Hello";
- 使用
new
关键字创建字符串对象:String str = new String("Hello");
- 使用字符串字面量创建字符串:
-
字符串连接和拼接:
- 使用
+
操作符进行字符串连接:String result = str1 + str2;
- 使用
concat()
方法拼接字符串:String result = str1.concat(str2);
- 使用
StringBuilder
或StringBuffer
类进行高效的字符串拼接。
- 使用
-
字符串的长度和字符获取:
- 使用
length()
方法获取字符串的长度:int length = str.length();
- 使用
charAt()
方法获取指定位置的字符:char ch = str.charAt(index);
- 使用
-
字符串的比较:
- 使用
equals()
方法比较字符串内容是否相等:boolean isEqual = str1.equals(str2);
- 使用
equalsIgnoreCase()
方法忽略大小写进行比较:boolean isEqual = str1.equalsIgnoreCase(str2);
- 使用
-
字符串的查找和替换:
- 使用
indexOf()
方法查找指定字符或字符串的位置:int index = str.indexOf("search");
- 使用
lastIndexOf()
方法从后往前查找指定字符或字符串的位置。 - 使用
replace()
方法替换字符串中的字符或字符串:String replaced = str.replace("old", "new");
- 使用正则表达式进行更复杂的查找和替换。
- 使用
-
字符串的分割和连接:
- 使用
split()
方法将字符串分割成字符串数组:String[] parts = str.split(delimiter);
- 使用
join()
方法将字符串数组连接成一个字符串:String joined = String.join(delimiter, strArray);
- 使用
-
字符串的切割和子串提取:
- 使用
substring()
方法提取子串:String sub = str.substring(startIndex, endIndex);
- 使用
-
字符串的格式化:
- 使用
printf()
方法进行字符串格式化输出:System.out.printf("Name: %s, Age: %d", name, age);
- 使用
需要注意的是,Java中的字符串是不可变的(immutable),即字符串对象创建后不可修改。因此,对字符串的任何修改都会创建一个新的字符串对象。为了避免频繁的字符串对象创建,可以使用StringBuilder
或StringBuffer
类进行可变字符串的操作,与String类不同,StringBuilder允许对字符串进行动态修改,包括追加、插入、删除和替换等操作,而不会创建新的字符串对象。
16. int 和 Integer 的区别?
-
数据类型:
- int是一种基本数据类型(Primitive Type),用于表示整数值。
- Integer是一个包装类(Wrapper Class),用于将int数据类型封装为对象。
-
可赋值性:
- int是原始数据类型,可以直接赋值给int类型的变量:
int num = 10;
- Integer是对象类型,需要使用包装类的构造函数或自动装箱(Autoboxing)将int值转换为Integer对象:
Integer number = new Integer(10);
或Integer number = 10;
- int是原始数据类型,可以直接赋值给int类型的变量:
-
空值表示:
- int不能表示为null,因为它是基本数据类型。
- Integer可以表示为null,因为它是一个对象,可以赋予null值:
Integer number = null;
-
自动装箱和拆箱:
- Java提供了自动装箱(Autoboxing)和自动拆箱(Unboxing)的特性,使得int和Integer之间的转换更加方便。
- 自动装箱是指将int类型自动转换为对应的Integer对象,而自动拆箱则是将Integer对象自动转换为int类型。
- 例如:
Integer number = 10;
(自动装箱),int num = number;
(自动拆箱)
-
方法调用:
- int是基本数据类型,不是对象,所以不能直接调用方法。
- Integer是一个对象,可以调用Integer类提供的方法,如:
Integer.parseInt("10");
需要注意的是,尽管可以进行自动装箱和拆箱操作,但频繁地进行装箱和拆箱可能会带来一定的性能开销。因此,在需要高效处理大量整数计算时,建议使用基本数据类型int。而在需要更多操作和特性的场景下,使用Integer对象可以更方便地处理。
17. 说说 java.time.LocalTime 类
由于Date
类和Calendar
类在某些方面存在设计上的缺陷,不推荐使用,在Java 8及以上版本,建议使用java.time
包中的类来处理日期和时间。
java.time.LocalTime
是Java 8及以上版本引入的类,位于java.time
包中,用于表示时间值,不包含日期和时区信息。LocalTime
提供了许多方法来处理和操作时间,具有良好的可读性和线程安全性。
以下是LocalTime
类的一些重要特点和常用方法:
-
创建
LocalTime
对象:- 可以使用静态工厂方法
of()
创建指定的时间对象,例如:LocalTime time = LocalTime.of(hour, minute, second)
。 - 还可以使用
now()
方法获取当前时间,或者使用解析方法parse()
将字符串解析为LocalTime
对象。
- 可以使用静态工厂方法
-
获取时间的部分值:
- 可以使用
getHour()
、getMinute()
、getSecond()
等方法获取时间的小时、分钟、秒等部分的值。 - 还可以使用
getNano()
方法获取纳秒部分的值。
- 可以使用
-
时间的比较和判断:
- 可以使用
compareTo()
方法比较两个LocalTime
对象的先后关系。 - 可以使用
isBefore()
、isAfter()
和equals()
等方法判断时间的先后和相等关系。
- 可以使用
-
时间的计算和调整:
- 可以使用
plusHours()
、plusMinutes()
、minusSeconds()
等方法对时间进行加减操作。 - 可以使用
withHour()
、withMinute()
、withSecond()
等方法调整时间的部分值。
- 可以使用
-
格式化和解析时间:
- 可以使用
format()
方法将LocalTime
对象格式化为字符串。 - 可以使用解析方法
parse()
将字符串解析为LocalTime
对象。
- 可以使用
需要注意的是,LocalTime
类表示的是本地时间,不包含时区信息。如果需要处理带有时区的时间,可以使用java.time.ZonedDateTime
类。
使用LocalTime
类获取当前时间并进行格式化的示例代码如下:
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) {
// 获取当前时间
LocalTime currentTime = LocalTime.now();
// 定义时间格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
// 格式化时间
String formattedTime = currentTime.format(formatter);
// 输出格式化后的时间
System.out.println("当前时间:" + formattedTime);
}
}
18. Integer缓存
在Java中,Integer
类提供了一个整数缓存机制,可以重用一定范围内的整数对象,以减少内存消耗和对象创建的开销。这个整数缓存是通过Integer
类中的一个内部静态类实现的,称为IntegerCache
。
IntegerCache
缓存了从-128到127范围内的整数对象,默认情况下,这些整数对象在第一次使用时会被自动创建并缓存起来。当我们使用Integer
类的valueOf()
方法创建一个处于该范围内的整数时,实际上是从缓存中获取已经存在的对象,而不是创建新的对象。
这个缓存机制的设计是为了提高性能和节省内存。由于整数在编程中经常被使用,重复创建新的整数对象会造成不必要的内存开销。通过缓存已有的整数对象,可以减少内存占用,并提高性能。
需要注意的是,这个整数缓存机制只适用于Integer
类的自动装箱操作,即将int
类型的值自动转换为Integer
对象。如果使用new Integer()
显式创建Integer
对象,将不会使用缓存机制,而是创建一个新的对象。
以下是一个使用Integer
缓存的示例代码:
Integer num1 = 10; // 自动装箱,从缓存中获取已有的Integer对象
Integer num2 = 10; // 从缓存中获取已有的Integer对象
System.out.println(num1 == num2); // 输出:true,两个引用指向同一个对象
Integer num3 = 200; // 超过缓存范围,创建新的Integer对象
Integer num4 = 200; // 超过缓存范围,创建新的Integer对象
System.out.println(num3 == num4); // 输出:false,两个引用指向不同的对象
19. 说说java中的异常
在Java中,异常(Exception)是指程序在运行时遇到的意外情况或错误。异常是一种中断正常程序执行流程的事件,它可能导致程序终止或产生错误结果。
Java中的异常可以分为两种类型:已检查异常(Checked Exception)和未检查异常(Unchecked Exception)。
-
已检查异常(Checked Exception):
- 已检查异常是在编译时强制要求处理的异常,必须通过
try-catch
块或在方法签名中使用throws
关键字声明。 - 已检查异常通常表示程序在正常运行过程中可能遇到的外部问题,例如文件不存在、网络连接中断等。
- 例子:
IOException
、SQLException
等。
- 已检查异常是在编译时强制要求处理的异常,必须通过
-
未检查异常(Unchecked Exception):
- 未检查异常是在运行时抛出的异常,编译器不会强制要求进行处理。
- 未检查异常通常表示程序内部出现了错误或违反了语义规则,例如空指针引用、除零错误等。
- 未检查异常是
RuntimeException
类及其子类的实例。 - 例子:
NullPointerException
、ArithmeticException
等。
异常处理在Java中使用了以下关键字和结构:
-
try-catch
块:- 使用
try-catch
块可以捕获并处理异常。 try
块用于包含可能引发异常的代码。catch
块用于捕获并处理异常,提供相应的处理逻辑。- 可以使用多个
catch
块捕获不同类型的异常。 - 可以使用
finally
块定义无论是否发生异常都需要执行的代码块。
- 使用
-
throws
关键字:- 在方法声明中使用
throws
关键字可以将异常的处理责任交给调用方。 - 方法使用
throws
声明可能会抛出的异常类型,告知调用方需要处理这些异常。
- 在方法声明中使用
-
自定义异常:
- 可以通过继承
Exception
类或其子类来自定义异常类。 - 自定义异常类可以根据需要添加额外的字段和方法。
- 可以通过继承
需要注意的是,捕获和处理异常应该是有针对性的,避免将所有异常都捕获到一个通用的catch
块中。
20. 常见的异常有哪些?
-
NullPointerException(空指针异常):
- 当尝试在一个空对象上调用方法或访问其属性时抛出。
- 常见原因包括未初始化对象、将null赋值给对象引用等。
-
IllegalArgumentException(非法参数异常):
- 当传递给方法的参数不满足方法要求时抛出。
- 常见原因包括参数为空、参数超出范围、不支持的参数等。
-
ArrayIndexOutOfBoundsException(数组下标越界异常):
- 当尝试访问数组中不存在的索引位置时抛出。
- 常见原因包括使用负数索引或超出数组大小的索引。
-
ClassCastException(类转换异常):
- 当尝试将对象强制转换为不兼容的类时抛出。
- 常见原因包括对象实际类型与要转换的类型不匹配。
-
ArithmeticException(算术异常):
- 当在数学运算中出现错误时抛出,如除以零。
- 常见原因包括除法操作中的除数为零或溢出。
-
FileNotFoundException(文件未找到异常):
- 当尝试访问不存在的文件时抛出。
- 常见原因包括文件路径错误、文件不存在或无权限访问。
-
IOException(输入输出异常):
- 当发生输入或输出操作失败时抛出。
- 常见原因包括文件读写错误、网络连接中断等。
-
InterruptedException(线程中断异常):
- 当一个线程正在等待、休眠或被中断时,它可能被另一个线程中断。
- 常见原因包括使用
Thread.sleep()
方法时被中断或调用Thread.interrupt()
方法。
除了上述异常外,还有许多其他类型的异常,如NumberFormatException(数字格式异常)、IllegalStateException(非法状态异常)、OutOfMemoryError(内存溢出错误)等,每种异常都有特定的原因和使用场景。
下一篇:JavaSE高级