集合与泛型,如何使用比较器,hashCode与equals的区别
2014我在郎木寺敖包下,友人所拍
微信公众号
本篇看一下关于集合的一些知识点。
集合与泛型
首先来看一下集合与泛型的结合使用,JDK1.5之后为了约束类型,更新了泛型技术。
ArrayList不加泛型
我们首先利用ArrayList来盛放元素,由于没有泛型限制,我们添加了三种类型,可以通过编译,获取时可以根据不同类型强制转换,或者使用Object盛放获取的元素,都可以通过编译,并可以执行成功。
ArrayList<Object>
接下来为ArrayList添加<Object>泛型限制,使用相同手段获取元素,也可以编译通过,并执行成功,与不加泛型限制的ArrayList效果相同。
ArrayList<Integer>
接下来为ArrayList arrayList3添加<Integer>泛型,并将arrayList1赋值给它,这时arrayList3的get操作只能返回Integer,但是实际上get(2)为一个String对象,这时就会抛出java.lang.ClassCastException类型转换异常。
此时ArrayList里的元素只能以Integer的形式获取,添加,这样就限制了ArrayList的元素类型,可以看到add方法添加别的类型元素无法通过编译。
ArrayList<?>
接下来我们看一下ArrayList<?>的用法,当我们创建一个ArrayList<?> arrayList4时,发现无法add任何元素,但是可以将arrayList1的所有元素赋值给arrayList4。<?>通常用来作为参数接收外部的集合,或者返回不确定类型的参数。
List<T>只能接收一种类型的参数,实际上JDK还为我们提供了更多选择,接下来我们来看一下<? extends T>与<? super T>这两种泛型语法在集合中的使用方式。
<? extends T>
可以赋值给T类型及T的子类类型的集合,上界为T,取出来的类型带有泛型限制,向上强制转换为T。null可以表示任何类型,所以除了null,任何元素都无法放入<? extends T>类型集合。
<? super T>
可以赋值给T类型及T的父类类型的集合,下界为T,<? super T>该类型集合内的元素泛型丢失,无法从中获取元素。
对于<? extends T>与<? super T>来讲,一个主要适用于消费或者获取元素,一个则是生产和放入元素。
接下来举一个例子来看一下这两种泛型语法在集合中的应用。
首先创建三个类,Animal,Dog,Huskie.Huskie继承Dog,Dog继承Animal。
然后我们分别创建三个不同泛型的集合,并向内add对应泛型类型的元素。
此时我们可以看到,三种类型的集合都可以添加元素。接下来以Dog为基准,创建<? extends Dog>与<? super Dog>两种类型的ArrayList。
此时,我们将animal集合赋值给ArrayList<? extends Dog> dogExtends时会发生编译错误,动物也包含猫,鸟类等,是不可以赋值给狗类型下的。
但是我们将animal集合赋值给dogSuper集合是可以的,huskie赋值给dogSuper时会编译错误,因为dogSuper只能接受Dog类型及其父类的元素集合赋值。
两个集合都可以被赋值Dog类型的元素集合。
接下来我们对这两种集合进行赋值。
dogExtends,任何元素都无法赋值,是因为多有List<? extends T>泛型限制除null以外,所有类型元素都无法进行add操作。
dogSuper可以添加元素,但是只能添加Dog本身及其子类,所以animal类型元素无法add。
接下来是get操作,dogExtends在get操作时,可以返回Object类,可以返回Dog类,但是无法返回Huskie类型,因为集合中可能存在萨摩或者柯基。
dogSuper进行get操作因为类型丢失,只能返回Object类型。
如果一个集合一直存放元素属于add first,我们可以使用<? super T>泛型限制,如果一个集合一直获取元素则属于get first,我们可以使用<? extends T>泛型限制。
比较器 Comparable与Comparator
我们在平时对元素进行排序中,通常使用比较器来实现对象的排序,常用的接口两个Comparable与Comparator,第一个为内部比较器,第二个为外部比较器。
先来看一下Comparable如何使用,为什么叫它内部比较器,首先我们创建一个Student类,有名字与年龄两个成员变量,我们将age作为首要条件排序,name作为次要条件。
接下来我们让Student继承Comparable接口,并实现它的compareTo方法。
因为我们需要在Student类内部实现,所以我们就把Comparable比作一个内部比较器。接下来我们看一下效果,创建一个TreeSet无序集合,创建元素放入,使用迭代器全部迭代并打印。
在放入元素时是无序的,然后看一下迭代结果:
首先按照年龄排序,年龄相同,我们使用String类的内部比较器进行比较可以看到xiao6与xiao7年龄相同,但是会按照name排序。
接下来看一下外部比较器Comparator的使用方式,使用Comparator无需修改Student类,只需要配合Collection的sort方法使用即可实现集合内元素排序。
创建一个普通的Bean,Human类。
创建一个外部比较器MyComparator实现Comparator接口实现compare方法。
接下来我们创建一个LIst集合并随机放入human元素,并使用Collections.sort方法用MyComparator对List进行排序后遍历List并打印元素。
打印结果为:
hashCode与equals
hashCode与equals都是Object类的方法。
在哈希相关的容器中,需要大量的比对,根据对象生成的哈希值可以使存取速度更快,hashCode与equals两个方法都是用来比较两个对象是否相等的方法。
由于hashCode方法计算哈希值可能存在哈希冲突的情况,所以还需要equals方法进行比较一次equals方法是绝对可靠的,hashCode不一定可靠。
所以如果两个对象的equals相等则他们的hashCode一定相等,任何时候覆写equals都必须同时覆写hashCode。
当hashCode相同时,需要再调用一下equals方法比较,如果hashCode都不同则直接跳过equals直接返回不同,是一个短路操作。
在hashset里要求对象不能重复,内部要对添加进去的每个对象进行对比,而它的对比规则就是像上面说的那样,先比较hashCode()产生的哈希值,如果哈希值相同,再用equals方法验证,如果哈希值都不同,则直接跳过equals方法,这样对比的效率就很高了。
所以好的哈希算法应该尽可能的让元素分配均匀,降低冲突的可能。
接下来我们举一个实际使用的例子,set集合中元素是不可重复的,但是由于哈希值基本上是由对象地址进行相关计算得到的int类型数据,所以例如我们在创建上Student类时,哪怕内容一样,在set中也会存储三个Student对象。
执行结果:
由于没有覆写hashCode方法,Object.hashCode()方法默认根据每一个对象的地址生成不同的哈希码,所以hashCode不相等则直接判断两个对象不相等,所以我们需要覆写hashCode方法与equals方法来根据id和name来保证元素的不同。
在这里覆写了Student类的hashCode方法与equals方法,来保证元素id的唯一性。
再次运行向hashSet中添加元素,返回size为1.
-END-