编写高质量代码:改善Java程序的151个建议(第3章:类、对象及方法___建议31~51)

书读的多而不思考,你会以为本身知道的不少。java

书读的多而思考,你会以为本身不懂的愈来愈多。  -----江疏影程序员

在面向对象编程(Object-Oriented Programming, OOP)的世界里,类和对象是真实世界的描述工具,方法是行为和动做的展现形式,封装、继承、多态则是其多姿多彩的主要实现方式,本章主要讲述关于Java对象,方法的种种规则,限制和建议。算法

建议31:在接口中不要存在实现代码编程

建议32:静态变量必定要先声明后赋值session

建议33:不要重写静态方法多线程

实例对象访问静态方法或静态属性不是好习惯,直接类名调用就好了。模块化

建议34:构造函数尽可能简化函数

建议35:避免在构造函数中初始化其余类工具

建议36:使用构造函代码块精简程序性能

一、代码块基本概念:

什么叫作代码块(Code Block)?用大括号把多行代码封装在一块儿,造成一个独立的数据体,实现特定算法的代码集合即为代码块,通常来讲代码快不能单独运行的,必需要有运行主体。在Java中一共有四种类型的代码块:

普通代码块:就是在方法后面使用"{}"括起来的代码片断,它不能单独运行,必须经过方法名调用执行;

静态代码块:在类中使用static修饰,并用"{}"括起来的代码片断,用于静态变量初始化或对象建立前的环境初始化。

同步代码块:使用synchronized关键字修饰,并使用"{}"括起来的代码片断,它表示同一时间只能有一个线程进入到该方法块中,是一种多线程保护机制。

构造代码块:在类中没有任何前缀和后缀,并使用"{}"括起来的代码片断;

二、代码实例展现:

package OSChina.Client;

public class Base {
    static {
        System.out.println("我是父类静态代码块1");
    }

    {
        System.out.println("我是父类构造代码块2");
    }

    Base() {
        System.out.println("我是父类无参构造3");
    }

    public static void doSomething(){
        System.out.println("我是父类静态函数");
    }
}
package OSChina.Client;

public class Sub extends Base{
    static {
        System.out.println("我是子类静态代码块1");
    }

    {
        System.out.println("我是子类构造代码块2");
    }

    Sub(){
        System.out.println("我是子类无参构造3");
    }
}

当实例化时:

Base base = new Sub();

0f2068267775be66c89ba467366ccf039f3.jpg

当实例化时,执行顺序:静态代码块>>构造代码块>>构造函数>>普通方法。

当类名调用静态方法,不实例化时:

f9e5eb570a5dfd6538a85698242cfc01bc0.jpg

只执行静态代码块和对应的静态函数,构造函数不执行!

三、构造代码块应用场景:

① 初始化实例变量

若是每一个构造函数都要初始化变量,能够经过构造代码块来实现。

注:不是每一个构造函数都要加载的,而构造代码块必定加载。

② 初始化实例变量

一个对象必须在适当的场景下才能存在,若是没有适当的场景,就须要在建立该对象的时候建立场景。例如HTTP request必须首先创建HTTP session,此时就能够经过构造代码块来检查HTTP session是否已经存在,不存在则建立。

注:构造函数要实现复杂逻辑的时候,能够经过编写多个构造代码块来实现,每一个代码块完成不一样的业务逻辑(构造函数尽可能简单的原则),按照业务顺序依次存放,这样在建立实例对象时JVM会按照顺序依次执行,实现复杂对象的模块化建立。

建议37:构造代码块会想你所想

建议38:使用静态内部类提升封装性

一、Java中的嵌套类分为两种:静态内部类和内部类。

public class Person {
    // 姓名
    private String name;
    // 家庭
    private Home home;

    public Person(String _name) {
        name = _name;
    }

    /* home、name的setter和getter方法略 */

    public static class Home {
        // 家庭地址
        private String address;
        // 家庭电话
        private String tel;

        public Home(String _address, String _tel) {
            address = _address;
            tel = _tel;
        }
        /* address、tel的setter和getter方法略 */
    }
}

其中,Person类中定义了一个静态内部类Home,它表示的意思是“人的家庭信息”,因为Home类封装了家庭信息,不用在Person类中定义homeAddr,homeTel等属性,这就提升了封装性,可读性也提升了。

public static void main(String[] args) {
    // 定义张三这我的
    Person p = new Person("张三");
    // 设置张三的家庭信息
    p.setHome(new Home("北京", "010"));
 }

二、静态内部类的优点:

提升封装性

提升代码的可读性

形似内部,神似外部

静态内部类虽然存在于外部类中,并且编译后的类文件也包含外部类(格式是:外部类+$+内部类),可是它能够摆脱外部类存在,也就是说能够经过new Home()声明一个home对象,只是须要导入Person.Home而已。

三、静态内部类和普通内部类的区别:

① 静态内部类不持有外部类的引用:

普通内部类能够访问外部类的全部属性和方法。

静态内部类只能访问外部类的静态属性和静态方法。

② 静态内部类不依赖外部类:

普通内部类与外部类同生共死,一块儿被垃圾回收。

静态内部类能够独立存在。

③ 普通内部类不能声明static的方法和变量,final static修饰的常量除外。

建议39:使用匿名类的构造函数?

建议40:匿名类的构造函数很特殊?

建议39和建议40暂时没看出有啥用,再说吧!若有须要请阅读原著《编写高质量代码:改善Java程序的151个建议》!

建议41:让多继承成为现实

在Java中一个类能够多重实现,但不能多重继承,也就是说一个类可以同时实现多个接口,但不能同时继承多个类。

Java中提供的内部类能够曲折的解决此问题。

建议42:让工具类不可实例化

建议43:避免对象的浅拷贝

咱们知道一个类实现了cloneable接口就表示它具有了被拷贝的能力,若是重写clone()方法就会彻底具有拷贝能力。拷贝是在内存中进行的,因此性能方面比直接new生成的对象要快不少,特别在大对象的生成上,性能提高很是显著。可是浅拷贝存在对象属性拷贝不完全的问题。

浅拷贝:

一、概念

建立一个新对象,而后将当前对象的非静态字段复制到新对象,若是是值类型,对该字段进行复制;若是是引用类型,复制引用但不复制引用的对象。所以,原始对象及其副本引用同一个对象。

二、实现方法

调用对象的 clone 方法,必需要让类实现 Cloneable 接口,而且覆写 clone 方法。

建议44:推荐使用序列化对象的拷贝

深拷贝:

一、概念

建立一个新对象,而后将当前对象的非静态字段复制到该新对象,不管该字段是值类型的仍是引用类型,都复制独立的一份。当你修改其中一个对象的任何内容时,都不会影响另外一个对象的内容。

二、实现方法

① 让每一个引用类型属性内部都重写clone()方法

② 利用序列化

序列化是将对象写到流中便于传输,而反序列化则是把对象从流中读取出来。这里写到流中的对象是原始对象的一个拷贝,由于原始对象还存在在JVM中,因此咱们能够利用对象的序列化产生克隆对象,而后经过反序列化获取这个对象。

注意每一个须要序列化的类都要实现serializable接口,若是有某个属性不须要序列化,能够将其声明为transient,即将其排除在克隆属性以外。

三、利用序列化完成深拷贝的代码实例

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public final class CloneUtils {
    private CloneUtils() {
        throw new Error(CloneUtils.class + " cannot instance ");
    }

    // 拷贝一个对象
    public static <T extends Serializable> T clone(T obj) {
        // 拷贝产生的对象
        T cloneObj = null;
        try {
            // 读取对象字节数据
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(cloneObj);
            oos.close();
            // 分配内存空间,写入原始对象,生成新对象
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            // 返回新对象, 并作类型转换
            cloneObj = (T) ois.readObject();
            ois.close();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return cloneObj;

    }
}

此工具类要求被拷贝的对象实现serializable接口。

注:Apache下的Commons工具包中SerializationUtils类,直接使用更加简洁。

建议45:重写equals方法时不要识别不出本身

重写的equals方法作了多个校验,考虑到Web上传递过来的对象有可能输入了先后空格,因此用trim方法剪切了一下。

建议46:equals应该考虑null值情景

建议47:在equals中使用getClass进行类型判断

建议48:重写equals方法必须重写hashcode方法

为啥?官方解释没看懂。

建议49:推荐重写toString方法

Java提供的默认toString方法不友好,打印出来看不懂,不重写不行!

建议50:使用package-info类为包服务

package-info简称包文档,为包级文档和包级别注释提供一个地方,并且该文件惟一必须包含的是包的声明。

一、package-info的建立

bb3a6dd8846e71134c98ecb33fe5d6e9ff9.jpg

c4a67fef7eba63d25f73730d84d584e6fd8.jpg

二、package-info类不能继承,没有接口,没有类间关系等。

但package-info有啥用呢?只是对这个包的注释说明?

① 声明友好类和包内访问常量:

这个比较简单,并且很实用,好比一个包中有不少内部访问的类或常量,就能够统一放到package-info类中,这样很方便,便于集中管理,能够减小友好类处处游走的状况,代码以下:

class PkgClazz {
    public void test() {
    }
}
    
class PkgConstant {
    static final String PACKAGE_CONST = "ABC";
}

注意以上代码是放在package-info.java中的,虽然它没有编写package-info的实现,可是package-info.class类文件仍是会生成。经过这样的定义,咱们把一个包须要的常量和类都放置在本包下,在语义上和习惯上都能让程序员更适应。

② 为在包上提供注解提供便利:

好比咱们要写一个注解(Annotation),查一下包下的对象,只要把注解标注到package-info文件中便可,并且不少开源项目中也采用了此方法,好比Struts2的@namespace、hibernate的@filterDef等。

③ 提供包的总体注释说明:

若是是分包开发,也就是说一个包实现了一个业务逻辑或功能点或模块或组件,则该包须要一个很好的说明文档,说明这个包是作什么用的,版本变迁历史,与其余包的逻辑关系等,package-info文件的做用在此就发挥出来了,这些均可以直接定义到此文件中,经过javadoc生成文档时,会吧这些说明做为包文档的首页,让读者更容易对该包有一个总体的认识。

建议51:不要主动进行垃圾回收

System.gc要中止全部的响应,才能检查内存中是否存在能够回收的对象,这对一个应用系统来讲风险极大。

 

编写高质量代码:改善Java程序的151个建议@目录

相关文章
相关标签/搜索