先把变量说清楚
学语法最容易犯的错,是把“能跑通”当成“写对了”。变量、类型转换、运算符这三件事,几乎每个 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,布尔判断用&&而不是&。
评论