【Java SE语法篇】6.数组

简介: 【Java SE语法篇】6.数组


1.数组的基本概念

1.1 为什么使用数组?

假设现在要存储5个学生的年龄,按照之前掌握的知识点,我们会写出如下代码:声明5个变量存储学生变量

public class Test {
    public static void main(String[] args) {
        int age1;
        int age2;
        int age3;
        int age4;
        int age5;
    }
}

如果我们有10个学生呢?我们就要声明20个变量,似乎没有什么问题。那如果有100,1000个学生呢,我们就要声明100,1000个变量,这样就有点离谱了,使用数组我们就可以解决一个问题。

1.2 什么是数组

数组,是指一组类型相同的数据的集合,数组中每个数据称为元素。数组可以存放任意类型的元素,但同一个数组里存放的元素类型必须一致。数组分为一维数组和多维数组。

数组在内存中是一段连续的空间,比如现实中的车库:

在 Java中,包含6个整形类型元素的数组,就相当于上图中连在一起的6个车位,从上图中可以看到:

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

1.3 数组的创建和初始化

1.3.1 数组的创建

基本语法格式:

T[] 数组名 = new T[N];
  • T:表示数组中存放元素的类型
  • T[]:表示数组类型
  • N:表示数组的长度

代码示例:存储10个人的年龄

int[] ages = new int[10];

1.3.2 数组的初始化

Java 数组初始化主要分为静态初始化以及动态初始化

  1. 动态初始化:在创建数组时,直接指定数组中元素的个数
int[] ages = new int[10];
  1. 动态初始化:在创建数组是不直接指定数据元素个数,而直接讲具体的数据内容进行指定
    语法格式:
T[] 数组名 = {data1,data2,....data};
int[] ages = new {1,2,3,4,5};

【注意事项】

  • 静态初始化虽然没有指定数组的长度,编译器在编译时会根据{}中元素个数来确定数组的长度。
  • 静态初始化时, {}中数据类型必须与[]前数据类型一致。
  • 静态初始化可以简写,省去后面的new T[]。
int[] arr = {1,3,2,5,4};
// 注意:虽然省去了new T[], 但是编译器编译代码时还是会还原
  • 数组也可以按照如下C语言个数创建,不推荐
int arr[] = {1, 2, 3};
/*
该种定义方式不太友好,容易造成数组的类型就是int的误解
[]如果在类型之后,就表示数组类型,因此int[]结合在一块写意思更清晰
*/
  • 静态和动态初始化也可以分为两步,但是省略格式不可以。
public class Main {
    public static void main(String[] args) {
        int[] array1;
        array1 = new int[10];
        
        int[] array2;
        array2 = new int[]{10, 20, 30};
        
        // 注意省略格式不可以拆分, 否则编译失败
        //int[] array3;
        //array3 = {1, 2, 3};
    }
}
  • 如果没有对数组进行初始化,数组中元素有其默认值
  • 如果数组中存储元素类型为基类类型,默认值为基类类型对应的默认值,比如:
类型 默认值
byte 0
short 0
int 0
long 0
float 0.0f
double 0.0
char /u0000
boolean false
  • 如果数组中存储元素类型为引用类型,默认值为null

1.4 数组的使用

1.4.1 数组中元素访问

数组在内存中是一段连续的空间,空间的编号都是从0开始的,依次递增,该编号称为数组的下标,数组可以通过下标访问其任意位置的元素。比如:

public class Main {
    public static void main(String[] args) {
        int[] arr = new int[]{1,2,3,4,5};
        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]);
    }
}

【注意事项】:

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

    抛出了 java.lang.ArrayIndexOutOfBoundsException 异常. 使用数组一定要下标谨防越界.

1.4.2 遍历数组

所谓 “遍历” 是指将数组中的所有元素都访问一遍, 访问是指对数组中的元素进行某种操作,比如:打印。

public class Main {
    public static void main(String[] args) {
        int[] arr = new int[]{1,2,3,4,5};
        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]);
    }
}

上述代码可以起到对数组中元素遍历的目的,但问题是:

  1. 如果数组中增加了一个元素,就需要增加一条打印语句
  2. 如果输入中有100个元素,就需要写100个打印语句
  3. 如果现在要把打印修改为给数组中每个元素加1,修改起来非常麻烦。

通过观察代码可以发现,对数组中每个元素的操作都是相同的,则可以使用循环来进行打印。

1. 循环遍历数组

public class Main {
    public static void main(String[] args) {
        int[] arr = new int[]{1,2,3,4,5};
        for (int i = 0; i < 5; i++) {
            System.out.println(arr[i]);
        }
    }
}

改成循环之后,上述三个缺陷可以全部2和3问题可以全部解决,但是无法解决问题1。那能否获取到数组的长度呢?

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

public class Main {
    public static void main(String[] args) {
        int[] arr = new int[]{1,2,3,4,5};
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

2. 使用 for-each 遍历数组

语法格式:

public class Main {
    public static void main(String[] args) {
        int[] arr = new int[]{1,2,3,4,5};
        for (int x : arr) {
            System.out.println(x);
        }
    }
}

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

for-each循环语句的循环变量将会遍历数组中的每个元素,而不是下标值。

3. 数组转字符串输出

import java.util.Arrays;
public class Main {
    public static void main(String[] args) {
        int[] arr = new int[]{1,2,3,4,5};
        String ret = Arrays.toString(arr);
        System.out.println(ret);
    }
}

代码分析:

2.数组是引用类型

2.1 JVM 内存分布

内存是一段连续的存储空间,主要是用来存储程序运行时数据的。比如:

  1. 程序运行时代码需要加载到内存
  2. 程序运行产生的中间数据要存放在内存
  3. 程序中的常量也要保存
  4. 有些数据可能需要长时间存储,而有些数据当方法运行结束后就要被销毁。

如果对内存中存储的数据不加区分的随意存储,那对内存管理起来将会非常麻烦。比如:

因此 JVM 也对所使用的内存按照功能的不同进行了划分:

  • 程序计数器:只是一个很小的空间,保存下一条执行的指令的地址
  • 虚拟机栈:与方法调用相关的一些信息,每个方法在执行时,都会先创建栈帧,栈帧中包含有:局部变量表、操作数栈、动态链接、返回地址以及其他的一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。当方法运行结束后吧,栈帧就被销毁了,即栈帧中保存的数据也被销毁了
  • 本地方法栈:本地方法栈于虚拟机栈的作用类似,只不过保存的内容是方法的局部变量。在有些版本的 JVM 实现中,本地方法栈和虚拟机栈是一起的
  • 堆:JVM 所管理的最大内存区域,使用**new创建的对象都是在堆上保存,堆是随着程序开始运行时而创建,随着程序的结束而销毁,堆中的数据只要还有在使用,就不会被销毁**
  • 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法编译出的字节码就是保存在这个区域。

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

基本数据类型的变量,称为基本变量,该变量空间中直接存放的是其所对应的值;

而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址

public class Main {
    public static void main(String[] args) {
        int a = 10;
        int[] arr = new int[]{1,2,3};
    }
}

在上述代码中,aarr,都是函数内部的变量,因此其空间都在main方法对应的栈帧中分配。

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

arr是数组类型的引用变量,其内部保存的内容可以简单理解成是数组在堆空间中的首地址。

上图可以看出,引用变量并不直接存储对象本生,可以简单理解成存储的是对象在堆中空间的起始地址。通过该地址,引用变量便可以去操作对象。有点类似C语言中的指针,但是 Java 中引用要比指针的操作更简单。

2.3 引用变量

public class Main {
    public static void main(String[] args) {
        int[] arr1 = new int[3];
        arr1[0] = 1;
        arr1[1] = 2;
        arr1[2] = 3;
        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 x : arr1) {
            System.out.println(x);
        }
    }
}

2.4 认识 null

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

public class Main {
    public static void main(String[] args) {
        int[] arr = null;
        System.out.println(arr[0]);
    }
}

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

【注意】:Java 中并没有约定 null 和 0 下标地址的内存有任何关联。

3. 数组应用场景

3.1 保存数据

public class Main {
    public static void main(String[] args) {
        int[] arr = new int[]{1,2,3};
        for (int x : arr) {
            System.out.println(x);
        }
    }
}

3.2 作为方法的参数

  1. 参数传基本数据类型
public class Main {
    public static void main(String[] args) {
        int num = 0;
        func(num);
        System.out.println("num = " + num);// 0
    }
    private static void func(int x) {
        x = 10;
        System.out.println("x = " + x); // 10
    }
}
  1. 上述代码我们可以发现func方法中修改了形参x的值,不影响实参的num值。
  2. 参数传引用数据类型
public class Main {
    public static void main(String[] args) {
        int[] arr = new int[]{1,2,3};
        fun1(arr);
        System.out.println(Arrays.toString(arr)); // [1,2,3]
        
        fun2(arr);
        System.out.println(Arrays.toString(arr)); // [99,2,3]
    }
    public static void fun1(int[] arr) {
        arr = new int[]{11,22,33,44,55}; // 修改了形参的指向
    }
    public static void fun2(int[] arr) {
        arr[0] = 99; // 形参改变了实惨的值
    }
}
  1. 上述代码我们可以发现fun1方法中修改了形参的指向,不影响实参数组的值
    fun2方法内部修改了数组的内容,方法外部的数组内容也发生了改变。因为数组是引用类型,按照引用类型进行传递,是可以修改其中存放的内容的。

【总结】:所谓的“引用”本质只是存了地址。Java 将数组设定为引用类型,这样的话后续进行数组参数传参,其实只是将数组的地址传入函数形参中,这样可以避免对整数数组的拷贝(数组可能比较长,那么拷贝开销就会很大)。

3.3 作为方法的返回值

public class Main {
    public static void main(String[] args) {
        int[] ret = fun();
        System.out.println(Arrays.toString(ret)); // [1, 2, 3, 4, 5]
    }
    public static int[] fun() {
        int[] arr = new int[]{1,2,3,4,5};
        return arr;
    }
}

4. 二维数组

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

基本语法:

数据类型[][] 数组名称 = new 数据类型 [行数][列数] { 初始化数据 };

代码示例:

public class Main {
    public static void main(String[] args) {
        int[][] arr = {{1, 2, 3},{4,5,6}};
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr[i].length; j++) {
                System.out.print(arr[i][j] + " ");
            }
            System.out.println();
        }
        System.out.println("=======");
        for (int[] tempArr : arr) {
            for (int x : tempArr) {
                System.out.print(x + " ");
            }
            System.out.println();
        }
        System.out.println("=======");
        String ret = Arrays.deepToString(arr); // deepToString()深度打印
        System.out.println(ret);
    }
}

Java 二维数组在定义的时候是可以省略列的

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

二维数组的用法和一维数组并没有明显差别, 因此我们不再赘述.

同理, 还存在 “三维数组”, “四维数组” 等更复杂的数组, 只不过出现频率都很低.

5. 不规则数组

代码示例:

public class Main {
    public static void main(String[] args) {
        int[][] arr = new int[2][];
        // 每一个一维数组 进行初始化
        arr[0] = new int[3];
        arr[1] = new int[5];
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr[i].length; j++) {
                System.out.print(arr[i][j] + " ");
            }
            System.out.println();
        }
    }
}
// 运行结果
0 0 0 
0 0 0 0 0

6. OJ题

合并两个有序数组

删除有序数组中的重复项

相关文章
|
6天前
|
存储 Java 程序员
Java 数组
4月更文挑战第16天
|
15天前
|
存储 安全 Java
Java语法掌握:打好编程基础的关键(二)
Java语法掌握:打好编程基础的关键
52 0
|
15天前
|
存储 Java
Java语法掌握:打好编程基础的关键(一)
Java语法掌握:打好编程基础的关键
15 0
Java语法掌握:打好编程基础的关键(一)
|
29天前
|
Java
java 8 数组转字符串并以逗号分隔
java 8 数组转字符串并以逗号分隔
11 0
|
1月前
|
Java
【Java】数组中的拷贝方法与初步理解深浅拷贝
【Java】数组中的拷贝方法与初步理解深浅拷贝
13 0
|
1月前
|
存储 Java C语言
【Java】以数组为例简单理解引用类型变量
【Java】以数组为例简单理解引用类型变量
15 1
|
1月前
|
存储 Java 索引
Java数组
Java数组
7 0
|
1月前
|
Java
java中判断数组中元素出现的次数
java中判断数组中元素出现的次数
11 0
|
存储 安全 Java
Java8语法最佳实践-什么是对象(下)
计算机革命起源机器。编程语言就像是那台机器。它不仅是我们思维放大的工具与另一种表达媒介,更像是我们思想的一部分。语言的灵感来自其他形式的表达,如写作,绘画,雕塑,动画和电影制作。编程语言就是创建应用程序的思想结构。
84 0
|
存储 算法 Java
Java8语法最佳实践-什么是对象(上)
计算机革命起源机器。编程语言就像是那台机器。它不仅是我们思维放大的工具与另一种表达媒介,更像是我们思想的一部分。语言的灵感来自其他形式的表达,如写作,绘画,雕塑,动画和电影制作。编程语言就是创建应用程序的思想结构。
140 0
Java8语法最佳实践-什么是对象(上)