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

前言:

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

正文:

第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 finalint low = -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() {}

    }

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

@Test
	public 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() {
	
	}
	@Override
	public boolean add(E e) {
		addCount++;
		return super.add(e);
	}
	@Override
	public boolean addAll(Collection<? extends E> c) {
		addCount += c.size();
		return super.addAll(c);
	}
	
	public int getAddCount() {
		return addCount;
	}
}
单元测试如下:

public class Test2 {
	@Test
	public 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 {
	@Test
	public 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();
	}
	@Override
	public void overRideMe() {
		System.out.println(date);
	}
}
单元测试:

@Test
	public 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条 优先考虑静态成员类;

略;
















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值