zoukankan      html  css  js  c++  java
  • 第15条:使可变性最小化

    不可变类是实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例的时候就提供,并在对象的整个生命周期内固定不变。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计算的结果都是一致的。

  • 相关阅读:
    Ubuntu 16.04实现SSH无密码登录/免密登录/自动登录(ssh-keygen/ssh-copy-id)
    简单理解Linux的Loopback接口
    iptables为什么需要增加loopback回环的规则
    [ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配
    [ASP.NET Core 3框架揭秘] 依赖注入[9]:实现概述
    [ASP.NET Core 3框架揭秘] 依赖注入[8]:服务实例的生命周期
    [ASP.NET Core 3框架揭秘] 依赖注入[7]:服务消费
    [ASP.NET Core 3框架揭秘] 依赖注入[6]:服务注册
    [ASP.NET Core 3框架揭秘] 依赖注入[5]: 利用容器提供服务
    AOP框架Dora.Interception 3.0 [5]: 基于策略的拦截器注册方式
  • 原文地址:https://www.cnblogs.com/13jhzeng/p/5681845.html
Copyright © 2011-2022 走看看