第16章 数组
数组为什么特殊
就JDK8及以后而言,数组与其他容器方面的区别就是,数组的效率更高,同时可以保存基本类型(在自动装箱后,容器也相当于拥有该能力了),但是带来的缺点是数组对象的大小是固定的,在声明周期中不可改变,同时并没有容器类如此丰富的功能。
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
long l = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
list.add(i);
}
System.out.println(System.currentTimeMillis()-l);
int[] ints = new int[1000000];
l = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
ints[i] = i;
}
System.out.println(System.currentTimeMillis()-l);
}
16.2 数组是第一级对象
无论使用哪种类型的数组,数组标识符其实只是一个引用,指向在堆中创建的一个真实对象,这个(数组)对象用以保存指向其他对象的引用。
public static void main(String[] args) {
int[] ints; // 未初始化前不能使用
String[] a = new String[4]; // 引用类型被初始化为null,基本类型初始化为0
String[] b = {"a", "b", "c"}; // 只能在初始化时使用
b = new String[]{"a"};
for (int i = 0; i < a.length/*仅代表数组容量,不能代表有多少元素*/; i++) {
if (a[i] == null)
a[i] = "a";
}
}
16.3 返回一个数组
public class Test2 {
// 源数组应当作为一个域
// 放在方法中的缺点是每次调用都会被创建,影响性能
private static String[] strings = {"a","b","c","d","e","f"};
public static void main(String[] args) {
System.out.println(Arrays.toString(randomLetter(3)));
}
// 返回一个指定数量的随机不重复字符串数组
public static String[] randomLetter(int n){
// 当返回数组长度大于源数组长度时抛出异常
if (n > strings.length)
throw new RuntimeException("请求数组超长");
String[] result = new String[n];
// 为什么要引入一个和源数组长度相同的布尔数组?
// 为了防止返回数组出现重复字母
boolean[] picked = new boolean[strings.length];
Random random = new Random();
int t;
for (int i = 0; i < n; i++) {
do {
t = random.nextInt(strings.length);
// 当该字母已被选择后重新选择一个
}while (picked[t]);
result[i] = strings[t];
// 该字母被选择后,设置该字母不可被选
picked[t] = true;
}
return result;
}
}
16.4 多维数组
public static void main(String[] args) {
int[][] ints ={{1,2,3},{1,2}};
int[][][] tints = new int[2][3][4];
// 将多维数组转为String
System.out.println(Arrays.deepToString(tints));
// [[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]]
}
粗糙数组
构成矩阵的每个向量都可以有任意长度
public static void main(String[] args) {
Random random = new Random();
// 初始化时确定三维数组的长度
int[][][] ints = new int[random.nextInt(5)][][];
for (int i = 0; i < ints.length; i++) {
// 确定二维数组的长度
ints[i] = new int[random.nextInt(5)][];
for (int j = 0; j < ints[i].length; j++) {
// 确定三维数组的长度
ints[i][j] = new int[random.nextInt(5)];
}
}
System.out.println(Arrays.deepToString(ints));
// 处理非基类类型数组
String[][][] strings = {/*三维*/
{/*二维*/
},{
{/*一维*/"a","b"},
{"c"}
}
};
}
16.5 数组与泛型
class A<T>{
// 更多的时候是使用参数化方法而不是参数化类
public T[] f(T[] args){
return args;
}
}
public static void main(String[] args) {
// 不允许实例化具有参数化类型的数组,
// 允许参数化数组本身的类型
// A<String>[] as = new A<>[2]; // 不允许
Integer[] integers = new A<Integer>().f(new Integer[]{1,2,3});
}
public static void main(String[] args) {
// 允许对有参数化类型数组的引用
List<String>[] lists = new List[10];
lists[0] = new ArrayList<>();
// 泛型数组是Object类型
Object[] objects = lists;
// Object[]数组可以放入任意Object类型
objects[1] = new ArrayList<Integer>();
}
class A<T>{
public T[] array;
public A(int size){
// array = new T[size]; // 不允许
// 只能转型,存在隐患,可以存入其他类型
array = (T[]) new Object[size];
}
}
16.6 创建测试数据
Arrays.fill()
用同一个值填充各个位置
public static void main(String[] args) {
int[] ints = new int[5];
Arrays.fill(ints,10);
System.out.println(Arrays.toString(ints)); // [10, 10, 10, 10, 10]
String[] strings = new String[5];
Arrays.fill(strings,0,2,"a");
System.out.println(Arrays.toString(strings)); // [a, a, null, null, null]
}
数据生成器
interface Generator<T>{
T next();
}
class RandomGenerator{
static char[] chars = "abcdef".toCharArray();
static Random random = new Random();
// 随机生成字节
public static class Byt implements Generator<Byte>{
@Override
public Byte next() {
return (byte)random.nextInt();
}
}
// 随机生成整数
public static class Int implements Generator<Integer>{
@Override
public Integer next() {
return random.nextInt();
}
}
// 随机生成字符
public static class Char implements Generator<Character>{
@Override
public Character next() {
int i = random.nextInt(chars.length);
return chars[i];
}
}
// 随机生成字符串
public static class Str implements Generator<String>{
@Override
public String next() {
int offset = random.nextInt(chars.length);
int count = 1+random.nextInt(chars.length-offset);
return String.valueOf(chars,offset,count);
}
}
}
public class Test2 {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
test(RandomGenerator.class);
}
public static void test(Class<?> surroundingClass) throws IllegalAccessException, InstantiationException {
// 获得内部所有类
for(Class<?> c:surroundingClass.getClasses()){
System.out.print(c.getSimpleName()+":");
Generator<?> generator = (Generator<?>) c.newInstance();
for (int i = 0; i < 5; i++) {
System.out.println(generator.next()+" ");
}
System.out.println();
}
}
}
/*
Str:abcd abcd e e def
Char:f c a d b
Int:745926114 -1501156431 1314007545 850905548 1753242117
Byt:-51 36 83 -128 -29
*/
从Generator中创建数组
// 填充数组
public static <T> T[] array(T[] ts,Generator<T> generator){
for (int i = 0; i < ts.length; i++) {
ts[i] = generator.next();
}
return ts;
}
// 生成数组
public static <T> T[] array(Class<T> c,int size,Generator<T> generator){
T[] ts = (T[]) Array.newInstance(c,size);
return array(ts,generator);
}
// 上述两个生成器只能生成Object数组,不能生成基本类型数组,所以,需要转换器
public static char[] primitive(Character[] characters){
char[] chars = new char[characters.length];
for (int i = 0; i < characters.length; i++) {
// 自动转型
chars[i] = characters[i];
}
return chars;
}
// 重载
public static byte[] primitive(Byte[] objects){
byte[] bytes = new byte[objects.length];
for (int i = 0; i < objects.length; i++) {
bytes[i] = objects[i];
}
return bytes;
}
public static void main(String[] args) {
String[] strings = new String[5];
// 填充
array(strings,new RandomGenerator.Str());
System.out.println(Arrays.toString(strings));
// 生成
Byte[] objects = array(Byte.class, 5, new RandomGenerator.Byt());
// 转换
byte[] bytes = primitive(objects);
System.out.println(Arrays.toString(bytes));
}
16.7 Arrays实用功能
复制数组
public static void main(String[] args) {
int[] ints1 = new int[5];
Arrays.fill(ints1,1);
int[] ints2 = new int[10];
// 源数组,起始位置,目标数组,目标位置,长度
// 不能自动装箱,数组类型必须完全一致
System.arraycopy(ints1,0,ints2,5,5);
System.out.println(Arrays.toString(ints2)); // [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
}
数组的比较
public static void main(String[] args) {
int[] ints1 = new int[5];
Arrays.fill(ints1,1);
int[] ints2 = new int[5];
Arrays.fill(ints2,1);
// 数组长度必须相等,每个元素也必须相等
System.out.println(Arrays.equals(ints1,ints2)); // true
ints2[0] = 2;
System.out.println(Arrays.equals(ints1,ints2)); // false
String[] strings1 = {"a","a","a"};
String[] strings2 = {new String("a"),new String("a"),new String("a")};
// 元素之间用equals()方法比较,虽然,strings1指向同一个位置,strings2是在堆中开辟了三个空间,但是他们的值是相等的
System.out.println(Arrays.equals(strings1,strings2)); // true
}
数组元素的比较
第一种比较,实现Comparable接口
class ComType implements Comparable<ComType>{
private static Random random = new Random();
private int i;
private int j;
public ComType(int i, int j) {
this.i = i;
this.j = j;
}
@Override
public int compareTo(ComType o) {
return i > o.i?1:(i == o.i?0:-1);
}
@Override
public String toString() {
return "{"+"i=" + i +
", j=" + j+"}" ;
}
// 生成ComType类
public static Generator<ComType> generator(){
return new Generator<ComType>() {
@Override
public ComType next() {
return new ComType(random.nextInt(10),random.nextInt(10));
}
};
}
}
public static void main(String[] args) {
ComType[] comTypes = new ComType[5];
array(comTypes,ComType.generator());
// 未排序
System.out.println(Arrays.toString(comTypes));
// [{i=9, j=3}, {i=6, j=1}, {i=9, j=6}, {i=0, j=5}, {i=2, j=1}]
Arrays.sort(comTypes);
// 排序后
System.out.println(Arrays.toString(comTypes));
// [{i=0, j=5}, {i=2, j=1}, {i=6, j=1}, {i=9, j=3}, {i=9, j=6}]
}
反转自然排序
public static void main(String[] args) {
ComType[] comTypes = new ComType[5];
array(comTypes,ComType.generator());
Arrays.sort(comTypes,/*反转自然排序*/Collections.reverseOrder());
// 排序后
System.out.println(Arrays.toString(comTypes));
// [{i=7, j=4}, {i=5, j=4}, {i=3, j=4}, {i=2, j=8}, {i=1, j=3}]
}
自定义排序规则
class MyComparator implements Comparator<ComType>{
// 按j的大小排序
@Override
public int compare(ComType o1, ComType o2) {
return o1.j > o2.j?1:(o1.j == o2.j?0:-1);
}
}
public static void main(String[] args) {
ComType[] comTypes = new ComType[5];
array(comTypes,ComType.generator());
Arrays.sort(comTypes,new MyComparator());
// 排序后
System.out.println(Arrays.toString(comTypes));
// [{i=1, j=2}, {i=8, j=3}, {i=3, j=3}, {i=1, j=8}, {i=9, j=9}]
}
数组排序
public static void main(String[] args) {
String[] strings = new String[5];
// 排序前
array(strings,new RandomGenerator.Str());
System.out.println(Arrays.toString(strings)); // [cdEF, F, EF, F, d]
// 排序后
Arrays.sort(strings);
System.out.println(Arrays.toString(strings)); // [EF, F, F, cdEF, d]
// 反转排序后
Arrays.sort(strings,Collections.reverseOrder());
System.out.println(Arrays.toString(strings)); // [d, cdEF, F, F, EF]
// 忽略大小写
Arrays.sort(strings,String.CASE_INSENSITIVE_ORDER);
System.out.println(Arrays.toString(strings)); // [cdEF, d, EF, F, F]
}
在已排序的数组中查找
public static void main(String[] args) {
int[] ints = {10,20,30,40,50};
int i = Arrays.binarySearch(ints, 30);
System.out.println(i); // 2
i = Arrays.binarySearch(ints, 6);
System.out.println(i); // -1
i = Arrays.binarySearch(ints, 60);
System.out.println(i); // -6
}
/*
如果可以查到元素,返回该元素的索引;
含有重复元素,无法保证返回的是哪一个的索引;
如果,不含有该元素则返回-插入点-1;
插入点:第一个大于要查找元素的索引,如果插入值最大,则为该数组的长度
*/
排序对象数组时,如果使用了自定义的排序,查询时也必须指定顺序
class A{
public int i ;
static Random random = new Random();
public A(int i) {
this.i = i;
}
@Override
public String toString() {
return "{" +
"i=" + i +
'}';
}
public static Generator<A> generator(){
return new Generator<A>() {
@Override
public A next() {
return new A(random.nextInt(10));
}
};
}
}
class MyComparator implements Comparator<A>{
@Override
public int compare(A o1, A o2) {
return o2.i > o1.i?1:(o2.i == o1.i?0:-1);
}
}
public static void main(String[] args) {
A[] as = new A[5];
MyComparator myComparator = new MyComparator();
array(as,A.generator());
Arrays.sort(as,myComparator); // 指定了自己的排序
System.out.println(Arrays.toString(as));
int i = Arrays.binarySearch(as,as[2],myComparator); // 查询时也必须指定
System.out.println(i);
}
16.8 总结
数组在早期版本中是必须的,但是随着泛型和自动装箱技术的出现,容器几乎完全可以替代数组了,在开发中应该使用容器,除非是对性能有极高的要求。