《Effective JAVA 》

Effective JAVA 读书笔记

看第二遍,仔细做笔记

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

优势:

  • 静态工厂方法有具体名称
  • 每次调用静态工厂方法时返回的是提前构造好的实例
  • 可以返回原返回类型任何子类型的对象
  • 创建参数化类型实例,代码较简洁

例如以下,通过静态工厂方法,确保获取KBServiceImpl的单例

public class KBServiceImpl extends KBService {
   
    //(1) 构造函数为空,防止直接调用构造函数创建
    private KBServiceImpl {
    }


     //  (2)定义一个hodler 的内部类,专门定义KBServiceImpl 类型的static 的 INSTANCE成员
    private static class KBServiceImplHolder {
         private static final KBServiceImpl INSTANCE = new KBServiceImpl();
    }

    //(3) 定义getInstance()函数,通过此函数获取KBServiceImpl的static 对象
    //确保外界可以直接调用 KBServiceImpl.getInstance() 而不用先new KBServiceImpl,所以必须声明static
    public static KBServiceImpl getInstance() {
       return KBServiceImplHolder.INSTANCE;
    }

}

以下为泛型HashMap 的一种初始化方式:

    public static <K, V> HashMap<K, V> newInstance() {
        return new HashMap<K, V>();
    }
    
    Map<String, List<String>> m = HashMap.newInstance();

2.构造器参数较多时采用 build 模式

//PYJ: 这种构造方法在项目中还未见过

如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式就是一种不错的选择

构造参数较多时一般是采用JavaBeans 方式:

Data data = new Data();
data.setA(...);
data.setB(...);
data.setC(...);

builder 方式:

public class Data {
    private final int A;
    private final int B;
    private final int C;
    private final int E;
    private final int F;

    public static class Builder {
        private final int A;
        private final int B;

        private  int C = 0;
        private  int E = 0;
        private  int F = 0;
        
        public Builder(int a, int b) {
            this.A = a;
            this.B = b;
        }
        
        public Builder setC(int c) {
            C = c;
            return this;
        }
        
        ... ...

        public Data build() {
            return new Data(this);
        }
        
        public Data(Builder build) {
            A = build.A;
            ... ...
        }
    }
}

最后Data data = new Data.build(1, 2).setC(3).setD(4).build();
其实也是直接调用build 这个内部静态类来实现的

3.私有构造器或者枚举类型强化Singleton属性

public class Data {
    private static final Data INSTANCE = new Data();
    private Data() {
        ... ...
    }
    public static getInstance() {return INSTANCE}
}
  1. 构造函数private 域
  2. 仅仅getInstance() public 域
  3. getInstance() 为静态方法,返回的是final 的静态成员变量 INSTANCE

INSTANCE作为静态成员仅仅会初始化一次,所以getInstance() 返回的一直是同一个对象;

4.通过构造器为private 域来确保此类无法被实例化

RT

5.避免创建不必要的对象

同时,还要尽量优先使用基本类型而不是装箱基本类型。

6.消除过期的对象引用

// TODO

7.避免使用终结方法

必须使用就调用 super.finalize

对所有对象通用的方法

8.覆盖equals 时需要注意点:

1.自反性:对象必须equals自己;
2.对称性: A 和 B 必须相互equals
3.传递性:A,B,C传递
4.一致性:equals的比较操作在对象中随用的信息没有被修改,则equals 的值不变;

注: 里氏替换原则----所有调用父类的地方,换成子类都悟异常。所以父类覆盖了equals 方法,子类就不再覆盖 equals 方法

  • 使用 == 操作符直接检查“参数是否为这个对象的引用”
  • 使用instanceof 操作符直接检查“参数是否为正确的类型”
  • 把参数转换为正确的类型
  • 覆盖了 equals 方法后一定要覆盖 hashCode 方法

9.覆盖equals时也要覆盖hashCode

注意: equals 的为用户判断相等的,但是 hashCode 是系统运行时判断的,比如HashMap中各成员的key值不能相等,系统就是通过key 的hashCode 来判断的

10.始终要覆盖toString方法

Object提供了toString的实现,但它返回的是**类名+@+散列码,**如果有转化为String的需求,应该覆盖toString方法
数据类型的类都覆盖 toString 方法

11.尽量避免覆盖clone 方法

12.Comparable 接口的问题


## 类 和 接口 ### 13. 类和成员的可访问性最小化
  • 尽可能使每个类或者成员不被外界访问;
  • 实例域(非 final 或者static)绝不能是公有的;

14.在共有类中使用访问方法而非公有域

非final 或 static 的成员变量都尽量设置为private 域;
将获取/修改 这些成员变量的方法设置为public 域来提供访问/修改

15.

16.复合优于继承

  • 同一个包内继承是十分安全的(仅讨论实现继承,不考虑接口继承)
  • 不同包,跨包继承是不安全的(仅讨论实现继承,不考虑接口继承)

以上,因为程序都是以包进行优化的,可能继承的包里的类进行了变化

17. 一个类要么为了继承,要么就禁止继承

  1. 为了继承的类:
  •  构造器不能调用可被覆盖的方法;
    
  •  发布前需要先写子类进行测试;
    
  1. 对于那些并非安全第进行子类化的类,要禁止子类化:
  • 方法1:把此类声明为final
    
  • 方法2:把构造器设置为private 的。(jave类设计的任何方法只要设置为private之后,就无法继承了)
    

18.接口优于抽象类

当类需要实现混合功能时,通过实现新的接口来实现
接口和抽象类本质区别: 抽象类允许某些方法的实现,但是接口不允许;(所以抽象类的继承有很大限制)

1.接口是定义mixin(混合类型)的理想选择:

一个类仅拥有一个功能,需要添加混合功能(非此类原设定功能)时,可以通过实现新的接口来增加功能;这样功能划分也清楚

2.接口允许我们构造非层次结构的类型框架:

与1相同,混合的接口层次,为了不违背类的单一职责原则

3.现有的类可以通过实现新的接口来进行 更新:

作为1 和2 的补充,不违背开闭原则,当类有新功能需要增加时,以 实现新接口为拓展,不是直接修改;

19.接口只用于定义类型

不应该出现: 实现接口就是为了到处其中定义的常量的情况,防止这个类及其子类被“常量污染”
所以一般接口中不定义常量,仅定义方法

20.类层次优于标签类

21.用函数对象表示策略

//TODO

22.优先考虑静态成员

1.镶嵌类

在一个类中定义另外一个类,就是镶嵌类;

2.内部类

镶嵌类分 静态成员类 ,非静态成员类,匿名类 和 局部类 , 其中 除了静态镶嵌类以外的镶嵌类就是内部类;

  • 镶嵌类的成员类 可以访问外围类的所有成员,包括私有成员!
  • 镶嵌类也可以访问 成员类的所有成员

泛型

23.不要在新代码中使用原生态类型

“List”的原生态类型就是“List” , 这条原则的意思就是尽量确定类似List 为那种类型的列表:
尽量定义:
public final List list = new ArrayList()
而不是:
public final List list = new ArrayList()

//1.使用原生态类型 , 不安全
static int numEqualsTest(Set s1, Set s2) {
    ... ...
    for(Object o1 : s1) {
        if(s2.contains(o1)) {
            result ++;
        }
    }
}

//2.使用 无限制通配符 , 安全
static int numEqualsTest(Set<?> s1, Set<?> s2) {
    ... ...
    for(Object o1 : s1) {
        if(s2.contains(o1)) {
            result ++;
        }
    }
}

24.消除非受检警告

25.列表优先于数组

  1. 数组是“协变”的,泛型是“不可变”的——泛型严厉控制了add 的类型,在编译时就会检查出问题;
  2. 数组是“具体化”的,在运行时才检查数组元素类型的约束,泛型编译时就检查了;

26. 优先使用泛型

// 非泛型

public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final DEFAULT_INITAL_CAPACITY = 16;
    
    public stack() {
        elements = new Object[DEFAULT_INITAL_CAPACITY];
    }
    
    public void push(Object e) {
        ... ...
        elements[size++] = e;
    }
    
    public void pop() {
        ... ...
        Object result = elements[size--];
        element[size] = null;
        result element;
    }

    ... ...
}

泛型化:

public void Stack<E> {
    private E<> elements;
    private size = 0;
    ... ...
    
    public Stack() {
        elements = new E[DEFAULT_INITAL_CAPACITY];
    }
    
    public void push(E e) {
        elements[size++]= e;
        ... ...
    }
    
    public void pop() {
        ... ...
        E result = elements[size--];
        element[size] = null;
        result element;
    }
}

27.优先考虑泛型方法

泛型方法的一个显著的特性是,无需明确指定类型参数的值;

//
Map<String, List<String>> testMap = new HashMap<String, List<String>>();

//泛型单例模式
public static <K,V> HashMap<K,V> newHashMap() {
    return new HashMap<K,V>();
}

Map<String, List<String>> testMap = new HashMap();

28.?利用有限制通配符来提升API 的灵活性

//未使用通配符
public void pushAll(Iterable<E> src) {
    ... ...
    push(e);
}

public void popAll(Collection<E> dst) {
   ... ...
   numberStack.popAll();
}

//使用通配符
public void pushAll(Iterable<? extends E> src) {
    ... ...
}

public void popAll(Collection<? super E> dst) {
    ... ...
}

有限制通配符可以限制泛型参数的范围:
pushAll()中,输入参数必须为初始化时E这个类的子类;
popAll() 中,输入参数必须为初始化时E这个类的父类;
//PYJ:以下需要结合具体业务实例来理解 ?????????

  • 如果参数化类型为一个T生产者,就使用<? extends T>
  • 如果表示一个T消费者,就使用<? super T>

29.?优先考虑类型安全的异构容器

Set 仅仅一个参数,Map 也仅仅2个参数,但是如果需要多个参数,此时需要将key进行参数化而不是将容器进行参数化,然后将参数化的key提交给容器,来插入或者获取值。用泛型系统来确保值的类型与它的键相符

//以Favorites 为例
public class Favorites {
    private HashMap<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();
    
    public <T> void putFavorite(Class<T> type, T instance) {
        ... ...
        favorites.put(type, instance);
    }
    
    public <T> T getFavorite(Class<T> type) {
        return type.cast(favorites.get(type));
    }
}

//运用PYJ:?????????

Favorites f = new Favorites();
f.putFavorite(String.class, "Java");
f.putFavorite(Integer.class, 0xcafaebabe);
f.putFavorite(Class.class, Favorites.class);

方法

38.检查参数有效性

  • 对应公有方法,要使用Javadoc的@throw标签在文档中说明违反参数值会抛出的异常

在这里插入图片描述

  • 对与非公有方法,使用“断言”

总的说,每次编写方法时,都需要考虑参数的现在,并且在方法体的开头处通过显示的检查来实施这些限制
**

39.必要时进行保护性拷贝

40.方法签名的设计

  • 方法名称:
  • 不要过于追求提供便利的方法:
  • 避免参数列表太长:
  • 对于参数类型,优先使用接口而不是类(“面向接口编程”)
  • 对于boolean参数,优先使用2个元素的枚举类型

41.谨慎使用重载

使用时尽量确保每个重载的方法的参数个数不一样: 永远不要导出2个具有相同参数数目的重载方法;

42. 返回零长度的数组或者集合,而不是null(尽量避免返回null)

通用程序设计

45. 将局部变量作用域最小化

  • 在第一次使用局部变量的地方声明
  • 每个局部变量的声明应该都包含一个初始化表达式;

46. for-each 循环优先于传统的for循环

以下三种情况下for-each循环无法使用,其他情况下尽量优先使用for-each

  • 过滤
  • 转换
  • 平行迭代

尽量优先使用for-each

48.如果需要精确的答案,避免使用float 和double

精确的数据尽量使用 BigDecimal , int 或者 long 进行计算

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值