Effective Java读书笔记-4

try-with-resources优先于try-finally

Java类库中包括许多必须通过调用close方法来手工关闭的资源。例如InputStream、OutputStream和java.sq1.connection。客户端经常会忽略资源的关闭,对性能造成严重影响。try-finally语句是确保资源会被适时关闭的最佳方法,就算发生异常或者返回也一样:

    // try-finally-No longer the best way to close resources!
    static String firstLineOfFile(String path) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(path));
        try {
            return br.readLine();
        } finally {
            br.close();
        }
    }  

如果再添加第二个资源,就会导致代码很混乱:

        // try-finally is ugly when used with more than one resource!
        static void copy(String src, String dst) throws IOException {
            InputStream in = new FileInputStream(src);
            try {
                OutputStream out = new FileOutputStream(dst);
                try {
                    byte[] buf = new byte[BUFFER_SIZE];
                    int n;
                    while ((n = in.read(buf)) >= 0) {
                        out.write(buf, 0, n);
                    } finally{
                        out.close();
                    } finally{
                        in.close();
                    }
                }
            }
        }

即便用try-finally语句正确地关闭了资源,如前两段代码范例所示,它也存在着些许不足。因为在try块和finally块中的代码,都会抛出异常。例如,在firstLineOfFile方法中,如果底层的物理设备异常,那么调用readLine就会抛出异常,基于同样的原因,调用close也会出现异常。在这种情况下,第二个异常完全抹除了第一个异常。在异常堆栈轨迹中,完全没有关于第一个异常的记录,导致系统调试变得非常复杂,因为通常需要看到第一个异常才能诊断出问题的原因。
Java7引人的try-with-resources语句,使这些问题一下子全部解决了。要使用这个构造的资源,必须先实现AutoCloseable接口,其中包含了单个返回void 的close方法。 Java类库与第三方类库中的许多类和接口,现在都实现或扩展了AutoCloseable接口。如果编写了一个类,它代表的是必须被关闭的资源,那么这个类也应该实现AutoCloseable。
以下就是使用try-with-resources的第一个范例:

    // try-with-resources-the the best way to close resources!
    static String firstLineOfFile(String path) throws IOException {
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            return br.readLine();
        }
    }

以下是使用try-with-resources的第二个范例:

    // try-with-resources on multiple resources-short and sweet
    static void copy(String src, String dst) throws IOException {
        try (InputStream  in = new FileInputStream(src);
             OutputStream out = new FileOutputStream(dst)) {
            byte[] buf = new byte[BUFFER_SIZE];
            int n;
            while ((n = in.read(buf)) >= 0) {
                out.write(buf, 0, n);
            }
        } 
    }

使用try-with-resources不仅使代码变得更简洁易懂,也更容易进行诊断。以firstLineOfFile方法为例,如果调用readLine和(不可见的)close方法都抛出异常,后一个异常就会被禁止,以保留第一个异常。这些被禁止的异常并不是简单地被抛弃了,而是会被打印在堆栈轨迹中,并注明它们是被禁止的异常。通过编程调用getSuppressed方法还可以访问到它们,getsuppressed方法也已经添加在Java7的Throwable中了。
在try-with-resources语句中还可以使用catch子句,就像在平时的try-finally语句中一样。这样既可以处理异常,又不需要再套用一层代码。下面举一个稍费了点心思的范例,这个firstLineOfFile方法没有抛出异常,但是如果它无法打开文件,或者无法从中读取,就会返回一个默认值:

    // try-with-resources with a catch clause
    static String firstLineOfFile(String path, String defaultVal){
        try (BufferedReader br = new BufferedReader(new FileReader(path)) {
            return br.readLine();
        } catch (IOException e) {
            return defaultVal;
        }
    }

结论很明显:在处理必须关闭的资源时,始终要优先考虑用try-with-resources,而不是用try-finally。 这样得到的代码将更加简洁、清晰,产生的异常也更有价值。

覆盖equals时请遵守通用约定

在覆盖equals方法的时候,必须要遵守它的通用约定。 下面是约定的内容,来自Object的规范。
equals方法实现了等价关系(equivalence relation),其属性如下:
自反性(reflexive):对于任何非null的引用值x,x.equals(x)必须返回true。
对称性(symmetric):对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。
传递性(transitive): 对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true。
一致性(consistent): 对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致地返回true,或者一致地返回false。
对于任何非null的引用值x,x.equals(null)必须返回false。
一个类的实例通常会被频繁地传递给另一个类的实例。许多类,包括所有的集合类(collection class)在内,都依赖于传递给它们的对象是否遵守了equals约定。

对称性(Symmetry)要求是说,任何两个对象对于“它们是否相等”的问题都必须保持一致。例如下面的类忽视了对称性,它实现了一个区分大小写的字符串。字符串由toString保存,但在equals 操作中被忽略。

//Broken-violatessymmetry!
public class CaseInsensitiveString {
        private final String s;

        public CaseInsensitiveString(String s) {
            this.s = Objects.requireNonNull(s);
        }
        // Broken - violates symmetry!
        @Override
        public boolean equals(Object o) {
            if (o instanceof CaseInsensitiveString) {
                return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
            }
            // One-way interoperability!
            if (o instanceof String) {
                return s.equalsIgnoreCase((String) o);
            }
            return false;
        }
        // Remainder omitted
}

在这个类中,equals方法图与普通的字符串对象进行互操作。假设我们有一个不区分大小写的字符串和一个普通的字符串:

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s="polish";

cis.equals(s) 会返回true。问题在于,虽然 CaseInsensitiveString 类中的equals方法知道普通的字符串对象,但是,String类中的equals方法却并不知道不区分大小写的字符串。因此,s.equals(cis) 会返回false,违反了对称性。假设把不区分大小写的字符串对象放到一个集合中:

List<CaseInsensitiveString> list = new ArrayList<>();
list.add(cis);

此时list.contains(s) 很难确定返回的结果。一旦违反了equals约定,当其他对象面对你的对象时,你完全不知道这些对象的行为会怎么样。
为了解决这个问题,只需把企图与String互操作的这段代码从equals方法中去掉就可以了。这样做之后,就可以重构该方法,使它变成一条单独的返回语句:

    @Override 
    public boolean equals(Object o) {
        return o instanceof CaseInsensitiveString &&
               ((CaseInsensitiveString)o).s.equalsIgnoreCase(s);
    }

方法:

1. 使用==操作符检查“参数是否为这个对象的引用”
2. 使用instanceof操作符检查“参数是否为正确的类型”为空。
3. 把参数转换成正确的类型
4. 对于该类中的每个“关键”(significant )域,检查参数中的域是否与该对象中对应的域相匹配。
5. 在编写完equals方法之后,应该问自己三个问题:它是否是对称的、传递的、一致的?

注意:
1. 覆盖equals时总要覆盖hashCode 。
2. 不要企图让equals方法过于智能 。
3. 不要将equals声明中的Object对象替换为其他的类型:这个方法并没有覆盖(override)Object.equals,因为它的参数应该是Object类型,相反,它重载(overload)了Object.equals 。

public class PhoneNumber {
    private final short areaCode, prefix, lineNum;
 
    public PhoneNumber(int areaCode, int prefix, int lineNum) {
        this.areaCode = rangeChek(areaCode, 999, "area code");
        this.prefix = rangeChek(prefix, 999, "pre fix");
        this.lineNum = rangeChek(lineNum, 999, "lineNum");
    }
 
    private static short rangeChek(int val, int i, String arg) {
        if (val < 0 || val > i) {
            throw new IllegalArgumentException(arg + ":" + val);
        }
        return (short) val;
    }
    
    /**
     * @param o
     * @return boolean
     * @Description
     * 使用==操作符检查“参数是否为这个对象的引用”
     * 使用instanceof操作符检查“参数是否为正确的类型”为空。
     * 把参数转换成正确的类型
     * 对于该类中的每个“关键”(significant )域,检查参数中的域是否与该对象中对应的域相匹配。
     **/
    @Override
    public boolean equals(Object o) {
        // 使用==操作符检查“参数是否为这个对象的引用”
        if (this == o) {
            return true;
        }
 
        // 使用instanceof操作符检查“参数是否为正确的类型”为空。测试等同性的同时,测试非空性。
        if (!(o instanceof PhoneNumber)) {
            return false;
        }
        // 把参数转换成正确的类型
        PhoneNumber that = (PhoneNumber) o;
        // 对于该类中的每个“关键”(significant )域,检查参数中的域是否与该对象中对应的域相匹配。
        return areaCode == that.areaCode &&
                prefix == that.prefix &&
                lineNum == that.lineNum;
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(areaCode, prefix, lineNum);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值