A
第 4 章JAVA45 分钟

Java 速成

为零基础同学准备的 Java 速成:类型、运算、流程、类、继承、接口、泛型、Lambda、集合、异常、IO 与 Android 开发相关特性

学习目标

  • 掌握 Java 基本数据类型、包装类与运算符,理解自动装箱与拆箱
  • 理解类与对象、构造方法、继承、多态、接口、内部类的设计动机
  • 会使用泛型与 Lambda 表达式简化集合操作
  • 能熟练使用 List/Map/Set 处理常见数据结构
  • 理解异常体系、try-with-resources 与 IO 流基础
  • 了解 Java 在 Android 开发中的特性与限制(如不能直接用 NIO 与反射限制)

本章定位:Kotlin 自 2017 年起成为 Android 官方首选语言,新项目推荐使用 Kotlin。本章面向需要维护 Java 存量代码、理解 Java 第三方库源码、或从 Java 入门便于后续过渡到 Kotlin 的读者。本章不意味着推荐新项目使用 Java,而是作为 Views 路线的语法基础。

学习目标

本章不是完整的 Java 教程,而是把写 Android 必备的 Java 知识点压成一节“速成课”。读完本章你将能够:

  • 看懂 Android Studio 模板生成的 MainActivity 中每个 Java 关键字与符号;
  • 理解类型系统与自动装箱、运算符与流程控制,写出能编译的方法体;
  • 用类、继承、接口、内部类组织业务逻辑;
  • 用泛型与 Lambda 简化集合操作;
  • 看懂异常处理、try-with-resources 与 IO 流基础;
  • 知道 Java 在 Android 开发中的特性与限制(如反射、NIO 的可用性)。

变量与数据类型

1. 八种基本类型

类型 占用 范围 示例
byte 1B -128~127 byte b = 1;
short 2B -32768~32767 short s = 100;
int 4B ±21 亿 int i = 10;
long 8B ±9×10^18 long l = 100L;
float 4B 单精度浮点 float f = 1.5f;
double 8B 双精度浮点 double d = 1.5;
char 2B 0~65535(UTF-16) char c = 'A';
boolean 1b true/false boolean ok = true;

字面量规则:long 加 L、float 加 f、double 默认、char 用单引号、String 用双引号、十六进制 0x、二进制 0b、下划线分隔 1_000_000。

2. 包装类与自动装箱

每种基本类型都有对应的引用类型(包装类),用于泛型与集合:

Integer a = 10;          // 自动装箱:Integer.valueOf(10)
int b = a;                // 自动拆箱:a.intValue()
Integer c = Integer.valueOf("123");
int parsed = Integer.parseInt("456");

Integer 在 [-128, 127] 范围内有缓存,Integer.valueOf(127) == Integer.valueOf(127) 返回 true;128 起会创建新对象,== 返回 false。比较包装类必须用 .equals()。

3. 引用类型

String、数组、自定义类都是引用类型。引用指向对象本身,赋值是“传引用”:

int[] arr = {1, 2, 3};
int[] arr2 = arr;            // arr2 与 arr 指向同一数组
arr2[0] = 99;
System.out.println(arr[0]);  // 99

String s = "abc";
String t = new String("abc");
System.out.println(s == t);             // false,比较引用
System.out.println(s.equals(t));        // true,比较内容

4. var 局部类型推断(Java 10+)

var list = new ArrayList<String>();     // 编译期推断为 ArrayList<String>
var name = "Alice";                     // String

Android 项目开启 sourceCompatibility 17 后可自由使用。注意 var 只能用于局部变量,不能用于字段、参数、返回值。

运算符

1. 算术与赋值

int a = 10;
a += 5;   // a = 15
int b = a++;   // 先取值再自增,b=15
int c = ++a;   // 先自增再取值,c=17

整数除法会截断:

int q = 7 / 2;    // 3
double r = 7.0 / 2;  // 3.5

2. 关系与逻辑

boolean ok = (a > b) && (c < d);
boolean any = (x == 1) || (y == 2);
boolean not = !ok;

&& 与 || 是短路运算:左侧能决定结果时右侧不执行。Android 中常用:

if (user != null && user.isAdmin()) { ... }

3. 三元与位运算

String msg = count > 0 ? "有" + count + "条" : "无";

int mask = 0b00001111;
int result = mask & 0b11110000;   // 0
int or = mask | 0b11110000;       // 255
int xor = mask ^ 0b11111111;      // 240
int shift = 1 << 4;               // 16

位运算在 Android 中常用于状态标记:

public static final int FLAG_VISIBLE = 1;
public static final int FLAG_EDITABLE = 2;
public static final int FLAG_DRAGGABLE = 4;

int flags = FLAG_VISIBLE | FLAG_EDITABLE;
boolean visible = (flags & FLAG_VISIBLE) != 0;

流程控制

1. if-else

if (score >= 90) {
    grade = "A";
} else if (score >= 80) {
    grade = "B";
} else {
    grade = "C";
}

2. switch

Java 17 起支持 switch 表达式与模式匹配(preview),但 Android Tooling 17 仅稳定支持经典 switch:

switch (status) {
    case 0:
        message = "未开始";
        break;
    case 1:
    case 2:
        message = "进行中";   // 多个 case 共用一段代码
        break;
    default:
        message = "未知";
}

3. for 与 for-each

int[] arr = {1, 2, 3, 4, 5};
for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}

for (int x : arr) {           // for-each
    System.out.println(x);
}

4. while 与 do-while

int n = 10;
while (n > 0) {
    n--;
}

int m = 0;
do {
    m++;
} while (m < 5);

5. break / continue / 标签

outer:
for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 10; j++) {
        if (j == 5) break outer;     // 直接跳出外层
        if (j % 2 == 0) continue;     // 跳过本次
    }
}

Android 性能:避免在 onDraw / onBindViewHolder 中嵌套循环太深,会拖慢渲染。

方法

1. 基本语法

[修饰符] 返回类型 方法名(参数列表) [throws 异常列表] {
    方法体
    return 返回值;
}

public static int add(int a, int b) {
    return a + b;
}

2. 可变参数

public static int sum(int... nums) {
    int total = 0;
    for (int n : nums) total += n;
    return total;
}

sum(1, 2, 3);    // 6
sum();           // 0

底层是数组,外部传 int[] 也可。

3. 方法重载

public String format(int v)        { return String.valueOf(v); }
public String format(double v)     { return String.valueOf(v); }
public String format(String v)    { return v; }

编译器根据参数类型选择最匹配的版本。

4. 静态方法与实例方法

public class MathUtil {
    public static int square(int x) { return x * x; }     // 静态
    public int instanceMethod() { return 1; }              // 实例
}

int r = MathUtil.square(5);
MathUtil obj = new MathUtil();
obj.instanceMethod();

类与对象

1. 类定义与构造方法

public class User {

    private String name;          // 实例字段
    private int age;

    public User(String name, int age) {     // 构造方法
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    public void setAge(int age) {
        if (age < 0) throw new IllegalArgumentException("age < 0");
        this.age = age;
    }
}

User u = new User("Alice", 18);
System.out.println(u.getName());

2. 静态字段与静态初始化块

public class Config {
    public static final String APP_ID = "abc";

    private static final Map<String, String> CACHE = new HashMap<>();
    static {
        CACHE.put("env", "prod");
        CACHE.put("version", "1.0");
    }
}

static {} 在类首次加载时执行一次,常用于初始化静态字段。

3. this / super 调用

public class Animal {
    protected String name;
    public Animal(String name) { this.name = name; }
    public Animal() { this("default"); }    // 调用本类其它构造
}

继承与多态

1. extends

public class Dog extends Animal {

    private String breed;

    public Dog(String name, String breed) {
        super(name);              // 调用父类构造,必须在第一行
        this.breed = breed;
    }

    public String getBreed() { return breed; }
}

Java 是单继承,一个类只能 extends 一个父类,但可 implements 多个接口。

2. 方法重写与多态

public class Animal {
    public String speak() { return "..."; }
}

public class Dog extends Animal {
    @Override
    public String speak() { return "Woof"; }     // 重写
}

public class Cat extends Animal {
    @Override
    public String speak() { return "Meow"; }
}

Animal a = new Dog();
Animal b = new Cat();
System.out.println(a.speak());   // Woof
System.out.println(b.speak());   // Meow

运行时根据对象真实类型调用对应方法,这就是多态。

3. final 与不可继承

public final class String { ... }      // 不能被继承
public class Dog {
    public final void bark() { }       // 不能被重写
}

4. Object 通用方法

所有类隐式继承 Object,常用方法:

public boolean equals(Object o)
public int hashCode()
public String toString()
protected Object clone() throws CloneNotSupportedException

Android 中重写 equals 必须同时重写 hashCode,否则在 HashMap / HashSet 中出错。

接口

1. 接口定义

public interface ClickListener {
    void onClick(View view);
    default void onLongClick(View view) {}   // Java 8 默认方法
}

public class Button implements ClickListener {
    @Override
    public void onClick(View view) { ... }
}

2. 接口与抽象类对比

维度 接口 抽象类
字段 只能 public static final 任意
方法 抽象 / default / static 抽象 / 具体
多继承 可实现多个 单继承
构造方法 无 有

Android 中大量回调用接口:View.OnClickListener、RecyclerView.Adapter 的接口契约等。

3. 静态接口方法

public interface Util {
    static int add(int a, int b) { return a + b; }
}

int r = Util.add(1, 2);

内部类

1. 静态内部类

public class Outer {
    static class Inner { }    // 不持有 Outer 实例
}

Android 推荐用静态内部类 + WeakReference 持有外部 Activity,避免内存泄漏。Handler 经典坑就源于匿名内部类持有外部 Activity。

2. 成员内部类

public class Outer {
    private int x = 10;
    class Inner {
        public int getX() { return x; }   // 可访问外部字段
    }
}

3. 局部内部类与匿名内部类

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Log.d("TAG", "clicked");
    }
});

匿名内部类持有外部类引用,且无法再被继承。Lambda 表达式(Java 8)能替代“单方法接口”的匿名内部类,且不持有外部 this。

泛型

1. 泛型类与方法

public class Pair<A, B> {
    public final A first;
    public final B second;
    public Pair(A first, B second) {
        this.first = first;
        this.second = second;
    }

    public static <T> Pair<T, T> of(T a, T b) {
        return new Pair<>(a, b);
    }
}

Pair<String, Integer> p = Pair.of("age", 18);

2. 通配符

// 只读:列表元素只能是 Number 子类
public static double sum(List<? extends Number> list) {
    double total = 0;
    for (Number n : list) total += n.doubleValue();
    return total;
}

// 只写:list 元素是 Number 父类
public static void addNumbers(List<? super Number> list) {
    list.add(1);
    list.add(2.0);
}

PECS 原则:Producer extends,Consumer super。

3. 泛型擦除

Java 泛型只在编译期检查,运行期擦除:

List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
System.out.println(a.getClass() == b.getClass());   // true

Lambda 表达式

1. 函数式接口

只有一个抽象方法的接口:

@FunctionalInterface
public interface Runnable { void run(); }

@FunctionalInterface 是可选注解,加上后编译器会检查。

2. Lambda 语法

Runnable r1 = new Runnable() {
    @Override public void run() { System.out.println("hi"); }
};

Runnable r2 = () -> System.out.println("hi");

完整写法:

(int a, int b) -> { return a + b; }

省略写法:

(a, b) -> a + b

3. 方法引用

List<String> names = Arrays.asList("alice", "bob");
names.forEach(System.out::println);
names.stream().map(String::toUpperCase).forEach(System.out::println);

// 构造方法引用
Supplier<ArrayList<String>> supplier = ArrayList::new;

4. Android 中的 Lambda

开启 sourceCompatibility 8 即可使用。常见场景:

view.setOnClickListener(v -> doSomething());

textView.postDelayed(() -> update(), 1000);

new Thread(() -> doBackgroundWork()).start();

Android Studio 默认把 lambda desugar(脱糖)成内部类,对 minSdk 24 不需额外配置;如需完整 Java 8 API 脱糖,在 compileOptions 加 coreLibraryDesugaringEnabled true。

集合(List/Map/Set)

1. List

List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add(0, "x");
String first = list.get(0);
list.remove("a");
int size = list.size();

for (String s : list) { ... }

List<String> immutable = List.of("a", "b", "c");  // Java 9+,minSdk 24 需 desugar

2. Map

Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
int v = map.get("a");
int def = map.getOrDefault("c", 0);

for (Map.Entry<String, Integer> e : map.entrySet()) {
    System.out.println(e.getKey() + "=" + e.getValue());
}

map.forEach((k, val) -> { ... });

3. Set

Set<String> set = new HashSet<>();
set.add("a");
set.add("a");           // 重复不生效
boolean has = set.contains("a");

Set<String> linked = new LinkedHashSet<>();   // 保留插入顺序
Set<String> sorted = new TreeSet<>();        // 自然排序

4. 选择哪个实现

类型 实现 场景
List ArrayList 随机访问多、插入少
List LinkedList 头尾频繁插入删除
Map HashMap 通用 key-value
Map LinkedHashMap 需保留插入顺序
Map TreeMap key 排序
Set HashSet 去重
Set TreeSet 去重 + 排序

Android 中不要用 Vector、Hashtable、Stack,它们是线程安全但性能差的老类。并发场景用 java.util.concurrent 下的 ConcurrentHashMap、CopyOnWriteArrayList。

5. Collections 工具类

List<Integer> list = new ArrayList<>(Arrays.asList(3, 1, 2));
Collections.sort(list);
Collections.reverse(list);
List<Integer> unmod = Collections.unmodifiableList(list);  // 不可变

异常处理

1. 异常体系

Throwable
 ├── Error               // 不应捕获:OOM、StackOverflow
 └── Exception
      ├── RuntimeException    // 非受检:NPE、IllegalArgument
      └── 其它 Exception     // 受检:IOException,必须 catch 或 throws

2. try-catch-finally

FileInputStream in = null;
try {
    in = new FileInputStream("a.txt");
    int b = in.read();
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    throw new RuntimeException(e);
} finally {
    if (in != null) {
        try { in.close(); } catch (IOException ignored) {}
    }
}

3. try-with-resources(Java 7+)

try (FileInputStream in = new FileInputStream("a.txt");
     InputStreamReader reader = new InputStreamReader(in)) {
    int b = reader.read();
} catch (IOException e) {
    e.printStackTrace();
}
// in 与 reader 自动 close,顺序与声明相反

实现了 AutoCloseable 的对象都可放这里,包括 Android 的 Cursor、FileInputStream 等。

4. 自定义异常

public class ApiException extends RuntimeException {
    private final int code;
    public ApiException(int code, String msg) {
        super(msg);
        this.code = code;
    }
    public int getCode() { return code; }
}

throw new ApiException(404, "Not Found");

5. Android 中常见异常

  • NullPointerException:未初始化的 View、回调 null;
  • IllegalStateException:Fragment 在 onSaveInstanceState 之后 commit;
  • ActivityNotFoundException:startActivity 没匹配到 Intent;
  • SecurityException:缺少权限调用受限 API;
  • NetworkOnMainThreadException:主线程做网络请求。

IO 基础

1. 字节流与字符流

// 字节流
try (FileInputStream in = new FileInputStream("a.txt");
     FileOutputStream out = new FileOutputStream("b.txt")) {
    byte[] buffer = new byte[1024];
    int n;
    while ((n = in.read(buffer)) != -1) {
        out.write(buffer, 0, n);
    }
} catch (IOException e) {
    e.printStackTrace();
}

// 字符流
try (BufferedReader reader = new BufferedReader(new FileReader("a.txt"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}

2. NIO 与 Android

java.nio.file.*(Path、Files)在 Android API 26+ 才支持。如果要在低版本使用,会报 NoClassDefFoundError。Android 上推荐用 java.io + File 或 Okio 库(更简洁、性能好)。

3. SharedPreferences 与文件存储

Android 上持久化推荐:

  • 小型 KV:SharedPreferences / DataStore;
  • 结构化数据:Room 数据库;
  • 大文件:内部 / 外部存储 + File。

示例:写入内部存储:

try (FileOutputStream fos = openFileOutput("note.txt", MODE_PRIVATE)) {
    fos.write("hello".getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
    e.printStackTrace();
}

与 Android 开发相关的 Java 特性

1. 注解(Annotation)

@Override              // 编译期检查是否真的重写
@Deprecated            // 标记废弃
@FunctionalInterface   // 标记函数式接口
@Nullable              // AndroidX 提供的空安全注解
@NonNull
@Keep                  // 被混淆器保留
@IntDef({MODE_A, MODE_B})   // 自定义枚举替代

2. 反射

Class<?> clz = Class.forName("com.example.helloapp.MainActivity");
Method m = clz.getMethod("onCreate", Bundle.class);
m.setAccessible(true);

Android 中反射开销大且受 hidden API 限制(API 28+ 反射访问非 SDK 接口会被系统拒绝)。生产环境应避免。

3. try-with-resources 自动资源管理

Android API 19+ 起支持 try-with-resources;Cursor、FileInputStream 等都实现了 AutoCloseable。

4. Stream API(Java 8 +)

List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
int sum = nums.stream().filter(n -> n % 2 == 1).mapToInt(Integer::intValue).sum();

String joined = nums.stream().map(String::valueOf).collect(Collectors.joining(","));

开启 coreLibraryDesugaringEnabled true 后在 minSdk 24 上也可用。

5. 单元测试与 JUnit

import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class CalculatorTest {
    @Test
    public void add() {
        Calculator calc = new Calculator();
        assertEquals(3, calc.add(1, 2));
    }
}

常见坑与最佳实践

  1. == 比较字符串:永远用 .equals() 或 Objects.equals(a, b);TextUtils.equals(a, b) 在 Android 中对 null 安全。
  2. 基本类型与包装类混用导致 NPE:Integer a = null; int b = a; 自动拆箱时抛 NPE;DTO 字段优先用 Integer 而非 int,注意判空。
  3. ArrayList 容量增长导致的性能问题:构造时指定初始容量 new ArrayList<>(expectedSize),避免反复扩容。
  4. HashMap 在多线程下数据丢失:并发用 ConcurrentHashMap,不要给 HashMap 加 synchronized,性能极差。
  5. lambda 误用 this:lambda 中的 this 指外部类实例,匿名内部类的 this 指自身。Handler 用 lambda 仍可能泄漏 Activity。
  6. finally 中改 return 值:finally 块里 return 会覆盖 try 中的 return,且会吞掉异常。绝不要在 finally 写 return。
  7. catch 仅写 e.printStackTrace():生产环境应统一日志框架上报,至少 Log.e(TAG, "msg", e)。
  8. 反射调用被 hidden API 拦截:API 28+ 通过反射访问非 SDK 接口会得到灰名单/黑名单警告,应用商店审核可能拒绝。改用公开 API 或下沉 native。
  9. Stream API 在大列表中表现差:小数据量用 Stream 反而比 for 循环慢,仅在可读性优先时使用。
  10. java.io.File 在 Android 10+ 受限:scoped storage 后,外部存储的访问需走 MediaStore 或 SAF,直接 new File() 会抛 FileNotFoundException。

章节小结

  • Java 8 大基本类型 + 对应包装类,自动装箱有缓存区间,比较包装类用 .equals();
  • 运算符支持短路逻辑、位运算适合状态标记;流程控制经典 if/switch/for/while + Java 17 表达式扩展;
  • 面向对象三件套:封装(private + getter/setter)、继承(extends + super)、多态(@Override + 运行时绑定);
  • 接口支持默认方法与静态方法,匿名内部类可被 lambda 替代;静态内部类不持有外部 this,是避免泄漏的常用手段;
  • 泛型 + Lambda + 集合(List/Map/Set)是日常 80% 的数据操作;PECS 原则帮你选 ? extends 还是 ? super;
  • 异常分受检/非受检,try-with-resources 自动关闭资源;Android 上 IO 推荐用 try-with-resources + AutoCloseable。

下一章预告

至此 Part 1 入门结束。Part 2 我们进入 UI 基础:View 与 ViewGroup 体系、常用布局(LinearLayout / ConstraintLayout)、TextView/Button/EditText/ImageView 等核心控件、Adapter 与 RecyclerView 列表、自定义 View 入门。带上本 Part 学到的项目结构与 Java 语法,正式开始动手做 UI!