java基础教程:java数组

本文详细介绍了Java数组的声明、创建及使用方法,包括一维数组、多维数组的区别和注意事项,并探讨了数组与泛型的关系,以及Java数组实现背后的原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

两种声明方法

type[] arrayName; 
type arrayName[]; 

java数组是一种引用型的变量,所以,不要试图在声明时为数组指定大小。这点C++程序员要注意,因为C++的数组不加限定的化是在函数栈上分配的,而java则是在堆里分配的。

数组的创建

数组名 = new 数组元素的类型 [数组元素的个数]

如果需要在创建的时候为数组指定初始值,请使用{}

int[][][] b = {{{1,2,3},{1,2,3}},{{3,4,1},{2,3,4}}};

数组是对象吗?

java数组实际上是Array类的对象。Array类并没有public的构造方法,需要使用newInstance来构造新对象。如下两种写法是等价的

//way1
 Object array = Array.newInstance(int.class,2);

//way2
int[] anArray ;
anArray = new int[2];

所以,数组就是对象,只不过是声明和创建方式不同的对象罢了,这种不同是为了复合过去的编程习惯而已。

java数组的实现方法

java的数组是真数组。这意味着元素是在内存地址里顺序摆放的。但是,java会对下标越界进行检查。当然对象数组里顺序摆放的只是引用。

java多维数组

如果确实有需要,也可以使用多维数组

int num1[][]; //二维数组声明
int num1[][][]; //三维数组声明
...
m = new int[4][4];//二维数组创建

java二维数组实际上是创建了一个数组的数组,三维数组就是一个数组的数组的数组…

聪明的你一定立即会想到这里面有浅拷贝和深拷贝问题。在这里该问题不再赘述,只是总结三种深拷贝的方法

  • (1)使用for循环,将数组的每个元素复制(需要将每个对象调clone方法,才能实现真正的复制)
  • (2) 使用clone方法,得到数组的值,而不是引用
  • (3)使用System.arraycopy方法。

第一种方法最好理解,自己实现而已。第二第三种方法则只能实现一层的数组拷贝。也就是说他们设计上只是拷贝一维数组的。所以,你需要在每个一维数组使用该方法。一般认为arraycopy方法的效率最高。for循环效率最慢。这是因为arraycopy调用的是JNI方法。

数组与范型

//下面这句代码无法通过编译
Object[] objArray = new ArrayList<String>[10];
//下面这句话则可以
List<Integer>[] genericArray = (List<Integer>[])new ArrayList[10];

之所以禁止范型数组,是因为类型擦除后,编译器 ArrayList《String》 被擦除成为ArrayList《Object》,此时

如果你使用上述的第二种方式,那么如果你使用下的语句,就只有在实际运行时才能抛出异常了。

genericArray[0] = new ArrayList<String>(Arrays.asList(new String[]{"Hello"})); 

数组的协变

下面的代码是可以通过编译的。但是运行期抛出异常。(ArrayStoreException)

        Object[] objArray = new String[10];
        objArray[0] = Integer.valueOf(1);
        System.out.println(objArray[0]);

运行期,数组是记得自己内部元素的类型的。如果加入的元素不符合,就会抛出异常。但是编译器为什么允许这个代码通过编译呢?这被称为允许协变。effective java的作者明确说过,允许协变是java的一个败笔。不过,败笔也有败笔的理由。可以大胆猜测,java作者在构思java的时候受到了C/C++的影响,把数组设计成只能允许一种数据类型存储的结果(字节码中创建数组必须指定一个数据类型)。然而,等到他写比较两个数组中的元素是否相等时,发现了一个问题。你当然不能为每一个类型都实现对应的Arrays.equals方法!当时还没有范型,唯一的写法似乎就是如下

    public static boolean equals(Object[] a, Object[] a2) {
        if (a==a2)
            return true;
        if (a==null || a2==null)
            return false;

        int length = a.length;
        if (a2.length != length)
            return false;

        for (int i=0; i<length; i++) {
            Object o1 = a[i];
            Object o2 = a2[i];
            if (!(o1==null ? o2==null : o1.equals(o2)))
                return false;
        }

        return true;
    }

最简单的做法就是把所有对象数组类的父类都是Object[]类。这时候回想我们的里氏替换原则,所有子类实例都必须能替换父类实例,Object[]可以接受一切Object,凭啥你的Integer[]就要敢不接受Float呢?Integer[]肯定从Object[]那里继承了添加Object的方法嘛。

这么多年后,我们觉得最好解决问题的方式是范型。

    public static <T extends Object> boolean equals(T[] a, T[] a2) {
        if (a==a2)
            return true;
        if (a==null || a2==null)
            return false;

        int length = a.length;
        if (a2.length != length)
            return false;

        for (int i=0; i<length; i++) {
            T o1 = a[i];
            T o2 = a2[i];
            if (!(o1==null ? o2==null : o1.equals(o2)))
                return false;
        }

        return true;
    }

但是,在那个没有范型年代,这样的设计也实属无奈。而考虑到Arrays.equals这个方法好多年了,突然说原有调用方式不支持了,有多少代码(包含JDK代码)都需要重写。所以,这个问题也成为了java永远的痛。

数组与范型

下面的语句不能通过编译。

  List<String> a[]  = new LinkedList<String>[10]

下面的代码反而可以通过编译

        List<String> a[];
        a = new List[10];
        a[0] = new ArrayList<String>();
        System.out.println("finished");

为什么要这么设计的。。。我也不知道。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值