Java 基础:从第一个程序到对象与集合
按顺序完成示例,再进入框架实践。本文先建立语言基础;旧的 Eclipse、SSM 安装截图保留在历史笔记中,不作为学习 Java 的前置条件。
1. 准备 JDK 并运行程序
JDK 提供编译与开发工具,JVM 执行字节码;运行时还需要标准类库。先安装项目要求的 JDK,并确认终端能找到同一套工具:
java -version
javac -version保存为 Hello.java,公开类名与文件名一致:
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, Java!");
}
}javac Hello.java
java Hellojavac 生成 Hello.class,java 按类名启动程序,不在类名后追加 .class。先跑通终端中的程序,再用 IDE,能更容易分清语言、构建工具和编辑器各自的问题。
2. 类型、变量和流程控制
Java 的八种基本类型是 byte、short、int、long、float、double、char、boolean。类、接口和数组属于引用类型;引用变量可以为 null,使用前要确认。
将 main 的方法体替换为:
int[] scores = {80, 90, 100};
int total = 0;
for (int score : scores) {
total += score;
}
if (total >= 270) {
System.out.println("passed: " + total);
}预期输出 passed: 270。&& 和 || 会短路,例如 name != null && !name.isEmpty() 不会在 name 为 null 时调用方法。& 还可用于整数位运算;用于布尔运算时会计算两侧,不等同于短路与。
把处理提取成方法
先把循环逻辑提取成有参数、有返回值的方法,再进入对象与接口。保存为 Count.java:
public class Count {
static int passed(int[] scores) {
int count = 0;
for (int score : scores) {
if (score >= 60) count++;
}
return count;
}
public static void main(String[] args) {
System.out.println(passed(new int[] {80, 50, 90}));
}
}编译运行后输出 2。int 是返回类型,scores 接收数组参数,return 把计数交给调用方;static 使当前例子能直接从静态入口调用,实例方法会在下一节结合对象解释。不要把 Java 方法和可以脱离类独立声明的顶层函数混为一谈。
3. 类、接口和对象相等
对象保存状态并通过方法提供行为。类可以继承一个父类、实现多个接口;优先先学组合与接口,再讨论复杂继承。
访问控制决定调用方可以看见哪些成员:
| 写法 | 基本访问边界 |
|---|---|
public | 可由其他类访问;跨模块访问还受模块导出规则约束 |
private | 限制在所属顶层类的嵌套范围内,不能由普通外部调用方直接访问 |
| 不写修饰符 | 包访问,同一包中可见 |
protected | 同包可见,也允许子类按语言规则访问;跨包子类访问有接收者等额外约束 |
下面把实现放在外部类内部,是为了让一个源文件就能运行;static 嵌套类不需要先创建外部类实例。implements 声明实现接口,@Override 让编译器帮助检查是否正确重写了接口方法。
public class Greeting {
interface Sender {
String send(String name);
}
static class ConsoleSender implements Sender {
@Override
public String send(String name) {
return "Hello, " + name;
}
}
public static void main(String[] args) {
Sender sender = new ConsoleSender();
System.out.println(sender.send("Ada"));
}
}保存为 Greeting.java,编译运行后输出 Hello, Ada。变量类型是接口,实际执行的是对象实现的方法。
- 重载:同名方法的参数列表不同,编译期根据调用信息选择;仅返回值不同不能构成重载。
- 重写:子类型提供继承方法的实现,受方法签名、访问权限与返回类型等规则约束。
- 接口与抽象类:二者都不能直接实例化;抽象类可持有实例字段和构造过程。接口除了抽象方法,也能声明默认方法、静态方法等,不能沿用“接口只有抽象方法”的旧结论。
static与实例成员:前者属于类,后者属于对象;静态字段也可能构成需要同步的共享可变状态。==与equals:基本类型按相应值语义比较,引用的==判断是否同一对象;equals的语义取决于类的实现。重写equals时通常需要一并重写hashCode。
String 不可变;需要反复拼接时可使用 StringBuilder。选择 StringBuffer 也不意味着一组业务操作自动具有原子性。
4. 异常、资源与输入输出
异常发生时应保留原因,并在能够处理的边界恢复或向上传递。不要把所有 Throwable 都吞掉,也不要把“错误一定不能捕获”当成语法规则。
| 类型 | 例子 | 处理重点 |
|---|---|---|
| 检查型异常 | IOException、SQLException | 编译期要求捕获或声明抛出 |
| 运行时异常 | NullPointerException、IllegalArgumentException | 常提示输入、状态或编程约束被破坏 |
Error | OutOfMemoryError、NoClassDefFoundError | 通常不靠普通业务重试解决 |
ClassNotFoundException 不是 RuntimeException,NoClassDefFoundError 也不是普通异常。旧笔记将它们放进同一清单,容易误导处理方式。
import java.io.BufferedReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
public class ReadFirstLine {
public static void main(String[] args) throws Exception {
try (BufferedReader reader = Files.newBufferedReader(
Paths.get("notes.txt"), StandardCharsets.UTF_8)) {
System.out.println(reader.readLine());
}
}
}准备 notes.txt 后运行。try 的资源声明负责关闭实现 AutoCloseable 的资源;教学示例把错误交给启动入口展示,业务方法应声明或处理更具体的异常。字节流处理原始字节,字符读取需要确认编码。
final 限制再次赋值、重写或继承,具体取决于修饰位置;finally 是异常处理结构的一部分,不能在进程被终止等情况下保证执行。finalize 已弃用并计划移除,不用于资源管理。
5. 集合:先选语义,再选实现
| 接口 | 需要的语义 | 常见实现 |
|---|---|---|
List | 保留顺序,允许重复 | ArrayList、LinkedList |
Set | 元素不重复 | HashSet、LinkedHashSet、TreeSet |
Map | 键映射到值,键不重复 | HashMap、LinkedHashMap、TreeMap |
接口不规定底层一定是数组或散列表,也不保证所有实现都有相同顺序。HashMap 不保证迭代顺序;需要特定顺序时显式选择相应实现。Collection 是接口,Collections 是工具类,Map 不继承 Collection。
先掌握泛型的基本读法:Map<String, Integer> 表示键为字符串、值为整数包装对象,尖括号中的类型参数让编译器检查放入和取出的类型。new LinkedHashMap<>() 根据左侧声明推断类型参数;暂时不需要先掌握泛型通配符和复杂约束。
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
public class WordCount {
public static void main(String[] args) {
Map<String, Integer> counts = new LinkedHashMap<>();
for (String word : Arrays.asList("go", "java", "go")) {
counts.put(word, counts.getOrDefault(word, 0) + 1);
}
for (Map.Entry<String, Integer> entry : counts.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
}预期按插入顺序输出 go=2、java=1。练习改用 TreeMap,观察键排序与插入顺序的区别。
6. 并发与反射的学习边界
先能独立写函数和类,再学习任务执行、共享数据与生命周期。Runnable 表示无返回值任务,Callable 可以返回结果;线程池负责调度任务,不是每个任务都创建一条新线程。提交任务后必须考虑等待结果、失败处理和关闭执行器。
synchronized 的互斥只约束使用同一把锁的代码。实例同步方法锁住接收者实例,静态同步方法锁住对应的 Class 对象。sleep 不释放已持有的监视器锁;wait 需要在持有对应监视器时调用,并释放该监视器,条件等待还要处理唤醒后再次检查。
Java 的线程状态为 NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED,不要用一条“就绪→运行→挂起”箭头代替实际状态与转换条件。
反射用于检查或操作运行时类型,入口包括 Type.class、object.getClass() 和 Class.forName(...)。它不意味着可以无条件绕过访问控制与模块边界;初学项目优先显式类型和接口。
7. 练习与后续顺序
- 给成绩数组增加不及格分数,写一个函数计算合格人数。
- 给
Sender增加另一个实现,说明重载与重写发生在哪里。 - 给词频例子增加大小写归一化,再检查计数结果。
- 删除
notes.txt观察错误,说明错误应在哪一层记录或处理。
参考解答(静态推演)
- 复用
passed方法,条件为score >= 60;输入{80, 50, 90}返回2,空数组返回0,正好60应计入。 - 新类实现
Sender并声明public String send(String name),这是重写;同一类另加send(String name, String prefix)才是重载,仅改返回类型不成立。 - 统计前用
word.toLowerCase(java.util.Locale.ROOT)归一化;输入"Go", "java", "go"仍按首次出现顺序输出go=2、java=1。显式 Locale 避免结果依赖机器默认地区。 - 读文件失败产生 IOException 的具体子类型;当前入口向上抛出,程序失败结束。库层应保留原因并交调用者决定,CLI 可在入口记录一次错误并返回非零状态;不能捕获后输出空报告冒充成功。
接下来按泛型、集合 API、异常与 I/O、并发、构建与测试的顺序深入,再学习 Spring。框架启动不等于掌握了语言基础。
继续阅读
- 排错
- 排错
- 开发实践