重新认识java(十一)---- java中的数组

面向对象的一些知识暂时告一段落,从本文开始,进入java语法的重学阶段~

初识数组

什么是数组

数组:相同类型的、用一个标识符名称封装到一起的一个对象序列或基本类型数据序列。

数组的定义及初始化

定义及动态初始化

  • 方式一,java推荐用法: type [] 变量名 = new type[数组中元素的个数];
      
  //举例
  int[] a = new int[10];
   for (int i = 0; i < a.length; i++){
       a[i] = new Random().nextInt();
   }

  • 方式二,c语言用法(不推荐): type 变量名 [] = new type[数组中元素的个数];
   int a[] = new int[10];
    for (int i = 0; i < a.length; i++){
        a[i] = new Random().nextInt();
    }

以上两种方式都叫做动态初始化,也就是说,只有当程序运行以后,你才能知道数组里到底存了哪些数据。方式二的命名方式c和c++程序员比较熟悉,但是java官方推荐使用第一种,一看就能知道,这是一个int型的数组,叫a。

静态初始化

int[] b = new int[]{1,2,3};

在定义数组的时候直接初始化,大括号里的值就是数组的值。

隐式初始化

int[] c = {1,2,3};

可以不写new,直接使用大括号初始化,但是本质上还是调用了new的,只是可以不写出来而已,所以叫隐式初始化。


最后,我们回过头来仔细的研究一下下面这一句代码:

int[] a = new int[10];

这句代码做了哪些事呢?

  1. int[] a: 定义了一个int型数组的引用,名字叫做a,存放在栈中。
  2. new int[10]:初始化一个长度为10的int型数组,在堆中开辟相应大小的内存。
  3. int[] a = new int[10]:将堆中开辟的数组的内存地址赋给数组引用a。

这样就可以通过a这个变量,来操作这个数组了。

是不是觉得这个过程很熟悉?没错!我们创建一个对象的过程也是这样的!那这是不是证明,数组其实是一个对象呢?我们后面会详细分析。

数组的使用

数组自身的使用

数组是使用方式大家应该都很清楚了,我这里简单的提一下。

数组的遍历

  • 方式一:for循环
      for (int i = 0; i < myList.length; i++) {
          System.out.println(myList[i] + " ");
      }
  • 方式二:foreach循环
      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());
}
//[I@61bbe9ba

这输出的是什么奇怪的东西?我们先不管,后面会详细说。那怎么输出数组呢?

方式一:

    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()方法,比较是否为同一个对象。

Object类中的equals方法默认使用==实现的,至于为什么数组也能使用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));
    }
}

Arrays类的使用

java.util.Arrays 类能方便地操作数组,它提供的所有方法都是静态的。

具有以下功能:

  • 给数组赋值:通过 fill 方法。
  • 对数组排序:通过 sort 方法,按升序。
  • 比较数组:通过 equals 方法比较数组中元素值是否相等。
  • 查找数组元素:通过 binarySearch 方法能对排序好的数组进行二分查找法操作。
  • .......

我觉得这些大家也都知道,我就不细说了,重点在后面。

数组的高级应用

二维数组

二维数组是数组的数组。其实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;i<args.length;i++){
        sum+=args[i];
    }
    return sum;
}

那个奇怪的int ...args就是可变参数,这样你就可以传递任意个你想传递的数据了。

java把可变参数当做数组处理。

注意:可变参数必须位于最后一项。当可变参数个数多余一个时,必将有一个不是最后一项,所以只支持有一个可变参数。因为参数个数不定,所以当其后边还有相同类型参数时,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());
}

所以就打出了类似于[I@61bbe9ba这样的稀奇古怪的数字。
如果要输出“{1,9}”这样的内容,可以写一个循环逐个输出,或者使用Arrays.toString()输出。

数组的length属性也是jvm添加的,数组一初始化,jvm就会给它一个固定的length【属性】,在它的生命周期中不可变。

数组的协变

java中数组为什么要设计为协变的?
比如:

Number[] num = new Integer[10]; 
num[0] = 2.1; 

这样的语句可以通过编译,而在运行时会错误。

那为何不禁止数组协变,在编译期间就指出错误呢?

因为SE5之前还没有泛型,但很多代码迫切需要泛型来解决问题。

举个例子,比较两个数组是否“值相等“的Arrays.equals( )方法。因为底层实现调用的是Object.equals( )方法,和数组中元素的具体类型无关。

for (int i=0; i<length; i++) {
    Object o1 = a[i];
    Object o2 = a2[i];
    if (!(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<Integer>的引用能传给List<Number>:

List<Integer> integerList = new ArrayList<Integer>();
List<Number> num = integerList; // 假设现在容器接受”协变“

这时候我想往List<Number>里插入一个Double。它不会像数组这样”坚贞“,它将”安静“地接受。

num.add(new Double(2.1));

然后当我们从原先的integerList里面取东西,才会发现出问题了。虽然看上去从integerList里取Integer,我们的操作无可指责。但取出来的却是Double型。

Integer itg=integerList.get(0);    //BOOM!

于其到拿出来之后才发现不对,那还不如当初就不让插入。这就是数组的好处。

而且,在引入了通配符(Wildcard)之后,协变的功能也已经被实现了。而且配合通配符的”上界“和”下界“一起用,容器内元素的类型还是受到严格控制的,虽然有点复杂。

List<? extends Number> 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方式。
  • 数组的长度是不可变的。
  • 数组是特殊的对象,父类是Object类。
  • java不支持泛型数组。
  • 数组是协变的。
  • 数组中可以保存任意类型的数据,从而可以创建多维数组。

本篇文章就到这里。如果文章内容有什么错误或者更好的理解,请及时与我联系。

本文首发自我的个人博客:
地址:http://wpblog.improvecfan.cn/
同步更新于csdn:
地址:http://blog.csdn.net/qq_31655965
同步更新于简书:
地址:http://www.jianshu.com/u/8dc5811b228f

转载请注明出处!!!!!!

看完了,如果对你有用,随心后点个赞呗~


引用:
《java编程思想》
《java核心卷一》
https://www.zhihu.com/question/21394322
http://www.cnblogs.com/jjdcxy/p/5870524.html
http://blog.csdn.net/renfufei/article/details/15503469
http://www.cnblogs.com/chenssy/p/3463719.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容