长整型 long
本章定位:long是Java中最大的整数类型,64位有符号二进制补码,取值范围约±9×10¹⁸(九百亿亿)。在飞翔科技的系统中,世界人口、订单号、时间戳、公司资产这些"天文数字"都离不开long。白歌强调:"赋值时别忘了写L——这是新手最容易犯的错,编译器不会帮你。"
定义与语法
| 属性 | 说明 |
|---|---|
| 关键字 | long(长整型) |
| 位数 | 64 bits(8字节) |
| 表示法 | 有符号二进制补码 |
| 取值范围 | -9223372036854775808 ~ 9223372036854775807(即 -2⁶³ ~ 2⁶³-1) |
| 默认值 | 0L |
| 包装类 | java.lang.Long |
| 字面量后缀 | 必须加 L 或 l(推荐大写L,小写l易与数字1混淆) |
基本声明
long orderId = 20251201123456L; // 订单号
long worldPopulation = 8_000_000_000L; // 世界人口80亿
long chinaPopulation = 1_400_000_000L; // 中国人口14亿
long guangdongPopulation = 125_000_000L; // 广东人口1.25亿
long companyAssets = 100_000_000_000L; // 公司资产1000亿
long timestamp = System.currentTimeMillis(); // 当前时间戳
long imageFileSize = 5_242_880L; // 5MB商品图片
深度原理:long的64位世界与JVM双槽位
JVM中long的存储特点
long在JVM操作数栈中占用两个连续槽位(64位),这使得long的读写不是原子性的(在32位JVM上)。Java内存模型(JMM)规定:对volatile long/double的写入是原子的,但对普通long/double的64位写入可能被拆分为两个32位操作。
long的字面量后缀:L为什么不能省?
完整代码示例
示例一:飞翔科技的人口与订单数据
/**
* 飞翔科技 - long 类型演示
* 场景:飞翔商城的订单号、全球用户统计、文件大小
*/
public class LongDemo {
public static void main(String[] args) {
// 订单与用户数据
long orderId = 20251201123456L;
long worldPopulation = 8_000_000_000L; // 80亿
long chinaPopulation = 1_400_000_000L; // 14亿
long guangdongPopulation = 125_000_000L; // 1.25亿
// 文件与时间
long imageFileSize = 5_242_880L; // 5MB商品图片(字节)
long timestamp = System.currentTimeMillis();
// 公司资产
long companyAssets = 100_000_000_000L; // 1000亿
// 计算:广东人口占中国人口比例
double guangdongRatio = (double) guangdongPopulation / chinaPopulation * 100;
System.out.println("【飞翔科技 - 大数据场景】");
System.out.println("订单号:" + orderId);
System.out.println("世界人口:" + worldPopulation);
System.out.println("中国人口:" + chinaPopulation);
System.out.println("广东人口:" + guangdongPopulation);
System.out.println("广东占比:" + String.format("%.2f", guangdongRatio) + "%");
System.out.println("图片文件大小:" + imageFileSize + " bytes ("
+ (imageFileSize / 1024 / 1024) + " MB)");
System.out.println("当前时间戳:" + timestamp);
System.out.println("公司资产:¥" + companyAssets);
System.out.println("\n【类型信息】");
System.out.println("long 最小值:" + Long.MIN_VALUE);
System.out.println("long 最大值:" + Long.MAX_VALUE);
System.out.println("long 位数:" + Long.SIZE + " bit");
}
}
运行输出:
【飞翔科技 - 大数据场景】
订单号:20251201123456
世界人口:8000000000
中国人口:1400000000
广东人口:125000000
广东占比:8.93%
图片文件大小:5242880 bytes (5 MB)
当前时间戳:1733097600000
公司资产:¥100000000000
【类型信息】
long 最小值:-9223372036854775808
long 最大值:9223372036854775807
long 位数:64 bit
示例二:时间戳转换——long到日期的实际应用
/**
* 飞翔科技 - long 时间戳应用
* 场景:李眉用long时间戳记录系统部署时间并计算运行天数
*/
import java.text.SimpleDateFormat;
import java.util.Date;
public class LongTimestampDemo {
public static void main(String[] args) {
// 飞翔科技成立时间:2018年1月1日 00:00:00
// 使用 long 存储时间戳
long foundedTimestamp = 1514736000000L; // 2018-01-01 00:00:00 UTC+8
long currentTimestamp = System.currentTimeMillis();
// 计算公司运营天数
long diffMillis = currentTimestamp - foundedTimestamp;
long diffDays = diffMillis / (24 * 60 * 60 * 1000L);
long diffYears = diffDays / 365;
// 格式化时间戳为可读日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String foundedDate = sdf.format(new Date(foundedTimestamp));
String currentDate = sdf.format(new Date(currentTimestamp));
System.out.println("【飞翔科技 - 公司运营数据】");
System.out.println("成立时间:" + foundedDate);
System.out.println("当前时间:" + currentDate);
System.out.println("运营天数:" + diffDays + " 天");
System.out.println("运营年数:约 " + diffYears + " 年");
// JDK 8 的 Instant 类也基于 long
System.out.println("\n【底层原理】");
System.out.println("System.currentTimeMillis() 返回的是");
System.out.println("自1970-01-01 00:00:00 UTC以来的毫秒数(long类型)");
}
}
运行输出:
【飞翔科技 - 公司运营数据】
成立时间:2018-01-01 00:00:00
当前时间:2025-12-01 12:00:00
运营天数:2891 天
运营年数:约 7 年
【底层原理】
System.currentTimeMillis() 返回的是
自1970-01-01 00:00:00 UTC以来的毫秒数(long类型)
易错场景
反例一:忘记L后缀——最经典的新手错误
// ❌ 错误:8000000000 超出 int 范围(int最大约21亿)
long population = 8000000000; // 编译错误:integer number too large
// ❌ 错误:虽然数值在int范围内但语义不对
long orderId = 20251201; // 编译器允许(在int范围内),但可能不够大
// ✅ 正确:加L后缀明确表示long字面量
long population = 8_000_000_000L;
long orderId = 20251201123456L;
反例二:用long计算除法时丢失精度
// ❌ 错误:先以long做整数除法,丢失小数
long a = 10L;
long b = 3L;
double result = a / b; // a/b = 3L(整数除法),然后3L→3.0
System.out.println(result); // 输出 3.0,不是 3.333...
// ✅ 正确:先转浮点再除
double correctResult = (double) a / b; // 3.3333333333333335
反例三:小写l与数字1混淆
// ❌ 危险:小写l看起来像1
long value = 100l; // 是"100L"还是"1001"?难以分辨!
// ✅ 正确:始终使用大写L
long value = 100L; // 清晰明确
反例四:long字面量溢出
// ❌ 错误:虽然后缀是L,但数值超过了long的最大值
long tooBig = 9223372036854775808L; // 编译错误
// long 最大值: 9223372036854775807
// 超出1就会编译错误
面试考点
Q1:long l = 100; 为什么不需要加L后缀?
因为整数字面量
100在int范围内(-2147483648~2147483647),Java会先将其作为int字面量处理,然后通过**自动拓宽转换(widening)**赋给long。只有在字面量超出int范围时(如8000000000),才必须加L。但建议long变量始终加L,以明确表达意图。
Q2:long类型在32位JVM上读写是原子操作吗?
不完全是。Java内存模型规定:对volatile修饰的long/double,读写是原子的;对非volatile的long/double,JVM可能将其64位操作拆分为两个32位操作。这意味着在多线程环境中,一个线程可能读到另一个线程"写了一半"的long值(高32位是新值,低32位是旧值)。64位JVM通常能保证原子性,但规范不保证。
Q3:System.currentTimeMillis() 返回的long会在什么时候溢出?
它返回自1970-01-01 00:00:00 UTC以来的毫秒数。long最大约9.22×10¹⁸毫秒,换算成年约2.92亿年。因此该值在可预见的未来不会溢出。但Java的
Instant类使用秒+纳秒偏移的存储方式,能表示的时间范围更大。
Q4:long和Long有什么区别?
long是基本类型,存储在栈或对象内,直接存值,默认0,没有方法。Long是包装类,存储在堆,是对象,默认null,提供parseLong()、compare()等工具方法。JDK 5+支持自动装箱(long→Long)和拆箱(Long→long),但拆箱时如果Long为null会抛出NullPointerException。