概述
数组
- 前言
- 数组的特性
- 显示数组的实用程序
- 一等对象
- 返回数组
- 多维数组
- 泛型数组
- Arrays工具类
- 数组拷贝
- 数组比较
- 流和数组
- 数组排序
- binarySearch二分查找
- parallelPrefix并行前缀
- 本章小结
前言
简单来看,数组需要你去创建和初始化,你可以通过下标对数组元素进行访问,数组的大小不会改变。大多数时候你只需要知道这些,但有时候你必须在数组上进行更复杂的操作,你也可能需要在数组和更加灵活的 集合 (Collection)之间做出评估。因此本章我们将对数组进行更加深入的分析。
数组的特性
将数组和其他类型的集合区分开来的原因有三:效率,类型,保存基本数据类型的能力。在 Java 中,使用数组存储和随机访问对象引用序列是非常高效的。数组是简单的线性序列,这使得对元素的访问变得非常快。然而这种高速也是有代价的,代价就是数组对象的大小是固定的,且在该数组的生存期内不能更改。
速度通常并不是问题,如果有问题,你保存和检索对象的方式也很少是罪魁祸首。你应该总是从 ArrayList 开始,它将数组封装起来。必要时,它会自动分配更多的数组空间,创建新数组,并将旧数组中的引用移动到新数组。这种灵活性需要开销,所以一个 ArrayList 的效率不如数组。在极少的情况下效率会成为问题,所以这种时候你可以直接使用数组。
数组和集合(Collections)都不能滥用。不管你使用数组还是集合,如果你越界,你都会得到一个 RuntimeException 的异常提醒,这表明你的程序中存在错误。
在泛型前,其他的集合类以一种宽泛的方式处理对象(就好像它们没有特定类型一样)。事实上,这些集合类把保存对象的类型默认为 Object,也就是 Java 中所有类的基类。而数组是优于 预泛型 (pre-generic)集合类的,因为你创建一个数组就可以保存特定类型的数据。这意味着你获得了一个编译时的类型检查,而这可以防止你插入错误的数据类型,或者搞错你正在提取的数据类型。
当然,不管在编译时还是运行时,Java都会阻止你犯向对象发送不正确消息的错误。然而不管怎样,使用数组都不会有更大的风险。比较好的地方在于,如果编译器报错,最终的用户更容易理解抛出异常的含义。
一个数组可以保存基本数据类型,而一个预泛型的集合不可以。然而对于泛型而言,集合可以指定和检查他们保存对象的类型,而通过 自动装箱 (autoboxing)机制,集合表现地就像它们可以保存基本数据类型一样,因为这种转换是自动的。
显示数组的实用程序
Java 提供了 Arrays.toString()
来将数组转换为可读字符串,然后可以在控制台上显示。
public class ArraysShow {
public static void main(String[] args) {
String[] strings = new String[]{"1", "2", "3"};
System.out.println(strings);
System.out.println(Arrays.toString(strings));
/** Output:
* [Ljava.lang.String;@29453f44
* [1, 2, 3]
*/
}
}
一等对象
不管你使用的什么类型的数组,数组中的数据集实际上都是对堆中真正对象的引用。数组是保存指向其他对象的引用的对象,数组可以隐式地创建,作为数组初始化语法的一部分,也可以显式地创建,比如使用一个 new 表达式。数组对象的一部分(事实上,你唯一可以使用的方法)就是只读的 length 成员函数,它能告诉你数组对象中可以存储多少元素。[ ] 语法是你访问数组对象的唯一方式。
public class ArraysShow {
public static void main(String[] args) {
int[] ints = new int[5];
String[] strings = {"a","b","c"};
double[] doubles = new double[]{1.0,1.1,1.2};
System.out.println(ints.length);
System.out.println(strings.length);
System.out.println(doubles.length);
/** Output:
* 5
* 3
* 3
*/
}
}
返回数组
假设你写了一个方法,这个方法不是返回一个元素,而是返回多个元素。对 C++/C 这样的语言来说这是很困难的,因为你无法返回一个数组,只能是返回一个指向数组的指针。这会带来一些问题,因为对数组生存期的控制变得很混乱,这会导致内存泄露。
而在 Java 中,你只需返回数组,你永远不用为数组担心,只要你需要它,它就可用,垃圾收集器会在你用完后把它清理干净。
public class ArraysShow {
public String[] respArray(){
return new String[5];
}
}
多维数组
要创建多维的基元数组,你要用大括号来界定数组中的向量:
public class ArraysShow {
public static void main(String[] args) {
int[][] ints = new int[2][3];
System.out.println(Arrays.deepToString(ints));
int[][] ints2 = {{1,2,3},{4,5,6}};
System.out.println(Arrays.deepToString(ints2));
/** Output:
* [[0, 0, 0], [0, 0, 0]]
* [[1, 2, 3], [4, 5, 6]]
*/
}
}
你也可以使用 new 分配数组,倘若你不对基元数组进行显式的初始化,它的值会自动初始化。这个例子使用 Arrays.deepToString()
方法,将多维数组转换成 String 类型,就像输出中显示的那样。
泛型数组
一般来说,数组和泛型并不能很好的结合。你不能实例化参数化类型的数组,类型擦除需要删除参数类型信息,而且数组必须知道它们所保存的确切类型,以强制保证类型安全。
public class ArraysShow<T> {
public T[] f(T[] t){
return t;
}
public static void main(String[] args) {
new ArraysShow<String>().f(new String[2]);
}
}
比起使用参数化类,使用参数化方法很方便。您不必为应用它的每个不同类型都实例化一个带有参数的类,但是可以使它成为 静态 的。你不能总是选择使用参数化方法而不用参数化的类,但通常参数化方法是更好的选择。
Arrays工具类
方法 | 说明 |
---|---|
asList() | 获取任何序列或数组,并将其转换为一个 列表集合 |
copyOf() | 以新的长度创建现有数组的新副本。 |
copyOfRange() | 创建现有数组的一部分的新副本。 |
equals() | 比较两个数组是否相等。 |
deepEquals() | 多维数组的相等性比较。 |
stream() | 生成数组元素的流。 |
hashCode() | 生成数组的哈希值(您将在附录中了解这意味着什么:理解equals()和hashCode())。 |
deepHashCode() | 多维数组的哈希值。 |
sort() | 排序数组 |
parallelSort() | 对数组进行并行排序,以提高速度。 |
binarySearch() | 在已排序的数组中查找元素。 |
parallelPrefix() | 使用提供的函数并行累积(以获得速度)。基本上,就是数组的reduce()。 |
spliterator() | 从数组中产生一个Spliterator;这是本书没有涉及到的流的高级部分。 |
toString() | 为数组生成一个字符串表示。你在整个章节中经常看到这种用法。 |
deepToString() | 为多维数组生成一个字符串。你在整个章节中经常看到这种用法。对于所有基本类型和对象,所有这些方法都是重载的。 |
… | … |
数组拷贝
copyOf()
方法复制指定长度的数组和 copyOfRange()
方法复制范围数组,与使用for循环手工执行复制相比要快得多。这些方法被重载以处理所有类型。如果你阅读过ArrayList的底层源码,你会发现它的扩容方式就是这样。
public class ArraysShow {
public static void main(String[] args) {
int[] ints = {1,2,3,4,5,6};
int[] copyOf = Arrays.copyOf(ints, ints.length);
System.out.println(Arrays.toString(copyOf));
int[] copyOfRange = Arrays.copyOfRange(ints, 0, 3);
System.out.println(Arrays.toString(copyOfRange));
/** Output:
* [1, 2, 3, 4, 5, 6]
* [1, 2, 3]
*/
}
}
数组比较
数组 提供了 equals()
来比较一维数组,以及 deepEquals()
来比较多维数组。对于所有原生类型和对象,这些方法都是重载的。
数组相等的含义:数组必须有相同数量的元素,并且每个元素必须与另一个数组中的对应元素相等,对每个元素使用 equals()
对于原生类型,使用原生类型的包装类的 equals()
方法;
public class ArraysShow {
public static void main(String[] args) {
int[] ints = {1,2,3,4,5,6};
int[] ints2 = {1,2,3,4,5,6,7};
System.out.println(Arrays.equals(ints,ints2));
int[][] int3 = {{1,2,3},{4,5,6}};
int[][] int4 = {{1,2,3},{4,5,7}};
System.out.println(Arrays.deepEquals(int3,int4));
/** Output:
* false
* false
*/
}
}
流和数组
stream()
方法很容易从某些类型的数组中生成元素流。
public class ArraysShow {
public static void main(String[] args) {
int[] ints = {1,2,3,4,5,6};
int asInt = Arrays.stream(ints).max().getAsInt();
System.out.println(asInt);
Arrays.stream(ints).skip(3).forEach(System.out::print);
System.out.println();
Arrays.stream(ints).limit(3).forEach(System.out::print);
/** Output:
* 6
* 456
* 123
*/
}
}
数组排序
使用内置的排序方法,您可以对实现了 Comparable 接口或具有 Comparator 的任何对象数组 或 任何原生数组进行排序。
public class ArraysShow {
public static void main(String[] args) {
String[] str = new String[]{"b","d","e","a","c"};
Arrays.sort(str);
System.out.println(Arrays.toString(str));
Arrays.sort(str, Comparator.reverseOrder());
System.out.println(Arrays.toString(str));
/** Output:
* [a, b, c, d, e]
* [e, d, c, b, a]
*/
}
}
注意字符串排序算法中的输出。它是字典式的,所以它把所有以大写字母开头的单词放在前面,然后是所有以小写字母开头的单词。
Java标准库中使用的排序算法被设计为最适合您正在排序的类型----原生类型的快速排序和对象的归并排序。
binarySearch二分查找
一旦数组被排序,您就可以通过使用 Arrays.binarySearch()
来执行对特定项的快速搜索。但是,如果尝试在未排序的数组上使用 binarySearch()
,结果是不可预测的。
public class ArraysShow {
public static void main(String[] args) {
int[] ints = {655,123,51,311,548,151};
int i = Arrays.binarySearch(ints, 51);
System.out.println("index:"+i+",value:"+ints[i]);
/** Output:
* index:2,value:51
*/
}
}
parallelPrefix并行前缀
它对前一个元素和当前元素执行一个操作,并将结果放入当前元素位置:
public class ArraysShow {
public static void main(String[] args) {
int[] ints = {655,123,51,311,548,151};
Arrays.parallelPrefix(ints, Integer::sum);
System.out.println(Arrays.toString(ints));
/** Output:
* [655, 778, 829, 1140, 1688, 1839]
*/
}
}
在位置0中,它将先前计算的值(因为没有先前的值)与原始数组位置0中的值组合在一起。在位置1中,它获取之前计算的值(它只是存储在位置0中),并将其与位置1中先前计算的值相结合。依次往复。
本章小结
Java为固定大小的低级数组提供了合理的支持。在Java的后续版本中,集合支持得到了显著的改进,现在集合在除性能外的所有方面都优于数组,即使这样,集合的性能也得到了显著的改进。
使用自动装箱和泛型,在集合中保存原生类型是毫不费力的,这进一步鼓励您用集合替换低级数组。由于泛型产生类型安全的集合,数组在这方面也不再有优势。所有这些问题都表明,在使用Java的最新版本进行编程时,应该“优先选择集合而不是数组”。
最后
以上就是拼搏路灯为你收集整理的重拾Java基础知识:数组前言的全部内容,希望文章能够帮你解决重拾Java基础知识:数组前言所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复