Quantcast
Channel: CodeSection,代码区,Linux操作系统:Ubuntu_Centos_Debian - CodeSec
Viewing all articles
Browse latest Browse all 11063

Effective java notes [PART 6]

$
0
0
No.36坚持使用Override注解

关于本章,Joshua Bloch的主要观点是我们本以为覆盖的地方实际没有。

作者举了一个覆盖equals的例子。

import java.util.Set;
import java.util.HashSet;
public class Bigram {
private final char first;
private final char second;
public Bigram(char first, char second) {
this.first = first;
this.second = second;
}
public boolean equals(Bigram value) {
return b.first == first && b.second == second;
}
public int hashCode() {
return 31 * first + second;
}
public static void main(String[] args) {
Set<Bigram> s = new HashSet<>();
for (int i = 0; i < 10; i++) {
for (char ch = 'a'; ch <= 'z'; ch++) {
s.add(new Bigram(ch, ch));
}
}
System.out.println(s.size());
}
}

输出结果是260,因为在HashSet中比较元素时,还用的未被覆盖的比较方式==,由于各个元素重新分配了空间,因而就不等了。sample中新生成的equals并未生效。为什么呢?

因为要覆盖equals,需要接受Object类型的参数,若注明了@Override,错误就显而易见了。更正如下:

@Override
public boolean equals(Object value) {
Bigram b = (Bigram) value;
return b.first == first && b.second == second;
} No.37慎用重载

看下Joshua Bloch给的例子:

public class CollectionClassifier {
public static String classify(Set<?> s) {
return "Set";
}
public static String classify(List<?> s) {
return "List";
}
public static String classify(Collection<?> c) {
return "Unknown";
}
public static void main(String[] args) {
Collection<?>[] collections = {
new HashSet<String>(),
new ArrayList<BigInteger>(),
new HashMap<String, String>().values()
};
for (Collection<?> c : collections) {
System.out.println(classify(c));
}
}
}

我们期待会依次打印”Set”, “List”, “Unknown”。

实际是打印了3次”Unknown”,为什么呢?

因为classify方法被重载了,而要调用哪个重载方法是在编译时做出决定的,参数编译时类型都是相同的: Collection<?>

我们常会将覆盖方法和重载方法进行比较,前者的选择则是动态的,选择被覆盖的方法的正确版本是在运行时进行的,选择的依据是被调用方法所在对象的运行类型。

class Wine {
String name() {return "wine";}
}
class SparklingWine extends Wine {
@Override
String name() {return "sparkling wine";}
}
class Champagne extends SparklingWine {
@Override
String name() {return "champagne";}
}
public class Overriding {
public static void main(String[] args) {
Wine[] wines = {new Wine(), new SparklingWine(), new Champagne{}};
for (Wine wine : wines) {
system.out.println(wine.name());
}
}
}

output: wine, sparkling wine, champagne.

另一个常见的重载错误:

public static void main(String[] args) {
Set<Integer> setT = new TreeSet<>();
List<Integer> listT = new ArrayList<>();
for (int i = 0; i < 6; i++) {
setT.add(i);
listT.add(i);
}
for (int i = 0; i < 3; i++) {
setT.remove(i);
listT.remove(i);
}
System.out.println(setT);
System.out.println(listT);
}

listT并没有如我们所愿,去移除前三个元素,而是移除了奇数位的元素。

因为list<E> 包含2个remove的实现,remove<E>以及remove(int), 后者是移除index位置的元素,而index是动态变化的。

关于重载,Joshua Bloch给了一点极端的建议,可以参考下:

始终为方法起不同的名字,而不使用重载机制,能够重载方法并不意味着应该重载方法。

例如,ObjectOutputStream类,对于每个基本类型,以及几种引用类型,它的write方法都有一种变形,这些变形并不是重载write方法,而是具有诸如writeBoolean(boolean), writeInt(int), writeLong(long)这样的签名。

No.38只针对异常的情况才使用异常

为什么使用异常?

可以提高程序的可读性、可靠性和可维护性。

异常是为了在异常情况下使用而设计的,勿将它们用于普通的控制流,可能掩藏了错误本身。

No.39对可恢复的情况使用受检异常,对编程错误使用运行时异常

何时使用受检异常?

使用受检异常,调用者可以发现并进行处理。

未受检异常包括运行时异常和错误:

都是不需要捕获的,往往属于不可恢复的情形。

Joshua Bloch 给出建议:对于可以恢复的情况,使用受检的异常;对于程序错误,使用运行异常。

No.40避免不必要地使用受检的异常

受检的异常是程序设计的一项很好的特性,会强迫开发者处理异常的条件,大大增强可靠性;但是过分使用受检异常会使api使用起来非常不方便。

No.41优先使用标准的异常

常见的异常:

IllegalArgumentException

IllegalStateException

NullPointerException

IndexOutOfBoundsException

ConcurrentModificationException 在禁止并发修改的情况下,检测到对象的并发修改。

UnsupportedOperationException 对象不支持用户请求的方法。

No.42抛出与抽象相对应的异常

若方法抛出的异常和其所执行的任务没有明显的关系(当方法传递由底层抽象抛出,与更高层间的关系),会让人困惑。如何避免呢?

刚高层的实现应该捕获低层的异常,同时抛出可以按照高层抽象进行解释的异常,该行为被称作异常转译。

try {
//...
} catch (LowerLevelException e) {
throw new HigherLevelException(...);
}

关于异常转译,作者给了一些经验之谈:

1. 尽管异常转译和不加选择地从底层传递异常的做法相比有所改进,但是不能被滥用。应尽量避免抛出异常,在给底层传递参数之前,检查更高层方法的参数的有效性,从而避免低层方法抛出异常。

我的理解是,保证代码的准确性和稳定性,使用try来给自己的代码留有余地,严格一点。

2. 可以绕开异常,将高层方法的调用者和低层的问题隔离开来。

我的理解是,在一些不可避免的异常模块,我们没有必要一定throw出去,一些没有必要通知高层的部分,我们以log形式记录日志即可了。


Viewing all articles
Browse latest Browse all 11063

Trending Articles