Effective java 读书记录

Effective JAVA

第1条:考虑用静态工厂方法代替构造器

使用方法

第2条:遇到多个构造器参数时要考虑使用构建器

  • 使用lombok的@Builder注解
  • 配合使用@NoArgsConstructor 提供无参构造函数

第3条:用私有构造器或者枚举类型强化Singleton属性

  • 用饥饿模式创建单例
  • 使用枚举类型实现singletons是最佳方法

第4条:使用私有化构造器强化不可实例化的能力

  • 强迫使用工厂方法获取实例对象
  • 坏处是导致子类化。继承

第5条:避免创建不必要的对象

  • 典型反例: String sss = new String(“now”),字符串的字面常量在jvm只有一份的,能重复利用。new String(“now”)操作如果还没有该字面常量则会创建并再创建一个String对象
  • jdk:map的keySet方法返回set视图时会缓存一份
  • 小心自动装箱和自动拆箱
  • 提倡保护性拷贝

第6条:清除过期的对象引用

  • jdk中有很多help gc的注释
  • 常常是remove调一个对象时需要释放对象的引用。
  • 想到了ThreadLocal的内存泄漏。弱引用导致的(下一次GC)。
  • 使用weakHashMap保持弱引用
  • 使用内存分析工具查找泄漏的地方

第7条:避免使用终结方法

  • Object的finalize方法如果子类有覆盖jvm会在gc时调用,放到调用finalize队列中,执行改队列的优先级很低,jvm不保证一定会执行完finalize
  • 子类覆盖了finalize后需要手动调用父类得到finalize方法
  • 不推荐使用finalize来处理一些资源的关闭。推荐使用try cache finally
  • 必须用到finalize时可以使用终结方法守卫者来执行。(此方法,子类无需手动调用finalize)

第8条:覆盖equals时请遵守通用约定

  • 要满足自反性(x.equals(x) == true),对称性(x.equals(y) == y.equals(x)),传递性(x.equals(y),y.equals(z),则x.equals(z)),一致性(比较的值未修改时,多次调用equals时需要返回一致的值)
  • 推荐使用 == 检查引用是否相等,instanceof检查参数的正确类型,转换成正确类型,对每个关键值比较
  • 关注null的情况
  • 覆盖equals时要覆盖hashcode

第9条:覆盖equals时总要覆盖hashCode

  • 字段是boolean 则计算f?1:0
  • 字段是byte,char,short,int 则(int) f
  • 字段是long 则计算 (int)(f^(f>>>32)
  • float则Float.floatToIntBits(f)
  • double则Double.doubleToLongBits(f),再计算 (int)(f^(f>>>32)
  • 引用对象则调用引用对象的hashCode
  • 如果时数组Arrays.hashCode
  • 写完单元测试检查
  • 可以使用lombok的@EqualsAndHashCode
    示例

第10条:始终覆盖toString

  • 使用lombok的@ToString

第11条:谨慎的覆盖clone

  • clone方法会递归调用字段的clone并赋值给新的对象
  • 最后不要覆盖clone方法

第12条:考虑实现comparable接口

  • 当需要用到排序时,考虑实现comparable接口替代创建Comparator的实现类
    满足以下条件
    comparable的约束条件

第13条:使类和成员的可访问性最小化

  • public、package-private、protected、private四种类型

第14条:在公有类中使用访问方法而非公有域

  • 这点比较常用

第15条:使可变性最小化

  • 就是增加各种限制,缩小访问权限。(final修饰、private修饰)

第16条:复合优先于继承

  • 当然啦,合理使用,复合继承都可以。

第17条:要么为继承而设计,并提供文档说明,要么就禁止继承
第18条:接口优于抽象类

  • 现有的类容易更改,可以实现新的接口
  • 接口是定义mix的理想选择
  • 接口允许我们构造非层次结构的类型框架

第19条:接口只用于定义类型

  • 接口是来定义方法的,其他使用都不恰当
  • 常量接口是不可取的

第20条:类层次优于标签类

  • 在一个类中增加标签字段,既可以表示这个也可以别是其他类型,这是不可取的
  • 使用定义抽象父类+多个类型的子类。类层次更加明确

第21条:用函数对象表示策略

  • 定义一个对象,它的方法执行其他对象上的操作,一个类仅仅导出这样的一个方法。则称为函数对象
  • 定义一个类,单例类,让具体的策略类成为宿主类的私有嵌套类
class Host{
	private static class StrLenCmp implements Comparator<String>,Serializable{
		public int compare(String s1,String s2){
			return s1.length()-s2.length();
		}
	}

	public static final Comparator<String> STRING_LENGTH_COMPARATOR = new StrLenCmp();
}

第22条:优先考虑静态成员类

  • 嵌套类是定义在另一个类的内部的类。有四种:静态成员类,非静态成员类,匿名类,局部类
  • 静态成员类,最后把它看作是普通的类,恰巧被声明在另一个类的内部。可以访问外围类的所有成员
  • 非静态成员类,实例化非静态成员类会关联外部类的对象,如map中的keySet
  • 匿名内部类往往是lambda表达式实现一个接口
  • 局部类,只有当局部类是非静态环境中定义的时候,才有外围实例,它们也不能包含静态成员

第23条:请不要在新代码中使用原生态类型

  • 声明具有一个或者多个类型参数(type parameter)的类或者接口,就是泛型类或者接口。
  • 泛型类和接口统称为泛型(generic type)
  • 每个泛型都定义一个原生态类型。如List的原生态类型就是List。
  • 可以使用无限制的通配符类型(unbounded wildcard type)替代原生态来兴
  • 在类文字中必须使用原生态类型
  • 在使用instanceof判断类型中不能使用参数化的类型,可以是无限制通配符类型

合理使用方法

if(o instanceof Set){
	Set<?> m = (Set<?>)o;
}
  • 黑话介绍
    泛型黑话
//递归类型限制
public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {

第24条:消除非受检警告

  • 无法消除的警告,但是类型是安全的使用@SuppressWarnings(“unchecked”)
  • 有非受检异常时,且确认是类型安全的,必须使用注解消除,并说明原因

第25条:列表优先于数组

  • 数组是协变的。子类型数组可以向上转换成父类型数组
  • List中的type parameter,如果是父子类型,转换时会报编译错误
  • 创建泛型数组是非法的
  • 禁止在可变长参数中使用泛型
  • 数组是协变且可以具体化的,泛型是不可变的且可以被擦除的
  • 数组提供了运行时的类型安全(不会抛出ClassCaseException),但是没有编译时的类型安全。泛型反之。
// 元素是父子类型那么数组也是父子类型,要是可以向上转换的
// 存储时会报ArrayStroyException
public static void main(String[] args) {
        Object[] objects = new Long[10];
        objects[0] = "123123";
    }
// 虽然编译能过,但是运行时E是不确定,运行时E[]是Object[],则在运行时会存在上述问题。
E[] objects = (E[]) list.toArray();

//以下是正确使用方法,提供了类型安全的转换
ArrayList<E> es = new ArrayList<>(list);

第26条:优先考虑泛型

  • 在自己的类中使用泛型替代object

第27条:优先考虑泛型方法

  • 使用泛型方法可以消除类型警告
  • 使用泛型方法的静态工厂方法创建HashMap等对象实例

第28条:利用有限制通配符来提升API的灵活性

  • ? extends T, ? super T。称为有限制的通配符
  • 参数化类型是不可变的,通配符类型比较灵活
  • PECS。Product extends。Consumer super。 入参是product。出参是consumer
  • 无限制通配符类型取值为Object,无法放入值
  • 这条不是特别懂
//解决无限制通配符类型无法放入值的问题
public void swap(List<?> list,int i,int j){
        swapInner(list,i,j);
    }

    private <E> void swapInner(List<E> list,int i,int j){
        list.set(i,list.set(j,list.get(i)));
    }

第29条:优先考虑类型安全的异构容器

  • CheckedSet,CheckedList,CheckedMap.

枚举和注解

第30条:用enum代替int常量

  • enum各种好,多用

第31条:用实例域替代序数、、、、、、、、、、、、、

  • 不要使用枚举的ordinal(),表示枚举实例的位置顺序

第32条:用EnumSet代替位域

  • EnumSet.of 替代 比如selector轮询时care的类型

第33条:用EnumMap代替序数索引

  • 使用enum作为key。enum的生态

第34条:用接口模拟可伸缩的枚举

  • 枚举不允许继承
  • 通过实现接口来扩展实现类
//参数类型多限制
public <E extends Enum<E> & Comparable<E>> void  swap(List<E> list,int i,int j){
        swapInner(list,i,j);
    }

第35条:注解优先于命名模式

  • 使用注解,而不是规定命名

第36条:坚持使用Override注解

  • 继承方法和实现方法都要加上Override注解,编译器会检查是否正常覆盖

第37条:用标记接口定义类型

  • Serializable标记接口 clone标记接口

方法

从处理参数、返回值、设计方法签名、编写文档方面讨论可用性、健壮性和灵活性

第38条:检查参数的有效性

  • 检验参数的有效性,并在javadoc中说明可能会抛出的异常和情况

第39条:必要时进行保护性拷贝

  • 总之一条,对应可能存在变化的对象进行拷贝。
  • 对于入参必要时进行拷贝,因为入参有可能变化

第40条:谨慎设计方法前面

  • 名字要做到顾名思义
  • 不要提供大而全的方法,提供基础的方法
  • 避免过长的参数列表,超过4个建议拆分方法
  • 入参优先使用类

第41条:慎用重载

  • 对于重载的方法,java是编译时决定调用哪个方法。注意不根据运行时实际类型决定

第42条:慎用可变参数

  • 不要为了方便而使用,要因为设计而使用

第43条:返回零长度的数组或者集合,而不是null

  • 空集合和数组,可以不需要判断就能使用
  • 为了避免重复创建可以定义一个全局的空数组和空集合

第44条:为所有导出的API元素编写文档注释

  • @throws @param @return @ @{code}
  • 使用swagger为外部接口写文档,可以配合yapi一键导入

第45条:将局部变量的作用域最小化

  • 保护变量,减少内存使用

第46条:使用for-each循环优于传统的for循环

  • 减少错误
  • 对数组索引的边界值只计算一次

第47条:了解和使用类库

  • Random.nextInt
  • juc包
  • collection utils
  • 去了解类库和使用类库

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

  • 使用BigDecimal、int、long进行货币计算

第49条:基本类型优先于装箱类型

  • 熟悉自动装箱和自己拆箱的情况
  • 在外部接口入参中还是使用包装类型为好

第50条:如果其他类型更合适,则避免使用字符串

  • 不要用字符串替代基本类型、枚举类型和聚集类型
  • 但是外部接口传入时,字符串是真的好用

第51条:注意字符串连接的性能

  • 使用stringBuilder和StringBuffer替代字符串连接
  • 字符串连接在java编译后,有一部分会自动使用StringBuilder进行连接

第52条:通过接口引用对象

  • 方便以后提交对象引用的实例类型

第53条:接口优先于反射机制

  • 执行反射访问所需要的代码非常笨拙和冗长
  • 性能损失
  • 最好仅仅使用反射机制来实例化对象,访问对象使用超类或者接口

第54条:谨慎的使用本地方法

  • jni允许应用程序可以调用本地方法
  • 我还没用过,写完试一下

第55条:谨慎地进行优化

  • 在设计时对模块之间的交互,模块和外部交互的API、线路层协议和永久数据格式合理设计

第56条:遵守普遍接受的命名管理

  • 常量全部大写 + 下划线
  • 变量用驼峰
  • 方法名驼峰
  • 类型参数 T,E,K,V,T1,T2

第57条:只针对异常的情况才使用异常

  • 在确实存在异常的情况捕获异常。避免滥用

第58条:对可恢复的情况使用受检异常,对编程错误使用运行时异常

  • checked exp,run-time exp,error
  • 对于由于错误使用方法,比如入参不规范的使用运行时异常。
  • 受检异常时可恢复的,受检异常需要cache住。希望用户对异常处理时使用checked exp
  • run-time则不需要

第59条:避免不必要地使用受检的异常

  • 往往受检的异常只能cache住打日志,并做合理提示,不如直接抛run-time的

第60条:优先使用标准的异常
在这里插入图片描述
第61条:抛出与抽象相对应的异常

  • 底层抛出异常时捕获,并封装成与本方法相符合的异常,虽然很麻烦。

第62条:抛出的异常要有文档

  • 不然也看不懂你为啥抛异常,还要看一堆逻辑

第63条:在细节消息中包含能捕获失败的信息

  • 打印堆栈时把错误的信息打的更详细,排查定位问题更快,谁用谁知道。

第64条:努力使失败保持原子性

  • 类似事务,失败后要还原状态。但是怎么还原呢,当然是提前校验参数的准确性
  • 写恢复的代码,比如mysql的undo.log
  • 拷贝一份对象,失败时恢复。 应该不多用吧

第65条:不要忽略异常

  • 至少解释下为啥忽略吧,好歹打个日志

并发

第66条:同步访问共享的可变数据

  • volatile和synchronized
  • 上面两个关键字的区别
  • 单个变量safe thread, 代码块safe thread

第67条:避免过度同步

  • 史诗级难题–要使用但是不能过度使用

第68条:多多使用juc下的包

  • 举个例子 ThreadPoolExecutor和ExecutorService,CompletableFuture
//优雅的退出ExecutorService
public void shutdown(ExecutorService executor){
	executor.shudown();
	while(!executor.isTerminated()){
		try{
			executor.awaitTermination(600L,TimeUnit.SECONDS);
		}cache(InterruptedExecption ignore){
			Thread.currentThread().interrupt();
		}
	}
}

第69条:并发工具优先于wait和notify

  • 多用juc下的并发控制工具
  • ReentrantReadWriteLock,ReentrantLock,StampedLock

第70条:线程安全性的文档化

  • 线程安全级别:
  • 不可变的
  • 无条件的线程安全
  • 有条件的线程安全
  • 非线程安全
  • 线程对立

第71条:慎用延迟初始化

  • 不建议使用lazy initializtion holder
  • 推荐使用双重校验初始化

第72条:不要依赖线程调度器

  • 使用并发控制工具-JUC
  • 尽量少使用Thread.yield,Thread.Sleep

第73条:避免使用线程组

  • 使用线程池替代Thread Group

序列化

第74条:谨慎地实现Serializable接口

  • 实现了Serializable接口就要有流得到唯一标识符–serialVersionUID
  • 可以不通过构造器实例化。对象的约束性遭到破坏
  • 测试成本增加。需要保证序列化和反序列化没问题

第75条:考虑使用自定义的序列化形式
在这里插入图片描述
在这里插入图片描述

 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
 ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream);
 BB bb = new BB();
 bb.getAa().age = 7;
 bb.setAge(7);
 out.writeObject(bb);
 ByteArrayInputStream byteArrayInputStream =
     new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
 ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
 BB o = (BB)objectInputStream.readObject();
 System.out.println(o.getAge());
 System.out.println(o.getAa());
 o.getAa().age = 8;
 System.out.println(bb.getAa().age);

第76条:保护性地编写readObject方法

  • 不知道

第77条:对于实例控制,枚举类型优先于readResolve

  • 感觉是用来防范客户端被篡改的

第78条:考虑用序列化代理代替序列化实例

  • 感觉是用来防范客户端被篡改的
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值