Effective Java(第二版) 记录上

最近看了一本Effective Java觉得不错给大家推荐下,同时也做些记录。

读书不是一蹴而就,需要常常温故知新,并慢慢的融会贯通。所以这里主要是记录下作者看完本书的印象点,希望日后作者回顾起来,会想起一二。

写得有什么不对的地方,望各位大神不吝赐教,在此谢过。

记录上(1-6)--------------------------------------------------------------------------------------------------------------------------

一、对象篇(创建和销毁):

1、在类中考虑用静态工厂方法代替构造器。

静态工厂方法与构造器不同的优势:

1):静态工厂方法可以比构造器更能见名知意。

2):不必在每次调用他们的时候都创建一个新对象。如:不可变类可以预先创建好实例,或者将构建好的实例缓存起来进行重复利用,从而避免创建不必要的对象。

3):它们可以返回原返回类型的任何子类型对象。这样我们在选择返回对象类时有了更大的灵活性。(注:服务者提供框架和jdk1.5提供的EnumSet底层实现)

4):创建参数化类型实例时可以是代码更简洁。

eg:(1)Map<String,List<String>> map = new HashMap<String,List<String>>();

(2)public static <K,V> Map<K,V>  newInstance(){

return new HashMap<K,V>();

}

缺点:

1):类如果不含有公有或受保护的构造器,就不能被子类化。

2):他们其实与一般的静态方法没有是吗区别。

2、当遇到多个构造器参数时考虑用构建器(Builder模式)。

这里大家可能会想到,重叠构造器或javabean方式,前者会造成客户端代码较难以编写和阅读,后者构建过程javabean可能处于状态不一致。

Builder模式还可以在build方法进行约束条件检验,并且这种模式相对于构造器足够灵活。

3、消除过期的对象应用。

示例:

数组实现的栈结构:如果这个栈先增长在收缩,那么从栈弹出的对象将不会被当做垃圾回收,即使这个栈并不再会引用这些对象。

这是因为这个栈维护着这些对象的过期引用。所谓过期引用是指,永远不会再被解除的引用。

清理过期引用


4、避免使用终结方法。因为终结方法(finalizer)通常是不可预测,也是危险的,一般情况下是不必要的。

二、通用方法篇

1、覆写equals方法。

期望:

1)超类已经覆写了equals方法,那么从超类继承过来的行为也同样合适。

2)类时私有的或包级私有的,那么可以确定他的equals方法永远不会调用。这种情况应该覆写equals方法,防止意外调用。

注意:equals的等价关系:自反性,对称性,传递性,一致性。

2、覆写equals方法时总要覆写hashCode方法。

如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常的运作。如:HashMap。

3、谨慎的覆写clone方法。

注意:1)Cloneable接口决定了Object的clone的方法的行为。这种改变了超类的行为不值得效仿。

    2)实际上clone方法相当于另一个构造器,你必须确保他不会伤害另一个对象,并且正确的创建被克隆对象中的约束条件。

            3)clone架构与引用对象的final域的正常用法是不兼容的。

   4)可以使用拷贝构造器和拷贝工厂复制对象。

三、类型和接口篇

1、使类和成员的访问最小化。

注意:在实体类的使用上Java和Android的使用方法应该是不一样。

    java是通过get/set方法来获取属性值;但其实Android不推荐这种方式,Android推荐直接使用公有域来进行访问这中方式比get/set这种方式快约3倍。

具体文档如下:

Avoid Internal Getters/Setters


In native languages like C++ it's common practice to use getters (i = getCount()) instead of accessing the field directly (i = mCount). This is an excellent habit for C++ and is often practiced in other object oriented languages like C# and Java, because the compiler can usually inline the access, and if you need to restrict or debug field access you can add the code at any time.

However, this is a bad idea on Android. Virtual method calls are expensive, much more so than instance field lookups. It's reasonable to follow common object-oriented programming practices and have getters and setters in the public interface, but within a class you should always access fields directly.

Without a JIT, direct field access is about 3x faster than invoking a trivial getter. With the JIT (where direct field access is as cheap as accessing a local), direct field access is about 7x faster than invoking a trivial getter.

Note that if you're using ProGuard, you can have the best of both worlds because ProGuard can inline accessors for you.

2、复合是优先于继承。

注意:继承打破了封装的特性。

   装饰者模式。包装类不适合用于回调框架中。

3、接口优于抽象类。

区别:抽象类允许包含某些方法的实现,而接口是不允许的。更为重要的是为了实现抽象类的定义类型,类必须成为抽象类的子类。

   因为Java是单继承,所以抽象类作为类型定义受到了极大的限制。

优势:

1)现有的类可以很容易被跟新,以实现新的接口。

2)接口是定义混合类型的理想选择。注:类除了实现主要的类型之外,还可以实现mixin类型选择,以提供额外的行为选择。

3)接口可以让我们构造非层次的类型框架。注:抽象骨架实现类,接口任用于类型定义,但是骨架接口实现类接管了接口所有相关的实现相关工作 (如:集合框架中 的一些集合)

缺点:

抽象的演变比接口容易得多。如:新增某个方法。

4、类层次优先于标签类。

5、用函数对象表示策略。

注:在java中一般用接口实现这种策略模式,声明一个接口表示该策略,并且为每个具体的策略声明一个实现了该接口的类。如:Comparator接口。

6、优先考虑静态成员类。

静态成员类,非静态成员类,匿名类,局部类。除了第一种之外,其它三种都称为内部类。

静态成员类:是外部类的一个静态成员,与其他静态成员一样,同样遵守访问规则。如果被声明为私有的,则只有外部类的内部进行访问。

一般用于公用的辅助类。如:一个计算器的内部的一个枚举,表示支持的各种操作。

非静态成员类:非静态成员类的每个实例都隐含着与一个外部实例想关联。所以:如果一个嵌套类的实例想独立于外部类的实例存在,那么嵌套类必须是静态成员类。

  没有外部实例的情况下,想要创建非静态成员类的实例是不可能的。

注意:所以如果成员类不要求访问外部实例,就把该成员类声明为静态的,减少开销。

四、泛型


1)不要在新代码中使用原生态类型。

参数化类型<>。如:List<String>

原生类型:不带任何参数的泛型名称。如:List

注意:每个泛型都定义了一个原生态类型。如果使用了原生态类型将丢失泛型在安全性和表述性方面的所有优势。

2)消除非受检警告。

3)列表优先于数组。

区别:

1)数组是协变的。如:sub是super的子类型,那么sub[]就是super[] 的子类型。

      泛型是不可变的。对任意两个类型Type1和Type2,List<Type1>既不是 List<Type2>的子类型,也不是List<Type2>的超类型。

2)数组是具体化的,因此数组子啊运行时才检查类型约束。

      泛型是通过类型擦除来实现的。因此泛型在编译时强化它们的类型信息,在运行时擦除它们的元素类型信息。擦除就是可以使泛型与没有使用泛型的代码进行互用

由于上述原因,数组和泛型并不能很好的进行互用。如:List<String>[]是非法 的。如果你发现你们将它们混合起来使用得到错误或警告,你应该尝试用列表代替数组

4)优先考虑泛型和泛型方法。

泛型和泛型方法比客户端使用类型转换来得更加安全和更加容易。注:类型推到、恒等函数。

5)利用有限制的通配符来提升API的灵活性。

为了获得最大程度的灵活性,要在表示生产者和消费者的输入参数上使用通配符类型。

PECS:producter-extends,customer-super。换句话说就是,如果参数化类型T表示一个生产者就用<? extends T>,如果是一个消费者就用<? super T>

6)优先考虑类型安全的异构容器。

五、枚举和注解篇。

1)用枚举代替int常量。

注:特定于常量的方法实现,策略枚举。

2)用实例域代替序数。

每个枚举天生就和一个int值想关联。所有的枚举都有一个ordinal方法,返回每个枚举常量在类型中的数字位置。

永远不要根据枚举的序数到处与它关联的值,而是应该保存到一个实例域中。

3)用接口模拟可伸缩的枚举。

虽然无法编写可扩展的枚举,却可以通过编写接口以及实现该接口的基础枚举类型,对它进行模拟。

4)用标记接口定义类型。

标记接口:是没有包含方法声明的接口。

如果想要定义一个任何新方法都不会与之关联的类型,标记接口就是最好的选择。如果想要标记程序元素而非类和接口,考虑到未来可能要给标记添加更多信息或者标 记要适合于已经广泛使用了注解类型的框架,那么标记注解就是正确的选择。

六、方法

1)检查参数的有效性。

简而言之,当我们编写方法或构造器的时候,应该考虑到它的参数有哪些限制,通过显示的检查来实施这些限制。

2)必要时进行保护性拷贝。

注意:1)保护性拷贝是在检查参数有效性之前进行的,并且有效性检查是针对拷贝之后的对象,而不是针对原始对象。

    2)对于参数类型可以被不信任方子类化的参数时,请不要使用clone方法进行保护性拷贝。

    3)如果类具有从客户端得到或者返回到客户端的可变组件,类就必须进行保护性地拷贝这些组件。

3)谨慎使用重载。

注意:重载是在编译时期做出决定的。

    重载的选择是静态的,而覆盖的选择是动态的。

eg:List结合Integer类再结合装箱/拆箱功能就有可能出现混乱。

4)谨慎使用可变参数。

注意:1)可变参数的方法每次调用会造成进行数组的分配和初始化。

当定义参数数目是不定时,可变参数是一种方便的方式,但不应该过度使用。使用不当很可能造成混乱的结果。

如:

待续----------------------------------------------------------



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值