【effective Java读书笔记】类和接口(二)

来源:互联网 发布:ipa 反编译源码 编辑:程序博客网 时间:2024/06/05 05:18

前言:

这一章有干货!不可变类、代码结构的梳理通过采用接口和抽象类结合搭架子,通过抽象类实现适配器,通过接口处理策略模式等。

正文:

第15条:使可变性最小化

第一个概念:不可变类;

1.不可变类举例:

String,基本类型的包装类,BigInteger和BigDecimal

2.关于不可变类的五要素,

见具体代码如下:

//1.final 保证类不会被扩展public final class Complex {//2.使所有的域都是final的,一旦初始化不能改变//3.使所有的域都是私有的。虽然final域不可改变,但是非私有意味着始终提供维护//5.不提供任何修改对象属性的方法private final double re;private final double im;public Complex(double re,double im) {this.re = re;this.im = im;}public Complex add(Complex c) {//4.返回对象引用,需要保护性拷贝return new Complex(c.re+re,c.im+im);}public Complex substract(Complex c){return new Complex(re-c.re, im-c.im);}}

注:关于第一点,BigInteger,BigDecimal是可被继承的,不是final的;

注:关于注释第四点,又叫函数式做法。

例如BigDecimal的add方法:

private static BigDecimal add(longxs,long ys, int scale){

        long sum = add(xs,ys);

        if (sum!=INFLATED)

            return BigDecimal.valueOf(sum,scale);

        return new BigDecimal(BigInteger.valueOf(xs).add(ys),scale);

    }

3.不可变类的优点:

本质上是线程安全的,不要求同步。即并发访问对象时,对象不会受到其他干扰导致对象内部改变。

4.不可变类应该尽可能重用类的实例。

为什么要重用?因为创建对象的开销。因此基本类型的包装类和BigInteger都有这样的静态工厂。俗称常量池。代码如下

private static class IntegerCache {

        static finalintlow = -128;

        static final int high;

        static final Integercache[];


        static {

            // high value may be configured by property

            int h = 127;

            String integerCacheHighPropValue =

                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");

            if (integerCacheHighPropValue !=null) {

                try {

                    int i = parseInt(integerCacheHighPropValue);

                    i = Math.max(i, 127);

                    // Maximum array size is Integer.MAX_VALUE

                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);

                } catch( NumberFormatExceptionnfe) {

                    // If the property cannot be parsed into an int, ignore it.

                }

            }

            high = h;


            cache = new Integer[(high - low) + 1];

            int j =low;

            for(intk = 0;k < cache.length;k++)

                cache[k] =new Integer(j++);


            // range [-128, 127] must be interned (JLS7 5.1.7)

            assert IntegerCache.high >= 127;

        }


        private IntegerCache() {}

    }

因此,面试中常常有这样的面试题:

@Testpublic void test(){Integer integer = new Integer(2);Integer integer2 = new Integer(2);System.out.println(integer == integer2);Integer integer3 = 2;Integer integer4 = 2;System.out.println(integer3==integer4);Integer integer5 = 128;Integer integer6 = 128;System.out.println(integer5==integer6);}
结果是什么?

false

true

false

原因不再多说。往上翻。。。

5.不可变类唯一的缺点,

对于每个不同的值都需要一个单独的对象。例如:

BigInteger bigInteger = new BigInteger("1000000");System.out.println(bigInteger);bigInteger = bigInteger.flipBit(0);System.out.println(bigInteger);
执行结果:

1000000

1000004

两个不同的对象。

BigInteger bigInteger = new BigInteger("1000000");for (int i = 0; i < 10000; i++) {bigInteger = bigInteger.flipBit(0);}System.out.println(bigInteger);
假如这样,则产生了10000个对象。这个时候,当然就回到了之前讲的问题。

用一句话总结就是:基本类型的包装类则转化为基本类型运算。其他类型例如String,BigInteger则使用辅助类StringBuilder,BitSet

6.通过静态工厂方法使不可变类final

静态工厂的优势不再多说,看代码第6点:

//1.final 保证类不会被扩展public class Complex2 {//2.使所有的域都是final的,一旦初始化不能改变//3.使所有的域都是私有的。虽然final域不可改变,但是非私有意味着始终提供维护//5.不提供任何修改对象属性的方法private final double re;private final double im;private Complex2(double re,double im) {this.re = re;this.im = im;}//6.保证类不会被扩展的第二种方式,采用静态工厂public static Complex2 valueOf(double re,double im){return new Complex2(re, im);}public Complex2 add(Complex2 c) {//4.返回对象引用,需要保护性拷贝return new Complex2(c.re+re,c.im+im);}public Complex2 substract(Complex2 c){return new Complex2(re-c.re, im-c.im);}}

第16条,复合优先于继承

复合其实也就是包装类的意思。

先看一个段继承的代码:

public class InstrumentedHashSet<E> extends HashSet<E>{private int addCount = 0;public InstrumentedHashSet() {}@Overridepublic boolean add(E e) {addCount++;return super.add(e);}@Overridepublic boolean addAll(Collection<? extends E> c) {addCount += c.size();return super.addAll(c);}public int getAddCount() {return addCount;}}
单元测试如下:

public class Test2 {@Testpublic void test(){InstrumentedHashSet<String> strs = new InstrumentedHashSet<>();strs.addAll(Arrays.asList("Snap","Crackle","Pop"));System.out.println(strs.getAddCount());}}
执行结果如下:

6

为什么不是3?明明只加了3个元素。原因HashSet的addAll是在add基础上实现的。

public boolean addAll(Collection<?extends E>c) {

        boolean modified =false;

        for (E e :c)

            if (add(e))

                modified = true;

        return modified;

    }

那么相当于add方法执行了3次,addAll方法加了个3。因此一切的罪魁祸首是覆盖操作的时候,没有考虑父类的方法之间的关联关系。

解决方案即是包装类,对父类代码无修改,代码如下:

public class ForWardHashSet<E>{private HashSet<E> hashSet;private int addCount = 0;public ForWardHashSet(HashSet<E> hashSet) {this.hashSet = hashSet;}public boolean add(E e) {addCount++;return hashSet.add(e);}public boolean addAll(Collection<? extends E> c) {addCount += c.size();return hashSet.addAll(c);}public int getAddCount() {return addCount;}}
单元测试如下:
public class Test2 {@Testpublic void test(){ForWardHashSet<String> strs2 = new ForWardHashSet<>(new HashSet<>());strs2.addAll(Arrays.asList("Snap","Crackle","Pop"));System.out.println(strs2.getAddCount());}}
执行结果:

3

总结:包装类的好处之前也说过,扩展性好,无侵入。只有当存在实际的包含情况才需要用到继承extends。


第17条,要么为继承而设计并提供文档说明,要么禁止继承;

此处仅仅有个原则需要注意:构造器不能调用可被覆盖的方法.

父类代码:

public class Super {public Super() {overRideMe();}public void overRideMe() {}}

子类代码:

public class Sub extends Super {private final Date date;public Sub() {date = new Date();}@Overridepublic void overRideMe() {System.out.println(date);}}
单元测试:

@Testpublic void test(){Sub sub = new Sub();sub.overRideMe();}
执行结果:第一条是父类执行的overRideMe,父类的overRideMe被子类覆盖后,应该会直接执行子类的overRideMe,然而date为null,所以第一条其实是null。打印出来的是第二条。

null

Wed Jul 19 14:40:55 CST 2017


总结:覆盖这个方法会导致异常,然而父类并没有说明或者禁止。

第18条 接口优先于抽象类;

略,接口、抽象类提供骨架,可以参考

AbstractList,AbstractCollection,Collection。同时可以通过抽象类,写适配器Adapter。


第19条 接口只用于定义类型;



第20条 类层次优先于标签类

略,一般能用标签类(通过枚举判断,进行各种构造方法初始化),都可以改造为接口类层次。


第21条 用函数对象表示策略

略,计划写一篇策略模式;


第22条 优先考虑静态成员类;

略;