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 }
-
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 }
七.数组元素的默认初始化
- 数组是引用类型,它的元素相当于类的成员变量,因此给数组分配内存空间后,每个元素也被按照成员变量的规则被隐式初始化
-
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 }
- 输出结果:
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的数组的合法下标取值范围为0 ~ 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类。