在Java中,除了八种基本数据类型外,其他都属于引用类型,包括上篇文章中的 String 和 下面要学习的数组。 在日常开发中,数组也是我们经常会使用的,下面就来分析一下它的用法和集合的区别。
二、 数组与容器 1、在 Java 中,数组和容器都可以持有对象,那么,数组与容器的区别是什么呢?当我们需要持有对象时,我们在什么情形下应优先选用数组,而又在什么情况下优先选用容器呢?在 Java 的初始版本中,尺寸固定的数组绝对是必需的,不仅因为 Java 设计者选择在 Java 中要包含基本类型和一些基于性能方面的考量,而且还因为初始版本对容器的支持非常少。因此,在 Java 早期版本中,选择包含数组总是合理的,具体表现在以下三方面:
(1)、效率在 Java 中,数组是一种效率最高的存储和随机访问对象引用序列的方式。数组就是一个简单的线性序列,
这使得元素访问非常快速。但是,为这种速度所付出的代价是数组对象的大小被固定,并且在其生命周期中不可改变。
由于 ArrayList 可以实现空间的自动分配并且更为灵活,所以,我们通常应首选 ArrayList 而不是数组,但这种弹性需要开销。
因此,ArrayList 的效率比数组低的多。
(2)、类型
在 JDK 1.5 之前,Java 并未引进泛型。所以,泛型之前的容器类在处理对象时,都将它们视作没有任何具体类型,
即将这些对象都当作 Object 来处理(所以每次都需要强制类型转换,很费事)。数组之所以优于泛型之前的容器,
就是因为你可以创建一个数组去持有某种具体的类型。这意味你可以通过编译期的类型检查来防止插入错误类型和抽取不当类型。
当然,无论在编译时还是运行时, Java都会阻止你向对象发送不恰当的消息。所以并不是说哪种方法更不安全,
只是若编译时就能够指出错误,会显得更为优雅。
(3)、保存基本类型的能力
数组可以持有基本类型,而泛型之前的容器则不能。
在 JDK 1.5 之后,Java 引进了泛型和自动包装机制(泛型可以保证产生类型安全的容器,自动包装机制使容器可以持有基本类型),
这使得现在的容器在除了性能之外的各个方面都使得数组相形见绌。
另外,泛型对数组是极大的威胁,而且通常状况下,二者不能很好地结合(不能实例化具有参数化类型的数组)。
因此,当我们使用最近的Java版本编程时,应优选容器而非数组。只有在已证明性能成为问题并且切换到数组对性能提高有所帮助时,我们才应该将程序重构为使用数组。
三、 数组的创建与初始化 1、数组(1)、数组标识符只是一个引用,指向在堆中创建的一个真实对象,这个堆中的对象用以保存指向其他对象的引用或基本类型的值;(数组的内容)
(2)、对象数组保存的是引用,基本类型数组直接保存基本类型的值;
(3)、"[ ]" 语法是访问数组对象的唯一方式;
2、创建与初始化 (1)、创建方式 一:作为数组初始化的一部分隐式创建String[] strs = { ... }; // 花括号内对象的个数就是数组大小
System.out.println(strs.length); // 输出 0
(2)、创建方式 二:使用 new 表达式显式创建
String[] strs = new String[5];
(3)、创建方式 三:
String[] ss = new String[]{ ... };
四、数组与泛型
(1)、数组与泛型不能很好的结合,也就是说,不能实例化具有参数化类型的数组 ;
T[] first = new T[3]; // ERROR
A[] arrays = new A[4]; // ERROR: Cannot create a generic array of A
(2)、可以创建泛型数组引用 ;
A[] arrays; // OK
(3)、数组是协变的 ;
Object[] objs = new String[3]; // OK
总之,泛型容器总是比泛型数组更好的选择。
五、数组与可变参数类型 1、可变参数类型概念Java SE5 添加了可变参数类型 ( Variable Argument Type ),形式为 “Type… args”,只可用作方法的参数。可变参数列表适用于参数个数不确定但类型确定的情形 ( Java 把可变参数当做数组处理 )。特别需要注意的是,可变参数列表必须位于最后一项 (即最多只支持一个可变参数)。当可变参数列表个数多余一个时,必将有一个不是最后一项,所以只支持一个可变参数。因为可变参数列表的参数个数不确定,所以当其后边还有相同类型参数时,Java 无法区分传入的参数属于前一个可变参数还是后边的参数,所以只能让可变参数位于最后一项。
// 代码示例
public class TestVarArgus {
public static void dealArray(int... intArray){
for (int i : intArray)
System.out.print(i +" ");
System.out.println();
}
public static void main(String args[]){
dealArray();
dealArray(1);
dealArray(1, 2, 3);
}
}
/* Output:
1
1 2 3
*///:~
2、可变参数列表具有以下特点:
(1)、只能出现在方法参数列表的最后;
(2)、"…" 位于变量类型和变量名之间,前后有无空格都可以;
(3)、调用可变参数所在的方法时,编译器会为该可变参数隐式创建一个数组,从而我们可以在方法体中以数组的形式访问可变参数列表 (编译器把可变参数当做数组进行处理)。
3、可变参数类型与数组的兼容性 (1)、编译器认为数组类型和可变参数类型是相同的,即二者不能重载; public class TestVarArgus {
public static void dealArray(int... intArray){
for (int i : intArray)
System.out.print(i +" ");
System.out.println();
}
//ERROR : Duplicate method dealArray(int[]) in type TestVarArgus
public static void dealArray(int[] intArray){
for (int i : intArray)
System.out.print(i +" ");
System.out.println();
}
public static void main(String args[]){
dealArray();
dealArray(1);
dealArray(1, 2, 3);
}
}
(2)、可变参数是兼容数组类型参数的,但是数组类型参数却无法兼容可变参数;
// 代码示例 1 : 给参数为可变参数类型的方法传递数组
public class TestVarArgus {
public static void dealArray(int... intArray){
for (int i : intArray)
System.out.print(i +" ");
System.out.println();
}
public static void main(String args[]){
int[] intArray = {1, 2, 3};
dealArray(intArray); // OK
}
}
// 代码示例 2 : 给参数为数组类型的方法传递可变参数
public class TestVarArgus {
public static void dealArray(int[] intArray){
for (int i : intArray)
System.out.print(i +" ");
System.out.println();
}
public static void main(String args[]){
dealArray(1, 2, 3); // ERROR
}
}
其实,对于示例代码 2 而言,只是需要一个定义为 dealArray(int, int, int)的方法或者一个定义为 dealArray(int… )的方法。所以,自然就无法去匹配具有数组类型的参数 dealArray( int[] ) 方法了。
(3)、参数匹配原则:能匹配定长的方法,那么 优先 匹配该方法 ; public class TestVarArgus {
//含有不定参数的那个重载方法是最后被选中的
public static void dealArray(int... intArray){
System.out.println("Bad");
}
public static void dealArray(int count, int count2){
System.out.println("Bingo");
}
public static void main(String args[]){
dealArray(1, 2);
}
}
/* Output:
Bingo
*///:~
就连我们耳熟能详的 main 函数的参数也能改写成可变参数类型的形式:public static void main(String… args) .
六、Arrays 工具类的实用功能 1、复制数组 函数说明备注System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)见下文若复制对象数组,那么只是复制了对象的引用,而不是对象本身的拷贝(浅复制);该方法不会执行自动包装和自动拆包,所以两个数组必须具有相同的确切类型;须明确自行新建立一个数组对象,作为副本copyOf(T[] original, int newLength)复制指定的数组,截取或用 null 填充底层调用的还是 System.arraycopy;返回一个新的数组对象,若新数组的长度超过原数组的长度,则保留数组默认值public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
代码解释:
Object src : 原数组
int srcPos : 从元数据的起始位置开始
Object dest : 目标数组
int destPos : 目标数组的开始起始位置
int length : 要copy的数组的长度
注意:对于以上两个方法:
(1)、若复制对象数组,那么只是复制了对象的引用,而不是对象本身的拷贝;
(2)、这两个方法不会执行自动包装和自动拆包,所以两个数组必须具有相同的确切类型。
2、数组的字符串方式表示 方法: Arrays.toString(Object[] a) 或 Arrays.deepToString(Object[] a)
作用: 返回指定数组内容的字符串表示形式:前者适用于一维数组,后者适用于多维数组
3、数组的比较方法: Arrays.equals(Object[] a, Object[] a2) 或 deepEquals(Object[] a1, Object[] a2) (多维)
作用: 比较两个数组:元素类型相同,元素个数相等,对应位置的元素相同;
注意:
- 通过对每个元素使用 equals() 作比较来判断;
- 对于基本类型,使用的是基本类型的包装器类的 equals() 方法(对于 int 类型使用 Integer.equals() 作比较);
- 使用 equals() 方法比较原则:是不是同一个对象,是不是同一个类型,是不是具有相同的内容。
int[] a1 = new int[10];
int[] a2 = new int[10];
Arrays.fill(a1, 47);
Arrays.fill(a2, 47);
print(Arrays.equals(a1, a2)); //true
4、数组的排序
使用内置的排序方法,就可以对任意的基本类型数组排序;也可以对任意的对象数组进行排序,只要该对象实现了 Comparable 接口或具有相关联的 Comparator (独立的实现该接口的类)。
方法:
Arrays.sort(Object[] a)
Arrays.sort(Object[] a, int fromIndex, int toIndex)
Arrays.sort(T[] a, Comparator
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?