Java 基础语法深度剖析 Java 中的数组含数组练习

Posted 吞吞吐吐大魔王

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 基础语法深度剖析 Java 中的数组含数组练习相关的知识,希望对你有一定的参考价值。

一、二维数组

和 C 语言一样,Java 中,二维数组本质上也就是一维数组,只不过每个元素又是一个一维数组

1. 创建数组

通过类比一维数组创建的方式,二维数组也有三种方式

  1. 静态初始化

    int[][] array = {{1, 2, 3}, {4, 5, 6}};
    
  2. 动态初始化

    int[][] array = new int[][]{{1, 2, 3}, {4, 5, 6}};
    
  3. 动态初始化

    int[][] array = new int[2][3];
    // 打印结果全为0
    

注意

在 Java 当中的二维数组不能省略行,但可以省略列。如

int[][] array = new int[2][];

但是这样如果不初始化,打印时会出现异常,因为没有列的话,相当于该二维数组的两个元素都为 null。

因此打印前要进行初始化,如

int[][] array = new int[2][];
array[0] = new int[4];
array[1] = new int[2];
System.out.println(Arrays.deepToString(array));
//打印结果为:[[0, 0, 0, 0], [0, 0]]

而这样的二维数组叫不规则二维数组,因为它的列是不确定的

2. 存储形式

和 C 语言不同,Java 中数组的存储是这样的

并且上图中 array 这个数组本身长度是2,并且每个元素又是一个一维数组

我们可以通过 array.length 验证长度为2的结论

int[][] array = {{1, 2, 3}, {4, 5, 6}};
System.out.println(array.length);
// 结果为:2

并且再用这个方式验证每个元素又是一个一维数组的结论

int[][] array = {{1, 2, 3}, {4, 5, 6}};
System.out.println(array[0].length);
System.out.println(array[1].length);
// 打印结果为
// 3
// 3

3. 使用方式

这里就讲介绍下遍历打印二位数组,至于二维数组的一些其他使用,其实都是建立在一维数组的基础上的,只要一维玩的溜,多维也飞起!

  1. 使用 for 循环遍历打印时

    int[][] array = {{1, 2, 3}, {4, 5, 6}};
    for(int i = 0; i < array.length; i++){
        for(int j = 0; j < array[0].length; j++){
            System.out.println(arrat[i][j] + " ");
        }
    }
    
  2. 使用 for-each 遍历打印时

    int[][] array = {{1, 2, 3}, {4, 5, 6}};
    for(int[] arr : array){
        for(int x : arr){
            System.out.println(x);
        }
    }
    
  3. 使用 Arrays.deepToString 打印

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

飞起

二、剖析 String[] args

你有没有想过每次我们用到的

public static void main(String[] args)

其中的 String[] args 是啥吗?我们可以知道它是一个字符串数组,那其中存放着什么呢?我们可以打印一下看看

public static void main(String[] args){
    System.out.println(Arrays.toString(args));
}
// 结果为:[]

结果啥都没有,哦豁!上述是我在 IDEA 中尝试的,现在我再在命令行中试试

  1. 新建一个 Java 文件的代码,将上述代码输入,并保存(注意别忘了导包)
  2. 在该文件目录下通过 Shift+鼠标右键 ,就可以找到 PowerShell,将其打开
  3. 通过 Javac 编译,java 运行该程序,结果为

人傻了,还是啥都没有

通过学习,如果我在运行时 java TestDemo 后面加上内容,如 Hello World! 就会有以下的结果

因为 main 函数里的 args 叫运行时命令行参数,如果运行java程序时,后面放了内容,就会将其存到该数组里面

三、数组练习

在训练今天的习题前,我们要理解什么是包。而在之前我们就已经用到过 java.util.Arrays

import java.util.Arrays;
public class TestDemo{
    public static void main(String[] args){
        int[] array = {1, 2, 3};
        String ret = Arrays.toString(array);
        System.out.println(ret);
    }
}
// 打印结果为:[1, 2, 3]

这其中使用到的 toString 就是上述包中的一个方法,它可以让数组变成字符串并返回一个字符串。

因此学会该包中和数组相关的常用方法,那数组的使用就可以更加舒服了!那么什么是包呢?

例如你吃的一碗热干面,如果一碗面从头到尾要你自己做的话,你需要先和面、擀面、扯面、再烧水、煮面、放调料等等。而大多数店铺其实它们做面只要煮面放调料就行了,因为制作面的这个繁琐的过程可以直接通过去超市买面来省去。

一个程序的开发也不一定要我们从头到尾去完成,我们实际上可以站在巨人的肩膀上。就比如 Java 中有大量的标准库和海量的第三方库提供我们使用,这里面的代码就放在一个个的

除此之外我们要知道

  1. 上述包中的 Arrays 意思是操作数组的工具类

  2. java.util 是 Arrays 所在的那个包的名字。大家可以去自己的 JDK\\jre\\lib\\rt.jar 中找到 util 这个包,里面包含了很多该包中已经封装好的字节码文件,要使用就导入对应的类就行

  3. 我们也可以直接使用 import java.util.*; ,它可以导入该包中全部的类。(**注意:**其实在使用它时并不会将该包中的全部类导入,而是你使用到了哪个类,它才会加载哪个类)

那么接下来我们将开始数组的练习,想要玩数组必须要掌握的基础的方法!

1. 数组转字符串

我们知道 toString 方法可以直接将数组变成字符串,那么我们怎么自己实现一个方法模拟 toString 呢?

public static String myToString(int[] array){
    if(array == null){
        return null;
    }
    if(array.length == 0){
        return "";
    }
    String str = "[";
    for(int i = 0; i < array.length; i++){
        if(i == 0){
            str +=array[i];
        }else{
            str +=", " + array[i];
        }
    }
    str += "]";
    return str;
}

其中我们要注意数组长度为0或者数组为 null 的情况

2. 数组拷贝(4种)

首先我们自己可以尝试写一个方法来拷贝数组

public static int[] copyArray(int[] array){
    int[] newArray = new int[array.length];
    for(int i = 0; i < array.length; i++){
        newArray[i] = array[i];
    }
    return newArray;
}

上述其实就是使用了 for 循环语句来进行遍历的拷贝,那么 Arrays 工具类中有没有更舒服的方式呢?

int[] array = {1, 2, 3};
int[] ret1 = Arrays.copyOf(array ,array.length);
System.out.println(Arrays.toString(ret1));
// 打印结果为:[1, 2, 3];

可以直接通过 copyOf(原数组,要返回的副本的长度) 方法来拷贝数组

如果副本长度大于原数组,相当于扩容,扩容的值默认为0

int[] array = {1, 2, 3};
int[] ret2 = Arrays.copyOf(array , 2 * array.length);
int[] ret3 = Arrays.copyOf(array , 2);
System.out.println(Arrays.toString(ret2));
System.out.println(Arrays.toString(ret3));
// 打印结果为:
// [1, 2 , 3, 0, 0, 0]
// [1, 2]

我们还可以通过 copyOfRange(原数组,起始索引值,终点索引值的后一个值) 方法来拷贝数组区间

int[] array = {1, 2, 3};
int[] ret4 = Arrays.copyOfRange(array ,1, 3);
System.out.println(Arrays.toString(ret4));
// 打印结果为:[2, 3]

除了上面所说的拷贝方式,我们还有其他方法。我们在 IDEA 上按住 Ctrl 键后点击 copyOf,跳转到它的源码,我们可以看到

通过比较我们自己创建的拷贝方法可以知道,中间这段代码(如下)就是拷贝的过程

System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));

我们再按住 Ctrl 键后点击 arraycopy,跳转到它的源码,我们可以看到

其中有一个 native,所以我们知道它是一个本地方法

而本地方法(native)具有以下特点

  • 本地方法由 C/C++ 代码实现
  • 运行速度快
  • 运行时是在本地方法栈上

通过它的参数我们可以猜测,它是从原数组(src)的起始位置(srcPos)开始,拷贝到目的数组(dest)的起始位置(destPos)开始且长度为(length)

我们可以敲个代码验证

int[] array = {1, 2, 3};
int[] ret5 = new int[array.length];
System.arraycopy(array, 0, ret5, 0, array.length);
System.out.println(Arrays.toString(ret5));
// 打印结果为:[1, 2, 3];

不过要注意这里的 length 不能超过原数组的长度,不然就会出现数组越界的异常

那么拷贝的方式就这样就结束了吗?NO!

我们还可以调用对象的 clone() 方法进行拷贝

int[] array = {1, 2, 3};
int[] ret6 = array.clone();
System.out.println(Arrays.toString(ret6));
// 打印结果为:[1, 2, 3];

至于克隆的过程其实可以用一张图描绘

以上总结了数组的四种拷贝方法。但是我们要注意一种代码

int[] array = {1, 2, 3};
int[] ret7 = array; 

这个根本就不是拷贝,因为 ret7 这个引用是指向 array 这个引用所指向的对象,如果 ret7 被改变, array 也会被改变

小结

上述介绍了拷贝的几种方法,但是拷贝其实还可以被分为深拷贝和浅拷贝。这里我简单的介绍一下

  • 深拷贝:(数组里面存的一般是基本数据类型)

如果将 array2[0] 改成 99,array1[0] 不会被影响,这种拷贝就是深拷贝

  • 浅拷贝:(数组里面存的是引用数据类型)

如图,加入原数组 array1[0] 存的是一个引用类型,它指向120这个对象。当数组拷贝后,array2[0] 这个引用也会指向120这个对象。如果将 array2[0] 所指向的对象改变,那么 array1[0] 所指向的对象也就改变了,这种就叫浅拷贝

  • 那如何对上述这种例子的浅拷贝怎么改成深拷贝呢?我们可以先将对象拷贝一份,然后再将拷贝后对象的地址拷贝到新数组中

  • 那么如何判断上述四种方法是深拷贝还是浅拷贝呢?一图让你理解明白

3. 找数组中的最大元素

这题就很简单啊,一首 for 循环送给你

public static void main(String[] args){
    int[] array = {1,6,2,0,8};
    System.out.println(maxNum(array));
}
public static int maxNum(int[] array){
    int max = array[0];
    for(int i = 1; i < array.length; i++){
        if(array[i] > max){
            max = array[i];
        }
    }
    return max;
}

4. 求数组中元素的平均值

这题也很简单,直接上代码

public static double avg(int[] array){
    int sum = 0;
    for(int x: array){
        sum += x;
    }
    return (double)sum/(double)array.length;
}

不够要注意返回的可能时浮点数

5. 查找数组中指定元素(顺序查找)

直接上代码

public static int findNum(int[] array, int x){
    for(int i = 0; i < array.length; i++){
        if(array[i] == x){
            return i;
        }
    }
    return -1;
} 

最后返回-1,是因为数组索引不可能为-1,其他非索引值也行,只是一个判断不存在的值

6. 查找数组中指定元素(二分查找)

这也直接上代码啦!不过使用二分的前提是该数组为有序的

public static int binarySearch(int[] array, int x){
    int left = 0;
    int right = array.length - 1;
    while(left <= right){
        int mid = (left + right) >> 1;
        if(array[mid] > x){
            right = mid -1;
        }else if(array[mid] < x){
            left = mid + 1;
        }else{
            return mid;
        }
    }
    return -1;
}

除了自己定义的方法,Arrays 类中也有一个 binarySearch 方法可以进行二分查找

public static void main(String[] args){
    int[] array = {1, 3, 7 ,9 ,10};
    System.out.println(Arrays.binarySearch(array, 9));
}

7. 检查数组的有序性

直接上自己写的方法

public static boolean isSorted(int[] array){
    for(int i = 0; i < array.length - 1; i++){
        if(array[i] > array[i+1]){
            return false;
        }
    }
    return true;
}

注意,遍历时 i 不能等于array.length,因为如果数组本身有界,则最后 array[i+1] 就会越界

8. 数组排序(冒泡排序)

这个题学 C 语言时也写烂了,直接上代码

public static void dubbleSort(int[] array){
    for(int i = 0; i < array.length - 1; i++){
        boolean flg = false;
        for(int j = 0; j < array.length - i - 1; j++){
            if(array[j] > array[j + 1]){
                int tmp = array[j];
                array[j] = array[ j 以上是关于Java 基础语法深度剖析 Java 中的数组含数组练习的主要内容,如果未能解决你的问题,请参考以下文章

Java抽象类和接口4000+字深度剖析

Java之hashCode与equals深度剖析与源码详解

用几张图深度剖析Java内存模型

用几张图深度剖析Java运行时数据区

Java基础语法(下)

JAVA热点基础大盘点<剖析字符串类>