java中数组的定义与使用

Java中的数组跟c语言的数组几乎不一样,我们要区分对待。在之后你就能理解到我为什么说这句话了。 

1.java中数组的创建与初始化

数组的创建

如下,皆为数组的创建。

double[] a;
int[] b;

创建时的[]里面绝对不能填数字。

 数组的初始化

主要分为动态初始化以及静态初始化。

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

int[] array = new int[10]; 

动态初始化只是分配了一个数组空间,里面的值并没初始化赋值,像平时如果创建一个变量没将其初始化就使用,Java是会直接报错的。

而在这数组里其值也没初始化,但系统却会自动帮助其数组给它们一个基础值。

如果数组中存储元素类型为基类类型,默认值为基类类型对应的默认值,比如:

 

如果数组中存储元素类型为引用类型(类型于c语言的指针),默认值为null  。

在动态初始化时,java语法允许可以new int[n]  :n为变量,这样就更加方便。

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

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

【注意事项】

静态初始化虽然没有指定数组的长度,编译器在编译时会根据{}中元素个数来确定数组的长度。

静态初始化时, {}中数据类型必须与[]前数据类型一致。

静态初始化可以简写,省去后面的new T[]。 本质还是一样的。

// 注意:虽然省去了new T[], 但是编译器编译代码时还是会还原
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", "!!!"};

但是简写在一些时候也是用不了的, 如我们数组的创建和初始化可以分成两步,在分为两步时,静态初始化的简写就用不了了。

int[] array1;
array1 = new int[10];
 
int[] array2;
array2 = new int[]{10, 20, 30};
 
// 注意省略格式不可以拆分, 否则编译失败
// int[] array3;
// array3 = {1, 2, 3};

数组也可以按照依照C语言创建数组的方法去创建,但不推荐,不要这么写 

/*
该种定义方式不太友好,容易造成数组的类型就是int的误解
[]如果在类型之后,就表示数组类型,因此int[]结合在一块写意思更清晰
*/
int arr[] = {1, 2, 3};

作者自己的思考理解

对于a和b这些数组名是引用变量,它们都是存地址的变量(数组是引用类型)。所以为了方便理解记忆,我们可以把[]理解成C语言的*,从而类型就是 double *或int *。

从而还可以这么理解,在初始化时,如new int[]{1,2}或着 new int[10]就在系统已经分配了一个数组空间,其还返回了这数组的最起始地址,从而让数组名(接收地址的变量)去接收,从而就创建了一个完整的数组。

不知道我这个思路理解是不是正确的,但这样确实更方便理解记忆。

 2.遍历数组

 第一种方法

这是第一种方法,很简单。值得注意的是 数组对象名.length就可以得到数组所含的元素个数 

 第二种方法

我们可以使用 for-each遍历数组,for-each就是一个加强版的for循环,其专门用在数组上(目前来看)。

其语法格式是这样。

for(数据类型 变量;数组名)
{}

其最开始是讲数组第一个元素赋值给变量。从而之后就是第二个元素赋值给变量。直到最后一个元素赋值给变量。然后就结束循环。

从而可以用该for-each循环遍历数组。代码如下:

int[] array = {1, 2, 3};
for (int x : array) {
    System.out.println(x);
}

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

3.数组是引用类型

初始JVM的内存分布

 

 程序计数器 (PC Register): 只是一个很小的空间, 保存下一条执行的指令的地址

虚拟机栈(JVM Stack): 与方法调用相关的一些信息,每个方法在执行时,都会先创建一个栈帧,栈帧中包含有:局部变量表、操作数栈、动态链接、返回地址以及其他的一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。

本地方法栈(Native Method Stack): 本地方法栈与虚拟机栈的作用类似. 只不过保存的内容是Native方法的局部变量等. 在有些版本的 JVM 实现中(例如HotSpot), 本地方法栈和虚拟机栈是一起的(native方法是使用其他语言如c/c++编写的方法,它可以在java程序中被调用),我们现在使用的方法创建的栈帧都是在虚拟机栈中。

堆(Heap): JVM所管理的最大内存区域. 使用 new 创建的对象都是在堆上保存 (例如前面的 new int[]{1, 2, 3} ),堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销毁。

在c语言中堆中申请的内存在使用完后要用free释放。而在java中当我们申请的内存没有引用类型引用时(可以理解为没指针指向其申请的内存区域),它就会自动销毁。

方法区(Method Area): 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. 方法编译出的的字节码就是保存在这个区域

现在我们只简单关心堆 和 虚拟机栈这两块空间,后序JVM中还会更详细介绍。

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

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

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

public static void func() {
    int a = 10;
    int b = 20;
    int[] arr = new int[]{1,2,3};
}

 再谈引用变量

public static void func() {
    int[] array1 = new int[3];
    array1[0] = 10;
    array1[1] = 20;
    array1[2] = 30;
 
    int[] array2 = new int[]{1,2,3,4,5};
    array2[0] = 100;
    array2[1] = 200;
 
    array1 = array2;
    array1[2] = 300;
    array1[3] = 400;
    array2[4] = 500;
    for (int i = 0; i < array2.length; i++) {
       System.out.println(array2[i]);
   }
}

 

 4.认识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.

注意: Java 中并没有约定 null 和 0 号地址的内存有任何关联.

4.数组的应用场景 

 保存数据 

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

作为函数的参数 

之前已经讲过这个作为函数的参数,但由于当时没学习数组,所以参数传数组类型并没很清楚的讲述,现在学了,就清楚的讲述一遍 

参数传基本数据类型

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);
}
 
// 执行结果
x = 10
num = 0

发现在func方法中修改形参 x 的值, 不影响实参的 num 值.

参数传数组类型(引用数据类型) 

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语言的指针就更好理解了,将int []看作int*不就一下子就理解了)  

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

作为函数的返回值 

在c语言中不存在将数组类型当作返回值类型处理,但java可以。(同理,将其看作指针就好理解了)

获取斐波那契数列的前N项就是一个很好的例子

public class TestArray {
    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 < n; ++i){
            array[i] = array[i-1] + array[i-2];
       }
 
        return array;
   }
 
    public static void main(String[] args) {
        int[] array = fib(10);
        for (int i = 0; i < array.length; i++) {
            System.out.println(array[i]);
       }
   }
}

5.数组练习

Java 中提供了 java.util.Arrays 包, 其中包含了一些操作数组的常用方法,里面就有sort方法,toString方法,full方法,equals方法。 

 现在我们先了解下fill方法和equals方法

fill 方法

public class one {
    public static void main(String[] args) {
        int[] a=new int[10];
        Arrays.fill(a,9);
        for(int x :a){
            System.out.println(x);
        }

在这代码中fill是将9全部填充到数组中 ,当然也可以部分填充,如下在中间添加了两个参数,从而就实现了部分填充。

public class one {
    public static void main(String[] args) {
        int[] a=new int[10];
        Arrays.fill(a,0,4,9);
        //第二个是起始位置,第三个是最终位置,左闭右开

        for(int x :a){
            System.out.println(x);
        }

 

 equals方法

 即使不用Arrays它也存在equals方法,之前讲过,那个是针对字符串去比较的。而Arrays中的equals方法是针对于数组去比较的。其区别如下

public class one {
    public static void main(String[] args) {
        int[] a=new int[10];
        int[] b=new int[10];

        System.out.println("das".equals("das"));//不属于Arrays里的
         System.out.println(Arrays.equals(a,b));//属于Arrays里的

所以两者存在区别,一个只适用于字符串,一个只适用于数组 。

1.数组转字符串

toString其参数类型为数组类型,返回值为字符串类型。所以能通过它将数组转为字符串类型。

import java.util.Arrays
 
int[] arr = {1,2,3,4,5,6};
 
String newArr = Arrays.toString(arr);
System.out.println(newArr);
 
// 执行结果
[1, 2, 3, 4, 5, 6]

使用这个方法后续打印数组就更方便一些.  

 2.数组拷贝

copyOf

该函数返回值为拷贝出的数组类型,所以需要用数组去接收。 

public class one {
    public static void main(String[] args) {
        int[]arr=new int[]{0,1,5,4};
     int[] arr2=  Arrays.copyOf(arr,arr.length);
       int[]arr3= Arrays.copyOf(arr,arr.length-1);
       int[]arr4= Arrays.copyOf(arr,arr.length*2);
       //拷贝的数组在堆区分配内存
       //当新数组长度等于原数组时,完全拷贝
        //当新数组长度小于原数组时,原数组会将最前面一部分拷贝到新数组中
    //当新数组长度大于原数组时,原数组会将其全部都拷贝到新数组中,新数组的其余部分为0(基础值)
       for(int x:arr2){
           System.out.print(x);
       }
        System.out.println();
        for(int x:arr3){
           System.out.print(x);
       }
        System.out.println();
        for(int x:arr4){
            System.out.print(x);
        }
    }
}

 

copyOfRange 

 同理返回值为数组类型,要用数组接收。

该函数作用是拷贝数组的某个范围。如下应该简而易懂。

public class one {
    public static void main(String[] args) {
        int[]arr=new int[]{1,4,6,8,5};
   int[]arr2= Arrays.copyOfRange(arr,2,5);
        for (int x:arr2
             ) {
            System.out.println(x);
        }
    }
}

 

 代码及文案

文案中提及拷贝分为深浅拷贝,其拷贝需要考虑深浅拷贝的问题。关于这个问题我们等到讲到接口时再讲 。

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

给定一个整型数组, 求平均值。(这个较简单,就直接看代码吧)

public static void main(String[] args) {
    int[] arr = {1,2,3,4,5,6};
    System.out.println(avg(arr));
}
 
public static double avg(int[] arr) {
    int sum = 0;
    for (int x : arr) {
        sum += x;
   }
    return (double)sum / (double)arr.length;
}
 
// 执行结果
3.5

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

很简单,就不详细讲述了,直接看文案

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

这个在c语言里也学过,这里就不过多讲述了,直接看文案。

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

之前在c语言里学过了,这里直接看文案,就不讲了。

 

 冒泡排序性能较低. Java 中内置了更高效的排序算法,其中sort用的就是更高效的排序算法。

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

sort还可以指定部分进行排序。如 

  Arrays.sort(a,0,6);

java中都是左闭右开,所以在这里是[0,6),从而是对数组中的下标为0到下标为5中的这部分进行排序。 

关于 Arrays.sort 的具体内部实现算法, 我们在后面的排序算法课上再详细介绍. 到时候我们会介绍很多种常见排序算法.

sort对数组是进行升序排列,sort并不能对数组进行降序排列 (如果要实现降序可以先用sort进行升序排列,再将数组逆序)

7.数组逆序 

这个很简单,在c语言中学过,这里直接看文案吧 

 6.二维数组

二维数组的内存图

 

 此时创建3个一维数组,这三个一维数组并不是连续分布的,三个一维数组分别有三个内存地址值,此时二维数组存放的就是这3个内存地址值。在二维数组通过3个地址值就可以找到3块空间,此时二维数组才算创建完毕,也会有一个对应的地址值(图上的0x0011),并把这个地址值赋值给arr。

此时如果输出arr[0]就会出现第一个一维数组的地址值,输出arr即输出二维数组的地址值。

所以说二维数组是个特殊的一维数组。

通过这java的二维数组的内存图也就能很好解释之后的二维数组代码了。

在c语言中二维数组的内存图也跟java的内存图差不多。(之前我对c语言二维数组的内存图理解有误,现在改正跟这个Java的内存图分布差不多,只是c语言数组是全部分布在栈区)

 二维数组的创建和初始化

这是二维数组的正常初始化 :分为三种,实则两种。

不规则的二维数组 

这是java特有的,c语言中二维数组不可能存在这种不规则的。 

public class one {
    public static void main(String[] args) {
        int[][] a = new int[3][]; //不规则二维数组必须要用这样的格式,列不能显示出来
//该代码先创建好存放一维数组地址的二维数组,子数组并没创建
        for(int i = 0; i < 3; i++)//再创建并初始化每个子数组
        {
            a[i] = new int[i + 1];
        }

    }//之后的代码就可以使用不规则的二维数组了,否则不能使用。

这就是不规则的二维数组。

个人对于二维数组的理解

int[][] arr=new int[4][3]

此时arr类型为int[][]  。[]可以理解为c语言的*,所以可以理解arr类型为int**,根据内存图不难发现arr是二维数组的地址,而二维数组存放的是存放整形的一维数组的地址,所以可以用int**表示.从而在java中arr类型是int[][].

同理, 还存在 "三维数组", "四维数组" 等更复杂的数组, 只不过出现频率都很低. 其道理跟二维数组是一样的(用二维数组可以推出来)

  • 28
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值