第1章 Java开发中通用的方法和准则

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

  • 包名:全小写。
  • 类名:首字母全大写。
  • 常量:全部大写并用下划线分割。
  • 变量:驼峰命名法。
package com.company;

/**
 * 数字后跟小写字母l的问题
 */
public class Client {
    public static void main(String[] args) {
        long i = 1l;
        System.out.println("i的两倍是:" + (i + i));
    }
}

输出结果:i的两倍是:2

注意:字母"l"作为长整型标志时务必大写。

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

package com.company;

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

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

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

package com.company;

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

  • 三元操作符类型的转换规则:
  • 若两个操作数不可转换,则不做转换,返回值为Object类型。
  • 若两个操作数市明确类型的表达式(比如变量),则按照正常的而进制数字来转换,int类型转换为long类型,long类型转换为float类型等。
  • 若两个操作数中有一个市数字S,另外一个是表达式,且其类型标记为T,那么,若数字S在T的范围内,则转换为T类型;若S超出了T类型的范围,则T转换为S类型。
  • 若两个操作数都是直接量数字,则返回值类型为范围较大者。

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

变长参数遵循的规则:变长参数必须是方法中的最后一个参数;一个方法不能定义多个变长参数。

package com.company;

import java.text.NumberFormat;

public class Client {

    // 简单折扣计算
    public void callPrice(int price, int discount) {
        float knowdownPrice = price * discount / 100.0F;
        System.out.println("简单折扣后的价格是:" + formateCurrency(knowdownPrice));
    }

    // 复杂多折扣计算
    public void callPrice(int price, int... discounts) {
        float knockdownPrice = price;
        for (int discount : discounts) {
            knockdownPrice = knockdownPrice * discount / 100;
        }
        System.out.println("复杂折扣后的价格是:" + formateCurrency(knockdownPrice));
    }

    // 格式化成本的货币形式
    private String formateCurrency(float price) {
        return NumberFormat.getCurrencyInstance().format(price / 100);
    }

    public static void main(String[] args) {
        Client client = new Client();
        // 499的货物,打75折
        client.callPrice(49900, 75);
    }
}

结果:简单折扣后的价格是:¥374.25

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

package com.company;

public class Client {

    public void methodA(String str, Integer... is) {
    }

    public void methodA(String str, String... strs) {
    }

    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);
    }
}

结果:编译通不过。

    public static void main(String[] args) {
        Client client = new Client();
        String[] strs = null;
        client.methodA("China", strs);
    }

这样修改后便可以编译通过。

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

覆写必须满足的条件:

  • 重写方法不能缩小访问权限。
  • 参数列表必须与被重写方法相同。
  • 返回类型必须与被重写方法的相同或是其子类。
  • 重写方法不能抛出新的异常,或者超出父类范围的异常,但是可以抛出更少、更有限的异常,或者不抛出异常。
package com.company;

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");
        }
    }
}

结果:编译通不过。

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

建议7:小心自增的陷阱

package com.company;

/**
 * 警惕自增的陷阱
 */
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++是一个表达式,是有返回值的,它的返回值就是count自加前的值,Java对自加是这样处理的:首先把count的值(注意是值,不是引用)拷贝到一个临时变量区,然后对count变量加1,最后返回临时变量区的值。
程序第一次循环时的详细处理步骤如下:

  • 步骤1 JVM把count值(其值是0)拷贝到临时变量区。
  • 步骤2 count值加1,这时候count的值是1。
  • 步骤3 返回临时变量区的值,注意这个值是0,没修改过。
  • 步骤4 返回值赋值给count,此时count值被重置为0。

“count=count++” 这条语句可以按照如下代码来解释:

    public static int mockAdd(int count) {
        //先保存初始值
        int temp = count;
        //做自增操作
        count = count + 1;
        //返回原始值
        return temp;
    }

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

package com.company;

/**
 * 不用让旧语法困扰你
 */
public class Client {

    public static void main(String[] args) {
        //数据定义及初始化
        int fee = 200;
        //其他业务处理
        saveDefault:
        save(fee);
        //其他业务处理
    }

    static void saveDefault() {
    }

    static void save(int fee) {
    }
}

注意:goto语法。

建议9:少用静态导入

从java5开始引入了静态导入语法,目的是为了减少字符输入量,提高代码的可阅读性。

package com.company.section1;

/**
 * 建议9:少用静态导入
 */
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;
    }
}

使用静态导入后的程序如下:

package com.company.section2;

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;
    }
}

静态导入的作用是把Math类中的PI常量引入到本类中。

如果在一个类中有多个静态导入语句时,若还使用了*通配符,简直是恶梦,看一段例子:

package com.company.section3;

import java.text.NumberFormat;

import static java.lang.Double.parseDouble;
import static java.lang.Integer.parseInt;
import static java.lang.Math.PI;
import static java.text.NumberFormat.getInstance;

public class Client {
    //输入半径和精度要求,计算面积
    public static void main(String[] args) {
        double s = PI * parseDouble(args[0]);
        NumberFormat nf = getInstance();
        nf.setMaximumFractionDigits(parseInt(args[1]));
        formatMessage(nf.format(s));
    }

    //格式化消息输出
    public static void formatMessage(String s) {
        System.out.println("圆面积是:" + s);
    }
}

对于静态导入,一定要遵循两个规则:

  • 不使用*通配符,除非是导入静态常量类(只包含常量的类或接口)。
  • 方法名是具有明确、清晰表象意义的工具类。

来看看JUnit4中使用的静态导入的例子:

package com.company.section3;

import org.junit.Test;

import static org.junit.Assert.*;

/*
 * 具有明确表象意义的方法
 */
public class DaoTest {
    @Test
    public void testInsert() {
        //断言
        assertEquals("foo", "foo");
        assertFalse(Boolean.FALSE);
    }
}

class Base {
    public Object doSomething(String str) {
        return null;
    }
}

class Sub extends Base {
    @Override
    public String doSomething(String str) {
        return null;
    }
}

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

package com.company;

/**
 * 建议10:不用在本类中覆盖静态导入的变量和方法
 */
public class Client {

    //常量名与静态导入的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));
    }
}

结果:
PI=祖冲之
abs(100)=0

注意: 编译器“最短路径”原则:如果能够在本类中查找到的变量、常量、方法,就不会到其他包或父类、接口中查找,以确保本类中的属性、方法优先。因此,如果要变更一个被静态导入的方法,最好的办法是在原始类中重构,而不是在本类中覆盖。

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

来看一个简单的序列化类:

package com.company;

import java.io.Serializable;

public class Person implements Serializable {

    private static final long serialVersionUID = 3424643650528555799L;

    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() {

    }

}

定义一个消息的生产者,代码如下:

package com.company;

/**
 * 消息的生产者,也就是序列化类
 */
public class Producer {
    public static void main(String[] args) throws Exception {
        Person person = new Person();
        person.setName("混世魔王");
        //序列化,保存到磁盘上
        SerializationUtils.writeObject(person);
    }
}

引入一个工具类,作用是对一个类进行序列化和反序列化,并存储到硬盘上(模拟网络传输),其代码如下:

package com.company;

import java.io.*;

/**
 * 序列化工具
 */
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;
    }

}

发送到消费者,并进行反序列化,生成实例对象,代码如下:

package com.company;

/**
 * 养成良好习惯,显式声明UID
 */

public class Consumer {

    public static void main(String[] args) throws Exception {
        // 反序列化化
        Person p = (Person) SerializationUtils.readObject();
        System.out.println("name=" + p.getName());
    }
}

JVM是根据什么来判断一个类版本的呢?
通过SerialVersionUID,也叫流标识符(Stream Unique Identifier),既类的版本定义的,可以显示声明也可以隐世声明。显示声明格式如下:

private static final long serialVersionUID = 3424643650528555799L;

生成的依据是通过包名、类名、继承关系、非私有的方法和属性,以及参数、返回值等诸多因子计算得出的,极度复杂,基本上计算出来的这个值是唯一的。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值