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

原文链接: https://my.oschina.net/u/4006148/blog/3071585

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

建议1:不要在常量和变量中出现易混淆的字母

建议2:莫让常量蜕变成变量

建议3:三元操作符的类型务必一样

建议4:避免带有变长参数的方法重载

建议5:别让null值和空值威胁到变长方法

建议6:覆写边长方法也循规蹈矩

建议7:警惕自增的陷阱

建议8:不要让旧语法困扰你

建议9:少用静态导入

建议10:不要在本类中覆盖静态导入的变量和方法

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

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

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

建议1:不要在常量和变量中出现易混淆的字母

建议2:莫让常量蜕变成变量

常量蜕变成变量?你胡扯吧,加了final和static的常量怎么可能会变呢?不可能为此赋值的呀。真的不可能吗?看看如下代码:

import java.util.Random;

public class Demo01 {
    public static void main(String[] args) {
        test02();
    }

    public static void test02() {
        System.out.println("常量会变哦:" + Constant.RAND_CONST);
    }
}

interface Constant {
    public static final int RAND_CONST = new Random().nextInt();
}

RAND_CONST是常量吗?它的值会变吗?绝对会变!这种常量的定义方式是绝对不可取的,常量就是常量,在编译期就必须确定其值,不应该在运行期更改,否则程序的可读性会非常差,甚至连作者自己都不能确定在运行期发生了何种神奇的事情。

   甭想着使用常量会变的这个功能来实现序列号算法、随机种子生成,除非这真的是项目中的唯一方案,否则就放弃吧,常量还是当常量使用。

注意:务必让常量的值在运行期保持不变。

建议3:三元操作符的类型务必一样

建议4:避免带有变长参数的方法重载

建议5:别让null值和空值威胁到变长方法  

client.methodA("china", null);

编译不通过,提示相同:方法模糊不清,编译器不知道调用哪一个方法,但这两处代码反应的味道是不同的。

String str = null;

client.methodA("china", str);

让编译器知道这个null值是String类型的,编译即可顺利通过,也就减少了错误的发生。

建议6:覆写边长方法也循规蹈矩

注意:覆写的方法参数与父类相同,不仅仅是类型、数量,还包括显示形式.

建议7:警惕自增的陷阱

建议8:不要让旧语法困扰你

建议9:少用静态导入

从Java5开始引入静态导入语法(import static),其目的是为了减少字符的输入量,提高代码的可阅读性,以便更好地理解程序。

对静态导入,追寻两个原则:

1、不使用通配符,除非是导入静态常量类;

2、方法名具有明确、清晰表象意义的工具类。

建议10:不要在本类中覆盖静态导入的变量和方法

如果在一个类中的方法及属性与静态导入的方法及属性相同会出现什么问题呢?看下面的代码

import static java.lang.Math.PI;
import static java.lang.Math.abs;

public class Client10 {
    // 常量名于静态导入的PI相同
    public final static String PI = "江疏影";
    //方法名于静态导入的方法相同
    public static int abs(int abs) {
        return 0;
    }

    public static void main(String[] args) {
        System.out.println("PI = "+PI);
        System.out.println("abs(-100) = "+abs(-100));
    }
}

以上代码中定义了一个String类型的常量PI,又定义了一个abs方法,与静态导入的相同。首先说好消息,代码没有报错,接下来是坏消息:我们不知道那个属性和方法别调用了,因为常量名和方法名相同,到底调用了那一个方法呢?运行之后结果为:

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

很明显是本地的方法被调用了,为何不调用Math类中的属性和方法呢?那是因为编译器有一个“最短路径”原则:如果能够在本类中查找到相关的变量、常量、方法,就不会去其它包或父类、接口中查找,以确保本类中的属性、方法优先。

因此,如果要变更一个被静态导入的方法,最好的办法是在原始类中重构,而不是在本类中覆盖。

建议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();
    }
}

编写高质量代码:改善Java程序的151个建议(第1章:Java开发中通用的方法和准则___建议1~13)..._第2张图片

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

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

通过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方法。

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

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

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

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

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

编写高质量代码:改善Java程序的151个建议(第1章:Java开发中通用的方法和准则___建议1~13)..._第3张图片

在构造函数中不允许对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);
    }
}

编写高质量代码:改善Java程序的151个建议(第1章:Java开发中通用的方法和准则___建议1~13)..._第4张图片

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

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

编写高质量代码:改善Java程序的151个建议(第1章:Java开发中通用的方法和准则___建议1~13)..._第5张图片

现在的Java好像已经命令禁止final的重新赋值了!

 

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

 

转载于:https://my.oschina.net/u/4006148/blog/3071585

你可能感兴趣的:(编写高质量代码:改善Java程序的151个建议(第1章:Java开发中通用的方法和准则___建议1~13)...)