1. 为什么学Java要先啃下常用类型这块硬骨头我见过太多自学的朋友卡在同一个地方环境搭好了Hello World也跑通了结果一接触变量就开始懵。什么int、double、String、char看着都认识一写就报错——incompatible types、possible lossy conversion……光看报错提示就够劝退一半人。这篇是Java自学系列第三篇专门聊常用类型。这个主题听着基础但它直接决定了你后面写代码、读代码、调Bug的效率。类型系统是Java的地基几乎所有编译错误和运行期异常都跟类型有关。我见过有人跳过这一块直接冲面向对象结果连引用类型和基本类型的区别都说不清楚越学越乱最后还得回头补课。这篇内容适合两类人一类是刚学完环境搭建、准备正式接触语法的新手另一类是已经写了一阵子代码但对类型转换、包装类这些概念还模模糊糊的同学。我会把基本类型、引用类型、类型转换、包装类这些核心内容拆开讲透配合可以直接运行的代码和实操中踩过的坑帮你在起步阶段就把地基打牢。Java程序说到底就是操作数据而数据在Java里必须有一个明确类型。类型决定了数据能占多大内存、能参与什么运算、能赋什么值。这一篇搞懂了后面学面向对象、集合框架、泛型都会顺畅很多。2. 基本类型内存里最小的数据单元Java一共有8种基本类型它们不是对象直接存值存在栈内存里。这8种类型是Java语法的一部分语言层面写死的任何JDK版本都一样。2.1 整型家族byte、short、int、long先说整型。Java里有四种整数类型区别在于占用的字节数和取值范围类型占用字节取值范围默认值byte1-128 ~ 1270short2-32768 ~ 327670int4约 -21.47亿 ~ 21.47亿0long8-9223372036854775808 ~ 92233720368547758070L实际开发里整数默认都是int。你写int age 25;没问题但写byte b 128;就直接编译报错因为128超出byte的取值范围。新手最容易踩的坑是给byte或short赋一个看起来不大的值结果根本存不下。long类型要注意赋值时字面量后面要加大写的L或小写l但小写容易和数字1混淆不建议。比如long population 8000000000L;如果不加LJDK会把这8000000000当作int处理直接报integer number too large这个报错信息对新手非常不友好我第一次见的时候还以为是电脑内存不够。日常开发里byte和short用得不多它们主要出现在文件读写、网络传输、字节数组处理这些场景。long常用于时间戳、ID、大数值计数。初学者不用在byte和short上纠结太久知道取值范围、会声明就够了重点是int和long用熟练。2.2 浮点型float和double的精度陷阱浮点类型用来表示小数有两个float4字节单精度赋值时字面量必须加F或f后缀double8字节双精度Java默认使用doublefloat price 19.99F; double score 98.5;直接写float price 19.99;会编译报错因为不带后缀的小数字面量默认是double把double赋给float属于大转小Java不允许隐式转换。这个规则很多新手栽过记住float字面量必须带F。浮点数最大的坑是精度问题。计算机用二进制表示小数很多十进制小数无法精确表示。经典的例子double result 0.1 0.2; System.out.println(result); // 输出 0.30000000000000004你没看错0.1加0.2不等于0.3这是IEEE 754浮点表示法的固有缺陷。所以涉及金额计算、精确数学运算的场景绝对不能用double或float要用BigDecimal。我在实际项目里见过线上金额对不上账的事故最后查下来就是某处用了double做累加积少成多差了几分钱排查起来极其痛苦。初学者从现在起就养成习惯精确计算用BigDecimaldouble只做科学计算、统计展示这类允许微小误差的场景。判断浮点数是否相等也有讲究不要用直接比较两个double应该比较差值绝对值是否小于一个极小阈值double a 0.1 0.2; double b 0.3; System.out.println(Math.abs(a - b) 1e-9); // true2.3 char与boolean两个容易忽略的基础类型char是字符类型占2字节用单引号括起来char letter A; char chinese 中;Java的char存的是Unicode编码所以能直接表示中文字符。这里有个区分点单引号是char双引号是字符串String两者完全不同写混了编译直接报错。char可以做加减运算比如char next (char)(A 1);结果是B因为字符本质上是个数值。boolean只有两个值true和false用于逻辑判断。初学者容易把和搞混一个是比较相等一个是赋值写错不只是逻辑问题编译或运行结果会非常诡异boolean flag (score 60); // 正确 boolean flag2 (score 60); // 编译报错score是int不能赋给booleanboolean类型在条件判断、循环控制里无处不在理解它的语义不需要太多额外知识但有个小经验写判断条件时把常量放前面、变量放后面可以避免一些低级错误比如if (60 score)而不是if (score 60)手误少写一个等号时前者会编译报错提醒你而后者会直接把score赋成60逻辑跑偏还不容易发现。3. 引用类型操作对象的入口基本类型存值引用类型存地址。引用类型变量保存的是对象在堆内存中的引用可以粗略理解成内存地址通过这个引用去访问真正的对象。这个理念是整个Java面向对象的基础。3.1 String最常用也最容易踩坑的类String是引用类型但它在Java里地位特殊创建方式简单、使用频率极高而且有字符串常量池的优化机制。String name 张三; // 字面量方式 String greeting new String(你好); // new方式不推荐用字面量方式时JVM会先去常量池里找有没有相同内容的字符串有就复用没有就创建。所以String s1 hello; String s2 hello; System.out.println(s1 s2); // true指向同一个对象但用new创建的对象不走常量池每次都新建一个String s3 new String(hello); System.out.println(s1 s3); // falses3在堆里是全新对象新手很容易在这里困惑明明内容一样为什么比较是false因为比较的是引用地址不是内容。字符串内容比较要用equals方法System.out.println(s1.equals(s3)); // true这是我教学时反复强调的重点字符串比较永远用equals不用。开始学的时候就记死后面写业务代码才不会被这个坑折磨。String还有一个特性不可变。每次对String做拼接、替换操作都会生成一个新字符串对象原对象不变String s hello; s s world; // 实际上是创建了新字符串hello worlds的引用指向新对象所以循环里大量拼接字符串时不要用性能会非常难看应该用StringBuilderStringBuilder sb new StringBuilder(); for (int i 0; i 1000; i) { sb.append(i); } String result sb.toString();3.2 数组同类型数据的固定容器数组也是引用类型用来存放一组相同类型的数据。声明方式int[] numbers new int[5]; // 长度为5的int数组 String[] names {张三, 李四}; // 初始化赋值 double[] scores new double[]{98.5, 88.0, 79.5};数组的索引从0开始访问numbers[1]其实是第二个元素。新手最常见的异常就是数组越界int[] arr {1, 2, 3}; System.out.println(arr[3]); // ArrayIndexOutOfBoundsException这个异常出现频率极高几乎每个Java开发者都见过。避免越界最稳的方法是用arr.length做边界控制遍历时用增强for循环for (int num : arr) { System.out.println(num); }数组创建后长度不可变这是它的硬伤。实际开发中动态增删数据用的都是集合框架里的ArrayList、HashMap数组更多用于底层存储、性能敏感的场景。初学者把数组的声明、遍历、length属性掌握好就够用了不用深挖多维数组那属于后话。3.3 类与接口自己定义类型的起点除了JDK提供的String、数组Java允许你自定义类型。定义一个类就是在定义一种全新的引用类型public class Student { String name; int age; }这样Student就成了一个类型可以声明变量、创建对象Student stu new Student(); stu.name 张三; stu.age 18;这里有个必须搞清楚的点stu变量实际保存的是new Student()创建出来的对象在堆内存中的地址不是Student对象本身。如果执行Student stu1 new Student(); Student stu2 stu1;stu1和stu2指向同一个对象修改stu2的namestu1的name也会变。这是引用类型和基本类型最本质的区别初学者可以通过这个例子实际跑一遍打印变量值观察变化印象会非常深刻。接口是更抽象的类型它定义行为规范不实现具体逻辑。一个类可以实现多个接口。学习到面向对象章节时这些东西会系统展开这里只需要建立认知类、接口、枚举都是引用类型它们统一构成了Java的类型体系。4. 类型转换与包装类桥接不同数据的规则4.1 自动类型转换小类型往大类型走Java允许兼容类型之间转换。从小范围到大范围可以自动完成不需要写任何特殊语法int num 10; long bigNum num; // int转long自动 double d num; // int转double自动自动转换的方向是byte → short → int → long → float → double以及char → int。这条路径的本质是把小杯子里的水倒进大杯子不会溢出所以编译器放心让你转。这里有一个很多人觉得反直觉的点long8字节转float4字节是自动的。因为float虽然占的字节少但它用科学计数法表示能表达的范围比long大得多。所以Java规定long可以自动转float这是按照取值范围的覆盖关系不是按字节数来的。4.2 强制类型转换大转小必须显式声明从大范围往小范围转比如double转int必须写强制转换符否则编译报错double d 19.99; int i (int) d; // 结果为19小数部分直接没了强制转换的危险在于精度丢失和溢出。double转int会截断小数19.99变成19不会四舍五入。把超过int范围的值强制转成int结果会乱套long big 3000000000L; int i (int) big; // 结果是 -1294967296负数这种溢出问题非常隐蔽程序不报错但结果是错的。我在实际开发中见过对用户ID做强制类型转换导致数据错乱的例子排查了一整天才定位到是数值溢出。经验总结下来就是使用强制转换前务必确认值在目标类型的范围内或者加一层范围检查逻辑if (big Integer.MAX_VALUE || big Integer.MIN_VALUE) { // 处理溢出情况不要直接转 } else { int safeInt (int) big; }字符串和数值之间的转换也要注意写法。字符串转int用Integer.parseIntString s 123; int num Integer.parseInt(s); // 123但如果字符串内容不是纯数字parseInt会抛NumberFormatExceptionString invalid 12a; int num Integer.parseInt(invalid); // 异常所以在解析用户输入、文件内容时最好用try-catch包一层或者提前用正则、封装工具判断是否数字。int转字符串更安全直接用String.valueOf(num)基本不会出错。4.3 包装类基本类型和对象世界的桥梁Java是面向对象语言但基本类型不是对象。为了让基本类型也能参与面向对象的操作比如放进集合、作为泛型Java为每个基本类型配了对应的包装类基本类型包装类byteByteshortShortintIntegerlongLongfloatFloatdoubleDoublecharCharacterbooleanBoolean从JDK 5开始Java提供了自动装箱基本类型→包装类和自动拆箱包装类→基本类型Integer num 100; // 自动装箱编译器帮我们执行 Integer.valueOf(100) int plain num; // 自动拆箱编译器帮我们执行 num.intValue()看起来方便但自动装箱拆箱背后藏着性能开销和空指针风险。一个非常经典的问题Integer a 127; Integer b 127; System.out.println(a b); // true Integer c 128; Integer d 128; System.out.println(c d); // false同样的代码只是值从127变成128结果就反转了。原因在于Integer在-128到127之间做了缓存Integer.valueOf会返回缓存对象超过这个范围就会创建新对象。这是JDK内部优化只在缓存区间内生效。这个知识点几乎是Java笔试面试的保留题型面试必考这里提前讲清楚后面不吃亏。拆箱的空指针问题更隐蔽Integer num null; int value num; // NullPointerException自动拆箱时调用num.intValue()num为null就炸了从集合里取出包装类对象再直接做算术运算就很容易触发这个坑。我项目的经验是能用基本类型的地方用基本类型只有必须放进集合或者作为泛型参数时才用包装类而且使用前做好null检查。5. 自学者最容易犯的类型错误实例理论和代码看再多不踩几次坑记忆不深。我把自己和身边人踩过的类型相关错误整理成了一张表照着这个单子检查自己的代码能省下不少排查时间错误现象错误代码正确写法核心认知数字后没加Llong n 9999999999;long n 9999999999L;整数字面量默认intfloat没加Ffloat f 3.14;float f 3.14F;小数字面量默认doublebyte越界byte b 200;用int或shortbyte上限是127字符串用比较if (s1 s2)if (s1.equals(s2))比较地址equals比较内容数组越界arr[arr.length]arr[arr.length - 1]索引从0开始double比较相等if (a b)用差值绝对值判断二进制精度偏差大数溢出Math.toIntExact(...)不用提前范围检查或try-catch溢出不报错但结果错强转丢失精度(int) 19.9用Math.round先四舍五入强转直接截断包装类拆箱空指针Integer i null; int j i;null判断后再用拆箱触发方法调用这张表值得收藏。我刚开始自学那阵上面几乎每一条都亲手触发过尤其是字符串比较和数组越界几乎每天都要见一次。犯错不可怕重要的是犯错后能通过报错信息和代码逻辑快速定位到问题根源。报错信息其实是很好的老师。看到incompatible types说明类型不匹配看到possible lossy conversion说明你在做有精度风险的大转小运算看到NullPointerException优先查引用类型是否没判空。学会读报错信息是每个自学者长期进步的底层能力。还有一个自查方法在IDE里把鼠标悬停在变量上看它推断出来的类型。强烈建议初学阶段把IDEA的自动提示类型功能开着写每一行代码时观察变量类型是怎么变化的时间长了自然形成类型敏感度写代码就会少很多类型低级错误。6. 一个综合小案例把常用类型串起来光罗列知识点容易看着看着就忘了。我设计一个小案例把前面讲过的类型知识全揉在一起你跟着敲一遍理解会深很多。需求很简单记录几个学生的姓名和成绩计算平均分判断每个学生是否及格。public class ScoreCalculator { public static void main(String[] args) { String[] names {张三, 李四, 王五}; double[] scores {78.5, 92.0, 58.5}; double sum 0; for (int i 0; i names.length; i) { boolean passed scores[i] 60; System.out.println(names[i] 的成绩是 scores[i] (passed ? 及格 : 不及格)); sum scores[i]; } double avg sum / names.length; System.out.println(平均分 avg); // 使用强制转换演示精度概念 int intAvg (int) avg; System.out.println(平均分取整数部分 intAvg); // 演示字符串和数字互转 String avgStr String.valueOf(avg); System.out.println(平均分的字符串形式 avgStr); } }这个程序每一行都在运用前面的知识数组存数据、增强for循环遍历、boolean做判断、float/double算平均、强制转换截断小数、String.valueOf转换。把它完整敲一遍再自己改着玩比如把及格线改成自定义输入、加一个最高分最低分的统计收获会更大。我自己学Java的时候最有效的学习方式就是这种拆解小案例。每个案例不大但必须亲手敲不能光看。大脑看代码和手指敲代码形成的记忆路径完全不同。哪怕只是抄代码也比盯着屏幕看一个小时有用得多。等敲到顺手了再开始改需求、加功能知识就慢慢变成自己的了。7. 实操中关于类型的一些建议7.1 变量命名与类型习惯类型的选型和命名其实是写代码的门面。约定俗成的命名习惯常量全大写加下划线变量用驼峰命名类名首字母大写public class StudentManager { private static final int MAX_STUDENT_COUNT 100; private String studentName; private int studentAge; }类型使用上我的习惯是能用int不用long能用long不用BigDecimal除非真的需要高精度。这不是说BigDecimal不好而是它运算复杂、性能低杀鸡用牛刀没必要。反过来涉及金额就坚决用BigDecimal别图省事用double。7.2 一个完整练习用户输入校验再给个贴近真实开发的练习。需求是从控制台读入一个字符串如果它是纯整数就转成int打印否则提示重新输入。这需要组合运用字符串比较、类型转换、异常处理public class InputCheck { public static boolean isNumeric(String str) { if (str null || str.isEmpty()) { return false; } for (int i 0; i str.length(); i) { char c str.charAt(i); if (!Character.isDigit(c)) { return false; } } return true; } }实际的业务系统里几乎每一个外部输入都要经过类似校验才能安全地做类型转换。掌握这个套路你写出的代码就不容易出用户乱输入导致程序崩溃的问题。这个练习里同时用到了String、char、int、boolean、循环、包装类方法Character.isDigit一道题把所有常用类型复习了个遍。写代码这事前期拼的是语法熟练度中期拼的是程序设计和数据结构能力后期拼的是工程经验和架构思维。但无论哪个阶段类型都是一切代码的表皮。你写的每一行Java代码都在和各种类型打交道。把类型这层搞通透后面学泛型、反射、序列化这些高级特性时你会明显感觉到轻松。我个人在大量实操后有个深刻的体会类型问题看起来又碎又基础但它最值得花时间彻底搞清楚。很多看起来高深难懂的框架源码底层逻辑拆开看依然逃不出类型转换、对象引用、内存分配这些基础概念。基础打得越牢以后走得多远都不怕。这一篇的内容就是Java自学路上的地基之一建议初学阶段多花几周反复练习把每个类型的使用场景烂熟于心再往后走的每一步都会顺很多。
阅读完成 · 觉得有帮助?