做为一个由影视圈转行作Java的菜鸟来讲,读书是很关键的,本系列是用来记录《编写高质量代码 改善java程序的151个建议》这本书的读书笔记。方便本身查看,也方便你们查阅。java
建议1:不要在常量和变量中出现易混淆的字母程序员
建议2:莫让常量蜕变成变量算法
建议3:三元操做符的类型务必同样网络
建议4:避免带有变长参数的方法重载dom
建议5:别让null值和空值威胁到变长方法 函数
建议6:覆写边长方法也循规蹈矩工具
建议7:警戒自增的陷阱this
建议8:不要让旧语法困扰你spa
建议9:少用静态导入.net
建议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),其目的是为了减小字符的输入量,提升代码的可阅读性,以便更好地理解程序。
对静态导入,追寻两个原则:
一、不使用通配符,除非是导入静态常量类;
二、方法名具备明确、清晰表象意义的工具类。
建议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方法,与静态导入的相同。首先说好消息,代码没有报错,接下来是坏消息:咱们不知道那个属性和方法别调用了,由于常量名和方法名相同,到底调用了那一个方法呢?运行以后结果为:
很明显是本地的方法被调用了,为什么不调用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(); } }
这是一个简单的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标识的属性是不变量,也就是只能赋值一次,不能重复赋值,可是在序列化类中就有点复杂了,好比这个类:
在构造函数中不容许对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的从新赋值了!