一、不要在常量和变量中出现易混淆的字母
public class Client {
public static void main(String[] args) {
long i = 1l;
System.out.println(i+i);
}
}
输出结果:2
注意:字母l和o尽量不要跟数字混用,如果需要时,l写成大写L,o要做下注释
二、莫让常量蜕变成变量
import java.util.Random;
public class Client {
public static void main(String[] args) {
System.out.println("常量会变哦:" + Const.RAND_CONST);
}
}
/*接口常量*/
interface Const{
//这还是常量吗?
public static final int RAND_CONST = new Random().nextInt();
}
输出结果:每次的值都会随机生成
注意:常量就是常量,在编译器就要确定他的唯一值,不要让他在运行期进行改变,否则程序的可读性会非常差。
三、三元操作符两个操作数的类型必须一致
public class Client {
public static void main(String[] args) {
int i = 80;
String s = String.valueOf(i<100?90:100);
String s1 = String.valueOf(i<100?90:100.0);
System.out.println("两者是否相等:"+s.equals(s1));
}
}
输出结果:两者是否相等:false(s的值是90,而s1的值是90.0,这是因为三元操作符的返回值类型要确定,而出现两个操作数的类型不一致时,会发生类型的转换,所以90就会变成90.0)
注意:
三元操作符的转换规则:
1、若两个操作符不可转换时,则不做转换,返回类型是Object
2、若两个操作符是明确类型的表达式(比如变量),则按照正常的二进制数字来转换,int变为long,long变为float等
3、若两个操作符都是直接的数字,则小范围的转换为大范围的
4、若两个操作符一个是表达式,一个是直接的数字,则如果数字在表达式类型的范围内,则数字转换为表达式的类型,如果数字大过表达式的类型,则表达式的类型转为数字的类型
所以要尽量保证三元操作符的两个操作数的类型一致。
四、避免带变长参数的方法的重载
import java.text.NumberFormat;
public class Client {
//简单折扣计算
public void calPrice(int price,int discount){
float knockdownPrice = (price / 100F) * (discount / 100.0F);
System.out.println("简单折扣后的价格是:"+formateCurrency(knockdownPrice));
}
//复杂多折扣计算
public void calPrice(int price,int... discounts){
float knockdownPrice = (price / 100F);
for(int discount:discounts){
knockdownPrice = knockdownPrice * discount / 100F;
}
System.out.println("复杂折扣后的价格是:" +formateCurrency(knockdownPrice));
}
//格式化成本地货币形式
private String formateCurrency(float price){
return NumberFormat.getCurrencyInstance().format(price);
}
public static void main(String[] args) {
Client client = new Client();
//499元的货物,打75折
client.calPrice(49900, 75);
}
}
输出结果:简单折扣后的价格是:¥374.25
注意:java引入变长参数是为了更好的提供方法的复用性,可以有0个或多个参数,然而要遵守一定得规则:1、变长参数必须是方法的最后一个参数,2、一个方法中只能有一个变长参数。然而有时候也会出现一些混淆的情况,比如上面的方法,如果我们是client.calPrice(49900, 75,95);调用,因为只有第2 个方法满足,所以只会调用第2个方法,而client.calPrice(49900, 75);调用则两个方法都满足,但是编辑器比较懒,发现第一种方法的参数最简单,又 满足条件,所以就选择了第一种方法来调用,但我们看代码时有时就会困惑,所以为避免这种情况,我们应该避免带变长参数的方法的重载。
五、别让null值和空值威胁到变长方法
public class Client {
public void methodA(String str,Integer... is){
System.out.println("Integer");
}
public void methodA(String str,String... strs){
System.out.println("String");
}
public static void main(String[] args) {
Client client = new Client();
client.methodA("China", 0);//正常运行
client.methodA("China", "People");//正常运行
// client.methodA("China");//编译不通过
// client.methodA("China",null);//编译不通过
}
}
注意:对于methodA("China"),两个方法都满足,编译器根据懒人原则又判断不出哪个比较简单,所以不知该调用哪个方法,就会出错,而methodA("China",null),由于null是没有类型的,所以编译器也判断不出该调用哪个,改正方法时先给出明确的类型,比如String str = null; client.methodA("China",str);或String[] str = null;
六、覆写变长方法也循规蹈矩
public class Client {
public static void main(String[] args) {
//向上转型
Base base = new Sub();
base.fun(100, 50);
//不转型
Sub sub = new Sub();
sub.fun(100, 50);//编译不过
}
}
//基类
class Base{
void fun(int price,int... discounts){
System.out.println("Base……fun");
}
}
//子类,覆写父类方法
class Sub extends Base{
@Override
void fun(int price,int[] discounts){
System.out.println("Sub……fun");
}
}
结果:上面的情况编译器编译不过
注意:第一种情况是因为基类对象把子类对象做了向上转型,形参列表由父类来决定,编译时base.fun(100, 50);会被编译器当成sub.fun(100, new int[]{50});来执行,所以编译通过,所以只要把sub.fun(100, 50);改为sub.fun(100, new int[]{50});就也可以编译通过。
七、警惕自增的陷阱
public class Client {
public static void main(String[] args) {
int count =0;
for(int i=0;i<10;i++){
count=count++;
}
System.out.println("count="+count);
}
}
结果:count=0
注意:count++是有返回值的,它的返回值就是count自加前的值,java对自加是这样处理的:首先把count的值(是值不是引用)拷贝到临时变量区,然后对count变量加1,最后返回临时变量区的值。上面程序的第一次循环时处理步骤:1、将count=0拷贝到临时变量去,然后count变量加1变为1;2、临时临时变量区的值0返回赋值给了count变量,所以count变量又变为了0;每次的循环都一样,所以最后count还是为0;上面程序改正确的方法为将count=count++;改为count++;
八、不用让旧语法困扰你
public class Client {
public static void main(String[] args) {
//数据定义及初始化
int fee=200;
//其他业务处理
saveDefault:save(fee);
//其他业务处理
}
static void saveDefault(){
}
static void save(int fee){
}
}
结果:编译不会报错
注意:上面主要是用到了:旧语法,一个标志位加:相当于是goto语句,一般是用在跳出多重循环
ok:
for(int i=0,j=10;i<j;i++){
for(int m=0,n=10;m<n;m++){
System.out.println("hzb");
if(m==5){
break ok;
}
}
}
上面只输出了6次hzb,跳出了双重循环,但建议别这么用,应该在内部循环中加一个标志属性来控制外面的循环。
九、少用静态导入
//没有用静态导入的 public class MathUtils{ //计算圆面积 public static double calCircleArea(double r){ return Math.PI * r * r; } //计算球面积 public static double calBallArea(double r){ return 4*Math.PI * r * r; } } //用了静态导入的
import static java.lang.Math.PI;; public class MathUtils{ //计算圆面积 public static double calCircleArea(double r){ return PI * r * r; } //计算球面积 public static double calBallArea(double r){ return 4 * PI * r * r; } }
注意:很明显,用了静态导入减少了代码量,但会大大增加了阅读难度,如果用了*把静态导入,有时候根本就不知道哪个方法是属于哪个类的,所以静态导入一般情况下不要用,要用也不要使用*
十、不要在本类中覆盖静态导入的变量和方法
import static java.lang.Math.PI;
import static java.lang.Math.abs;
public class Client {
//常量名与静态导入的PI相同
public final static String PI="hzb";
//方法名与静态导入的相同
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));
}
}
输出结果为:
PI=hzb
abs(100)=0
注意:编译器有个最短原则,就是能在本类中找到的变量,常量和方法就不会去别处查找,所以他用了本地的常量和方法,静态导入的常量和方法就没用了。
十一、养成良好习惯,显式声明UID
我们编写一个实现了Serializable(序列化标志接口)的类时,myeclipse会提示要增加一个uid,类实现Serializable是为了可序列化,从而可在网络传输或本地存储,我们来看下例子:
import java.io.Serializable;
public class Person implements Serializable{
private String name;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
protected void test(){
}
}
public class Producer {
public static void main(String[] args) throws Exception {
Person person = new Person();
person.setName("混世魔王");
//序列化,保存到磁盘上
SerializationUtils.writeObject(person);
}
}
/**
* 序列化工具
*/
public class SerializationUtils {
private static String FILE_NAME = "c:/obj.bin";
// 序列化
public static void writeObject(Serializable s) {
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_NAME));
oos.writeObject(s);
oos.close();
} catch (Exception e) {
//异常处理
}
}
public static Object readObject(){
Object obj=null;
// 反序列化化
try {
ObjectInput input = new ObjectInputStream(new FileInputStream(FILE_NAME));
obj = input.readObject();
input.close();
} catch (Exception e) {
//异常处理
}
return obj;
}
}
public class Consumer {
public static void main(String[] args) throws Exception {
// 反序列化化
Person p = (Person) SerializationUtils.readObject();
System.out.println("name="+p.getName());
}
}
假设Person 、SerializationUtils 、Producer 是发送端项目,Person 、SerializationUtils 、Consumer接收端项目,如果我们没有在Person显示声明uid,假设当发送端的Person多增加了个属性,而没有将该类发给接收端更新,然后就序列化,这样当接收端去反序列化时由于jvm判断了序列化的类和自己本地的类的版本不一致(根据uid来判断的,uid又是根据字段,方法等所有东西去计算出来的唯一的字符串),所以就会报异常。所以我们可以显示声明uid,这样当反序列化时,jvm就会以为两个类的版本是一致的,所以可以正常运行原先的方法,只是发送端新增加的方法和字段没法访问。
十二、序列化类避免在构造函数中为final变量赋值
注意:反序列化时构造函数不会执行