目录
1.Java的内置数组
1.1 概念和特点
- 概念:数组是一种容器,可以存放多个数据值
- 特点:
- 1.数组是引用数据类型
- 2.数组当中的多个数据类型统一
- 3.数组的长度在程序运行期间不可改变
1.2 初始化
(1)动态初始化(指定长度)
动态初始化:在创建数组的时候,直接指定数组当中的元素个数,不指定具体的内容
格式:
- 数据类型[] 数组名称 = new 数据类型[数组长度];
格式解释:
- 左侧的数据类型:也就是数组中保存的数据,全都是统一的什么类型,既可以是基本类型,也可以是引用类型
- 左侧的中括号:代表这是一个数组
- 左侧的数组名称:给数组起一个名字
- 右侧的new:代表创建数组的动作
- 右侧的数据类型:必须和左边的数据类型保持一致
- 右侧中括号的数组长度:也就是数组中,到底可以保存多少个数据
示例代码:
int[] arrayA = new int[5];
double[] arrayB = new double[6];
String[] arrayC = new String[10];
注意:
- new String[10]只是开辟10个引用类型即10个存放地址的空间的大小,但是这些引用都没有指向任何对象,所以就更不存在调用类的构造函数
(2)静态初始化(指定内容)
静态初始化:在创建数组的时候,不指定数组的长度,而是直接将具体的数据内容进行指定
基本格式:
- 数据类型[] 数组名称 = new 数据类型[]{元素1,元素2,元素3,......};
省略格式:
- 数据类型[] 数组名称 = {元素1,元素2,元素3,......};
示例:
//基本格式:
int[] arrayA = new int[]{5,15,25,35};
String[] arrayB = new String[]{"Hello","World","Java"};
//省略格式:
int[] arrrayC = {5,15,25,60};
注意:
- 1.虽然静态初始化没有直接告诉长度,但是根据大括号里面的元素具体内容也可以自动推算出来长度
- 2.动态初始化和基本格式的静态初始化可以拆分为两个步骤
-
//静态初始化的标准格式,可以拆分成两个步骤: int[] arrayB; //此处是定义一个指向数组的引用,在栈中开辟1个引用空间 arrayB = new int[]{11,21,31}; //此处是创建一个数组对象,并在堆中开辟三个int大小的连续空间, //并且为它们赋予默认值0,然后程序又自动将对应的三个位置的值替换为11,21,31 //并且让栈中的arrayB引用又指向堆中开辟的这段连续空间的首地址
-
//动态初始化的标准格式,可以拆分成两个步骤: int[] arrayC; //此处是定义一个指向数组的引用,在栈中开辟1个引用空间 arrayC = new int[3]; //此处是创建一个数组对象,即在堆中开辟3个int大小的连续空间,并给这3个int空间中赋予类型的默认值0 //并且让栈中的arrayD引用又指向堆中开辟的这段连续空间的首地址 String[] arrayD; //此处是定义一个指向数组的引用,在栈中开辟1个引用空间 arrayD = new String[3]; //此处是创建一个数组对象,即在堆中开辟3个引用的连续空间,并且让3个对象不指向任何地址,即为null //并且让栈中的arrayD引用又指向堆中的这3个引用的第一个引用的首地址
-
动态初始化一个数组后,虽然没有赋予数组内容,但是数组的内容会有默认值:
-
整数:0
-
浮点数:0.0
-
boolean:false
-
char:\u0000(\u0000不是空格,仅仅是字符的空)
-
引用类型:null
-
注意:静态初始化其实也有赋予默认值的过程,只是系统马上自动替换默认值为大括号中的具体数值
-
-
3.省略格式的静态初始化不能分解为两个步骤,并且省略格式只是省略了new的写法,在内存中还是有new的动作的,在堆中创建数组对象
使用建议:
- 如果不确定数组当中的具体内容,用动态初始化;否则,已经确定了数组当中的内容,用静态初始化
1.3 访问数组中的元素
直接打印数组名,得到的是数组对应的内存地址的哈希值
public class ArrayDemo {
public static void main(String[] args) {
int[] arrayA = {1,2,3,4,5};
System.out.println(arrayA);
}
}
访问数组元素的格式:
- 数组名称[索引值]
- 索引就是一个int数字,代表数组当中元素的编号
- 索引值从0开始一直到“数组长度-1”为止
修改数组元素的值:
- 数组名称[索引值] = 新值
示例:
public class ArrayDemo {
public static void main(String[] args) {
int[] arrayA = new int[5];
System.out.println(arrayA[0]);
System.out.println(arrayA[1]);
System.out.println("============");
arrayA[1] = 20; //将第二个元素的值从默认值0替换为20
System.out.println(arrayA[0]);
System.out.println(arrayA[1]);
}
}
1.4 内存图分析
(1)只有一个数组的内存图
(2)两个数组的内存图
(3)两个引用指向同一个数组的内存图
(4)创建引用类型数组的内存图
class Person{
String name;
int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
}
Person[] arrayD; //此处是定义一个指向数组的引用,在栈中开辟1个引用空间
arrayD = new Person[3]; //此处是创建一个数组对象,即在堆中开辟3个引用的连续空间,并且让3个对象不指向任何地址,即为null
//并且让栈中的arrayD引用又指向堆中的这3个引用的第一个引用的首地址
arrayD[0] = new Person("Mike",15); //在堆中创建一个Person对象,并将它的首地址赋给arrayD[0]
1.5 数组中容易发生的两大异常
(1)数组索引越界异常
public class ArrayDemo {
public static void main(String[] args) {
int[] arrayA = new int[5];
System.out.println(arrayA[0]);
System.out.println(arrayA[1]);
System.out.println(arrayA[5]); //超越上界
System.out.println(arrayA[-1]); //超越下界
}
}
数组的索引的范围为[0,数组长度-1],超出这个范围去访问数组,就会抛出java.lang.ArrayIndexOutOfBoundsException,即数组索引范围越界异常
特别注意:
- 所以我们在写代码或者刷算法题的过程中首先应该对范围进行检查,防止越界
(2)空指针异常
public class ArrayDemo {
public static void main(String[] args) {
int[] arrayA = null;//定义了一个数组指向了空对象
System.out.println(arrayA[0]); //这里访问了一个空对象
}
}
- Java中所有的引用类型,我们都可给它赋值给一个null,即空对象,而我们去访问空对象,就会爆出空指针异常java.lang.NullPointerException
- 所以数组必须进行初始化,没有使用new进行创建,如果只是赋值了一个null,这时去访问它就会发生空指针异常
1.6 对“数组的长度不可变”的理解
- 获取数组的长度:数组名.length
public class ArrayDemo {
public static void main(String[] args) {
int[] arrayC = new int[3]; //在堆中创建了一个长度为3数组,并将它的首地址赋值给引用arrayC
System.out.println(arrayC.length); //3
arrayC = new int[5]; //在堆中创建了一个长度为5数组,并将它的首地址赋值给引用arrayC,
// 覆盖掉了原来的引用地址,本质上堆中数组对象本身没有变,变化的只是栈中引用所指的地址
System.out.println(arrayC.length);//5
}
}
- 数组长度不可变的真正含义是堆中创建的真正的数组对象长度不可变,而不是栈中对象的引用,栈中的引用可以改变指向其他数组对象
- 上述的道理不同适用于不可变对象如String、Integer等,在堆中的它们对象本身不可改变,指向它们的引用可以发生变化
1.7 数组作为方法参数传递
任何数据类型都能作为方法的参数
public class ArrayDemo {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
System.out.println("arr引用指向的地址:"+arr);
//传递的是引用,即地址,该地址指向的是堆中实际的数组对象,
// 而我们这样的传递就是将arr引用存放的地址值复制一份给printArray方法的方法栈中局部变量引用array
// 而在该printArray方法中通过该引用访问到的就是堆中实际的该对象,对它所作的所有修改都是对堆中该实际数组对象的修改
// 而整个这种思想就是“按引用传递”(在C++中使用指针或引用(C++中的引用本质还是指针)实现按引用传递)
printArray(arr);
}
public static void printArray(int[] array){
System.out.println("array引用指向的地址:"+array);
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
}
1.8 数组作为返回值传递
- 一个方法可以有多个参数,但只能有一个返回值,但是只能有1个或0个返回值,如果希望返回值有多个数据,就可以使用数组作为返回值
- 与上同理,返回的也是堆中数组的引用地址值
public class ArrayDemo {
public static void main(String[] args) {
int[] result = calculate(4,5,6);
System.out.println("main函数中接收到的返回的引用地址为:"+result);
int sum = result[0];
int avg = result[1];
System.out.println("sum:"+sum+" avg:"+avg );
}
public static int[] calculate(int a,int b,int c){
int sum = a + b + c; //总和
int avg = (a+b+c)/3; //平均数
//两个结果都希望进行返回,并且类型一致,那么我们可以定义一个数组进行返回
//这里是在calculate的方法栈中创建引用array,在堆中创建实际数组对象,最终返回的是array引用地址值,
// 当方法返回时,calculate方法栈的生命周期结束,但是堆中的数组对象仍然存在,而且返回值为该对象的地址值,
// 我们就可以通过该地址在其他方法中操纵该对象
// (这样的思想在C++中,在函数内部,通过C++的new关键字在堆中开辟空间,创建的时候用指针来接收了该对象在堆中的地址值
// ,当函数返回的时候返回该指针,从而可以操作堆中的该对象)
int[] array = new int[2];
System.out.println("calculate函数中创建的数组array的引用地址为:"+array);
array[0] = sum;
array[1] = avg;
return array;
}
}
- 拓展:其实不管Java还是C++,最根本的是:不能返回一个指向栈内存的指针(因为随着方法执行完,该内存被释放,不能被外部访问)
- 而对Java而言,只有基本类型和引用类型,返回基本类型,其实是返回基本类型的副本,而返回引用类型,返回的要么是指向堆内存的地址,要么是null,所以Java是可以返回局部变量的
public class ArrayDemo {
public static void main(String[] args) {
System.out.println("方法返回了局部变量的值:"+test());
}
//在Java中,我们可以直接在函数中定义一个基本类型(如int i = 5;)然后返回它
public static int test()
{
int i = 5;
return i; //此处返回的是i的副本
}
}
1.9 数组的遍历
(1)for循环
(2)foreach
2. 数组的几个应用案例
(1)求出数组中的最大值
(2)反转数组的元素
(3)洗牌
(4)计算质数
(5)证明哥德巴赫猜想