Skip to content
目录概览

数组

数组是编程语言中非常常见的一种数据结构,Java 也不例外,数组的运用非常的普遍;在Java中,数组是存储着相同类型值的集合,然后通过一个整数类型的索引(index,又称为下标)访问数组中的每一个值;

声明数组

在声明数组变量时,需要指出数组的类型,类型后面跟上[]以及变量,如:

java
// 声明一个字符串类型的数组
// 以下两种方式都可以,比较起来大家更加喜欢第一种
String[] s; 
//或者
String s[];

以上语句仅仅时一个声明,数据并没有对s真正的初始化,需要通过new操作符初始化这个数组;

java
// 初始化一个长度为10的字符串数组
String[] s = new String[10];

这行代码声明并初始化了一个字符串数组:s,长度为10,可以存储10个字符串元素;

初始化的数组如果是基础数据类型的数组,初始化之后所有元素将被赋予对应类型的初始值(如int的初始值为:0,boolean的初始值为:false,double的初始值为:0.0);如果是包装类型或者对象,所有元素全部为null;

java
String[] s1 = new String[10];
int[] i1 = new int[10];
double[] d1 = new double[10];
boolean[] b1 = new boolean[10];
Boolean[] b2 = new Boolean[10];
System.out.println(s1[0]); // null
System.out.println(i1[0]); // 0
System.out.println(d1[0]); // 0.0
System.out.println(b1[0]); // false
System.out.println(b2[0]); // null

数组的长度可以根据自己的需要指定任意值,但是一旦数组创建并初始化,数组的长度就不允许改变,如果需要运行时可扩展的数组,就需要用到数组列表(ArrayList);


当已知了所有元素,想要快速创建数组时,Java 为我们提供了一种简写方式来创建,如下:

java
String[] s = {
     "a",
     "b",
     "c",
     // 更多元素
};

所有元素通过{}括起来,元素之间通过,隔开; 这种方式不需要new操作符号,也不需要指定长度,会根据括号中元素的个数指定数组的长度;

上面的方式,在初次声明数组的时候可以,但没办法对一个已经创建的数组重新初始化,比如想对象数组s重新初始化,下面的写法就会报错:

java
// 这样写时会报错的
s = {"d","e","f","g"};

此时就可以声明一个匿名数组(anonymous array):

java
s = new String[]{"d","e","f","g"};

以上的声明等价于:

java
String[] tmp = {"d","e","f","g"};
s = tmp;

数组长度

数组有个length变量,能够直接获取数组的长度

java
System.out.println(s.length);

数组访问与遍历

数组的元素是通过数组的索引(index,又称为下标)进行访问,索引的起始是从0开始,到array.length-1结束;

  • 添加、修改

    由于数组的长度在初始化之后就固定了,因此,数组的访问索引不能越过数组长度;

    java
    String[] s = new String[10];
    s[0] = "一行";  // 下标为0的元素设置为:一行
    s[1] = "Java"; // 下标为1的元素设置为:Java
    s[2] = "一航";  // 下标为2的元素设置为:一航
    s[2] = "张三";  // 下标为2的元素设置为:张三

    一旦超过,就会报越界(ArrayIndexOutOfBoundsException)异常,如:

    java
    s[100] = "ehang";

    异常信息:

    java
    Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 100 out of bounds for length 10
    	  at main.ArrayTest.t1(ArrayTest.java:18)
        at main.ArrayTest.main(ArrayTest.java:13)
  • 遍历

    数组可以通过增强型for循环快速遍历:

    java
    String[] s = new String[]{"d", "e", "f", "g"};
    for(String item : s){
      System.out.println(item);
    }

    这种方式能快速遍历除元素,但是没办法直接拿到索引,需要索引的话,只能单独使用变量去计算;

    java
    int index = 0;
    for(String item : s){
      index ++;
      System.out.printf("索引%s的值为:%s %n", index, item);
    }

    或者使用普通的for循环去遍历

    java
    String[] s = new String[]{"d", "e", "f", "g"};
    for (int index = 0; index < s.length; index++) {
      String item = s[index];
      System.out.printf("索引%s的值为:%s %n", index, item);
    }

拷贝

开发过程中,可能会遇到数组拷贝的情况,比如一段业务,需要临时处理一下数组中的数据,又不想影响到原始数据,就会需要拷贝一份新的出来使用,Java中,是允许将一个对象赋值给另外一个对象,如下示例:

java
String[] s1 = {"a", "b", "c"};
String[] s2 = s1;

这样操作,s1和s2指向的是同一个对象,当通过其中任何一个对象去修改元素,另外一个也会跟着变化;

java
s1[0] = "m";
System.out.println(s2[0]); // 输出:m

当通过s1去把第一个元素修改为m时,打印s2的第一个元素也变成了m

此时就可以利用Arrays.copyOf的方式来完成深层拷贝,如下示例:

java
String[] s1 = {"a", "b", "c"};
String[] s2 = s1;
String[] s3 = Arrays.copyOf(s1, s1.length);

s1[0] = "m";
System.out.println(s2[0]);
System.out.println(s3[0]);

输出结果:

m

a

根据执行结果来看,通过Arrays.copyOf拷贝的s3在s1被修改之后,并没有受到影响;

上面的测试,数组都是使用的基本数据类型,如果换成对象,情况又不一样了;Arrays.copyOf只是拷贝出了一个新的数组,元素对象没有拷贝新的,如果元素引用的是用一个对象,互相之间也会受到影响; 可以看看以下简单的例子:

  • 简单的User(用户)对象

    java
    public static class User {
        private String name;
    
        private Integer age;
    
        public User(String name, Integer age) {
            this.name = name;
            this.age = age;
        }
    }
  • 对象拷贝

    java
    public static void t4() {
        User[] users1 = new User[2];
        users1[0] = new User("zhangsan", 10);
        users1[1] = new User("lisi", 20);
    
        // 这里得到的数组users2 和 users1 不是一个对象
        User[] users2 = Arrays.copyOf(users1, users1.length);
    
        // 但是两个数据里面保存的 ”zhangsan“ 还是同一个
        User zhangsan = users2[0];
        // 当修改users2的张三的名字时
        zhangsan.name = "zhang3";
    
        // users2的张三对象的名字也变了
        System.out.println(users1[0].name);
    }

    虽然通过Arrays.copyOf得到了两个不同指向的数组,但是里面的zhangsanlisi元素还是指向了同一个对象,当任何一个数组中用户对象的名称修改之后,另一个数组中的对象也会跟着变化;对象信息如下:

归根结底,这种问题还是由于对象的深拷贝与浅拷贝问题,后面章节会进一步探讨;

排序

Arrays 提供了sort 方法,能方便对数组进行快速排序

  • 数值类型排序

    java
    public static void t5() {
        int[] i1 = {1, 8, 2, 5, 3, 10, 7};
        Arrays.sort(i1);
        for (int i : i1) {
            System.out.println(i);
        }
    }

    输出:

    java
    1
    2
    3
    5
    7
    8
    10
  • 对象排序

    数值类型的数组,排序非常的方便,但是对象排序,就比较的麻烦了;

    在了解对象排序之前,我们需要简单的了解一下ComparableComparator两个接口,他们都是用来实现集合中元素的比较、排序的,只是 Comparable 是在集合内部定义的方法实现的排序,Comparator 是在集合外部实现的排序, 当要使用Arrays.sort对对象进行排序时,需要通过Comparator指明对象的排序规则;

    Comparator 接口需要指定排序对象,并实现compare方法,方法传入两个对象(a和b),用于开发者指定两个对象的比较规则,返回的参数有三类:

    • 负数:a比b小
    • 0:a比b相等
    • 正数:a比b大

    如下示例,通过对象的年龄进行排序:

    java
    User[] users1 = new User[2];
    users1[0] = new User("一行Java", 30);
    users1[1] = new User("一航", 20);
    
    Arrays.sort(users1, new Comparator<User>() {
        @Override
        public int compare(User a, User b) {
            return a.age - b.age;
        }
    });
    
    for (User user : users1) {
        System.out.printf("name:%s age:%s %n", user.name, user.age);
    }

    输出:

    java
    name:一航 age:20 
    name:一行Java age:30

多维数组

二维数组

五子棋,相信大家都玩过,那如何设计一个五子棋的棋盘呢?就可以借助二位数组来实现

java
int[][] checkerboard = new int[9][9];

// 上面的声明和下面的声明是一样的

int[][] checkerboard2 = {
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
};

以上代码,就声明了一个 9x9 的棋盘;

默认棋盘初始化之后,全部为0,我们将黑子标记为1,白子标记为2;

比如:

在最中间放一个黑子:

java
checkerboard[4][4] = 1;

在黑子的右边放一颗白子:

java
checkerboard[4][5] = 2;

在黑子的右边再放一颗黑子:

java
checkerboard[3][4] = 1;

打印棋盘:

java
for (int i = 0; i < 9; i++) {
    for (int j = 0; j < 9; j++) {
        System.out.print(checkerboard[i][j] + "  ");
    }
    System.out.println();
}

输出

java
0  0  0  0  0  0  0  0  0  
0  0  0  0  0  0  0  0  0  
0  0  0  0  0  0  0  0  0  
0  0  0  0  1  0  0  0  0  
0  0  0  0  1  2  0  0  0  
0  0  0  0  0  0  0  0  0  
0  0  0  0  0  0  0  0  0  
0  0  0  0  0  0  0  0  0  
0  0  0  0  0  0  0  0  0

三维数组

我们可以将以为数组理解为线,二维数组里面为面,三维数据理解为体;三维数组就好比是一个魔方体;

比如一个3x3的魔方

java
int[][][] a = new int[3][3][3];

int[][][] a1 = {
        {
                {0, 0, 0},
                {0, 0, 0},
                {0, 0, 0},
        },
        {
                {0, 0, 0},
                {0, 0, 0},
                {0, 0, 0},
        },
        {
                {0, 0, 0},
                {0, 0, 0},
                {0, 0, 0},
        }
};

多维数组

上面介绍的二维、三维主要是为了方便大家理解多维数组;

其实在Java里面,是没有二维、三维数组这种叫法,只有一维和多维(数组中的数组)数组之分,二维、三维数组统一称之为多维数组;

上面的演示的结构比较的有规律,都是9x9、3x3x3的结构,下面带大家创建一个不太规律的多维数组;

java
// 只指定第一个维度的长度
int[][] a = new int[5][];
// 初始化第二个维度,第二个维度的长度并不固定
for (int i = 0; i < a.length; i++) {
    a[i] = new int[i + 1];
}

// 打印
for (int i = 0; i < a.length; i++) {
    for (int j = 0; j < a[i].length; j++) {
        System.out.print(a[i][j] + "  ");
    }

    System.out.println();
}

示例输出:

java
0  
0  0  
0  0  0  
0  0  0  0  
0  0  0  0  0

可以看出,整个多维数组的结构是一个三角形的;

  • int[][] a = new int[5][];

    这样初始化只指定了第一个维度的长度是5,第二个维度的数组还没有初始化;

  • 初始化第二个维度

    数组的长度并不固定;

    java
    for (int i = 0; i < a.length; i++) {
        a[i] = new int[i + 1];
    }

    根据第一个维度的索引(下标),初始化第二个维度,长度索引+1