美文网首页JAVA程序员
Effective Java读书笔记

Effective Java读书笔记

作者: 李良逸 | 来源:发表于2018-11-03 21:10 被阅读10次

    一.创建和销毁对象

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

    (1)不必每次都创建新的对象,可以使用==代替eauqls()返回缓存中已有的对象。

    (2)可以返回子类实例

    (3)可根据不同目的为静态工厂方法起不一样的名称,不必像构造方法写死

    2.运用构造器处理多个参数时使用Builder

    三种方式:构造器、Bean、Builder

    (1)参数多时构造器调用不方便

    (2)Bean开发者同时操作同一对象时线程不安全

    (3)Builder缺点在于开销

    Builder示例如下:

    public class Test1 {
    
    private int height;
    
    private int weight;
    
    private int size;
    
    public Test1(Builder builder) {
    
        this.height = builder.height;
    
        this.weight = builder.weight;
    
        this.size = builder.size;
    
    }
    
    public static class Builder {
    
        private final int height;
    
        private int weight;
    
        private int size;
    
        public Builder(int height) {
    
            this.height = height;
    
        }
    
        public Builder weight(int weight) {
    
            this.weight = weight;
    
            return this;
    
        }
    
        public Builder size(int size) {
    
            this.size = size;
    
            return this;
    
        }
    
        public Test1 build() {
    
            return new Test1(this);
    
        }
    
    }
    
    public static void main() {
    
        Test1 test1 = new Test1.Builder(0).weight(0).size(0).build();
    
    }
    

    }

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

    (1)私有构造器,公有静态final域

    (2)私有构造器,公有静态工厂方法:缺点一:AccessibleObject.setAccessible可调用私有构造器,缺点二:反序列化一个序列化的实例时会重新创建对象,要实现Serializable接口,提供readResolve方法避免这个问题

    (3)单元素枚举类型(首选)

    4.通过private构造器使类不可实例化

    (1)应用场景:某些只提供静态方法或变量的工具栏,开发者并不希望其被继承

    (2)使用private构造器要通过注释声明为什么使用

    (3)缺点:该类不能再被子类化

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

    类初始化的顺序:先初始化父类的静态代码 —> 子类的静态代码 —> 父类的非静态代码 —> 父类构造函数 —> 子类非静态代码 —>子类构造函数

    (1)自动装箱与拆箱

    (2)使用static代码块

    6.消除过期的对象引用

    内存泄露常出现在:

    (1)类是自己管理内存。

    (2)缓存,由于缓存没有及时清除无用的条目而出现,可以使用weakHashMap来避免这种情况

    (3)监听器和其他回调,确保回调立即被当做垃圾回收的最佳方法是只保留它们的弱引用

    7.避免使用finalize方法

    如果使用了一定要调用super.finalize

    原因:

    (1)finalize不是一定能执行

    (2)该方法存在严重的性能问题

    8.覆盖equals时请遵守通用约定

    不覆盖:

    (1)类的每个实例都唯一。

    (2)不关心类是否提供了“逻辑相等”的测试功能。

    (3)超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的

    (4)类是私有的或是包级私有的,可以确定它的equals方法永远也不会被调用。

    覆盖:

    类具有自己特有的“逻辑相等”概念(不同于对象等同的概念),而且超类还没有覆盖equals以实现期望的行为

    高质量equals:

    (1)使用==操作符检查“参数是否为这个对象的引用”。ref1 == ref2

    (2)使用instanceof操作符检查“参数是否为正确的类型”。o instanceof ColorPoint

    (3)把参数转换成正确的类型。ColorPoint cp = (ColorPoint) o;

    (4)对于该类中的每个“关键域”,徐检查参数中的域是否与对象的域相匹配。

         return cp.point.equals(point) && cp.color.equals(color)
    

    (5)当编写完equals方法后,应该检查是否满足对称性,传递性以及一致性。

    9.覆盖equals时总要覆盖hashCode

    10.始终覆盖toString

    11.谨慎覆盖clone

    clone方法设想提供一种不需要构造器就可以创建对象的方法,但是自身存在很多问题,专家一般都不会去调用它

    12.实现Comparable接口

    实现此接口的对象列表(和数组)可以通过 Collections.sort(和 Arrays.sort)进行自动排序

    二.类和接口

    13.使类和成员的可访问性最小

    实例域决不能是共有的,包含公有可变域的类即便是final的也不是线程安全的

    优势:

    (1)更好的解除各个模块之间的耦合关系

    (2)最大化并行开发

    (3)性能优化和后期维护

    (4)代码的高可复用性

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

    (1)不因通过类名访问类属性,而应该通过类似getter或setter等方法

    (2)公有类不应该直接暴露数据域(可变的域)

    15.使可变性最小化

    不可变对象本质上是线程安全的。

    (1)不要提供任何会修改对象状态的方法

    (2)保证类不会被扩展,即声明为final类,或将构造函数定义为私有

    (3)使所有的域都是final的

    (4)使所有的域都成为私有的

    (5)确保对于任何可变组件的互斥访问。

    16.复合优先于继承

    (1)类中增加一个私有域(要继承的父类)

    (2)实现一个带父类公有方法的接口

    17.要么为继承而设计,并提供文档说明,要么就禁止继承

    构造器决不能调用可悲覆盖的方法。

    为继承而设计的类应提供文档说明,应实现Serializable接口

    禁止子类化:(1)把类声明为final的;(2)把所有的构造器变为私有的,或者包级私有的,并增加一些共有的静态工厂来替代构造器。

    18.接口优先于抽象类

    抽象类是对事物的抽象,接口是对行为的抽象。

    三.深入理解Java的接口和抽象类

    19.接口只用于定义类型

    不应该使用接口来导出常量, 直接使用一个不能初始化的类来导出就可以了。

    20.类层次优于标签类

    标签类拆分成继承结构的类层次

    21.用函数对象表示策略

    函数对象:调用操作符的类,其对象常称为函数对象

    22.优先考虑静态成员类

    (1)非静态成员类的每一个实例有隐含着与外围类的一个外围实例

    (2)如果声明的成员类不需要访问外围实例,则将其声明为static , 如果省略了 static ,那么这个类的每个实例都包含了一个指向外围对象的引用。

    嵌套类有四种: A.静态成员类 B.非静态成员类 C.匿名类 D.局部类

    四.泛型

    E — Element,常用在java Collection里,如:List<E>,Iterator<E>,Set<E>

    K,V — Key,Value,代表Map的键值对

    N — Number,数字

    T — Type,类型,如String,Integer等等

    S,U,V etc. - 2nd, 3rd, 4th 类型,和T的用法一样

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

    原生态类型在取出时会有不安全性

    24.消除unchecked warnings

    特别确认没问题时可以用@SuppressWarnings("unchecked")消除警告

    25.List列表优先于数组

    数组是协变的,List是不可变的

    26.优先考虑泛型

    泛型实现原理是类型擦除

    27.优先考虑泛型方法

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

    <? extends T> <? super T>

    1. 优先考虑类型安全的异构容器(?)

    五.枚举和注解

    1. 用enum组织int常量

    31.用实例域代替序数?

    32.用EnumSet代替位域?

    33.用EnumMap代替序数索引?

    1. 用接口模拟可伸缩的枚举?
    1. 注解优先于命名模式

    @interface是用于自定义注解的,它里面定义的方法的声明不能有参数,也不能抛出异常,并且方法的返回值被限制为简单类型、String、Class、emnus、@interface ,和这些类型的数组。

    注解@Target也是用来修饰注解的元注解,它有一个属性ElementType也是枚举类型,值 为:ANNOTATION_TYPE,CONSTRUCTOR ,FIELD,LOCAL_VARIABLE,METHOD,PACKAGE,PARAMETER和TYPE,如 @Target(ElementType.METHOD) 修饰的注解表示该注解只能用来修饰在方法上。

    @RetentionRetention注解表示需要在什么级别保存该注释信息,用于描述注解的生命周期,它有一个RetentionPolicy类型的value,是一个枚举类型,它有以下的几个值:

    (1)用@Retention(RetentionPolicy.SOURCE)修饰的注解,指定注解只保留在源文件当中,编译成类文件后就把注解去掉;

    (2)用@Retention(RetentionPolicy.CLASS)修饰的注解,指定注解只保留在源文件和编译后的class 文件中,当jvm加载类时就把注解去掉;

    (3)用@Retention(RetentionPolicy.RUNTIME )修饰的注解,指定注解可以保留在jvm中,这样就可以使用反射获取信息了。

    默认是RUNTIME,这样我们才能在运行的时候通过反射获取并做对应的逻辑处理。

    1. 坚持使用Override注解

    帮助编译器检查更精确

    37.用标记接口定义类型

    使用注解替代标记接口

    六.方法

    38.检查参数的有效性

    在方法入口使用assert,assert需要显式开启

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

    构造器内拷贝传入参数,防止在参数被传入后内部进行改变导致的不一致性

    40.谨慎设计方法的签名

    拆分长参数列表策略:

    (1)长参数列表分为多个短参数方法

    (2)创建帮助类用于传入多个参数

    (3)使用Builder设置多个参数(类似第2条)

    (4)优先使用接口代替类作为参数数据类型

    (5)优先使用仅包含两个枚举量的枚举类代替boolean参数类型 ?

    41.慎用重载

    42.慎用可变参数

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

    Collections.emptyList()

    Collections.emptySet();

    Collections.emptyMap();

    零长度数组:

    private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];

    public Cheese[] getCheese(){

    return cheesesInStack.toArray(EMPTY_CHEESE_ARRAY);

    }

    避免取数据时进行再次判空

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

    七.通用程序设计

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

    (1)for循环优先于while,for能够避免“剪切-粘贴”错误,能够更早发现程序中的错误

    (2)类似for中变量尽量在for中声明

    46.for-each循环优先于for循环

    47.了解和使用类库

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

    用BigDecimal

    49.基本类型优先于装箱基本类型

    (1)基本类型比装箱节省时间和空间

    (2)装箱类型还有一个非功能值null

    (3)装箱类型不能用==作比较

    什么时候使用装箱类型:

    (1)作为集合中的元素、键和值

    (2)在参数化类型中,必须使用装箱类型作为类型参数,因为java不允许使用基本类型(参数化类型:List<类型(例如String)> list = new ArrayList<String>();)

    (3)在进行反射的方法调用时,必须使用装箱基本类型

    1. 不要使用字符串来组织数据?
    1. 当心字符串连接的性能

    str1 += str2的底层实现为

    str1 = new StringBuilder().append(str1).append(str2).toString();

    多个字符串拼接时用StringBuilder代替“+”

    +和concat实现原理类似,但+更灵活,concat可能导致NPE

    52.使用接口引用对象

    53.接口优先于反射机制

    反射缺陷:

    ① 它在编译时不会进行类型检查;

    ② 实现代码冗长乏味,不易阅读;

    ③ 性能与一般的方法调用相比,要低下很多;

    54.谨慎地使用本地方法

    本地方法不安全

    55.谨慎地进行优化

    1)任何优化都存在风险,有时候弄不好反而带来其他的问题

    2)并不是 性能 优先。努力编写好的程序而不是快的程序。

    3)对前人,尤其是类似于Java API这样的成熟代码,进行优化,是不明智的(要是能优化,人家早就做了)

    1. 遵守普遍接受的命名惯例

    八.异常

    57.只针对异常情况才使用异常

    (1)异常没有发生时,try catch一般不会影响性能

    (2)try的作用范围区别就是异常表中的开始地址和结束地址,作用范围也是不会影响性能的

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

    受检异常:FileNotFoundException,ClassNotFoundException,SQLException,IOException

    非受检异常:RunTimeException(运行时异常),Error(错误)

    59.避免不必要的使用受检异常

    60.优先使用标准异常

    异常类越少,意味着内存占用越小,并且转载这些类的时间开销也越小。

    61.抛出与抽象相对应的异常

    异常转译

    62.每个方法抛出的异常都要有文档

    使用@throws标签说明异常情况

    63.在细节中包含能捕获失败的信息

    fillInStackTrace()输出细节消息

    64.努力使失败保持原子性

    (1)在对其进行处理之前,先做参数有效性的检查

    (2)编写一段恢复代码,发生失败时,可以使对象回滚到操作开始之前的状态

    (3)在对象的一份临时拷贝上执行操作,当操作正确结束后,再把临时拷贝中的结果复制给原来的对象。如果一旦失败,不进行这个复制,也就保持了原对象的状态

    65.不要忽略异常

    关闭fileinputstream时可以忽略异常,其他情况下空的catch块都应该警钟长鸣

    九.并发

    66.同步访问共享的可变数据

    使用volatile更方便

    67.避免过度同步

    68.executor和task优先于线程

    Executors.newSingleThreadExecutor().execute

    69.并发工具优先于wait和notify

    concurrent包提供了并发工具

    70.线程安全性的文档化

    71.慎用延迟初始化

    实例域,使用双重检查模式初始化

    静态域,使用内部类初始化

    72.不要依赖于线程调度

    不要依赖Thread.yield

    73.避免使用线程组

    ThreadGroup

    十.序列化

    74.谨慎地实现Serializable接口

    (1)大多数的Data和BigInteger这样的的值集合类应该实现Serializable

    (2)代表活动实体的类,如线程池,一般不应该实现Serializable

    为实现Serializable而付出的代价是:

    (1)一旦一个类被发布,就大大降低了"改变这个类的实现"的灵活性

    (2)实现Serializable的第二个代价是,它增加了出现Bug和安全漏洞的可能性

    (3)随着类发行新的版本,相关的测试负担也增加了

    75.考虑使用自定义的序列化形式

    readObject,writeObject

    76.保护性的编写readObject方法

    77.对于实例控制,枚举类型优先于readResolve

    78.考虑用序列化代理代替序列化实例

    相关文章

      网友评论

        本文标题:Effective Java读书笔记

        本文链接:https://www.haomeiwen.com/subject/ndhlxqtx.html