【Java知识课堂】数组详细介绍

目录

1、数组的基本概念

1.1为什么要使用数组

1.2什么是数组

1.3数组的创建及初始化 

1.3.1 数组的创建和介绍

1.3.2 数组的初始化

1.4数组的使用

1.4.1数组中元素访问

1.4.2 遍历数组 【3种方式】

 2.数组是引用类型

2.1基本类型变量与引用类型变量的区别

2.2细讲引用变量

 2.3 引用变量的空引用

3.数组的应用场景

3.1作为函数的参数

3.2作为函数的返回值

4.二维数组

4.1二维数组初始化

1.二维数组动态初始化

2.二维数组静态初始化

4.2二维数组遍历

 1.两层for循环嵌套遍历打印

 2.两层for-each遍历打印二维数组

 3.通过Arrays.deepToString()类打印


1、数组的基本概念

1.1为什么要使用数组

        如果我们要定义三个整型变量并赋值,我们可以一个一个写,但是如果我们要定义10个或100个变量呢?我们如果再一个一个定义就会显得过于麻烦,也会让代码显的冗余!

而如果我们想定义一组数据,同时这组数据都是相同数据类型的,那我们就可以用“数组”来定义这书数据

1.2什么是数组

数组:可以看成是相同类型元素的一个集合。在内存中是一段连续的空间。

  1. 数组中存放的元素类型形同
  2. 数组的空间是连在一起的
  3. 每个空间有自己的编号,起始位置的编号为0,即数组的下标


1.3数组的创建及初始化 

1.3.1 数组的创建和介绍

int[] array = new int[5];
组成intint[ ]arraynew5
含义表示数组中存放的元素的类型为整数型表示数组的类型为整数型 数组的名字创建对象关键字表示数组的长度

 “array”是一个变量,其中存储的是数组的地址

其中 '[' 表示数组类型,'I' 表示整数,'14ae5a5' 是地址的哈希值,也可以理解为地址。

在Java中,我们对于这种存储地址变量,通常称为 “引用变量”【引用】

而‘new’是创建对象的关键字,所以我们创建的数组也是‘对象’!(这就和C语言中数字区分开了,Java中的数组其实就是对象)

所以,在引用变量当中,存储了对象的地址,一般叫做这个引用/指向了一个对象。

1.3.2 数组的初始化

数组的初始化主要分为动态初始化和静态初始化

1.动态初始化:在创建数组时,直接指定数组中元素的个数

int[] array = new int[5];

2. 静态初始化:在创建数组时,不直接指定数据元素个数,而直接将具体的数据内容进行指定

int[] array1 = new int[]{1,2,3,4,5,6,7,8};
double[] array2 = new double[]{1.0,2.0,3.0,4.0,5.0};
String[] array3 = new String[]{"hello","world"};

注意事项

  • 静态初始化虽然没有指定数组长度,但编译器在编译时,会根据{}中元素个数来确定数组的长度。
  • 静态初始化时,{}中数据必须与 [ ] 前的数据类型一样。
  • 静态初始化可以简写,省去new T[] 

 例如:

    int[] array1 = {1,2,3,4,5,6,7,8};
    double[] array2 = {1.0,2.0,3.0,4.0,5.0};
    String[] array3 = {"hello","world"};

注意:虽然省去了new T[ ],但是编译器编译代码时还是会还原

  • 如果不确定数组当中内容时,使用动态初始化,否则建议使用静态初始化。
  • 静态和动态初始化也可以分为两步,但是省略格式不可以
    //动态初始化
    int[] array1;
    array1 = new int[10];

    //静态初始化
    int[] array2;
    array2 = new int[]{1,2,3,4};

注意:省略格式不可以拆分,否则编译失败

  • 如果没有对数组进行初始化,数组中元素被这设置为默认值

         1、如果数组中存储的元素为基类类型,默认值为基类类型对于的默认值

类型默认值
byte0
short0
int0
long0
float0.0f
double0.0
char/u0000
booleanfalse

        2、如果数组中存储元素类型为引用类型,默认值为null

1.4数组的使用

1.4.1数组中元素访问

        数组在内存中是一段连续空间,空间的编号都是从0开始的,依次递增,该编号称为数组的下标

        数组可以通过下标访问数组中任意位置的元素,同时还能通过 [ ] 对数组中的元素进行修改

    int[] array = new int[]{1,2,3,4,5};
    System.out.println(array[0]);
    System.out.println(array[1]);
    System.out.println(array[2]);

    array[2] = 30;
    System.out.println(array[2]);

【注意事项】

  • 数组是一段连续的内存空间,因此支持随机访问,即通过下标快速访问数组中任意位置的元素
  • 下标从0开始,介于[0,N) 之间不包含N,N为元素个数,数组访问不能越界,否则编译器会报出下标越界异常

1.4.2 遍历数组 【3种方式】

什么是“遍历”?  所谓遍历就是指将数组中的所有元素都访问一遍!

例如:

    int[] array = {1,2,3,4,5};
    System.out.println(array[0]);
    System.out.println(array[1]);
    System.out.println(array[2]);
    System.out.println(array[3]);
    System.out.println(array[4]);

 上述代码虽然可以起到对数组中元素的遍历,但是如果数组中元素增加到100个呢?我们难道要写100条打印语句吗?

第一种遍历方式

为了解决上述问题,用循环来进行遍历打印。

    int[] array = {1,2,3,4,5,6,7};
    for (int i = 0; i < 7; i++) {
        System.out.print(array[i]+" ");
    }

 现在又有一个问题,如果数组增加了一个元素,那么我就应该把for循环的条件改为“ i < 8”,这样就显得会很麻烦。我们能否直接获取数组的长度呢?

【注意】:在数组中,可以通过 ‘数组对象.length’ 来获取数组的长度

第二种遍历方式 

 同时,也可以使用 for-each 遍历数组

    int[] array = {10,20,30,40};
    for(int x:array){
        System.out.print(x+" ");
    }

 for-each 是 for 循环的另外一种使用方式,能够更方便的完成对数组的遍历,可以避免循环条件和更新语句写错。

第三种遍历方式

在Java中,为了让我们更好的去操作数组,提供了一系列的方法,这些方法在工具类Arrays当中

如下

在使用上述方法时,我们需要到入Arrays的包!

import java.util.Arrays;

如果你不想手动添加,当你在写Arrays时,编译器会提示,按enter键编译器就能直接导入

而toString方法其实就是把我们传入的数组,以字符串的形式进行输出。

我们查看toString源代码就能看出来,返回值为String类型,并且在数组前后加上了‘[’‘]’。

 2.数组是引用类型

2.1基本类型变量与引用类型变量的区别

在上文我们提到,引用类型的变量,是对对象的引用,而与基本类型变量最大的不同就是,

基本变量中存储的是本身的值,而引用类型的变量存储的是对象的地址

基本类型变量:用基本数据类型创建的变量

我们来看下面这个例子:

在讲解下面这个例子之前先扩展两个小知识

  • 虚拟机栈(JVM Stack) : 其中存储的是与方法调用相关的信息,每个方法执行时,都会先创建一个栈帧,栈帧中包含有:局部变量表、操作数栈、动态链接、返回地址以及其他一些信息,保存的都是与方法执行时相关的一些信息。
  • 堆(Heap): JVM管理的最大内存区域,使用 new 创建的对象都是在堆上保存(例如下面的 new int[]{1,2,3} ),堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销毁。
public static void main(String[] args) {
        int a = 10;
        int[] array = new int[]{1,2,3};
    }

 在上面代码中,a和array都是main方法内部的变量,其空间都在main方法对应的栈帧都分配

a 是内置类型的变量,因此空间中保存的就是给该变量初始化的值

array 是数组类型的引用变量,其中保存的值是所引用对象在堆中存放的地址 

我们可以看到,引用变量并不直接存储对象本身,而是存储对象在堆中所在的地址。通过该地址,引用变量便可以去操作对象。(类似于C语言中的指针) 

2.2细讲引用变量

 我们来看一下下面这段代码运行的结果

public static void main(String[] args) {
        int[] array1 = new int[3];
        array1[0] = 1;
        array1[1] = 2;
        array1[2] = 3;
        System.out.println("array1 :"+Arrays.toString(array1));

        int[] array2 = new int[]{100,200,3,4,5};
        System.out.println("array2 :"+Arrays.toString(array2));

        array1 = array2;

        array1[2] = 300;
        array1[3] = 400;
        array2[4] = 500;
        System.out.println("array1 :"+Arrays.toString(array1));
    }

 

我们不难发现,通过array1所打印出数组变了,也就是说引用变量array1指向的对象变了,同时array2和array1通过下标去对象进行了修改。

下面我通过画图的形式分三步具体讲解这一过程:

第一步:对象的创建

 1.创数组array1 ,但是没有给数组元素设置初始值,因此每个位置都是0

 2.array1通过下标的方式将数组元素分别修改为1、2、3

 3.创建数组array2

第二步:改变引用对象  array1 = array2

array1 = array2 这段代码的意思是让array1去引用array2 引用的数组空间,也就是说array1和array2可以同时调用同一个数组。此时array1中的地址改变为0x002412

第三步:array1和array2通过下标修改同一个数组对象

array1通过下标将数组2中第三个和第四个元素分别修改为300和400,此时array2也能看到数组中修改后的值,这里是因为array1和array2此时引用的是同一个数组对象!

然后array2通过下标把数组中第五个元素修改为500,此时array1也能看到数组中修改的值。

 通过打印我们能看到最终的结果

 2.3 引用变量的空引用

 在实际使用中,我们可能只想创建一个引用变量,并不想有所指向。这样我们就可以把null赋值给该引用变量

【引出】 null 在Java中表示“空引用”,也就是不指向对象的引用

public static void main(String[] args) {
        int[] arr = null;
    }

在这里要强调一点,在C语言语言中NULL和0等价的,但是在Java中null就是null跟0没有任何关系

比如我们将0赋值给引用变量时就会报错!

【注意】

        null 的作用类似于C语言中的 NULL(空指针),都是表示一个无效的内存位置因此不能对这个内存进行任何读写操作!

        所以当给引用变量赋值 null 时,该引用变量既不能通过'.'访问length,也不能通过下标进行访问。如果进行以上错误的操作的话,编译器就会报出空指针引用异常警报 ‘NullPointerException’

以下是报错信息截图:

3.数组的应用场景

3.1作为函数的参数

当我们需要传递一堆相同类型的数据时,我们就可以把数组作为方法的参数进行传递。如下

    public static void func(int[] arr){
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i]+" ");
        }
    }
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5};
        func(array);
    }

 在上述代码中,main方法中的array是实参,而funct方法中arr是形参。而把array传递给arr的过程,其实就是地址的传递,也可以理解为把array和arr指向了同一个对象。

这时就出现了一个问题,如果修改形参arr,那么实参array会改变吗?

我们来看下面的代码

    public static void func1(int[] arr1){
        arr1 = new int[]{10,20,30,40,50};
    }
    
    public static void func2(int[] arr2){
        arr2[0] = 10; 
    }
    
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5};
        func1(array);
        
        func2(array);

        System.out.println(Arrays.toString(array));
    }

大家猜一下打印的结果是什么?结果如下

 我们发现array指向的对象中,只有0号下标的元素改变了。而在func1和func2中都对形参进行了就该,结果只有方法func2修改对形参造成了影响!是不是有点迷惑,别急让我慢慢来解答

        首先main先在虚拟机栈里面创建栈帧,也可以理解为创建一个空间。然后在main所述的空间中,声明一个引用变量array同时在堆中创建数组对象,array中存储对象的地址‘0x99’(array指向对象)。

       接着,在main上面创建了func1的栈帧,并在其中声明了引用变量arr1。

        array作为实参传递给方法func1中,也就是把array中的地址传递给arr1,所以arr1也指向‘0x99’。 

  然后,在中func1(),又创建了一个数组对象,在堆中的地址为‘0x56’,并赋值给了arr1。

arr1 = new int[]{10,20,30,40} ;

 此时,arr1指向的对象变了,但并不影响array中存储的地址,也不影响array所指向的对象。

所以当方法func1()执行完,其在虚拟机栈中开辟空间也会被回收,同时因为地址为‘0x56’对象没有人引用,也会被回收。

 到此方法func1()执行完了,array中存储的地址还是“0x99”!

 接下来轮到执行方法func2()了,还是先创建空间,然后创建临时变量等一系列操作。

 array作为实参把所引用对象的地址传递过去,然后在方法func2()中arr2接收,所以array和arr2所指的对象一致。

arr2[0] = 10;

 接下来,arr2通过下标把所指对象的第一个元素改为10,这个修改就影响到了array,因为他俩引用的是同一个对象。当array通过下标访问第一个元素的值也就更改为了‘10’ !

         所以,形参只能通过相同地址的对象时,实参才能受到影响。而如果形参所指向对象改变了,那么实参将不受影响!

总结:所谓的‘引用’本质上只是存了一个地址,Java将数组设定成引用类型,这样的话后续进行数组参数传参,其实就是将数组的地址传入到函数形参中,这样可以避免对整个数组的拷贝! 

3.2作为函数的返回值

数组不仅可以作为参数传递,也可以作为返回值

当我们想返回多个相同类型的数时,我们就可以把它们定义为数组在进行返回

比如:获取斐波那契数列的前N项

    public static int[] fib(int n){
        if(n<=0){
            return null;
        }

        int[] array = new int[n];
        array[0] = array[1] = 1;
        for (int i = 2; i < array.length; i++) {
            array[i] = array[i-1] + array[i-2];
        }
        return array;
    }

    public static void main(String[] args) {
        int[] arr = fib(10);

        System.out.println(Arrays.toString(arr));
    }

 

4.二维数组

二维数组本质上也就是一维数组,只不过每个元素都是一个一维数组

4.1二维数组初始化

同时二维数组初始化可分为动态初始化和静态初始化

1.二维数组动态初始化

int[][] arr = new int[2][3];

 在Java中的二维数组跟C语言中的二维数组有一点不同。C语言中可以省略行,而Java中可以省略列!

这种二维数组叫‘不规则数组’

在不规则数组中,因为省略了列,所以编译器无法判断什么时候换行,所以我们要给每一行进行初始化。如下

int[][] arr = new int[2][];
arr[0] = new int[3];
arr[1] = new int[5];

这样代表着第一行有3个元素,第二行有5个元素

2.二维数组静态初始化

int[][] arr = new int[][]{{1,2,3},{4,5,6}};
int[][] arr2 = {{1,2,3},{4,5,6}};

4.2二维数组遍历

在讲数组遍历之前,我来讲一下二维数组的存储结构,以下面代码为例

int[][] array = new int[][]{{1,2,3},{4,5,6}};

如果单看二维数组的行而不看列的话,二维数组中就有两个元素,而这两个元素可以看成两个单独的引用变量,每个引用变量可以具体指向每个对象

 如下,拆解

我们可以只打印每一行的数据看看结果是什么

 

 如图打印出的是每一行元素指向对象的地址,通俗点就是每一行都是一个一维数组,所以打印出的都是地址。

从这我们可以看出,想要遍历二维数组每一个元素单层for循环是做不到的,所以便有了一下遍历方式

 1.两层for循环嵌套遍历打印

public static void main(String[] args) {
        int[][] array = new int[][]{{1,2,3},{4,5,6}};
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array[i].length; j++) {
                System.out.print(array[i][j]+" ");
            }
            System.out.println();//打印完一行,换行
        }
    }

打印结果: 

 

  这里面需要注意的是外层循环和内层循环的条件,就那上述代码来说,外层循环遍历的是着二维数组行数,为2,所以条件为i<2。在以为数组中数组名加length可以得到数组中的元素个数,所以单独引用二维数组的数组名array可以看做为一维数组。

  array.length就代表着array中有两个元素,分别为两个一维数组!

了解了外层,内存就清晰了不少,内存循环的条件为j<array[i].length。因为二维数组每行都是一维数组,所以就可以把array[i]看成每个一维数组的数组名! 

2.两层for-each遍历打印二维数组

public static void main(String[] args) {
        int[][] array = new int[][]{{1,2,3},{4,5,6}};
        for (int[] tmp:array) {
            for (int x:tmp) {
                System.out.print(x+" ");
            }
            System.out.println();//打印一行完换行
        }
    }

打印结果: 

 

 3.通过Arrays.deepToString()类打印

 在Java中提供了一个让我们打印二维数组的类,如下

public static void main(String[] args) {
        int[][] array = new int[][]{{1,2,3},{4,5,6}};
        System.out.println(Arrays.deepToString(array));
    }

打印结果: 

 跟打印一维的类一样,我们需要导入Arrays包才能调用该类

import java.util.Arrays;

最后,二维数组的用法跟一维数组没有明显的区别,因此就不再赘述了。

同理,还存在‘三维数组’‘四位数组’等更复杂的数组,只不过出现概率很低

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱躺平的威威

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值