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 教程目录