15. Java基础之数组

转自https://www.cnblogs.com/xdp-gacl/p/3623759.html  &  https://blog.csdn.net/qq_31655965/article/details/60882588

一.数组的基本概念

  • 数组可以看成是多个相同类型数据组合,对这些数据的统一管理。
  • 数组变量属引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量。
  • 数组的元素可以是任何数据类型,包括基本类型和引用类型。
  • C和C++中的数组都可以分配在栈上面,而JAVA中的数组是只能分配在堆上面的,因为JAVA中的数组是引用类型。

二.一维数组

一维数组的声明方式有2种:

  • 格式一:数组元素类型  数组名[ ];  即type var[ ];
  • 格式二:数组元素类型[ ] 数组名; 即type[ ] var;
  • 格式二声明数组的方法与C#上声明一维数组的方法一样。

例如:int a1[ ];   int[ ] a2;

       double b[ ];

       person[ ] p1;  String s1[ ];

注意:JAVA语言中声明数组时不能指定其长度(数组中的元素个数)

       如:int a[5]; 这样声明一维数组是非法的。

三.数组的模型

  • 一维数组:一维数组就是一行,一行小格。
  • 二维数组:二维数组就是一行加一列组成的一个平面分成的小格,有行有列。
  • 三维数组:三维数组就是一个立方体。
  • 人类对最多认识到三维空间。

四.数组对象的创建

  JAVA中使用关键字new创建数组对象。

  格式为:数组名 = new 数组元素的类型[数组元素的个数]

       例如:

五.元素为引用数据类型的数组

  注意:元素为引用数据类型的数组中的每一个元素都需要实例化。

例如:

class Date{

      int year; int moth; int day;

      Date(int y; int m, int d){

        year=y ;

    month=m ;

    day=d ;

  }

}

 

六.数组的初始化

  • 1.动态初始化

    数组定义与为数组元素分配空间和赋值的操作分开进行

例如:

 1 public class Test{
 2     public static void main(String args[ ]){
 3     int a[ ];  //定义数组,即声明一个int类型的数组a[ ]
 4     a=new int[3];  //给数组元素分配内存空间。
 5     a[0]=3; a[1]=9; a[2]=8;  //给数组元素赋值。
 6     Date days[ ];
 7     days=new Date[3];
 8     days[0]=new Date(1, 4, 2004);
 9     days[1]=new Date(2, 4, 2004);
10     days[2]=new Date(3, 4, 2004);
11     } 
12 }
13 
14 class Date{
15     int year, month, day;
16     Date(int y, int m, int d){
17         year = y ;
18         month = m ;
19         day = d ;
20     }
21 }
View Code
  • 2.静态初始化

       在定义数组的同时就为数组元素分配空间并赋值。

例如:

 1 puclic class Test{
 2         public static void main(String args[ ]){
 3             int a[ ] = { 3, 9, 8};   //在定义数组的同时给数组分配空间并赋值。
 4             Date days[ ] = {
 5                 new Date(1, 4, 2004),
 6                 new Date(2 ,4 ,2004),
 7                 new Date(3 ,4, 2004)
 8         };
 9     }
10 }
11 class Date{
12     int year, month, day;
13     Date(int y, int m, int d){
14         year = y ;
15         month = m ;
16         day = d ;
17     }
18 }
View Code

七.数组元素的默认初始化

  • 数组是引用类型,它的元素相当于类的成员变量,因此给数组分配内存空间后,每个元素也被按照成员变量的规则被隐式初始化
  •  1 public class Test{
     2         public static void main(String args[ ]){
     3             int a[ ] = new int[5];
     4             Date[ ] days=new Date[3];
     5             System.out.println(a[3]);
     6             System.out.println(days[2]);
     7     }
     8 }
     9 class Date{
    10     int year, month, day;
    11     Date(int y, int m, int d){
    12         year = y ;
    13         month = m ;
    14         day = d ;
    15     }
    16 }
    View Code
    • 输出结果:

      System.out.println(a[3]);    打印出来的结果是:0。

      System.out.println(days[2]);  打印出来的结果是:null(空)

 

八.数组元素的引用

        定义并用运算符new为之分配内存空间后,才可以引用数组中的每个元素,数组元素的引用方式为:arrayName[index], index为数组元素下标,可以是整型常量或整型表达式。如:a[3], b[i], c[6*i]

   数组元素下标从0开始;长度为n的数组的合法下标取值范围为 n—1

        每个数组都有一个属性length指明它的长度,例如:a.length的值为数组a的长度(元素个数)

九.二维数组

  

十、理解JAVA中的各个维度的数组模型

  

十一.数组的高级使用

 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}};

2.可变参数

有的时候,你需要一个方法,但是你在调用它之前不知道要传递几个参数给他,这个时候你就需要可变参数了。

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);//编译出错
}

但是上面反之则成立,即如果test方法参数是int... 形式,在调用的时候就可以以数组的形式或者变参的形式传入。

这样是不行的。

除此之外,可变参数是不可以使用泛型的

3. 数组到底是什么

说了那么多,那么,数组究竟是个什么东西呢?

我们来看看数组有没有什么可以用的方法:

哟,还真有?怎么看着这么像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【属性】,在它的生命周期中不可变。

4.数组的协变

java中的数组为什么要设计成协变?----------不好不坏,不易修改了

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的引用能传给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)之后,协变的功能也已经被实现了。而且配合通配符的”上界“和”下界“一起用,容器内元素的类型还是受到严格控制的,虽然有点复杂。

List<? extends Number> derivedNum=new ArrayList<Integer>();

所以总的来说,虽然数组的协变不是一个完美的设计,但也不能算非常烂。起码还能用,没有捅出大篓子。而且数组又不支持泛型,底层类库到处是Object[],现在也不可能改了。

5.数组不支持泛型

比如:

List<String>[] l = new ArrayList<String>[10]; 

会报错,无法编译通过

根本的原因是:数组在创建的时候必须知道内部元素的类型,而且一直都会记得这个类型信息,每次往数组里添加元素,都会做类型检查。

但因为Java泛型是用擦除(Erasure)实现的,运行时类型参数会被擦掉。所以对于泛型数组,编译器看不到泛型的String类型参数。数组由于无法确定所持有元素的类型,所以不允许初始化。

 十二. 其它

数组和容器的区别:

  • 效率:在Java中,数组是一种效率最高的存储和随机访问对象引用序列的方式。数组就是一个简单的线性序列,这使得元素访问非常快速。但是为这种速度所付出的代价是数组对象的大小被固定,并且在其生命周期中不可改变。你可能会建议使用ArrayList,它可以通过创建一个新实例,然后把旧实例中所有的引用移到新实例中,从而实现更多空间的自动分配。尽管通常应该首选ArrayList而不是数组,但是这种弹性需要开销,因此,ArrayList的效率比数组低很多。------硕果仅存的优点
  • 类型:在泛型出现之前,容器不能进行编译时期类型检查的。但是数组却可以。但是泛型和自动装箱等的出现,让容易更具多功能。
  • 保存基本类型的能力都具有。基本类型的自动装箱在1.5之后

重点:数组是特殊的对象,父类是Object类。

 

posted @ 2018-10-03 16:56  Hermioner  阅读(263)  评论(0编辑  收藏  举报