effective java学习总结
创建和销毁对象 1、静态工厂方法结合单例代替构造器 1) 避免每次都创建一个新对象 2) 能确切地描述正被返回的对象 3) 可以返回原返回类型的任何子类对象 但也要考虑到静态工厂创建的实例不能作为父类被其他类继承,同时其命名也需规范化。 思考:Spring ioc的对bean也是单例的,创建过程中把bean放在三级HashMap缓存中,每次使用注入使用从缓存中拿去,没有对应实例则创建,多方autowired使用同一bean。对于那些不用Spring容器管理,变动少,却常使用的类,使用静态工厂创建是性能优化的方式之一
2、遇到多个构造器参数时要考虑用Builder模式 静态工厂和构造器不能很好地扩展到大量的可选参数 1)使用必须的参数调用构造器,得到一个 Builder 对象,再在 builder 对象上调用类似 setter 的方法设置各个可选参数,最后调用无参的 build 方法生成不可变对象,new Instance.Builder(必须参数).setter(可选参数).build()。 Student student = new Student.Builder() .name(“张三”).age(35).build(); Builder 模式让类的创建和表示分离,链式调用,简洁明了,易读性高,灵活性高。对单元测试构建测试用例的效率与可读性明显提高。同时 idea支持builder的构建模式。
3、避免创建不必要的对象 一般来说,我们要重用一个相同功能的新对象。重用方式既快速,又流行。如果对象是不可变的,它就始终可以被重用。 比如,String str = new String("") 和String str ="",String是不可变的,如果使用第一种,jvm会重新创建一个String对象,而第二种则会复用字符冲池的对象 在方法中使用局部变量是,优先使用基本类型而不是装箱的基本类型,也要注意无意识的自动装箱。比如 int sum = 0 和 Integer sum =0;
4、消除过期的对象引用 我们知道Java的垃圾回收机制会进行内存管理,但对象的回收是根据可达性分析策略的,如果栈中堆积了大量临时变量,没有及时清理,是会影响服务性能的,甚至可能造成oom。 对于一旦对象引用过期,将它们设置为 null。使用Map缓存数据时优先使用WeakHashMap,LinkedHashMap 这些数据结构,及时清掉没用的项。
对于所有方法都通用的方法 1、 覆盖 equals 类Object中有equals()这个方法,该方法用于比较两个对象内存地址是否相等,即两个引用是否指向同一个对象。但某些业务场景下我们想根据对象的某些属性判定对象是否相等,比如guid,使用equals时比较就需要重写了,
2、覆盖 equals 时总是覆盖 hashCode 在object规范中,相等的对象必须具有相等的散列码,如果没有一起去覆盖 hashcode,则会导致俩个相等的对象未必有相等的散列码,造成该类无法结合所有基于散列的集合一起工作。比如使用HashMap存储object时,其存放规则一般是拉链法hash(key)%len,以object的hash值求余
类和接口 1、使类和成员的可访问性最小化 我们在抽象封装类时,要尽可能的对类的属性隐藏内部实现细节,有效解耦,尽量少的对外暴露,避免安全问题,private、protected、默认、public 四种修饰符在不同的jdk版本中可访问性是有调整的
2、复合优于继承 继承就是父类和子类的关系,复合则是引用原本的超类,在选择使用复合还是继承时要考虑父类是否还有拓展性,父类中api实现是否有缺陷,这些都会传递到子类的,
枚举和注解 1、用 enum 代替 int 常量 在业务中经常有status字段,或者一些不可变的常量,通常在Consts类中用public static fianl… 定义,用enum去替代一组固定的常量,更安全,使用也更方便。jdk8还至此set和map的enum,拓展了其使用范围
Lambda和Stream 1、 谨慎使用Stream lambda表达式结合stream无疑使代码更简洁,其提供的api,fileter、map、collect等一系列api,对代码书写是十分方便的。过度使用流去处理复杂的计算是难以阅读和维护。但在统一转换元素序列、 过滤元素序列、使用单个操作组合元素序列 (例如添加、连接或计算最小值)、 将元素序列累积到一个集合中,可能通过一些公共属性将它们分组 在元素序列中搜索满足某些条件的元素。等场景使用是十分方便的。
2、优先选择Stream中无副作用的函数
方法 1、 检查参数的有效性 在调用方法前先对参数进行有效性检验,避免因为参数的不规范造成接口调用的失败
2、 返回零长度的数组或者集合,而不是null 在代码扫描中,有许多都是返回值为空,对放回结果忽略了而造成的bug, 引起NullPointerException异常,有效的处理方法之一就是返回空集合或空对象。
3、 为API元素编写文档注释 规范的文档注释是对于代码的阅读于后期的维护是帮助极大的,需要养成写有效性注释的习惯,idea的可定制类、方法等的注释模板。
通用规范 1、将局部变量的作用域最小化 在方法中我们往往在方法一开始就定义局部变量,这对方法的维护性和阅读性是不友好的,将局部变量的作用域最小化,在需要使用某局部变量的时候,才定义它,使逻辑小而集中,如果一个方法包含多种操作,便于抽取小方法。
2、for-each循环优先于传统的for循环 传统for循环 for (int i = 0; i < a.length; i++) { … // Do something with a[i] }
增强for语句(for-each) for (Element e : elements) { … // Do something with e }
很明显传统for循环容易在索引与列表大小方面出问题,同时在嵌套循环中也可能出现过界。增强for语句在清晰度,灵活性和错误预防方面都优于传统for循环
3、接口优先于反射机制 反射机制 java.lang.reflect 提供对任意类的编程访问。给定一个 Class 对象,可获取对象的构造器、方法、字段。但如果一个程序试图反射性地调用一个不存在的或不可访问的方法,它将在运行时失败,同时阅读困难,性能也较低。平常业务代码是不建议使用反射的,运用于设计部分
4、了解字符串连接的性能,字符串大量拼接时避免使用String String字符串是不可变的,每一次拼接都是创建一个新对象,当大量字符串进行拼接时时间复杂度为n的平方,且耗内存。可使用StringBuilder 或StringBuffer代替
异常 1、 对可恢复的情况使用受检异常,对编程错误使用运行时异常 java有三种可抛出异常,受检的异常(checked exception),运行时异常(run-time exception)和错误(error)。对于调用或逻辑方面的异常,catch捕获后能够给予开发者提示。而运行时异常和错误则应该抛出,不让其执行下去,如果想要自定义其抛出格式,可继承runtimeException。
2、 抛出与抽象对应的异常 当底层抽象抛出的异常与所执行的任务没有明显联系时,会困扰对高层api的实现形成困扰,底层抛出的异常应该是实际可能产生的,而不是用Exception大包大揽,代码中这种问题很常见,同时,高层应该对底层的异常进行捕获,避免异常的直接抛出
3、努力使失败保持原子性 异常抛出后,我们要保证数据恢复到执行前的状态,保证数据操作的原子性,我们第一反应可能是通过事务,操作失败时数据回滚。一下方法在编写代码时也是可以考虑的。 调整计算处理过程的顺序,使得任何可能会失败的计算部分都在对象状态被修改之前发生,即提前检查执行条件
|