Java编程笔记15:数组

Java编程笔记15:数组

5c9c3b3b392ac581.jpg

图源:PHP中文网

声明和创建

Java中的数组其实是一种特殊的类,自然地,数组句柄是对真实数组对象的引用。

需要注意的是,和其它类型的变量一样,方法中声明的数组变量不会被编译器“自动”初始化:

package ch15.array;

public class Main {
    public static void main(String[] args) {
        int[] arr;
        // System.out.println(arr);
        // The local variable arr may not have been initializedJava(536870963)
    }
}

像示例中展示的那样,因为没有被初始化,所以就无法被利用,除非手动进行初始化。

同样的,如果定义数组为类的属性,就会被编译器初始化为null

package ch15.array2;

public class Main {
    private static int[] arr;
    public static void main(String[] args) {
        System.out.println(arr == null);
    }
}
// true

创建数组时需要注意,因为Java的数组大小固定,且无法改变,所以在创建时必须要让编译器可以“知晓”将要创建的数组长度。

package ch15.array3;

import java.util.Arrays;
import java.util.Random;

public class Main {
    public static void main(String[] args) {
        Random rand = new Random();
        int[] arr = new int[rand.nextInt(10)];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }
        System.out.println(Arrays.toString(arr));
        arr = new int[] { 1, 2, 3 };
        // arr = {1,2,3};
        // Array constants can only be used in initializersJava(1610612944)
        int[] arr2 = { 1, 2, 3 };
    }
}

上面的示例展示了常见的几种创建和初始化数组的方式,需要注意的是,{1,2,3}这种数组常量(array constants)只能被用于声明并同时初始化数组时,并不能用于给已有的数组引用分配一个新数组。

基于同样的原因,你不能将数组常量(或者可以叫数组字面量)当做一个数组对象作为参数进行传递:

package ch15.array4;

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        // test({1,2,3});
        test(new int[] { 1, 2, 3 });
    }

    private static void test(int[] arr) {
        System.out.println(Arrays.toString(arr));
    }
}
// [1, 2, 3]

new关键字结合数组常量就可以,上面的示例说明了这一点。

数组创建后,其中的元素会按照数组的类型初始化为相应的“零值”:

package ch15.array5;

import java.util.Arrays;

import util.Fmt;

class Student {
    private static int counter = 0;
    private final int id = ++counter;

    @Override
    public String toString() {
        return Fmt.sprintf("Student(%d)", id);
    }
}

public class Main {
    public static void main(String[] args) {
        final int SIZE = 5;
        int[] intArr = new int[SIZE];
        char[] charArr = new char[SIZE];
        Student[] students = new Student[SIZE];
        System.out.println(Arrays.toString(intArr));
        System.out.println(Arrays.toString(charArr));
        System.out.println(Arrays.toString(students));
        for (int i = 0; i < students.length; i++) {
            students[i] = new Student();
        }
        System.out.println(Arrays.toString(students));
    }
}
// [0, 0, 0, 0, 0]
// [, , , , ]
// [null, null, null, null, null]
// [Student(1), Student(2), Student(3), Student(4), Student(5)]

多维数组

在Java中可以创建多维数组:

package ch15.multi_vector;

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[][] matrix = new int[3][2];
        for (int i = 0; i < matrix.length; i++) {
            for(int j=0;j<matrix[i].length;j++){
                matrix[i][j] = (i+1)*(j+1);
            }
        }
        System.out.println(Arrays.toString(matrix));
    }
}
// [[I@5ca881b5, [I@24d46ca6, [I@4517d9a3]

打印多维数组时需要使用Arrays.deepToString

...
public class Main {
    public static void main(String[] args) {
        ...
        System.out.println(Arrays.deepToString(matrix));
    }
}
// [[1, 2], [2, 4], [3, 6]]

同样可以使用数组字面量来初始化多维数组:

package ch15.multi_vector3;

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[][] matrix = { { 1, 2 }, { 2, 4 }, { 3, 6 } };
        System.out.println(Arrays.deepToString(matrix));
        int[][] matrix2 = new int[][] { { 1, 2 }, { 2, 4 }, { 3, 6 } };
    }
}
// [[1, 2], [2, 4], [3, 6]]

和一般想法不同的是,虽然我们通常见到的多维数组都各个维度的长度都相等,但实际上可以创建维度不同的多维数组:

package ch15.multi_vector4;

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        final int SIZE = 5;
        int[][] arr = new int[SIZE][];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = new int[i + 1];
        }
        System.out.println(Arrays.deepToString(arr));
    }
}
// [[0], [0, 0], [0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0, 0]]

在上边的示例中,二维数组arr中每个元素都是一个长度不同的一维数组。所以多维数组其实可以看做是一个一维数组,只不过其中的元素是一个多维数组的引用。

就像示例中的那样,在创建多维数组时并不需要同时指定所有维度的长度,只有“最外层”的数组长度是必须要指定的。其它维度的长度可以在遍历外层数组时分别指定,也正是利用了这点,在示例中创建了维度长度不等的多维数组。

这种维度长度不等的多维数组也叫做“粗糙数组”。

数组与泛型

因为类型擦除的缘故,无法创建泛型类的数组:

package ch15.generic;

import java.util.List;

public class Main {
    public static void main(String[] args) {
        // List<String>[] lists = new List<String>[5];
        // Cannot create a generic array of List<String>Java(16777751)
        ...
    }
}

有意思的是,可以创建一个相应的句柄:

List<String>[] lists;

你可能觉得这样做没什么用,因为无法被正常初始化。实际上可以通过一种特别的方式来完成初始化:

package ch15.generic;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        // List<String>[] lists = new List<String>[5];
        // Cannot create a generic array of List<String>Java(16777751)
        List<String>[] lists;
        lists = (List<String>[]) new List[5];
        for (int i = 0; i < lists.length; i++) {
            lists[i] = new ArrayList<String>();
        }
        lists[1].add("hello");
        lists[2].add("world");
        System.out.println(Arrays.toString(lists));
    }
}
// [[], [hello], [world], [], []]

就像示例中的那样,可以先创建一个普通的List数组,然后再将其转型为List<String>[]

这样多少有些奇怪,但实际上除了会因为转型出现warning之外,在之后的使用中表现的很好,并且在lists[1].add此类方法被调用时,会提供泛型类相应的类型检查。

此外,在泛型类中是可以创建诸如T[]这样的属性的,但同样因为擦除的缘故会产生一些问题,这方面的讨论已经在Java编程笔记13:泛型 - 魔芋红茶’s blog (icexmoon.cn)中详细说明了,这里不再赘述。

很自然地,在Java中会将数组和容器,尤其是ArrayList进行对比,它们的主要区别有:

  • 数组作为原生类型,运行效率要比ArrayList高。
  • 数组仅包含一个只读的length属性用于获取长度,并不支持其它操作,ArrayList提供非常多的操作。
  • ArrayList作为泛型类,只支持类作为类型参数,不支持基础类型,但可以通过自动打包和解包机制间接支持。数组则支持全部的类型。
  • 数组支持协变,ArrayList不支持。

关于最后一点,在之前介绍泛型时有过说明,这里再通过一个简单示例说明:

package ch15.generic2;

import java.util.ArrayList;
import java.util.List;

class Fruit {
}

class Apple extends Fruit {
}

class Orange extends Fruit {
}

public class Main {
    public static void main(String[] args) {
        Apple[] apples = new Apple[10];
        Fruit[] fruits = apples;
        fruits[0] = new Orange();
        // Exception in thread "main" java.lang.ArrayStoreException:
        // ch15.generic2.Orange
        // at ch15.generic2.Main.main(Main.java:16)
        List<Apple> apples2 = new ArrayList<>();
        // List<Fruit> fruits2 = apples2;
        // Type mismatch: cannot convert from List<Apple> to List<Fruit>Java(16777233)

    }
}

在上边的示例中,AppleFruit的子类,所以也可以认为可以将Apple数组看做是Fruit数组,在语法上是允许的。但如果在使用中试图将一个不合适的元素(在示例中是Orange对象)添加进这个数组,就会产生一个运行时异常ArrayStoreException

ListArrayList,作为一个泛型类,是不允许类似的协变操作的,在示例中体现为List<Fruit> fruits2 = apples2这样的语句无法通过编译。

填充测试数据

学习数组的过程中,很自然地会需要批量给数组中填充一些测试数据,下面介绍一些常用的方法。

标准库提供一个工具方法Arrays.fill,可以用于对数组进行“简单填充”:

package ch15.test;

import java.util.Arrays;

import util.Fmt;

class Student {
    private static int counter = 0;
    private int id = ++counter;
    @Override
    public String toString() {
        return Fmt.sprintf("Student(%d)", id);
    }
    public void set(int id){
        this.id = id;
    }
}

public class Main {
    public static void main(String[] args) {
        final int SIZE = 5;
        int[] arr = new int[SIZE];
        Arrays.fill(arr, 99);
        System.out.println(Arrays.toString(arr));
        Student[] students = new Student[SIZE];
        Student defaultStudent = new Student();
        Arrays.fill(students, defaultStudent);
        System.out.println(Arrays.toString(students));
        defaultStudent.set(99);
        System.out.println(Arrays.toString(students));
    }

}
// [99, 99, 99, 99, 99]
// [Student(1), Student(1), Student(1), Student(1), Student(1)]
// [Student(99), Student(99), Student(99), Student(99), Student(99)]

需要注意的是,对于基础类型的数组,是直接用指定值填充。而如果是类,则填充的是指定对象的引用,示例中说明了这一点。

Arrays.fill本身重载了多个版本,可以支持所有类型的数组。此外,Arrays.fill还支持指定填充的起始和结束位置:

...
public class Main {
    public static void main(String[] args) {
        ...
        Student newStudent = new Student();
        newStudent.set(50);
        Arrays.fill(students, 1, 3, newStudent);
        System.out.println(Arrays.toString(students));
    }

}
// [99, 99, 99, 99, 99]
// [Student(1), Student(1), Student(1), Student(1), Student(1)]
// [Student(99), Student(99), Student(99), Student(99), Student(99)]
// [Student(99), Student(50), Student(50), Student(99), Student(99)]

当然,Arrays.fill具有相当的局限性,我们可以自行编写填充测试数据的辅助类。使用泛型编写适用性更广泛的工具类是一个不错的主意。

我们可以利用之前提过的泛型接口Generator<T>来创建分别适用于各种数据类型的生成器类,并最终利用这些生成器类完成数组填充。

package ch15.test2;

interface Generator<T> {
    T next();
}

public class CommonGenerator {
    public static class IntGenerator implements Generator<Integer> {
        private int counter = 0;

        @Override
        public Integer next() {
            return counter++;
        }

    }

    public static class CharGenerator implements Generator<Character> {
        private static char[] chars = "abcdefghijklmnopqrstuvwxyz".toCharArray();
        private int index = -1;

        @Override
        public Character next() {
            index = (index + 1) % chars.length;
            return chars[index];
        }

    }

    public static class StringGenerator implements Generator<String> {
        private CharGenerator charGenerator = new CharGenerator();
        private int wordsLength = 5;

        public StringGenerator() {
        }

        public StringGenerator(int wordsLength) {
            this.wordsLength = wordsLength;
        }

        @Override
        public String next() {
            char[] words = new char[wordsLength];
            for (int i = 0; i < words.length; i++) {
                words[i] = charGenerator.next();
            }
            return new String(words);
        }

    }

    public static void main(String[] args) {
        test(CommonGenerator.class);
    }

    @SuppressWarnings("unchecked")
    public static void test(Class<?> cls) {
        Class<?>[] clses = cls.getClasses();
        for (Class<?> genCls : clses) {
            Class<Generator<?>> genCls2 = (Class<Generator<?>>) genCls;
            testGen(genCls2);
        }
    }

    private static <T> void testGen(Class<? extends Generator<T>> genCls) {
        Generator<T> gen = null;
        try {
            gen = genCls.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        System.out.print(genCls.getSimpleName() + ": ");
        for (int i = 0; i < 10; i++) {
            System.out.print(gen.next() + " ");
        }
        System.out.println();
    }
}
// CharGenerator: a b c d e f g h i j
// IntGenerator: 0 1 2 3 4 5 6 7 8 9
// StringGenerator: abcde fghij klmno pqrst uvwxy zabcd efghi jklmn opqrs tuvwx

上面的示例展示了IntegerCharString对应版本的Generator类,这些类以CommonGenerator的静态内部类的方式进行定义,并最后使用反射对这些类进行了测试。

类似的,还可以创建随机产生数据的版本:

package ch15.test2;

import java.util.Random;

public class RandomGenerator {
    private static Random rand = new Random();

    public static class IntGenerator implements Generator<Integer> {
        private int max = 100;

        public IntGenerator() {
        }

        public IntGenerator(int max) {
            this.max = max;
        }

        @Override
        public Integer next() {
            return rand.nextInt(max);
        }

    }

    public static class CharGenerator implements Generator<Character> {
        char[] chars = "abcdefghijklmnopqrstuvwxyz".toCharArray();

        @Override
        public Character next() {
            int index = rand.nextInt(chars.length);
            return chars[index];
        }

    }

    public static class StringGenerator implements Generator<String> {
        private int wordsLength = 5;
        private static CharGenerator charGenerator = new CharGenerator();

        public StringGenerator() {
        }

        public StringGenerator(int wordsLength) {
            this.wordsLength = wordsLength;
        }

        @Override
        public String next() {
            char[] words = new char[wordsLength];
            for (int i = 0; i < words.length; i++) {
                words[i] = charGenerator.next();
            }
            return new String(words);
        }

    }

    public static void main(String[] args) {
        CommonGenerator.test(RandomGenerator.class);
    }
}
// CharGenerator: a b k d h y f a y a
// IntGenerator: 13 0 63 48 40 51 82 41 5 29
// StringGenerator: jsnfh ctwsc mumxw bhode xigwo lyguy zkiaq nrajs nlpnd lqbqw

最后添加一个类ArrayFiller,用于利用Generator接口填充数组:

package ch15.test2;

import java.lang.reflect.Array;
import java.util.Arrays;

public class ArrayFiller {
    public static <T> T[] fill(T[] arr, Generator<T> gen) {
        for (int i = 0; i < arr.length; i++) {
            arr[i] = gen.next();
        }
        return arr;
    }

    @SuppressWarnings("unchecked")
    public static <T> T[] fill(Generator<T> gen, Class<T> type, int size) {
        T[] arr = (T[]) Array.newInstance(type, size);
        for (int i = 0; i < arr.length; i++) {
            arr[i] = gen.next();
        }
        return arr;
    }

    public static void main(String[] args) {
        final int SIZE = 10;
        Integer[] numbers = new Integer[SIZE];
        fill(numbers, new CommonGenerator.IntGenerator());
        System.out.println(Arrays.toString(numbers));
        numbers = fill(new RandomGenerator.IntGenerator(), Integer.class, 6);
        System.out.println(Arrays.toString(numbers));
    }

}
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
// [55, 21, 98, 95, 40, 59]

一切看起来都很好,但缺陷是因为使用了泛型,所以只能用这种方式填充或者产生非基础类型的数组。如果要解决这个问题,可以添加一个类利用解包机制将非基础类型的数组转换为基础类型的数组:

package ch15.test2;

import java.util.Arrays;

public class ArrayConvertor {
    public static int[] convert(Integer[] arr) {
        int[] newArr = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            newArr[i] = arr[i];
        }
        return newArr;
    }

    public static char[] convert(Character[] arr) {
        char[] newArr = new char[arr.length];
        for (int i = 0; i < arr.length; i++) {
            newArr[i] = arr[i];
        }
        return newArr;
    }

    public static void main(String[] args) {
        int[] numbers = convert(ArrayFiller.fill(new RandomGenerator.IntGenerator(), Integer.class, 6));
        System.out.println(Arrays.toString(numbers));
    }

}
// [46, 45, 15, 3, 47, 95]

常用操作

这里介绍一些Java中常见的关于数组的操作。

拷贝

标准库提供一个方法System.arraycopy可以将一个数组中的元素拷贝到另一个数组中:

package ch15.copy;

import java.util.Arrays;

import ch15.test2.ArrayConvertor;
import ch15.test2.ArrayFiller;
import ch15.test2.CommonGenerator;

public class Main {
    public static void main(String[] args) {
        int[] arr = new int[10];
        Arrays.fill(arr, 99);
        Integer[] arr2 = new Integer[5];
        ArrayFiller.fill(arr2, new CommonGenerator.IntGenerator());
        int[] arr3 = ArrayConvertor.convert(arr2);
        System.out.print("arr: ");
        System.out.println(Arrays.toString(arr));
        System.out.print("arr3: ");
        System.out.println(Arrays.toString(arr3));
        System.arraycopy(arr3, 0, arr, 0, arr3.length);
        System.out.print("arr:");
        System.out.println(Arrays.toString(arr));
    }
}
// arr: [99, 99, 99, 99, 99, 99, 99, 99, 99, 99]
// arr3: [0, 1, 2, 3, 4]
// arr:[0, 1, 2, 3, 4, 99, 99, 99, 99, 99]

arraycopy方法的性能是优于编写for循环对元素一对一进行拷贝的,不过一般而言,这种性能差异是不需要关注的,除非的确涉及大量数组拷贝的代码,且的确拖累了整个程序的性能。

此外,需要注意的是,这种拷贝是“浅拷贝”,如果是引用数组而非基础类型的数组,拷贝的实际上是对象引用:

package ch15.copy2;

import java.util.Arrays;

import ch15.test2.ArrayFiller;
import ch15.test2.Generator;
import util.Fmt;

class Student {
    private static int counter = 0;
    private int id = ++counter;

    @Override
    public String toString() {
        return Fmt.sprintf("Student(%d)", id);
    }

    public void setId(int id) {
        this.id = id;
    }

    public static Generator<Student> generator() {
        return new Generator<>() {

            @Override
            public Student next() {
                return new Student();
            }

        };
    }
}

public class Main {
    public static void main(String[] args) {
        final String PREFIX1 = "students1: ";
        final String PREFIX2 = "students2: ";
        Student[] students1 = new Student[7];
        Student[] studetns2 = new Student[5];
        ArrayFiller.fill(students1, Student.generator());
        Arrays.fill(studetns2, new Student());
        System.out.println(PREFIX1 + Arrays.toString(students1));
        System.out.println(PREFIX2 + Arrays.toString(studetns2));
        System.arraycopy(studetns2, 0, students1, 0, studetns2.length);
        System.out.println(PREFIX1 + Arrays.toString(students1));
        studetns2[0].setId(99);
        System.out.println(PREFIX1 + Arrays.toString(students1));
        System.out.println(PREFIX2 + Arrays.toString(studetns2));
    }
}
// students1: [Student(1), Student(2), Student(3), Student(4), Student(5), Student(6), Student(7)]
// students2: [Student(8), Student(8), Student(8), Student(8), Student(8)]
// students1: [Student(8), Student(8), Student(8), Student(8), Student(8), Student(6), Student(7)]
// students1: [Student(99), Student(99), Student(99), Student(99), Student(99), Student(6), Student(7)]     
// students2: [Student(99), Student(99), Student(99), Student(99), Student(99)]

比较

可以通过Arrays.equals方法比较两个数组是否相等:

package ch15.equal;

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[] arr1 = { 1, 2, 3, 4, 5 };
        int[] arr2 = { 1, 2, 3, 4, 5 };
        boolean result = Arrays.equals(arr1, arr2);
        System.out.println(result);
    }
}

实际上Java中所有需要比较的地方,都使用的是目标对象的equals方法完成比较工作,该方法在Object中就有定义,并且提供一个通过比较对象地址是否相同来判断司否相等的默认实现。

所以如果需要比较对象数组,就需要确保相应的类实现了合适的equals方法:

package ch15.equal2;

import ch15.test2.Generator;
import util.Fmt;

class Student {
	...
    @Override
    public boolean equals(Object obj) {
        if (super.equals(obj)) {
            return true;
        } else if (obj instanceof Student) {
            Student other = (Student) obj;
            return this.id == other.id;
        } else {
            return false;
        }
    }
}

这里的比较逻辑是:

  1. 先通过Object.equals比较obj是否与this是同一个对象,如果是,肯定相等。
  2. 判断obj是否为Student或其子类,如果是,就比较id属性是否相等。
  3. 如果即不是同一个对象,也不是Student及其子类,那就返回false

然后就可以比较Student对象组成的数组了:

package ch15.equal2;

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        Student[] students1 = new Student[5];
        Student[] students2 = new Student[5];
        Arrays.fill(students1, new Student());
        Arrays.fill(students2, new Student());
        System.out.println(Arrays.equals(students1, students2));
        students1[0].setId(99);
        students2[0].setId(99);
        System.out.println(Arrays.equals(students1, students2));
    }
}
// false
// true

示例中用了两个Student对象分别填充students1students2数组,然后将其id都设置为99,所以最终的结果是相等的。

排序

可以使用Arrays.sort对数组进行排序:


package ch15.sort;

import java.util.Arrays;

import ch15.test2.ArrayConvertor;
import ch15.test2.ArrayFiller;
import ch15.test2.RandomGenerator;

public class Main {
    public static void main(String[] args) {
        Integer[] arr = new Integer[10];
        ArrayFiller.fill(arr, new RandomGenerator.IntGenerator());
        int[] arr2 = ArrayConvertor.convert(arr);
        System.out.println(Arrays.toString(arr2));
        Arrays.sort(arr2);
        System.out.println(Arrays.toString(arr2));
    }
}
// [34, 55, 72, 29, 10, 53, 17, 88, 57, 11]
// [10, 11, 17, 29, 34, 53, 55, 57, 72, 88]

默认排序时自然排序,如果需要倒序,需要使用sort另一个重载后的方法:

    public static <T> void sort(T[] a, Comparator<? super T> c) {
		...
	}

这个sort方法需要传入一个实现了Comparator接口的对象,但因为个重载的sort方法使用了泛型,所以无法对基础类型的数组进行排序:


package ch15.sort2;

import java.util.Arrays;
import java.util.Collections;

import ch15.test2.ArrayConvertor;
import ch15.test2.ArrayFiller;
import ch15.test2.RandomGenerator;

public class Main {
    public static void main(String[] args) {
        Integer[] arr = new Integer[10];
        ArrayFiller.fill(arr, new RandomGenerator.IntGenerator());
        System.out.println(Arrays.toString(arr));
        Arrays.sort(arr, Collections.reverseOrder());
        System.out.println(Arrays.toString(arr));
    }
}
// [71, 7, 70, 18, 49, 52, 97, 58, 92, 41]
// [97, 92, 71, 70, 58, 52, 49, 41, 18, 7]

示例中使用了Collections.reverseOrder返回的倒序排序的Comparator接口实现。

实际上,使用Arrays.sort方法排序时,默认是依赖元素实现的Comparable接口,也就是说如果要对引用数组进行排序,就必须让相应的类实现Comparable接口:

package ch15.sort3;

import java.util.HashSet;
import java.util.Random;
import java.util.Set;

import ch15.test2.Generator;
import util.Fmt;

class Student implements Comparable<Student> {
    ...
    @Override
    public int compareTo(Student o) {
        if (id < o.id) {
            return -1;
        } else if (id == o.id) {
            return 0;
        } else {
            return 1;
        }
    }

    public static Generator<Student> randomGenerator() {
        return new Generator<Student>() {
            private Random rand = new Random();
            private Set<Integer> ids = new HashSet<>();
            private int counter = 0;
            private final int MAX = 100;

            @Override
            public Student next() {
                if (counter >= MAX) {
                    return null;
                }
                Student student = new Student();
                int newId = 0;
                do {
                    newId = rand.nextInt(MAX);
                    if (!ids.contains(newId)) {
                        break;
                    }
                } while (true);
                student.id = newId;
                counter++;
                return student;
            }

        };
    }

}

然后就可以进行排序了:

package ch15.sort3;

import java.util.Arrays;

import ch15.test2.ArrayFiller;

public class Main {
    public static void main(String[] args) {
        Student[] students = new Student[6];
        ArrayFiller.fill(students, Student.randomGenerator());
        System.out.println(Arrays.toString(students));
        Arrays.sort(students);
        System.out.println(Arrays.toString(students));
    }
}
// [Student(88), Student(38), Student(84), Student(78), Student(8), Student(98)]
// [Student(8), Student(38), Student(78), Student(84), Student(88), Student(98)]

如果数组元素没有实现Comparable接口,也不好直接实现,可以像之前倒序那样,利用Comparator接口实现排序:

package ch15.sort4;

import java.util.Comparator;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

import ch15.test2.Generator;
import util.Fmt;

class Student implements Comparable<Student> {
	...
    public static Comparator<Student> comparator() {
        return new Comparator<Student>() {

            @Override
            public int compare(Student o1, Student o2) {
                if (o1.id < o2.id) {
                    return 1;
                } else if (o1.id == o2.id) {
                    return 0;
                } else {
                    return -1;
                }
            }

        };
    }
}

如果仔细观察,就能发现上边示例中实现的Comparator接口实际上是倒序的,这点可以通过测试得到验证:

package ch15.sort4;

import java.util.Arrays;

import ch15.test2.ArrayFiller;

public class Main {
    public static void main(String[] args) {
        Student[] students = new Student[6];
        ArrayFiller.fill(students, Student.randomGenerator());
        System.out.println(Arrays.toString(students));
        Arrays.sort(students, Student.comparator());
        System.out.println(Arrays.toString(students));
    }
}
// [Student(64), Student(16), Student(49), Student(30), Student(4), Student(91)]
// [Student(91), Student(64), Student(49), Student(30), Student(16), Student(4)]

查找

数组排好序后就可以使用Arrays.binarySearch进行快速查找,实际上从名字上就可以看出,其使用的是二分查找算法。

package ch15.search;

import java.util.Arrays;
import java.util.Random;

import ch15.test2.ArrayFiller;
import ch15.test2.RandomGenerator;

public class Main {
    public static void main(String[] args) {
        Integer[] numbers = new Integer[6];
        ArrayFiller.fill(numbers, new RandomGenerator.IntGenerator());
        Arrays.sort(numbers);
        System.out.println(Arrays.toString(numbers));
        find(numbers);
    }

    private static int find(Integer[] nums) {
        Random rand = new Random();
        Integer target = rand.nextInt(100);
        int index = Arrays.binarySearch(nums, target);
        if (index > 0) {
            System.out.println(target + " is matched, index is " + index);
            return index;
        } else {
            return find(nums);
        }
    }
}
// [12, 14, 24, 41, 63, 83]
// 63 is matched, index is 4

因为使用的是二分查找,所以前提必须是经过排序过的数组。

总结

数组应该说是一个语言的基础,但如果对比Java的数组和Python、PHP等语言中的数组,就会发现Java中的数组并不好用,究其原因是Java并非纯面向对象的语言,因为支持了基础类型。相比之下Python中所有的类型都是类,继承自object

之所以这样设计,是最初Java的设计者为了照顾到性能,但有意思的是在现在看来,无论是执行效率还是编码效率,在主流语言中Java都不占优势,反而是2010年左右才出现的Go在这两方面都相当突出。

所以现在看来,这也只能被评价为Java的又一个历史包袱。但幸运的是,JavaSE 5加入的泛型可以改善这一点,所以在编写Java代码时,我们应该更多地使用容器而非数组,即使会降低性能,但对Java这门语言说,这点反而可以忽略。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值