Java 练习题(18 题)

学了那么多语法,得动手写才算真的会。这一页给 18 道精心挑选的 Java 练习题,按难度分三级——基础 8 题(字符串 / 数字 / 数组 / 集合入门)、进阶 6 题(OOP / 接口 / Lambda / Stream / 异常 / 泛型)、实战 4 题(管理系统 / 计算器 / 多线程 / JSON)。每题都含题目描述、提示、可折叠的参考答案。

使用建议:先别急着展开答案。读题后自己打开 IDE(或 在线 Java)试着写,卡住了再看提示,实在写不出再对照参考答案。Java 是强类型语言,变量类型、方法签名、异常声明都得写清楚——这正是它"严谨"的代价和价值。

基础(8 题):字符串 / 数字 / 数组 / 集合入门

这一组覆盖 Java 最基础的语言元素和常用 API(String、Arrays、ArrayList、HashMap),训练把思路翻译成 Java 代码的能力。注意 Java 一切都在类里,main 方法是程序入口。

第 1 题:字符串翻转

题目:写一个方法 reverse(String s),把字符串翻转过来。比如 "hello" 翻转成 "olleh"。

提示:StringBuilder.reverse() 是最简写法;也可用 toCharArray() 转字符数组,双指针从头尾向中间交换——理解原理。

参考答案
public class ReverseDemo {

    // 方法 1:StringBuilder.reverse() 最常用
    public static String reverse(String s) {
        return new StringBuilder(s).reverse().toString();
    }

    // 方法 2:char 数组双指针手动翻转(理解原理)
    public static String reverseManual(String s) {
        char[] chars = s.toCharArray();
        int i = 0, j = chars.length - 1;
        while (i < j) {
            char tmp = chars[i];
            chars[i] = chars[j];
            chars[j] = tmp;
            i++;
            j--;
        }
        return new String(chars);
    }

    public static void main(String[] args) {
        System.out.println(reverse("hello"));        // olleh
        System.out.println(reverseManual("Java"));    // avaJ
    }
}

第 2 题:判断素数

题目:写一个方法 isPrime(int n),判断正整数 n 是否为素数。

提示:小于 2 都不是素数;只需检查到 sqrt(n)。注意 i*i 在 i 较大时可能溢出 int,要转 long 比较。

参考答案
public class PrimeDemo {

    public static boolean isPrime(int n) {
        if (n < 2) return false;
        // 用 (long) 避免大数 i*i 溢出
        for (int i = 2; (long) i * i <= n; i++) {
            if (n % i == 0) return false;
        }
        return true;
    }

    public static void main(String[] args) {
        for (int n : new int[]{1, 2, 7, 10, 13}) {
            System.out.println(n + " -> " + isPrime(n));
        }
    }
}
// 输出:1->false / 2->true / 7->true / 10->false / 13->true

第 3 题:FizzBuzz

题目:打印 1 到 15。3 的倍数打印 Fizz,5 的倍数打印 Buzz,同时是 3 和 5 的倍数打印 FizzBuzz,其余打印数字。

提示:if / else if / else 的顺序很关键——必须先判断 15 的倍数,否则被前面的分支截胡。Java 14+ 还可以用增强 switch 写得更现代。

参考答案
public class FizzBuzz {

    public static void fizzBuzz() {
        for (int i = 1; i <= 15; i++) {
            if (i % 15 == 0) System.out.println("FizzBuzz");
            else if (i % 3 == 0) System.out.println("Fizz");
            else if (i % 5 == 0) System.out.println("Buzz");
            else System.out.println(i);
        }
    }

    public static void main(String[] args) {
        fizzBuzz();
    }
}

第 4 题:数组求和

题目:写一个方法 sum(int[] arr),返回整型数组所有元素的和。

提示:增强 for 循环 for (int n : arr) 最直观;Java 8+ 还能用 Arrays.stream(arr).sum() 一行搞定。

参考答案
import java.util.Arrays;

public class SumDemo {

    // 方法 1:增强 for 循环
    public static int sum(int[] arr) {
        int total = 0;
        for (int n : arr) {
            total += n;
        }
        return total;
    }

    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        System.out.println(sum(arr));  // 55

        // 方法 2:Stream 一行搞定(Java 8+)
        int total = Arrays.stream(arr).sum();
        System.out.println(total);     // 55
    }
}

第 5 题:ArrayList 去重

题目:写一个泛型方法 dedupe(List<T> list),去掉列表中的重复元素,且保持原顺序。

提示:用 LinkedHashSet 包一层再转回 ArrayList——LinkedHashSet 既能去重又保持插入顺序;HashSet 会丢顺序。

参考答案
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;

public class DedupeDemo {

    // 利用 LinkedHashSet 去重并保持原顺序
    public static <T> List<T> dedupe(List<T> list) {
        return new ArrayList<>(new LinkedHashSet<>(list));
    }

    public static void main(String[] args) {
        List<Integer> nums = List.of(1, 3, 2, 3, 1, 4, 2);
        System.out.println(dedupe(nums));  // [1, 3, 2, 4]
    }
}
// 提示:用 HashSet 也能去重但会丢顺序,LinkedHashSet 才能保持插入顺序

第 6 题:HashMap 词频统计

题目:写一个方法 wordCount(String text),统计每个单词的出现次数,返回 Map<String, Integer>。

提示:text.split("\\\\s+") 按空白切分;Map.merge(key, 1, Integer::sum) 一行搞定"不存在放 1,存在就累加",比手动 if/else 简洁。

参考答案
import java.util.HashMap;
import java.util.Map;

public class WordCountDemo {

    public static Map<String, Integer> wordCount(String text) {
        Map<String, Integer> counts = new HashMap<>();
        for (String word : text.split("\\s+")) {
            // merge:key 不存在或为 null 时用第二个参数,否则用 lambda 合并
            counts.merge(word, 1, Integer::sum);
        }
        return counts;
    }

    public static void main(String[] args) {
        String text = "the quick brown fox the lazy dog the fox";
        System.out.println(wordCount(text));
        // {quick=1, brown=1, lazy=1, fox=2, the=3, dog=1}
    }
}

第 7 题:计算阶乘

题目:写一个方法 factorial(int n),返回 n!。要求 factorial(0) == 1,且对负数抛 IllegalArgumentException。

提示:用 long 而非 int 防止溢出(factorial(13) 就溢出 int);更大的数要用 BigInteger。Java 异常是抛出来的,throw new IllegalArgumentException(msg)。

参考答案
public class FactorialDemo {

    // 用 long 防止溢出;循环写法
    public static long factorial(int n) {
        if (n < 0) {
            throw new IllegalArgumentException("阶乘不接受负数");
        }
        long result = 1;
        for (int i = 2; i <= n; i++) {
            result *= i;
        }
        return result;
    }

    public static void main(String[] args) {
        System.out.println(factorial(5));  // 120
        System.out.println(factorial(20)); // 2432902008176640000
    }
}
// 注意:factorial(21) 会溢出 long;更大的数要用 BigInteger

第 8 题:判断回文

题目:写一个方法 isPalindrome(String s),判断字符串是否回文(正读反读一样)。忽略大小写,"Anna" 也算回文。

提示:双指针从首尾向中间走,比较 s.charAt(i) 与 s.charAt(j);先 .toLowerCase() 统一大小写。

参考答案
public class PalindromeDemo {

    public static boolean isPalindrome(String s) {
        // 统一小写,忽略大小写差异
        s = s.toLowerCase();
        int i = 0, j = s.length() - 1;
        while (i < j) {
            if (s.charAt(i) != s.charAt(j)) {
                return false;
            }
            i++;
            j--;
        }
        return true;
    }

    public static void main(String[] args) {
        System.out.println(isPalindrome("level"));  // true
        System.out.println(isPalindrome("hello"));  // false
        System.out.println(isPalindrome("Anna"));   // true
    }
}

进阶(6 题):OOP / 接口 / Lambda / Stream / 异常 / 泛型

这一组是 Java 的灵魂——面向对象、接口契约、函数式编程、异常体系、泛型。做完了你会理解为什么 Java 是企业级开发的霸主。

第 9 题:类继承

题目:定义父类 Animal(有 name 字段和 speak() 方法),子类 Dog 继承它并重写 speak() 输出"汪汪!"。

提示:extends 继承、super(name) 调父类构造方法、@Override 注解标记重写(编译器会校验)。Java 是单继承,一个类只能有一个直接父类。

参考答案
// 父类:通用动物
class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
    }

    public void speak() {
        System.out.println(name + " 发出声音");
    }
}

// 子类:狗,继承 Animal 并重写 speak
class Dog extends Animal {

    public Dog(String name) {
        super(name);              // 调用父类构造方法
    }

    @Override                      // 标注重写,编译器会帮你校验签名
    public void speak() {
        System.out.println(name + ": 汪汪!");
    }
}

public class InheritanceDemo {
    public static void main(String[] args) {
        Animal a = new Animal("某动物");
        Dog d = new Dog("旺财");
        a.speak();  // 某动物 发出声音
        d.speak();  // 旺财: 汪汪!
    }
}

第 10 题:接口实现

题目:定义 Shape 接口(double area() 方法),实现 Circle 和 Rectangle 两个类,写一个 printArea(Shape s) 方法接收任意实现类。

提示:implements 实现接口;参数声明为接口类型 Shape,传不同实现类表现不同行为——这就是多态。接口强调"能做什么",抽象类强调"是什么"。

参考答案
// 接口:定义"能算面积"的契约
interface Shape {
    double area();
}

class Circle implements Shape {
    private double r;

    public Circle(double r) {
        this.r = r;
    }

    @Override
    public double area() {
        return Math.PI * r * r;
    }
}

class Rectangle implements Shape {
    private double w, h;

    public Rectangle(double w, double h) {
        this.w = w;
        this.h = h;
    }

    @Override
    public double area() {
        return w * h;
    }
}

public class InterfaceDemo {
    // 参数声明为接口类型,能接收任何 Shape 实现类——这就是多态
    public static void printArea(Shape s) {
        System.out.printf("面积: %.2f%n", s.area());
    }

    public static void main(String[] args) {
        printArea(new Circle(2));          // 12.57
        printArea(new Rectangle(3, 4));    // 12.00
    }
}

第 11 题:Lambda 排序

题目:给定 List<String>,用 Lambda 表达式分别实现:(a) 按字典序升序;(b) 按字符串长度排序。

提示:List.sort((a, b) -> ...) 接收一个 Comparator;Lambda 让你直接写比较逻辑,不用匿名内部类;String::compareTo 是方法引用的简写。

参考答案
import java.util.Arrays;
import java.util.List;

public class LambdaSortDemo {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>(Arrays.asList("Charlie", "Alice", "Bob"));

        // 1. Lambda 按字典序升序
        names.sort((a, b) -> a.compareTo(b));
        System.out.println(names); // [Alice, Bob, Charlie]

        // 2. 按字符串长度排序
        names.sort((a, b) -> a.length() - b.length());
        System.out.println(names); // [Bob, Alice, Charlie]

        // 3. 等价写法:方法引用(更简洁)
        names.sort(String::compareTo);
    }
}
// Lambda 让"传一段代码"代替"写一个匿名内部类",Java 8 起推荐写法

第 12 题:Stream 过滤与映射

题目:给定 List<Integer> 1~10,用 Stream:(a) 筛出偶数,求它们的平方和;(b) 筛出大于 5 的数,拼成 "No.6" 这样的字符串列表。

提示:.stream().filter(...).mapToInt(...).sum() 是经典链式;.map(n -> "No." + n).toList() 收集成列表(Java 16+)。Stream 让集合操作声明式、可读性高。

参考答案
import java.util.Arrays;
import java.util.List;

public class StreamDemo {
    public static void main(String[] args) {
        List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 筛偶数 -> 平方 -> 求和,链式声明式写法
        int sum = nums.stream()
            .filter(n -> n % 2 == 0)
            .mapToInt(n -> n * n)
            .sum();

        System.out.println(sum);  // 4+16+36+64+100 = 220

        // 进阶:把对象列表收集成只含某字段的列表
        List<String> names = nums.stream()
            .filter(n -> n > 5)
            .map(n -> "No." + n)
            .toList();              // Java 16+ 不可变列表
        System.out.println(names);  // [No.6, No.7, No.8, No.9, No.10]
    }
}

第 13 题:异常处理

题目:写一个 divide(int a, int b) 方法,除数为 0 时不崩溃,打印友好信息并返回 0;无论是否异常都打印"计算结束"。

提示:try / catch(ArithmeticException e) / finally 三件套;catch 捕获具体类型而非笼统的 Exception;finally 块无论是否异常都执行,常用于关闭资源。

参考答案
public class ExceptionDemo {

    // 用 try/catch/finally 处理除零,不让程序崩溃
    public static int divide(int a, int b) {
        try {
            return a / b;
        } catch (ArithmeticException e) {
            // 捕获具体异常,打印友好信息
            System.out.println("除零错误: " + e.getMessage());
            return 0;
        } finally {
            // 无论是否异常都执行(常用于关资源)
            System.out.println("计算结束");
        }
    }

    public static void main(String[] args) {
        System.out.println(divide(10, 2));  // 计算结束 \n 5
        System.out.println(divide(10, 0));  // 除零错误... \n 计算结束 \n 0
    }
}

第 14 题:泛型方法

题目:写一个泛型方法 maxOf(T a, T b),返回两个可比较对象中较大的那个。要求 String、Integer、Double 都能用同一个方法。

提示:<T extends Comparable<T>> 约束 T 必须实现 Comparable;方法体内用 a.compareTo(b) 比较。泛型让代码既复用又类型安全。

参考答案
public class GenericDemo {

    // <T extends Comparable<T>> 表示 T 必须是可比较的类型
    public static <T extends Comparable<T>> T maxOf(T a, T b) {
        return a.compareTo(b) >= 0 ? a : b;
    }

    public static void main(String[] args) {
        // 同一个方法能用于 String、Integer 等任意可比较类型
        String s = maxOf("apple", "banana");   // banana
        Integer n = maxOf(3, 7);                // 7
        Double d = maxOf(1.5, 2.8);             // 2.8
        System.out.println(s + " / " + n + " / " + d);
    }
}
// 泛型让代码复用且类型安全,编译期就能发现类型不匹配

实战(4 题):综合应用

这一组每题都会综合用到多个知识点——OOP、集合、异常、IO、多线程、第三方库——做出一个接近真实场景的小程序。建议每题都完整跑一遍。

第 15 题:学生管理系统

题目:实现一个学生管理类,支持:添加学生(姓名 + 分数)、按姓名删除、按分数降序排名输出。用 List<Student> 存数据。

提示:Student 类带 toString() 控制打印;students.sort((a, b) -> b.score - a.score) 按分降序;removeIf(...) 按条件批量删除。

参考答案
import java.util.ArrayList;
import java.util.List;

class Student {
    String name;
    int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return name + "(" + score + ")";
    }
}

public class StudentManager {

    private final List<Student> students = new ArrayList<>();

    // 增
    public void add(String name, int score) {
        students.add(new Student(name, score));
    }

    // 删(按姓名)
    public void remove(String name) {
        students.removeIf(s -> s.name.equals(name));
    }

    // 按分数降序排名
    public void rank() {
        students.sort((a, b) -> b.score - a.score);
        for (int i = 0; i < students.size(); i++) {
            System.out.printf("  %d. %s%n", i + 1, students.get(i));
        }
    }

    public static void main(String[] args) {
        StudentManager mgr = new StudentManager();
        mgr.add("小明", 90);
        mgr.add("小红", 95);
        mgr.add("小刚", 85);
        mgr.rank();
        //   1. 小红(95)
        //   2. 小明(90)
        //   3. 小刚(85)
    }
}

第 16 题:简易计算器

题目:实现一个计算器方法 calc(double a, String op, double b),支持 + - * /。要求:除零抛 ArithmeticException,未知运算符抛 IllegalArgumentException;再做一个交互式版本(用 Scanner 读输入)。

提示:switch 分派运算符;Scanner 读整行后 split("\\\\s+") 拆三段;Double.parseDouble 转数字,异常用 try/catch 接住并提示。

参考答案
import java.util.Scanner;

public class Calculator {

    public static double calc(double a, String op, double b) {
        switch (op) {
            case "+": return a + b;
            case "-": return a - b;
            case "*": return a * b;
            case "/":
                if (b == 0) {
                    throw new ArithmeticException("除数不能为 0");
                }
                return a / b;
            default:
                throw new IllegalArgumentException("未知运算符: " + op);
        }
    }

    public static void main(String[] args) {
        // 非交互演示
        System.out.println(calc(3, "+", 5));   // 8.0
        System.out.println(calc(10, "/", 4));  // 2.5

        // 交互式:输入格式 "3 + 5",输入 q 退出
        Scanner sc = new Scanner(System.in);
        System.out.println("简易计算器(示例:3 + 5,输入 q 退出)");
        while (sc.hasNextLine()) {
            String line = sc.nextLine().trim();
            if (line.equals("q")) break;
            try {
                String[] p = line.split("\\s+");
                double a = Double.parseDouble(p[0]);
                double b = Double.parseDouble(p[2]);
                System.out.println("= " + calc(a, p[1], b));
            } catch (Exception e) {
                System.out.println("格式错误: " + e.getMessage());
            }
        }
    }
}

第 17 题:多线程求和

题目:写一个方法把数组分两半,用两个线程各算一半,最后合并结果。要求用 ExecutorService 和 Future。

提示:Executors.newFixedThreadPool(2) 建线程池;Callable<Long> 是有返回值的任务;pool.submit(task) 返回 Future,future.get() 阻塞等结果;最后 pool.shutdown() 关池。

参考答案
import java.util.concurrent.*;

public class ConcurrentSum {

    // 把数组分两半,两个线程各算一半,最后合并结果
    public static long sum(int[] nums) throws Exception {
        int half = nums.length / 2;

        ExecutorService pool = Executors.newFixedThreadPool(2);

        Callable<Long> task1 = () -> {
            long s = 0;
            for (int i = 0; i < half; i++) s += nums[i];
            return s;
        };
        Callable<Long> task2 = () -> {
            long s = 0;
            for (int i = half; i < nums.length; i++) s += nums[i];
            return s;
        };

        Future<Long> f1 = pool.submit(task1);
        Future<Long> f2 = pool.submit(task2);
        pool.shutdown();            // 别忘了关线程池

        return f1.get() + f2.get(); // get() 会阻塞等待结果
    }

    public static void main(String[] args) throws Exception {
        int[] nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        System.out.println(sum(nums));  // 55
    }
}

第 18 题:JSON 序列化

题目:用 Gson 库实现:(a) 把 User 对象序列化成 JSON 字符串;(b) 把 JSON 字符串反序列化回 User 对象。

提示:new Gson().toJson(obj) 序列化、gson.fromJson(json, User.class) 反序列化;字段名要和 JSON 的 key 对应。Spring Boot 内置 Jackson,注解 @RestController 会自动处理。

参考答案
// 依赖:在 pom.xml 加 com.google.gson:gson:2.10.1
// 或用 JDK 自带的 com.fasterxml.jackson(Jackson 库)写法类似
import com.google.gson.Gson;

class User {
    String name;
    int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class JsonDemo {
    public static void main(String[] args) {
        Gson gson = new Gson();

        // 1. 对象 -> JSON 字符串
        User user = new User("小明", 20);
        String json = gson.toJson(user);
        System.out.println(json);
        // {"name":"小明","age":20}

        // 2. JSON 字符串 -> 对象
        User parsed = gson.fromJson(
            "{\"name\":\"小红\",\"age\":22}", User.class);
        System.out.println(parsed.name + " / " + parsed.age);
        // 小红 / 22
    }
}
// 提示:Spring Boot 内置 Jackson,@RestController 会自动把返回对象序列化成 JSON

练习建议

不要只看答案。把每道题在 IDE(IDEA / VS Code + Java 扩展)或在线环境亲手敲一遍,改一改输入试试边界情况(null、空数组、负数、超大数)。Java 的报错信息通常很详细——读懂 stack trace 本身就是在学。

试着扩展:做完第 15 题学生管理,能不能加上"按单科排名""导出 CSV"?做完第 16 题计算器,能不能加上幂运算和括号解析?做完第 17 题多线程,能不能改成用 CompletableFuture 异步编排?每个扩展都是一次小型的真实开发。

更多练习资源:刷算法去 LeetCode;做小项目去 GitHub 找灵感;遇到不懂的先查 官方 API 文档(Java 21 LTS)。

← 上一篇 Java 实战项目

返回 Java 教程目录

✈️💬