Effective Java 总结

    xiaoxiao2021-03-25  72

    前言

    读完了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:使类和成员的可访问行最小化

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

    7:使可变性最小化

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

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

    2. 不可变类的好处

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

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

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

    8:优先使用组合

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

    总结:

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

    9:接口优先于抽象类

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

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

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

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

    四、方法

    11:慎用重载

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

    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:基本数据类型优于装箱类型

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

    什么时候使用装箱类型?

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

    六、异常

    参考:J2EE项目异常处理

    转载请注明原文地址: https://ju.6miu.com/read-36893.html

    最新回复(0)