JavaSE——数组详解

Java中数组的特性

Java中的数组有一些令人十分困惑的地方。在Java中,数组是引用数据类型,引用类型的数据被创建时,首先要在栈上给其引用(句柄)分配一块内存,而它的具体信息都存储在堆内存上,然后由栈上面的引用指向堆中对象的地址。而令人困惑之处在于:数组是引用数据类型,那它是不是对象呢?

我们从以下几点进行探讨:

  1. 要判断数组是不是对象,那么首先明确什么是对象,也就是对象的定义。在较高的层面上,对象是根据某个类创建出来的一个实例,表示某类事物中一个具体的个体。对象具有各种属性,并且具有一些特定的行为。而在较低的层面上,站在计算机的角度,对象就是内存中的一个内存块,在这个内存块封装了一些数据,也就是类中定义的各个属性,所以,对象是用来封装数据的。
    然而,在较高的层面上,数组不是某类事物中的一个具体的个体,而是多个个体的集合,那么它应该不是对象。而在计算机的角度,数组也是一个内存块,也封装了一些数据,这样的话也可以称之为对象。

  2. 在使用数组的引用变量时,我们可以用它来调用一些属性和方法:

    int[] arr = {1,2,3};
    System.out.println(arr.length); //3
    
    Class c = arr.getClass();
    System.out.println(c.getName());    //[I
    
    int[] arr2 = arr.clone();
    System.out.println(Arrays.toString(arr2));  //[1, 2, 3]
    
    System.out.println(arr.hashCode());
    

    可以发现,除了数组独有的length属性外,其他的所有方法都来自Object这个类,这说明数组继承了Object类,接下来验证我们的想法:

    int[] arr = {1,2,3};
    Object obj = arr;   //向上转型,可以,编译器没有报错,可以使用obj里面的方法。
    
    int[] arr2 = (int[]) obj;   //向下转型,也可以。
    
    System.out.println(obj instanceof int[])	//用instanceof判断obj是不是int[]类型,结果true
    

所以到这里,我们基本可以得到这样一个结论:数组是一个特殊的类型,有Class实例对象,继承了Object类的很多方法,只是这个类型显得比较奇怪。我们没有自己创建这个类,也没有在Java的标准库中找到这个类。这只能有一个解释,那就是虚拟机自动创建了数组类型,可以把数组类型和8种基本数据类型一样, 当做java的内建类型,基本数据类型也有自己的Class实例对象,包括void也有。这种类型的命名规则是这样的:

每一维度用一个[表示;开头两个[,就代表是二维数组。

[ 后面是数组中元素的类型(包括基本数据类型和引用数据类型)

在java语言层面上,arr是数组,也是一个对象,那么他的类型应该是int[],这样说是合理的。但是在JVM中,他的类型为[I。顺便说一句普通的类在JVM里的类型为 包名+类名,也就是全限定名。

看到了这里,你以为事情真的这么简单?不不不,来看一个例子:

public class Test01 {
    public static void main(String[] args) {
        String[] s = {"abc","123"};
        Object obj = s;     //obj指向s,没毛病,String[]是Object的子类,父类变量指向子类的引用
        Object[] objs = s;  //objs也可以指向s,这说明String[]也是Object[]的子类,这显然不符合Java的单继承

        //结果为java.lang.Object,我们通过String[]的Class对象得到它的父类的Class对象可以得到他的父类是Object,这说明String[]的直接父类就是Object
        System.out.println(s.getClass().getSuperclass().getName());
    }
}

既然String[]的直接父类是Object,那么对于Object[]类型的引用也可以指向String[]类型的对象,只可能是因为这是Java的一种特性,赋予我们的一种特权。只要有继承关系,都可以这么使用,比如:

Student[] s;
Person[] p = s;

当然对于多维数组,这种情况也奏效:

String[][] s = {{"123","abc"},{"456","def"}};
Object[][] objs = s2;

但是对于基本数据类型就不可以了:

//因为基本类型不是引用类型,Object不是它们的父类,在这里自动装箱不起作用
int[] i = {1,2,3};
Object[] objs1 = i;	//报错

//但是可以这么使用
Object[] objs2 = {1,'c',true};	//也就是Object类型的数组可以接受任意类型的值,包括基本数据类型,基本数据类型可以自动装箱

Java为什么会为数组提供这样一种语法特性呢?也就是说这种语法有什么作用?

所以这种特性主要是用于方法中参数的传递。Object数组中存放的就是原数组同维度的数据

public class Test01 {
    public static void main(String[] args) {
        String[] strs = {"123","456","abc"};
        test(strs);
    }
    /*
        如果不传递数组,而是依次传递各个值,会使方法参数列表变得冗长。如果使用具体的数组类型,如String[],那么就限定了类型,失去了灵活性。
        所以传递数组类型是一种比较好的方式。
     */
    public static void test(Object[] objects){
        for (Object object : objects) {
            System.out.println(object);
        }
    }
}

但是如果没有上面的数组特性(如果有两个类A和B,如果B继承了A,那么A[]类型的引用就可以指向B[]类型的对象),那么数组类型就只能通过Object类型接收,这样就无法在方法内部访问或遍历数组中的各个元素

为什么说同维度呢?这是什么意思?来看看下面这种情况:

public class Test04 {
    public static void main(String[] args) {
        String[][][] arr = {{{"123","abc"},{"456","def"}},{{"789","ghi"},{"kfj","yyd"}}};
        Object[] objects = arr;
        for (Object object : objects) {
            System.out.println(object);
        }
    }
}

傻眼了吧,还有这种操作???

输出的结果是:

[[Ljava.lang.String;@1b6d3586
[[Ljava.lang.String;@4554617c

其实这就是这个三维数组的第二维在内存中的 数据类型+哈希值,即三维数组就是一维数组,只不过三维数组中存放的不是具体的值,而是一组二维数组的引用。

所以对待数组,有时候我们要把它看为数组,有时候得把它看为对象,至于到底是数组还是对象,那就要看Java的设计者了。

为什么java要设计这样迷惑人的特性呢?太折磨人了吧!!!

一维数组

定义
//方式一(推荐):数据类型[] 数组名
int[] arr1;

//方式二:数据类型 数组名[];
String arr2[];
静态初始化
//方式一:数据类型[] 数组名 = {值1,值2,值3...};
int[] arr3 = {1,2,3};

//方式二:数据类型[] 数组名 = new 数据类型[]{值1,值2,值3...};
int arr4 = new int[]{1,2,3};
动态初始化
//数据类型[] 数组名 = new 数据类型[数组长度];
int[] arr5 = new int[5];

二维数组

定义
//数据类型[][] 数组名
int[][] arr1;
静态初始化
//方式一:数据类型[][] 数组名 = {{值1,值2,值3...},{值1,值2,值3...}...}
int[][] arr2 = {{1,2,3},{4,5,6}};

//方式二:数据类型[][] 数组名 = new 数据类型[][]{{值1,值2,值3...},{值1,值2,值3...}...}
int[][] arr3 = new int[][]{{1,2,3},{4,5,6}};
动态初始化
//方式一:数据类型[][] 数组名 = new 数据类型[第一维的长度][第二维的长度]
int[][] arr4 = new int[2][3];

//方式二:数据类型[][] 数组名 = new 数据类型[第一维的长度][],第二维在另行创建
int [][] arr5 = new int[2][];

Arrays工具类

转字符串
//一维数组转字符串
String toString(基本数据类型或者Object[] arr);

//多维数组转字符串
String deepToString(Object[] arr);
填充数组
//全部填充
void fill(int[] arr,int value);	

//部分填充,左闭右开
void fill(int[] arr,int fromIndex,int toIndex,int value);	
排序
//默认排序
void sort(基本数据类型或Object[] arr);

//根据指定排序器排序
<T> void sort(T[] a, Comparator<? super T> c);

//部分排序,左闭右开
void sort(int[] arr,int fromIndex,int toIndex);

//根据指定排序器排序,部分排序
<T> void sort(T[] a,int fromIndex,int toIndex,g Comparator<? super T> c);

//并行排序,我的理解是排序所有元素同时进行
void parallelSort(基本数据类型或Object[] arr);	
数组比较
//比较的是值
boolean equals(基本数据类型或者Object[] arr1,基本数据类型或者Object[] arr2);

//多维数组的比较
boolean deepEquals(Object[] arr1,Object[] arr2);	
数组拷贝
/*
	基本数据类型数组的拷贝,指定长度
	original表示原数组,newLength表示新数组的长度,不足的补默认值。
*/
基本数据类型[] copyOf(基本数据类型[] original,int newLength);

T[] copyOf(T[] original,int newLength);

/*
	基本数据类型数组的拷贝,指定范围,左闭右开
	original表示原数组
*/
基本数据类型[] copyOfRange(基本数据类型[] original,int fromIndex,int toIndex);
二分查找
//全局查找,元素必须有序
基本数据类型 binarySearch(基本数据类型[] arr,基本数据类型 key);

Object binarySearch(Object[] arr,Object key);

T binarySearch(T[] arr,T key,Comparator<? extends T> c);	//指定排序器,元素无需有序

//部分查找,元素必须有序
基本数据类型 binarySearch(基本数据类型[] arr,int fromIndex,int toIndex,基本数据类型 key);

Object binarySearch(Object[] arr,int fromIndex,int toIndex,Object key);

T binarySearch(T[] arr,T key,int fromIndex,int toIndex,Comparator<? extends T> c);	//指定排序器,元素无需有序
数组转列表
//如果arr是基本数据类型数组,asList会把这个数组的引用加入列表中,并不会对每个元素进行装箱操作,这时需要使用包装类。
List<T> asList(T... arr);
对数组元素进行指定运算
array = new int[]{3, 10, 4, 0, 2};

Arrays.parallelPrefix(array, (x,y)->(x+y)); //[3, 13, 17, 17, 19]

Arrays.parallelSetAll(array, (x)->(x*x)); 	//[0, 1, 4, 9, 16]

Arrays.setAll(array, (x)->(x%3));			//[0, 1, 2, 0, 1], 与parallelSetAll相比只是不并行
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值