数组的基本操作 一维数组的创建Array数组是同一种类型数据的集合,是一种存储数据的方式,数组就是一个容器。
数组指的就是一组相关类型的变量集合,并且这些变量可以按照统一的方式进行操作。数组本身属于引用数据类型,那么既然是引用数据类型,这里面实际又会牵扯到内存分配,而数组的定义语法如下。
声明Type[] arrayName;
Type arrayName[];
两种声明方式有何区别,是否推荐使用某种? --- 两种声明方式实质上无区别,但推荐使用*第一种*。原因如下: 数组本身属于**引用数据类型**,即第一种声明方式中,表示`Type[]`是一种引用类型(即数组),而不是`Type`类型。 同时第一种声明方式具有更好的可读性,将类型`Type[]`与变量名`arrayName`区分开。思考以下声明方式是否有误?
int[5] ints;
答:该种声明方式是错误的。
在Java语言中声明数组时不能指定其长度,因为数组是一种引用类型的变量。因此使用其定义一个变量时,仅仅定义了一个引用变量(可以理解为指针),这个引用变量还未指向任何有效的内存,所以定义其时不能指定长度。
同时因为其并未指向任何有效的空间,因此还没有内存空间用来存储元素,因此该数组不能被使用,只有在数据进行初始化后才可被使用。
初始化数组的初始化分为静态初始化和动态初始化。
静态初始化
初始化时由程序员显式指定每个数组元素的初始值,由系统决定数组的长度。
type[] arrayName;
arrayName = new type[]{element1, element2, element3, ...};
上述可简写为:type[] arrayName = {ele1, ele2, ele3, ele4, ...};
int[] intArray = new int[]{1,2,3,4,5};
int[] intArray = {1,2,3,4,5};
思考以下声明初始化方式是否有误?
int[] ints; ints = {1,2,3,4,5}
答:原声明方式的含义为,在声明时直接分配内存空间并赋值,不可将其拆分。
动态初始化
初始化时由程序员指定数组的长度,由系统初始化每个数组元素的默认值。
type[] arrayName; arrayName = new type[length]; int[] intArray = new int[5];思考以下数组的声明方式是否有误?
int[] intArray = new int[5]{1,2,3,4};
int[] intArray = new int[5]{1,2,3,4,5};
int[] intArray = new int[5]{1,2,3,4,5,6};
答:有误。
不能同时使用静态初始化和动态初始化。即不能在进行数组初始化时,既指定数组长度,又为每个数组元素分配初始值。
一旦数组初始化完成,数组在内存中所占的空间将被固定下来,数组的长度将不可变。
for(int i : ints){
System.out.println(i);
}
public static void main(String[] args) {
int[] aInts = {1, 2, 3, 4, 5};
System.out.print("aInts :: ");
print(aInts); // aInts :: 1 2 3 4 5
int[] bInts = new int[5];
System.out.print("bInts :: ");
print(bInts); // bInts :: 0 0 0 0 0
}
public static void print(int[] ints ){
for (int i : ints) {
System.out.print(i + "t");
}
System.out.println();
}
思考以下二维数组的打印结果是哪种?
int[][] aInts = {{1,2,3}, {4,5}, {6}};
1 2 3 1 2 3 4 5 4 5 0 6 6 0 0
int[][] bInts = new int[3][3];
(什么都没有) 0 0 0 0 0 0 0 0 0其他操作 扩容
数组的扩容实际上也是数组的拷贝。
public static void reSize() {
String[] aStrs = new String[]{"aaa", "bbb", "ccc"};
String[] bStrs = new String[5];
bStrs[3] = "aaa";
bStrs[4] = "bbb";
System.arraycopy(aStrs, 0, bStrs, 0, aStrs.length);
for (String str : bStrs) {
System.out.println(str);
}
}
排序
Arrays类的静态sort()方法可以实现对数组的排序。
public static void sortArray() {
int[] ints = {1, 3, 2, 5, 7, 9};
System.out.println("原来的数组" + Arrays.toString(ints));
//对数组排序
Arrays.sort(ints);
System.out.println("排序后的数组" + Arrays.toString(ints));
}
填充
Arrays类的静态方法fill()来对数组中的元素进行替换
public static void fillArray() {
int[] ints = new int[]{1, 2, 3, 4};
Arrays.fill(ints, 1, 3, 5);
for (int i : ints) {
System.out.println(i);
}
}
数组到底是什么?
思考以下代码的执行结果经过以上介绍,对数组有比较清晰的认识。
Integer aInt = 1024;
Integer bInt = 1024;
aInt == bInt;
aInt.equals(bInt);
int cInt = 1024;
int dInt = 1024;
cInt == dInt;
Integer[] aInts = {1024, 1025, 1026};
Integer[] bInts = {1024, 1025, 1026};
aInts == bInts;
aInts.equals(bInts);
int[] cInts = {1024, 1025, 1026};
int[] dInts = {1024, 1025, 1026};
cInts == dInts;
cInts.equals(dInts);
思考以下代码能否正常执行?
int[] ints = {1,2,3,4};
String[] strs = {"aaa","bbb","ccc"};
Integer[] integers = (Integer[]) ints;
Object[] objs1 = (Object[]) ints;
Object[] objs2 = (Object[]) strs;
答:int[] 不能强制转成 Object[] 或 Integer[],因为 int[] 是 Object 对象。(思考:Object[]接收的是什么对象?)
为什么 String[] 可以强制转换成 Object[] ?因为 String 类的父类就是Object,自然而然可以转。
Array—是基于索引(index)的数据结构,它使用索引在数组中搜索和读取数据很快。Array获取数据的时间复杂度是O(1),但是要删除数据却开销十分大(思考为何?)
List—是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式,它继承Collection。List有两个重要的实现类:ArrayList和linkedList。
-
ArrayList中存放的都是对象,即引用类型,即使我们可以向里面 put 一个基本数据类型,那么也是基于自动装箱特性,将基本数据类型转换成对象;而数组中可以是任意类型。
ArrayList
aList = new ArrayList<>(); // 运行结果如何? -
Array 相对高效,但其容量固定且无法改变。
ArrayList 容量可动态增长(牺牲一定效率) -
ArrayList是Array的复杂版本
- ArrayList内部封装了一个Object类型的数组,从一般的意义来说,它和数组没有本质的差别,甚至于ArrayList的许多方法,如Index、IndexOf、Contains、Sort等都是在内部数组的基础上直接调用Array的对应方法。
public class ArrayList
extends AbstractList implements List , RandomAccess, Cloneable, java.io.Serializable { ... ... transient Object[] elementData; ... ... } public E get(int index) { Objects.checkIndex(index, size); return elementData(index); } -
ArrayList 是类型安全的,因为它支持泛型允许编译器检查 ArrayList 里所包含的对象是否是正确的类型。然而 array 并不支持泛型。这代表在编译时期检查 array 所保存对象的类型是不可能的,但是array 通过抛出 ArrayStoreException 异常的方式来进行运行时的类型检查如果你存储了一个错误类型的对象。
类型安全是指同一段内存在不同的地方,会被强制要求使用相同的办法来解释(内存中的数据是用类型来解释的)。java语言是类型安全的,除非强制类型转换。
public static void main(String[] args) { // 不使用泛型(此时非类型安全) ArrayList aList = new ArrayList(); aList.add("aaa"); aList.add("bbb"); aList.add("ccc"); aList.add(123); Iterator iterator = aList.iterator(); while (iterator.hasNext()) { String next = (String)iterator.next(); System.out.print(next + "t"); } }上述代码可以成功编译(虽然有警告)
尝试运行代码,报错ClassCastException类型转换错误。
修改为使用泛型ArrayList
aList = new ArrayList<>();后,在编译期发现错误,阻止编译通过。
public static void arrayTypeSafety(){ Object[] obj = new String[3]; obj[1] = 0; }编译通过,无任何报错
运行时报错ArrayStoreException
观察上述ArrayStoreException的例子,结合以下代码,对于Array数组,进行如下演示:
public static void arrayTypeSafety() {
List[] ls = new List[5];
ArrayList li = new ArrayList<>();
li.add(123);
li.add(234);
Arrays.fill(ls, li);
Iterator> iterator = Arrays.stream(ls).iterator();
while (iterator.hasNext()) {
List next = iterator.next();
Iterator stringIterator = next.iterator();
while (stringIterator.hasNext()) {
String val = stringIterator.next();//此处类型转换报错ClassCastException
System.out.println(val);
}
}
}
能够通过编译生成class字节码文件
运行代码,出现ClassCastException报错,运行失败
协变为什么要设计成协变?在某些情况下,即使某个对象不是数组的基类型,我们也可以把它赋值给数组元素。这种属性叫做协变(covariance)。(与此对应还有逆变)
因为SE5之前还没有泛型,但很多代码迫切需要泛型来解决问题。
举个例子,比较两个数组是否 “值相等” 的 Arrays.equals() 方法。因为底层实现调用的是Object.equals() 方法,和数组中元素的具体类型无关。
for (int i=0; i所以不想让每个类型都要重新定义Arrays.equals( )方法。而是”泛化“地接受任何元素类型的数组为参数,就像现在这样:
public static boolean equals(Object[] a, Object[] a2) { ... ... }要让Object[]能接受所有数组类型,那个时候又没有泛型,最简单的办法就是让数组接受协变,把String[],Integer[]都定义成Object[]的派生类,然后多态就起作用了。
为什么数组设计成协变不会有大问题?数组记得他的内部元素的具体类型,并且会在运行时做类型检查。(也是为什么不能创建泛型数据的原因,数组创建时必须知道确切类型)
Object[] obj = new String[3]; obj[1] = 0;如同上面的协变代码,obj 记得它的内部元素是 Integer,所以运行时给它插入0是不允许的。
在某种层面上这也是数组的优点,也可能是当初允许将数组设计为协变的原因。虽然向上转型以后,编译器类型检查放松了,但是因为数组运行时对内部元素类型看的紧,不匹配的类型还是插入不进去。
实验源代码public class OneDimensionInit { public static void main(String[] args) { // statementTest(); // initArraysTest(); // initArrayThinkingOne(); // initArrayThinkingTwo(); } public static void statementTest() { // int[5] ints; } public static void initArraysTest() { // 静态初始化 int[] aInts; aInts = new int[]{1, 2, 3, 4, 5}; print(aInts); // 上述可简写为 int[] bInts = {1, 2, 3, 4, 5}; print(bInts); // 动态初始化 int[] cInts; cInts = new int[5]; print(cInts); } public static void initArrayThinkingOne() { int[] ints; // ints = {1,2,3,4,5}; } public static void initArrayThinkingTwo() { // int[] aInts = new int[5] { 1, 2, 3, 4 } ; // int[] bInts = new int[5] { 1, 2, 3, 4, 5 } ; // int[] cInts = new int[5] { 1, 2, 3, 4, 5, 6 } ; } }public class ArrayIterator { public static void main(String[] args) { // iteratoroneDimensionArray(); iteratorTowDimensionArray(); } public static void iteratorOneDimensionArray(){ int[] aInts = {1, 2, 3, 4, 5}; System.out.print("aInts :: "); print(aInts); int[] bInts = new int[5]; System.out.print("bInts :: "); print(bInts); } public static void print(int[] ints ){ for (int i : ints) { System.out.print(i + "t"); } System.out.println(); } public static void iteratorTowDimensionArray(){ int[][] aInts = {{1,2,3}, {4,5}, {6}}; print(aInts); int[][] bInts = new int[3][3]; print(bInts); } public static void print(int[][] ints){ for (int[] aInts : ints) { for (int i : aInts) { System.out.print(i + "t"); } System.out.println(); } } }public class OtherOpts { public static void main(String[] args) { reSize(); } public static void reSize() { String[] aStrs = new String[]{"aaa", "bbb", "ccc"}; String[] bStrs = new String[5]; bStrs[3] = "aaa"; bStrs[4] = "bbb"; System.arraycopy(aStrs, 0, bStrs, 0, aStrs.length); for (String str : bStrs) { System.out.println(str); } } public static void sortArray() { int[] ints = {1, 3, 2, 5, 7, 9}; System.out.println("原来的数组" + Arrays.toString(ints)); Arrays.sort(ints);//对数组排序 System.out.println("排序后的数组" + Arrays.toString(ints)); } public static void fillArray() { int[] ints = new int[]{1, 2, 3, 4}; Arrays.fill(ints, 1, 3, 5); for (int i : ints) { System.out.println(i); } } }public class ArrayExplore { public static void main(String[] args) { // thinkingOne(); // thinkingTwo(); } public static void thinkingOne() { Integer aInt = 1024; Integer bInt = 1024; System.out.println("aInt == bInt :: " + (aInt == bInt)); System.out.println("aInt.equals(bInt) :: " + (aInt.equals(bInt))); System.out.println(); int cInt = 1024; int dInt = 1024; System.out.println("cInt == dInt :: " + (cInt == dInt)); System.out.println(); Integer[] aInts = {1024, 1025, 1026}; Integer[] bInts = {1024, 1025, 1026}; System.out.println("aInts == bInts :: " + (aInts == bInts)); System.out.println("aInts.equals(bInts) :: " + (aInts.equals(bInts))); System.out.println(); int[] cInts = {1024, 1025, 1026}; int[] dInts = {1024, 1025, 1026}; System.out.println("cInts == dInts :: " + (cInts == dInts)); System.out.println("cInts.equals(dInts) :: " + (cInts.equals(dInts))); } public static void thinkingTwo(){ int[] ints = {1,2,3,4}; String[] strs = {"aaa","bbb","ccc"}; // Integer[] integers = (Integer[]) ints; // Object[] objs1 = (Object[]) ints; // Object[] objs2 = (Object[]) strs; } }public class ArrayAndArrayList { public static void main(String[] args) { arrayTypeSafety2(); } public static void arrayTypeSafety2(){ Object[] obj = new String[3]; obj[1] = 0; } public static void arrayTypeSafety() { List[] ls = new List[5]; ArrayList li = new ArrayList<>(); li.add(123); li.add(234); Arrays.fill(ls, li); Iterator > iterator = Arrays.stream(ls).iterator(); while (iterator.hasNext()) { List
next = iterator.next(); Iterator stringIterator = next.iterator(); while (stringIterator.hasNext()) { String val = stringIterator.next(); System.out.println(val); } } } public static void arrayListTypeSafety() { ArrayList aList = new ArrayList<>(); aList.add("aaa"); aList.add("bbb"); aList.add("ccc"); // aList.add(123); Iterator iterator = aList.iterator(); while (iterator.hasNext()) { String next = (String) iterator.next(); System.out.print(next + "t"); } } }



