数组:相同类型的、用一个标识符名称封装到一起的一个对象序列或基本类型数据序列。
- //举例
- int[] a = new int[10];
- for (int i = 0; i < a.length; i++){
- a[i] = new Random().nextInt();
- }
- int a[] = new int[10];
- for (int i = 0; i < a.length; i++){
- a[i] = new Random().nextInt();
- }
- int[] b = new int[] {
- 1,
- 2,
- 3
- };
在定义数组的时候直接初始化,大括号里的值就是数组的值。
- int[] c = {
- 1,
- 2,
- 3
- };
可以不写 new,直接使用大括号初始化,但是本质上还是调用了 new 的,只是可以不写出来而已,所以叫隐式初始化。
最后,我们回过头来仔细的研究一下下面这一句代码:
- int[] a = new int[10];
这句代码做了哪些事呢?
这样就可以通过 a 这个变量,来操作这个数组了。
是不是觉得这个过程很熟悉?没错!我们创建一个对象的过程也是这样的!那这是不是证明,数组其实是一个对象呢?我们后面会详细分析。
数组是使用方式大家应该都很清楚了,我这里简单的提一下。
- for (int i = 0; i < myList.length; i++) {
- System.out.println(myList[i] + " ");
- }
- for (int element: myList) {
- System.out.println(element);
- }
- int length = myList.length;
java 中的每个数组都有一个名为 length 的属性,表示数组的长度。
length 属性我们后面会详细分析。
数组是可以存放任意类型的数据的,不一定非得是基本数据类型。数组元素不为基本原生数据类型时,存放的是引用类型,而不是对象本身。当生成对象之后,引用才指向对象,否则引用为 null。
- Person[] p = new Person[3];
- //未生成对象时,引用类型均为空
- System.out.println(p[0]);
- //生成对象之后,引用指向对象
- p[0] = new Person(10);
- p[1] = new Person(20);
- p[2] = new Person(30);
- for(int i = 0; i < p.length; i++){
- System.out.println(p[i].age);
- }
- public void printArray(int[] array) {
- for (int i = 0; i < array.length; i++) {
- System.out.print(array[i] + " ");
- }
- }
- public int[] reverse(int[] list) {
- int[] result = new int[list.length];
- for (int i = 0, j = result.length - 1; i < list.length; i++, j--) {
- result[j] = list[i];
- }
- return result;
- }
首先,这样写是不对的。
- public static void main(String[] args) {
- int a[] = {
- 1,
- 9
- };
- System.out.println(a.toString());
- }
- //[
这输出的是什么奇怪的东西?我们先不管,后面会详细说。那怎么输出数组呢?
方式一:
- public static void main(String[] args) {
- int a[] = {
- 1,
- 9
- };
- for (int i: a) {
- System.out.println(i);
- }
- }
方式二:
- public static void main(String[] args) {
- int a[] = {
- 1,
- 9
- };
- System.out.println(Arrays.toString(a));
- }
数组内容的比较可以使用 equals() 方法吗?
看代码:
- public class ArrayTest {
- public static void main(String[] args) {
- int[] a = {
- 1,
- 2,
- 3
- };
- int[] b = {
- 1,
- 2,
- 3
- };
- System.out.println(a.equals(b));
- //结果是false。
- }
- }
所以证明不能直接用 equals() 方法比较数组内容,因为没有 override Object 中的实现,所以仍采用其实现,即采用 == 实现 equals() 方法,比较是否为同一个对象。
怎么比较呢?一种解决方案是自己写代码,另一种方法是利用 java.util.Arrays。
java.util.Arrays 中的方法全是 static 的。其中包括了 equals() 方法的各种重载版本。
代码如下:
- import java.util.Arrays;
- public class ArrayEqualsTest {
- public static boolean isEquals(int[] a, int[] b) {
- if (a == null || b == null) {
- return false;
- }
- if (a.length != b.length) {
- return false;
- }
- for (int i = 0; i < a.length; ++i) {
- if (a[i] != b[i]) {
- return false;
- }
- }
- return true;
- }
- public static void main(String[] args) {
- int[] a = {
- 1,
- 2,
- 3
- };
- int[] b = {
- 1,
- 2,
- 3
- };
- System.out.println(isEquals(a, b));
- System.out.println(Arrays.equals(a, b));
- }
- }
java.util.Arrays 类能方便地操作数组,它提供的所有方法都是静态的。
具有以下功能:
我觉得这些大家也都知道,我就不细说了,重点在后面。
二维数组是数组的数组。其实 java 只有一维数组,但是由于数组可以存放任意类型的数据,当然也就可以存放数组了,这个时候,就可以模拟多维数组了。
基本的定义方式同样有两种,如:
- type[][] i = new type[2][3];//(推荐)
- type i[][] = new type[2][3];
二维数组的每个元素都是一个一维数组,这些数组不一定都是等长的。
声明二维数组的时候可以只指定第一维大小,空缺出第二维大小,之后再指定不同长度的数组。但是注意,第一维大小不能空缺(不能只指定列数不指定行数)。
- public class ArrayTest4{
- public static void main(String[] args){
- //二维变长数组
- int[][] a = new int[3][];
- a[0] = new int[2];
- a[1] = new int[3];
- a[2] = new int[1];
- //Error: 不能空缺第一维大小
- //int[][] b = new int[][3];
- }
- }
二维数组也可以在定义的时候初始化,使用花括号的嵌套完成,这时候不指定两个维数的大小,并且根据初始化值的个数不同,可以生成不同长度的数组元素。
- int[][] c = new int[][] {
- {
- 1,
- 2,
- 3
- },
- {
- 4
- },
- {
- 5,
- 6,
- 7,
- 8
- }
- };
有的时候,你需要一个方法,但是你在调用它之前不知道要传递几个参数给他,这个时候你就需要可变参数了。
- public static void main(String [] args){
- System.out.println(add(2,3));
- System.out.println(add(2,3,5));
- }
- public static int add(int x,int ...args){
- int sum=x;
- for(int i=0;ireturn sum;
- }
那个奇怪的
就是可变参数,这样你就可以传递任意个你想传递的数据了。
- int ...args
java 把可变参数当做数组处理。
可变参数实质上是一个数组,所以下面这样重载是不可以的!
- private int sumUp(int... values) {
- }
- private int sumUp(int[] values) {
- }
尽管在背地里,编译器会把能匹配不确定个实参的形参,转化为数组形参;而且也可以用数组包了实参,再传递给实参个数可变的方法;但是,这并不表示 "能匹配不确定个实参的形参" 和 "数组形参" 完全没有差异。
一个明显的差异是,如果按照调用实参个数可变的方法的形式,来调用一个最后一个形参是数组形参的方法,只会导致一个 "cannot be applied to" 的编译错误。
比如:
- private static void testOverloading(int[] i) {
- System.out.println("A");
- }
- public static void main(String[] args) {
- testOverloading(1, 2, 3);//编译出错
- }
这样是不行的。
除此之外,可变参数是不可以使用泛型的,关于泛型,我们下一篇文章会详细讲解。
可变参数还有许多其他的坑,感兴趣的可以详细了解一下,我就不多说了。毕竟。。打字好累啊。
- int[] a = new int[] {
- 1,
- 2
- };
- int[] b = a;
- b[1] = 5;
这个时候 a[1] 也变成了 5,为什么会这样?就不用我多说了吧,所以,要拷贝一个数组,还是需要些技巧的:
方式一:System.arraycopy 的用法
- int[] src = {
- 1,
- 3,
- 5,
- 7,
- 9,
- 11,
- 13,
- 15,
- 17
- };
- int[] dest = {
- 2,
- 4,
- 6,
- 8,
- 10,
- 12,
- 14,
- 16,
- 18,
- 20
- };
- //从src中的第一个元素起复制三个元素,即1,3,5复盖到dest第2个元素开始的三个元素
- System.arraycopy(src, 0, dest, 1, 3);
- System.out.println(Arrays.toString(dest));
- //[2, 1, 3, 5, 10, 12, 14, 16, 18, 20]
方式二:Arrays.copyOf 的用法
- int[] src = {
- 1,
- 3,
- 5,
- 7,
- 9,
- 11,
- 13,
- 15,
- 17
- };
- int[] dest = {
- 2,
- 4,
- 6,
- 8,
- 10,
- 12,
- 14,
- 16,
- 18,
- 20
- };
- //copyOf(是复制src数组从0开始的两个元素到新的数组对象)
- int[] copyof = Arrays.copyOf(src, 2);
- System.out.println(Arrays.toString(copyof));
- //[1, 3]
方式三:Arrays.copyOfRange 的用法
- nt[] src = {
- 1,
- 3,
- 5,
- 7,
- 9,
- 11,
- 13,
- 15,
- 17
- };
- int[] dest = {
- 2,
- 4,
- 6,
- 8,
- 10,
- 12,
- 14,
- 16,
- 18,
- 20
- };
- //copyRange(从src数组中从0开始的第二个元素到第五个元素复制到新数组,含头不含尾)
- int[] copyofRange = Arrays.copyOfRange(src, 2, 6);
- System.out.println(Arrays.toString(copyofRange));
- //[5, 7, 9, 11]
说了那么多,那么,数组究竟是个什么东西呢?
我们来看看数组有没有什么可以用的方法:
哟,还真有?怎么看着这么像 Object 类里那几个方法啊! 这其中,必有蹊跷。
来看这段代码:
- public class Test {
- public static void main(String[] args) {
- int[] array = new int[10];
- System.out.println("array的父类是:" + array.getClass().getSuperclass());
- System.out.println("array的类名是:" + array.getClass().getName());
- }
- }
- //array的父类是:class java.lang.Object
- //array的类名是:[I
从上面示例可以看出, 数组的是 Object 的直接子类, 它属于 "第一类对象",但是它又与普通的 java 对象存在很大的不同,从它的类名就可以看出:[I,这是什么东东??
我们再看如下示例:
- public class Test {
- public static void main(String[] args) {
- int[] array_00 = new int[10];
- System.out.println("一维数组:" + array_00.getClass().getName());
- int[][] array_01 = new int[10][10];
- System.out.println("二维数组:" + array_01.getClass().getName());
- int[][][] array_02 = new int[10][10][10];
- System.out.println("三维数组:" + array_02.getClass().getName());
- }
- }
- //一维数组:[I
- //二维数组:[[I
- //三维数组:[[[I
通过这个实例我们知道:[代表了数组的维度,一个 [表示一维,两个 [表示二维。可以简单的说数组的类名由若干个'['和数组元素类型的内部名称组成。不清楚我们再看:
- public class Test {
- public static void main(String[] args) {
- System.out.println("Object[]:" + Object[].class);
- System.out.println("Object[][]:" + Object[][].class);
- System.err.println("Object[][][]:" + Object[][][].class);
- System.out.println("Object:" + Object.class);
- }
- }
- //Object[]:class [Ljava.lang.Object;
- //Object[][]:class [[Ljava.lang.Object;
- //Object[][][]:class [[[Ljava.lang.Object;
- //Object:class java.lang.Object
从这个实例我们可以看出数组的 "庐山真面目"。同时也可以看出数组和普通的 Java 类是不同的,普通的 java 类是以全限定路径名 + 类名来作为自己的唯一标示的,而数组则是以若干个 [+L + 数组元素类全限定路径 + 类来最为唯一标示的。这个不同也许在某种程度上说明了数组也普通 java 类在实现上存在很大的区别,也许可以利用这个区别来使得 JVM 在处理数组和普通 java 类时作出区分。
我们在 jdk 中并没有找到一个可以代表数组的类,但是数组的的确确是 Object 类的一个子类,那么,它究竟是从哪冒出来的呢?
首先,数组是对象!
但是这个数组对象并不是从某个类实例化来的,而是由 JVM 直接创建的,因此查看类名的时候会发现是很奇怪的样子,这个直接创建的对象的父类就是 Object,所以可以调用 Object 中的所有方法,包括你用到的 toString()。
所以我们之前的输出问题就很明显了,因为调用的 toString() 方法是来自于 Object 的,这个方法的实现是
- public String toString() {
- return getClass().getName() + "@" + Integer.toHexString(hashCode());
- }
所以就打出了类似于 [
如果要输出 "{1,9}" 这样的内容,可以写一个循环逐个输出,或者使用 Arrays.toString() 输出。
java 中数组为什么要设计为协变的?
比如:
- Number[] num = new Integer[10];
- num[0] = 2.1;
这样的语句可以通过编译,而在运行时会错误。
那为何不禁止数组协变,在编译期间就指出错误呢?
因为 SE5 之前还没有泛型,但很多代码迫切需要泛型来解决问题。
举个例子,比较两个数组是否 "值相等" 的 Arrays.equals() 方法。因为底层实现调用的是 Object.equals() 方法,和数组中元素的具体类型无关。
- for (int i=0; iif (!(o1==null ? o2==null : o1.equals(o2)))
- return false;
- }
所以不想让每个类型都要重新定义 Arrays.equals() 方法。而是" 泛化 " 地接受任何元素类型的数组为参数,就像现在这样:
- public static boolean equals(Object[] a, Object[] a2) {
- ... ...
- }
要让 Object[] 能接受所有数组类型,那个时候又没有泛型,最简单的办法就是让数组接受协变,把 String[],Integer[] 都定义成 Object[] 的派生类,然后多态就起作用了。
但为什么数组设计成 "协变" 不会有大问题呢?这是基于数组的一个独有特性:
数组记得它内部元素的具体类型,并且会在运行时做类型检查。
这就是上面的代码能通过编译,但运行时报错的原因:
- Number[] num = new Integer[10];
- num[0] = 2.1; //Error
num 变量记得它内部元素是 Integer。所以运行时给它插入 double 型的时候不让执行。
这反而是数组的优点,也是当初 "敢于" 把数组设计成协变的原因。虽然向上转型以后,编译期类型检查放松了,但因为数组运行时对内部元素类型看得紧,不匹配的类型还是插不进去的。
这也是为什么容器 Collection 不能设计成协变的原因。Collection 不做运行时类型检查,比较耿直。还是题主 Number 的例子,如果 Collection 接受 "协变",List 的引用能传给 List:
- List < Integer > integerList = new ArrayList < Integer > ();
- List < Number > num = integerList; // 假设现在容器接受"协变"
这时候我想往 List 里插入一个 Double。它不会像数组这样 "坚贞",它将 "安静" 地接受。
- num.add(new Double(2.1));
然后当我们从原先的 integerList 里面取东西,才会发现出问题了。虽然看上去从 integerList 里取 Integer,我们的操作无可指责。但取出来的却是 Double 型。
- Integer itg = integerList.get(0); //BOOM!
于其到拿出来之后才发现不对,那还不如当初就不让插入。这就是数组的好处。
而且,在引入了通配符(Wildcard)之后,协变的功能也已经被实现了。而且配合通配符的 "上界" 和 "下界" 一起用,容器内元素的类型还是受到严格控制的,虽然有点复杂。
- ListNumber> derivedNum=new ArrayList<Integer>();
所以总的来说,虽然数组的协变不是一个完美的设计,但也不能算非常烂。起码还能用,没有捅出大篓子。而且数组又不支持泛型,底层类库到处是 Object[],现在也不可能改了。
比如:
- List<String>[] l = new ArrayList<String>[10];
会报错,无法编译通过
根本的原因是:数组在创建的时候必须知道内部元素的类型,而且一直都会记得这个类型信息,每次往数组里添加元素,都会做类型检查。
但因为 Java 泛型是用擦除(Erasure)实现的,运行时类型参数会被擦掉。所以对于泛型数组,编译器看不到泛型的 String 类型参数。数组由于无法确定所持有元素的类型,所以不允许初始化。
具体我们会在下一篇《泛型》中详细说明。
- int arr[] = new int[3];
- int[ ][ ] arr = new int[3][ ];
- arr[0] = new int[3];
- arr[1] = new int[5];
- arr[2] = new int[4];
方式。
- int[] a
本篇文章就到这里。如果文章内容有什么错误或者更好的理解,请及时与我联系。
本文首发自我的个人博客:
地址:
同步更新于 csdn:
地址:
同步更新于简书:
地址:
引用:
《java 编程思想》
《java 核心卷一》
来源: http://www.bubuko.com/infodetail-1975105.html