从Android到Java(二)

作者: IAM四十二 | 来源:发表于2017-06-25 23:00 被阅读609次

    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的集合

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

    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));
        }
    

    相关文章

      网友评论

        本文标题:从Android到Java(二)

        本文链接:https://www.haomeiwen.com/subject/bgtdcxtx.html