JAVA笔记
----------------------------------第一章JAVA数组
----------------------------------2018.12.23
1.什么是数组?
- 程序=算法+数据结构
流程控制解决的问题即为算法问题。数据结构,就是把数据按照特定的某种结构来保存,数组就是一种基本的数据结构。 - 数组:相同数据类型的元素组成的集合。元素按照线性顺序排列,即除了第一个和最后一个元素外,每个元素都有前驱和后继。
- Java语言是典型的静态语言,因此Java数组是静态的,即当数组被初始化之后,该数组所占的内存空间和数组长度都是不可改变的。
2.定义数组(声明数组变量)
根据数组元素的数据类型,可以将其分为基本类型数组和引用类型数组。
Java语言支持两种语法格式来定义数组:
type[ ] arrayName;
type arrayName[ ]; (不建议使用,为了和C语法兼容的写法)
- 基本类型数组
- 引用类型数组
public class TEST02 {
public static void main(String[] args) {
Person[] students;
System.out.println("动态初始化");
students = new Person[2];
Person zhang = new Person();
zhang.age = 19;
zhang.height = 180;
zhang.info();
Person lee = new Person();
lee.age = 20;
lee.height = 165;
lee.info();
System.out.println("数组变量指向Person对象");
students[0] = zhang;
students[1] = lee;
students[0].info();
students[1].info();
System.out.println("数组变量修改Person对象的属性");
students[0].age = 20;
students[0].info();
}
}
class Person{
public int age;
public double height;
public void info() {
System.out.println("我的年龄是:"+age+",我的身高是:"+height);
}
}
此时,完成数组定义,但暂时还没初始化。
数组是一种引用类型的变量,因此使用它定义一个变量时,仅仅表示定义了一个引用变量(也就是定义了一个指针),这个引用变量还未指向任何有效的内存,因此定义数组时不能指定数组的长度(初始化的工作任务)。
3.数组初始化
包括两种方式:静态初始化和动态初始化。
静态初始化
- 完整写法:
声明数组变量与数组初始化分开写
type[] arrayName;
arrayName= new type[]{data1,data2,data3};
声明数组变量时初始化
type[] arrayName = new type[]{data1,data2,data3};
其中,arrayName为数组变量;{data1,data2,data3}为数组对象;data1,data2,data3为数组元素。
- 简化形式:
type[] arrayName = {data1,data2,data3};(这种写法只能在声明数组变量时同时初始化)
动态初始化
type[] arrayName = new type[ length];
对于静态初始化方式,无须指定数组长度,指定了数组的数组元素,由系统来决定该数组的长度即可。
对于动态初始化,仅需要指定数组的长度(new关键字分配空间时需指定分配的间大小),即为每个数组元素指定所需的内存空间,系统将为这些元素分配初始值。
基本类型:
- 整数类型(byte、short、int、long)则数组元素的初始值为0;
- 浮点类型(float、double)则数组元素的初始值为0.0;
- 布尔类型(boolean)则数组元素的初始值为false;
- 字符类型(char)则数组元素的初始值为’\u0000’;
引用类型:
- 引用类型(类、接口、数组)则数组元素的初始值为null。
4.使用数组
4.1数组元素的访问、遍历
- 访问
数组对象的大小是固定的,长度为n,下标范围为0~n-1。
通常使用arrayName[下标]访问数组元素,当下标范围不在0~n-1时出现数组索引越界异常(运行时异常)
编译时异常和运行时异常,简单来说就是编译时异常就是在写代码时就会提示异常,运行时异常是在运行之后异常。
- 遍历,输出数组
(1)普通for循环
.length属性,获取数组对象长度。
for(int i=0;i<arrayName.length;i++) {
System.out.println(arrayName[i]);
}
(2)foreach循环(Java5)
无须获取数组长度,无须循环迭代条件,无须根据索引来访问数组元素,由系统完成。
for(type variableName :arrayName) {
System.out.println(variableName);
}
variableName为一个临时变量,系统依次把数组元素赋给这个临时变量,这个临时变量不是数组元素(仅保存),所以不能对其进行赋值修改数组。
(3).toString()
System.out.println(Arrays.toString(arrayName));
4.2复制
首先为什么要复制呢?
因为单纯的数组赋值无法使数组对象隔离,这样子容易使数组对象不小心被修改。
4.2.1赋值
public static void main(String[] args) {
int[] arr1 = new int[]{1,2,3,4};
int[] arr2 = arr1;
System.out.println("arr1:"+Arrays.toString(arr1));
System.out.println("arr2:"+Arrays.toString(arr2));
arr2[0]=0;
System.out.println("arr1:"+Arrays.toString(arr1));
System.out.println("arr2:"+Arrays.toString(arr2));
}
运行结果为:
arr1:[1, 2, 3, 4]
arr2:[1, 2, 3, 4]
arr1:[0, 2, 3, 4]
arr2:[0, 2, 3, 4]
以上为数组赋值,两个引用变量指向同一个数组对象,两个引用变量均可以修改数组元素的值。
修改arr2的数组元素,会使该数组对象的元素值被修改,所以会发现arr1的打印结果和arr2一样。
如此,数组对象没有隔离性,不小心就会被修改。
4.2.2拷贝
拷贝:实现数组对象隔离性
public static void main(String[] args) {
int[] arr1 = new int[]{1,2,3,4};
int[] arr3 = new int[arr1.length];
System.out.println("初始化::");
System.out.println("arr1:"+Arrays.toString(arr1));
System.out.println("arr3:"+Arrays.toString(arr3));
for(int i=0;i<arr1.length;i++) {
arr3[i] = arr1[i];
}
System.out.println("拷贝后:");
System.out.println("arr1:"+Arrays.toString(arr1));
System.out.println("arr3:"+Arrays.toString(arr3));
arr3[0]=0;
System.out.println("修改arr3数组元素值:");
System.out.println("arr1:"+Arrays.toString(arr1));
System.out.println("arr3:"+Arrays.toString(arr3));
}
结果为:
初始化::
arr1:[1, 2, 3, 4]
arr3:[0, 0, 0, 0]
拷贝后:
arr1:[1, 2, 3, 4]
arr3:[1, 2, 3, 4]
修改arr3数组元素值:
arr1:[1, 2, 3, 4]
arr3:[0, 2, 3, 4]
可以看到arr3的修改并不会影响到arr1,这就是实现了数组对象的隔离。
两个数组变量实际指向不同的数组对象。
4.2.3数组拷贝的其他方法
public static void main(String[] args) {
int[] src = new int[]{1,2,3,4};
int[] dest1 = new int[src.length];
System.arraycopy(src,0,dest1,0,src.length);
System.out.println("src:"+Arrays.toString(src));
System.out.println("dest1:"+Arrays.toString(dest1));
int[] dest2 = Arrays.copyOf(src, src.length);
System.out.println("dest2:"+Arrays.toString(dest2));
}
结果为:
src:[1, 2, 3, 4]
dest1:[1, 2, 3, 4]
dest2:[1, 2, 3, 4]
需要import java.util.Arrays;
System.arraycopy(); Java API提供,底层使用C++写的,速度快,比for循环实现数组拷贝效率更高,推荐使用。
Arrays.copyOf(); JDK1.6版本提供,底层是arraycopy()方法,但需要注意JDK版本。
4.2.4利用拷贝进行数组扩容
数组的长度在初始化后是不可改变的,可以创建一个更大的新数组并将小数组复制到其中,实现“数组扩容”。
public static void main(String[] args) {
int[] a = new int[] {1,2,3,4,5,6};
System.out.println("原始a:"+Arrays.toString(a));
System.out.println(a);
a = Arrays.copyOf(a, a.length+5);
System.out.println("扩容后a:"+Arrays.toString(a));
System.out.println(a);
}
结果为:
原始a:[1, 2, 3, 4, 5, 6]
[I@7852e922
扩容后a:[1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0]
[I@4e25154f
实际上,只是将数组变量指向另一个新的数组对象,原始的数组长度并没有发生变化。
4.3数组的内存管理
4.3.1基本类型数组的初始化
程序先为数组分配内存空间,再将数组元素的值存入对应内存中。
int[] a;
在main方法栈区定义了一个数组变量,还未指向数组对象。
a= new int[] {1,2,3,4,5,6};
静态初始化之后,确定了数组长度,将数组元素填入堆区数组对象的内存中(若是动态初始化,默认值)。
注意:
指定类型的变量只能存储指定类型的值。
所有局部变量都存放在栈内存中,不管是基本类型的变量还是引用类型的变量,都是存储在各自的方法栈内存中的;但引用类型的变量所引用的对象(包括数组、普通的Java对象)则是存储在堆内存中。
4.3.2引用类型数组的初始化
引用类型数组的数组元素依然是引用类型的,因此数组元素里存储的还是引用,它指向另一块内存,这块内存里存储了该引用变量所引用的对象。
4.4易混淆的点
4.4.1数组对象和数组变量
数组变量只是一个引用变量,数组对象就是保存在堆内存中的连续内存空间。所以数组初始化并不是对数组变量执行初始化,而是在堆内存中创建数组对象(为该数组对象分配一块连续的内存空间)。
就像是一个数组对象可以看做一间房间,我们可以给它取不同的名字,但是我要是把这个名字挂到另一个房间去,那这个名字对应的就是另一间房间了,也就是指向了另一个数组对象(数组赋值)。当然,我一个房间可以取很多名字,但是它们中哪怕一个一旦修改了这个房间的属性,那么别的名字看到的房间属性也就变了,这就是数组对象的不隔离性。
从内存来看,数组变量存储在栈内存区,数组对象存储在堆内存区,两者是不一样的。
4.4.2数组长度
只要类型相互兼容,可以让一个数组变量指向另一个实际的数组,这种操作会产生数组的长度可变的错觉(数组对象改变了)。
由于数组变量整体赋值(上面讲的数组扩容)导致的数组的长度可以改变,只是一个假相(因为数组对象不是同一个对象)。
4.4.3多维数组
Java实际上没有多维数组,所谓多维数组其实就是数组元素依然是数组的一维数组,N维数组是数组元素为(N-1)维数组的数组。