Java 异常类
在 Java 中,异常是指在程序执行过程中发生的错误或意外情况。Java 的异常类可以分为两种类型:checked exception(检查型异常)和 unchecked exception(非检查型异常)。checked exception 必须在代码中显式地处理,而 unchecked exception 可以不处理。Java 中的异常类都继承自 Throwable 类,包括 Error、Exception 和 RuntimeException 三个子类。其中 Error 和 RuntimeException 类型的异常被称为非检查型异常,其他异常被称为检查型异常。
Error
Error 类表示 Java 运行时系统内部错误或资源耗尽错误。当出现这种错误时,Java 虚拟机(JVM)无法恢复。通常情况下,程序不应该抛出 Error 异常。以下是一个示例:
public void myMethod() {
try {
// some code
} catch (StackOverflowError e) {
// handle the error
}
}
上面的代码演示了如何处理 StackOverflowError,这是一个 Error 类型的异常。在 try 块中执行的代码可能会导致栈溢出,这会抛出 StackOverflowError 异常。
Exception
Exception 类是所有异常的超类,它表示程序可能会遇到的异常情况。Exception 类下又有许多子类,包括 IOException、SQLException、ClassNotFoundException 等等。这些异常都需要在代码中进行显式处理。以下是一个示例:
public void myMethod() {
try {
// some code that may throw an exception
} catch (IOException e) {
// handle the IOException
} catch (SQLException e) {
// handle the SQLException
}
}
上面的代码演示了如何处理 IOException 和 SQLException 两种异常。在 try 块中执行的代码可能会抛出这两种异常之一,所以在代码中必须显式地处理它们。
RuntimeException
RuntimeException 类是 Exception 类的子类,表示程序中的错误或意外情况。与 Exception 类型的异常不同,RuntimeException 类型的异常不需要在代码中显式处理。以下是一个示例:
public void myMethod() {
int x = 1 / 0;
}
上面的代码会抛出一个 ArithmeticException 异常,因为试图将一个数除以 0 是不允许的。由于 ArithmeticException 是 RuntimeException 的子类,所以不需要在代码中显式地处理它。
自定义异常类
除了 Java 内置的异常类之外,程序员还可以自己定义异常类,以便更好地处理程序中的错误情况。自定义异常类需要继承 Exception 或 RuntimeException 类,并提供自己的构造函数和异常信息。以下是一个示例:
public class MyException extends Exception {
public MyException(String message) {
super(message);
}
}
上面的代码定义了一个名为 MyException 的自定义异常类,它继承自 Exception 类,并提供了一个带有 message 参数的构造函数。
try-with-resources 语句
在 Java 7 中引入了 try-with-resources 语句,用于在 try 块中自动关闭资源。try-with-resources 语句可以自动关闭实现了 AutoCloseable 接口的资源,比如流和数据库连接。以下是一个示例:
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
String line = reader.readLine();
// process the line
} catch (IOException e) {
// handle the exception
}
上面的代码演示了如何使用 try-with-resources 语句自动关闭 BufferedReader 对象。在 try 块中创建 BufferedReader 对象,try 块执行完毕后,不需要显式地关闭它,因为 BufferedReader 实现了 AutoCloseable 接口。
finally 语句
finally 语句用于在 try 块和 catch 块之后执行一些清理工作,比如关闭文件或数据库连接。无论是否抛出异常,finally 语句都会执行。以下是一个示例:
try {
// some code that may throw an exception
} catch (Exception e) {
// handle the exception
} finally {
// code to be executed after try or catch block
}
上面的代码演示了如何使用 finally 语句执行清理工作。在 try 块中执行的代码可能会抛出异常,catch 块用于处理异常,而 finally 块则用于执行清理工作,比如关闭文件或数据库连接。、
异常方法
getMessage()方法:返回关于异常原因的详细信息字符串。toString()方法:返回异常对象的字符串表示形式,包括异常的类名和异常信息。printStackTrace()方法:在控制台打印异常信息的堆栈轨迹,方便调试。
下面是一个示例:
try {
// some code that may throw an exception
} catch (IOException e) {
System.out.println("IO error occurred: " + e.getMessage());
e.printStackTrace();
}
上面的代码演示了如何使用 getMessage() 方法和 printStackTrace() 方法打印异常信息。
除了上述方法之外,Java 异常类还提供了其他一些方法,如 getCause()、getLocalizedMessage()、fillInStackTrace() 等,这些方法可以根据具体的需求进行使用。
异常处理最佳实践
以下是一些 Java 异常处理的最佳实践:
- 避免捕获 Throwable 类型的异常,因为它包括 Error 和 Exception 两种类型的异常,过于宽泛。
- 对于检查型异常,要求在代码中显式处理,而对于非检查型异常,则无需显式处理。
- 在 catch 块中使用日志记录异常信息,而不是仅仅将异常信息输出到控制台。
- 避免在 finally 块中使用 return 语句,因为它会导致其他异常被忽略。
异常处理机制
Java 异常处理机制的基本流程如下:
- 程序执行到可能抛出异常的语句时,会抛出一个异常对象。
- 如果抛出的异常对象没有在当前方法中被捕获,则该方法将立即停止执行,并将异常对象传递给调用该方法的方法。
- 如果异常对象在调用方法中没有被捕获,则该方法将立即停止执行,并将异常对象传递给更高层次的调用方法,直到异常被捕获为止。
- 如果异常一直没有被捕获,则该程序将终止执行,并显示异常的堆栈信息。
异常链
在 Java 异常处理中,异常可以被传递到调用栈的上层,形成异常链。在异常链中,每个异常都包含一个指向下一个异常的引用。当某个方法捕获异常时,可以使用 getCause() 方法获取前一个异常。以下是一个示例:
try {
// some code that may throw an exception
} catch (Exception e) {
throw new MyException("An error occurred", e);
}
上面的代码演示了如何将前一个异常作为参数传递给一个自定义异常。
多异常捕获
一个 try 块可以捕获多个异常,可以使用多个 catch 块来分别处理不同类型的异常。多个 catch 块按照它们声明的异常类型的顺序进行匹配。以下是一个示例:
try {
// some code that may throw an exception
} catch (IOException e) {
// handle IOException
} catch (SQLException e) {
// handle SQLException
} catch (Exception e) {
// handle other exceptions
}
上面的代码演示了如何使用多个 catch 块捕获不同类型的异常。首先尝试匹配 IOException 类型的异常,然后是 SQLException 类型的异常,最后是其他类型的异常。
抛出异常
除了捕获异常之外,程序员还可以通过 throw 语句抛出异常。throw 语句用于在程序中显式地抛出一个异常对象。以下是一个示例:
if (x < 0) {
throw new IllegalArgumentException("x must be non-negative");
}
上面的代码演示了如何使用 throw 语句抛出 IllegalArgumentException 异常。
常用类
Object 类
Object 类是 Java 中所有类的基类,包含了一些通用的方法,如 equals()、hashCode()、toString() 等。其他类都继承自 Object 类,因此 Object 类在 Java 中具有重要的地位。
equals() 方法
equals() 方法用于比较两个对象是否相等,该方法默认比较的是两个对象的地址是否相等,如果需要比较对象的内容是否相等,需要重写该方法。
例如:
public class Person {
private String name;
private int age;
// 省略构造函数和其他方法
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
if (this.getClass() != obj.getClass()) {
return false;
}
Person p = (Person) obj;
return this.name.equals(p.name) && this.age == p.age;
}
}
hashCode() 方法
hashCode() 方法返回对象的哈希码,通常用于集合中的元素比较和存储等操作。如果两个对象的 equals() 方法返回 true,则它们的哈希码也应该相等。
例如:
public class Person {
private String name;
private int age;
// 省略构造函数和其他方法
@Override
public int hashCode() {
int result = 17;
result = 31 * result + name.hashCode();
result = 31 * result + age;
return result;
}
}
toString() 方法
toString() 方法返回对象的字符串表示形式,通常用于调试和日志输出等操作。如果没有重写该方法,则默认返回对象的类名和哈希码。
例如:
public class Person {
private String name;
private int age;
// 省略构造函数和其他方法
@Override
public String toString() {
return "Person{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
String 类
String 类表示字符串,是 Java 中使用最广泛的类之一,提供了丰富的方法来操作字符串。
例如:
String str1 = "hello";
String str2 = "world";
String str3 = str1 + " " + str2; // 拼接字符串
System.out.println(str3); // 输出:hello world
String str4 = "Hello World";
System.out.println(str4.toLowerCase()); // 输出:hello world
System.out.println(str4.toUpperCase()); // 输出:HELLO WORLD
String str5 = " hello world ";
System.out.println(str5.trim()); // 输出:hello world
String str6 = "hello,world";
String[] strs = str6.split(","); // 分割字符串
System.out.println(Arrays.toString(strs)); // 输出:[hello, world]
String str7 = "hello";
System.out.println(str7.charAt(1)); // 输出:e
System.out.println(str7.indexOf('e')); // 输出:1
System.out.println(str7.endsWith("lo")); // 输出:true
ArrayList 类
ArrayList 类实现了可调整大小的数组,可以动态添加和删除元素,是 Java 中常用的集合类之一。
例如:
ArrayList<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("orange");
System.out.println(list); // 输出:[apple, banana, orange]
list.remove(1);
System.out.println(list); // 输出:[apple, orange]
System.out.println(list.get(1)); // 输出:orange
System.out.println(list.size()); // 输出:2
HashMap 类
HashMap 类实现了哈希表,可以存储键值对,其中键和值均为对象。可以通过键快速访问到值,是 Java 中常用的集合类之一。
例如:
HashMap<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.put("orange", 3);
System.out.println(map); // 输出:{orange=3, banana=2, apple=1}
map.remove("banana");
System.out.println(map); // 输出:{orange=3, apple=1}
System.out.println(map.get("apple")); // 输出:1
System.out.println(map.containsKey("orange")); // 输出:true
System.out.println(map.size()); // 输出:2
Date 类
Date 类表示时间和日期,可以用于获取当前时间、格式化日期等操作。
例如:
Date date = new Date();
System.out.println(date); // 输出:Tue Mar 30 17:18:14 CST 2023
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(date)); // 输出:2023-03-30 17:18:14
File 类
File 类表示文件或目录,可以用于创建、删除、重命名、遍历文件和目录等操作。
例如:
File file = new File("test.txt");
if (!file.exists()) {
file.createNewFile();
}
System.out.println(file.getName()); // 输出:test.txt
File dir = new File("test");
if (!dir.exists()) {
dir.mkdir();
}
System.out.println(dir.isDirectory()); // 输出:true
File[] files = dir.listFiles();
System.out.println(Arrays.toString(files)); // 输出:[]
容器
Java 中的容器用于存储一组对象,并提供对这些对象进行添加、删除、查找等操作。常见的容器包括 Collection 和 Map 两类。
Collection
Collection 接口是 Java 容器中最基本的接口,它定义了一些操作集合的方法。常见的 Collection 子接口包括 List、Set 和 Queue。
List
List 是一个有序的集合,其中元素可以重复。常用的 List 实现类包括 ArrayList、LinkedList 和 Vector。
List<String> list = new ArrayList<>(); // 创建 ArrayList 对象
list.add("apple"); // 添加元素
list.add("banana");
list.get(0); // 获取元素
list.remove("apple"); // 移除元素
Set
Set 是一个不允许元素重复的集合,其中元素没有顺序。常用的 Set 实现类包括 HashSet、TreeSet 和 LinkedHashSet。
Set<String> set = new HashSet<>(); // 创建 HashSet 对象
set.add("apple"); // 添加元素
set.add("banana");
set.contains("apple"); // 判断是否包含某个元素
set.remove("apple"); // 移除元素
Queue
Queue 是一个队列,通常按照 FIFO(先进先出)的顺序存储元素。常用的 Queue 实现类包括 LinkedList 和 PriorityQueue。
Queue<String> queue = new LinkedList<>(); // 创建 LinkedList 对象
queue.offer("apple"); // 添加元素
queue.offer("banana");
queue.peek(); // 查看队头元素
queue.poll(); // 移除队头元素
Map
Map 接口表示一组键值对,其中键是唯一的,而值可以重复。常见的 Map 实现类包括 HashMap、TreeMap 和 LinkedHashMap。
Map<String, String> map = new HashMap<>(); // 创建 HashMap 对象
map.put("key1", "value1"); // 添加键值对
map.put("key2", "value2");
map.get("key1"); // 获取对应值
map.remove("key1"); // 移除键值对
Arrays
Arrays 类提供了一些方法用于操作数组,例如排序、查找等。
int[] nums = {3, 1, 4, 1, 5, 9};
Arrays.sort(nums); // 对数组排序
Arrays.binarySearch(nums, 4); // 在数组中查找元素
Stack
Stack 类表示一个后进先出(LIFO)的栈。它继承自 Vector 类,提供了 push、pop、peek 等方法。
Stack<Integer> stack = new Stack<>();
stack.push(1); // 入栈
stack.push(2);
stack.pop(); // 出栈
stack.peek(); // 查看栈顶元素
StringBuilder
StringBuilder 类表示可变的字符串,它比 String 类更加高效。
StringBuilder sb = new StringBuilder(); // 创建 StringBuilder 对象
sb.append("Hello"); // 追加字符串
sb.append(" ");
sb.append("world!");
sb.toString(); // 转换为 String 类型
StringTokenizer
StringTokenizer 类用于分割字符串。
String str = "Hello,world!";
StringTokenizer st = new StringTokenizer(str, ","); // 使用 , 分割字符串
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
泛型
Java 的泛型是一种参数化类型的机制,它可以在编译时强制检查类型的一致性,并避免了在运行时因类型转换错误而导致的异常。使用泛型可以使代码更加类型安全,提高代码的可读性和可维护性。
泛型参数
- E:表示元素类型,常用于集合类(例如 List、Set)中。
- K:表示键(Key)的类型,常用于 Map 类中。
- V:表示值(Value)的类型,常用于 Map 类中。
- T:表示任意类型(Type)。
- S、U、V 等:表示除了 T 之外的任意类型。
这些字母仅仅是习惯上的使用方法,并没有强制规定。在实际编程中,你可以使用任何合法的标识符来代表泛型参数。
泛型类
泛型类是一种具有泛型参数的类。在类的声明中,可以使用尖括号(<>)来指定泛型参数,泛型参数可以是任意类型,包括类、接口和基本数据类型。
例如,下面的代码定义了一个泛型类 Box,它可以存储任意类型的对象:
public class Box<T> {
private T object;
public void set(T object) {
this.object = object;
}
public T get() {
return object;
}
}
在使用泛型类时,需要在实例化时指定具体的类型参数,例如:
Box<String> box = new Box<String>();
box.set("Hello");
String str = box.get();
在这个例子中,Box 类的类型参数 T 被实例化为 String 类型,因此 box 对象只能存储和获取 String 类型的对象。
泛型方法
泛型方法是一种具有泛型参数的方法。在方法的声明中,可以使用尖括号(<>)来指定泛型参数,泛型参数可以是任意类型,包括类、接口和基本数据类型。
例如,下面的代码定义了一个泛型方法 printArray,它可以打印任意类型的数组:
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
在使用泛型方法时,不需要在调用时指定类型参数,编译器会根据方法参数推断出类型参数,例如:
Integer[] intArray = {1, 2, 3};
Double[] doubleArray = {1.1, 2.2, 3.3};
String[] stringArray = {"Hello", "World"};
printArray(intArray); // 输出:1 2 3
printArray(doubleArray); // 输出:1.1 2.2 3.3
printArray(stringArray); // 输出:Hello World
在这个例子中,printArray 方法的类型参数 T 被推断为 Integer、Double 和 String 类型,因此可以打印对应类型的数组。