关于java:程序员必备基础改善Java程序的20个实用建议

50次阅读

共计 7407 个字符,预计需要花费 19 分钟才能阅读完成。

最近细读了秦小波老师的《编写高质量代码改善 Jaav 程序的 151 个倡议》,要说是 151 个倡议,其实更适合的说是防止 Java 的一些冷门的坑,上面整顿了 20 个比拟乏味的倡议重新学习了一遍。

三元操作符的类型务必统一

三元操作符运算也称为三目运算,其表达式形如:” 条件表达式 ? 表达式 1 : 表达式 2 ″,在大部分语言中都有这样的三元操作符,其目标就是为了简化 if-else,当条件表达式为真时执行表达式 1,否则执行表达式 2。来剖析一下上面这段代码:

public static void main(String[] args){
    int i = 80;
    String s = String.valueOf(i < 100 ? 80 : 100);
    String s1 = String.valueOf(i < 100 ? 80 : 100.0);
    boolean equals = s.equals(s1);
    // 两者是否相等:false, 字符串 s 的值:80, 字符串 s1 的值:80.0
    System.out.println("两者是否相等:" + equals);
    System.out.println("字符串 s 的值:" + s);
    System.out.println("字符串 s1 的值:" + s1);
}

阐明:如果三目运算符的类型不统一,返回的后果也不统一。

防止带有变长参数的办法重载

public class Client {private static final Logger log = LoggerFactory.getLogger(Client.class);

    public static void main(String[] args) {Client client = new Client();
        client.calPrice(5000, 80);
    }

    /**
     * calPrice 简略折扣计算
     *
     * @param price    价格
     * @param discount 折扣
     * @description
     * @author luokangyuan
     * @date 2019-4-2 14:58
     * @version 1.0.0
     */
    private void calPrice(int price, int discount) {
        float knockdownPrice = price * discount / 100.0F;
        log.info("简略的折扣后的价格:{}", knockdownPrice);
    }

    /**
     * calPrice
     *
     * @param price     价格
     * @param discounts 折扣
     * @description 简单折扣计算
     * @author luokangyuan
     * @date 2019-4-2 15:08
     * @version 1.0.0
     */
    private void calPrice(int price, int... discounts) {
        float knockdownPrice = price;
        for (int discount : discounts) {knockdownPrice = knockdownPrice * discount / 100;}
        log.info("简单折扣后的价格:{}", knockdownPrice);
    }
}

阐明 :办法重载就是办法名雷同,参数类型或者参数数量不同,在上述例子中Java 编辑器会依据办法签名找到适合的适合的办法,上述测试调用的就是简略的折扣计算,而非简单折扣计算。

不要只替换一个类

public class Constant{public final static int MAX_AGE = 150;}

public class Client{public static void main(String[] args){System.out.println("人类寿命极限是:" + Constant.MAX_AGE);
    }
}

对于 final 润饰的根本类型和 String 类型,编译器会认为它是稳固态的 (Immutable Status) 所以在编译时就间接把值编译到字节码中了,防止了在运行期援用(Run-time Reference),以进步代码的执行效率。对于咱们的例子来说,Client 类在编译时字节码中就写上了 ”150″,这个常量,而不是一个地址援用,因而无论你后续怎么批改常量类,只有不从新编译 Client 类,输入还是依旧。

对于 final 润饰的类(即非根本类型),编译器会认为它不是稳固态的(Mutable Status),编译时建设的则是援用关系(该类型也叫作 Soft Final)。如果 Client 类引入的常量是一个类或实例,及时不从新编译也会输入最新值。

用偶判断,不必奇判断

String s = n % 2 == 1 ? "奇数" : "偶数";
String s1 = n % 2 == 0 ? "偶数" : "奇数";

阐明 :通常应用第二种偶数判断,应用第一种的话。-1 也会被判断为偶数。

用整数类型解决货币

// 0.40000000000000036
System.out.println(10.00 - 9.60);

阐明:Java 中的浮点数是不精确的,在解决货币上应用浮点数就会存在问题,因而应用BigDecimal,类来进行计算,或者应用整数,将须要计算的数放大 100 倍,计算后在放大。

1、应用BigDecimal

BigDecimal是专门为补救浮点数无奈准确计算的缺憾而设计的类,并且它自身也提供了加减乘除的罕用数学算法。特地是与数据库 Decimal 类型的字段映射时,BigDecimal 是最优的解决方案。

2、应用整型

把参加运算的值扩充 100 倍,并转为整型,而后在展示时再放大 100 倍,这样解决的益处是计算简略,精确,个别在非金融行业 (如批发行业) 利用较多。此办法还会用于某些批发 POS 机,他们输出和输入的全副是整数,那运算就更简略。

应用 String 间接赋值

public static void main(String[] args) {
    String str1 = "China";
    String str2 = "China";
    String str3 = new String("China");
    String str4 = str3.intern();

    System.out.println(str1 == str2); // true

    System.out.println(str1 == str3); // false

    System.out.println(str1 == str4); // true
}

阐明 :倡议应用String str1 = "China"; 这中形式对字符串赋值,而不是通过 new String("China"); 这种形式,在 Java 中会给定义的常量寄存在一个常量池中,如果池中存在就不会在反复定义,所以 str1 == str2 返回 truenew 出的是一个对象,不会去查看字符串常量池是否存在,所以 str1 == str3 是不同的援用,返回 false。通过intern() 解决后,返回 true,是因为intern() 会去对象常量池中查看是否存在字面上雷同得援用对象。

asList 产生的 list 不可批改

private static void arraysTest() {String[] week = {"Mon", "Tue", "Wed", "Thu"};
    List<String> strings = Arrays.asList(week);
    strings.add("Fri");
}

阐明:运行报错,asList 产生的 list 不可批改。

别让 null 值和空值威逼到变长办法

public void countSum(String type, Integer... is){}

public void countSum(String type, String... strs){}

public static void main(String[] args) {ClientReload clientReload = new ClientReload();
    clientReload.countSum("China", 0);
    clientReload.countSum("China", "People");
    // 编译报错
    clientReload.countSum("China");
    // 编译报错
    clientReload.countSum("China", null);
}

阐明 :同样是含有变长参数的重载办法,内部调用的应用应用NULL 或者空值都会呈现编译不通过的谬误,这是应为 NULL 和空值在上述重载的办法中都满足参数条件,所以编译器不晓得调什么办法,在重载办法的设计中违反了 KISS 准则,同时在内部调用的时候,内部调用这暗藏了实参的类型,如将调用代码做如下批改,就不存在编译报错了。

String strs = null;
clientReload.countSum("China", strs);

警觉自增的陷阱

public static void main(String[] args) {
    int count = 0;
    for (int i = 0; i < 100; i++) {
        int i1 = count++;
        count = i1;
        System.out.println("每次 count++ 的值:" + i1);
    }
    System.out.println(count);
}

阐明 :后果是0,而不是咱们100,这是count++ 是一个表达式,返回的是自加之前 count 的值。

break 不可遗记

public static void main(String[] args) {String s = toChineseNumber(2);
    log.info("转换后果:{}", s);
}

private static String toChineseNumber(int n) {
    String chineseNumber = "";
    switch (n) {
        case 0 : chineseNumber = "零";
        case 1 : chineseNumber = "壹";
        case 2 : chineseNumber = "贰";
        case 3 : chineseNumber = "叁";
        case 4 : chineseNumber = "肆";
        case 5 : chineseNumber = "伍";
        case 6 : chineseNumber = "陆";
        case 7 : chineseNumber = "柒";
        case 8 : chineseNumber = "捌";
        case 9 : chineseNumber = "玖";
        case 10 : chineseNumber = "拾";

    }
    return chineseNumber;
}

阐明 :在switchbreak肯定不能少。

不要让类型轻轻转换

/** 光速 */
private static final int LIGHT_SPEED = 30 * 10000 * 1000;

public static void main(String[] args) {
    long dis = LIGHT_SPEED * 60 * 8;
    // -2028888064
    System.out.println(dis);
}

阐明:LIGHT_SPEED * 60 * 8计算后是 int 类型,可能存在越界问题,尽管,咱们在代码中写了转换为 Long 型,然而,在 Java 中是先运算后在进行类型转换的,也就是 LIGHT_SPEED * 60 * 8 计算后是 int 型,超出了长度,从头开始,所以为负值,批改为显示的定义类型。如下:

/** 光速 */
private static final long LIGHT_SPEED = 30L * 10000 * 1000;

public static void main(String[] args) {
    long dis = LIGHT_SPEED * 60 * 8;
    System.out.println(dis);
}

防止带有变长参数的办法重载

public class MainTest {public static void main(String[] args) throws ExecutionException, InterruptedException {System.out.println(PriceTool.calPrice(12, 1)); // 1
    }
}
 
class PriceTool {public static int calPrice(int price, int discount) {return 1;}
    public static int calPrice(int price, int... discount) {return 2;}
}

阐明:编译器会从最简略的开始猜测,只有合乎编译条件的即采纳。

少用动态导入

import static java.lang.Math.PI;

public double calCircleArea(double r) {return Math.PI * r * r;}

public double calBallArea (double r) {return 4 * PI * r * r;}

阐明:动态导入能够缩小代码的量,但不易于浏览,可读性差。

提防包装类型的 null 值

public static int f(List<Integer> list){
    int count = 0;
    for (Integer i : list){count += (i != null) ? i : 0;
    }
    return count;
}

阐明:包装对象和拆箱对象能够自在转换,这不假,然而要剔除 null 值,null 值并不能转换为根本类型。对于此问题,咱们谨记一点:包装类型参加运算时,要做 null 值校验。

审慎包装类型的大小比拟

举个例子。i==j false。Integer 是援用类型。

public static void main(String[] args){Integer i = new Integer(100);
    Integer j = new Integer(100);
    System.out.println(i == j);
}

防止 instanceof 非预期后果

instanceof 是一个简略的二元操作符,它是用来判断一个对象是否是一个类实例的,两侧操作符须要有继承或实现关系。

public static void main(String[] args) {
    // String 对象是否是 Object 的实例 - true,"String" 是要给字符串,字符串继承了 Object,当然是 Object 的实例。boolean b1 = "String" instanceof Object;
    // String 对象是否是 String 类的实例 - true,一个类的对象当然是一个类的实例。boolean b2 = new String() instanceof String;
    // Object 对象是否是 String 的实例,编译报错,Object 是父类。boolean b3 = new Object() instanceof String;
    // 拆箱类型是否是装箱类型的实例,编译报错,“A”是一个 Char 型,是一个根本类型,instanceof 只能用于对象判断。boolean b4 = "A" instanceof Character;
    // 空对象是否是 String 的实例 - false,instanceof 的规定,右边为 null,无论左边什么类型,都返回 false。boolean b5 = null instanceof String;
    // 类型转换后的空对象是否是 String 的实例 - false,null 能够说是没有类型,类型转换后还是 null。boolean b6 = (String) null instanceof String;
    // Date 对象是否是 String 的实例,编译报错,Date 类和 String 类没有继承关系
    boolean b7 = new Date() instanceof String;}

不要轻易设置随机数种子

在 Java 中,随机数的产生取决于种子,随机数和种子之间的关系听从以下两个准则:种子不同,产生不同的随机数;种子雷同,即便实例不同也产生雷同的随机数。

public static void main(String[] args)
{Random r = new Random();
    for (int i = 1; i < 4; i++)
    {System.out.println("第" + i + "次:" + r.nextInt());

    }
}

运行后果:第 1 次:846339168
第 2 次:756051503
第 3 次:1696875906

程序启动后,生成的随机数会不同。然而每次启动程序,生成的都会是三个随机数。产生随机数和种子之间的关系如下:

1)种子不同,产生不同的随机数。

2)种子雷同,即便实例不同也产生雷同的随机数。

Random 的默认种子(无参结构)是 System.nanoTime() 的返回值(jdk1.5 以前是 System.currentTimeMillis()),这个值是间隔某一个固定工夫点的纳秒数,不同的操作系统和硬件有不同的固定工夫点,随机数天然也就不同了。

防止在构造函数中初始化其它类

public class Client35 {public static void main(String[] args) {Son son = new Son();
        son.doSomething();}
}

// 父类
class Father {public Father() {new Other();
    }
}

// 相干类
class Other {public Other() {new Son();
    }
}

// 子类
class Son extends Father {public void doSomething() {System.out.println("Hi, show me Something!");
    }
}

阐明:造成构造方法循环调用。

优先应用整型池

Integer 缓存了 -128-127 的 Integer 对象。所以通过装箱(Integer.valueOf())取得的对象能够复用。

 public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

性能思考,首选数组

private static int getSumForArray(int[] array) {
    int sum = 0;
    for (int i = 0; i < array.length; i++) {sum += array[i];
    }
    return sum;
}

private static int getSumForList(List<Integer> list) {return list.stream().mapToInt(Integer::intValue).sum();}

正文完
 0