ElIauk

Java 基础语法:变量、类型转换与运算符

先把变量说清楚 学语法最容易犯的错,是把“能跑通”当成“写对了”。变量、类型转换、运算符这三件事,几乎每个 Java 新手都会在第一次写业务代码时栽跟头:金额算出来差一分、两个 Integer 用 == 比较时灵时不灵、循环里不小心把下标算错。先把这些基础概念捋顺,后面读框架源码会轻松很多。 Jav

评论
Java 基础语法:变量、类型转换与运算符

先把变量说清楚

学语法最容易犯的错,是把“能跑通”当成“写对了”。变量、类型转换、运算符这三件事,几乎每个 Java 新手都会在第一次写业务代码时栽跟头:金额算出来差一分、两个 Integer 用 == 比较时灵时不灵、循环里不小心把下标算错。先把这些基础概念捋顺,后面读框架源码会轻松很多。

Java 是静态类型语言,变量一旦声明,类型就定死了。声明一个变量至少要回答三个问题:存什么类型、叫什么名字、初值是多少。名字建议用小驼峰,别用 a、b、tmp 这种过两天自己都看不懂的命名。

八种基本类型

类型 字节 范围/说明 字面量注意
byte 1 -128 ~ 127 文件读写、网络传输常用
short 2 -32768 ~ 32767 业务代码很少用
int 4 约 ±21 亿 整数字面量的默认类型
long 8 范围极大 超出 int 的字面量必须加 L
float 4 单精度小数 字面量必须加 f
double 8 双精度小数 小数字面量的默认类型
char 2 单个字符 用单引号,如 'A'
boolean 1 true / false 不能用 0/1 代替

记住两条默认规则:写 100 得到的是 int,写 3.14 得到的是 double。所以 long id = 3000000000; 编译不过,因为右边已经超出 int 范围,必须写成 3000000000L;float rate = 0.85; 同样编译不过,得写 0.85f。涉及金额、利率这类精确计算的场景,不要用 float、double,直接用 BigDecimal。

八种基本类型都有对应的包装类:Integer、Long、Double、Character、Boolean 等。集合里只能放对象,所以 List<Integer> 是合法的,List<int> 不行。包装类可以为 null,基本类型不能,这个差异既是便利也是隐患:声明成 Integer 的字段忘了赋值,参与运算时就会空指针。

字面量还有几种写法值得知道:0x10 是十六进制,0b1010 是二进制,1_000_000 是 JDK 7 引入的下划线分隔,编译时会被忽略。char 既可以直接写字符,也可以写 Unicode 转义,比如 '\u4e2d' 就是“中”字。

默认值只在成员变量上生效:类的字段不赋值时,int 是 0、boolean 是 false、对象是 null;方法里的局部变量没有默认值,必须先赋值再使用,否则编译直接报错。

类型转换:小转大自动,大转小强制

public class ConvertDemo {
    public static void main(String[] args) {
        int price = 199;
        long total = price;                    // 小转大,自动完成
        double rate = 0.85;
        int pay = (int) (price * rate);        // 大转小,必须强转

        byte b = (byte) 200;                   // 200 超出 byte 范围
        long big = 3_000_000_000L;             // 超出 int,必须加 L

        System.out.println(total);             // 199
        System.out.println(pay);               // 169,小数被直接截断
        System.out.println(b);                 // -56,溢出后的结果
        System.out.println(big);               // 3000000000
    }
}

强制转换只是截断,不会四舍五入。想要四舍五入用 Math.round(199 * 0.85),想要保留两位小数用 BigDecimal.setScale(2, RoundingMode.HALF_UP)。另外,强转前一定要确认目标类型的范围,(byte) 200 会静默变成负数,编译器不会给任何提示。

运算符:四个高频问题

public class OperatorDemo {
    public static void main(String[] args) {
        System.out.println(5 / 2);          // 2,整数除法
        System.out.println(5 / 2.0);        // 2.5
        System.out.println(-7 % 3);         // -1,余数符号跟被除数

        int i = 1;
        System.out.println(i++);            // 1,先取值再加
        System.out.println(++i);            // 3,先加再取值

        Integer a = 128;
        Integer b = 128;
        System.out.println(a == b);         // false,比较的是对象地址
        System.out.println(a.equals(b));    // true

        Integer count = null;
        // System.out.println(count + 1);   // NPE,自动拆箱
    }
}

&& 和 & 的区别也常被忽略:&& 短路,左边为 false 时右边不执行;& 两边都会算。判断 user != null && user.getName() != null 时必须用 &&,写成 & 一样会空指针。

常见坑

1. 整数溢出

int max = Integer.MAX_VALUE; max + 1 会变成 -2147483648,且不报错。涉及数量、金额、时间戳的计算,提前用 long,或者用 JDK 8 的 Math.addExact,溢出时直接抛异常,比静默算错强。

2. 浮点数直接比较

0.1 + 0.2 == 0.3 的结果是 false,因为二进制无法精确表示 0.1。判断浮点相等要写 Math.abs(a - b) < 1e-9,金额比较用 BigDecimal.compareTo。

3. 包装类型的 ==

Integer 在 -128 到 127 之间会走缓存,此时 == 恰好为 true;超过 127 就是两个对象,== 变成 false。这种“小数据对、大数据错”的 bug 最难查,统一用 equals 或先拆成 int 再比。

4. 复合赋值隐含强转

short s = 1; s = s + 1; 编译不过,因为 s + 1 会提升成 int;但 s += 1; 能编译,因为它自带隐式强转。知道这一点,看到别人的代码里 += 不报错就不用奇怪了。

5. char 参与运算

char c = 'a'; c = c + 1; 编译不过,c + 1 已经是 int。要写 c = (char) (c + 1),或者直接用 c++。

验证清单

  • 每个变量声明时都给了合理初值,没有“先声明后面再说”;
  • long 字面量带 L,float 字面量带 f;
  • 强转前确认过目标类型的范围;
  • 金额、精度计算使用 BigDecimal,没有用 double;
  • 包装类型比较用 equals,布尔判断用 && 而不是 &。

参考

评论

推荐阅读

订阅邮件

订阅更新,及时收到最新文章。

作者