Effective Java 总结

前言

读完了Effective Java,对这本书总结一下,结合自己在实际中的使用,这里只记录部分我觉得重点的点,顺便加上自己平时注意到的点

一、对象创建和销毁

1:考虑用静态工厂方法替换构造器

静态工厂方法的惯用名称:

valueOf——该方法返回的实例与它的参数具有相同的值,实际上是类型转换方法;
getInstance——返回的实例是通过方法的参数来描述的,对于单例模式(Singleton)来说,该方法无参数,并返回唯一的实例;
newInstance——功能同getInstance,但与getInstance不同的是,它能够确保返回的每个实例都与其它实例不同。
因此在写程序的时候我们可以优先考虑静态工厂方法,然后再考虑构造器。

2:遇到多个构造器参数时考虑用建造者模式(Builder)

一般做法:

例如有这个一个类的构造函数:

public Register(int id, int age, String name, String address, String like, int high, int weight)

那么此时的做法:

new Register(1, 20, "A", "B", "C", 170, 60);

这样的缺点就是:
1. 参数会很乱,调用者很容易搞错参数的位置
2. 如果不想为某个参数赋值,那么你还是要赋值,除非你另写一个没有该值的构造函数

JavaBean做法

新建一个User类,利用set设置值,将User传到Register(User user)中,但是JavaBean本身具有很严重的缺点,因为构造过程被分为几个调用之中,在构造过程中JavaBean可能处于不一致的状态(这一点我也不太清除,如果知道的请在下面留言,不胜感激

Builder模式

public class Register {
    private int id;
    private int age;
    private String name;
    private String address;
    private String like;
    private int high;
    private int weight;

    public static class Builder {
        private int id;
        private String name;
        private int age;
        private String address;
        private String like;
        private int high;
        private int weight;

        public Builder(int id, String name) {
            this.id = id;
            this.name = name;
        }

        public Builder age(int age){
            this.age = age;
            return this;
        }

        public Builder address(String address){
            this.address = address;
            return this;
        }

        public Builder like(String like){
            this.like = like;
            return this;
        }

        public Builder high(int high){
            this.high = high;
            return this;
        }

        public Builder weight(int weight){
            this.weight = weight;
            return this;
        }

        public Register build(){
            return new Register(this);
        }
    }

    //私有化
    private Register(Builder builder){
        id = builder.id;
        name = builder.name;
        age = builder.age;
        address = builder.address;
        like = builder.like;
        high = builder.high;
        weight = builder.weight;
    }
}

此时的用法:

Register re = new Register.Builder(1, "liu").age(20).address("LA").like("Ball").high(180);

简洁清晰明了,同时又可以根据自己的需要赋值!

3:消除过期的引用

public class Stack {

    private Object[] elements;
    private int size;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }

        return elements[--size];
    }

    /**
     * Ensure space for at least one more element
     */
    private void ensureCapacity() {
        if (elements.length == size) {
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }

}

这个程序在发生内存泄露:

return elements[--size];

如果一个栈是先增长再收缩,那么从栈中弹出来的对象将不会被GC回收,即使使用栈的程序不会在引用这些对象,这是因为,栈会维持着elements[size]的过期引用,即永远也不会被解除的引用。
应该要这样修改:

public Object pop(){
    if(size == 0){
        throw new EmptyStackException();
    }
    Object result = elements[--size];
    //消除引用
    elements[size] = null;
    return result;
}

这里有一篇很好的文章:JAVA 内存泄露详解(原因、例子及解决)
内存泄露:无用的对象仍然被引用着
内存溢出:当前分配内存不足以存放数据
通常:内存泄露 ——> 内存溢出

二、对象通用方法

4:覆盖equals要遵守约定,覆盖equals总要覆盖hashcode

参考:Java容器(六):从容器出发探讨hashCode和equals

5:始终要覆盖toString

三、类和接口

6:使类和成员的可访问行最小化

  1. 受保护的成员应该尽量少用
  2. 如果方法覆盖了超类的一个方法,子类的访问级别不允许低于超类的访问级别

7:使可变性最小化

1. 为了使类不可变,需要遵循如下规则:

1.不要提供任何会修改对象状态的方法
2.保证类不会被扩展
3.所有域都要是final,且为私有
4.确保对于任何可变组件的互斥访问。

2. 不可变类的好处

1.不可变对象本质是线程安全的,不要求同步
2.不可变对象为其他对象提供了大量的构件

3. 缺点在于需要为每一个不同的值提供不同的对象。如果创建的对象比较大,那么所产生的代价就有点高了

为了不可变性,类是绝对不允许被子类化,我们首先想到的就是“final”修饰,其实除了这点还有一个比较好的办法:让类的所有构造器全部变为私有,并且提供一个公有的静态工厂。

8:优先使用组合

组 合 关 系继 承 关 系
优点:不破坏封装,整体类与局部类之间松耦合,彼此相对独立缺点:破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性
优点:具有较好的可扩展性缺点:支持扩展,但是往往以增加系统结构的复杂度为代价
优点:支持动态组合。在运行时,整体对象可以选择不同类型的局部对象缺点:不支持动态继承。在运行时,子类无法选择不同的父类
优点:整体类可以对局部类进行包装,封装局部类的接口,提供新的接口缺点:子类不能改变父类的接口
缺点:整体类不能自动获得和局部类同样的接口优点:子类能自动继承父类的接口
缺点:创建整体类的对象时,需要创建所有局部类的对象优点:创建子类的对象时,无须创建父类的对象

总结:

  1. 满足 is-a,才继承
  2. 如果父类的API有缺陷,继承是否担心吧这些缺陷传到子类中?
  3. 继承会打破封装性
  4. 优先使用组合

9:接口优先于抽象类

  1. 抽象类适合用做骨架实现,例如集合中的AbstractList,AbstractSet,AbstraceMap等
  2. 使用接口可以达到多重继承
  3. 接口的一个缺点:接口一旦被使用,如果修改接口,所有实现类都会受到影响!

10:优先考虑使用静态成员类

内部类的主要作用应该只为他的外围类提供服务。内部类主要有四种:静态成员类、非静态成员类、匿名类、局部类。

静态成员类:可以访问外围类的所有成员
非静态内部类:含有对外围类的一个隐式引用,可以通过这个引用来访问外围类

四、方法

11:慎用重载

  1. 重载是在编译器决定的,是静态分派的体现
  2. 重写是运行期决定的,是动态分派的体现
  3. 保守的方法:不要导出两个具有相同参数数目的重载方法
  4. 安全的方法:重载的参数类型完全不同(非继承,实现,装箱拆箱),就没有安全隐患

List的一个陷阱:

List<Integer> list = new ArrayList<Integer>();
for(int i = 0; i < 6; i++){
    list.add(i);
}
//删除0,1,2
for(int i = 0; i < 3; i++){
    list.remove(i);
}
for(int i = 0; i < 3; i++){
    System.out.println(list.get(i));
}

结果是:1,3,5,原因是因为,remove方法有两个重载:
remove(int index),remove(Object o)

12:慎用可变参数

可变参数接受0个或者多个指定类型的参数。其机制是先创建一个数组,数组的大小为在调用位置所传递参数的数量,然后将参数值传递给数组,最后将数组传递给方法

13:当使用数组或集合时,返回零长度的数组或集合而不是null

可以避免程序员调用,未判断null,而报NullPointerException

五、通用程序设计

14:局部变量作用域最小化

尽量使用for,而不是while,因为在循环中的局部变量可以定义在for中,但是不能定义在while中

15:for-each优先于for

for-each比起for有一定的性能优势:

List<Integer> list = new LinkedList<Integer>();
for (int i = 0; i < 10000; i++) {
    list.add(i);
}

long begin = System.currentTimeMillis();
for (int i = 0; i < list.size(); i++) {
    list.get(i);
}
System.out.println(System.currentTimeMillis() - begin);

begin = System.currentTimeMillis();
for (int i: list
        ) {
}
System.out.println(System.currentTimeMillis() - begin);

结果:
73
2

16:基本数据类型优于装箱类型

  1. 基本数据类型更节省时间和空间
  2. 使用装箱类型时,频繁的装箱和拆箱会影响效率

什么时候使用装箱类型?

  1. 与数据库映射的po中,要用Integer,因为如果你使用int,你无法判断数据库返回0,导致值为0,还是数据库没有返回值,导致初始化为0
  2. 同理url参数

六、异常

参考:J2EE项目异常处理

  • 8
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值