Java:数组的定义和使用(万字解析)

目录

1. 数组的概念

2. 数组的基础知识

2.1 数组的创建

\1. 基础创建格式:

\2. 类似C语言的创建格式:

【错误的创建(初始化)格式】

2.2 数组的数据类型

2.3 数组的初始化 —— 两种方式

\1.动态初始化:(完全默认初始化)

\2. 静态初始化:

【注意事项】

3. 数组的使用

3.1 数组元素的下标访问

3.2 计算数组长度

3.3 循环遍历数组的2种方式

\1. for语句

\2. for – each语句

【两种遍历方式的区别】

4. 数组是引用类型(内存空间的底层知识)

4.1 JVM的内存区域划分

4.2 内存分配:基础类型的变量 和 引用类型的变量

4.3 数组大小可变吗?

4.4 “数组1 = 数组2”会发生什么?

4.5 空引用null

5. 数组 与 方法

5.1 数组作为方法参数

5.2 数组作为方法返回类型

6. Arrays的一些实用工具方法

6.1 数组转字符串

6.2 数组的拷贝

6.3 数组的排序

6.4 二分查找指定元素

6.5 判断两个数组是否相等

6.6 填充数组元素


1. 数组的概念

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

1. 数组中存放的元素其类型相同。

2. 数组的空间是连在一起的。

3. 每个空间有自己的编号,起始位置的编号为0,即数组的下标。

2. 数组的基础知识

首先要明确一点,数组在Java中属于引用类型,引用类型变量的创建都需要new出来。

2.1 数组的创建

\1. 基础创建格式:

T[ ] 数组名 = new T[N]; 

  • T:表示数组中存放元素的类型
  • T[ ]:表示数组的类型
  • N:表示数组的长度
  • 例如:

    1. int[] array1 = new int[10];       // 创建一个可以容纳10个int类型元素的数组
    2. double[] array2 = new double[5];  // 创建一个可以容纳5个double类型元素的数组
    3. String[] array3 = new String[3];  // 创建一个可以容纳3个字符串元素的数组

    数组的创建也可以类似C语言那样,把方括号[]写在变量名的右边。

    \2. 类似C语言的创建格式:

    T 数组名[ ] = new T[N];

  • T:表示数组中存放元素的类型
  • T[ ]:表示数组的类型
  • N:表示数组的长度
  • 例如:

    1. int array1[] = new int[10]; // 创建一个可以容纳10int类型元素的数组
    2. double array2[] = new double[5]; // 创建一个可以容纳5double类型元素的数组
    3. String array3[] = new String[3]; // 创建一个可以容纳3个字符串元素的数组

    【错误的创建(初始化)格式】

  • 没有new一个数组对象。
  • 将数组长度的定义写在了第一个方括号[ ]里面。
  • 例如:

    2.2 数组的数据类型

    注意:java中数组的数据类型是T[],而不是T[n]。

    【在C语言中,数组的数据类型是T[n]】

    比如现在有1个元素个数为3的整型数组“int[] arr = new int[3]”,那么该数组arr的数据类型是int[ ],而不是int[3]。

    2.3 数组的初始化 —— 两种方式

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

    \1.动态初始化(完全默认初始化)

    在创建数组时,直接指定数组中元素的个数N。创建后,从0到N-1的数组元素都被默认初始化。

    语法格式:T[ ] 数组名 = new T[N];

    例如:int[] array = new int[10];

  • 如果数组中存储元素类型为基础类型,默认值为基础数据类型对应的默认值,比如:
  • \u 是用来表示Unicode转义字符的前缀。它的格式为 \u 后面跟着四个十六进制数字,用于表示一个特定的Unicode字符。
  • 例如:\u0041 表示字符 A,因为 0041A 的Unicode编码。

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

  • \2. 静态初始化:

    在创建数组时不直接指定数据元素个数,而直接将具体的数据内容进行指定

    (完全格式)语法格式①:T[ ] 数组名 = new T[ ]{data1, data2, data3, …, datan};   (左边也可以写成“T 数组名[ ]”)

    (省略格式)语法格式②:T[ ] 数组名 = {data1, data2, data3, …, datan};    (左边也可以写成“T 数组名[ ]”)

    【这写法类似C语言的数组完全初始化,虽然看上去省去了new T[ ],但是编译器编译代码时还是会还原

    例如:

    格式1的静态初始化:

  • int[] array1 = new int[]{0,1,2,3,4,5,6,7,8,9};
  • String[] array3 = new String[]{"hell", "Java", "!!!"};
  • 格式2的静态初始化:

  • int[] array1 = {0,1,2,3,4,5,6,7,8,9};
  • double[] array2 = {1.0, 2.0, 3.0, 4.0, 5.0};
  • String[] array3 = {"hell", "Java", "!!!"};

  • 【注意事项】

  • 1.静态和动态初始化也可以分为两步,但是省略格式不可以。【类似C语言的初始化格式不能分成两步,而且这在C语言中也是不可以的】
  • int[] array1;
     array1 = new int[10];
     int[] array2;
     array2 = new int[]{10, 20, 30};
     // 注意省略格式不可以拆分, 否则编译失败
    // int[] array3;
     // array3 = {1, 2, 3};
  • 2. 在使用省略格式的静态初始化时,浮点数数组不能用float[ ]类型接收,整数无限制。(所以不太建议使用省略格式)
  • 3. 数组的初始化不能既是动态初始化,又是静态初始化。(同时也说明:Java中不能像C语言那样不完全初始化。)
  • 这里既直接指定了数组的大小是5(动态初始化),又用大括号来指定数组大小(静态初始化),所以报错了。

    3. 数组的使用

    3.1 数组元素的下标访问

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

    int[]array = new int[]{10, 20, 30, 40, 50};
     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]);
     
    // 也可以通过[]对数组中的元素进行修改
    array[0] = 100;
     System.out.println(array[0]);

    tips:在Java中,方括号 [] 确实用于访问数组中的元素,但它并不被称为“下标访问运算符”或者索引运算符。这与其他支持运算符重载的语言有所不同。Java将 [] 视为数组访问的直接语法,仅用于数组,并没有定义为运算符或允许重载。

    【注意事项】

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

    3.2 计算数组长度

    在数组中可以通过“ 数组对象.length ”来获取数组的长度。

    例如:

    输出:


    为什么可以这样获得数组的长度?(简单了解)

  • 我们注意到,我们用到了一个符号".",这个符号是成员访问运算符,用法就类似C语言中的结构体。Java中的对象就类似C语言的结构体(但会多了很多复杂的语法,比如对象中可以有方法)
  • 其实在Java中使用数组时,会自动导入“java.lang.reflect.Array”类。我们new的int[10]或String[4]等等,其实是new了(创建了)一个Array类型的对象。
  • 而Array类中有一个成员变量length,它记录着数组的长度。【可以简单得理解为C语言中的顺序表】

  • 3.3 循环遍历数组的2种方式

    \1. for语句

    这是最简单的方法,例如:

    int[] arr = new int[10];
    for(int i = 0; i < arr.length; i++){
        System.out.println(arr[i]);
    }

    \2. for – each语句

    for-each 是 for 循环的另外一种使用方式。

    语法格式:

    for (元素类型 变量名 : 数组名) {

            // 循环体

    }

    1. “元素类型”是数组中元素的类型
    2. “变量名”是在每次迭代中用来引用当前元素的变量
    3. “数组名”是要遍历的数组

    注意:迭代变量与数组名之间有一个冒号“ : ”

    例如:

    int[] arr = new int[10];
    for(int x: arr){
        System.out.println(x);
    }
  • arr数组中的每个元素都是int型,所以创建的变量x是int类型的。
  • 第一次循环中,x等于arr[0];第二次循环中,x等于arr[1]……以此类推。

  • 【两种遍历方式的区别】

    循环条件控制:

  • for语句中的循环条件由程序员编写,可以完成更复杂的要求,但也容易出错。
  • for-each语句不需要手动管理索引,使得代码的意图更加明确,减少了出错的可能性,但循环的条件不能手动改变。
  • 元素访问方式:

  • for循环中通过访问数组下标会直接对数组元素进行操作。
  • for-each循环通过迭代变量间接访问数组元素,即使发生错误操作也不会改变原数组的数据。
  • 4. 数组是引用类型(内存空间的底层知识)

    4.1 JVM的内存区域划分

    JVM对所使用的内存按照功能的不同进行了划分,主要分为5的区域:

  • 程序计数器 (PC Register): 只是一个很小的空间, 保存下一条执行的指令的地址。
  • 虚拟机栈(JVM Stack): 与方法调用相关的一些信息,每个方法在执行时,都会先创建一个栈帧,栈帧中包含有:局部变量表、操作数栈、动态链接、返回地址以及其他的一些信息,保存的都是与方法执行时相关的一 些信息。比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。(局部变量的存储、方法栈帧空间的开辟和销毁就在这里)
  • 本地方法栈(Native Method Stack): 本地方法栈与虚拟机栈的作用类似。只不过保存的内容是Native方法的局部变量在有些版本的 JVM 实现中(例如HotSpot), 本地方法栈和虚拟机栈是一起的。
  • 堆区(Heap): JVM所管理的最大内存区域。使用 new 创建的对象都是在堆上保存 ,堆是随着程序开始运行时而创建,随着程序的退出而销毁。在运行时,堆中的数据只要还有在使用,就不会被销毁
  • 方法区(Method Area): 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法编译出的的字节码就是保存在这个区域。
  • 补充一点:字节码是与平台无关的中间代码表示形式。字节码是Java平台核心特性之一,它的存在使得Java程序具备了高度的可移植性和灵活性。

    4.2 内存分配:基础类型的变量 和 引用类型的变量

    基本数据类型创建的变量,该变量空间中直接存放的是其所对应的数值; 而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址

    例如:

    public static void func() {
        int a = 10;
        int b = 20;
        int[] arr = new int[]{1,2,3};
     }
  • 在上述代码中,a、b、arr,都是函数内部的局部变量,因此其空间都在func方法对应的栈帧中分配。(即它们的空间在栈区上)
  • a、b是内置类型的变量,因此其空间中保存的就是给该变量初始化的值
  • array是数组类型的引用变量,其内部保存的内容可以简单理解成是数组在堆空间中的首地址,而数组的本体在堆区连续存放
  • 引用变量并不直接存储对象本身,可以简单理解成存储的是对象在堆中空间的起始地址。通过该 地址,引用变量便可以去操作对象。有点类似C语言中的指针,但是Java中引用要比指针的操作更简单。

    4.3 数组大小可变吗?

    先说结论:数组的大小是固定的,一旦创建无法改变其大小

    public static void main(String[] args) {
            int arr[] = new int[10];
            System.out.println(arr);
            arr = new int[5];
            System.out.println(arr);
        }

    可以发现,虽然我们用new的方式把数组arr的大小从10改变成了5,但前后两次arr存储的地址值不同了。

    这说明数组的大小是无法改变的,每次使用new时都会重新创建一个新的数组。

    4.4 “数组1 = 数组2”会发生什么?

    1. arr1 = arr2,相当于把arr2所引用的地址交给了arr1,所以现在arr1和arr2都指向了同一个数组。
    2. arr1现在改变了引用的对象,如果arr1原来所引用的数组没有被其他引用变量引用,则系统会自动回收该堆上的空间。

    【C语言中不支持“数组1 = 数组2”的语法】

    例如:

    public static void func() {
            int[] array1 = new int[]{10,20,30};
            int[] array2 = new int[]{1,2,3,4,5};
    
            int[] t = array1;
            array1 = array2;
            array2 = t;
            
            for(int x:array1){
                System.out.print(x+" ");
            }
            System.out.println();
            for (int x:array2){
                System.out.print(x+" ");
            }
        }

    运行结果:

    可以发现array1和array2所引用的对象成功被交换。

    【注意事项】

    在对象交换的过程中,数组array1、数组array2和临时变量 t 都必须是同类型的。(刚刚的例子中它们都是int[]类型的变量)

    4.5 空引用null

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

     int[] arr = null;
     System.out.println(arr[0]);
    
     // 执行结果
    Exception in thread "main" java.lang.NullPointerException
     at Test.main(Test.java:6)

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

    注意事项:

    1. 当引用类型的变量没有new一个对象的时候,必须要赋值null。

    2. null不能写成NULL,这是因为:

  • NULL是“ (void*)0 ”的宏定义,在Java中既没有宏定义的语法,也没有指针的语法。
  • 5. 数组 与 方法

    5.1 数组作为方法参数

    数组作为方法参数时,形参数组要与实参数组的数组类型一致

    看一个例子:

    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        func(arr);
        System.out.println("arr[0] = " + arr[0]);
     }
     
    public static void func(int[] a) {
        a[0] = 10;
        System.out.println("a[0] = " + a[0]);
     }
     
    // 执行结果
    a[0] = 10
    arr[0] = 10

    可以发现,在func方法内部修改数组的内容, 方法外部的数组内容也发生改变。

    与C语言类似:数组作为参数传递的也是数组的地址

    但不同的是:

  • C语言中的形参数组是一个“伪数组”,本质上是一个指针变量,所以形参的类型可以用指针类型来代替
  • 而Java中的形参数组是一个“真数组”,本质上是数组类型的引用变量,所以形参的类型不能用其他引用类型来代替。
  • 将数组的地址传入到函数形参中,这样可以避免对整个数组的拷贝(数组可能比较长, 那么拷贝开销就会很大)。

    5.2 数组作为方法返回类型

    数组作为返回值,一般都是在方法内部new了一个数组对象,然后返回该对象的地址。

    例如:

     public static int[] makeIntArray(int n){
            int[] arr = new int[n];
            return arr;
        }
    
     public static void main(String[] args) {
            int[] array = makeIntArray(10);
        }

    这种用法就类似C语言中,给顺序表中的数组用malloc函数或colloc函数创建数组一样。

    6. Arrays的一些实用工具方法

    Java为我们提供了一个专门处理数组的工具类Arrays,里面有一系列实用的方法。

    【注意】我们本章使用的工具类是Arrays类,不是Array类:

  • 数组类型:是Java语言的核心部分,没有特定的包路径。
  • Array:位于 java.lang.reflect 包中,主要用于反射机制中的数组操作。
  • Arrays:位于 java.util 包中,提供了更多实用的数组操作方法,适用于日常开发中的数组处理需求。
  • 它们三者都是引用类型,前者是数组类型,后两者属于类类型;三者均无包含关系。

  • 即使没有显式导入 Array 类,你仍然可以创建和操作数组,这是因为数组是Java语言的核心部分。
  • 不过Arrays需要显式导入“import  java.util.Arrays”
  • 6.1 数组转字符串

    toString方法可以把数组的信息转换为字符串。

  • 参数是要被转化数组
  • 返回类型是字符串。
  • 原数组的信息不会被修改。
  • 例如:

    public static void main(String[] args) {
            int[] arr = new int[]{1,2,3,4,5};
            String str = Arrays.toString(arr);//把数组转换为字符串,由字符串str接收
            //打印数组被转换后的结果
            System.out.println(str);
            //打印原数组
            for(int x: arr){
                System.out.print(x+" ");
            }
        }

    输出结果:


    模拟实现Arrays.toString

    public static String myToString(int[] arr){
            String str = "[";
            for(int i = 0; i < arr.length; i++){
                str += arr[i];
                if(i != arr.length - 1){  //最后一个元素不要加逗号
                    str += ",";
                }
            }
            str += "]";
            return str;
        }
    public static void main(String[] args) {
            int[] arr1 = new int[]{1,2,3,4,5};
            String str = myToString(arr1);
            System.out.println(str);
        }

    输出:

    6.2 数组的拷贝

    1. 两个参数的copyOf (数组名arr,拷贝长度N):

  • 在方法中新创建一个长度为N的数组,把arr中的数据拷贝到该方法中的数组,最后返回该数组的地址。
  • 从序号0一直拷贝到下标N-1处。
  • 如果长度N大于arr的大小,则用默认值填充
  • public static void main(String[] args) {
            int[] arr1 = new int[]{1,2,3,4,5};
            int[] arr2 = Arrays.copyOf(arr1,7);
            
            for(int x: arr2){
                System.out.print(x+" ");
            }
        }

    输出:


    2. 三个参数的copyOfRange(数组名arr,整数1,整数2):

  • 使用方式与上面的方法类似,区别是该方法是范围拷贝。
  • 拷贝范围是 [ 整数1,整数2 )。(左闭右开)
  • 拷贝的范围如果超出arr数组的右边,则用用默认值填充
  • 例如:

    public static void main(String[] args) {
            int[] arr1 = new int[]{1,2,3,4,5};
            int[] arr2 = Arrays.copyOfRange(arr1,3,7); //拷贝区间[3,7)
            
            for(int x: arr2){
                System.out.print(x+" ");
            }
        }

    输出:

    6.3 数组的排序

    使用sort方法可以为数组排序

    例如:

    public class Blog {
        public static void main(String[] args) {
            int[] arr = {9, 5, 2, 7};
            Arrays.sort(arr);
            System.out.println(Arrays.toString(arr));
        }
    }

    6.4 二分查找指定元素

    1. 两个参数的binarySearch (数组名arr,  要查找的元素key):

  • 返回值 是key所在的序号
  • 如果找不到该元素,则会返回一个负数。
  • 例如:

    public static void main(String[] args) {
            int[] arr = {0,1,2,3,4,5,6,7,8,9};
            int pos = Arrays.binarySearch(arr,5);
            System.out.println("要查找的元素位于序号"+pos);
        }


    二分查找方法的模拟

     public static int myBinarySearch(int[] arr, int key){
            int left = 0;
            int right = arr.length-1;
            while(left <= right) {  //如果left不能等于right的话,那么当key在最右边时,会误以为找不到该元素
                int mid = (left + right) / 2;
                if(arr[mid] > key){
                    right = mid - 1;//此时要找的数据在mid左边,right变成mid-1,则区间[left,right]就位于mid左边了
                }else if(arr[mid] < key){
                    left = mid + 1;//此时要找的数据在mid右边,left变成mid+1则区间[left,right]就位于mid右边了
                }else{
                    return mid;
                }
            }
            return -1;
        }
    public static void main(String[] args) {
            int[] arr = {0,1,2,3,4,5,6,7,8,9};
            int pos = myBinarySearch(arr,9);
            System.out.println("要查找的元素位于序号"+pos);
        }


    2. 四个参数的binarySearch (数组名arr, 左边界k1, 右边界k2, 要查找的元素key):

  • 效果与上一个方法类似,区别是在规定范围内查找,区间[ k1, k2 )(左闭右开)
  • 例如:

    public static void main(String[] args) {
            int[] arr = {0,1,2,3,4,5,6,7,8,9};
            int pos = Arrays.binarySearch(arr,6,9,9);//区间[6,9)
            System.out.println("要查找的元素位于序号"+pos);
            pos = Arrays.binarySearch(arr,6,10,9);//区间[6,10)
            System.out.println("要查找的元素位于序号"+pos);
        }

    6.5 判断两个数组是否相等

    使用equals(数组1,数组2)可以判断两个数组是否相等。

  • 相等返回true,不相等返回false。
  • 例如:

    public static void main(String[] args) {
            int[] arr = new int[]{1,2,3,4};
            int[] arr1 = new int[]{1,2,3,4};
            int[] arr2 = new int[]{1,2,3,3};
            System.out.println(Arrays.equals(arr, arr1));
            System.out.println(Arrays.equals(arr, arr2));
        }

    6.6 填充数组元素

    1. 两个参数的 fill (数组名arr,填充值m):

  • 用填充值m对数组arr中的所有元素进行赋值。
  • 该方法会直接操作数组arr。
  • 例如:

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


    2. 四个参数的 fill (数组名arr,左边界k1,右边界k2,填充值m):

  • 范围填充,区间是[ k1, k2 )
  • 例如:

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


    总的来说,数组作为Java编程中最基本且常见的数据结构之一,其重要性不言而喻。通过深入了解数组的特点、用法以及在实际应用中的优势,开发者可以更好地应用它来解决问题,提高代码的效率和可读性。


    本期分享完毕,谢谢大家的支持Thanks♪(・ω・)ノ

    作者:蟹至之

    物联沃分享整理
    物联沃-IOTWORD物联网 » Java:数组的定义和使用(万字解析)

    发表回复