Skip to content

模块 02:Java 语言基础(面向前端开发者)

目标:掌握阅读和理解 Java 代码所需的核心语法,通过与 JavaScript/TypeScript 的对比快速建立映射。

前言:Java 与 JavaScript 的关系

虽然名字相似,Java 和 JavaScript 是两门完全不同的语言。但作为前端开发者,你已经具备的编程思维(变量、函数、条件、循环、对象、异步)会直接迁移过来。

核心差异一览:

特性JavaScript / TypeScriptJava
类型检查运行时(JS)/ 编译时+运行时(TS)编译时(强类型)
执行方式解释执行(V8 引擎)编译为字节码 → JVM 执行
入口脚本直接执行 / main() 非必须必须有 public static void main()
文件结构一个文件可导出多个内容一个文件只能有一个 public 类
包管理import + package.jsonimport + 包声明 + Gradle/Maven
null 处理null / undefined只有 null(无 undefined
可选(函数式也行)必须(一切皆在类中)

1. 基本语法对比

1.1 变量声明

java
// 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 的基本类型(intdoublebooleanchar)不是对象,它们有对应的包装类(IntegerDoubleBooleanCharacter

1.2 基本类型 vs 引用类型

这是 Java 与 JS 最大的区别之一。JS 中一切皆对象,Java 中不是:

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 对应默认值
intIntegernumber0
longLongnumber / bigint0L
doubleDoublenumber0.0
floatFloatnumber0.0f
booleanBooleanbooleanfalse
charCharacterstring(单字符)'\u0000'
byteByte无直接对应0
shortShort无直接对应0

为什么需要包装类? 因为 Java 的泛型不支持基本类型。List<int> 是非法的,必须用 List<Integer>。这类似于 TypeScript 中 numberNumber 的区别,但 Java 中这个区别更关键。

栈(Stack)与堆(Heap)

理解 Java 内存模型对排查内存泄漏和性能问题很有帮助:

java
public void run() {
    int count = 10;              // 基本类型:存于栈帧
    User user = new User("张三"); // user 引用在栈,对象本身在堆
}
区域存放内容生命周期前端类比
局部变量、方法参数、基本类型方法返回即释放函数调用栈
所有 new 出来的对象、数组由 GC 回收JS 堆内存

排查提示OutOfMemoryError 通常与堆有关(创建了过多对象或加载了大图片);StackOverflowError 与栈有关(无限递归)。

1.3 字符串

java
// 字符串是不可变的(和 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()

java
String a = new String("hello");
String b = new String("hello");
a == b       // false!比较的是引用地址
a.equals(b)  // true!比较的是内容

1.4 数组

java
// 声明和初始化
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 条件语句

java
// 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 循环

java
// 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 类与对象

java
// 定义一个类
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 对应
publicpublic
protectedprotected
(无修饰符)无直接对应
privateprivate

在老项目中,private + getter/setter 是最常见的模式,称为"封装"。

3.3 继承

java
// 父类
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 的三大特性之一(封装、继承、多态)。简单说就是同一个方法调用,不同对象有不同行为

java
// 父类引用指向子类对象
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 等
        // 运行时根据实际类型执行不同逻辑
    }
};

类型转换:多态常配合类型检查使用:

java
if (animal instanceof Dog) {
    Dog dog = (Dog) animal;   // 向下转型
    dog.getBreed();
}
// Java 16+ 模式匹配(更简洁)
if (animal instanceof Dog dog) {
    dog.getBreed();  // 自动转型
}

3.4 接口(Interface)

接口是 Java 中定义"契约"的方式,类似 TypeScript 的 interface

java
// 定义接口(类似 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 抽象类

java
// 抽象类:不能直接实例化,必须被子类继承
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)

java
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.PIArray.isArray()Object.keys() 都是静态成员的概念。

4. 集合框架

Java 的集合框架比 JS 的 Array / Map / Set 更丰富。这是你会在老项目中最频繁遇到的部分。

4.1 List(对应 JS 的 Array)

java
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 / 对象)

java
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)

java
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 对应
有序列表,可重复ArrayListArray
键值对HashMapMap / {}
唯一值集合HashSetSet
有序键值对LinkedHashMap无直接对应
排序的键值对TreeMap无直接对应
线程安全列表CopyOnWriteArrayList无直接对应

Stream API 常见陷阱

java
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. 异常处理

java
// 类似 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 块,很多是为了处理受检异常
java
// 受检异常:必须在方法签名中声明
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);
}

异常处理最佳实践

java
// ✅ 优先捕获具体异常,不要直接吞掉所有异常
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 一样写函数式代码:

java
// 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)

java
// 当 Lambda 体只是调用一个方法时,可以用方法引用简写
// s -> System.out.println(s)  等价于  System.out::println
// s -> s.length()             等价于  String::length
// () -> new ArrayList<>()     等价于  ArrayList::new

Optional(Java 8+)

Optional 用于显式表达"值可能不存在",避免到处都是 null 检查:

java
// 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 的泛型,用于创建可复用的类型安全代码:

java
// 类似 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:

java
// 源码
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):

java
// 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)

java
// 类似 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());  // 3

9. 注解(Annotation)

注解是 Java 代码中的元数据标签,类似 TypeScript 的装饰器(Decorator):

java
// 常见内置注解
@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") // Room

10. I/O 操作基础

java
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

java
// 方式一:继承 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 主线程与子线程

java
// ⚠️ Android 核心规则:
// - 主线程(UI 线程):处理 UI 更新和用户交互,不能执行耗时操作
// - 子线程:执行网络请求、数据库操作、文件读写等耗时任务
// - 子线程不能直接更新 UI!必须切回主线程

// 在 Activity 中常见的模式
new Thread(() -> {
    // 子线程:执行耗时操作
    String result = fetchDataFromNetwork();

    // 切回主线程更新 UI
    runOnUiThread(() -> {
        textView.setText(result);
    });
}).start();

11.3 Handler 机制(Android 特有)

java
// 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(线程池)

java
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 中的异步模式演变

java
// 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 / CompletableFuture
async/awaitKotlin 协程 / CompletableFuture.get()
Web WorkerThread / ExecutorService
postMessageHandler.sendMessage()
RxJSRxJava

12. Android 开发特有的 Java 知识点

12.1 Context(上下文)

Context 是 Android 中最重要的概念,几乎所有操作都需要它:

java
// 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

java
// 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 老项目中大量使用匿名内部类(前端开发者会觉得类似回调函数):

java
// 前端类比: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 注解详解

java
// @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 文件与资源引用

java
// 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 生命周期与回调模式

java
// 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 对应
windowContext
document.getElementById()findViewById()
addEventListenersetOnXxxListener()
localStorageSharedPreferences
fetch()OkHttp / Retrofit
React 组件生命周期Activity 生命周期
Router / 路由Intent

13. 常见设计模式速览

你会在老项目中频繁遇到以下模式:

单例模式(Singleton)

java
// 类似 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)

java
// 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)

java
// 类似 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-awaitJava 用 CompletableFutureExecutorService,Android 用回调/协程
JSON 内置需要第三方库:Gson、Jackson、Moshi
typeofinstanceof.getClass()
Array.isArray()obj instanceof Listobj.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
instanceofinstanceof
nullnull(没有 undefined)
this.xxxthis.xxx
super.xxxsuper.xxx
staticstatic
finalconst(近似)
@Override无直接对应(方法覆盖是隐式的)
try-catch-finallytry-catch-finally
implements Interfaceimplements Interface(TS)
extends Classextends Class
import com.xxx.Yyyimport { Yyy } from "com/xxx"
System.out.println()console.log()

下一步

掌握了 Java 基础语法后,接下来学习 模块 03:项目结构与构建系统,了解 Android 项目的组织方式和 Gradle 构建工具。

面向前端开发者的 Java Android 开发教程