Effective Java摘录(二)

4.类和接口

13.使类和成员的可访问性最小化
    良好的设计模块会隐藏所有的实现细节,把它的API与它的实现清晰的隔离开来.然后模块之间只通过它们的api进行通信, 一个模块不需要知道其它模块的内部工作情况.封装
    它可以有效地解除组成系统的各模块之间的耦合关系,是的这些模块可以独立地开发测试优化 使用 理解 和修改;虽然信息隐藏本身不会带来更好的性能, 但是一旦完成一个系统,通过剖析确定哪些模块影响了性能,可以对那些模块进一步优化, 而不影响到其它模块的正确性
    提高了软件的可重用性
    私有的(private):只有在声明该成员的顶层类内部才可以访问这个成员
    包级私有的(package-private):声明该成员的包内部的任何类都可以访问这个成员,如果没有为成员指定访问修饰符,就采用的这个访问级别
    受保护的(protected):声明该成员的类的子类可以访问这个成员,并且,声明该成员的任何类也可以访问这个成员
    公有的(public):在任何地方都可以访问该成员
    类具有公有的静态final数组域,或者返回这种域的访问方法, 这几乎总是错误的
    public static final int[] private_values=...
    修正这个问题:
    private static final int[] values=....
    public static final List<Integer> valus=Collections.unmodifiableList(Arrays.asList(private_values));
    或者
    private static final int[] private_values=....
    public static final int[] values(){
        return private_values.clone();
    }
14.在公有类中使用访问方法而非公有域
    如果类可以在它所在的外部进行访问,就提供访问的方法
15.使可变性最小化
    不可变类:实例不能被修改的类, 每个实例中包含的所有信息都必须在创建该实例的时候提供, 并在对象的整个生命周期内固定不变.
    原则:
    1. 不要提供任何修改对象状态的方法
    2. 保证类不会被扩展
    3. 使所有的域都是final
    4. 使所有的域都是私有的
    5. 确保对于任何可变组件的互斥访问
    如果类不能被做成不可变的, 仍然应该尽可能地限制它的可变性
16. 复合优于继承
    继承打破了封装性
    不用扩展现有的类,而是在新的类中增加一个私有域,它引用现有类的一个实例,这中设计被称作复合
    对于两个类A和B, 只有当两者之间确实存在"is-a"的关系的时候, 类B才应该扩展类A
17.要么为继承而设计, 并提供文档, 要么就禁止继承
为了允许继承, 类还必须遵守其他的一些约束,构造器决不能调用可被覆盖的方法,无论是直接还是间接调用
18.接口优于抽象类
19.接口只用于定义类型
当类实现接口时,接口就充当可以引用这个类的实例的类型,不应该被用来导出常量
如果这些常量与一个现有的类或接口紧密相关, 就应该把这些常量添加到这个类或者接口中,如果能被看成枚举,最好使用枚举类型,否则就应该使用不可实例化得工具类来导出这些常量
20.类层次优于标签类
21.用函数对象表示策略
    一个类仅仅有一个这样的方法:这个方法执行其他对象上的操作,这样的实例称为函数对象
    函数指针的主要用途就是实现策略模式,Java中要声明一个借口来表示该策略,并且为每个具体策略声明一个实现了该接口的类.
22.优先考虑静态成员类
非静态成员类的每个实例都隐含着与外围类的一个外围实例相关联
如果一个嵌套类需要在单个方法之外仍然可见,或者它太长了,不适合放在方法内部,就应该使用成员类,如果成员类的每个实例都需要一个指向其外围实例的引用,就要把成员类做成非静态的,否则就做成静态的,假设这个嵌套类属于一个方法的内部,如果你只需要在一个地方创建实例,并且已经有了一个预置的类型可以说明这个类的特征,就要把它做成匿名类,否则,就做成局部类

5.泛型

23. 请不要在代码中使用原生态类型
    声明中具有一个或者多个类型参数的类或者接口,就是泛型类或者接口,泛型类和接口统称为泛型(generic type)
    每种泛型定义一组参数化得类型(parameterized type),List<String>, String 为形式类型参数E相对应的实际类型参数,List为原生态类型(raw type)
    Set<Object>是个参数化类型,表示可以包含任何对象类型的一个集合,Set<?>则是一个通配符类型,表示只能包含一种未知对象类型的一个集合,Set则是个原生态类型,它脱离了泛型系统,前两种是安全的,最后一种是不安全的
24.消除非受检警告
如果无法消除警告,同时可以证明引起警告的代码是类型安全的,(只有这种情况下才)可以用一个@SuppressWarnings("unchecked")注解来禁止这条警告
应该始尽可能小的范围使用SuppressWarnings注解,永远不要在整个类上使用SuppressWarnings,这么做可能会掩盖了重要的警告,如果发现自己在长度不止一行的方法或者构造器中使用了SuppressWarnings注解,可以将它移到一个局部变量的声明中
25,列表优先于数组
    数组与泛型相比:数组是协变的(covariant),如果Sub是Super的子类型,那么数组类型Sub[]是Super[]的子类型;泛型是不可变得(invariant),对于任意两个不同的类型Type1和Type2,List<Type1>既不是List<Type2>的子类型,也不是List<Type2>的超类型
    数组是具体化的(reified),数组在运行时才知道并检查它们的元素类型约束;泛型是通过擦除来实现的,只在编译时强化它们的类型,运行时丢弃它们的元素类型信息
    因此无法创建泛型数组
26.优先考虑泛型类
27.优先考虑泛型方法
28.利用有限制通配符来提升API的灵活性
为了获得最大限度的灵活性,要在表示生产者或者消费者的输入蚕食上使用通配符类型
producer-extends  consumer-super
如果参数化类型表示一个T生产者,就使用<? extend T> 如果它表示一个T消费者,就使用<? super T>,所有的comparable和comparator都是消费者
public static void swap(List<?> list, int i, int j){
    swapHelper(list, i, j);
}
public static <E> void swapHelper(List<E> list,int i, int j){
    list.set(i, list.set(j,list.get(i)));
}
29.优先考虑类型安全的异构容器

6.枚举和注解

30.用enum代替int常量
    枚举类型(enum type)是指一组固定的常量组成合法值的类型
    枚举类型的基本想法:它们就是通过公有的静态final域为每个枚举常量导出实例的类
    枚举中的switch语句适合于给外部的枚举类型增加特定于常量的行为
    什么时候使用枚举:每当需要一组固定常量的时候,这包括"天然的枚举类型",列如行星 一周的天数 以及棋子的数目等等,但它也包括你在编译时就知道其所有可能值得其他集合,列如菜单的选项 操作代码以及命令行标记等
    如果多个枚举常量同时共享相同的行为,则应考虑策略枚举
31.用实例域代替序数
    永远不要根据枚举的序数导出与它关联的值,而是要将它保存在一个实例域中,避免使用ordinal方法
32.使用EnumSet代替位域
33.使用EnumMap代替序数索引
    Map(起始阶段,Map(目标阶段,阶段过渡))
    最好不要使用序数来索引数组,而是使用EnumMap,如果你所表达的这种关系是多维的,就使用EnumMap<...,EnumMap<...>>,一般情况下都不使用Enum.ordinal
34.用接口模拟可伸缩的枚举
     private static <T extends Enum<T> & Operation> void test(
        Class<T> opSet, double x, double y
) {
    for (Operation op : opSet.getEnumConstants()) {
        System.out.println(op.apply(x, y));
    }
}

private static void test(Collection<? extends Operation> opSet, double x, double y) {
    for (Operation operation : opSet) {
        System.out.println(operation.apply(x, y));
    }
}

enum BasicOperation implements Operation {
    PLUS("+") {
        @Override
        public double apply(double x, double y) {
            return x + y;
        }
    };
    private final String symbol;

    BasicOperation(String symbol) {
        this.symbol = symbol;
    }

    @Override
    public String toString() {
        return symbol;
    }
}

enum ExtendedOpertation implements Operation {
    EXP("^") {
        @Override
        public double apply(double x, double y) {
            return Math.pow(x, y);
        }
    };
    private final String symbol;

    ExtendedOpertation(String symbol) {
        this.symbol = symbol;
    }

    @Override
    public String toString() {
        return symbol;
    }
}

public interface Operation {
    double apply(double x, double y);
}
用接口模拟可伸缩枚举有个小小的不足,即无法实现从一个枚举类型继承到另一枚举类型,如果共享功能比较多,则可以将它封装在一个辅助类或者静态辅助方法中,来避免代码的复用
虽然无法编写可扩展的枚举类型, 却可以通过编写接口以及实现该接口的基础枚举类型,对它进行模拟, 这样允许客户端编写自己的枚举来实现接口
35.注解优先于命令模式
   @Retention(RetentionPolicy.RUNTIME)
   @Target(ElementType.METHOD)
   public @interface Test{}
Test注解类型的声明就是它自身通过Retention和Target注解进行了注解,注解类型声明中的这种注解称作元注解(meta-annotation).@Retention(RetentionPolicy.RUNTIME)元注解表明,Test注解应该在运行时保留,@Target(ElementType.METHOD)元注解表明,Test注解只在方法声明中才是合法的,它不能运用到类声明,域声明或者其他程序元素上.
36.坚持使用Override注解
    只能用在方法声明中,它表示被注解的方法声明覆盖了超类型中的一个声明
    在你想要覆盖超类声明的每个方法声明中使用Override注解
37.用标记接口定义类型
    标记接口是没有包含方法声明的接口,而只是指明一个雷实现了具有一种属性的接口,例如:Serializable接口,通过这个接口,类表明它的实例可以被写到ObjectOutputStream

猜你喜欢

转载自blog.csdn.net/a90123/article/details/80632163
今日推荐