复习
1.数组作为一种很简单的容器使用,有诸多的缺点,所以针对不同的需求需要有更适合使用的容器,jdk提供了解决方案,集合框架。Collection framework。
2.学习容器就是学习具有不同功能特点的容器类。
3.容器分类:序列、集、映射
4.Collection 是集合部分的顶层的接口:元素无序不唯一,可以有null元素。
5.List 是Collection的子接口:元素有序(添加元素的顺序)不唯一,可以有null。
6.Java.util.ArrayList 底层使用数组实现,是一个带封装的数组的实现类。
7.Java.util.LinkedList 底层使用的数据结构为双向链表。每一个节点包含了数据+前驱节点引用prev+后继节点引用 next。
8.泛型对于容器来说,解决了2个问题:1、避免对容器添加不想加入的元素的类型。2、当想使用元素的具体类型的时候,不需要向下强转。
9.Java.util.Iterator 三个方法。hasNext() 游标后是否有元素,next()让游标后移一个元素的位置,并返回该元素,remove() 删除next()方法最后返回的元素对象。
第一节 ListIterator
迭代器Iterator是集合框架中所专有的遍历方式,也成为集合框架中标准的遍历方式,那么专门针对List这一支容器呢,还有一个迭代器的子类,是专门用于迭代List子类的。
ListIterator 是Iterator 的子接口。是专门用于遍历List 类型容器的迭代器。可以实现从头到尾,和从尾到头的双向的遍历,可以在遍历的过程中,实现对容器中元素的,增删改查的操作。具有不会一次失效的特点。
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
/**
* 学习ListIterator
*/
public class TestListIterator {
public static void main(String[] args) {
List<String> list=new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
//得到list对象的ListIterator对象
ListIterator<String> it=list.listIterator();
while(it.hasNext()){
//it.next(); //让游标往后走一个位置,并返回该元素
//it.previous(); //让游标往回走一个位置,并返回该元素
String next=it.next();
if("c".equals(next)){
//修改刚刚被返回的对象(也就是next所指向的对象)
it.set("C");
}
if("d".equals(next)){
//在刚刚被返回的对象后面添加元素(也就是next所指向的对象)
it.add("D");
}
System.out.println(next);
}
System.out.println();
//上面遍历一次之后,游标到最末尾了,所有再从后往前遍历
while(it.hasPrevious()){
System.out.println(it.previous());
}
System.out.println();
//上面的遍历一遍之后,游标到最前面了,又可以从头到尾又遍历一次
while(it.hasNext()){
System.out.println(it.next());
}
//死循环
/*
while(it.hasPrevious()){
it.previous();//游标往回走一格
it.next(); //游标往后走一格
}
*/
}
}
问题:哪些类型的容器可以使用迭代器遍历?
如果这个类里面提供了得到迭代器的方法,那么就可以使用迭代器遍历,(ArrayList、Vector、LinkedList里面都有一个iterator()方法,这个方法返回的是Iterator的子类对象。所以一个类里面有这个iterator()这个方法,那么一定可以使用迭代器遍历。那么我们就去看哪些类里面包含了这些方法??还有一个问题就是这个iterator()是从哪里来的呀,根儿是什么呀?) 看下源码:
//ArrayList源码
//Overrides mehthod in java.util.AbstractList
//Overrides method in java.uitl.List
public Iterator<E> iterator() {
return new Itr();
}
//List源码
//Overriders method in java.util.Collection
/**
* Returns an iterator over the elements in this list in proper sequence.
*
* @return an iterator over the elements in this list in proper sequence
*/
Iterator<E> iterator(); //这是一个抽象方法,那么List的子类都要实现这个方法
//我们再来看Collection的源码
//Overrides method java.lang.Iterable
/**
* Returns an iterator over the elements in this collection. There are no
* guarantees concerning the order in which the elements are returned
* (unless this collection is an instance of some class that provides a
* guarantee).
*
* @return an <tt>Iterator</tt> over the elements in this collection
*/
Iterator<E> iterator(); //那么Collection的子类都要实现这个方法
//我们再来看Iterable这个接口的源码(这个接口的源码都在这里了):
//在这里我们又认识了一个接口,Iterable(可以被迭代的)
public interface Iterable<T> {
//这个接口里面定义了一个如何得到当前容器对象的迭代器的方法,
//那么所有实现这个方法的子类都要重写它,而且要得到当前容器对象上的迭代器对象。
Iterator<T> iterator();
//
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
//
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
//我们再看一下Iterator这个接口的源码(这个接口的源代码都在这里了)
public interface Iterator<E> {
boolean hasNext();
E next();
//
default void remove() {
throw new UnsupportedOperationException("remove");
}
//
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
因为Collection继承了Iterable接口,实现了iterator()方法,所以Collection和它的子类可以使用迭代器进行遍历。
1.Iterator() 返回的是Iterator 的实现的子类对象。如果一个类中包含了该方法,那么该类容器的对象一定是可以使用迭代器遍历的。
2.Java.lang.Iterable 接口中定义了 iterator() 方法。所有该接口的子类都需要实现该方法,用来得到当前容器对象的迭代器对象。Collection 接口继承了该接口,所以所有的Collection的实现在子类都应该实现该方法。所以 List 和 Set 的子类都可以使用迭代器遍历元素。
3.所有实现了Iterable接口的类的对象都实现了iterator()方法,都可以生成一个迭代器对象,可以使用Iterator迭代器进行遍历。Iterator中的抽象方法,是由它的子类(具体是哪个子类实现这里不讨论)实现的,我们平时用的Iterator it=new set.iterator(); 这里是父类引用指向子类对象,当调用it.next()方法时,就是多态了。
第二节Set
1.HashSet
Set 元素的特点无序、唯一,可以有一个null.
HashSet 元素特点:无序、唯一,可以有一个null。底层使用的数据结构为哈希表。哈希表也称为散列表。
2.哈希表的特点和HashSet 的工作原理
说明:1.对于一个Integer对象来说,它的哈希码的就是它自身。
2.hashCode()方法是Object类当中的一个方法,所以所有的对象都可以调用。
3,。假如让我们自己写一个遍历的算法去遍历一个哈希表的数据结构怎么遍历呀?我们要以一维数组为基础,从头开始遍历,先遍历下标是0的,如果它有链表,那是不是还要把它的链表遍历完了呀!以此类推。。。。。
3.我们说过在哈希表中查找一个元素是很快的,由于它是无序的,所以不能根据下标进行查找,只能根据内容进行查找。
4.查找一个元素的过程,先计算它的哈希码,然后做同样的算法运算,得到位置值。然后去哈希表中找到相应的位置,如果没有链表,直接比较、取数据就行了;如果这里面还有一个链表,那么就从链表的头,用equals进行比较,直到相等的时候取值。
5.HashSet重要在数据结构,用的不多,因为对对象的访问只能通过内容,不能通过索引或者通过其他的信息。
6.如何提高哈希表的工作效率?当然是链表的长度越短效率越高了。如何保证链表越短,当然是元素存的越均匀链表越短,所以我们希望元素可以均匀的散列在这个数组范围内。
7.没有获得某一个元素的方法,只能判断里面有没有这个元素,但是不能获得它,(仅仅能通过遍历来获得),所以用的非常的少。
/**
* 测试HashSet的一些方法
*/
public class TestHashSet {
public static void main(String[] args) {
Set<String> set=new HashSet<>();
set.add("China");
set.add("Japan");
set.add("America");
set.add("Italia");
set.add("China");
//不能直接修改
//可以删除
set.remove("Japan");
//添加(这个添加并不一定刚好添加到刚被删元素的位置)
set.add("China_1");
//可以有一个null元素
set.add(null);
set.add(null);
//没有获得某个元素的方法
System.out.println(set);
//可以判断是不是包含某个元素
System.out.println(set.contains("China"));//返回true
//元素个数
set.size();
//set.clear();
set.isEmpty();
set.toArray();
//遍历两种方式:迭代器和foreach
Iterator<String> it=set.iterator();
while(it.hasNext()){
String s=it.next();
System.out.println(s);
}
for (String s:set) {
System.out.println(s);
}
}
}
3.hashCode和equals方法
Object类的hashCode()方法是一个native 方法,调用的底层的c的方法实现的。实际上,由 Object 类定义的 hashCode 方法会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的) 。
如果希望不同的对象,如果内容相同,也不能添加到HashSet 中的话,那么需要在元素对应的类中重写 hashCode(),以保证相同内容的对象返回相同的哈希码值。还要重写equals方法保证内容相同,equals返回true。
如果希望某种类型的对象添加到HashSet容器中,而且希望根据内容去重,那么就需要在该类中重写hashCode 和 equals 方法。
重写equals 和 hashCode需要遵循的规范:如果两个对象通过equals 比较相等,那么哈希码的值是否必须一致?是的,必须一致。如果equals比较不同,那么哈希码可以相同,但是要尽量不同,才可以保证HashSet的高效率。
重写equals 和 hashCode需要遵循的规范:如果两个对象的哈希码一致,那么equals 方法比较是否必须一致?不是必须的。
源码Object类里面的hashCode() public native int hashCode(); |
---|
说明:1.重写hashCode()方法的目的就是,相同内容的对象,它的哈希码是一致的。重写equals()方法的目的是,相同内容的对象通过equals比较实相等的。
这里面的native,指本地的意思,调用过的本地的方法,也就是调用的底层的用C的代码实现的方法,(java里面有些功能,直接使用java是实现不了的,必须依赖于更底层的语言才能实现)
import java.util.HashSet;
import java.util.Set;
/**
*如果两个对象的内容相等&