Java基础全套教程(四)—— 数组与基础数据结构

前面章节我们掌握了Java流程控制、方法、递归的核心逻辑,具备了基础编程思维。本章将学习Java最核心的基础数据结构——数组。数组是存储批量数据的容器,是后续集合、算法、框架开发的底层基础,几乎所有业务代码和算法题目都会用到数组。

同时本章搭配冒泡排序、二分查找两大经典入门算法,结合可视化学习工具,帮大家建立基础数据结构与算法思维,为后续高阶编程铺路。

本章学习目标

  1. 理解数组的核心概念、四大特性,区分基本类型数组与引用类型数组;

  2. 熟练掌握数组三种初始化方式、数组遍历、数组拷贝等常用操作;

  3. 精通 java.util.Arrays 工具类所有常用方法,解决数组日常开发场景;

  4. 掌握多维数组的内存结构与使用场景,实现表格数据存储;

  5. 掌握JavaBean结合数组存储结构化数据的开发规范;

  6. 熟练手写基础冒泡排序、优化冒泡排序、二分查找三大经典算法。


4.1 数组核心概念与四大特点

4.1.1 什么是数组

数组是相同数据类型的有序、连续存储集合。数组中每一条数据称为元素,所有元素按照存入顺序有序排列,开发者可通过**索引(下标)**精准访问、修改对应元素。

数组索引规则:从0开始,最大索引为 数组长度-1,超出范围会抛出数组越界异常。

4.1.2 数组四大核心特点(面试重点)

特点1:长度固定不可变

数组在内存中是连续空间,一旦初始化完成、分配内存,数组长度永久固定,无法扩容或缩容。日常集合扩容本质是新建新数组+拷贝旧数组数据实现。

特点2:元素类型统一

同一个数组中,所有元素必须是完全相同的数据类型,不支持整型、字符串、对象等混合存储,保证数据存储的规整性。

特点3:适配所有数据类型

数组的元素类型无限制,既可以存储byte、int、double等基本数据类型,也可以存储自定义对象、字符串等引用数据类型

特点4:数组属于引用类型

数组本身是Java中的对象,继承自Object类,存储在堆内存中;数组变量是引用变量,存储在栈内存,指向堆内存中的数组对象,数组元素等价于对象的属性。


4.2 数组声明与初始化

4.2.1 数组声明语法(一维数组)

Java提供两种数组声明格式,推荐使用第一种,更符合开发规范

// 规范写法:类型[] 数组名(推荐,区分类型与数组)
double[] scoreArr;
// 兼容写法:类型 数组名[](C语言风格,不推荐)
double scoreArr[];

重要注意事项:仅声明数组不会创建对象、不分配内存、无长度属性,只有通过new关键字实例化后,JVM才会在堆内存分配连续空间。

4.2.2 数组三种初始化方式

1. 动态初始化(先开辟空间,后赋值)

手动指定数组长度,系统自动为元素分配默认初始值,后续手动赋值。适用于提前确定数据条数,但不确定具体数据的场景。

全新示例:存储学生成绩数组

public class ArrayDynamicInit {
    public static void main(String[] args) {
        // 动态初始化:创建长度为5的浮点型数组
        double[] studentScores = new double[5];

        // 循环为数组元素赋值
        studentScores[0] = 88.5;
        studentScores[1] = 92.0;
        studentScores[2] = 79.5;
        studentScores[3] = 95.0;
        studentScores[4] = 85.0;

        // 遍历输出成绩
        for (int i = 0; i < studentScores.length; i++) {
            System.out.println("第" + (i + 1) + "名学生成绩:" + studentScores[i]);
        }
    }
}
2. 静态初始化(创建即赋值)

创建数组时直接传入所有元素值,系统自动根据元素个数确定数组长度,适用于数据已知、固定不变的场景。

全新示例:存储四季名称、固定数字序列

public class ArrayStaticInit {
    public static void main(String[] args) {
        // 字符串数组静态初始化
        String[] seasons = {"春季", "夏季", "秋季", "冬季"};
        // 整型数组静态初始化
        int[] nums = {10, 20, 30, 40, 50};

        System.out.println("四季数组长度:" + seasons.length);
        System.out.println("数字数组长度:" + nums.length);
    }
}
3. 默认初始化(系统自动赋值)

数组开辟空间后,未手动赋值时,系统会根据元素类型自动赋予默认值,是数组对象的固有特性。

默认值规则

基本类型:整数默认0、浮点默认0.0、布尔默认false、字符默认空字符;

引用类型:所有对象、字符串默认null。

全新示例:查看各类数组默认初始值

public class ArrayDefaultInit {
    public static void main(String[] args) {
        int[] intArr = new int[3];
        boolean[] boolArr = new boolean[3];
        String[] strArr = new String[3];

        System.out.println("整型数组默认值:" + intArr[0]);
        System.out.println("布尔数组默认值:" + boolArr[0]);
        System.out.println("字符串数组默认值:" + strArr[0]);
    }
}

4.2.3 引用类型数组初始化(全新案例)

数组可以存储自定义对象,实现批量对象数据存储,是后续表格数据存储的基础。

class Book {
        private String bookName;
        private double price;

        // 构造方法
        public Book(String bookName, double price) {
            this.bookName = bookName;
            this.price = price;
        }

        // 重写toString
        @Override
        public String toString() {
            return "书籍名称:" + bookName + ",价格:" + price + "元";
        }
    }

public class ReferenceArrayTest {
    public static void main(String[] args) {
        // 声明并创建长度为3的书籍对象数组
        Book[] bookArr = new Book[3];

        // 为数组元素赋值(存储对象)
        bookArr[0] = new Book("Java编程入门", 59.9);
        bookArr[1] = new Book("数据结构详解", 69.9);
        bookArr[2] = new Book("算法实战指南", 79.9);

        // 遍历对象数组
        for (Book book : bookArr) {
            System.out.println(book);
        }
    }
}

4.3 数组核心常用操作

4.3.1 普通for循环遍历数组

通过索引遍历,支持读取、修改数组元素,灵活性最高,开发中可精准操作指定下标元素。

全新示例:遍历数组并统计元素总和、平均值

public class ArrayForTraverse {
    public static void main(String[] args) {
        int[] data = {15, 28, 36, 42, 55, 67};
        int sum = 0;

        // 普通for循环遍历
        for (int i = 0; i < data.length; i++) {
            sum += data[i];
            System.out.println("索引" + i + ",元素值:" + data[i]);
        }

        System.out.println("数组元素总和:" + sum);
        System.out.println("数组元素平均值:" + (double) sum / data.length);
    }
}

4.3.2 for-each增强循环遍历

专门用于快速遍历数组和集合,语法简洁、无需操作索引,仅可读取元素,无法修改数组内容

全新示例:遍历存储城市名称的数组

public class ArrayForeachTraverse {
    public static void main(String[] args) {
        String[] citys = {"北京", "上海", "广州", "深圳", "杭州"};

        // 增强for循环遍历
        for (String city : citys) {
            System.out.println("热门城市:" + city);
            // 无法修改原数组元素,仅做读取操作
        }
    }
}

开发规范总结:需要下标、修改元素用普通for循环;仅快速遍历读取用for-each循环。

4.3.3 数组拷贝(System.arraycopy)

系统原生拷贝方法,效率极高,支持局部拷贝、全量拷贝、跨数组赋值,是集合扩容、数据迁移的底层核心方法。

方法参数说明:System.arraycopy(原数组, 原数组起始下标, 目标数组, 目标数组起始下标, 拷贝元素个数)

全新示例:商品数据数组拷贝

public class ArrayCopyTest {
    public static void main(String[] args) {
        // 原数组:商品列表
        String[] goods = {"手机", "电脑", "平板", "耳机", "手表"};
        // 目标数组:长度更大,预留扩容空间
        String[] goodsBak = new String[8];

        // 拷贝原数组全部数据到新数组
        System.arraycopy(goods, 0, goodsBak, 0, goods.length);

        // 遍历新数组
        System.out.println("拷贝后的商品数组:");
        for (String good : goodsBak) {
            System.out.print(good + " ");
        }
    }
}

4.4 java.util.Arrays 工具类精讲

Arrays是Java官方提供的数组工具类,包含数组打印、排序、查找、填充等静态方法,无需手动造轮子,是开发高频工具。

4.4.1 数组格式化打印(toString)

直接打印数组变量会输出内存地址,Arrays.toString()可直接输出数组所有元素,简洁高效。

import java.util.Arrays;
public class ArraysPrintTest {
    public static void main(String[] args) {
        int[] numArr = {12, 45, 7, 89, 23};
        // 直接打印:输出内存地址
        System.out.println("数组内存地址:" + numArr);
        // 工具类打印:输出元素内容
        System.out.println("数组元素内容:" + Arrays.toString(numArr));
    }
}

4.4.2 数组排序(sort)

支持基本类型、对象数组排序,默认升序排序,底层优化算法,效率高于手写基础排序。

import java.util.Arrays;
public class ArraysSortTest {
    public static void main(String[] args) {
        int[] randomArr = {56, 12, 98, 3, 77, 29};
        System.out.println("排序前:" + Arrays.toString(randomArr));
        // 数组升序排序
        Arrays.sort(randomArr);
        System.out.println("排序后:" + Arrays.toString(randomArr));
    }
}

4.4.3 二分查找(binarySearch)

高效查找元素位置,必须先排序再查找,找到返回元素索引,未找到返回负数。

import java.util.Arrays;
public class ArraysSearchTest {
    public static void main(String[] args) {
        int[] scoreArr = {85, 92, 78, 96, 88, 90};
        Arrays.sort(scoreArr);
        System.out.println("排序后数组:" + Arrays.toString(scoreArr));

        // 查找目标元素索引
        int index = Arrays.binarySearch(scoreArr, 88);
        System.out.println("元素88的索引位置:" + index);
    }
}

4.4.4 数组局部填充(fill)

支持批量替换数组指定区间的元素,快速初始化、重置数组数据。

import java.util.Arrays;
public class ArraysFillTest {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5, 6};
        System.out.println("填充前:" + Arrays.toString(arr));
        // 将索引1~3的元素替换为99
        Arrays.fill(arr, 1, 4, 99);
        System.out.println("填充后:" + Arrays.toString(arr));
    }
}

4.5 多维数组(二维为主)

4.5.1 多维数组概念与内存结构

多维数组本质是存储数组的数组,开发中仅二维数组常用,用于存储表格、矩阵等二维数据,三维及以上几乎不用。

内存结构:二维数组外层存储多个一维数组的引用,每个一维数组可独立设置长度,支持不规则二维数组。

4.5.2 二维数组初始化与遍历

全新不规则二维数组案例

import java.util.Arrays;
public class TwoArrayTest {
    public static void main(String[] args) {
        // 动态初始化二维数组
        int[][] twoArr = new int[3][];
        twoArr[0] = new int[]{11, 22};
        twoArr[1] = new int[]{33, 44, 55};
        twoArr[2] = new int[]{66, 77, 88, 99};

        // 双层循环遍历二维数组
        for (int i = 0; i < twoArr.length; i++) {
            System.out.println("第" + (i + 1) + "行数据:" + Arrays.toString(twoArr[i]));
        }
    }
}

4.5.3 二维数组存储表格数据

用二维数组模拟员工信息表格,每一行对应一条员工数据,实现结构化数据存储。

import java.util.Arrays;
public class ArrayTableTest {
    public static void main(String[] args) {
        // 二维数组存储员工表格:编号、姓名、年龄、岗位
        Object[][] empTable = {
                {1001, "张三", 22, "开发工程师"},
                {1002, "李四", 24, "测试工程师"},
                {1003, "王五", 23, "运维工程师"}
        };

        // 遍历表格数据
        for (Object[] row : empTable) {
            System.out.println(Arrays.toString(row));
        }
    }
}

4.6 JavaBean+数组存储结构化表格(开发规范)

二维数组存储表格存在数据类型混乱、可读性差的问题,企业开发统一使用 JavaBean实体类+数组存储表格数据,结构清晰、易于维护。

全新完整案例:员工实体+数组存储数据

// 员工实体类(JavaBean)
class Employee {
    // 私有成员变量
    private int empId;
    private String empName;
    private int age;
    private String job;

    // 无参、有参构造
    public Employee() {}
    public Employee(int empId, String empName, int age, String job) {
        this.empId = empId;
        this.empName = empName;
        this.age = age;
        this.job = job;
    }

    // 重写toString方法,方便打印数据
    @Override
    public String toString() {
        return "员工编号:" + empId + ",姓名:" + empName + ",年龄:" + age + ",岗位:" + job;
    }

    // getter/setter方法
    public int getEmpId() { return empId; }
    public void setEmpId(int empId) { this.empId = empId; }
    public String getEmpName() { return empName; }
    public void setEmpName(String empName) { this.empName = empName; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    public String getJob() { return job; }
    public void setJob(String job) { this.job = job; }
}

// 测试类:数组存储实体对象
public class JavaBeanArrayTest {
    public static void main(String[] args) {
        // 实体类数组存储多条员工数据
        Employee[] empArr = {
                new Employee(1001, "赵六", 25, "后端开发"),
                new Employee(1002, "孙七", 21, "前端开发"),
                new Employee(1003, "周八", 26, "产品经理")
        };

        // 遍历打印结构化数据
        for (Employee emp : empArr) {
            System.out.println(emp);
        }
    }
}

4.6.1 Comparable接口实现对象数组排序

普通数组可直接排序,自定义对象数组需要实现Comparable接口,定义对象比较规则,实现排序功能。

全新案例:根据员工年龄排序

import java.util.Arrays;

// 实现Comparable接口,重写比较规则
class Staff implements Comparable<Staff> {
    private String name;
    private int age;

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

    // 重写比较方法:按年龄升序排序
    @Override
    public int compareTo(Staff o) {
        return this.age - o.age;
    }

    @Override
    public String toString() {
        return "姓名:" + name + ",年龄:" + age;
    }
}

public class ObjectArraySortTest {
    public static void main(String[] args) {
        Staff[] staffArr = {
                new Staff("小明", 28),
                new Staff("小红", 22),
                new Staff("小刚", 25)
        };

        Arrays.sort(staffArr);
        System.out.println("按年龄升序排序结果:");
        for (Staff staff : staffArr) {
            System.out.println(staff);
        }
    }
}

4.7 数组经典算法(全新原创代码)

算法可视化练习工具:https://visualgo.net/,可直观观察排序、查找的执行过程,快速理解算法原理。

4.7.1 基础冒泡排序

核心原理:相邻元素两两对比,逆序则交换,每轮排序将当前最大值冒泡到末尾,多轮循环完成整体排序。

import java.util.Arrays;
public class BaseBubbleSort {
    public static void main(String[] args) {
        // 随机无序数组
        int[] arr = {22, 5, 88, 12, 67, 3, 49};
        System.out.println("排序前数组:" + Arrays.toString(arr));
        bubbleSort(arr);
        System.out.println("排序后数组:" + Arrays.toString(arr));
    }

    // 基础冒泡排序算法
    public static void bubbleSort(int[] arr) {
        int temp;
        // 外层控制排序轮数
        for (int i = 0; i < arr.length - 1; i++) {
            // 内层控制每轮对比次数
            for (int j = 0; j < arr.length - 1 - i; j++) {
                // 前大于后,交换位置(升序)
                if (arr[j] > arr[j + 1]) {
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }
}

4.7.2 优化冒泡排序

优化原理:添加有序标记,若某一轮未发生任何元素交换,说明数组已有序,直接终止循环,减少无效运算。

import java.util.Arrays;
public class OptimizeBubbleSort {
    public static void main(String[] args) {
        int[] arr = {13, 6, 79, 25, 33, 8, 56};
        System.out.println("排序前数组:" + Arrays.toString(arr));
        optimizeSort(arr);
        System.out.println("排序后数组:" + Arrays.toString(arr));
    }

    public static void optimizeSort(int[] arr) {
        int temp;
        for (int i = 0; i < arr.length - 1; i++) {
            // 标记本轮是否有序
            boolean isSorted = true;
            for (int j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                    isSorted = false;
                }
            }
            // 数组已有序,直接退出循环
            if (isSorted) {
                break;
            }
        }
    }
}

4.7.3 二分查找(折半查找)

核心原理:基于有序数组,每次取中间元素对比,缩小一半查找区间,查询效率远高于遍历查找。

import java.util.Arrays;
public class BinarySearchTest {
    public static void main(String[] args) {
        int[] numArr = {9, 23, 15, 88, 41, 66, 37};
        int target = 41;
        // 二分查找前必须排序
        Arrays.sort(numArr);
        System.out.println("有序数组:" + Arrays.toString(numArr));
        int index = binarySearch(numArr, target);
        System.out.println("目标元素" + target + "的索引位置:" + (index == -1 ? "未找到" : index));
    }

    // 手写二分查找算法
    public static int binarySearch(int[] arr, int target) {
        int left = 0;
        int right = arr.length - 1;

        while (left <= right) {
            // 计算中间索引
            int mid = left + (right - left) / 2;
            if (arr[mid] == target) {
                return mid;
            } else if (arr[mid] > target) {
                // 目标更小,向左区间查找
                right = mid - 1;
            } else {
                // 目标更大,向右区间查找
                left = mid + 1;
            }
        }
        // 未找到返回-1
        return -1;
    }
}

本章核心知识点总结

  1. 数组是固定长度、同类型的有序存储集合,属于引用类型,内存空间固定不可变;

  2. 数组分为动态、静态、默认三种初始化方式,适配不同业务场景;

  3. 普通for循环可修改元素,for-each仅能遍历读取,System.arraycopy实现高效数组拷贝;

  4. Arrays工具类封装排序、查找、填充、打印等常用方法,是开发必备工具;

  5. 二维数组存储表格数据,JavaBean+数组是企业结构化数据存储的标准写法;

  6. 自定义对象通过Comparable接口定义排序规则,实现对象数组排序;

  7. 优化冒泡排序减少无效循环,二分查找基于有序数组实现高效检索,是入门核心算法。

更多推荐