从一段 Java 代码说起
很多人对 Java 的第一印象是「啰嗦」:写一个能打印文字的程序还要好几行。但如果把这几行拆开看,它其实把 Java 的核心思想都摆在明面上了——一切代码都要待在类里面,程序从 main 方法开始执行。理解了这两点,后面所有语法都是在这套骨架上的填充。
Java 是一门静态类型、编译型与解释型结合的面向对象语言。源码先由 javac 编译成字节码,再由 JVM 在各平台上执行,这就是「一次编写,到处运行」的来源。对初学者来说,前期不必深究虚拟机细节,先把语法写顺、把面向对象的思维方式建立起来,收益最大。
学 Java 的顺序很关键:先能写出一段完整可运行的小程序,再回头理解类型系统与对象模型。上来就背概念,很容易陷入「每个字都认识、连起来不会用」的状态。
- 基本类型八种内置类型各占多少位、默认值是什么,什么时候该用包装类。
- 流程控制if / switch、for / while 与作用域,决定代码的执行路径。
- 类与对象类描述结构与行为,对象是它的具体实例,靠构造方法初始化。
- 三大特性封装保护内部状态,继承复用结构,多态让同一调用产生不同行为。
变量、基本类型与运算符
Java 是强类型语言,每个变量在声明时就要确定类型,之后不能随意改变。基本类型共八种,它们直接存值、不涉及对象头,所以访问速度快。写代码时先在脑子里建立这张表,遇到「为什么整数除法丢小数」「为什么 long 要加 L」这类问题,答案基本都在类型宽度和取值范围里。
| 类型 | 位数 | 典型取值范围 | 常见用途 |
|---|---|---|---|
| byte | 8 | -128 ~ 127 | 文件、网络字节流 |
| short | 16 | -32768 ~ 32767 | 较少使用 |
| int | 32 | 约 ±21 亿 | 计数、索引的默认选择 |
| long | 64 | 约 ±9.2×10¹⁸ | 时间戳、大整数 |
| float | 32 | 约 ±3.4×10³⁸ | 精度要求低的浮点 |
| double | 64 | 约 ±1.8×10³⁰⁸ | 浮点运算的默认选择 |
| char | 16 | 0 ~ 65535 | 单个 UTF-16 字符 |
| boolean | 1 | true / false | 条件判断 |
对应的包装类(Integer、Long、Double……)把基本类型包成对象,方便放进集合、支持泛型与空值判断。二者之间会自动装箱与拆箱,但要注意:Integer 对 -128 到 127 做了缓存,用 == 比较小数值可能「碰巧相等」,比较对象值务必用 equals。
// HelloJava.java —— 第一个 Java 程序
public class HelloJava {
public static void main(String[] args) {
String name = "吉极云码";
int age = 3;
double score = 96.5;
boolean active = true;
long timestamp = 1781000000000L; // long 字面量要加 L
System.out.println("你好," + name);
System.out.println("年龄:" + age + ",评分:" + score + ",在线:" + active);
System.out.println("时间戳:" + timestamp);
}
}
运算符方面,整数相除会截断小数部分,需要精确结果先把任意一端转成浮点;++ 与 -- 的前后缀语义、&& 与 || 的短路特性,都是排查逻辑错误时的高频考点。类型转换分自动与强制两种,把大范围类型塞进小范围类型要显式强转,并小心溢出。
流程控制:分支、循环与作用域
程序默认从上往下顺序执行,流程控制语句用来改变这条直线。分支决定走哪条路,循环决定重复多少次,两者配合就构成了业务逻辑的基本形状。
- if / else if / else:按条件真假选择分支,条件必须是 boolean,不能用整数代替。
- switch:适合对同一个变量做多值匹配;现代 Java 的 switch 支持箭头语法与 yield,写法更紧凑。
- for:已知循环次数时首选,三段式(初始化、条件、步进)一目了然。
- while / do-while:条件驱动,do-while 至少执行一次。
- for-each:遍历数组与集合最简洁,但不适合需要在遍历中改动索引的场景。
作用域是初学者最容易踩的隐坑:一对花括号就是一层作用域,变量出了这层就不可见。循环里声明的变量不会泄露到循环外,方法里的局部变量不会影响别的同名变量——这条规则同时保证了代码的可读性与内存的可回收性。
控制循环还需要 break 和 continue:前者直接跳出整个循环,后者跳过本次进入下一次。它们常与带标签的循环配合使用,但过度嵌套会让可读性急剧下降,通常更推荐把内层逻辑抽成独立方法。
类与对象:把数据和行为装在一起
有了变量和流程控制,你已经能写出「能做事的程序」;但要写出「能长大的程序」,必须学会组织代码的方式,这就是面向对象。一句话概括:类是模板,描述有哪些属性和方法;对象是模板造出来的具体实例。比如「账户」是类,「张三的账户」是对象。
对象通过构造方法初始化。构造方法名与类名相同、没有返回值,可以重载多个版本,用来保证「对象一旦创建出来,内部状态就是合法的」。下面这段代码把余额设为私有字段,只允许通过方法读写,越界的输入在入口处就被拦下:
public class Account {
private double balance; // 私有字段,外部不能直接改
public Account(double initial) {
if (initial < 0) {
throw new IllegalArgumentException("初始余额不能为负");
}
this.balance = initial;
}
public double getBalance() {
return balance;
}
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("存款金额必须为正");
}
balance += amount;
}
public static void main(String[] args) {
Account acc = new Account(1000);
acc.deposit(500);
System.out.println("余额:" + acc.getBalance()); // 余额:1500.0
}
}
这里出现了几个关键细节:private 表示字段只在本类可见;this 指向当前对象,用来区分同名字段与参数;static 表示属于类而不属于某个对象,所以 main 方法能直接通过类名调用。把「数据」和「操作数据的方法」放在一起,正是面向对象区别于面向过程的核心。
封装、继承与多态
面向对象有三大特性,它们解决的是三个不同的问题:封装负责保护内部状态不被外部随意破坏;继承负责复用共性、避免重复代码;多态负责让程序在运行时按对象的真实类型选择行为,从而支持可替换、可扩展的结构。
| 特性 | 要解决的问题 | Java 中的抓手 |
|---|---|---|
| 封装 | 内部状态被外部改坏 | private 字段 + public 方法 |
| 继承 | 共性代码被重复书写 | extends、super |
| 多态 | 新增类型要改调用方 | 父类引用 + 方法重写 |
继承用 extends 表达「是一种」的关系。子类自动获得父类的非私有成员,并可以重写(override)父类方法。注意 Java 不支持类的多重继承,一个类只能有一个直接父类,但可以实现多个接口。下面这段代码用一个循环处理不同类型的动物,调用方完全不需要 instanceof 判断:
// 继承与多态:父类引用指向子类对象
class Animal {
protected String name;
Animal(String name) { this.name = name; }
String speak() { return name + " 发出了声音"; }
}
class Dog extends Animal {
Dog(String name) { super(name); }
@Override
String speak() { return name + " 汪汪叫"; }
}
class Cat extends Animal {
Cat(String name) { super(name); }
@Override
String speak() { return name + " 喵喵叫"; }
}
public class PolyDemo {
public static void main(String[] args) {
Animal[] zoo = { new Dog("旺财"), new Cat("咪咪") };
for (Animal a : zoo) {
System.out.println(a.speak()); // 运行时决定调用哪个版本
}
}
}
如果把 @Override 去掉,方法名拼错就会变成「新增一个方法」而不是重写,这类 bug 很难发现,所以注解一定要写。多态的价值在于:以后新增一种动物,只要它继承 Animal 并重写 speak,上面的循环一行都不用改。
接口与抽象类:约定优于实现
当多个毫不相关的类需要遵循同一套能力约定时,抽象类就不够用了,这时候用接口。接口只描述「能做什么」,不关心「怎么做到」,是实现解耦与依赖倒置的基础设施。
- 抽象类用
abstract声明,可以有字段、构造方法和已实现的方法,适合表达一组关系紧密、共享状态的类型。 - 接口用
interface声明,方法默认是公开抽象的;Java 8 起支持 default 与 static 方法,Java 9 起支持 private 方法。 - 选择标准:子类之间存在「是一种」的天然层级用抽象类;只是需要具备某种能力、可能出现在完全不同的继承体系中,用接口。
一个类可以 implements 多个接口,这弥补了单继承的局限。接口最常见的用法是定义服务契约:上层代码只依赖接口,具体实现可以在运行时注入,测试时替换成替身。这套思路正是后面 Spring Boot 依赖注入的语义基础。
异常处理与常用类库
程序出错有两种:一种是可以预料的(文件不存在、参数不合法),一种是不该发生的(数组越界、空指针)。Java 用异常体系把两者统一起来,让错误处理成为代码结构的一部分,而不是满屏的返回码判断。
- 受检异常(Exception 的非运行时子类)必须捕获或声明抛出,编译器会强制你处理。
- 运行时异常(RuntimeException 及其子类)通常是编码错误,不强制捕获,靠规范与测试避免。
- 错误(Error)由虚拟机层面抛出,如 OutOfMemoryError,程序一般无法恢复。
- try-with-resources 能自动关闭实现了 AutoCloseable 的资源,写文件流时强烈推荐。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class ReadDemo {
public static void main(String[] args) {
// try-with-resources:无论是否抛异常,流都会被关闭
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.err.println("读取失败:" + e.getMessage());
}
}
}
常用类库也值得尽早熟悉:String 与 StringBuilder 的取舍、集合框架里 List / Set / Map 的选择、Objects 与 Optional 对空值的处理、以及 java.time 提供的日期时间 API。它们决定了你日常写业务代码时的顺手程度。
从语法到工程:一份练手路线
语法学完不等于会用。把上面这些零散知识点串起来,最有效的方式是做一个有明确边界的小项目,比如一个命令行记账程序:用类表示账目与账本,用集合存储数据,用异常处理非法输入,用接口抽象导出格式。
- 第 1 周:把基本类型、流程控制写成小练习,确保能独立调试编译错误。
- 第 2 周:动手写三到五个类,练习构造方法、封装与 toString,理解对象生命周期。
- 第 3 周:引入继承与接口,重构出可扩展的结构,体会多态带来的好处。
- 第 4 周:补上集合与异常,把小项目做完整,并尝试加一个单元测试。
接下来自然的分岔口是 Web 开发。当你需要把代码变成对外提供的服务时,Spring Boot 会用依赖注入和自动装配替你处理大量样板工作,这也是 Java 生态最主流的路线。地基打稳之后,向上走会顺很多。
面向对象不是背下来的概念,而是写多了之后自然形成的组织代码的直觉。先写出能跑的代码,再回头问「这里还能不能拆得更清楚」。