编写高质量代码:改善Java程序的151个建议(第1章:Java开发中通用的方法和准则___建议11~15)

做为一个由影视圈转行作Java的菜鸟来讲,读书是很关键的,本系列是用来记录《编写高质量代码 改善java程序的151个建议》这本书的读书笔记。方便本身查看,也方便你们查阅。java

建议11:养成良好习惯,显示声明UID程序员

    序列化Serializable是Java提供的通用数据保存和读取的接口。任何类只要实现了Serializable接口,就能够被保存到文件中,或者做为数据流经过网络发送到别的地方。服务器

package OSChina.Serializable;

import java.io.Serializable;

public class Man implements Serializable {
    private static final long serialVersionUID = 1L;

    private String username;
    private String password;

    public Man(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
package OSChina.Serializable;

import java.io.Serializable;

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;

    private Man man;
    private String username;
    private transient int age;

    public Person() {
        System.out.println("person constru");
    }

    public Person(Man man, String username, int age) {
        this.man = man;
        this.username = username;
        this.age = age;
    }

    public Man getMan() {
        return man;
    }
    public void setMan(Man man) {
        this.man = man;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
package OSChina.Serializable;

import java.io.*;

public class MainTest {
    private static final String FILE_NAME = "D:/data/rtdata.txt";
    public static void writeSerializableObject() {
        try {
            Man man = new Man("huhx", "123456");
            Person person = new Person(man, "刘力", 21);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(FILE_NAME));
            objectOutputStream.writeObject("string");
            objectOutputStream.writeObject(person);
            objectOutputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // Serializable:反序列化对象
    public static void readSerializableObject() {
        try {
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(FILE_NAME));
            String string = (String) objectInputStream.readObject();
            Person person = (Person) objectInputStream.readObject();
            objectInputStream.close();
            System.out.println(string + ", age: " + person.getAge() + ", man username: " + person.getMan().getUsername());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        writeSerializableObject();
        readSerializableObject();
    }
}

这是一个简单的JavaBean,实现了Serializable接口,能够在网络上传输,也能够在本地存储而后读取。网络

序列化和反序列化的类在不一致的状况下,反序列化时会报一个InalidClassException异常,缘由是序列化和反序列化所对应的类版本发生了变化,JVM不能把数据流转换为实例对象。刨根问底:JVM是根据什么来判断一个类的版本呢?app

经过SerializableUID,也叫作流标识符(Stream Unique Identifier),即类的版本定义的,它能够显示声明也能够隐式声明。显示声明格式以下:分布式

private static final long serialVersionUID = 1867341609628930239L;

serialVersionUID的做用:函数

JVM在反序列化时,会比较数据流中的serialVersionUID与类的serialVersionUID是否相同,若是相同,则认为类没有改变,能够把数据load为实例相同;若是不相同,抛个异常InviladClassException。性能

刚开始生产者和消费者持有的Person类一致,都是V1.0,某天生产者的Person类变动了,增长了一个“年龄”属性,升级为V2.0,因为种种缘由(好比程序员疏忽,升级时间窗口不一样等)消费端的Person类仍是V1.0版本,添加的代码为 priavte int age;以及对应的setter和getter方法。this

此时虽然生产这和消费者对应的类版本不一样,可是显示声明的serialVersionUID相同,序列化也是能够运行的,所带来的业务问题就是消费端不能读取到新增的业务属性(age属性而已)。spa

经过此例,咱们反序列化也实现了版本向上兼容的功能,使用V1.0版本的应用访问了一个V2.0的对象,这无疑提升了代码的健壮性。

显示声明serialVersionUID能够避免对象的不一致,但尽可能不要以这种方式向JVM撒谎。

建议12:避免用序列化类在构造函数中为不变量赋值

咱们知道带有final标识的属性是不变量,也就是只能赋值一次,不能重复赋值,可是在序列化类中就有点复杂了,好比这个类:

在构造函数中不容许对final变量从新赋值。

去掉final以后呢?

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    public static String perName="程咬金";

    public Person() {
        System.out.println("person constru");
        perName = "秦叔宝";
    }
}
public class Test {
    public static void main(String[] args) {
        System.out.println(perName);
    }
}

序列化与反序列化的时候构造函数不会执行。

建议13:避免为final变量复杂赋值

如今的Java好像已经命令禁止final的从新赋值了!

建议14:使用序列化类的私有方法巧妙解决部分属性持久化问题

例如:一个计税系统和一个HR系统,计税系统须要从HR系统得到人员的姓名和基本工资,而HR系统的工资分为两部分:基本工资和绩效工资,绩效工资是保密的,不能泄露到外系统。

public class Salary implements Serializable {
    private static final long serialVersionUID = 2706085398747859680L;
    // 基本工资
    private int basePay;
    // 绩效工资
    private int bonus;

    public Salary(int _basepay, int _bonus) {
        this.basePay = _basepay;
        this.bonus = _bonus;
    }
//Setter和Getter方法略

}
public class Person implements Serializable {

    private static final long serialVersionUID = 9146176880143026279L;

    private String name;

    private Salary salary;

    public Person(String _name, Salary _salary) {
        this.name = _name;
        this.salary = _salary;
    }

    //Setter和Getter方法略

}
public class Serialize {
    public static void main(String[] args) {
        // 基本工资1000元,绩效工资2500元
        Salary salary = new Salary(1000, 2500);
        // 记录人员信息
        Person person = new Person("张三", salary);
        // HR系统持久化,并传递到计税系统
        SerializationUtils.writeObject(person);
    }
}
public class Deserialize {
    public static void main(String[] args) {
        Person p = (Person) SerializationUtils.readObject();
        StringBuffer buf = new StringBuffer();
        buf.append("姓名: "+p.getName());
        buf.append("\t基本工资: "+p.getSalary().getBasePay());
        buf.append("\t绩效工资: "+p.getSalary().getBonus());
        System.out.println(buf);
    }
}

但这个不符合需求,你可能会想到一下四种解决方案:

一、java 的transient关键字为咱们提供了便利,你只须要实现Serilizable接口,将不须要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。

static修饰的变量也不能序列化。

在bonus前加上关键字transient,使用transient关键字就标志着salary失去了分布式部署的功能,一旦出现性能问题,再想分布式部署就不可能了,此方案否认。

注:分布式部署是将数据分散的存储于多台独立的机器设备上,采用可扩展的系统结构,利用多台存储服务器分担存储负担,利用未知服务器定位存储信息,提升了系统的可靠性、可用性和扩展性。

二、新增业务对象:增长一个Person4Tax类,彻底为计税系统服务,就是说它只有两个属性:姓名和基本工资。符合开闭原则,并且对原系统也没有侵入性,只是增长了工做量而已。可是这个方法不是最优方法;

下面展现一个优秀的方案,其中实现了Serializable接口的类能够实现两个私有方法:writeObject和readObject,以影响和控制序列化和反序列化的过程。

public class Person implements Serializable {

    private static final long serialVersionUID = 9146176880143026279L;

    private String name;

    private transient Salary salary;

    public Person(String _name, Salary _salary) {
        this.name = _name;
        this.salary = _salary;
    }
    //序列化委托方法
    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();
        oos.writeInt(salary.getBasePay());
    }
    //反序列化委托方法
    private void readObject(ObjectInputStream input)throws ClassNotFoundException, IOException {
        input.defaultReadObject();
        salary = new Salary(input.readInt(), 0);
    }
}

其它代码不作任何改动,运行以后结果为:

这里用到了序列化的独有机制:序列化回调。

Java调用ObjectOutputStream类把一个对象转换成数据流时,会经过反射(refection)检查被序列化的类是否有writeObject方法,而且检查其实否符合私有,无返回值的特性,如有,则会委托该方法进行对象序列化,若没有,则由ObjectOutputStream按照默认规则继续序列化。一样,从流数据恢复成实例对象时,也会检查是否有一个私有的readObject方法,若是有经过该方法读取属性值。

① oos.defaultWriteObject():告知JVM按照默认规则写入对象

② ois.defaultWriteObject():告知JVM按照默认规则读出对象

③ oos.writeXX和ois.readXX

分别是写入和对出响应的值,相似一个队列,先进先出,若是此处有复杂的数据逻辑,建议按封装Collection对象处理。

上面的方式也是Person失去了分布式部署的能了,确实是,可是HR系统的难点和重点是薪水的计算,特别是绩效工资,它所依赖的参数很复杂,计算公式也不简单(通常是引入脚本语言,个性化公式定制)而相对来讲Person类基本上都是静态属性,计算的可能性不大,因此即便为性能考虑,Person类为分布式部署的意义也不大。

既然这样,为什么不直接使用transient???

建议15:break万万不可忘

 

江疏影读书系列之编写高质量代码:改善Java程序的151个建议

相关文章
相关标签/搜索