Java基础之数组

文章地址:Java基础之数组  关注码农爱刷题,看更多技术文章!!

【基本内容】

      数组是Java中四种引用数据类型之一,是若干具有相同数据类型的数据的有序集合;有序集合的每一项数据称之为数组元素,每一个数组元素可以通过数组下标来访问,数组下标从0开始,至数组长度-1结束。数组元素可以是基本数据类型,也可以是引用数据类型。

 一、数组的声明和初始化

       Java中,数组是通过符号标识[]来声明数组的,通常有以下两种方式:

// 基于基本数据类型的声明
int[] studentIds; // 方式1
int studentIds[]; // 方式2

//  基于引用数据类型的声明
String[] names;  // 方式1
String names[];  // 方式2

      数组的初始化有通常有静态初始化和动态初始化两种方式,如果一开始不确定数组元素的值,可采用动态初始化;具体参考下列代码:

// 静态初始化: 声明+创建+赋值一步到位
int heights = {173,168,201,199,178,188};

// 动态初始化
Integer dynamic = new Integer[3];
// 长度不能再改变       
// 此处未赋值,系统默认会初始化数据元素;
// 通常,基本数据类型会初始化其对应的默认值,而引用数据类型则初始化为null

 二、数组的内存分配

       数组是引用数据类型,因而它遵循引用数据类型的内存分配规则,其本身内容即数组元素存储在堆中,而其引用存储在栈中。对于数组元素,如果是基本数据类型的,则存储的是元素的值,但这时这个值是存储在堆中的;如果是引用数据类型的,则存储的是元素的地址值即引用,同样此时元素的引用也存储在堆中,只是它会指向元素在堆中的真正位置。具体看以下代码:


public class Student {
    
    private String name;
    
    public static void main(String[] args) {
    
        Student[] students = new Student[3];//引用数据类型
        students[0] = new Student();
        students[1] = new Student();
        students[2] = new Student();
        System.out.println(students);
        System.out.println(students[1]);       
    }    
}

-----------------Output:
[Lcom.jxopen.cloud.framework.common.action.test.Student;@2dda6444
com.jxopen.cloud.framework.common.action.test.Student@5e9f23b4

// 可以看出数组和数组元素输出的都是地址

      这种设计的好处是,无论是基本数据类型的数组还是引用数据类型的数组,都可以通过数组引用方便地访问和操作数组的内容,而不需要每次都复制整个数组或其内容。这也符合Java中对象和引用类型的内存管理原则。

三、数组的遍历和应用


// 普通for循环遍历
int[] a = {1, 2, 3, 4, 5, 6, 7, 8};
for (int i = 0; i < a.length; i++) {
    System.out.println(a[i]);
    //正常遍历出 a 这个数组内所有值
}


// for-each循环
int[] arrays = {1, 2, 3, 4, 5};
//JDK1.5特性  只遍历全部值,没有下标
for (int array : arrays) {  //增强型 for 循环,关键字 arrays.for 自动生成
    System.out.println(array);
}

//计算所有元素的和
int sum = 0;
for (int i = 0; i < arrays.length; i++) {
    sum += arrays[i];   //sum = sum + arrays[i]
}
System.out.println("sum=" + sum);
  
  

四、Arrays类的辅助操作

      由于数组对象本身并没有什么方法可以供我们调用,但JDK中提供了一个工具类Arrays供我们使用,从而可以让我们对数组对象进行一些基本操作。Arrays类中的方法都是static修饰的静态方法,在使用的时候可以直接使用类名进行调用,而不用使用对象调用,具体有以下常用功能:


//1、Arrays.toString方法
//将一维数组变成字符串。二维数组使用Arrays.deepToString方法。
int[] array=new int[]{1,2,3,4};
System.out.println(array);//[I@14514713
System.out.println(Arrays.toString(array));//[1, 2, 3, 4]

//2.1、Arrays.copyOf方法,
//注意:Arrays.copyOf是一个静态方法,它接受两个参数:要复制的原始数组和新数组的大小(这个长度可以超过旧数组的长度)。
//从0下标来复制指定长度数组的内容到新的数组中。
int[] array1=Arrays.copyOf(array,2);
System.out.println(Arrays.toString(array1));//[1,2]

//2.2、Arrays.copyOfRange方法
//和Arrays.copfOf方法类似,这个是指定范围的拷贝。范围是左闭右开区间。
int[] array2=Arrays.copyOfRange(array,2,4);
System.out.println(Arrays.toString(array2));//[3, 4]

//3、Arrays.sort方法
//对数组进行升序排序。
int[] ret={2,1,4,3};
Arrays.sort(ret);
System.out.println(Arrays.toString(ret));//[1, 2, 3, 4]

//4、Arrays.fill方法
//对数组内容进行指定填充。
int[] ret1=new int[10];
Arrays.fill(ret1,6);
System.out.println(Arrays.toString(ret1));//[6, 6, 6, 6, 6, 6, 6, 6, 6, 6]
Arrays.fill(ret1,1,3,8);//左闭右开
System.out.println(Arrays.toString(ret1));//[6, 8, 8, 6, 6, 6, 6, 6, 6, 6]

// 5、Arrays.equal方法
//判断两个数组的内容是否相同
int[] ret2={6, 8, 8, 6, 6, 6, 6, 6, 6, 6};
boolean equals = Arrays.equals(ret1, ret2);
System.out.println(equals);//true

// 6、Arrays.binarySearch方法
//二分查找数组内容,使用前先sort排序数组。
int[] ret3={4,1,3,2};
//先进行从小到大排序
Arrays.sort(ret3);
//二分查找
int i = Arrays.binarySearch(ret3, 3);
System.out.println(i);//返回索引2


// 7. Arrays.asList
// 数组转换为List
String [] strs = new String[]{"1","2","3"};
List strList = Arrays.asList(strs);
System.out.println(strList);//[1, 2, 3]

  五、数组的优劣

      数组是有序的集合,这个有序是指创建一个数组时,JVM会为这个数组分配一块连续的内存空间,并且你可以通过索引直接访问数组中的元素。这个索引机制就是保证数组有序的方式之一,也是数组元素能被高效访问的原因,同时也是数组的主要优势。

      此外,相对Java集合框架的集合类,数组还有一个优势,就是数组的元素可以是基本数据类型,而集合类则只能是引用数据类型;如果要进行简单数值运算例如求和,集合类需要通过自动装箱和拆箱功能才能进行,但是正如前面文章所说自动装箱和拆箱功能是有性能代价的,在大规模简单数值运算过程中,数组的优势就会很明显,如下代码:


Long time1 = System.currentTimeMillis();
for(int i = 0 ; i < 100000000 ;i++){
   sum += arrays[i%10];
}
Long time2 = System.currentTimeMillis();
System.out.println("数组求和所花费时间:" + (time2 - time1) + "毫秒");
Long time3 = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
   sum  += list.get(i%10);
}
Long time4 = System.currentTimeMillis();
System.out.println("List求和所花费时间:" + (time4 - time3) + "毫秒");
--------------Output:
数组求和所花费时间:696毫秒
List求和所花费时间:3498毫秒

          至于的数组的劣势如下:

  1. 大小固定:一旦创建,数组的大小就是固定的,不能更改。

  2. 类型固定:数组只能存储同一类型的元素。

  3. 不易扩展:不能直接增加数组的大小,需要创建一个更大的数组并复制现有元素。

  4. 效率问题:在数组的中间位置插入或删除元素效率低下,因为需要移动大量元素

【注意事项】

1. 数组的继承关系这个可能是工作中容易被大家忽略的问题,如果不是有人特别提起;数组虽然和类不同是另一种引用数据类型,但实际上它继承Object,看下列代码:

public class Test {
    public static void main(String[] args) {
        int[] array = new int[10];
        System.out.println("array的父类是:" + array.getClass().getSuperclass());
        System.out.println("array的类名是:" + array.getClass().getName());
    }
}
-------Output:
array的父类是:class java.lang.Object
array的类名是:[I


public class Test {
    public static void main(String[] args) {
        int[] array_00 = new int[10];
        System.out.println("一维数组:" + array_00.getClass().getName());
        int[][] array_01 = new int[10][10];
        System.out.println("二维数组:" + array_01.getClass().getName());
        
        int[][][] array_02 = new int[10][10][10];
        System.out.println("三维数组:" + array_02.getClass().getName());
    }
}
-----------------Output:
一维数组:[I
二维数组:[[I
三维数组:[[[I

      从上述代码中可以看出数组直接继承于java.lang.Object,它的类名很特别是[I,没有包路径限定,也不符合标识命名规则和规范,这也更印证了数组是和类不一样的引用数据类型。

2. 数组的边界数组下标的合法区间:[0, length-1],如果越界就会报错 ArrayIndexOutOfBoundsException(数组下标越界异常),例如以下代码:


public static void main(String[] args) {
        //静态初始化:创建的同时赋值
        int[] a = {1, 2, 3, 4, 5, 6, 7, 8};
        for (int i = 0; i <= a.length; i++) {
            System.out.println(a[i]);
            //or 循环走到 i <= a.length 时,下标为 8 (a[8]),a(8)未赋值且超过数组最大长度因此会越界报错
        }
 }       

3.数组拷贝的问题:Arrays类提供了copyOf方法进行数组之间的拷贝,但是当数组元素是引用数据类型时,使用该方法可能需要注意一下以下场景,参看代码:

public class Test {

      public static void main(String[] args) {
        Person person_01 = new Person("chenssy_01");
        
        Person[] persons1 = new Person[]{person_01};
        Person[] persons2 = Arrays.copyOf(persons1,persons1.length);
        
        System.out.println("数组persons1:");
        display(persons1);
        System.out.println("---------------------");
        System.out.println("数组persons2:");
        display(persons2);
        //改变其值
        persons2[0].setName("chessy_02");
        System.out.println("------------改变其值后------------");
        System.out.println("数组persons1:");
        display(persons1);
        System.out.println("---------------------");
        System.out.println("数组persons2:");
     } 
         
     public static void display(Person[] persons){
        for(Person person : persons){
            System.out.println(person.toString());
        }
    }
}
-------------Output:
数组persons1:
姓名是:chenssy_01
---------------------
数组persons2:
姓名是:chenssy_01
------------改变其值后------------
数组persons1:
姓名是:chessy_02
---------------------
数组persons2:
姓名是:chessy_02

    上面的代码,当拷贝后生成新的数组后,修改原数组的值,新数组的元素值也跟着改变;这是因为Arrays.copyOf()方法产生的数组是一个浅拷贝,而数组的元素保存的是元素的引用地址,所以原数组的值一旦修改,新数组对应元素的值也会跟着改变。如果我们希望拷贝前后的数组要独立使用,那么浅拷贝是一个隐患,需要我们自己去实现深度拷贝。

4. 数组转换的问题:还是Arrays类,它提供了asList()方法用于数组转换为List集合;但是在使用这个方法的过程中,可能会产生一些诡异的结果让人困惑,看下面的代码:


int[] datas = new int[]{1,2,3,4,5};
 List list = Arrays.asList(datas);
 System.out.println(list.size());// 输出的 1

 String [] strs = new String[]{"1","2","3"};
 List strList = Arrays.asList(strs);
 System.out.println(strList.size()); // 输出的是3

 都是数组转换为List,为什么datas数组转换后的list的长度是1,而不是和datas的长度一致为5呢?而strs数组转换前后长度却一致呢?这是因为Arrays.asList()方法的入参是一个泛型,如下:

 public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

     注意这个参数:T…a,这个参数是一个泛型的变长参数,我们知道Java中基本数据类型是不可能泛型化的,而数组是引用数据类型,它是可以泛型的,所以编译器把上面的int型的数组作为了T的入参类型,而此时传递进来的int[]数组只有一个而不是多个,所以在转换之后list中就只会存在一个类型为int数组的元素了,List长度也自然就是1。而数组strs的元素是引用数据类型,每个元素都可以泛型,编译器则会把T的入参类型识别为多个字符串传入,转换后长度自然也就不会有问题。所以要避免上述隐患,对要转换的基本数据类型数组应该使用其对应的包装类作为数组元素类型。

     Arrays.asList()方法带来的另一个问题是,当要对转换后的list集合进行增删元素时,系统在运行时会抛出异常,例如下面代码:

String [] strs = new String[]{"1","2","3"};
 List strList = Arrays.asList(strs);
 strList.add("5");
 
 //一旦运行,会抛出以下异常:
 Exception in thread "main" java.lang.UnsupportedOperationException
  at java.util.AbstractList.add(AbstractList.java:148)
  at java.util.AbstractList.add(AbstractList.java:108)
  at com.jxopen.cloud.framework.common.action.TestAction1.main(TestAction1.java:42)
  

       这是为什么呢?这是因为 Arrays.asList()方法返回的ArrayList并不是我们日常使用的java.util.ArrayList,而是Arrays类的一个内部类,这个类同样实现了List接口,但是它底层是一个用final修饰的不可变列表,因而不能对其增删元素,其在Arrays类中声明如下:

 private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
{
        private static final long serialVersionUID = -2764017481108945198L;
        private final E[] a;

        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }
        
        ......
 }      

 Jdk8以后,可以采用stream去转换创建新的集合,能避免转换后不能增删元素的问题。如:

List<String> list = Stream.of(array).collect(Collectors.toList());

5. 多维数组前述都是以一维数组为例介绍的,但数组是可以多维的;一维数组在数据结构上是线形结构,而关于多维数组后续再另行结章说明。

码农爱刷题

为计算机编程爱好者和从业人士提供技术总结和分享 !为前行者蓄力,为后来者探路!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值