模块 02:Java 语言基础(面向前端开发者)
目标:掌握阅读和理解 Java 代码所需的核心语法,通过与 JavaScript/TypeScript 的对比快速建立映射。
前言:Java 与 JavaScript 的关系
虽然名字相似,Java 和 JavaScript 是两门完全不同的语言。但作为前端开发者,你已经具备的编程思维(变量、函数、条件、循环、对象、异步)会直接迁移过来。
核心差异一览:
| 特性 | JavaScript / TypeScript | Java |
|---|---|---|
| 类型检查 | 运行时(JS)/ 编译时+运行时(TS) | 编译时(强类型) |
| 执行方式 | 解释执行(V8 引擎) | 编译为字节码 → JVM 执行 |
| 入口 | 脚本直接执行 / main() 非必须 | 必须有 public static void main() |
| 文件结构 | 一个文件可导出多个内容 | 一个文件只能有一个 public 类 |
| 包管理 | import + package.json | import + 包声明 + Gradle/Maven |
| null 处理 | null / undefined | 只有 null(无 undefined) |
| 类 | 可选(函数式也行) | 必须(一切皆在类中) |
1. 基本语法对比
1.1 变量声明
// Java:类型在前,变量名在后,必须声明类型
String name = "张三"; // 类似 TS: let name: string = "张三"
int age = 25; // 类似 TS: let age: number = 25
boolean isActive = true; // 类似 TS: let isActive: boolean = true
double price = 19.99; // 浮点数用 double(64位)
float ratio = 0.5f; // 浮点数也可以用 float(32位),需加 f 后缀
long timestamp = 1700000000L; // 长整数需加 L 后缀
char letter = 'A'; // 单引号表示单个字符(不是字符串)
// 常量:用 final 代替 const
final String APP_NAME = "MyApp"; // 类似 TS: const APP_NAME = "MyApp"
// 类型推断(Java 10+):类似 TS 的 let x = ...
var count = 42; // 编译器推断为 int
var message = "hello"; // 编译器推断为 String与 JS 的关键区别:
- Java 没有
let/const/var的关键字选择(Java 10 之前必须写类型,Java 10+ 可用var推断) - Java 没有
undefined,未赋值的引用类型默认为null - Java 的基本类型(
int、double、boolean、char)不是对象,它们有对应的包装类(Integer、Double、Boolean、Character)
1.2 基本类型 vs 引用类型
这是 Java 与 JS 最大的区别之一。JS 中一切皆对象,Java 中不是:
// 基本类型(primitive):直接存储值,不是对象
int a = 10;
int b = a; // b 是 a 的副本,修改 b 不影响 a
b = 20;
// 此时 a = 10, b = 20
// 引用类型(reference):存储的是对象的引用(地址)
int[] arr1 = {1, 2, 3};
int[] arr2 = arr1; // arr2 指向同一个数组
arr2[0] = 99;
// 此时 arr1[0] 也是 99,因为它们指向同一个对象基本类型对照表:
| Java 基本类型 | 包装类 | JS/TS 对应 | 默认值 |
|---|---|---|---|
int | Integer | number | 0 |
long | Long | number / bigint | 0L |
double | Double | number | 0.0 |
float | Float | number | 0.0f |
boolean | Boolean | boolean | false |
char | Character | string(单字符) | '\u0000' |
byte | Byte | 无直接对应 | 0 |
short | Short | 无直接对应 | 0 |
为什么需要包装类? 因为 Java 的泛型不支持基本类型。
List<int>是非法的,必须用List<Integer>。这类似于 TypeScript 中number和Number的区别,但 Java 中这个区别更关键。
栈(Stack)与堆(Heap)
理解 Java 内存模型对排查内存泄漏和性能问题很有帮助:
public void run() {
int count = 10; // 基本类型:存于栈帧
User user = new User("张三"); // user 引用在栈,对象本身在堆
}| 区域 | 存放内容 | 生命周期 | 前端类比 |
|---|---|---|---|
| 栈 | 局部变量、方法参数、基本类型 | 方法返回即释放 | 函数调用栈 |
| 堆 | 所有 new 出来的对象、数组 | 由 GC 回收 | JS 堆内存 |
排查提示:
OutOfMemoryError通常与堆有关(创建了过多对象或加载了大图片);StackOverflowError与栈有关(无限递归)。
1.3 字符串
// 字符串是不可变的(和 JS 一样)
String greeting = "Hello";
String name = "World";
// 拼接(推荐用 + 或 String.format)
String message = greeting + ", " + name + "!";
String formatted = String.format("%s, %s!", greeting, name);
// 模板字符串(Java 没有模板字符串,最接近的方式)
// JS: `Hello, ${name}! You are ${age} years old.`
// Java:
String result = String.format("Hello, %s! You are %d years old.", name, age);
// 常用方法(与 JS 对比)
greeting.length(); // JS: greeting.length (注意有括号)
greeting.toUpperCase(); // JS: greeting.toUpperCase()
greeting.contains("ell"); // JS: greeting.includes("ell")
greeting.startsWith("He"); // JS: greeting.startsWith("He")
greeting.indexOf("l"); // JS: greeting.indexOf("l")
greeting.substring(1, 3); // JS: greeting.substring(1, 3) → "el"
greeting.replace("l", "r"); // JS: greeting.replaceAll("l", "r")
greeting.trim(); // JS: greeting.trim()
greeting.isEmpty(); // JS: greeting.length === 0
greeting.split(","); // JS: greeting.split(",")
greeting.equals("Hello"); // ⚠️ Java 用 .equals() 比较字符串内容!不是 ==⚠️ 最常见的坑:Java 中
==比较的是引用(地址),不是值。字符串比较必须用.equals():javaString a = new String("hello"); String b = new String("hello"); a == b // false!比较的是引用地址 a.equals(b) // true!比较的是内容
1.4 数组
// 声明和初始化
int[] numbers = {1, 2, 3, 4, 5}; // 直接初始化
String[] names = new String[3]; // 指定长度,元素默认 null
int[] scores = new int[]{90, 85, 78}; // 另一种写法
// 访问和修改
int first = numbers[0]; // 和 JS 一样
numbers[2] = 99; // 和 JS 一样
int length = numbers.length; // ⚠️ 没有括号!是属性不是方法
// 遍历
for (int i = 0; i < numbers.length; i++) {
System.out.println(numbers[i]);
}
// 增强 for 循环(类似 JS 的 for...of)
for (int num : numbers) {
System.out.println(num);
}
// 多维数组
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};前端开发者注意:Java 数组长度固定,不能像 JS 数组那样
push/pop。需要动态大小的集合,请用ArrayList(见下文)。
2. 控制流
2.1 条件语句
// if-else:和 JS 完全一样
if (age >= 18) {
System.out.println("成年");
} else if (age >= 12) {
System.out.println("青少年");
} else {
System.out.println("儿童");
}
// 三元运算符:和 JS 一样
String status = age >= 18 ? "成年" : "未成年";
// switch(传统形式)
switch (day) {
case 1:
System.out.println("周一");
break; // ⚠️ 必须有 break,否则会穿透到下一个 case
case 2:
System.out.println("周二");
break;
default:
System.out.println("其他");
}
// switch 表达式(Java 14+,更现代的写法)
String dayName = switch (day) {
case 1 -> "周一";
case 2 -> "周二";
case 3 -> "周三";
default -> "其他";
};2.2 循环
// for 循环:和 JS 一样
for (int i = 0; i < 10; i++) {
if (i == 5) break;
if (i == 3) continue;
System.out.println(i);
}
// while:和 JS 一样
while (condition) {
// ...
}
// do-while:和 JS 一样
do {
// ...
} while (condition);3. 面向对象编程(OOP)
这是 Java 的核心,也是与 JS 差异最大的部分。JS 的 class 只是语法糖,Java 的 class 是语言的一等公民。
3.1 类与对象
// 定义一个类
public class User {
// 字段(类似 JS 类的属性)
private String name; // private = 私有,外部不能直接访问
private int age;
private String email;
// 构造方法(类似 JS 的 constructor)
public User(String name, int age) {
this.name = name; // this 和 JS 的 this 概念类似
this.age = age;
this.email = ""; // 默认值
}
// 方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setEmail(String email) {
this.email = email;
}
public String getEmail() {
return email;
}
// 类似 JS 的 toString()
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + "}";
}
}
// 使用
User user = new User("张三", 25);
System.out.println(user.getName()); // "张三"
System.out.println(user); // 自动调用 toString()为什么这么多 getter/setter? 这是 Java 的编码惯例(JavaBean 规范)。在老项目中你会看到大量的
getXxx()/setXxx()方法。Kotlin 的属性语法可以消除这种样板代码。
3.2 访问修饰符
| 修饰符 | 类内部 | 同包 | 子类 | 任何地方 | JS/TS 对应 |
|---|---|---|---|---|---|
public | ✅ | ✅ | ✅ | ✅ | public |
protected | ✅ | ✅ | ✅ | ❌ | protected |
| (无修饰符) | ✅ | ✅ | ❌ | ❌ | 无直接对应 |
private | ✅ | ❌ | ❌ | ❌ | private |
在老项目中,
private+getter/setter是最常见的模式,称为"封装"。
3.3 继承
// 父类
public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void speak() {
System.out.println(name + " makes a sound");
}
}
// 子类:用 extends 继承(和 JS 的 class extends 一样)
public class Dog extends Animal {
private String breed;
public Dog(String name, String breed) {
super(name); // 调用父类构造方法(类似 JS 的 super())
this.breed = breed;
}
@Override // 重写父类方法(类似 JS 的方法覆盖)
public void speak() {
System.out.println(name + " barks");
}
public String getBreed() {
return breed;
}
}与 JS 的区别:
- Java 只支持单继承(一个类只能
extends一个父类) - 用
final修饰的类不能被继承(类似 JS 中没有直接对应) @Override注解不是必须的,但强烈建议加上,编译器会帮你检查
3.3.1 多态(Polymorphism)
多态是 OOP 的三大特性之一(封装、继承、多态)。简单说就是同一个方法调用,不同对象有不同行为:
// 父类引用指向子类对象
Animal animal1 = new Dog("旺财", 3, "金毛");
Animal animal2 = new Cat("咪咪", 2, true);
// 调用同一个方法,运行时根据实际对象类型决定执行哪个版本
animal1.speak(); // 输出 "旺财 barks"(实际执行 Dog.speak)
animal2.speak(); // 输出 "咪咪 makes a sound...喵"(实际执行 Cat.speak)
// 前端类比:TS 中 interface + 实现
// interface Animal { speak(): void; }
// class Dog implements Animal { speak() { console.log('汪!'); } }
// class Cat implements Animal { speak() { console.log('喵!'); } }
// const animals: Animal[] = [new Dog(), new Cat()];
// animals.forEach(a => a.speak()); // 各自输出不同
// 多态在 Android 中的典型应用
// OnClickListener 接口 → 不同 View 实现不同的点击行为
View.OnClickListener listener = new View.OnClickListener() {
@Override
public void onClick(View v) {
// v 可能是 Button、ImageView、TextView 等
// 运行时根据实际类型执行不同逻辑
}
};类型转换:多态常配合类型检查使用:
javaif (animal instanceof Dog) { Dog dog = (Dog) animal; // 向下转型 dog.getBreed(); } // Java 16+ 模式匹配(更简洁) if (animal instanceof Dog dog) { dog.getBreed(); // 自动转型 }
3.4 接口(Interface)
接口是 Java 中定义"契约"的方式,类似 TypeScript 的 interface:
// 定义接口(类似 TS: interface Clickable { void onClick(); String getLabel(); })
public interface Clickable {
void onClick();
String getLabel();
// Java 8+:默认方法(有实现体)
default void onDoubleClick() {
onClick();
onClick();
}
}
// 实现接口(用 implements,类似 TS 的 class Xxx implements Clickable)
public class Button implements Clickable {
private String label;
public Button(String label) {
this.label = label;
}
@Override
public void onClick() {
System.out.println("Button clicked: " + label);
}
@Override
public String getLabel() {
return label;
}
}
// 一个类可以实现多个接口(弥补单继承的限制)
public class ImageButton extends ImageView implements Clickable, LongPressable {
// 必须实现 Clickable 和 LongPressable 中的所有抽象方法
}为什么接口重要? Java 不允许多重继承,但允许多重实现。当你看到老项目中
implements Serializable, Parcelable, OnClickListener这种代码时,就知道它同时遵循了多个契约。
3.5 抽象类
// 抽象类:不能直接实例化,必须被子类继承
public abstract class Shape {
protected String color;
public Shape(String color) {
this.color = color;
}
// 抽象方法:没有方法体,子类必须实现
public abstract double getArea();
// 普通方法:有实现,子类可以直接用
public String getColor() {
return color;
}
}
public class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
// Shape shape = new Shape("red"); // ❌ 编译错误,不能实例化抽象类
Shape circle = new Circle("red", 5.0); // ✅ 用子类实例化3.6 静态成员(static)
public class MathUtils {
// 静态字段:属于类本身,不属于某个实例
public static final double PI = 3.14159265;
// 静态方法:不需要创建实例就能调用
public static int add(int a, int b) {
return a + b;
}
public static int max(int a, int b) {
return a > b ? a : b;
}
}
// 使用(类似 JS: Math.PI, Math.max())
double area = MathUtils.PI * r * r;
int sum = MathUtils.add(3, 5);前端对照:JS 中的
Math.PI、Array.isArray()、Object.keys()都是静态成员的概念。
4. 集合框架
Java 的集合框架比 JS 的 Array / Map / Set 更丰富。这是你会在老项目中最频繁遇到的部分。
4.1 List(对应 JS 的 Array)
import java.util.ArrayList;
import java.util.List;
// 声明(面向接口编程,推荐)
List<String> names = new ArrayList<>();
// 添加元素(类似 JS 的 push)
names.add("张三");
names.add("李四");
names.add("王五");
// 获取元素
String first = names.get(0); // 类似 JS: names[0]
// 修改元素
names.set(1, "李四四"); // 类似 JS: names[1] = "李四四"
// 删除元素
names.remove("王五"); // 按值删除
names.remove(0); // 按索引删除
// 大小
int size = names.size(); // 类似 JS: names.length
// 是否包含
boolean has = names.contains("张三"); // 类似 JS: names.includes("张三")
// 遍历
for (String name : names) { // 类似 JS: for (const name of names)
System.out.println(name);
}
// Stream API(Java 8+,类似 JS 的数组方法链)
List<String> filtered = names.stream()
.filter(name -> name.startsWith("张")) // 类似 .filter()
.map(String::toUpperCase) // 类似 .map()
.sorted() // 类似 .sort()
.toList(); // 收集为 List
// 查找
boolean anyMatch = names.stream().anyMatch(n -> n.length() > 2); // 类似 .some()
boolean allMatch = names.stream().allMatch(n -> n.length() > 0); // 类似 .every()
Optional<String> found = names.stream().findFirst(); // 类似 .find()4.2 Map(对应 JS 的 Map / 对象)
import java.util.HashMap;
import java.util.Map;
// 创建(类似 JS: const user = new Map() 或 const user = {})
Map<String, Object> user = new HashMap<>();
// 添加/修改键值对
user.put("name", "张三");
user.put("age", 25);
user.put("email", "zhangsan@example.com");
// 获取值
String name = (String) user.get("name"); // 需要类型转换!
Integer age = (Integer) user.get("age");
// 获取并设置默认值(类似 JS: obj.key ?? defaultValue)
String phone = (String) user.getOrDefault("phone", "未设置");
// 是否包含键
boolean hasName = user.containsKey("name"); // 类似 JS: "name" in obj
// 删除
user.remove("email");
// 遍历
for (Map.Entry<String, Object> entry : user.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 指定类型的 Map(推荐,避免类型转换)
Map<String, String> config = new HashMap<>();
config.put("theme", "dark");
config.put("lang", "zh-CN");
String theme = config.get("theme"); // 不需要类型转换4.3 Set(对应 JS 的 Set)
import java.util.HashSet;
import java.util.Set;
Set<String> tags = new HashSet<>();
tags.add("java");
tags.add("android");
tags.add("java"); // 重复元素不会添加
int size = tags.size(); // 2
boolean hasJava = tags.contains("java"); // true
for (String tag : tags) {
System.out.println(tag);
}4.4 集合类型选择指南
| 需求 | 使用 | JS 对应 |
|---|---|---|
| 有序列表,可重复 | ArrayList | Array |
| 键值对 | HashMap | Map / {} |
| 唯一值集合 | HashSet | Set |
| 有序键值对 | LinkedHashMap | 无直接对应 |
| 排序的键值对 | TreeMap | 无直接对应 |
| 线程安全列表 | CopyOnWriteArrayList | 无直接对应 |
Stream API 常见陷阱
List<String> names = Arrays.asList("张三", "李四", "王五");
// ❌ 错误:Stream 只能消费一次
Stream<String> stream = names.stream();
stream.forEach(System.out::println);
stream.filter(s -> s.startsWith("张")); // IllegalStateException: stream has already been operated upon
// ✅ 正确:需要多次操作时重新创建流
names.stream().forEach(System.out::println);
List<String> zhangs = names.stream()
.filter(s -> s.startsWith("张"))
.collect(Collectors.toList());
// ❌ 错误:在 Stream 中修改外部变量
totalLength = 0;
names.forEach(name -> totalLength += name.length()); // 编译错误
// ✅ 正确:使用 collect 汇总
int total = names.stream().mapToInt(String::length).sum();
// ⚠️ 注意:并行流在 Android 主线程不常用,容易引发线程安全问题
names.parallelStream().forEach(...); // 谨慎使用| 场景 | 推荐写法 | 不推荐写法 |
|---|---|---|
| 只遍历一次 | list.forEach(...) | 手动 for 循环(也可以) |
| 过滤+映射+收集 | stream().filter().map().collect() | 多次循环 |
| 求和/最大/最小 | mapToInt().sum() / max() | 手写累加 |
| Android 主线程 | 避免大型 Stream 操作 | 在主线程处理大量数据 |
5. 异常处理
// 类似 JS 的 try...catch...finally
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.err.println("数学错误: " + e.getMessage());
} catch (Exception e) {
System.err.println("其他错误: " + e.getMessage());
} finally {
// 无论是否异常都会执行(和 JS 一样)
System.out.println("清理资源");
}
// 抛出异常
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数");
}
this.age = age;
}
// 多异常捕获(Java 7+)
try {
// ...
} catch (IOException | SQLException e) {
System.err.println("操作失败: " + e.getMessage());
}与 JS 的关键区别:
- Java 的异常分为受检异常(Checked Exception)和非受检异常(Unchecked Exception)
- 受检异常必须在
throws声明或try-catch处理,否则编译不通过 - 你会在老项目中看到大量的
try-catch块,很多是为了处理受检异常
// 受检异常:必须在方法签名中声明
public void readFile(String path) throws IOException {
// 如果调用者不处理 IOException,编译会失败
BufferedReader reader = new BufferedReader(new FileReader(path));
String line = reader.readLine();
reader.close();
}
// 非受检异常:不需要声明(RuntimeException 的子类)
public void divide(int a, int b) {
// ArithmeticException 是非受检的,不需要 throws
System.out.println(a / b);
}异常处理最佳实践
// ✅ 优先捕获具体异常,不要直接吞掉所有异常
try {
processFile(path);
} catch (IOException e) {
Log.e(TAG, "读取文件失败: " + path, e); // 记录完整堆栈
throw new BusinessException("无法加载配置", e); // 包装后重新抛出
}
// ❌ 不要这样做:吞掉异常会让问题难以定位
try {
processFile(path);
} catch (Exception e) {
// 什么都不做!
}
// ✅ 使用 try-with-resources 自动关闭资源
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
// 读取操作
} catch (IOException e) {
Log.e(TAG, "IO 错误", e);
}
// ✅ 在 Android 中,异步回调的异常要切回主线程处理
new Thread(() -> {
try {
String result = fetchFromNetwork();
runOnUiThread(() -> textView.setText(result));
} catch (IOException e) {
runOnUiThread(() -> Toast.makeText(this, "网络错误", Toast.LENGTH_SHORT).show());
}
}).start();| 反模式 | 问题 | 推荐做法 |
|---|---|---|
catch (Exception e) { e.printStackTrace(); } | 生产环境可能看不到日志 | 用 Log.e(TAG, msg, e) 或上报 |
catch (Exception e) {} | 静默失败,问题难定位 | 至少记录日志或抛出 |
| 在 finally 中 return | 会吞掉异常 | 避免在 finally 中 return |
| 在 UI 线程捕获所有异常 | 可能隐藏 ANR 根因 | 区分可恢复和致命异常 |
6. Lambda 表达式与函数式编程(Java 8+)
Java 8 引入的 Lambda 让 Java 也能像 JS 一样写函数式代码:
// JS: const add = (a, b) => a + b;
// Java:
// 注意:Java Lambda 必须赋值给函数式接口(只有一个抽象方法的接口)
// 使用内置函数式接口
import java.util.function.*;
Function<String, Integer> strLength = s -> s.length();
// 类似 JS: const strLength = (s) => s.length;
Consumer<String> printer = s -> System.out.println(s);
// 类似 JS: const printer = (s) => console.log(s);
Predicate<Integer> isPositive = n -> n > 0;
// 类似 JS: const isPositive = (n) => n > 0;
Supplier<Double> random = () -> Math.random();
// 类似 JS: const random = () => Math.random();
// 在集合中使用(最常见的场景)
List<String> names = List.of("张三", "李四", "王五", "赵六");
names.stream()
.filter(name -> name.length() > 2) // Predicate
.map(String::toUpperCase) // Function(方法引用)
.forEach(System.out::println); // Consumer
// 排序(类似 JS 的 sort)
names.sort((a, b) -> a.length() - b.length());
// 或简写为:
names.sort(Comparator.comparingInt(String::length));方法引用(Method Reference)
// 当 Lambda 体只是调用一个方法时,可以用方法引用简写
// s -> System.out.println(s) 等价于 System.out::println
// s -> s.length() 等价于 String::length
// () -> new ArrayList<>() 等价于 ArrayList::newOptional(Java 8+)
Optional 用于显式表达"值可能不存在",避免到处都是 null 检查:
// JS/TS 对照:类似 optional chaining + nullish coalescing
// const name = user?.name ?? "匿名";
Optional<User> userOpt = findUserById(1);
String name = userOpt.map(User::getName)
.orElse("匿名");
// 不要这样用(违背设计意图)
Optional<User> user = Optional.ofNullable(getUser()); // ✅ 可以
User u = user.get(); // ❌ 可能抛出 NoSuchElementException
// 推荐用法
userOpt.ifPresent(u -> System.out.println(u.getName()));Android 注意:
Optional在 Android API 24+ 才原生支持。老项目(minSdk < 24)可以使用 Guava 的Optional,或继续使用显式null检查。
7. 泛型(Generics)
泛型类似 TypeScript 的泛型,用于创建可复用的类型安全代码:
// 类似 TS: class Box<T> { private value: T; ... }
public class Box<T> {
private T value;
public Box(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
// 使用
Box<String> stringBox = new Box<>("hello");
String val = stringBox.getValue(); // 不需要类型转换
Box<Integer> intBox = new Box<>(42);
int num = intBox.getValue();
// 泛型方法
public <T> void printArray(T[] array) {
for (T item : array) {
System.out.println(item);
}
}
// 泛型通配符(你会在老项目中看到)
// ? extends Number → 上界,只能是 Number 或其子类
// ? super Integer → 下界,只能是 Integer 或其父类
// ? → 无界,任意类型
public void process(List<? extends Number> numbers) {
for (Number n : numbers) {
System.out.println(n.doubleValue());
}
}泛型擦除与 PECS 原则
泛型擦除
Java 泛型在编译后会被擦除为 Object(或上界),这是为了兼容老版本 JVM:
// 源码
List<String> names = new ArrayList<>();
// 编译后大致等价于
List names = new ArrayList();
String name = (String) names.get(0); // 编译器自动插入类型转换前端注意:TypeScript 的类型也会在编译时擦除,但 Java 的泛型擦除更严格——运行时无法直接获取
List<String>的泛型参数。 Gson 解析List<User>时需要传入TypeToken就是因为这个原因。
PECS 原则
PECS = Producer-Extends, Consumer-Super(生产者用 extends,消费者用 super):
// Producer:只读取数据,用 ? extends T
public void printNumbers(List<? extends Number> numbers) {
for (Number n : numbers) {
System.out.println(n);
}
// numbers.add(1); // ❌ 编译错误,无法保证类型安全
}
// Consumer:只写入数据,用 ? super T
public void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
// Integer i = list.get(0); // ❌ 编译错误,只能按 Object 读取
}| 通配符 | 含义 | 适用场景 |
|---|---|---|
? extends T | 上界,T 或 T 的子类 | 只读(Producer) |
? super T | 下界,T 或 T 的父类 | 只写(Consumer) |
? | 无界 | 既不需要读也不需要写具体类型 |
8. 枚举(Enum)
// 类似 TS: enum Status { PENDING, ACTIVE, COMPLETED }
public enum Status {
PENDING,
ACTIVE,
COMPLETED,
CANCELLED
}
// 使用
Status currentStatus = Status.ACTIVE;
switch (currentStatus) {
case PENDING:
System.out.println("等待中");
break;
case ACTIVE:
System.out.println("进行中");
break;
case COMPLETED:
System.out.println("已完成");
break;
case CANCELLED:
System.out.println("已取消");
break;
}
// 带数据的枚举(Java 独有,比 TS 枚举更强大)
public enum Priority {
LOW(1, "低"),
MEDIUM(2, "中"),
HIGH(3, "高");
private final int level;
private final String label;
Priority(int level, String label) {
this.level = level;
this.label = label;
}
public int getLevel() { return level; }
public String getLabel() { return label; }
}
Priority high = Priority.HIGH;
System.out.println(high.getLabel()); // "高"
System.out.println(high.getLevel()); // 39. 注解(Annotation)
注解是 Java 代码中的元数据标签,类似 TypeScript 的装饰器(Decorator):
// 常见内置注解
@Override // 标记方法重写父类方法
@Deprecated // 标记已过时的 API
@SuppressWarnings("unchecked") // 抑制编译器警告
// 在 Android 开发中常见的注解
@NonNull // 参数/返回值不能为 null
@Nullable // 参数/返回值可以为 null
@SuppressLint("...") // 抑制 Android Lint 警告
@RequiresApi(21) // 需要特定 API 级别
// 你会在老项目中看到的自定义注解
@BindView(R.id.tv_name) // ButterKnife
@Inject // Dagger/Hilt
@SerializedName("user_name") // Gson
@Entity(tableName = "users") // Room10. I/O 操作基础
import java.io.*;
// 读文件
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
// try-with-resources 会自动关闭资源(类似 JS 的 using 提案)
// 写文件
try (FileWriter writer = new FileWriter("output.txt")) {
writer.write("Hello, World!");
} catch (IOException e) {
e.printStackTrace();
}注意:在 Android 开发中,直接用 Java I/O 的场景较少。网络请求用 OkHttp/Retrofit,数据库用 Room,文件操作用 Context 提供的 API。但理解 I/O 基础有助于阅读老项目代码。
11. 多线程基础
JS 是单线程的(通过事件循环处理异步),Java 是多线程的。这是前端开发者需要特别理解的部分。
11.1 Thread 与 Runnable
// 方式一:继承 Thread(不推荐,因为 Java 单继承)
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("在子线程中执行: " + Thread.currentThread().getName());
}
}
new MyThread().start();
// 方式二:实现 Runnable(推荐,更灵活)
// 前端类比:类似 setTimeout(() => { ... }, 0) 但真正并行
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("子线程: " + Thread.currentThread().getName());
}
};
new Thread(task).start();
// 方式三:Lambda 简写(最常用)
new Thread(() -> {
System.out.println("子线程执行");
}).start();11.2 主线程与子线程
// ⚠️ Android 核心规则:
// - 主线程(UI 线程):处理 UI 更新和用户交互,不能执行耗时操作
// - 子线程:执行网络请求、数据库操作、文件读写等耗时任务
// - 子线程不能直接更新 UI!必须切回主线程
// 在 Activity 中常见的模式
new Thread(() -> {
// 子线程:执行耗时操作
String result = fetchDataFromNetwork();
// 切回主线程更新 UI
runOnUiThread(() -> {
textView.setText(result);
});
}).start();11.3 Handler 机制(Android 特有)
// Handler 是 Android 中线程间通信的核心机制
// 前端类比:类似 postMessage + onmessage,但更复杂
// 在主线程创建 Handler
Handler mainHandler = new Handler(Looper.getMainLooper());
// 从子线程发送消息到主线程
new Thread(() -> {
String data = doHeavyWork();
// 方式一:post Runnable
mainHandler.post(() -> {
textView.setText(data); // 在主线程更新 UI
});
// 方式二:发送 Message
Message msg = mainHandler.obtainMessage();
msg.what = 1;
msg.obj = data;
mainHandler.sendMessage(msg);
}).start();11.4 ExecutorService(线程池)
import java.util.concurrent.*;
// 创建线程池(前端类比:类似 Promise 并发控制)
ExecutorService executor = Executors.newFixedThreadPool(4); // 固定 4 个线程
// 提交任务
Future<String> future = executor.submit(() -> {
Thread.sleep(2000); // 模拟耗时操作
return "任务完成";
});
// 获取结果(会阻塞当前线程)
try {
String result = future.get(); // 类似 JS: await promise
System.out.println(result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
// 关闭线程池
executor.shutdown();
// CompletableFuture(Java 8+,类似 Promise 链)
CompletableFuture.supplyAsync(() -> fetchUser())
.thenApply(user -> user.getName()) // 类似 .then()
.thenAccept(name -> System.out.println(name))
.exceptionally(e -> { e.printStackTrace(); return null; }); // 类似 .catch()11.5 Android 中的异步模式演变
// 1️⃣ 老项目:AsyncTask(已废弃,但你会在老代码中看到)
public class LoadDataTask extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... params) {
return fetchData(params[0]); // 子线程
}
@Override
protected void onPostExecute(String result) {
textView.setText(result); // 主线程
}
}
new LoadDataTask().execute("https://api.example.com");
// 2️⃣ 过渡期:RxJava(响应式编程,类似 JS 的 RxJS)
Observable.fromCallable(() -> fetchData(url))
.subscribeOn(Schedulers.io()) // 在 IO 线程执行
.observeOn(AndroidSchedulers.mainThread()) // 在主线程观察
.subscribe(
result -> textView.setText(result), // 成功
error -> showError(error) // 失败
);
// 3️⃣ 现代方案:协程(Kotlin Coroutines,新项目首选)
// viewModelScope.launch {
// val result = withContext(Dispatchers.IO) { fetchData(url) }
// textView.text = result
// }前端开发者对照:
JS 异步模式 Java/Android 对应 setTimeout/setIntervalHandler.postDelayed()PromiseFuture/CompletableFutureasync/awaitKotlin 协程 / CompletableFuture.get()Web WorkerThread/ExecutorServicepostMessageHandler.sendMessage()RxJS RxJava
12. Android 开发特有的 Java 知识点
12.1 Context(上下文)
Context 是 Android 中最重要的概念,几乎所有操作都需要它:
// Context 是访问系统资源和应用信息的入口
// 前端类比:类似 window + document + 应用配置的集合体
// 常见的 Context 来源
Activity activity = this; // Activity 本身就是 Context
Context appContext = getApplicationContext(); // 应用级 Context
Context context = getContext(); // View 中获取 Context
// 常见用法
Toast.makeText(context, "提示消息", Toast.LENGTH_SHORT).show(); // 显示提示
context.startActivity(new Intent(context, DetailActivity.class)); // 跳转页面
context.getSharedPreferences("prefs", Context.MODE_PRIVATE); // 存储配置
context.getResources().getString(R.string.app_name); // 获取资源12.2 Serializable 与 Parcelable
// Serializable:Java 标准序列化(简单但性能差)
// 前端类比:类似 JSON.stringify / JSON.parse
public class User implements Serializable {
private String name;
private int age;
// 自动支持序列化,无需额外代码
}
// 传递 Serializable 对象
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtra("user", user);
// Parcelable:Android 专用序列化(性能好,代码多)
public class User implements Parcelable {
private String name;
private int age;
protected User(Parcel in) {
name = in.readString();
age = in.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) { return new User(in); }
@Override
public User[] newArray(int size) { return new User[size]; }
};
@Override
public int describeContents() { return 0; }
}经验法则:在 Activity/Fragment 间传递数据用 Parcelable,持久化存储用 Serializable 或 JSON。
12.3 匿名内部类
Android 老项目中大量使用匿名内部类(前端开发者会觉得类似回调函数):
// 前端类比:element.addEventListener('click', function(e) { ... })
// 按钮点击监听
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 处理点击事件
}
});
// Lambda 简写(Java 8+)
button.setOnClickListener(v -> {
// 处理点击事件
});
// 文本变化监听(前端类比:input.addEventListener('input', ...))
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// 文本变化时处理
}
@Override
public void afterTextChanged(Editable s) {}
});12.4 常见 Android 注解详解
// @Override:重写父类方法(编译检查)
@Override
protected void onCreate(Bundle savedInstanceState) { ... }
// @NonNull / @Nullable:空安全注解
public void setName(@NonNull String name) { ... } // 调用者传 null 会警告
public @Nullable String getNickname() { ... } // 返回值可能为 null
// @SuppressLint:抑制特定 Lint 警告
@SuppressLint("SetTextI18n") // 抑制硬编码字符串警告
textView.setText("Hello " + name);
// @RequiresApi:标记需要特定系统版本
@RequiresApi(api = Build.VERSION_CODES.O)
public void useNotificationChannel() { ... }
// @Deprecated:标记过时 API
@Deprecated
public void oldMethod() { ... }
// 第三方库注解(老项目常见)
@SerializedName("user_name") // Gson: JSON 字段映射
@Entity(tableName = "users") // Room: 数据库表映射
@Inject // Dagger/Hilt: 依赖注入
@BindView(R.id.tv_name) // ButterKnife: View 绑定12.5 R 文件与资源引用
// R 文件是 Android 自动生成的资源索引,类似前端的 import 资源
// R.id.xxx → 布局中的 View ID
// R.string.xxx → strings.xml 中的字符串
// R.layout.xxx → 布局文件
// R.drawable.xxx → 图片资源
// R.color.xxx → 颜色值
// 在 Activity 中
setContentView(R.layout.activity_main); // 加载布局
TextView tv = findViewById(R.id.tv_title); // 获取 View
String appName = getString(R.string.app_name); // 获取字符串
int color = getColor(R.color.primary); // 获取颜色12.6 生命周期与回调模式
// Android 组件通过生命周期回调与系统交互
// 前端类比:类似 React 的 useEffect + 组件生命周期
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // 必须调用父类方法
// 类似 React: componentDidMount 初始化
}
@Override
protected void onResume() {
super.onResume();
// 页面可见且可交互时(类似页面 focus)
}
@Override
protected void onPause() {
super.onPause();
// 页面失去焦点(类似页面 blur)
}
@Override
protected void onDestroy() {
super.onDestroy();
// 页面销毁(类似 React: componentWillUnmount)
}
}前端开发者常见疑问:
前端概念 Android 对应 windowContextdocument.getElementById()findViewById()addEventListenersetOnXxxListener()localStorageSharedPreferencesfetch()OkHttp/RetrofitReact 组件生命周期 Activity 生命周期 Router / 路由 Intent
13. 常见设计模式速览
你会在老项目中频繁遇到以下模式:
单例模式(Singleton)
// 类似 JS 中的模块单例
public class DatabaseHelper {
private static DatabaseHelper instance;
private DatabaseHelper() {} // 私有构造方法
public static synchronized DatabaseHelper getInstance() {
if (instance == null) {
instance = new DatabaseHelper();
}
return instance;
}
// 业务方法...
}
// 使用
DatabaseHelper db = DatabaseHelper.getInstance();观察者模式(Observer)
// Android 中无处不在:OnClickListener、TextWatcher、LiveData 都是观察者模式
public interface OnDataChangeListener {
void onDataChanged(String newData);
}
public class DataManager {
private List<OnDataChangeListener> listeners = new ArrayList<>();
public void addListener(OnDataChangeListener listener) {
listeners.add(listener);
}
public void notifyDataChanged(String data) {
for (OnDataChangeListener listener : listeners) {
listener.onDataChanged(data);
}
}
}建造者模式(Builder)
// 类似 JS 中的链式调用:new Request().url("...").method("GET").build()
AlertDialog dialog = new AlertDialog.Builder(context)
.setTitle("提示")
.setMessage("确定要退出吗?")
.setPositiveButton("确定", (d, w) -> finish())
.setNegativeButton("取消", null)
.create();
dialog.show();14. Java 中没有但 JS 中有的东西
| JS 特性 | Java 替代方案 |
|---|---|
undefined | 不存在,只有 null |
== 松散比较 | Java 的 == 是严格引用比较,值比较用 .equals() |
| 解构赋值 | 不存在,逐个赋值 |
展开运算符 ... | 不存在,手动拷贝 |
可选链 ?. | 不存在,手动判空 if (obj != null) { obj.xxx } |
空值合并 ?? | 不存在,用三元 obj != null ? obj : default |
| Promise / async-await | Java 用 CompletableFuture、ExecutorService,Android 用回调/协程 |
| JSON 内置 | 需要第三方库:Gson、Jackson、Moshi |
typeof | instanceof 或 .getClass() |
Array.isArray() | obj instanceof List 或 obj.getClass().isArray() |
| 模板字符串 | String.format() 或 StringBuilder |
前端开发者备忘
阅读 Java 代码时的快速翻译指南:
| 你看到的 Java 代码 | 等价的 JS/TS 思维 |
|---|---|
String name = "hi" | const name = "hi" |
new ArrayList<>() | [] |
new HashMap<>() | {} 或 new Map() |
.add(item) | .push(item) |
.get(index) | [index] |
.size() | .length |
.equals(other) | === other |
instanceof | instanceof |
null | null(没有 undefined) |
this.xxx | this.xxx |
super.xxx | super.xxx |
static | static |
final | const(近似) |
@Override | 无直接对应(方法覆盖是隐式的) |
try-catch-finally | try-catch-finally |
implements Interface | implements Interface(TS) |
extends Class | extends Class |
import com.xxx.Yyy | import { Yyy } from "com/xxx" |
System.out.println() | console.log() |
下一步
掌握了 Java 基础语法后,接下来学习 模块 03:项目结构与构建系统,了解 Android 项目的组织方式和 Gradle 构建工具。