下面我带大家一起加深一下对Java数组的认识:
1.理解数组
数组也是一种数据类型,本身就是一种引用类型,我们从它的初始化方法,通过关键字new去完成定义及初始化就可以知道。
数组的长度是不变的,一旦数组完成初始化后,它的长度就固定下来了,在内存中占有的空间也就固定了,即使里面的数据被清空了,占有的空间还是保留下来了,依然是属于数组的,当然长度依旧是不变的。
数组里边存放的数据类型要一致,可以基本数据类型,也可以是引用数据类型,但是唯一的标准就是相同的类型。在Java中,类与类是支持继承关系的,所以就可能造成数组里面可以存在多中数据类型的假象:
示例:
class Animal { public String name; public Animal(String name) {
this.name = name;
}
} class Cat extends Animal { public Cat(String name) {
super(name);
}
} class Dog extends Animal { public Dog(String name) {
super(name);
}
} public class ArrayDemo { public static void main(String[] args) { Animal[] animals = new Animal[2];
Cat cat = new Cat("little cat");
Dog dog = new Dog("little dog");
animals[0] = cat;
animals[1] = dog;
System.out.println(animals[0].name);
System.out.println(animals[1].name);
}
}
这样看上去,好像数组里面存放了Cat类型和Dog类型,但是实际上他们都是Animal类型,数组里面都是相同的类型,请大家不要搞混淆了,哈哈。
2.定义数组和初始化数组
定义数组的语法格式:
type[] arrayName;
type arrayName[];
这两种格式,我是强烈推荐第一种的,第一种格式给人非常的明确,定义了一个变量,它是type数组类型,但是第二种就可能给人一种假象,定义了一种变量,它是type类型的,为了不给别人误会,还是选择第一种好点咯。
定义完了数组,这是内存中还没给数组分配空间,真正分配内存空间的时候就是在给数组初始化的时候。
初始化数组分两种:
静态初始化:
arrayName = new type[]{element1,element2, element3......};
由上面很容易看出静态的初始化就是显式指定数组每个元素的初始值,由系统决定数组的长度。
动态初始化:
arrayName = new type[length];
由上面看出动态初始化就是只指定数组的长度,由系统为数组分配初始值。
对于不同类型,系统分配的初始值也是不一样的。
类型 |
初始值 |
整数类型(byte、short、int、long) |
0 |
浮点类型(float、double) |
0.0 |
字符类型(char) |
'\u0000'(代表空格) |
布尔类型(boolean) |
false |
引用类型(类、接口、数组) |
null |
你可能产生这样的猜想:不是可以同时使用静态初始化和动态初始化吗?
答案是不可以的,编译器是提示Cannot define dimension expressions when an array initializer is provided,因为你定义了长度,说明你已经初始化完了,我想想也是觉得这样不合理的,如果可以两者混合使用的话,那么我定义好了数组的长度后,系统是不是要给它附上默认的初始值,但是你后面又是静态的初始化,指定了特定的值,系统又要为你改变里面的值,这样做系统真累,我也觉得累。当然编译器是不支持两者混合使用的,大家注意了!
3.数组的使用
数组的使用就是引用数组的索引,数组的索引是从0开始的,到length-1为止,如果超过了这样范围就是抛出java.lang.ArrayIndexOutOfBoundException异常,就是数组索引越界异常,引用数组的使用太简单了,下面就简单用for和foreach来演示一下访问:
public class ArrayDemo2 { public static void main(String[] args) { String[] strings = new String[]{"hello", "world"}; for(int i = 0; i < strings.length; i++)
System.out.println(strings[i]); for(String string : strings)
System.out.println(string);
}
}
4.内存中的数组:
type[] arrayName = new type[]{element1, element2, element3......};
arrayName就是一个引用的变量,这个数组的引用变量可以指向任何有效的内存,只有当指向有效的内存后才可以方位数组元素,可以这么说,引用变量是访问真是对象的根本方式。
实际的数组对象被存储在堆(heap)内存中,如果引用该数组独享的数组引用变量是一个局部变量的话,那么它被存储在栈(stack)内存中,示意图如下:
由这里我们可以很清楚的看出局部数组引用变量是怎么访问到内存中的数组的,党当一个方法调用完后,局部变量就没了,也就是从栈内存中消失了,如果堆内存中数组不再有任何引用变量指向自己,这个数组将成为垃圾,该数组所占的内存就会被系统的垃圾回收机制回收,因此,为了让垃圾回收机制回收一个数组所占的内存空间,可以将数组变量赋值为null,也就切断了数组引用变量和实际数组之间的关系,实际的数组也就成为了垃圾。
在这里,我们让一个引用变量指向另外一个实际的数组的时候,可能产生数组长度可变的假象,大家来看看一个例子:
public class ArrayDemo3 { public static void main(String[] args) { int[] a = new int[]{1, 2};
int[] b = new int[4];
System.out.println("length of b:" + b.length); b = a;
System.out.println("length of b:" + b.length);
}
}
结果:
length of b:4
length of b:2
从上面看我们b数组的长度好像发生了变化,但是实际上不是这样的,我们来分析一下内存中的变化:
先是这样:
然后这样:
我们所说的数组的长度不变是针对堆内存中真正数组的长度,引用变量是可以改变指向的,指到哪里肯定就显示指到的数组的长度了,但是真正的长度是不曾改变的。
5.操作数组的工具类:
Java提供的Arrays类里包含的一些static修饰的方法可以直接操作数组
int binarySearch(type[] a, type key):使用二分法查询key元素值在a数组中出现的索引,如果a数组不包含key元素值,就返回负数,调用该方法的前提是数组中的元素已经按照升序排列好了。
type[] copyOf(type[] original, int newLength):这个方法可以将original数组复制到一个新的数组,这个新的数组的长度为newLength。
boolean equals(type[] a, type[] b):如果a数组和b数组的长度相等,而且数组中的元素一一相等,该方法就返回true。
void fill(type[] a, type val):该方法会将数组中的全部元素赋值为val。
void sort(type[] a):该方法对a数组进行(升序)排序。