列表,数组,终端输入

集合

集合 是一种可以存储多个值的数据类型。

有时也把它叫做容器类型。就像一个容器,可以装很多东西。

这在编程开发中非常常见。

Java 中,集合类型有很多种,常用的有:列表(List),数组(Array),映射(Map) 等。

这一章,我们先介绍 列表数组 这两种。

列表 (List)

列表定义

前面我们介绍过字符串,它是存储 字符 组成的序列,是一种 引用类型

在 Java 中,还有 其它类型数据序列,其中最常用就是 列表

列表 是一个可以动态调整大小的容器,经常被用来存储一组对象。

你可以把它想象成一连串的储物格,每个格子都可以存放一个指定类型的对象。

在 Java 中,最常用的列表类型是 ArrayList<E>

ArrayList<E>是一个泛型 (Generic) 类型。关于 泛型 ,我们会在后面章节中详细学习。

这里我们只需要知道,ArrayList<E> 中的 E 是一个类型参数 (Type Parameter),用于指定列表中要存放的对象类型。

例如: ArrayList<Integer> 表示一个存放整数的列表,ArrayList<String> 表示一个存放字符串的列表。

Java 的泛型集合只能存放对象,不能存放基本数据类型(如 int, double)。如果要存放整数,必须使用其包装类 Integer;存放小数用 Double


ArrayList初始化时,如果里面有值,通常需要用到 List 接口。 关于接口,也在后面详细学习,这里我们把它看作一种特殊的 “类” 。


因为 ArrayListList 都定义在 java.util 包里,所以,要使用 ListArrayList,所以需要引入 java.util 包。

import java.util.ArrayList; // 引入 ArrayList 类
import java.util.List;      // 引入 List 接口

public class Main {
    public static void main(String[] args) {
        // 创建一个用于存放字符串的空列表
        // 推荐使用 List 接口作为变量类型
        List<String> nameList1 = new ArrayList<>();

        // 使用 var 关键字 (Java 10+)
        var nameList2 = new ArrayList<String>();

        // 创建一个初始化好字符串的列表
        // List.of 是 Java 9 引入的,生成的是不可变列表
        // 为了让它可变(能添加删除),我们需要把它传给 ArrayList 的构造函数
        List<String> nameList3 = new ArrayList<>(List.of("张三", "李四", "王五"));
        System.out.println(nameList3);

        // 创建一个初始化好整数的列表
        List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4));
        System.out.println(numbers);
    }
}

也可以先定义变量,再赋值:

List<String> nameList4;
// 其它代码
nameList4 = new ArrayList<>();
nameList4.add("张三");

这种主要适用于: 开始不知道初始值,后面根据条件再赋值的场景。


如果希望像 Python 的列表那样 存放多种不同类型的对象,可以指定类型为 Object,因为 Object 是所有 Java 类型的基类(基类的概念后面会学到)。

// Object 类型的列表可以存放任何类型的数据
List<Object> mixedList = new ArrayList<>();
mixedList.add(1);
mixedList.add(3.14);
mixedList.add("hello");

虽然 List<Object> 很灵活,但在常规 Java 编程中,我们更推荐使用强类型列表(如 List<Integer>),因为这样更安全、性能更好。


✳️ 列表是可变的

ArrayList 的内容是可以改变的, 可以添加,删除,修改,清空等操作。

添加元素:add

List<Object> a = new ArrayList<>(List.of(1, 2, 3.14));

a.add("hello"); // a 变为 [1, 2, 3.14, "hello"]

a.add(0, "你好"); // a 变为 ["你好", 1, 2, 3.14, "hello"]

删除元素:remove,clear

List<String> a = new ArrayList<>(List.of("a", "b", "c", "a"));

// 按索引移除
a.remove(1); // 移除 "b",a 变为 ["a", "c", "a"]

// 按值移除
a.remove("a"); // 移除第一个 "a",a 变为 ["c", "a"]

// 清空列表
a.clear();   // a 变为一个空列表 []

索引/查找:get,indexOf

和 Python 一样,List 也是通过从 0 开始的索引来访问元素的。

注意:Java 的列表不能像数组那样使用 [] 语法访问,必须使用 get() 方法。

List<Object> a = new ArrayList<>();
a.add(1);
a.add(2);
a.add(3.14);
a.add("hello");

System.out.println(a.get(0));      // 输出: 1

// 获取最后一个元素
System.out.println(a.get(a.size() - 1)); 

// Java 21+ 可以使用 getLast()
// System.out.println(a.getLast()); 

可以通过 :

List<Integer> numbers = new ArrayList<>(List.of(2, 3, 7, 8, 9, 7));

System.out.println(numbers.indexOf(8)); // 输出: 3

System.out.println(numbers.lastIndexOf(7)); // 输出: 5

修改指定元素:set

Java 列表修改元素必须使用 set(index, element) 方法。

List<Object> a = new ArrayList<>(List.of(1, 2, 3.14, "hello"));
System.out.println(a); // 输出: [1, 2, 3.14, hello]

// 通过索引直接修改元素
a.set(0, "你好");
System.out.println(a); // 输出: [你好, 2, 3.14, hello]

获取列表长度:size()

可以通过 size() 方法获取列表中元素的数量。

List<Object> a = new ArrayList<>(List.of(1, 2, 3.14, "hello"));
System.out.println(a.size()); // 输出: 4

注意:size 是一个方法,所以需要加括号 ()。这与数组的 length 属性不同。

判断是否存在某元素 contains

判断列表中是否存在某个元素,可以使用 contains() 方法。

List<String> list1 = new ArrayList<>(List.of("apple", "banana", "orange"));

if (list1.contains("banana")) {
    System.out.println("列表中存在 banana");
}

if (!list1.contains("grape")) {
    System.out.println("列表中不存在 grape");
}

排序与反转 : Collections.sort, Collections.reverse

Java 的 List 接口本身没有 sortreverse 方法(List.sort 需要传入比较器),通常我们使用 Collections 工具类。

import java.util.Collections;

List<Integer> numbers = new ArrayList<>(List.of(7, 3, 8, 2, 9));

// 从小到大排序
Collections.sort(numbers); // numbers 变为 [2, 3, 7, 8, 9]

// 反转列表顺序
Collections.reverse(numbers); // numbers 变为 [9, 8, 7, 3, 2]

// 获取列表中元素的数量
int count = numbers.size(); // count 的值是 5

注意Collections.reverse()Collections.sort() 都是“就地”操作,它们会直接修改原始列表。

列表的切片:subList

通过 subList(int fromIndex, int toIndex) 方法获取列表的一个视图。 注意:toIndex 是不包含的(左闭右开区间)。

List<Object> a = new ArrayList<>(List.of(1, 2, 3.14, "hello", 7, 8, 9));

// 从索引0开始,获取到索引3(不含3),即 0, 1, 2
List<Object> sliceView = a.subList(0, 3); // 结果是 [1, 2, 3.14]

// 注意:subList 返回的是原列表的一个“视图”。
// 如果修改 sliceView,原列表 a 也会被修改!
sliceView.set(0, "World");
System.out.println(a.get(0)); // 输出: World

// 如果想要一个独立的列表,需要重新构造
List<Object> sliceCopy = new ArrayList<>(a.subList(0, 3));

切片赋值

所谓切片赋值,指的是将列表中某个范围的元素替换为新的元素。

Java 可以通过 subList 配合 clearaddAll 实现。

List<Integer> list1 = new ArrayList<>(List.of(0, 1, 2, 3, 4, 5));

// 替换索引 1, 2, 3 处的元素 (即索引 1 到 4)
List<Integer> sub = list1.subList(1, 4);
sub.clear(); // 移除原列表对应范围的元素
sub.addAll(List.of(8, 9, 10)); // 在该位置插入新元素

// list1 现在是 [0, 8, 9, 10, 4, 5]
System.out.println(list1);

合并两个列表:addAll

使用 addAll 方法可以将一个列表的所有元素添加到另一个列表的末尾。

List<Integer> a = new ArrayList<>(List.of(1, 2, 3));
List<Integer> b = List.of(4, 5, 6);

a.addAll(b);
// a 现在是 [1, 2, 3, 4, 5, 6]

越界错误

如果访问或修改列表中不存在的索引位置,会抛出 IndexOutOfBoundsException 异常。

如果没有特别捕获处理,程序会终止并显示错误信息。

List<Integer> list1 = new ArrayList<>(List.of(1, 2, 3));
// list1.get(5);  // 抛出异常:java.lang.IndexOutOfBoundsException

数组 (Array)

数组定义

和列表类似,数组也是“一排连续的格子”,每个格子都能装同一个类型的东西。

数组也是一种 引用类型

索引:   0       1       2       3       4
       ┌──────┬──────┬──────┬──────┬──────┐
数组 → │ 10   │ 20   │ 30   │ 40   │ 50   │
       └──────┴──────┴──────┴──────┴──────┘

但是,数组的长度是固定的,创建后就不能再增加或减少元素了。


✳️ 没有初始值 的定义 :

写法①:先声明,再 new

int[] scores;                    // 只是声明了一个数组变量,不分配内存
scores = new int[5];             // 真正开辟 5 个格子(默认都是 0)

写法②:一步到位

int[] scores = new int[5];       // 5 个格子,全是 0

✳️ 有初始值 的定义 :

// 传统写法
int[] scores = new int[] { 98, 76, 85, 99, 88 }; // 自动就是 5 个长度,用new
// 简写(最常用)
int[] scores2 = { 98, 76, 85, 99, 88 };     // 省略new

String[] names = { "小明", "小红", "小刚", "小美" }; // 字符串数组

char[] chars = { '白', '月', '黑', '羽' };  // 字符数组
char[] arr = "白月黑羽".toCharArray(); // 通过把字符串转换成字符数组 设置初始值

前面说过:数组字符串 是 Java 里2种特殊的 “引用类型”,却能省略 new 的特例!

访问和修改数组元素(用下标/索引)

数组是固定大小的,一旦创建,就不能改变其大小。所以不能添加,删除元素。

但是可以修改现有元素的值。

int[] arr = { 10, 20, 30, 40, 50 };

System.out.println(arr[0]);   // 输出 10(第一个)
System.out.println(arr[2]);   // 输出 30(第三个)

arr[1] = 999;                // 把第二个改成 999
System.out.println(arr[1]);   // 输出 999

注意:索引从 0 开始,最后一个是 长度-1

length 字段,获取数组长度

通过 length 字段(注意不是方法,没有括号)可以获取数组的长度(元素个数)。

int[] nums = { 95, 87, 91, 100, 78 };

System.out.println("数组长度: " + nums.length); // 输出: 数组长度: 5

打印数组

直接打印数组变量,通常会得到一个看不懂的内存地址哈希码(如 [I@1b6d3586)。 要打印数组的内容,需要使用 Arrays.toString() 方法。

import java.util.Arrays;

int[] nums = { 95, 87, 91, 100, 78 };
System.out.println(nums); // 输出类似 [I@...
System.out.println(Arrays.toString(nums)); // 输出: [95, 87, 91, 100, 78]

二维数组(表格)

Java 的二维数组实际上是“数组的数组”。

// 3 行 4 列的成绩表
int[][] matrix = {
    { 95, 86, 77, 88 },
    { 67, 92, 81, 90 },
    { 100, 78, 89, 94 }
};

System.out.println(matrix[0][1]);  // 第1行第2列 → 86
System.out.println(matrix[2][0]);  // 第3行第1列 → 100

越界错误

如果访问或修改数组中不存在的索引位置,会抛出 ArrayIndexOutOfBoundsException 异常。

如果没有特别捕获处理,程序会终止并显示错误信息。

int[] arr = { 1, 2, 3 };
// arr[5] = 99;  // 抛出异常:java.lang.ArrayIndexOutOfBoundsException

什么时候用数组?(真实开发场景)

场景为什么用数组
方法参数是可变参数 ...void print(String... arr) 本质是数组
需要极致性能(游戏主循环)连续内存,CPU 缓存最友好
存储基本类型 int避免 Integer 包装类的开销

数组 vs ArrayList

你要做什么?用谁?一句话理由
长度永远不变(比如每月 12 个月)数组最简洁
长度会变(购物车、聊天记录、玩家列表)ArrayList不炸,还自带一堆好用方法
不知道放多少个,先占个坑ArrayList数组要先指定长度,太麻烦
要极致性能(每帧上万次循环)数组快一点点,省内存
需要排序,查找等操作ArrayList数组操作比较麻烦,List 配合 Collections 很方便

真实项目使用率 : 数组 10% , List 90%

命令行参数

Java 程序的 main 方法定义了一个 字符串数组 参数 String[] args,用于接收命令行传入的参数。

public class Program {
    public static void main(String[] args) {
        System.out.println("命令行参数个数: " + args.length);
        for (int i = 0; i < args.length; i++) {
            System.out.println("参数 " + i + ": " + args[i]);
        }
    }
}

然后执行命令

java Program --name byhy --site www.byhy.net

输出结果是:

命令行参数个数: 4
参数 0: --name
参数 1: byhy
参数 2: --site
参数 3: www.byhy.net

可变数量的参数 (Varargs)

假设我们有这样的一个 Map,存储学生年龄,如下:

import java.util.Map;

Map<String, Integer> studentInfo = Map.of(
    "张飞", 18,
    "赵云", 17,
    "许褚", 16,
    "典韦", 18,
    "关羽", 19
);

要实现一个方法 printAge ,它的输入参数是一些学生的姓名,方法需要打印出这些输入学生的年龄。

这里有个问题,就是调用 printAge 时, 输入的学生名字个数是不固定的,可能是1个、2个、100个, 甚至也可能是0个。

在 Java 中,我们可以使用 ... 语法来定义 可变数量的参数 (Variable Arguments,简称 Varargs)。

void printAge(String... students) {
    // 在方法内部,students 就是一个 String[] 数组
    for (String student : students) {
        System.out.println("学生:" + student + ", 年龄 " + studentInfo.get(student));
    }
}

String... 告诉编译器,这个参数可以接受零个或多个 String 类型的参数。

这些参数在方法内部会被组织成一个 String 数组。

然后我们就可以这样调用该方法了:

printAge("张飞", "典韦", "关羽");
System.out.println("---");
printAge("赵云");

效果和我们期望的一样。

在调用该方法的时候,Java 编译器会创建一个数组来存储传入的参数。


✳️ 规则限制

void log(String prefix, String... messages) { } // ✅ 正确

// void bad(int... nums, String suffix) { } // ❌ 编译错误!可变参数不在最后

✳️ 现成数组

printAge 使用上面这样的可变参数,假如我们要传入的参数恰好已经在一个数组中,比如

String[] onebatch = {"张飞", "典韦", "关羽"};

怎么调用 printAge 呢?

在 Java 中,直接把数组传递给可变参数即可:

printAge(onebatch);

这和 printAge("张飞", "典韦", "关羽") 的调用效果是完全一样的。

终端输入

现在我们 再来看一下 System.out.println 方法。

System 是一个out 是它的一个静态字段printlnout 对象的一个方法


你可能会问,什么是终端?

简单来说,就是我们运行程序时看到的那个命令行窗口,通常是黑色的背景。

我们目前编写的程序,都是使用这个窗口来 输出信息输入信息 的。


想象一下,你如果要开发一个计算税后薪资的软件,需要用户输入员工的薪资,怎么做?

当然可以开发一个带图形界面的程序,但是图形界面的开发需要更多的基础知识。

我们先学习如何在终端上,让用户用键盘输入信息。

前面我们已经学过,使用 System.out.println() 方法可以 输出 信息到屏幕上。

那么,我们怎么让用户 输入 信息给程序呢?

Scanner 类

在 Java 中,读取控制台输入最常用的方式是使用 java.util.Scanner 类。

import java.util.Scanner; // 必须导入 Scanner 类

void main() {
    // 创建一个 Scanner 对象,扫描 System.in (标准输入流)
    Scanner scanner = new Scanner(System.in);

    // 打印提示信息
    System.out.print("请输入张三的薪资:");

    // 等待用户输入一行文本
    String salaryStr = scanner.nextLine();

    System.out.println("你输入的薪资是:" + salaryStr);

    scanner.close(); // 使用完 Scanner 后关闭它
}

代码解释


注意scanner.nextLine() 方法返回的用户输入内容,永远都是 String(字符串) 类型。

如果我们想根据用户输入的薪资计算出税后薪资(假设税率25%,即扣税后剩余75%),下面的代码是错误的:

System.out.print("请输入张三的薪资:");
String salary = scanner.nextLine();

// 下面这行代码无法通过编译!
// double realSalary = salary * 0.75; 

因为 Java 是强类型语言,字符串 String 类型不能直接和数字 double 类型进行数学运算。

要解决这个问题,我们必须进行类型转换

我们需要把用户输入的、代表数字的字符串,显式地转换为真正的数字类型(如 int, double)。

正确的代码应该这样写:

import java.util.Scanner;

void main() {
    Scanner scanner = new Scanner(System.in);

    System.out.print("请输入薪资:");
    String salaryStr = scanner.nextLine();

    // 1. 先将字符串转化为 double 数字类型
    double salary = Double.parseDouble(salaryStr);

    // 2. 进行数学计算
    double realSalary = salary * 0.75;

    // 3. 将计算结果拼接成最终的输出字符串
    System.out.println("税后薪资是:" + realSalary);
}

读取单个字符

System.in.read() 方法用于从终端读取单个字符,并返回该字符的 Unicode 编码(int 类型)。

System.out.println("按任意键继续...");
int charCode = System.in.read(); // 读取单个字符的 Unicode 编码   
char ch = (char)charCode; // 将编码转换为字符
System.out.println("你按下的键是: " + ch);