史上最全EffectiveJava总结(一)

打印 上一主题 下一主题

主题 959|帖子 959|积分 2877

创建和销毁对象

1、静态工厂方法代替构造器

优点

  • 静态工厂方法闻名称,能确切地描述正被返回的对象。
  • 不必每次调用都创建一个新的对象。
  • 可以返回原返回范例的任何子类对象。
  • 创建参数化范例实例时更加简便,比如调用构造 HashMap 时,使用 Map < String,List < String > m = HashMap.newInstance() ,与 Map < String,List < String > m > = new HashMap < String,List < String > >();
缺点

  • 没有公共或受保护构造方法的类不能被子类化
  • 不像构造方法一样容易被找到
2、遇到多个构造参数时要思量用建造者模式

其它方式的缺点

  • 静态工厂和构造器不能很好地扩展到大量的可选参数。
  • JavaBean 模式下使用 setter 来设置各个参数,无法仅通过查验构造器参数的有效性来包管一致性,会试图使用不一致状态的对象。
Builder 的建造者模式:使用必须的参数调用构造器,得到一个 Builder 对象,再在 builder 对象上调用类似 setter 的方法设置各个可选参数,最后调用无参的 build 方法生成不可变对象,new Instance.Builder(必须参数).setter(可选参数).build()。
建造者模式详解
Builder 模式让类的创建和表现分离,使得相同的创建过程可以创建差别的表现。
  1. public class Computer {
  2.     private final String cpu;//必须
  3.     private final String ram;//必须
  4.     private final int usbCount;//可选
  5.     private final String keyboard;//可选
  6.     private final String display;//可选
  7.     private Computer(Builder builder){
  8.         this.cpu=builder.cpu;
  9.         this.ram=builder.ram;
  10.         this.usbCount=builder.usbCount;
  11.         this.keyboard=builder.keyboard;
  12.         this.display=builder.display;
  13.     }
  14.     public static class Builder{
  15.         private String cpu;//必须
  16.         private String ram;//必须
  17.         private int usbCount;//可选
  18.         private String keyboard;//可选
  19.         private String display;//可选
  20.         public Builder(String cup,String ram){
  21.             this.cpu=cup;
  22.             this.ram=ram;
  23.         }
  24.         public Builder setUsbCount(int usbCount) {
  25.             this.usbCount = usbCount;
  26.             return this;
  27.         }
  28.         public Builder setKeyboard(String keyboard) {
  29.             this.keyboard = keyboard;
  30.             return this;
  31.         }
  32.         public Builder setDisplay(String display) {
  33.             this.display = display;
  34.             return this;
  35.         }        
  36.         public Computer build(){
  37.             return new Computer(this);
  38.         }
  39.     }
  40.   //省略getter方法
  41. }
  42. // 使用:
  43. Computer computer=new Computer.Builder("因特尔","三星")
  44.                 .setDisplay("三星24寸")
  45.                 .setKeyboard("罗技")
  46.                 .setUsbCount(2)
  47.                 .build();
复制代码
但是,建造者模式也有缺点:

  • 为了创建对象,首先得它的创建Builder,在性能关键的情况下可能会出现标题
  • Builder模式比伸缩构造方法模式更冗长,因此只有在充足参数的情况下才值得使用,比如四个以上
3、使用私有构造方法或罗列实现Singleton属性

Singleton指最多会被实例化一次的类。通常情况下,以前的做法是没有标题的。但是在某些高级情况,通过使用反射的相关知识访问private的构造函数,破坏Singleton。
  1. public class Elvis{
  2.     // 注意,公有final对象
  3.     public static final Elvis INSTANCE = new Elvis();
  4.     private Elvis(){}
  5. }
复制代码
另一种情况,在序列化的过程中,反序列化得到的对象已经不再是以前的对象(破坏了Singleton),这种情况下,可以通过单位素罗列型处置惩罚。
  1. public enum Elvis{
  2.     INSTANCE;
  3.     // some methods
  4. }
复制代码
单例模式 详解看这篇文章
4、使用私有构造方法执行非实例化

有一些工具类,仅仅是提供一些能力,本身本身不具备任何属性,所以,不得当提供构造函数。然而,缺失构造函数编译器会自动添加上一个无参的构造器。所以,需要提供一个私有化的构造函数。为了防止在类内部误用,再加上一个保护措施和解释。
  1. public class Util{
  2.     private Util(){
  3.         // 抛出异常,防止内部误调用
  4.         throw new AssertionError();
  5.     }
  6. }
复制代码
弊端是无法对该类举行继续(子类会调用super())。
5、依靠注入优于硬链接资源

硬毗连资源:
上面使用的方式都是硬毗连(硬编码);什么是硬毗连,我在此处的理解是在代码中直接引用某个固定的资源。但是如果一个类依靠多种资源呢?以下面的例子为例,引用的字典资源可能是中笔墨典,也可能是英笔墨典,每次资源发生变化时都需要在代码中去修改,如许的方式显然不敷机动。
许多类依靠于一个或多个底层资源。例如,拼写检查器依靠于字典。
通过静态方法来替代硬资源毗连 :
  1. // Inappropriate use of static utility - inflexible & untestable!
  2. public class SpellChecker {
  3.     //仅提供了一个资源
  4.     private static final Lexicon dictionary = "依赖的资源";
  5.    
  6.      //私有构造方法,非实例化
  7.     private SpellChecker() {} // Noninstantiable
  8.    
  9.      //拼音检查器提供给外部类调用的方法
  10.     public static boolean isValid(String word) { ... }
  11.     public static List<String> suggestions(String typo) { ... }
  12. }
复制代码
使用单例来替代硬毗连资源,同样也是不机动的:
  1. // Inappropriate use of singleton - inflexible & untestable!
  2. public class SpellChecker {
  3.     private final Lexicon dictionary = ...;
  4.     private SpellChecker(...) {}
  5.     public static INSTANCE = new SpellChecker(...);
  6.     public boolean isValid(String word) { ... }
  7.     public List<String> suggestions(String typo) { ... }
  8. }
复制代码
也可以在类需要字典资源的时间再去指定,例如将属性设置为非 final,再提供一个方法去修改资源,但如许的设置显得非常笨拙、容易出错、并且无法并行工作。静态工具类和单例类不得当与需要引用底层资源的类。
依靠注入:
使用依靠注入替代硬毗连资源,依靠注入提供了机动性和可测试性。虽然下方的实例仍是一个基础资源,但是它由外部类提供,如许包管了我们的SepllChecker提供的功能不变,但是数据却是机动多变的。
  1. public class SpellChecker03 {
  2.     private final Lexicon dictionary;
  3.     //需要检查什么资源,由外部类提供基础的数据
  4.     public SpellChecker03(Lexicon dictionary) {
  5.         this.dictionary = Objects.requireNonNull(dictionary);
  6.     }
  7.     public boolean isValid(String word) { ... }
  8. }
复制代码
依靠注入模式非常简单,许多程序员使用它多年而不知道它有一个名字。 虽然拼写检查器的例子只有一个资源(字典),但是依靠项注入可以使用任意数量的资源和任意依靠图。 它保持了不变性(详见第 17 条),因此多个客户端可以共享依靠对象(假设客户需要相同的底层资源)。 依靠注入同样实用于构造器、静态工厂
但是使用依靠注入来实现硬资源毗连,也有肯定的弊端,一般大型的项目,可能有数千个依靠项,这让项目变得混乱,不过可以使用Spring框架来消除这些混乱。依靠注入,它极大地提升了类的机动性、可重用性和可测试性。
6、制止创建不须要的对象


  • 对于 String 范例,String s = new String("") 每次执行时都会创建一个新的实例,而使用 String s = "" 则不会,因为对于假造机而言,包含相同的字符串字面常量会重用,而不是每次执行时都创建一个新的实例。
  • 重用相同功能的对象:

    • 重用那些已知不会被修改的可变对象,如Date 、Calendar
    • 可变对象的重用:比如,Map的keySet()方法就会返回Map对象所有key的Set视图。这个视图是可变的,但是当Map对象不变时,在任何地方返回的任何一个keySet都是一样的,当Map对象改变时,所有的keySet也会相应的发生改变。

  • 小心自动装箱(auto boxing):优先使用根本范例,制止无意识的自动装箱。
  • 对于廉价的对象,慎用对象池。当代JVM对廉价对象的创建和销毁非常快,此时不适于使用对象池。
  • 用静态工厂方法而不是构造器
7、消除过期的对象引用

以下三种情况可能会造成内存泄露:

  • 本身管理的内存(数组长度减小后,pop出的对象容易导致内存泄漏)
  • 缓存
  • 监听和回调
本身管理的内存:
  1. public class Stack {
  2.      // ……
  3.      
  4.     public Object pop() {
  5.         if (size == 0) {
  6.             throw new EmptyStackException();
  7.         }
  8.         return elements[--size];
  9.     }
  10.    
  11.     // ……
  12. }
复制代码
这个程序在发生内存泄露:
  1. return elements[--size];
复制代码
如果一个栈是先增长再收缩,那么从栈中弹出来的对象将不会被GC回收,纵然使用栈的程序不会在引用这些对象,这是因为,栈会维持着elements[size]的过期引用,即永远也不会被解除的引用。
应该要如许修改:
  1. public Object pop(){
  2.     if(size == 0){
  3.         throw new EmptyStackException();
  4.     }
  5.     Object result = elements[--size];
  6.     //消除引用
  7.     elements[size] = null;
  8.     return result;
  9. }
复制代码
缓存:
缓存的对象容易被程序员遗忘,需要设置机制来维护缓存,例如不定期回收不再使用的缓存(使用定时器)。某些情况下,使用WeakHashMap可以达到缓存回收的功效。注,只有缓存依靠于外部环境,而不是依靠于值时,WeakHashMap才有效。
监听或回调:
使用监听和回调要记着取消注册。确保回收的最好的实现是使用弱引用(weak reference),例如,只将他们生存成WeakHashMap的键。
8、制止使用 Finalizer和Cleaner 机制

Java的GC有强盛的回收机制,可以简单的记着:不要表现调用finalizer。可以如许理解:
JVM是针对详细的硬件设计的,然而程序却不是针对详细硬件设计的,所以,Java代码无法很好的解决GC标题(因为他具有平台差别化)。另外,finalizer的性能开销也非常大,从这个角度上思量也不应该使用它。
Java 9中,Finalizer 机制已被弃用,但仍被 Java 类库所使用。 Java9中 Cleaner 机制代替了 Finalizer 机制。
Cleaner 机制不如 Finalizer 机制那样危险,但仍旧是不可猜测,运行缓慢,通常是不须要的。
对于所有对象都通用的方法

10、覆盖 equals要服从通用约定

当重写equals方法时,必须服从以下约定

  • 自反性:对于任何非空引用x。x.equals(x) 必须返回 true
  • 对称性:对于任何非空引用x,y。当且仅当y.equals(x) 为 true时,x.equals(y) 也必须为 true
  • 传递性。对于任何非空引用x,y,z。如果 (x.equals(y)&&y.equals(z)) 为 true,那么y.equals(z) 也为 true
  • 一致性:对于任何非空引用x,y。如果在equals中比力的信息没有修改,那么 x.equals(y) 的调用必须始终为true或始终为 false
  • 非空性:对于任何非空引用x。x.equals(null) 必须返回 false
编写高质量equals方法:

  • 使用 == 操纵符检查”参数是否为这个对象的引用“。
  • 使用 instanceof 操纵符检查“参数是否为准确的范例”。
  • 把参数转换成准确的范例。因为转换操纵在 instanceof 中已经处置惩罚过,所以肯定会成功。
  • 对于该类中的每个关键域,检查参数中的域是否与该对象中对应的域相匹配。

    • 域可能为根本数据范例,非float和double范例,使用==来举行比力
    • 可能为引用范例,递归调用equals方法
    • 对于float范例,使用Float.compare(float,float)处置惩罚,对于double范例,使用Double.compare(double,double)处置惩罚,但会导致自动装箱,消耗性能。并且由于存在Float.NaN,因此需要额外处置惩罚

  • 由于可能存在null,因此使用Object.equals(Object,Object)来检查是否相等
其他提醒:

  • 当重写 equals 方法时,同时也要重写 hashCode 方法
  • 不要让 equals 方法试图太聪明。如果只是简单地测试用于相等的属性,那么要服从 equals 约定并不困难。
  • 在 equal 时方法声明中,不要将参数 Object 更换成其他范例,否则只是重载了方法。应使用@Override注解来制止犯这个错误
11、覆盖 equals 方法时,总要覆盖 hashCode 方法

当覆盖equals方法时,必须要覆盖hashCode方法。因为相等的对象必须要有相同的哈希码。如果没有一起去覆盖 hashcode,则会导致俩个相等的对象未必有相等的散列码,造成该类无法联合所有基于散列的集合一起工作。
详情请关注 equals和hashcode
12、始终重写 toString 方法

Object 所提供的 toString,是该对象的描述,输出的是 【类名@散列码】的格式。因此,为了更容易阅读,应该重写 toString 方法
注意:

  • 重写toString方法时,如果有格式,则应该严酷按照格式返回。
  • 本身覆盖的 toString,返回对象中包含的所有值得关注的信息。
  • 在静态工具类 和 罗列类 中重写 toString 方法是没故意义的
  • 如果是抽象类,应该重写 toString 方法来指定格式。如集合实现的toString 方法都是从抽象集合类中继续的/
  1. // java.util.AbstractCollection#toString
  2. public String toString() {
  3.     Iterator<E> it = iterator();
  4.     if (! it.hasNext())
  5.         return "[]";
  6.     StringBuilder sb = new StringBuilder();
  7.     sb.append('[');
  8.     for (;;) {
  9.         E e = it.next();
  10.         sb.append(e == this ? "(this Collection)" : e);
  11.         if (! it.hasNext())
  12.             return sb.append(']').toString();
  13.         sb.append(',').append(' ');
  14.     }
  15. }
复制代码
不足:

  • 当类被广泛使用,一旦指定格式,那就会编写出相应的代码来解析这种字符串表现法,以及把字符串表现法嵌入持久化数据中,之后如果想改变这种格式,则会遭到破坏。
13、谨慎重写 clone 方法

调用clone()方法需要对象实现Cloneable接口,但是这个接口有着许多缺陷。最主要的缺陷在于,Cloneable接口中不包含任何方法,而Object中的clone方法是protect的,也就是说如果一个类只是继续了Cloneable接口,但是却没有重写clone()方法,那么对于这个类的对象,clone()方法依然是不可用的。
既然Cloneable接口中没有任何方法,那么它有什么作用呢,可以看看Object中的clone()方法:
  1. protected Object clone() throws CloneNotSupportedException {
  2.     if (!(this instanceof Cloneable)) {
  3.         throw new CloneNotSupportedException("Class " + getClass().getName() +" doesn't implement Cloneable");
  4.     }
  5.     return internalClone();
  6. }
复制代码
从上面的代码中可以看出Cloneable接口实际上决定了clone()方法的行为,按书中所说:“这是接口的一种极度非典型的用法,并不值得仿效。”
也就是说如果clone()方法想要实现预想中的效果,就需要服从一个“相当复杂的,不可实施的,并且根本上没有文档说明的协议”。
但是纵然是java.lang.Object规范中的约定内容中也存在着许多标题,例如:“不调用构造器”,这一条规定太过倔强,因为行为良好的clone()方法可以调用构造器来创建对象,构造之后在复制内部数据。
在最理想的状况下,如果需要使用clone方法,只需要实现Cloneable接口,并且重写clone()方法即可:
  1. @Override
  2. public Object clone(){
  3.     try{
  4.         return super.clone();
  5.     }catch(CloneNotSupportedException e){
  6.         throw new AssertionError();
  7.     }
  8. }
复制代码
但是这种方法只实用于需要浅拷贝的情况。但如果这么做,而且拷贝的类中存在可变的对象,就会导致灾难性的结果。
例如希望通过上面的方式clone一个如下的Stack:
  1. public class Stack{
  2.     private Object[] elements;
  3.     private int size = 0;
  4.     //other......
  5. }
复制代码
如许clone出来的Stack对象与原Stack对象却使用的是同一个elements引用,但是size属性却是相互独立的。如许很快就会发现这两个Stack对象已经无法正常工作。
对于这种情况,可以通过深拷贝来制止这种情况的发生,例如上面的Stack类,虽简单的做法就是在elements数组中递归地调用clone:
  1. @Override
  2. public Stack clone(){
  3.     try{
  4.         Stack result = (Stack) super.clone();
  5.         result.elements = elements.clone();
  6.         return result;
  7.     }catch(CloneNotSupportedException e){
  8.         throw new AssertionError();
  9.     }
  10. }
复制代码
克隆复杂对象还有一个方法:
先调用super.clone(),然后把返回对象中的所有域都设为空缺状态,再将原对象中各个域的值赋给克隆对象中的相应部分,但是它运行起来通常没有“直接操纵对象及其克隆对象的内部状态的clone方法”快。
正是因为 Cloneable 接口有着这么多的标题,可以肯定的说,其他接口都不应该扩展此接口;那些为了继续而设计的类也不应该实现此接口。
因此应使用其它方式来克隆对象:

  • 使用序列化和反序列化实现深拷贝,但要注意,被克隆的对象及其所有属性都必须是可序列化的。此外,序列化和反序列化过程中可能会抛出 IOException 和 ClassNotFoundException 异常,需要举行处置惩罚。
  • 使用第三方工具,例如 Apache Commons BeanUtils 库的 BeanUtils.cloneBean() 方法和 Spring Framework 的 ObjectUtils.clone() 方法。
14、思量实现 Comparable 接口

如果类实现了comparable 接口,便可以跟许多泛型算法以及依靠该接口的集合实现协作,比如可以使用 Array.sort 等集合的排序。
如果是具有明显自然顺序(如字母顺序,数字顺序或时间顺序)的值类,则应该实现Comparable 接囗
排序的另一种实现方式是实现Comparator接口,性能大概比Comparable慢10%,例如:
  1. // Comparable with comparator construction methods
  2. private static final Comparator<PhoneNumber> COMPARATOR = comparingInt((PhoneNumber pn) -> pn.areaCode)
  3.     .thenComparingInt(pn -> pn.prefix)
  4.     .thenComparingInt(pn -> pn.lineNum);
  5. public int compareTo(PhoneNumber pn) {
  6.     return COMPARATOR.compare(this, pn);
  7. }
复制代码
偶然人们会用计算差值的方法来实现compare方法,例如:
  1. // BROKEN difference-based comparator - violates transitivity!
  2. class DateWrap{
  3.     Date date;
  4. }
  5. static Comparator<DateWrap> order = new Comparator<>() {
  6.     public int compare(DateWrap d1, DateWrap d2) {
  7.         return d1.date.getTime() - d2.date.getTme();
  8.     }
  9. };
复制代码
但是这种做法是有缺陷的。在上例中,Date.getTime返回long型时间戳,两者的差值有可能溢出int。所以应该用Date类自带的比力方法:
  1. // Comparator based on static compare method s
  2. static Comparator<DateWrap> order = new Comparator<>() {
  3.     public int compare(DateWrap d1, DateWrap d2) {
  4.         return d1.date.compareTo(d2.date);
  5.     }
  6. };
复制代码
类和接口

15、使类和成员的可访问性最小化

封装和信息隐藏是软件设计的根本原则。 Java的访问控制机制指定了类、接口和成员的可访问性。它们的可访问性由其声明的位置以及声明中出现的访问修饰符(private、protected 和 public)决定。
控制可访问级别的最佳实践是:在不影响软件功能的条件下,让每个类或成员尽可能不可访问。
对于顶级(非嵌套)类和接口,只有两个可能的访问级别:包级和公共。使用哪个级别,取决于是否需要将API对外导出。
如果包级顶级类或接口只被一个类使用,那么可以思量在使用它的这个类中,将顶级类设置为私有静态嵌套类。
对于成员(字段、方法、嵌套类和嵌套接口),存在四种访问级别,按可访问性从低到高分别是:

  • 私有(private):成员只能从声明它的顶级类访问。
  • 包级(package-private):成员可以从声明它的类所在的包访问。不加修饰符时默认的访问级别。
  • 保护(protected):成员可以从声明它的类的子类和声明它的类所在的包访问。
  • 公共(public):成员可以从任意地方访问。
如果一个方法覆盖了超类方法,那么它在子类中的访问级别就不能比超类更严酷。
公共类的实例字段很少用public修饰,除非是静态final常量。带有公共可变字段的类通常不是线程安全的。
Java 9的模块体系引入了两个额外的隐式访问级别。模块是包的分组,它可以显式导出一些包,未导出包的公共类的公共和保护成员在模块外不可访问,它们产生了两个隐式访问级别,即模块内公共和模块内保护。
16、在公共类中,使用访问器方法,而不是公共字段

对于公共类中的可变字段,不应该将它们设为公共,因为如许破坏了类的封装性。应该通过setter、getter方法访问。对于公共类中的不可变字段,设为公共的危害要小一些。
对于包级类或私有嵌套类,公开它们的字段是答应的。因为这两种类的访问受到限制,所以对它们字段的更改也能限制在肯定范围内。
17、最小化可变性

应该只管降低类的可变性。
不可变类是实例不能被修改的类。Java中有许多不可变类,如String、Integer等。不可变类的优点是:简单、线程安全,可作为缓存共享。
不可变类需满足以下5条规则:

  • 不要提供修改状态的方法。
  • 确保类不能被继续。可以通过为类加上final修饰符,或者通过静态工厂方法对外提供创建对象的唯一方法。
  • 所有字段用final修饰。
  • 所有字段设为私有。
  • 确保对可变对象引用的独占访问。不要给用户提供访问这些引用的方法。
不可变类的缺点是每个差别的值都需要一个单独的对象。如许会产生许多对象创建和回收的开销。解决办法是提供一个公共可变陪同类。例如String的公共可变陪同类就是StringBuilder,用后者处置惩罚多个字符串的拼接时可以减少对象创建数量。
对于那么无法做到不可变的类,应只管限制它的可变性。如许可以减少出错的可能性。每个字段如果可以,只管设为私有final的。
18、组合优于继续

继续并不得当于所有场所。在同一个包中使用继续是安全的,对专为扩展而设计的类使用继续也是安全的。但对普通的详细类做跨包的继续是危险的。
继续打破了封装性,除非超类是专门为了扩展而设计的。超类若在后续的发行版本中获得新的方法,并且其子类覆盖超类中与新方法有关的方法,则可能会发生错误。
组合:在新的类中增加一个私有域,引用现有类。它不依靠现有类的实现细节,对现有类举行转发。
两个类只有满足is-a关系时才应该建立继续关系。Java库中有许多违背这一原则的地方,如Stack不是Vector却继续了后者,Properties不是HashTable也继续了后者。这些情况本应该使用组合。
19、继续要设计良好并且具有文档,否则禁止使用

为了制止继续影响子类实现的准确性,需要为可覆盖方法提供详细的文档,说明它的实现细节,以及覆盖它产生的影响。
这看上去违背了一条准则:好的API文档应该描述一个方法做什么,而不是如何做。确实,这是继续违背封装导致的后果。
20、接口优于抽象类

接口相对抽象类的优点是:

  • 一个类只能继续单个抽象类,却能实现多个接口。
  • 接口的使用更加机动,可以很容易对现有类举行改造,实现新的接口。
  • 接口答应构造非条理化范例结构。
为了代码复用,可以为接口提供默认方法。但是默认方法有不少限制,例如编译器会制止你提供一个与Object类中的方法重复的默认方法,而且接口不答应包含实例字段或非公共静态成员(私有静态方法除外)。
这时可以实现一个抽象骨架类来联合抽象类和接口的优点。例如Java类库中的AbstractList就是典型的抽象骨架类。抽象骨架类使用了设计模式中的模板模式。
21、为后代设计接口

在Java 8之前,向接口添加方法会导致现有的实现出现编译错误,影响版本兼容性。为了解决这个标题,在Java 8中添加了默认方法的功能,答应向现有接口添加方法,但是这个添加过程存在很大风险。
由于默认方法被注入到已有实现的过程对实现者是透明的,实现者无需对此做任何反应。但是偶然很难为所有的实现提供一个通用的默认方法,提供的默认方法很可能在某些场所是错误的。
下面的例子是Java 8 中被添加到集合接口中的 removeIf 方法:
  1. // Default method added to the Collection interface in Java 8
  2. default boolean removeif(predicate<? super e> filter) {
  3.     objects.requirenonnull(filter);
  4.     boolean result = false;
  5.     for (iterator<e> it = iterator(); it.hasnext(); ) {
  6.         if (filter.test(it.next())) {
  7.             it.remove();
  8.             result = true;
  9.         }
  10.     }
  11.     return result;
  12. }
复制代码
无界通配符范例和原始范例之间的区别是:前者中不能放入任何元素(除了 null)。如果你如许做,会得到一个编译报错:
  1. // Tagged class - vastly inferior to a class hierarchy!
  2. class Figure {
  3.     enum Shape {RECTANGLE, CIRCLE};
  4.     // Tag field - the shape of this figure
  5.     final Shape shape;
  6.     // These fields are used only if shape is RECTANGLE
  7.     double length;
  8.     double width;
  9.     // This field is used only if shape is CIRCLE
  10.     double radius;
  11.     // Constructor for circle
  12.     Figure(double radius) {
  13.         shape = Shape.CIRCLE;
  14.         this.radius = radius;
  15.     }
  16.     // Constructor for rectangle
  17.     Figure(double length, double width) {
  18.         shape = Shape.RECTANGLE;
  19.         this.length = length;
  20.         this.width = width;
  21.     }
  22.     double area() {
  23.         switch (shape) {
  24.             case RECTANGLE:
  25.                 return length * width;
  26.             case CIRCLE:
  27.                 return Math.PI * (radius * radius);
  28.             default:
  29.                 throw new AssertionError(shape);
  30.         }
  31.     }
  32. }
复制代码
为便于参考,本条目中介绍的术语(以及背面将要介绍的一些术语)总结如下:
[table][tr]术语例子条目[/tr][tr][td]参数化范例[/td][td]List[/td][td]第26条[/td][/tr][tr][td]实际的范例参数[/td][td]String[/td][td]第26条[/td][/tr][tr][td]泛型范例[/td][td]List[/td][td]第26条、第29条[/td][/tr][tr][td]形式化范例参数[/td][td]E[/td][td]第26条[/td][/tr][tr][td]无界泛型表达式[/td][td]List[/td][td]第26条[/td][/tr][tr][td]原始范例[/td][td]List[/td][td]第26条[/td][/tr][tr][td]有界范例参数[/td][td][/td][td]第29条[/td][/tr][tr][td]递归范例限制[/td][td][/td][td]第30条[/td][/tr][tr][td]有界泛型表达式[/td][td]List, Object> favorites = new HashMap();  public  void putFavorite(Class type, T instance) {    favorites.put(Objects.requireNonNull(type), instance);  }  public  T getFavorite(Class type) {    return type.cast(favorites.get(type));  }    // Typesafe heterogeneous container pattern - client  public static void main(String[] args) {        Favorites f = new Favorites();        f.putFavorite(String.class, "Java");        f.putFavorite(Integer.class, 0xcafebabe);        f.putFavorite(Class.class, Favorites.class);        String favoriteString = f.getFavorite(String.class);        int favoriteInteger = f.getFavorite(Integer.class);        Class favoriteClass = f.getFavorite(Class.class);        System.out.printf("%s %x %s%n", favoriteString,favoriteInteger, favoriteClass.getName());  }}[/code]罗列和注解

34、用罗列范例代替 int 常量

罗列范例相比int常量有不少优点,如:能提供范例安全性,能提供 toString 方法打印字符串,还答应添加任意方法和字段并实现任意接口,使得罗列成为功能齐全的抽象(富罗列范例)。
一般来说,罗列在性能上可与 int 常量相比,不过加载和初始化罗列范例需要花费空间和时间,实际应用中这一点可能不太明显。
35、使用实例字段替代序数

所有罗列都有一个 ordinal 方法,该方法返回罗列范例中每个罗列常量的数值位置:
  1. // Class hierarchy replacement for a tagged class
  2. abstract class Figure {
  3.     abstract double area();
  4. }
  5. class Circle extends Figure {
  6.     final double radius;
  7.     Circle(double radius) {
  8.         this.radius = radius;
  9.     }
  10.     @Override
  11.     double area() {
  12.         return Math.PI * (radius * radius);
  13.     }
  14. }
  15. class Rectangle extends Figure {
  16.     final double length;
  17.     final double width;
  18.     Rectangle(double length, double width) {
  19.         this.length = length;
  20.         this.width = width;
  21.     }
  22.     @Override
  23.     double area() {
  24.         return length * width;
  25.     }
  26. }
复制代码
如许写虽然可行,但难以维护。如果常量被重新排序,numberOfMusicians 方法将被破坏。 更好的办法是使用一个额外的字段来代表序数:
  1. class Square extends Rectangle {
  2.   Square(double side) {
  3.     super(side, side);
  4.   }
  5. }
复制代码
ordinal是为基于罗列的通用数据结构(EnumSet 和 EnumMap)设计的。除非你用到这些数据结构,否则最好完全制止使用这个方法。
36、用 EnumSet 替代位字段

如果罗列范例的元素主要在 Set 中使用,传统上使用 int 罗列模式,通过差别的 2 的平方数为每个常量赋值:
  1. public class Main {
  2.     public static void main(String[] args) {
  3.         System.out.println(Utensil.NAME + Dessert.NAME);
  4.     }
  5. }
复制代码
如果在 Sample 上运行 RunTests,输出如下:
  1. // Two classes defined in one file. Don't ever do this!
  2. class Utensil {
  3.     static final String NAME = "pan";
  4. }
  5. class Dessert {
  6.     static final String NAME = "cake";
  7. }
复制代码
现在让我们添加一个只在抛出特定异常时才成功的测试支持。需要一个新的注解范例:
[code]// Annotation type with a parameterimport java.lang.annotation.*;/*** Indicates that the annotated method is a test method that* must throw the designated exception to succeed.*/@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface ExceptionTest {    Class
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

飞不高

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表