从Android到Java(二)

Java基础知识填坑继续。

Java 集合与泛型

数组 VS ArrayList

数组大概是我们学习任何语言时接触到的第一个集合

        String[] strs = new String[10];
        strs[0] = "a";
        strs[1] = "b";

        List<String> lists = new ArrayList<>();

数组也是对象;相较于普通的数组,ArrayList在创建时不必指定大小,会在进行增删操作时动态的调整自己的大小。

数组与List相互转换
        //数组转换为List
        lists = Arrays.asList(strs);
        //List 转 数组
        strs =  lists.toArray(strs);

将集合中的对象进行排序

使用Collections.sort()方法对集合中的对象进行排序的两种方式。

  • 该对象实现了Comparable接口,明确指定了排序方式。
public class Student implements Comparable<Student>{
    private String name;
    private int id;

    public Student(String name, int id) {
        this.name = name;
        this.id = id;
    }

    @Override
    public int compareTo(Student student) {
        //按照name(字符串值)从小到大排序
        return name.compareTo(student.name);
    }
}

这样,由Student对象构成的集合就可以使用Collections.sort()方法进行排序了。

        List<Student> students = new ArrayList<>();
        for(int i=0;i<10;i++) {
            Student student = new Student(i + "-name", i);
            students.add(student);
        }
        Collections.sort(students);
  • 实现Comparator接口,动态定义排序方式。
    private static class ComprareByName implements Comparator<Student> {
        @Override
        public int compare(Student student, Student t1) {
            //按name 从大到小进行排序
            return t1.getName().compareTo(student.getName());
        }
    }

这样就可以使用重载的sort方法进行排序

        Collections.sort(students,new ComprareByName());

Comparator的compare实现方式会覆盖集合元素中默认的实现,也就说虽然Student内部是从小到大排序,但会被这里ComprareByName的实现所覆盖

为了使集合有序,我们也可以使用TreeSet。

TreeSet 以有序的状态存储元素,并防止重复。

因此,为了正确的使用TreeSet 需要注意以下两点:

  • 同上面第一点,加入到TreeSet集合中对象(元素)必须实现了Comparable接口。
  • 使用重载,用Comparator参数的构造函数来创建TreeSet。
        TreeSet<Student> mStudents = new TreeSet<>(new ComprareByName());

集合分类

  • Collections
    • List 索引位置明确的集合
    • Set 不允许重复元素的集合
  • Map 使用成对key和value的集合

以上三种集合的类图如下:

《从Android到Java(二)》

Java 集合框架简图

从中可以看到我们常用的一些类,如ArrayList,HashMap 等。

如何检查对象的重复性?如何判定两个对象相等?

        Student a = new Student("a", 1);
        Student b = new Student("b", 2);
        Student c = a;
        Student d = new Student("b", 2);

        System.err.println("a.hashCode()="+a.hashCode());
        System.err.println("b.hashCode()="+b.hashCode());
        System.err.println("c.hashCode()="+c.hashCode());
        System.err.println("d.hashCode()="+d.hashCode());

输出

a.hashCode()=1118140819
b.hashCode()=1975012498
c.hashCode()=1118140819
d.hashCode()=1808253012

通过打印a,b,c,d 四个对象的hashcode 值,可以看到引用变量a和c 指向的是堆上的同一个对象,因此他们的hash值必然是相等的。引用对象b和d虽然创建的对象内容是一致的,但他们任然是分别指向两个不同的对象,因此hash值也是不同的。

下面我们用equals 方法比较一下这四个对象

        System.out.println("a.equals(b) " + a.equals(b));
        System.out.println("a.equals(c) " + a.equals(c));
        System.out.println("b.equals(d) " + b.equals(d));

输出

a.equals(b) false
a.equals(c) true
b.equals(d) false

结果很明显,因为Object的equals 默认执行的是对象引用是否相等的比较,因此b.equals(d)的结果为false。

    public boolean equals(Object obj) {
        return (this == obj);
    }

但是,从我们创建对象的代码可以得知,b和d 这两个对象内容是一样的;因此,这两个对象就应该是同一个,他们应该是相等的;因此,我们可以覆盖默认的hashCode()和equals()方法。

public class Student implements Comparable<Student>{
    private String name;
    private int id;


    @Override
    public int hashCode() {
        return name.hashCode();
    }

    @Override
    public boolean equals(Object o) {
        Student mStudent= (Student) o;
        return getName().equals(mStudent.getName());
    }

    public String getName() {
        return name;
    }
}

现在再次测试,就可以看到b.equals(d)的结果为true了。

因此,我们可以得出以下结论:

  1. 如果两个对象相等,则hashcode值必须相等
  2. 两个对象的hashcode值相等,他们也不一定是相等的。(当然,这种几率应该很小)
  3. equals()默认执行的是== 比较,也就是所回去测试两个引用是否对堆上同一个对象引用;因此,对于我们自己创建的对象,应该同时覆盖equals()方法和hashCode()方法,规范检测对象一致性的标准。

泛型

使用泛型可以构建出类型更加安全的集合,让问题可能的在编译期就被发现,而不是等到了执行期才冒出来

public class ArrayList<E> extends AbstractList<E> implements List<E>

以常用的ArrayList为例,使用泛型后代表以后所有对ArrayList的操作,添加,删除或返回的对象类型都是E,不能是其他类型。

        List<Student> students = new ArrayList<>();
        for(int i=0;i<10;i++) {
            Student student = new Student(i + "-name", i);
            students.add(student);
        }

students 中添加的元素只能是Student类型的,不能是其他;同时从students结合中获取到的对象也一定是Student类型。

从泛型的角度看,extends和implements是等价的,都表示当前类是一个……。对ArrayList来说,他既是一个AbstractList,也是List。

当泛型遇到多态

现在有Student的子类:SeniorStudent和CollegeStudent。

    private static void printStudents(List<Student> students) {
        for (Student mStudent : students) {
            System.out.println(mStudent.getName());
        }
        //面对这样的情况,这个方法只能接受List<Student>类型的参数
        students.add(new SeniorStudent("hacker", 999));
    }

    public static void main(String[] args) {
        ArrayList<Student> students = new ArrayList<>();
        //因为泛型,List现在可以接受所有Student类型的对象
        students.add(new Student("mike",001));
        students.add(new CollegeStudent("lucy",002));
        students.add(new SeniorStudent("tom", 003));
        //
        printStudents(students);

        List<CollegeStudent> colleges = new ArrayList<>();
        colleges.add(new CollegeStudent("a", 100));
        colleges.add(new CollegeStudent("b", 101));
        colleges.add(new CollegeStudent("c", 102));
        //这样做是不行的
        printStudents(colleges);

    }

在上面的代码中,printStudents(List<Student> students),我们可以这样

printStudents(ArrayList<Student>)

但却不能这样

printStudents(List<CollegeStudent>)

在泛型泛型方法中,参数中的集合可以是泛型,但集合中的对象不能是泛型。其中的道理我们通过printStudents 方法中最后一行语句很容易理解。因为你不能保证使用集合的方法,会对集合做怎样的操作。为了保证集合的安全性,这是很好的做法。

但是,这样不就丧失了多态的意义吗?如果不能用子类作为集合的类型,那难道要为每一个Student的子类型,单独写一个printStudents()方法吗? 其实不必,只要做如下改动即可:

    private static void printStudents(List<? extends Student> students) {
        for (Student mStudent : students) {
            System.out.println(mStudent.getName());
        }
        //当使用通配符声明后,将不能再向集合中添加元素,因此以下语句非法
        students.add(new SeniorStudent("hacker", 999));
    }

这种情况虽然从语法角度看似合理,但编译器会帮我们做限制,限制再次修改集合中的元素

这样printStudents(List<CollegeStudent>)就变得合法了。

当然,为了更容易理解,也可以这样声明:

    private static <T extends Student> void printStudents(List<T> students) {
        for (Student mStudent : students) {
            System.out.println(mStudent.getName());
        }
        //当使用通配符声明后,将不能再向集合中添加元素,因此以下语句非法
        students.add(new SeniorStudent("hacker", 999));
    }
    原文作者:java集合源码分析
    原文地址: https://juejin.im/entry/594fd0985188250d753a14e4
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞