Effective Java 简要笔记

创建和销毁对象

用静态工厂方法替代构造器

  • 静态工厂方法具有名称,更具有可读性,尤其是构造器比较多的类;
  • 通过静态工厂方法创建对象时,可以不必每次创建对象,使用预先构建好的实例,例如Boolean类:
 

typescript

代码解读

复制代码

public static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE); }

  • 工厂方法可以返回原类型的任何子类,这相比构造器更加灵活。

一些静态方法的惯用名称:

  • from, 类型转换方法,只有一个参数,返回该类型对应的实例。
 

ini

代码解读

复制代码

Date d = Date.from(instance);

  • of, 聚合方法,多个参数,返回一个实例。
 

ini

代码解读

复制代码

Set<Rank> faceCards = EnumSet.of(JACK, QUEUE);

遇到多个构造器参数时考虑使用构建器

静态工厂方法和构造器都不能很好的扩展到大量参数。对于大量参数的场景,一种方法是使用重叠构造器的方式,第一个构造器只有少量必要参数,第二个构造器除了必要参数,还添加一些可选参数,以此类推。重叠构造器在参数量可控的时候还好,随着参数增多,构造器方法也会爆炸式增多,变得难以维护。另一种办法是Java Beans模式,通过无参构造器构造对象,调用setter方法传递参数,但是这种方法会导致对象在构建过程中处于不一致状态,而且把类做成不可变的可能不复存在。

构建器模式通过让调用方传递必要参数调用Builder类的构造器得到builder对象,然后调用可选参数的setter方法设置可选参数,最后调用build方法生成不可变的最终对象。

 

kotlin

代码解读

复制代码

public final class NutritionFacts { private final int servingSize; private final int servings; private final int calories; // 私有构造函数,仅通过Builder类实例化 private NutritionFacts(Builder builder) { this.servingSize = builder.servingSize; this.servings = builder.servings; this.calories = builder.calories; } // Getter方法 public int getServingSize() { return servingSize; } public int getServings() { return servings; } public int getCalories() { return calories; } // 构建器类 public static class Builder { // 必填属性 private final int servingSize; private final int servings; // 可选属性 private int calories = 0; // 构造函数,设置必填属性 public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; } // 设置可选属性的方法 public Builder calories(int val) { this.calories = val; return this; } // 构建最终的NutritionFacts对象 public NutritionFacts build() { return new NutritionFacts(this); } } }

所有的字段都是 final 的,并且没有提供任何 setter 方法,对象一旦创建,其状态就不能再改变。 Builder 类用于构建 NutritionFacts 对象,Builder 类的构造函数接受必填属性。,Builder 类提供了链式调用的方法来设置可选属性。 最后,通过 build 方法创建 NutritionFacts 对象。

用私有构造器或者枚举类型强化Singleton属性

Singleton是指仅仅需要被实例化一次的对象,通常用来代表没有状态的对象。通过定义私有构造器,提供公有静态域或者工厂方法来获取单例对象。私有构造方法可以保证调用方无法通过构造器获取对象,只能获取创建好的单例对象,为防止通过反射机制调用私有构造器,可以在第二次创建对象时抛出异常。另一种实现单例的方法是声明一个包含单个元素的枚举类型,这种方法提供了序列化机制,我们不用考虑单例对象在序列化和反序列化时需要做的额外工作。

 

arduino

代码解读

复制代码

public enum Singleton { INSTANCE; }

通过私有构造器强化不可实例的能力

一些工具类只是用来提供一些静态变量或者静态的工具方法,不希望被实例化,因为实例化没有意义。但是在缺少显式声明的构造器时,编译器会自动提供一个无参的构造器,还是能被实例化和继承。正确的做法是提供一个私有的构造器,让这种类不能被继承,也不能被实例化。

 

arduino

代码解读

复制代码

public final class Arrays { private Arrays() {} }

 

csharp

代码解读

复制代码

public class Collections { private Collections() {} }

避免创建不必要的对象

下面这段代码,将变量sum声明为Long类型以后,每次在做加法操作时,会先将int类型的i转变为Long类型的实例,这将导致构建大量的Long实例。要优先使用基本类型而不是对应的装箱类,防止无意识的自动装箱。

 

ini

代码解读

复制代码

private static long sun() { Long sum = 0L; for (int i = 0; i < Integer.MAX_VALUE; i++) { sum += i; } return sum; }

try-with-resources 优先于try-finally

使用try-finally来关闭资源存在一些问题,比如在try块和finally块中都抛出异常,try块中抛出的异常会被finally块中抛出的异常完全抹除,在异常堆栈轨迹中完全没有try块中的异常记录。使用try-with-resources可以解决这个问题。资源必须实现 java.lang.AutoCloseable 接口,这样才能在 try-with-resources 语句中使用,在 try 块结束时,所有声明的资源都会自动关闭。

 

typescript

代码解读

复制代码

public class TryWithResourcesExample { public static void main(String[] args) { // 使用 try-with-resources 语句 try (BufferedReader br = new BufferedReader(new FileReader("example.txt"))) { String line; while ((line = br.readLine()) != null) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } } }

在 try 语句的括号内声明资源,如果是多个资源,使用分号分割。在 try 块中使用声明的资源,如果在 try 块中或资源关闭时发生 IOException,会捕获并处理异常。如果处理资源或者关闭资源都发生了异常,后一个异常会被禁止,保留第一个异常,禁止的异常会被打印到堆栈轨迹中,也可以通过java.lang.Throwable#getSuppressed获取。

对于所有对象都通用的方法

覆盖equals方法时请遵守通用约定

如果类具有自己逻辑相等的概念,应该覆盖equals方法。覆盖时,遵守以下约定:

  • 自反性,对于非空性x,x.equals(x)必须返回true;
  • 对称性,对于非空引用x, y, x.equals(y)为true时,y.equals(x)也必须为true;
  • 传递性,对于非空引用x, y, z, x.equals(y)为true,y.equals(z)为true,x.equals(z)也必须为true。
  • 一致性,对于非空引用x, y, x.equals(y)的多次调用结果应该相同;
  • 非空性,非空引用x,x.equals(null)必须为false。

实现高质量equals方法的诀窍:

  1. 使用==操作符检查参数是否为当前对象的引用,如果是,则返回true;
  2. 使用instanceof操作符检查参数类型是否为当前类型,不是则返回false;
  3. 把参数转换为当前类型;
  4. 根据逻辑相等的定义,对关键域进行相等判断,如果全部判断相等,则返回true,否则返回false。对于既不是float也不是double的基本类型,使用==操作符判断;对于float类型或者double类型,使用java.lang.Float#compare(float f1, float f2)或者java.lang.Double#compare(double d1, double d2)进行判断;对于引用类型,可以递归调用引用类型的equals方法。

equals方法不需要对入参进行null检查,因为类型检查会返回false。

覆盖equals方法时总要覆盖hashCode方法

这是因为如果两个对象调用equals方法比较是相等的,则hashCode方法返回值也必须一致,否则该类无法结合所有基于散列的集合一起工作。比如两个通过equal方法判定为相等的对象,在添加到HashSet中时,由于没有覆盖hashCode方法,会重复添加,但是根据Set的定义,Set中的元素应该是唯一的,不能有两个“相等”的对象。

始终覆盖toString

 

typescript

代码解读

复制代码

public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }

这是默认的toString实现,通常情况下,需要和对象相关的更独特的信息,而不是类名和散列值。

考虑实现Comparable接口

类一旦实现了Comparable接口,就可以跟许多范型算法以及依赖于该接口的集合实现进行协作。例如java.util.Collections接口中的方法:

 

typescript

代码解读

复制代码

public static <T extends Comparable<? super T>> void sort(List<T> list) { list.sort(null); }

以及java.time.Year:

 

arduino

代码解读

复制代码

public int compareTo(Year other) { return year - other.year; }

Comparable接口只有一个compare方法,返回结果小于0表示当前值小于参数值,等于0表示相等,大于0表示当前值大于参数值。 对于基本类型,所对应的装箱类型都已经提供了compara方法,也不必在使用关系操作符<和>进行比较。

使类和成员的可访问性最小

设计良好的组件会隐藏所有的实现细节,这可以有效的解除组件之间的耦合关系,使得这些组件可以独立的开发、测试和修改。对于顶层的类和接口,只有两种访问级别,包级私有和公有的。类或者接口使用pulic修饰就是公有的,否则是包级私有的。如果一个包级私有的类只有使用它的类用到,就应该考虑将这个类设计为私有内部类。

对于成员,有四种访问级别,私有的、包级私有、受保护的、公开的。公有类的实例域决不能被公开,如果实例域是公开的并且是非final的,公开之后,就等于放弃了存储在这个域值值的控制能力。对于公开的final的数组域,或者提供了返回域的方法也是错误的,会导致数组内容被修改,如果必有,应该将数组域设置为私有,只提供一个数组拷贝。

使可变性最小化

不可变类是指其实例不能被修改的类。每个实例包含的信息都应该在创建该实例时提供,并且在对象的整个生命周期内不可变。不可变类更加易于设计、实现和使用,而且不易出错。 设计不可变类遵循以下原则:

  1. 不要为类提供setter方法;
  2. 保证类不会被继承,通过final修饰类或者构造方法私有的方式;
  3. 所有域都是private final修饰的,在构造时就需要赋值,并且不允许修改;
  4. 如果类具有指向可变对象的域,需要确保使用该类对象的客户端无法获得指向可变对象的引用。

接口优先于抽象类

对于在设计抽象类时,应该首先考虑一下,这个抽象类能不能设计成为接口,相比于抽象类,现有的实现了接口的类更容易被更新,因为Java语言允许实现多个接口,但是只允许继承一个抽象类。

接口虽然可以提供缺省方法,为某些方法提供实现,但是缺省方法仍然有一些缺点:接口无法给equals、hashCode等方法提供缺省实现;接口中不能包含非公有的静态域或者实例域。 为了结合接口和抽象类的优势,通过对接口提供一个抽象的骨架实现,接口负责定义类型,或者提供一些缺省方法,而骨架实现类则负责提供除基本方法之外的方法。

常量接口是对接口的不良使用

类在内部使用某些常量,属于实现细节,实现常量接口会导致把这样的实现细节泄露到该类导出的API中。以java.io.ObjectStreamConstants为例:

 

java

代码解读

复制代码

public interface ObjectStreamConstants { static final short STREAM_MAGIC = (short)0xaced; static final short STREAM_VERSION = 5; static final byte TC_BASE = 0x70; // 审略部分代码 }

如果要导出常量,首先考虑这些常量是不是与某个类或者接口紧密相关,如果是,应该把常量添加在这些接口或者类中;其次,使用枚举类型导出这些常量;最后,考虑使用不可实例化的工具类进行导出。

静态成员类优先于非静态成员类

如果成员类没有访问外围类实例的需求,就应该把成员类设计为static的,因为非static的成员类需要外围实例才能创建,在创建成员类实例以后,成员类实例会持有外围类实例的引用,保留这份引用不但需要消耗空间,而且会导致外围类实例不能被GC,造成内存泄漏。

范型

不要使用原生态类型

如果使用原生类型,会失去范型在安全性和描述性方面的所有优势,下面的List在声明时没有指定类型,所以可以加入任何类型的元素,但是在从list中获取元素时很可能强制转换失败,除非使用Object类型。

 

csharp

代码解读

复制代码

public List test() { List list = new ArrayList(); list.add(1); list.add("zz"); return list; }

优先考虑范型

使用泛型类时,编译器会在编译阶段检查类型安全,避免运行时出现类型转换错误。在使用非泛型类时,通常需要进行强制类型转换,这可能导致 ClassCastException 异常。使用泛型类后,编译器会自动处理类型转换,提高代码的安全性。泛型类可以处理多种数据类型,使得类的设计更加通用,适用于不同的场景。

 

typescript

代码解读

复制代码

public class Main { public static void main(String[] args) { Box box = new Box(); box.set("Hello"); String message = (String) box.get(); // 需要强制类型转换 System.out.println(message); } }

优先考虑范型方法

静态工具方法与范型类一样,尤其适合范型化。声明范型方法时,声明类型参数化的类型参数列表,处在方法的修饰符和返回值之间。

 

c

代码解读

复制代码

// 定义一个泛型方法,用于打印任何类型的数组 public <T> void printArray(T[] array) { for (T element : array) { System.out.println(element); } }

利用有限制通配符提高API灵活性

两个通配符:? extends T表示它可以提供 T 或其子类型的数据,? super T,表示它可以接收 T 或其父类型的数据。根据PECS原则使用。Producer Extends:当类型参数用于从集合中获取数据时,使用 ? extends T。 Consumer Super:当类型参数用于向集合中添加数据时,使用 ? super T。一言以蔽之,生产者可以把范围缩小(衍生到子类),消费者需要把支持范围扩大。

 

typescript

代码解读

复制代码

import java.util.ArrayList; import java.util.List; public class PECSExample { // 生产者使用 ? extends public void printNumbers(List<? extends Number> numbers) { for (Number number : numbers) { System.out.println(number); } } // 消费者使用 ? super public void addNumbers(List<? super Integer> numbers, int... values) { for (int value : values) { numbers.add(value); } } public static void main(String[] args) { PECSExample example = new PECSExample(); // 生产者示例 List<Integer> integers = new ArrayList<>(); integers.add(1); integers.add(2); example.printNumbers(integers); // 输出: 1, 2 List<Number> numbers = new ArrayList<>(); numbers.add(3.14); numbers.add(42); example.printNumbers(numbers); // 输出: 3.14, 42 // 消费者示例 List<Integer> integerList = new ArrayList<>(); example.addNumbers(integerList, 1, 2, 3); System.out.println(integerList); // 输出: [1, 2, 3] List<Number> numberList = new ArrayList<>(); example.addNumbers(numberList, 4, 5, 6); System.out.println(numberList); // 输出: [4, 5, 6] } }

枚举和注解

用enum代替int常量

枚举相比int常量有很多优势,第一是枚举类型保证类编译时安全,其次,枚举类型还能添加任意的域和方法并实现接口。枚举类具有更多的描述信息,其每个实例的toString方法会返回枚举值声明的名称,更有描述意义。

使用EnumSet代替位域

EnumSet 是 Java 集合框架中专门为枚举类型设计的一种高效集合。它提供了许多优点,使其成为处理枚举类型集合的首选工具。EnumSet 的实现非常高效,因为它利用了位向量(bit vector)来存储枚举值,因此占用的内存非常少。EnumSet 是类型安全的,只能包含特定枚举类型的元素。

EnumSet 提供了多种工厂方法来创建集合,常用的有: noneOf:创建一个空的 EnumSet。 of:创建一个包含指定元素的 EnumSet。 copyOf:创建一个包含另一个集合所有元素的 EnumSet。 range:创建一个包含指定范围内的所有枚举值的 EnumSet。

 

csharp

代码解读

复制代码

public enum Color { RED, GREEN, BLUE, YELLOW, PURPLE } public class EnumSetExample { public static void main(String[] args) { // 创建一个空的 EnumSet EnumSet<Color> emptySet = EnumSet.noneOf(Color.class); System.out.println("Empty Set: " + emptySet); // 创建一个包含指定元素的 EnumSet EnumSet<Color> someColors = EnumSet.of(Color.RED, Color.GREEN); System.out.println("Some Colors: " + someColors); // 创建一个包含另一个集合所有元素的 EnumSet Set<Color> anotherSet = new HashSet<>(Arrays.asList(Color.BLUE, Color.YELLOW)); EnumSet<Color> copiedSet = EnumSet.copyOf(anotherSet); System.out.println("Copied Set: " + copiedSet); // 创建一个包含指定范围内的所有枚举值的 EnumSet EnumSet<Color> rangeSet = EnumSet.range(Color.GREEN, Color.PURPLE); System.out.println("Range Set: " + rangeSet); // 添加和删除元素 someColors.add(Color.BLUE); someColors.remove(Color.RED); System.out.println("Modified Set: " + someColors); // 检查元素是否存在 boolean containsGreen = someColors.contains(Color.GREEN); System.out.println("Contains Green: " + containsGreen); // 遍历集合 for (Color color : someColors) { System.out.println(color); } } }

用EnumMap代替序数索引

EnumMap 内部使用一个数组来存储键值对,数组的索引对应枚举常量的序号(ordinal)。 枚举常量的序号是从 0 开始的,因此 EnumMap 可以通过枚举常量的序号快速定位到对应的值。相比序数索引,EnumMap 是类型安全的,只能接受特定枚举类型的键。由于 EnumMap 内部使用数组存储键值对,因此查找、插入和删除操作的时间复杂度都是 O(1)。

 

csharp

代码解读

复制代码

public enum DayOfWeek { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY } public class EnumMapExample { public static void main(String[] args) { // 创建一个空的 EnumMap EnumMap<DayOfWeek, String> emptyMap = new EnumMap<>(DayOfWeek.class); System.out.println("Empty Map: " + emptyMap); // 创建一个包含初始键值对的 EnumMap EnumMap<DayOfWeek, String> dayMap = new EnumMap<>(DayOfWeek.class); dayMap.put(DayOfWeek.MONDAY, "星期一"); dayMap.put(DayOfWeek.TUESDAY, "星期二"); dayMap.put(DayOfWeek.WEDNESDAY, "星期三"); System.out.println("Initial Map: " + dayMap); // 添加和删除键值对 dayMap.put(DayOfWeek.THURSDAY, "星期四"); dayMap.remove(DayOfWeek.MONDAY); System.out.println("Modified Map: " + dayMap); // 获取值 String value = dayMap.get(DayOfWeek.TUESDAY); System.out.println("Value for TUESDAY: " + value); // 检查键是否存在 boolean containsKey = dayMap.containsKey(DayOfWeek.FRIDAY); System.out.println("Contains FRIDAY: " + containsKey); // 遍历映射 for (Map.Entry<DayOfWeek, String> entry : dayMap.entrySet()) { System.out.println(entry.getKey() + ": " + entry.getValue()); } // 创建一个包含另一个映射所有键值对的 EnumMap Map<DayOfWeek, String> anotherMap = new HashMap<>(); anotherMap.put(DayOfWeek.FRIDAY, "星期五"); anotherMap.put(DayOfWeek.SATURDAY, "星期六"); EnumMap<DayOfWeek, String> copiedMap = new EnumMap<>(anotherMap); System.out.println("Copied Map: " + copiedMap); } }

Lambda 和Stream

Lambda优先于匿名类

对于函数接口,建议使用Lambda。Lambda表达式支持类型推断,减少了显式类型声明的需要,使代码更加简洁和易读。JVM对Lambda表达式进行了优化,提高了运行时性能。Lambda表达式支持函数式编程风格,便于进行流式操作,通过使用Lambda表达式,可以显著提升代码的质量和开发效率。

但是,Lambda没有名称和文档,如果一个计算本身不是自描述的,或者超出了几行,就不要放在一个lambda里面。在lambda表达式中的this表示外围实例,在匿名类中this表示匿名类实例,这里需要留意。

方法引用优先于Lambda

方法引用比lambda更简洁,如果lambda过于复杂,可以从lambda提取代码,放到新的方法中,使用该方法的一个引用代替lambda。使用方法引用需要先了解函数式接口:

  • Conmsumer,接受一个参数,不返回结果
 

arduino

代码解读

复制代码

import java.util.function.Consumer; public class ConsumerExample { public static void main(String[] args) { Consumer<String> consumer = System.out::println; consumer.accept("Hello, World!"); } }

  • Function<T, R>,接受一个参数,返回一个结果。
 

typescript

代码解读

复制代码

import java.util.function.Function; public class FunctionExample { public static void main(String[] args) { Function<String, Integer> function = Integer::parseInt; int result = function.apply("123"); System.out.println(result); // 输出 123 } }

  • Predicate,接受一个参数,返回一个布尔值。
 

typescript

代码解读

复制代码

import java.util.function.Predicate; public class PredicateExample { public static void main(String[] args) { Predicate<String> predicate = String::isEmpty; boolean result = predicate.test(""); System.out.println(result); // 输出 true } }

  • Supplier,不接受参数,返回一个结果
 

java

代码解读

复制代码

import java.util.function.Supplier; public class SupplierExample { public static void main(String[] args) { Supplier<Long> supplier = System::currentTimeMillis; long result = supplier.get(); System.out.println(result); // 输出当前时间的毫秒数 } }

  • UnaryOperator,接受一个参数,返回相同类型的结果。
 

arduino

代码解读

复制代码

import java.util.function.UnaryOperator; public class UnaryOperatorExample { public static void main(String[] args) { UnaryOperator<String> operator = String::toUpperCase; String result = operator.apply("hello"); System.out.println(result); // 输出 HELLO } }

  • BiConsumer<T, U>,接受两个参数,不返回结果。
 

arduino

代码解读

复制代码

import java.util.function.BiConsumer; public class BiConsumerExample { public static void main(String[] args) { BiConsumer<String, String> biConsumer = System.out::println; biConsumer.accept("Hello, ", "World!"); } }

  • BiFunction<T, U, R>,接受两个参数,返回一个结果。
 

arduino

代码解读

复制代码

import java.util.function.BiFunction; public class BiFunctionExample { public static void main(String[] args) { BiFunction<Integer, Integer, Integer> biFunction = Integer::sum; int result = biFunction.apply(3, 5); System.out.println(result); // 输出 8 } }

  • 自定义函数接口
 

arduino

代码解读

复制代码

@FunctionalInterface public interface MyFunction<T, R> { R apply(T t); } public class CustomFunctionExample { public static void main(String[] args) { MyFunction<String, Integer> myFunction = Integer::parseInt; int result = myFunction.apply("123"); System.out.println(result); // 输出 123 } }

方法

检查参数有效性

应该在执行方法之前对参数进行检查,如果传递了无效参数,应该尽快·出现合适的异常进行提示。对于公有或者受保护的方法,要用Javadoc@throws标签在文档中说明违反参数限制时会跑出的异常。 经常需要对空指针进行检查,可以使用java.util.Objects#requireNonNull(T, java.lang.String):

 

typescript

代码解读

复制代码

public static <T> T requireNonNull(T obj, String message) { if (obj == null) throw new NullPointerException(message); return obj; }

对于越界检查,也有相应的工具方法:java.util.Objects#checkFromToIndex(int, int, int),java.util.Objects#checkIndex(int, int)。

慎用重载

对象的运行时类型并不影响哪个重载版本将被执行,选择工作在编译时就已经确定,这不同于重写,当调用被覆盖的方法时,最具体的子类的方法将被调用,而不是其父类的方法。

应该避免使用具有相同参数数目的重载方法,可以通过修改方法名称的方式实现这一点。

慎用可变参数

每次调用可变参数方法,都涉及到一次数组的分配和初始化。因为可变参数的机制首先会创建一个数组,数组的大小为调用时传递的参数的数量,然后将参数传值传入数组,再将数组传递给方法。

 

arduino

代码解读

复制代码

public class VarargsExample { public static void main(String[] args) { int sum = sumIntegers(1, 2, 3, 4, 5); System.out.println("Sum: " + sum); } public static int sumIntegers(int... numbers) { int sum = 0; for (int number : numbers) { sum += number; } return sum; } }

在重视性能的情况下,如果无法承受创建数组的成本,可以通过重载方法的方式避免使用可变参数,比如90%的情况参数不会超过5个,那就定义五个重载方法,超过5个参数的再使用可变参数。

 

csharp

代码解读

复制代码

public void foo() { } public void foo(int a1) { } // 省略 public void foo(int a1, int a2, int a3, int a4, int a5) { } public void foo(int a1, int a2, int a3, int a4, int a5, int... restArgs) { }

返回0长度的数组或者集合,而不是null

bad case:

 

csharp

代码解读

复制代码

private final List<Integer> codes =...; public List<Integer> getCodes() { return codes.isEmpty() ? null : new ArrayList<>(codes); }

调用方需要专门处理返回null的情况,这容易导致出错。 对于返回没有包含元素的list的情况,可以使用java.util.Collections#emptyList,对于返回set的情况,可以使用java.util.Collections#emptySet,对于返回map的情况,可以使用java.util.Collections#emptyMap。

异常

受检异常与运行时异常

受检异常是指那些在编译时必须处理的异常。也就是说,如果一个方法可能会抛出受检异常,那么调用该方法的代码必须显式地处理这个异常,要么通过 try-catch 块捕获,要么通过 throws 关键字声明抛出。非受检异常是指那些在编译时不需要强制处理的异常。这类异常通常是由于程序逻辑错误引起的,如空指针异常、数组越界等。非受检异常继承自 RuntimeException 或其子类。

优先使用标准异常

  • IllegalArgumentException:当传递给方法的参数不合适时抛出。

  • IllegalStateException:当调用方法的对象处于不允许该方法调用的状态时抛出。

  • NullPointerException:当尝试访问空对象的成员时抛出。

  • UnsupportedOperationException:当不支持某个操作时抛出。

  • IndexOutOfBoundsException:当索引超出范围时抛出。

  • ConcurrentModificationException:当检测到并发修改时抛出。

不要忽略异常

 

csharp

代码解读

复制代码

public class IgnoreExceptionExample { public static void readFile() { try { FileReader reader = new FileReader("file.txt"); // 其他读取文件的操作 reader.close(); } catch (IOException e) { // 忽略异常 } } }

空的catch块会使异常达不到应有的目的,抛出了异常,但是异常没有被处理或者传播出去,导致问题被隐藏,不能及时暴露,会给排查造成阻碍。如果确定不需要处理,最好加一条注释。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值