您的位置 首页 java

Effective Java 使可变性最小化

不可变类是实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例的时候就提供,并在对象的整个生命周期内固定不变。Java平台包含的不可变类:String、基本类型的包装类、BigInteger、BigDecimal。不可变类比可变类更容易设计、实现和使用。

为了使类成为不可变,要遵循五条规则:

1.不要提供任何会修改对象状态的方法。

2.保证类不会被扩展,为了防止恶意子类假装对象状态被改变,从而破坏不可变。

3.使所有的域都是final的。

4.使所有的域都成为私有的,防止客户端获得访问被域引用的可变对象的权限,防止客户端直接修改这些对象,从技术上讲,允许不可变的类具有公有的final域,只要这些域包含基本类型的值或者指向不可变对象的引用,但不建议这样做,因为会导致无法再以后的版本改变内部表示法。

5.确保对任何可变组件的互斥访问,如果类具有指向可变对象的域,则必须确保该类的客户端无法获得指向这些对象的引用,永远不要用客户端提供的对象引用来初始化这样的域,也不要从任何访问方法中返回该对象的引用。在 构造器 、访问方法和readObject方法中使用保护性拷贝。

一个复数例子:

 public final class  Complex  {
    private final  Double  re;
    private final double im;
    
    public Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }
    
    public double realPart() {//提供访问实部的方法
        return re;
    }
    
    public double imaginaryPart() {//提供访问虚部的方法
        return im;
    }
    
    public Complex add(Complex c) {
        return new Complex(re + c.re, im + c.im);
    }
    
    public Complex subtract(Complex c) {
        return new Complex(re - c.re, im - c.im);
    }
    
    public Complex multiply(Complex c) {
        return new Complex(re * c.re - im * c.im, re * c.im + im * c.re);
    }
    
    public Complex divide(Complex c) {
        double tmp = c.re * c.re + c.im * c.im;
        return new Complex((re * c.re + im * c.im) / tmp,
                (im * c.re - re * c.im) / tmp);
    }
    
    @ Override 
    public boolean equals(Object o) {
        if(o == this)
            return true;
        if(!(o instanceof Complex))
            return false;
        Complex c = (Complex) o;
        return Double.compare(re, c.re) == 0 && Double.compare(im, c.im) == 0;
    }
    
    @Override
    public int  hashCode () {
        int result = 17 * hashDouble(re);
        result = 31 * result + hashDouble(im);
        return result;
    }
    
    private int hashDouble(double val) {
        long longBits = Double.doubleToLongBits(val);
        return (int)(longBits ^ (longBits >>> 32));
    }
    
    @Override
    public String toString() {
        char s = '-';
        return "(" + re + " + " + im + "i)";
    }
    
}  

加法、减法、乘法和除法四种基本的算术运算返回的是一个新的Complex实例,而不是修改这个实例。这样实例的状态就无法被改变(状态在实例创建的时候就确定了,并且没有提供改变状态的方法),Complex类成为一个不可变类。

不可变对象本质上是线程安全的,不要求同步。因为状态无法被修改,所以多线程并发访问这样的对象不会破坏它的不可变性。客户端应该尽可能地重用现有的实例。例如Complex类可能提供下面的常量:

 public static final Complex ZERO = new Complex(0, 0);  

进一步扩展,不可变类可以提供一些静态工厂,把频繁被请求的实例缓存起来,从而当现有实例可以符合请求时,不必创建新的实例:

 private static final Complex ZERO = new Complex(0, 0);

public Complex valueOfZero() {
    return ZERO;
}  

不可变对象可以被自由地共享导致,永远都不需要进行保护性拷贝,因为这些拷贝始终等于原始的对象,所以,不需要,也不应该为不可变类提供clone方法或者拷贝构造器。

不可变对象也可以共享它们的内部信息。如BigInteger类的内部使用了符号数值表示法。符号用一个int类型的值signum表示,数值则用一个int数组mag表示。它有一个negate方法产生一个新的BigInteger,其中数值是一样的,符号是相反的,它不需要拷贝数组,新建的BigInteger也指向原始实例中的同一个内部数组。

 public BigInteger negate() {
        return new BigInteger(this.mag, -this.signum);//构造器传入的参数是原始实例的数组this.mag
}  

不可变对象真正唯一的缺点是,对于不同的值都需要一个单独的对象。考虑String的“+”,String s = “a”+“b”会创建三个字符串“a”、“b”、“ab”

让不可变类变成final,除了在类声明为final外,还有让类的所有构造器变成私有的或者包级私有的,并添加公有的静态工厂来代替公有的构造器。

 public Class Complex {
    private final double re;
    private final double im;

    private Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }

    public static Comolex valueOf(double re, double im) {
        return new Complex(re, im);
    }
}  

优势:如果希望提供一种基于“极坐标创建复数”的方式,使用这样的构造器,因为与Complex(double,double),参数类型一致,必要通过另外的方法在使得不会与原构造器冲突。通过静态工厂,很容易做到:

 public static Complex valueOfPolar(double r, double theta) {
    return new Complex(r * Math.cos(theta), r * Math.sin(theta));
}  

实际上,不可变对象所有域都必须是final的,过于严格,为了提高性能可以有所放松,比如String类有一个private int hash;域,当hashCode方法第一次被调用时,把结果放在hash域中,之后调用hashCode方法直接返回hash的值,减少了计算开销,有可能提高性能。因为String被设计成不可变类,所以每次可以确保hashCode计算的结果都是一致的。

文章来源:智云一二三科技

文章标题:Effective Java 使可变性最小化

文章地址:https://www.zhihuclub.com/194219.shtml

关于作者: 智云科技

热门文章

网站地图