Effective Java高效编程

Posted 杨迈1949

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Effective Java高效编程相关的知识,希望对你有一定的参考价值。

前言

最近读了这本书,发现平时在写代码的过程中,也无意有意的使用到了书中的观点,特此记录下来,后用。

创建和销毁对象

建议我们什么时候创建,怎么创建,什么时候销毁,怎么销毁,以及销毁前对对象的管理。

1,使用静态工厂方法代替构造函数

1.平时使用最多的当然是new String()语法来构建一个对象。
2.对于String来说,提供了使用静态工厂方法来构建String对象,比如String.valueOf()方法。
好处:
- 静态方法有自己的方法名,可以区分构造时,传入的参数,构造什么样的对象;
- 不一定非要创建一个新的对象,比如单例模式就使用到了这一点;
- 可以返回一个原返回类型的子类;

2,使用私有构造函数,强化singleton属性

在使用单例模式时,尽量私有化构造方法建议两种写法:

//公有静态属性方式
public class Elvis 
    public static final Elvis INSTANCE = new Elvis();

    public Elvis() 
    
//静态工厂方法方式
public class Elvis 
    private static final Elvis INSTANCE = new Elvis();

    public Elvis() 
    

    public static Elvis getInstance() 
        return INSTANCE;
    

在序列化时,注意一下要提供readResolve().

通过私有构造函数强化不可实例化能力

有些时候,会编写出只包含静态方法或者静态域的类,比如一些utils,常量类,还有java api中的java.lang.Math,java.util.Arrays等,这些类显然实例化也没有任何意义,这时,应该私有化其无参构造函数,使其不能创建出对象。
最好也对类加final修饰,表示不能被继承。

避免创建重复对象

应该重复使用一个对象,而不是每次都创建一个相同对象。比如说在使用
String s = new String("silly");
创建一个String对象时,就很笨,因为这种方式,每次都会new出一个String对象,你也会发现这些String对象都是相等的(值相等),使用下面的方式来创建就好很多

String s = "No longer silly";

无论调用多少次,都是同一个对象。
避免这一条的方法,就是使用静态static变量

消除过期的对象引用

介绍了数组在使用过程中,会存在过期引用问题。
不过自己写代码过程中,好像没有注意到这个问题,反而一些其他引起OOM的问题注意的更多,比如:IO流,Cursor游标,TypedArray等,都需要及时的关闭。但注意有时我们会不经意间写出这样的代码:

            FileInputStream fis = null;
            ByteArrayOutputStream bos = null;
            try 
                fis = new FileInputStream(file);
                bos = new ByteArrayOutputStream();
                int len = 0;
                byte[] bytes = new byte[1024];
                while ((len = fis.read(bytes)) != -1) 
                    bos.write(bytes, 0, len);
                
             catch (Exception e) 
                e.printStackTrace();
             finally 
                try 
                    fis.close();
                    bos.close();
                 catch (Exception e) 
                    e.printStackTrace();
                
            
            return bos.toByteArray();

bos流已经关闭了,但是后面的代码还会使用到,就会报错。解决方法很简单,就在合适的位置声明一个String类型,在try代码中给它赋值即可。

避免使用终结函数

finalize()是Object的方法,这个方法是在当一个对象销毁的时候被调用,很明显对象销毁的时候,是系统执行的,所以我们不应该去管它。
还记得刚毕业去面试时,面试题目还有final,finally,finalize这3个关键字的关系呢,现在我知道了,原来他们3个根本没有关系。

对于所有对象都通用的方法

本章主要讲解Object对象需要重写的方法:equals(),hashCode(),toString(),clone()等.因为这些方法很通用,很常用,所以重要性也就会提高。

在改写equals的时候请遵守通用约定

对于这一条,在实际项目中还没有遇到,通常也就用用String类的equals(),可能项目中对数据的处理还是少了些吧。
equals()的几个通用约定:
- 自反性:对于任意的引用值x,x.equals(x)一定为true。
- 对称性:x.equals(y) 等于 true,那么y.equals(x)也为true。
- 传递性:x,y,z三个值,x == y ,y == z ,那么x == z也成立。
- 一致性:就是说,x,y在没有变化的实话,在任何时候调用x.equals(y)都必须一致的返回true(false)。
- 对于任意的非空引用值x,x.equals(null)一定返回false。

高质量equals()的’处方’:
1. 使用==检查“实参是否指向同一个对象的引用”
2. 使用instanceof检查”实参是否是正确的类型”
3. 把实参转换到正确的类型(因为第二步已经检查过了)
4. 对于该类的每一个“关键域”,都要做检查,只有所有匹配成功才能返回true
5. 写好equals()后,检测一下,是否符合上面提到的通用准则
注意:不要将equals(Object o)中的参数修改为其他类型,这样做并没有重写Object中的equals方法(参数不同),会增加对象的复杂度,例:

public boolean equals(MyClass o)
    ...

改写equals()时,总是要改写hashCode()

改写的目的就是为了在某些集合类中正常工作,比如:HashMap、HashSet 、Hashtable等基于散列值(Hash)的集合。
hashCode()的几个通用约定:
- 在一次程序执行期间,只要一个对象的equals()比较信息没有修改过,那么多次执行hashCode(),必须返回同一个整数。在多次程序执行期间,hashCode()返回值可以不同。
- 如果两个对象的通过equals()判断相等,那么他们的hashCode()返回值也相等。
- 如果两个对象的通过equals()判断不相等,那么他们的hashCode()返回值不要求必须产生不同的值(就说可以是相同的hashCode值),但我们要知道,产生不同的值,能提高hash表的性能。

所以没有重写hashCode(),就违反了第二条约定。
这一条也很少用到,反正我们应该从对象的属性中去获取一些基本类型的对象,然后在想办法返回一个不同的值,比如,如果都是int型的属性,那么可以编写一个固定的公式算出一个值返回。如果是String类型,可以先求出单个的hashCode()然后在组合起来。

总是要重写toString()

调用toString(),应该返回一个“简洁的,信息丰富,便于阅读”的字符串。如果该类没有重写toString()就会调用Object提供的toString():getClass().getName() + '@' + Integer.toHexString(hashCode()),返回形式:”PhoneNumber@:63b91”。只能看出类名,所以最好重写toString()返回更有意义的字符串。
在使用字符串连接操作符”+”时,toString()会自动调用,例如:System.out.println("failed to connect: " + phoneNumber)
这里要注意一点,就是toString()返回的字符串应该是我们感兴趣的信息,并且如果有特定需求,还应该返回一些固定格式的字符串,比如json,xml格式。
在实际项目中,发现java bean对象总是应该重写该方法,这样做方便我们打印日志时看清楚对象的值。特定的情况下,还应该返回具有格式的字符串,方便后面保存到数据库或者文件中,供下次使用时,方便生成对象。

谨慎的重写clone()

目前项目中并没有遇到重写该方法。

考虑实现Comparable接口

实现该接口后,在保存该对象的集合,数组中,可以对所有该对象排序。
数组:Arrays.sort();
list集合:Collections.sort()
记得是在展示手机通讯录中信息是用到的,当时是按照人名拼音字母排序,重写过这个方法。

类和接口

使类和成员的可访问能力最小化

考虑到封装性,我们应该这么做。好处:类之间无关联,每个类可以单独开发,维护,测试等。使得程序员可以并行开发产品。
- private使用场景:field域,私有方法,内部类
- protected使用场景:父类中方法,父类的field域
- public使用场景:只要是允许对象成员访问的域或者方法

支持非可变性

就是说,我们可以设计一个不可变的类,该类所有域都是final private的,只要该对象一生成就不可变。
这样的类在实际项目中还没有遇到。书中举了一个虚数的栗子。

复合优先继承

继承是实现代码重用的有力手段,但不一定是最佳选择。在一个包内部使用继承是非常安全的,但是跨包继承则很危险。
继承会打破封装性,使得父类的方法修改会影响到子类。所以常常我们更应该考虑用复合机制来设计类,也就是装饰模式Decorator。
这两种机制很容易搞混,在实际项目中常常会遇到这样的问题:
需要扩展一个类的时候,到底是继承一个这个类,还是在一个类中保持这个类的对象?
原则就是只有明显为继承关系而设计类时,才使用继承,否则用复合,特别是使用别人api的时候,更要用复合。

要么专门为继承二设计,并给出文档说明,要么禁止继承

和上一条类似

接口优于抽象类

任何情况下都要考虑使用接口来扩展类的功能。
只有在专门为继承来设计类结构时,将他们的公用方法抽取出来,使用抽象类来实现的使用是有抽象类机制。
所以抽象类在项目中使用明显少于接口,因为接口很灵活。

接口只是被用于定义类型

这节讲了接口不应该设计成包含常量的接口,虽然可以在接口中定义常量。比如下面的接口就是接口的不良使用:

public interface PhysicalConstants 
    static final doubel AVOGADROS_NUMBER = 6.02214199e23;

更应该把这些常量写在具体的类中,比如:Integer.MAX_VALUE.

优先考虑静态成员类

嵌套类(nested class)是指被定义在另一个类内部的类。嵌套类存在的目的就是只为他的外围类提供服务,如果用于其他环境中,则不应该这样设计。
嵌套类有4种:
- 静态成员类
- 非静态成员类
- 匿名类
- 局部类
后3种被称为内部类。
这几种嵌套类中,使用最频繁的就是匿名类,特别是在android开发中:

View.onClickListener(new onClickListener()
        ...
);

只要在表达式可以使用的地方都可以使用,会生成一个匿名对象。
其次就是非静态成员类,在安卓中Adapter的编写,均可以作为嵌套类:

//有个一外围类
class VideoAdapter extends BaseAdapter 
    ...

书中说如果该Adapter不需要保存一个外部实例,应该设计为静态成员类,那好像这里的VideoAdapter对象确实不需要保存外部势力,应该也可以设计为静态成员类吧。
局部类项目中很好用,也没有用过,书中表示局部类和域类似的作用区间
引用书中总结:

C语言结构的替代

这一章讲了如何使用java语法代替C语法的办法。虽然博主学过C,也是从C转过来的,不过由于现在java发展成熟了,而且现在好多人都是直接学的java,所以本章的知识点显得没有那么重要了,不过还是把标题列出来吧。

用类代替结构

用类层次来代替联合

用类来代替enum结构

目前jdk1.5开始已经又Enum类型了。

用类和接口来代替函数指针

方法

本章讨论几个方面:如何处理参数和返回值,如何设计方法原型,如何为方法编写文档。

检查参数的有效性

在很多方法和构造函数中都会对传递给它们的参数值有某些限制。比如,索引值是非负数,对象引用不能为null等。这些限制,我们应该在文档中说明,有必要的话,更应该在方法的开头对这些参数做检查。
因为某些时候无效的参数会使程序抛出一个运行时异常。特别是某些时候,参数不会再改方法中时候,而是赋值给field变量,留在后面使用。这样的话,如果后面的方法抛出关于这个变量的异常,就不容易找到异常的源头了。常常这样的事件就在构造函数中发生,因为构造函数的参数,一般都是初始化变量的。
在项目中,因为是自己写的方法,参数,所以一般只在有必要时去检测参数的有效性。

需要时使用保护性拷贝

谨慎设计方法的原型

  • 谨慎选择方法的名字,尽量保持和系统api命名方式一致,比如getXXX(),setXXX(),removeXXX()等
  • 不要对于最求提供便利的方法,每个方法一个功能点
  • 避免长长的参数列表
  • 对于参数类型,优先使用接口 而不是类

谨慎地使用重载

重载机制,确实会让人在调用的时候考虑该传那些参数,会增加代码复杂度。当然系统api自己用习惯了,当然感觉很好用,但用第三方api时就会感觉复杂。就如同书中提的建议,重载时,尽量保持参数不同。
其实这一条到不是很重要,项目中使用时,方法都是你写的,很熟悉,不容易搞混。

返回零长度的数组而不是null

集合也一样,这样做我相信能避免很多空指针异常。
因为我们常常会忘记去判空,直接for(),但是在for()中,如果数组为空,则会直接报异常,而零长度数组不会。

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

如果项目中需要的话,则很必要。如果项目没有要求,可以在关键处使用标准的文档注释。这样做也可以在IDE中,自己写代码时,可以使用快捷键查看方法的注释。

以上是关于Effective Java高效编程的主要内容,如果未能解决你的问题,请参考以下文章

Effective Java 读书笔记之六 方法

Effective Java 第十五条:使可变性最小化

[读书笔记]Effective Java 第一章

《Effective Java》读书笔记 - 最小化类的可变性

关于阅读java编程思想和effective java的一些看法

《Effective Java 第三版》《Java编程思想》2020中文版开放下载