目录
1. 数组的基本概念
1.1 为什么要使用数组
假设要存储并输出5个学生的成绩,我们可以像下面这样操作。
public static void main(String[] args) {
int score1=70;
int score2=80;
int score3=90;
int score4=95;
int score5=100;
System.out.println(score1);
System.out.println(score2);
System.out.println(score3);
System.out.println(score4);
System.out.println(score5);
}
但是如果我们要存储并且输出20个同学的成绩呢?难道要重复这个操作20次吗?
那未免也太过繁冗!
1.2 什么是数组
数组的概念:数组可以看成是 相同类型元素的一个集合 。在内存中是一段连续的空间。
在java中,包含6个整形类型元素的数组,就相当于上图中连在一起的6个空间,从上图中可以看到:
1. 数组中存放的元素其类型相同
2. 数组的空间是连在一起的
3. 每个空间有自己的编号,其实位置的编号为0,即数组的下标。
1.3 数组的创建及初始化
1.3.1 数组的创建
T[] 数组名 = new T[N];
其中:
代码示例:
public static void main(String[] args) {
int[] arr1=new int[10];
double[] arr2=new double[20];
String[] arr3=new String[3];
}
1.3.2 数组的初始化
数组的初始化主要分为 动态初始化 以及 静态初始化 。
1. 动态初始化:在创建数组时,直接指定数组中元素的个数
//定义一个长度为10的int类型数组,且每个元素都初始化为0(默认值)
int[] arr=new int[10];
2.静态初始化:在创建数组时不直接指定数据元素个数,而直接将具体的数据内容进行指定
语法格式:T[] 数组名3称 = {data1,data2,data3,data4,data5,data6,...};
int[] arr1=new int[]{1,2,3,4,5,6};
double[] arr2=new double[]{1.0,2.0,3.0};
String[] arr3=new String[]{"Hello","World"};
【注意事项】
对于静态初始化,需要注意以下几点:
①静态初始化虽然没有指定数组的长度,编译器在编译时会根据{}中元素个数来确定数组的长度。
②静态初始化时, {}中数据类型必须与[]前数据类型一致。
③静态初始化可以简写,省去后面的new T[]。(省略格式就像下面这样👇)
注意:虽然省去了new T[], 但是编译器编译代码时还是会还原
int[] arr1={1,2,3,4,5,6};
double[] arr2={1.0,2.0,3.0};
String[] arr3={"Hello ","World"};
④ 问:那么能不能像C语言那样定义数组呢? 答:可以,但是不推荐!
跟C语言一样的定义方式👇:
int arr[]={1,2,3};
不推荐的理由:
该种定义方式不太友好,容易造成数组的类型就是int的误解。[]如果在类型之后,就表示数组类型,因此int[]结合在一块写意思更清晰。
public static void main(String[] args) {
int[] arr1;
arr1=new int[10];
int[] arr2;
arr2=new int[]{1,2,3,4,5,6};
//注意省略模式不可以拆分,否则编译错误!!!
//int[] arr3;
//arr3={1,2,3};
}
类型 | 默认值 |
byte | 0 |
short | 0 |
int | 0 |
long | 0 |
float | 0.0f |
double | 0.0 |
char | /u0000 |
boolean | false |
如果数组种的存储元素类型为引用类型(例如:String),默认值为null 。
1.4 数组的使用
1.4.1 数组中元素的访问
public static void main(String[] args) {
int[] arr=new int[]{10,20,30,40,50};
System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr[2]);
System.out.println(arr[3]);
System.out.println(arr[4]);
//也可以通过下标来改变数组的值
arr[0]=100;
System.out.println(arr[0]);
}
运行结果:
【注意事项】
1. 数组是一段连续的内存空间,因此 支持 随机访问 ,即通过下标访问快速访问数组中任意位置的元素2. 下标从 0 开始,介于 [0, N )之间不包含 N , N为元素个数 , 不能越界,否则会报出下标越界异常。
下面是一个数组越界的例子👇:
public static void main(String[] args) {
int[] arr=new int[]{1,2,3};//定义一个长度为3的数组
//越界访问
System.out.println(arr[3]);//数组只有3个元素,下标分别为0 1 2,arr[3]为越界访问
}
执行结果:
出现这种报错,就说明是越界访问数组了,所以一定要谨防下标越界!
1.4.2 遍历数组
所谓 "遍历" 是指将数组中的所有元素都访问一遍 , 访问是指对数组中的元素进行某种操作
下面是一个遍历打印数组的例子:
public static void main(String[] args) {
int[] arr=new int[]{10,20,30,40,50,60};
System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr[2]);
System.out.println(arr[3]);
System.out.println(arr[4]);
System.out.println(arr[5]);
}
上述的代码可以达到遍历数组的目的,但存在许多问题:
①如果数组中增加了一个元素,那么就需要增加一条打印语句。
②如果数组中有100个元素,就需要写100条打印语句
③如果我现在不想打印数组了,想给数组每个元素的值加 1 ,修改起来会很麻烦。
那么这些问题要怎么解决呢?
通过观察代码可以发现,我们对每个元素的操作都是重复的,那么就可以使用循环遍历。
public static void main(String[] args) {
int[] arr=new int[]{10,20,30,40,50,60};
for (int i = 0; i < 6; i++) {
System.out.print(arr[i]);
}
System.out.println();
}
在JAVA中,我们可以通过 数组对象 .lenth 来获取数组长度。
public static void main(String[] args) {
int[] arr=new int[]{10,20,30,40,50,60};
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]);
}
System.out.println();
}
或者也可以使用for-each遍历数组
public static void main(String[] args) {
int[] arr=new int[]{10,20,30,40,50,60};
for (int x:arr) {
System.out.print(x);
}
System.out.println();
}
for-each 是 for 循环的另外一种使用方式 . 能够更方便的完成对数组的遍历 . 可以避免循环条件和更新语句写错 .
2. 数组是引用类型
2.1 初始JVM的内存分布
为了方便管理内存,JVM(Java Virtual Machine)对所使用的内存按照功能的不同进行了划分:
(如下图所示👇)
程序计数器 (PC Register): 只是一个很小的空间 , 保存下一条执行的指令的地址虚拟机栈 (JVM Stack): 与方法调用相关的一些信息, 每个方法在执行时,都会先创建一个栈帧 ,栈帧中包含有:局部变量表 、 操作数栈 、 动态链接 、 返回地址 以及其他的一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了 。本地方法栈 (Native Method Stack): 本地方法栈与虚拟机栈的作用类似 . 只不过 保存的内容是 Native 方法的局 部变量 . 在有些版本的 JVM 实现中 ( 例如 HotSpot), 本地方法栈和虚拟机栈是一起的堆 (Heap) : JVM 所管理的最大内存区域 . 使用 new 创建的对象都是在堆上保存 ( 例如前面的 new int[]{1, 2, 3} ) , 堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销 毁 。方法区 (Method Area) : 用于 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据 . 方法编译出的的字节码就是保存在这个区域
总共分为这些区域,现在我们只简单关心 堆 和 虚拟机栈 这两块空间。
虚拟机栈:用于存放局部变量(与C语言中的那个栈相同)。
堆:用于给对象分配内存,即使用 new 创建的对象都是在堆上保存。
2.2 基本类型变量与引用类型变量的区别
基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的是其所对应的值;而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址。
就拿下面这段代码作为例子:
public static void func() {
int a = 10;
int b = 20;
int[] arr = new int[]{1,2,3};
}
2.3 再谈引用变量
先来看这段代码:
public static void func() {
int[] arr1 = new int[3];
arr1[0]=10;
arr1[1]=20;
arr1[2]=30;
int[] arr2=new int[]{1,2,3,4,5};
arr2[0]=100;
arr2[1]=200;
arr1=arr2;
arr1[2]=300;
arr1[3]=400;
arr2[4]=500;
for (int i = 0; i < arr2.length; i++) {
System.out.print(arr2[i]+" ");
}
}
对于上面这段代码,我们来画图分析:
2.4 认识null
null 在 Java 中表示 " 空引用 " , 也就是一个不指向对象的引用 .
public static void main(String[] args) {
int[] arr = null;//定义一个引用变量指向null
System.out.println(arr[0]);
}
运行结果:
编译器报错:Exception in thread "main" java.lang.NullPointerException
注意事项:Java 中并没有约定 null 和 0 号地址的内存有任何关联.
3. 数组的应用场景
3.1 保存数据
public static void main(String[] args) {
int[] arr=new int[10];
for (int i = 0; i < arr.length; i++) {
arr[i]=i+1;
}
System.out.println(Arrays.toString(arr));
}
3.2 作为函数的参数
讲数组作为函数参数之前,我们先来看看基本数据类型作为函数参数,做一个对照试验。
public static void main(String[] args) {
int num=0;
func(num);
System.out.println("num= "+num);
}
public static void func(int x){
x=10;
System.out.println("x= "+x);
}
运行结果如下:
观察上面例子可以发现:将变量num传入函数func内,在func中对传参x(实参为num)进行修改
实际并没有影响到实际的num值。num依旧是0,而不是10。
可以得出结论:将基本数据类型变量作为函数参数传入时,函数内对该变量做出的改变会不会影响到实际的(main函数中的)变量。
接下来我们来看看数组作为函数参数,跟上面的例子的结果有什么区别。
public static void main(String[] args) {
int[] array=new int[10];
System.out.println("这是main函数内第一次的数组打印"+Arrays.toString(array));
func(array);
System.out.println("这是main函数内第二次的数组打印"+Arrays.toString(array));
}
public static void func(int[] arr){
for (int i = 0; i < arr.length; i++) {
arr[i]=i+1;
}
System.out.println("这是func函数内的数组打印"+Arrays.toString(arr));
}
运行结果:
结论:将数组作为函数参数传入时,函数内对数组做出的改变会影响到实际的
(main函数中的)数组。
因为数组是引用类型,按照引用类型来进行传递,是可以修改其中存放的内容的。
总结 : 所谓的 " 引用 " 本质上只是存了一个地址 . Java 将数组设定成引用类型 , 这样的话后续进行数组参数传参 , 其实 只是将数组的地址传入到函数形参中. 这样可以避免对整个数组的拷贝 ( 数组可能比较长 , 那么拷贝开销就会很大 ).
3.3作为函数的返回值
就拿斐波那契数列前N项作为例子:
public static void main(String[] args) {
int[] array=func(10);
System.out.println(Arrays.toString(array));
}
public static int[] func(int n){
int[] arr=new int[n];
arr[0]=1;
arr[1]=1;
for (int i = 2; i < arr.length; i++) {
arr[i]=arr[i-1]+arr[i-2];
}
return arr;
}
可以看到在函数func内,数组arr可以作为函数返回值并且被外界接收。
完
看完了觉得有用的姥爷们求求您们点个免费的赞和关注,这对我真的很重要!非常感谢大家!