数组
数组是编程语言中非常常见的一种数据结构,Java 也不例外,数组的运用非常的普遍;在Java中,数组是存储着相同类型值的集合,然后通过一个整数类型的索引(index,又称为下标)访问数组中的每一个值;
声明数组
在声明数组变量时,需要指出数组的类型,类型后面跟上[]以及变量,如:
// 声明一个字符串类型的数组
// 以下两种方式都可以,比较起来大家更加喜欢第一种
String[] s;
//或者
String s[];
以上语句仅仅时一个声明,数据并没有对s真正的初始化,需要通过new操作符初始化这个数组;
// 初始化一个长度为10的字符串数组
String[] s = new String[10];
这行代码声明并初始化了一个字符串数组:s
,长度为10,可以存储10个字符串元素;
初始化的数组如果是基础数据类型的数组,初始化之后所有元素将被赋予对应类型的初始值
(如int的初始值为:0,boolean的初始值为:false,double的初始值为:0.0);如果是包装类型或者对象,所有元素全部为null
;
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 为我们提供了一种简写方式来创建,如下:
String[] s = {
"a",
"b",
"c",
// 更多元素
};
所有元素通过{}
括起来,元素之间通过,
隔开; 这种方式不需要new操作符号,也不需要指定长度,会根据括号中元素的个数指定数组的长度;
上面的方式,在初次声明数组的时候可以,但没办法对一个已经创建的数组重新初始化,比如想对象数组s重新初始化,下面的写法就会报错:
// 这样写时会报错的
s = {"d","e","f","g"};
此时就可以声明一个匿名数组(anonymous array):
s = new String[]{"d","e","f","g"};
以上的声明等价于:
String[] tmp = {"d","e","f","g"};
s = tmp;
数组长度
数组有个length变量,能够直接获取数组的长度
System.out.println(s.length);
数组访问与遍历
数组的元素是通过数组的索引(index,又称为下标)进行访问,索引的起始是从0
开始,到array.length-1
结束;
添加、修改
由于数组的长度在初始化之后就固定了,因此,数组的访问索引不能越过数组长度;
javaString[] s = new String[10]; s[0] = "一行"; // 下标为0的元素设置为:一行 s[1] = "Java"; // 下标为1的元素设置为:Java s[2] = "一航"; // 下标为2的元素设置为:一航 s[2] = "张三"; // 下标为2的元素设置为:张三
一旦超过,就会报越界(
ArrayIndexOutOfBoundsException
)异常,如:javas[100] = "ehang";
异常信息:
javaException 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循环快速遍历:
javaString[] s = new String[]{"d", "e", "f", "g"}; for(String item : s){ System.out.println(item); }
这种方式能快速遍历除元素,但是没办法直接拿到索引,需要索引的话,只能单独使用变量去计算;
javaint index = 0; for(String item : s){ index ++; System.out.printf("索引%s的值为:%s %n", index, item); }
或者使用普通的for循环去遍历
javaString[] 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中,是允许将一个对象赋值给另外一个对象,如下示例:
String[] s1 = {"a", "b", "c"};
String[] s2 = s1;
这样操作,s1和s2指向的是同一个对象,当通过其中任何一个对象去修改元素,另外一个也会跟着变化;
s1[0] = "m";
System.out.println(s2[0]); // 输出:m
当通过s1去把第一个元素修改为m时,打印s2的第一个元素也变成了m
此时就可以利用Arrays.copyOf
的方式来完成深层拷贝,如下示例:
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(用户)对象
javapublic static class User { private String name; private Integer age; public User(String name, Integer age) { this.name = name; this.age = age; } }
对象拷贝
javapublic 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
得到了两个不同指向的数组,但是里面的zhangsan
、lisi
元素还是指向了同一个对象,当任何一个数组中用户对象的名称修改之后,另一个数组中的对象也会跟着变化;对象信息如下:
归根结底,这种问题还是由于对象的深拷贝与浅拷贝问题,后面章节会进一步探讨;
排序
Arrays 提供了sort 方法,能方便对数组进行快速排序
数值类型排序
javapublic static void t5() { int[] i1 = {1, 8, 2, 5, 3, 10, 7}; Arrays.sort(i1); for (int i : i1) { System.out.println(i); } }
输出:
java1 2 3 5 7 8 10
对象排序
数值类型的数组,排序非常的方便,但是对象排序,就比较的麻烦了;
在了解对象排序之前,我们需要简单的了解一下
Comparable
与Comparator
两个接口,他们都是用来实现集合中元素的比较、排序的,只是 Comparable 是在集合内部定义的方法实现的排序,Comparator 是在集合外部实现的排序, 当要使用Arrays.sort
对对象进行排序时,需要通过Comparator
指明对象的排序规则;Comparator 接口需要指定排序对象,并实现
compare
方法,方法传入两个对象(a和b),用于开发者指定两个对象的比较规则,返回的参数有三类:- 负数:a比b小
- 0:a比b相等
- 正数:a比b大
如下示例,通过对象的年龄进行排序:
javaUser[] 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); }
输出:
javaname:一航 age:20 name:一行Java age:30
多维数组
二维数组
五子棋,相信大家都玩过,那如何设计一个五子棋的棋盘呢?就可以借助二位数组来实现
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;
比如:
在最中间放一个黑子:
checkerboard[4][4] = 1;
在黑子的右边放一颗白子:
checkerboard[4][5] = 2;
在黑子的右边再放一颗黑子:
checkerboard[3][4] = 1;
打印棋盘:
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
System.out.print(checkerboard[i][j] + " ");
}
System.out.println();
}
输出
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的魔方
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的结构,下面带大家创建一个不太规律的多维数组;
// 只指定第一个维度的长度
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();
}
示例输出:
0
0 0
0 0 0
0 0 0 0
0 0 0 0 0
可以看出,整个多维数组的结构是一个三角形的;
int[][] a = new int[5][];
这样初始化只指定了第一个维度的长度是5,第二个维度的数组还没有初始化;
初始化第二个维度
数组的长度并不固定;
javafor (int i = 0; i < a.length; i++) { a[i] = new int[i + 1]; }
根据第一个维度的索引(下标),初始化第二个维度,长度
索引+1