目录
一、 一维数组的基本概念
1.1. 什么是数组
1.2. 数组的创建及初始化
1.3. 数组的使用
二、数组是引用类型
2.1. 初始JVM的内存分布
2.2. 基本类型变量与引用类型变量
2.3. 引用变量的理解
2.4. null
三、数组的应用场景
3.1. 作为函数的参数
3.2. 作为函数的返回值
一、 一维数组的基本概念
1.1. 什么是数组
当我们需要存储多个类型相同的变量时,如果一个一个去创建会很麻烦 ,此时我们就可以使用数组去简化这类问题。数组:可以看成是相同类型元素的一个集合。在内存中是一段连续的空间,地址是连续存放的。每个空间有自己的编号,第一个位置的编号为0,即数组的下标。
1.2. 数组的创建及初始化
(一)数组的创建规则:
数据类型 [] = new 数据类型[N],其中N表示数组的长度。
int[] array1=new int[10];//表示一个可容纳10个int型的数组double[] array2=new double[20];//表示一个可容纳20个double型的数组String[] array3=new String[30];//表示一个可容纳30个String性的数组
(二)数组的初始化
数组的初始化主要分为动态初始化以及静态初始化。
(1)动态初始化:在创建数组时,直接指定数组中元素的个数
int[] array=new int[10]
(2) 静态初始化:在创建数组时不直接指定数据元素个数,而直接将具体的数据内容进行指定
int[] array4 = new int[]{0,1,2,3,4,5,6,7,8,9};double[] array5 = new double[]{1.0, 2.0, 3.0, 4.0, 5.0};String[] array6 = new String[]{"hell", "Java", "!!!"};
注意:1.静态初始化虽然没有指定数组的长度,编译器在编译时会根据{}中元素个数来确定数组的长度 2.静态初始化时, {}中数据类型必须与[]前数据类型一致 3.静态初始化可以简写,省去后面的new T[]。
如果没有对数组进行初始化,数组中元素有其默认值。如果数组中存储元素类型为基类类型,默认值为基类类型对应的默认值。
类型 | 默认值 |
byte | 0 |
short | 0 |
int | 0 |
long | 0 |
float | 0.0f |
double | 0.0 |
char | /u000 |
boolean | false |
String | null |
在这里我们只演示boolean和String类型的默认值,代码如下,对此代码进行调试,运行最后一行时,我们就可以看到boolean的默认值为false
public static void main(String[] args) {boolean[] array=new boolean[3];//默认值为falseSystem.out.println("=====");String[] str=new String[3];}
1.3. 数组的使用
(1)数组元素的访问
数组是一段连续的内存空间,因此支持随机访问,即通过下标访问快速访问数组中任意位置的元素 ,即我们可以通过下标来进行打印、修改,并且不能越界访问,否则会出现如下报错。
int[] arrays=new int[]{1,2,3,4};arrays[2]=10;System.out.println(arrays[1]);//打印元素System.out.println(arrays[2]);//修改元素System.out.println(arrays[4]);//越界访问
(2)遍历数组:将所有元素都访问一遍。
如果我们一个一个打印,或者对里面的元素进行修改,会非常麻烦。此时我们可以使用for循环来解决。
int[] arrays = new int[]{1,2,3,4,5,6};for(int i=0;i<=5;i++){System.out.println(arrays[i]);}
我们来对此代码进行调试,当运行到“System.out.println(arrays[i]);”时,就会产生如下图所示。
虽然for循环可以帮助我们解决上面提到的问题,但是我们无法获取数组的长度。那我们就可以通过下面的代码来进行实现。
int[] arrays = new int[]{1,2,3,4};for(int i=0;i< arrays.length;i++){System.out.print(arrays[i]);}System.out.println();for (int x:arrays) {System.out.println(x);}
第一种是for循环,原理不必多说;第二种是for-each,原理是遍历数组,将数组中的元素赋值给x,但这种方法无法获取数组元素的下标,也无法对下标进行运算。
当然还有第三种打印方式,是由Java官方提供操作数组的工具,代码如下:
import java.lang.reflect.Array;
import java.util.Arrays;public class Main {public static void main(String[] args) {int[] arrays=new int[]{1,2,3,4,5};String ret=Arrays.toString(arrays);System.out.println(ret);}
}
Array需要导入一个Arrays的包,我们可以查看一下toString的源码,是String类型的,所以使用String ret来接收一下,直接打印ret就可以了。 下图为打印结果。
二、数组是引用类型
2.1. 初始JVM的内存分布
我们经常说数组是引用变量,数组里的元素是如何储存在数组里的呢?要想搞清楚这两个问题,那我们首先需要了解JVM的内存分布。
我们可以把JVM理解为一个大房子,房子分为客厅、厨房、卧室、厕所。同样,JVM也可以划分出多个内存空间,如下图所示。设想一下,如果我们的房子非常乱,找起东西来就会很麻烦。如果对内存中存储的数据不加区分的随意存储,那对内存管理起来将会非常麻烦。
2.2. 基本类型变量与引用类型变量
基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的是其所对应的值;而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址。 好,我们来看下面一组代码:
public static void main(String[] args){int a = 1;int[] arrays = {1,2,3,4,5};
arrays就是一个局部类型的引用变量,数组里的元素会在堆上有一个地址,arrays这个变量就会在栈上存这个地址;a也是main方法里的局部变量,它只会在栈上开辟内存空间,就不会在堆上。所以引用变量并不直接存储对象本身,可以简单理解成存储的是对象在堆中空间的起始地址。通过该 地址,引用变量便可以去操作对象。
2.3. 引用变量的理解
看下面一组代码:
public static void main(String[] args) {int[] array1 = new int[3];array1[0] = 10;array1[1] = 20;array1[2] = 30;int[] array2 = new int[]{1,2,3,4,5};array2[0] = 100;array2[1] = 200;array1 = array2;//重点,必须理解array1[2] = 300;array1[3] = 400;array2[4] = 500;for (int i = 0; i < array2.length; i++) {System.out.println(array2[i]);}}
array1=array2,相当于我们把array2的地址赋给了array1,则array1就不能存原来的地址,这样就会造成两块引用同时指向一个空间,相当于通过array1去修改并访问array2,所以说打印出来的值则为100、200、300、400、500。那array1之前所指向的内存空间呢?答案是被回收掉了。
2.4. null
null 的作用类似于 C 语言中的 NULL (空指针), 都是表示一个无效的内存位置.。因此不能对这个内存进行任何读写操作。 一旦尝试读写, 就会抛出 NullPointerException,说明某个引用为空,访问了一个空的引用。
public class Main {public static void main(String[] args) {int[] array1 = {1,2,3};System.out.println(array1);//打印结果是16进制的地址int[] array2 = null;//array2属于引用类型,默认值不能为0
// array引用不指向任何对象System.out.println(array2.length);//null不指向任何对象,运行就会显示空指针异常}
}
博主还留下了两个问题给你去思考:1、引用可以指向引用吗? 2、一个引用可以指向多个对象吗?好,第一个问题:array1=array2,上面我们提到了,array1指向的是array2所指向的对象。第二个问题:一个引用只能指向一个对象,比如 int a=10,不能说a在栈上存储了10个数据吗。
三、数组的应用场景
3.1. 作为函数的参数
public class Main {public static void print(int[] array){for (int i = 0; i < array.length; i++) {System.out.print(array[i]);}System.out.println();}public static void main(String[] args) {int[] array={1,2,3};print(array);}
}
虽然两个引用都指向了同一个对象,但两个引用的作用域不一样,上面的array是形参,下面的array是实参 ,打印出来的结果就是1 2 3。在这种情况下,我们修改形参的值, 不影响实参的值。那如果我们来看下面一组代码,参数传数组类型呢?
两个方法都是传递引用类型,第一个方法改变了形参的地址,形参的指向被改变了;而第二个方法中,形参修改了指向对象的内容。
public class Main {public static void func1(int[] array){array =new int[]{10,20,30,40,50};}public static void func2(int[] array){array[0]=10;}public static void main(String[] args) {int[] array={1,2,3};func1(array);print(array);main1(args);//在main方法中,我们调用main1,就可以同时运行main和main1}public static void main1(String[] args) {int[] array={1,2,3};func2(array);print(array);}
3.2. 作为函数的返回值
我们都知道调用方法时,只能返回一个值,那我们可以使用数组来进行多个返回值的接收,代码如下:利用ret来接收,并返回给array。
import java.util.Arrays;public class Main {public static int[] test(){int[] ret=new int[2];ret[0]=10;ret[1]=20;return ret;}public static void main(String[] args) {int[] array=test();System.out.println(Arrays.toString(array));}
}