一.集合的引入
1.数组:存储同一种数据类型的集合容器。
2.数组的特点:
(1) 只能存储同一种数据类型的数据。
(2) 一旦初始化,长度固定。
(3) 数组中的元素与元素之间的内存地址是连续的。
3.注意:Object类型的数组可以存储任意类型的数据,因为Object类是所有类的一个父类。
4.实例:
public class Demo1 {
public static void main(String[] args) {
Object[] arr = new Object[3];
arr[0] = "abc";
arr[1] = 'a';
arr[2] = 12;
for(int i = 0;i<arr.length;i++){
System.out.println("数组中的元素:"+arr[i]);
}
}
}
运行结果如下图所示:
5.集合引言:
比如要收集每个人的兴趣爱好,如果用数组String[] arr= new String[1000];兴趣爱好数组长度给的太长浪费,给的太短又不够,数组的长度不好操控,集合可以帮我们解决这个问题。
6.集合:集合是存储对象数据的集合容器。
7.集合比数组的优势:
(1) 集合可以存储任意类型的对象数据,数组只能存储同一种数据类型的数据(Object类型的数组除外)。
(2) 集合的长度是会发生变化的,数组的长度是固定的。
8.集合类体系:
---| Collection:单例集合的根接口(集合类可以划分为List和Set两种,List和Set是Collection的子接口)
---------| List:如果是实现了List接口的集合类,存储元素具备的特点: 有序,可重复。
---------------| ArrayList:底层是维护了一个Object数组实现 的, 特点: 查询速度快,增删慢。
---------------| LinkedList:底层是使用了链表数据结构(链接列表)实现的,特点: 查询速度慢,增删快。
---------------| Vector(了解即可):底层也是维护了一个Object的数组实现的,实现与ArrayList是一样的,但是 Vector是线程安全的,操作效率低。---------| Set:如果是实现了Set接口的集合类,存储元素具备特点: 无序,不可重复。
---------------| HashSet:底层是使用了哈希表来支持的,特点: 存取速度快。
---------------| TreeSet:如果元素具备自然顺序的特性,那么就按照元素自然顺序的特性进行排序存储。
二.Collection类
1.在Java中有很多的容器类,所以需要为这些容器类提供一个规范,如果实现了它的接口那么就可以认为是一个容器。Collection只是定义了集合类中最基本的规范,另外用了两个子接口在它的基础上又扩展了一些功能,增加了一些约束,就形成了List和Set两个派别的集合类。
2.Collection接口中的方法:
(1) 增加的方法:
① add(E e):添加成功返回true,添加失败返回false。
public class Demo1 {
public static void main(String[] args) {
//Collection c = new Collection();会报错,因为Collection是一个接口,接口是不能new的,但是我们又要用这个方法,我们就可以使用多态,我们可以创建它的实现类
Collection c = new ArrayList();
//集合可以存储任意类型的数据
c.add("abc");
c.add(1);
c.add(3.14);
System.out.println("集合的元素:"+c);
}
}
运行结果如下图所示:
注意:集合的长度是会发生变化的,也就是说不管存多少数据都可以,如果长度不够用会自动增长。
public class Demo2 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("张三");
c.add("李四");
System.out.println("添加成功吗?"+c.add("狗娃"));
System.out.println("集合的元素:"+c);
}
}
运行结果如下图所示:
② addAll(Collection c):把一个集合的元素添加到另外一个集合中去。
public class Demo4 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("张三");
c.add("李四");
System.out.println("添加成功吗?"+c.add("狗娃"));
//创建集合
Collection c2 = new ArrayList();
c2.add("王五");
c2.add("狗子");
c.addAll(c2);//把c2的元素添加到c集合中去。
System.out.println("集合的元素:"+c);
}
}
运行结果如下图所示:
(2) 删除的方法:
① clear():移除此collection中的所有元素;
public class Demo5 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("张三");
c.add("李四");
System.out.println("添加成功吗?"+c.add("狗娃"));
System.out.println("执行clear前集合中的元素:"+c);
c.clear();//clear()清空集合中的元素
System.out.println("执行clear后集合中的元素:"+c);
}
}
运行结果如下图所示:
② remove(Object o):指定集合中的元素删除,删除成功返回true,删除失败返回false。
public class Demo6 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("张三");
c.add("李四");
System.out.println("添加成功吗?"+c.add("狗娃"));
System.out.println("执行remove前集合中的元素:"+c);
System.out.println("删除成功吗?"+c.remove("狗娃"));
System.out.println("删除成功吗?"+c.remove("美美"));
System.out.println("执行remove后集合中的元素:"+c);
}
}
运行结果如下图所示:
③removeAll(Collection c):移除此 collection 中那些也包含在指定 collection 中的所有元素。
public class Demo7 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("张三");
c.add("李四");
System.out.println("添加成功吗?"+c.add("狗娃"));
//创建集合
Collection c2 = new ArrayList();
c2.add("王五");
c2.add("狗子");
c2.add("狗娃");
System.out.println("执行removeAll之前集合中的元素:"+c);
c.removeAll(c2);//删除c集合中与c2的交集元素。
System.out.println("c2集合中的元素:"+c2);
System.out.println("执行removeAll之后集合中的元素:"+c);
}
}
运行结果如下图所示:
④ retainAll(Collection c) :保留交集元素,其他非交集元素删除掉。
public class Demo8 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("张三");
c.add("李四");
System.out.println("添加成功吗?"+c.add("狗娃"));
//创建集合
Collection c2 = new ArrayList();
c2.add("王五");
c2.add("狗子");
c2.add("狗娃");
System.out.println("执行retainAll之前集合中的元素:"+c);
c.retainAll(c2);//删除c集合中与c2的非交集元素。
System.out.println("c2集合中的元素:"+c2);
System.out.println("执行retainAll之后集合中的元素:"+c);
}
}
运行结果如下图所示:
(3) 查看的方法:
① size():返回此 collection 中的元素数。
public class Demo9 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("张三");
c.add("李四");
System.out.println("添加成功吗?"+c.add("狗娃"));
System.out.println("查看集合中的元素个数:"+c.size());
System.out.println("集合的元素:"+c);
}
}
运行结果如下图所示:
(4) 判断的方法:
① isEmpty():如果此 collection 不包含元素,则返回 true。
② contains(Object o):如果此 collection 包含指定的元素,则返回 true。
public class Demo10 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("张三");
c.add("李四");
System.out.println("判断集合是否为空?"+c.isEmpty());
System.out.println("判断集合中是否存在指定的元素:"+ c.contains("李四"));
}
}
运行结果如下图所示:
③ containsAll(Collection<?> c):如果此 collection 包含指定 collection 中的所有元素,则返回 true。
class Person{
int id;
String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "{编号:"+this.id+" 姓名:"+ this.name+"}";
}
@Override
public boolean equals(Object obj) {
Person p = (Person)obj;
return this.id == p.id ;
}
//java规范: 一般重写equlas方法我们都会重写hashCode方法的。
@Override
public int hashCode() {
return this.id;
}
}
public class Demo11 {
public static void main(String[] args) {
// 集合中添加自定义的元素
Collection c = new ArrayList();
c.add(new Person(110, "狗娃"));
c.add(new Person(119, "狗剩"));
c.add(new Person(120, "铁蛋"));
Collection c2 = new ArrayList();
c2.add(new Person(110, "狗娃"));
c2.add(new Person(119, "狗剩"));
c2.add(new Person(104, "陈狗剩"));
System.out.println("c集合有包含c2集合中的所有元素吗?" + c.containsAll(c2));
// 如果在现实生活中,只要身份证编号一致,那么就为同一个人。
System.out.println("存在该元素吗?" + c.contains(new Person(120, "铁蛋"))); // 其实contains方法内部是依赖于equals方法进行比较的。equals方法默认是比较两个对象的内存地址的
}
}
运行结果如下图所示:
(5) 迭代的方法:
① toArray():返回包含此 collection 中所有元素的数组。
public class Demo1 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("狗娃");
c.add("狗剩");
c.add("铁蛋");
c.add("美美");
//遍历集合的元素------>方式一: 可以使用toArray方法。
Object[] arr = c.toArray(); // toArray() 把集合的元素存储到一个 Object的数组中 返回。
for(int i = 0 ; i<arr.length ; i++){
System.out.print(arr[i]+",");
}
}
}
运行结果如下图所示:
② iterator():返回在此 collection 的元素上进行迭代的迭代器。
public class Demo2 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("狗娃");
c.add("狗剩");
c.add("铁蛋");
c.add("美美");
Iterator it = c.iterator(); //返回一个迭代器 疑问:iterator()方法返回的是一个接口类型,为什么接口又可以调用方法可以使用呢? 虽然iterator返回了一个接口类型,但是iterator 实际 上返回的是iterator接口的实现类对象。
System.out.println("有元素可以遍历吗?"+it.hasNext());
//既然有元素可以遍历就需要去获取元素。
System.out.println("获取元素:"+it.next());//next方法每次只会抓取一个元素。要抓取四个元素就需要四个next方法。
System.out.println("获取元素:"+it.next());
System.out.println("获取元素:"+it.next());
System.out.println("获取元素:"+it.next());
//如果抓取完以后没有元素可以迭代,再继续使用next就会报异常
//System.out.println("获取元素:"+it.next());
}
}
运行结果如下图所示:
Demo2中重复代码的改进:
//Demo2中重复代码的改进
public class Demo3 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("狗娃");
c.add("狗剩");
c.add("铁蛋");
c.add("美美");
Iterator it = c.iterator(); //返回一个迭代器 疑问:iterator()方法返回的是一个接口类型,为什么接口又可以调用方法可以使用呢? 虽然iterator返回了一个接口类型,但是iterator 实际 上返回的是iterator接口的实现类对象。
while(it.hasNext()){ // hasNext() 问是否有元素可以遍历。
System.out.println("元素:"+ it.next()); //获取元素
}
}
}
运行结果如下图所示:
3.补充:迭代器
(1) 迭代器的作用:就是用于抓取集合中的元素。
(2) 迭代器的方法:
① hasNext():问是否有元素可以继续遍历?如果有元素可以遍历,返回true,否则返回false 。
② next():获取元素。
③ remove():移除迭代器最后一次返回 的元素。也就是从迭代器指向的Collection中移出迭代器返回的最后一个元素。
(3) 迭代器的原理
现在集合中有四个元素,当我们取c.iterator()的时候,执行到那一句话会获取到一个迭代器,获取到迭代器就相当于获取到容器中的爪子一样,一旦获取到迭代器,迭代器中就有一个指针指向了集合中的第一个元素。
(4) 实例:
① 实例一
public class Demo4 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("狗娃");
c.add("狗剩");
c.add("铁蛋");
c.add("美美");
Iterator it = c.iterator();
it.next();
it.next();
it.remove();//如果Iterator it = c.iterator();后直接运行这句话,那么是会报错的,因为迭代器没有获取到元素。
System.out.println("集合的元素:"+c);
}
}
运行结果如下图所示:
② 实例二
public class Demo5 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("狗娃");
c.add("狗剩");
c.add("铁蛋");
c.add("美美");
Iterator it = c.iterator();
//这里的While循环实际上做的是清空集合的元素。
while(it.hasNext()){
it.next();
it.remove();
}
System.out.println("集合的元素:"+c);
}
}
运行结果如下图所示:
(5) NoSuchElementException 没有元素的异常,在迭代中经常见。
出现的原因: 没有元素可以被迭代了。
三.List类
1.List:如果是实现了List接口的集合类,该集合类具备的特点:有序,可重复。
2.有序:集合的有序不是指自然顺序,而是指添加进去的顺序与元素出来的顺序是一致的。
3.List接口中特有方法
注意:List是一个接口,不能直接new,只能new它的实现类。
(1) 添加的方法:
① add(int index, E element):在列表的指定位置插入指定元素 。
public class Demo1 {
public static void main(String[] args) {
List list= new ArrayList();
list.add("狗娃");
list.add("狗剩");
list.add("铁蛋"); //把元素添加到集合的末尾处。
list.add("狗娃");
list.add(1, "张三"); // 把元素添加到集合中的指定索引值位置上。也就是在狗娃和狗剩之间插入张三
System.out.println("集合的元素:"+list);
}
}
运行结果如下图所示:
② addAll(int index, Collection<? extends E> c):将指定 collection 中的所有元素都插入到列表中的指定位置。
public class Demo2 {
public static void main(String[] args) {
List list= new ArrayList();
list.add("狗娃");
list.add("狗剩");
list.add("铁蛋"); //把元素添加到集合的末尾处。
List list2 = new ArrayList();
list2.add("张三");
list2.add("李四");
list.addAll(2,list2); //把list2的元素添加到list集合指定索引值的位置上。也就是在狗剩和铁蛋之间插入list2集合中的元素
System.out.println("集合中的元素:"+list);
}
}
运行结果如下图所示:
(2) 获取的方法:
① get(int index):返回列表中指定位置的元素。
public class Demo3 {
public static void main(String[] args) {
List list= new ArrayList();
list.add("狗娃");
list.add("狗剩");
list.add("铁蛋"); //把元素添加到集合的末尾处。
System.out.println("get方法获取元素:"+list.get(1)); //根据索引值获取集合中的元素
//使用get方法遍历集合的元素:
//注意:只有在List接口下的实现类才能使用这种方式遍历,因为这个get方法是list接口特有的方法。
for (int i = 0; i < list.size() ; i++) {
System.out.print(list.get(i)+",");
}
}
}
运行结果如下图所示:
② indexOf(Object o):返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1。
③ lastIndexOf(Object o):返回此列表中最后出现的指定元素的索引;如果列表不包含此元素,则返回 -1。
④ subList(int fromIndex, int toIndex):返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图。public class Demo4 {
public static void main(String[] args) {
List list= new ArrayList();
list.add("狗娃");
list.add("狗剩");
list.add("铁蛋"); //把元素添加到集合的末尾处。
list.add("狗娃");
System.out.println("集合的元素是:"+ list);
System.out.println("找出指定元素第一次出现在集合中 的索引值:"+ list.indexOf("狗娃"));
System.out.println("找出指定元素第一次出现在集合中 的索引值:"+ list.indexOf("张三"));
System.out.println("找指定的元素最后一次出现在集合中的索引值:"+list.lastIndexOf("狗娃"));
List subList = list.subList(1, 3); //指定开始与结束的索引值截取集合中的元素。包头不包尾
System.out.println("子集合的元素是:"+ subList);
}
}
运行结果如下图所示:
(3) 修改的方法:
① set(int index, E element):用指定元素替换列表中指定位置的元素。
public class Demo5 {
public static void main(String[] args) {
List list= new ArrayList();
list.add("狗娃");
list.add("狗剩");
list.add("铁蛋"); //把元素添加到集合的末尾处。
System.out.println("执行set操作前集合中的元素:"+list);
list.set(2, "张三"); //使用指定的元素替换指定索引值位置的元素。
System.out.println("执行set操作后集合中的元素:"+list);
}
}
运行结果如下图所示:
(4) 迭代的方法:
① listIterator():返回此列表元素的列表迭代器(按适当顺序)。 返回的是一个List接口中特有的迭代器。
public class Demo6 {
public static void main(String[] args) {
List list= new ArrayList();
list.add("狗娃");
list.add("狗剩");
list.add("铁蛋"); //把元素添加到集合的末尾处。
list.add("美美");
ListIterator it = list.listIterator();
it.next();
System.out.println("获取上一个元素:"+it.previous());
}
}
运行结果:
4.List接口中特有的方法具备的特点: 操作的方法都存在索引值。
(1) 记住一句话:以后凡是用集合类的时候,如果想操作索引值, 那么就应该用List接口下的集合类,其他接口下的集合类是没有索引值的。
(2) 注意:只有List接口下面的集合类才具备索引值,其他接口下面的集合类都没有索引值。
5.补充:List接口下的迭代器
注意:每取到一个迭代器,那么迭代器就有一个指针指向了第0个元素。
(1) ListIterator特有的方法:
① hasPrevious():判断是否存在上一个元素。
② previous():当前指针先向上移动一个单位,然后再取出当前指针指向的元素。先移再取。
③ next(); 先取出当前指针指向的元素,然后指针向下移动一个单位。先取再移。
public class Demo7 {
public static void main(String[] args) {
List list= new ArrayList();
list.add("狗娃");
list.add("狗剩");
list.add("铁蛋"); //把元素添加到集合的末尾处。
list.add("美美");
ListIterator it = list.listIterator();
while(it.hasNext()){
it.next();
}
while(it.hasPrevious()){
System.out.println("元素:"+ it.previous());
}
}
}
运行结果如下图所示:
④ add(E e):把当前元素插入到当前指针指向的位置上。
public class Demo8 {
public static void main(String[] args) {
List list= new ArrayList();
list.add("狗娃");
list.add("狗剩");
list.add("铁蛋"); //把元素添加到集合的末尾处。
list.add("美美");
ListIterator it = list.listIterator();
it.add("张三");//把张三插入到狗娃的前面
System.out.println("集合中的元素:"+list);
}
}
运行结果如下图所示:
⑤ set(E e):替换迭代器最后一次返回的元素。
public class Demo9 {
public static void main(String[] args) {
List list= new ArrayList();
list.add("狗娃");
list.add("狗剩");
list.add("铁蛋"); //把元素添加到集合的末尾处。
list.add("美美");
ListIterator it = list.listIterator();
it.next();
it.next();
System.out.println("执行set操作前集合中的元素:"+list);
it.set("张三");//把狗剩替换为张三
System.out.println("执行set操作后集合中的元素:"+list);
}
}
运行结果如下图所示:
(2) 使用迭代器要注意的事项:
① 注意事项一:
public class Demo10 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("张三");
list.add("李四");
list.add("王五");
ListIterator it = list.listIterator(); //获取到迭代器
while(it.hasNext()){
System.out.print(it.next()+",");
it.add("aa"); // 把元素添加到当前指针指向位置
}
System.out.println("\r\n集合的元素:"+ list);
}
}
运行结果如下图所示:
通过上述代码和运行结果我们可以看出,每迭代一次加一个元素进去,实际上并不会出现死循环,遍历出来后并不会把新加进去的元素遍历,但实际上已经加了进去,当我们输出集合中的元素时是已经把新的元素加进去了,只不过在迭代的过程中,没有把新加进去的元素遍历出来而已,因为如果遍历出来就是一个死循环了。
它的设计过程如下: 目前每next一次,游标就向下移动一次,第一次执行add操作,实际上是把aa加入到张三和李四之间,如果这时候把aa遍历出来,这时候指针就指向aa,那就会没完没了的添加aa,实际上已经插入进去,那么它的设计是如何做到跳过它而不遍历呢? 通过查看源代码我们发现,执行add操作时,会把游标向下移动一位就会跳过这个新加入的元素不去遍历。 这样设计的原因就是为了避免死循环。
② 注意事项二:
public class Demo11 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("张三");
list.add("李四");
list.add("王五");
ListIterator it = list.listIterator(); //获取到迭代器
while(it.hasNext()){
System.out.print(it.next()+",");
list.add("aa"); // 把元素添加到集合的末尾处
}
System.out.println("\r\n集合的元素:"+ list);
}
}
运行结果如下图所示:
通过上述代码和运行结果我们发现:如果改成list.add("aa");这时候每迭代一次就会在末尾加一个aa,首先指针刚开始指向张三,执行next操作把张三取出来后就会在末尾添加aa元素,这时候指针就不能再跳了,如果跳到末尾,那么就只会遍历一个元素而已,所以这样直接会报错,不允许这样操作。因为如果这样操作,迭代主要是把这个集合中的元素全部输出,如果跳过只会遍历一个元素,不符合预期;如果不跳过的话那么就会出现死循环,所以就不允许这样操作。
③ 注意事项三:
public class Demo12 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("张三");
list.add("李四");
list.add("王五");
ListIterator it = list.listIterator(); //获取到迭代器,迭代器创建
list.add("aa");//如果只有这一个语句是没有问题的,因为这句话后面已经没有迭代器的用法了。
//如果在后面一旦出现迭代器的用法,不管是add还是next都会报错。如果把迭代器的用法移到上面又会正常。
//只要添加元素的后面没有迭代器的方法就没有问题,如果该变了元素的个数,后面还有迭代器的语句,那么就是在中间修改它,都是不允许的。
System.out.println("\r\n集合的元素:"+ list);
}
}
运行结果如下图所示:
④ 总结:
迭代器在变量元素的时候要注意事项: 在迭代器迭代元素 的过程中,不允许使用集合对象改变集合中的元素 个数(不管是添加元素add还是删除元素remove都不允许),如果需要添加或者删除只能使用迭代器的方法进行操作。如果使用过了集合对象改变集合中元素个数那么就会出现ConcurrentModificationException异常。
注意:只要不改变个数就可以,如果是set方法就可以用集合对象去操作。
什么是迭代元素 的过程中?: 这个过程就是迭代器从创建到使用结束的时间。
四.ArrayList类
1.ArrayList 底层是维护了一个Object数组实现 的, 特点: 查询速度快,增删慢。
2.ArrayList特点的原理:
(1) 我们用ArrayList实际上是在用Object数组来存储元素,数组中的元素与元素之间的内存地址是连续的,所以它的查询速度是很快的。比如它的内存地址是0x98,0x99,一开始就用指针指向了第0个元素,如果要执行查询操作比如list.get(100);查询100号的元素是多少,因为它们之间的内存地址是连续的,所以可以让指针直接移动100个单位直接可以找得到目标的元素,不连续就不可以这样做。
(2) 那为什么增加慢呢?假设目前Object数组的长度(容量)是7,假设现在添加第8个元素的时候,现在长度不够,就需要创建一个容量是原来数组1.5倍的新数组,长度为10,还要把旧数组中的内容拷贝到新数组中去,这个过程是很繁琐的。所以每次添加的时候都要首先检查长度够不够用,检查需要时间;当容量不够用时还需要把旧数组中的元素拷贝到新数组中,如果遇到数据量比较大的时候,过程会很漫长。
(3) 那为什么删除也慢呢?比如现在要把王五删了,那么现在就会空出一个位置,它就会把后面的元素再往前面挪动,把前面的空格填补上去,所以效率也是很低的。
3.什么时候使用ArrayList?
如果目前的数据是查询比较多,增删比较少的时候,那么就使用ArrayList存储这批数据。比如:高校的图书馆。
4.笔试题目:使用ArrayList无参的构造函数创建一个对象时,默认的容量是多少? 如果长度不够使用时又自增增长多少?
答:ArrayList底层是维护了一个Object数组实现的,使用无参构造函数时,Object数组默认的容量是10,当长度不够时,自动增长0.5倍。
5.ArrayList 特有的方法(很少用):
(1) ensureCapacity(int minCapaci上ty):用来指定初始容量,一般很少用,因为ArrayList有一个构造方法就可以指定初始容量,我们一般习惯用构造方法去指定。
(2) trimToSize():将此ArrayList实例的容量调整为列表的当前大小。比如说现在Object数组默认的长度是10,假设现在只添加了3个元素,那么现在还有7个位置是空的,这个方法就是把后面的7个删掉,节省内存空间,一般也不使用,因为如果以后还要添加元素,那么就还会执行拷贝的动作。效率又会降低,还不如用内存去换取效率更合适。
6.实例:
(1) 需求:编写一个函数清除集合中重复元素。 如果书号是一样就视为重复元素。要求:遍历集合元素的时候必须使用迭代器。get迭代器。
(2) 分析:先创建一个全新的集合,然后从存有重复元素的集合中把元素挨个取出,与全新集合中的元素比较,看全新集合中是否有这个元素,(每取出一个元素都问一下这个新的集合中是否有这个元素),没有就放进这个新的集合中,如果有就不要这个元素了。此时这个新的集合中存放的元素就没有重复元素了。
(3) 实例:
class Book{
int id;
String name;// 名字
public Book(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "{ 书号:"+ this.id+" 书名:"+ this.name+" }";
}
@Override
public boolean equals(Object obj) {
Book book =(Book)obj;
return this.id==book.id;
}
}
public class Demo7 {
public static void main(String[] args) {
ArrayList list= new ArrayList();
list.add(new Book(110,"java编程思想"));
list.add(new Book(220,"java核心技术"));
list.add(new Book(330,"深入javaweb"));
list.add(new Book(110,"java神书"));
ArrayList list2 = clearRepeat(list);
System.out.println("新集合的元素是:"+ list2);
}
public static ArrayList clearRepeat(ArrayList list){
//创建一个新的集合
ArrayList newList = new ArrayList();
//获取迭代器遍历旧的集合
Iterator it = list.iterator();
while(it.hasNext()){
Book book = (Book) it.next(); //从旧集合中获取的元素
if(!newList.contains(book)){
//如果新集合没有包含该书籍,那么就存储到新集合中
newList.add(book);
}
}
return newList;
}
}
(4) 运行结果:
五.LinkedList类
1.LinkedList:底层是使用了链表数据结构(链接列表)实现的,特点:查询速度慢,增删快。
2.Linkedlist的实现原理:
当我们往Linkedlist中添加数据的时候,它的一个元素被分成两块,比如添加张三,在Linkedlist中一个元素由两部分组成,一部分存储着张三,另外一部分存储着下个元素的内存地址,这样就好像是链子一样,一环扣着一环。
3.Linkedlist的特点的原理:
(1) 那么为什么它的查询速度会慢呢?
因为它的元素与元素之间的内存地址不是连续的,比如要找第100号元素,那么对于链表数据结构来说,它就不能直接再移动100个单元,因为它的内存地址并不连续,它只能挨个把所有的元素都遍历完,直到找到它要的那个元素为止。挨个遍历的速度是很慢的。
(2) 那么为什么它的增加速度会快呢?
比如要在张三和李四之间插入一个狗娃,只需要把张三和李四之间的连线断开,让张三的指针域记录狗娃的内存地址,让狗娃的指针域记录李四的内存地址。
(3) 那么为什么它的删除的速度也快呢?
比如要把王五删除,只需要让李四的指针域不再指向王五的内存地址,这时候就没有人指向王五,一旦没有变量指向它,那么它就会变成一个垃圾对象,就没有人再去使用它。
4.Linkedlist特有的方法:
(1) 添加的方法:
① addFirst(E e):将指定元素插入此列表的开头。
② addLast(E e):将指定元素添加到此列表的结尾。
public class Demo1 {
public static void main(String[] args) {
LinkedList list= new LinkedList();
list.add("张三");
list.add("李四");
list.add("王五");
list.addFirst("狗娃"); //把元素添加到集合的首位置上。
list.addLast("狗剩"); //把元素添加到集合的末尾处。
System.out.println("集合中的元素:"+ list);
}
}
运行结果如下图所示:
(2) 获取的方法:
① getFirst():返回此列表的第一个元素。
② getLast():返回此列表的最后一个元素。
public class Demo2 {
public static void main(String[] args) {
LinkedList list= new LinkedList();
list.add("张三");
list.add("李四");
list.add("王五");
System.out.println("集合中的元素:"+ list);
System.out.println("获取集合中首位置的元素:"+list.getFirst());
System.out.println("获取集合中末尾的元素:"+ list.getLast());
}
}
运行结果如下图所示:
(3) 删除的方法:
public class Demo3 {
public static void main(String[] args) {
LinkedList list= new LinkedList();
list.add("张三");
list.add("李四");
list.add("王五");
System.out.println("执行删除操作前集合中的元素:"+ list);
System.out.println("删除集合中的首位置元素并返回:"+ list.removeFirst());
System.out.println("删除集合中的末尾元素并返回:"+ list.removeLast());
System.out.println("执行删除操作后集合中的元素:"+ list);
}
}
运行结果如下图所示:
(4) 数据结构:
① 栈:先进后出,主要是用于实现堆栈数据结构的存储方式。
push(E e):将元素推入此列表所表示的堆栈。 每次都是把元素放入栈顶。
pop():从此列表所表示的堆栈处弹出一个元素。public class Demo4 {
public static void main(String[] args) {
LinkedList list= new LinkedList();
list.add("张三");
list.add("李四");
list.add("王五");
list.push("狗娃"); //将该元素插入此集合的开头处。
System.out.println("执行pop操作前集合中的元素:"+ list);
System.out.println("删除集合的首元素:"+list.pop()); // 移除并返回集合中的第一个元素
System.out.println("执行pop操作后集合中的元素:"+ list);
}
}
运行结果如下图所示:
② 队列:先进先出,引入这两个方法主要是为了让我们可以使用LinkedList模拟队列数据结构的存储方式。
offer(E e):将指定元素添加到此列表的末尾(最后一个元素)。
poll():获取并移除此列表的头(第一个元素)。
public class Demo5 {
public static void main(String[] args) {
LinkedList list= new LinkedList();
list.add("张三");
list.add("李四");
list.add("王五");
list.offer("狗剩");
System.out.println("执行offer操作后集合中的元素:"+ list);
System.out.println("删除集合的首元素: "+list.poll());
System.out.println("执行poll操作后集合中的元素:"+ list);
}
}
运行结果如下图所示:
(5) 返回逆序的迭代器对象
① descendingIterator():返回以逆向顺序在此双端队列的元素上进行迭代的迭代器。
public class Demo6 {
public static void main(String[] args) {
LinkedList list= new LinkedList();
list.add("张三");
list.add("李四");
list.add("王五");
Iterator it = list.descendingIterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
运行结果如下图所示:
5.实例一:
(1) 需求:使用LinkedList实现堆栈数据结构的存储方式与队列的数据结构存储方式。
(2) 实例:
// 使用LinkedList模拟堆栈的数据结构存储方式
class StackList{
LinkedList list;
public StackList(){
list = new LinkedList();
}
//进栈
public void add(Object o){
list.push(o);
}
//弹栈 : 把元素删除并返回。
public Object pop(){
return list.pop();
}
//获取元素个数
public int size(){
return list.size();
}
}
//使用LinkedList模拟队列的存储方式
class TeamList{
LinkedList list;
public TeamList(){
list = new LinkedList();
}
public void add(Object o){
list.offer(o);
}
public Object remove(){
return list.poll();
}
//获取元素个数
public int size(){
return list.size();
}
}
public class Demo9 {
public static void main(String[] args) {
TeamList list= new TeamList();
list.add("张三");
list.add("李四");
list.add("王五");
int size = list.size();
for(int i = 0 ; i<size ; i++){
System.out.println(list.remove());
}
}
}
(3) 运行结果:
6.实例二:
(1) 需求:使用LinkedList存储一副扑克牌,然后实现洗牌功能。
(2) 实例:
//扑克类
class Poker{
String color; //花色
String num; //点数
public Poker(String color, String num) {
super();
this.color = color;
this.num = num;
}
@Override
public String toString() {
return "{"+color+num+"}";
}
}
public class Demo2 {
public static void main(String[] args) {
LinkedList pokers = createPoker();
shufflePoker(pokers);
showPoker(pokers);
}
//洗牌的功能
public static void shufflePoker(LinkedList pokers){
//创建随机数对象
Random random = new Random();
for(int i = 0 ; i <100; i++){
//随机产生两个索引值
int index1 = random.nextInt(pokers.size());
int index2 = random.nextInt(pokers.size());
//根据索引值取出两张牌,然后交换两张牌的顺序
Poker poker1 = (Poker) pokers.get(index1);
Poker poker2 = (Poker) pokers.get(index2);
pokers.set(index1, poker2);
pokers.set(index2, poker1);
}
}
//显示扑克牌
public static void showPoker(LinkedList pokers){
for(int i = 0 ; i<pokers.size() ; i++){
System.out.print(pokers.get(i));
//换行
if(i%10==9){
System.out.println();
}
}
}
//生成扑克牌的方法
public static LinkedList createPoker(){
//该集合用于存储扑克对象。
LinkedList list = new LinkedList();
//定义数组存储所有的花色与点数
String[] colors = {"黑桃","红桃","梅花","方块"};
String[] nums = {"A","2","3","4","5","6","7","8","9","10","J","Q","K"};
for(int i = 0 ; i < nums.length ; i++){
for(int j = 0 ; j<colors.length ; j++){
list.add(new Poker(colors[j], nums[i]));
}
}
return list;
}
}
(3) 运行结果:
7.实例三
(1) 需求:编写一个函数根据人的年龄排序存储。
(2) 实例:
class Person{
String name;
int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "{ 名字:"+ this.name+" 年龄:"+ this.age+"}";
}
}
public class Demo3 {
public static void main(String[] args) {
LinkedList list = new LinkedList();
list.add(new Person("狗娃", 7));
list.add(new Person("狗剩", 17));
list.add(new Person("铁蛋", 3));
list.add(new Person("美美", 30));
//编写一个函数根据人的年龄及逆行排序存储。
for(int i= 0 ; i<list.size() -1 ; i++){
for(int j = i+1 ; j<list.size() ; j++){
//符合条件交换位置
Person p1 = (Person) list.get(i);
Person p2 = (Person) list.get(j);
if(p1.age>p2.age){
//交换位置
list.set(i, p2);
list.set(j, p1);
}
}
}
System.out.println(list);
}
}
(3) 运行结果:
六.Vector类(了解即可)
1.Vector:底层也是维护了一个Object的数组实现的,实现与ArrayList是一样的,但是Vector是线程安全的,操作效率低。
2.笔试题:说出ArrayList与Vector的相同点与不同点?
(1) 相同点:ArrayList与Vector底层都是使用了Object数组实现的。
(2) 不同点:
① ArrayList是线程不同步的,操作效率高。 Vector是线程同步的,操作效率低。
② ArrayList是jdk1.2出现,Vector是jdk1.0的时候出现的。
3.Vector实例:
public class Demo1 {
public static void main(String[] args) {
Vector v = new Vector();
//添加元素
v.addElement("张三");
v.addElement("李四");
v.addElement("王五");
//迭代该集合
Enumeration e = v.elements(); //获取迭代器
while(e.hasMoreElements()){
System.out.println(e.nextElement());
}
}
}
运行结果如下图所示:
七.Set类
1.Set:如果是实现了Set接口的集合类,具备的特点:无序,不可重复。
注意:无序指的是添加元素的顺序与元素出来的顺序是不一致的。
2.实例:
public class Demo1 {
public static void main(String[] args) {
Set set = new HashSet();//Set是一个接口,不能直接new,只能new它的实现类。
set.add("王五");
set.add("张三");
set.add("李四");
System.out.println("添加成功吗?"+set.add("李四"));
System.out.println(set);
}
}
运行结果如下图所示:
八.HashSet
1.HashSet:底层是使用了哈希表来支持的,特点: 存取速度快。
2. HashSet的存储原理:
(1) 实例
class Person{
int id;
String name;
public Person(int id, String name) {
super();
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "{ 编号:"+ this.id+" 姓名:"+ this.name+"}";
}
@Override
public int hashCode() {
System.out.println("=======hashCode=====");
return this.id;
}
@Override
public boolean equals(Object obj) {
System.out.println("======equals======");
Person p = (Person)obj;
return this.id==p.id;
}
}
public class Demo2 {
public static void main(String[] args) {
HashSet set = new HashSet();
set.add(new Person(110,"狗娃"));
set.add(new Person(220,"狗剩"));
set.add(new Person(330,"铁蛋"));
//在现实生活中只要编号一致就为同一个人.
System.out.println("添加成功吗?"+set.add(new Person(110,"狗娃")));
System.out.println("集合的元素:"+set);
}
}
(2) 运行结果:
(3) HashSet的存储原理:
往Haset添加元素的时候,HashSet会先调用元素的hashCode方法得到元素的哈希值 ,然后通过元素的哈希值经过移位等运算,就可以算出该元素在哈希表中的存储位置。会有以下两种情况:
① 情况1:如果算出元素存储的位置目前没有任何元素存储,那么该元素可以直接存储到该位置上。
② 情况2:如果算出该元素的存储位置目前已经存在有其他的元素了,那么会调用该元素的equals方法与该位置的元素再比较一次,如果equals返回的是true,那么该元素与这个位置上的元素就视为重复元素,不允许添加,如果equals方法返回的是false,那么该元素运行 添加。
(4) 分析:
① 当它存储狗娃的时候,HashSet就会调用狗娃的hashCode()方法,得到一个哈希码值,一个哈希码值相当于一个对象的内存地址,经过HashSet一些特有的算法之后,它就可以算出狗娃应该存储在哪一个位置上,接着算出狗剩和铁蛋应该存储的位置,再存储狗娃的时候,这个狗娃和上一个狗娃的HashCode码值并不一致,不一致的话算出来的位置也不一样,它的运算过程就是这样。
② 所以如果要让第二个狗娃存不进去,那么就要重写Person的hashCode方法,让这个狗娃和上一个狗娃算出的位置是一样的。
③ 每添加一个元素都会调用这个元素的hashCode方法。
④ 那么第一个狗娃存储的位置已经被占用,为什么第二个狗娃还可以存?那是因为哈希表的其中的一个特点是:桶式结构,它并不是一个萝卜一个坑,它是桶式结构,所以第一个元素进来后,第二个元素还可以进。一个位置可以存放有多个元素的。
⑤ 那么重写hashCode方法后为什么第二个狗娃还是可以存进去? 是因为调用第二个狗娃的equals方法和第一个狗娃比较的话,因为没有重写equals方法,比较的是这两个对象的内存地址,所以比较结果返回的是false,所以还是可以添加进去,所以要想使第二个狗娃添加不进去,还要重写equals方法,
⑥ 综上所述,所以会调用4次hashCode方法,调用1次equals方法。
3.疑问:为什么HashSet的存取速度快?
答:因为它是通过一个元素的内存地址来算出它的存储位置的,就像学校教室给学生安排座位表,说出一个学生的名字就可以给它根据一定的算法安排一个座位;同理,比如有人来教室找人,只要说出这个人的名字算出的位置也是一样的。
4.实例一
(1) 需求:接受键盘录入用户名与密码,如果用户名与密码已经存在集合中,那么就是视为重复元素,不允许添加到HashSet中。
(2) 实例:
class User{
String userName;
String password;
public User(String userName, String password) {
super();
this.userName = userName;
this.password = password;
}
@Override
public String toString() {
return "{ 用户名:"+this.userName+" 密码:"+ this.password+"}";
}
@Override
public boolean equals(Object obj) {
User user = (User)obj;
return this.userName.equals(user.userName)&&this.password.equals(user.password);
}
@Override
public int hashCode() {
return userName.hashCode()+password.hashCode();
}
}
public class Demo3 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
HashSet set = new HashSet();
while(true){
System.out.println("请输入用户名:");
String userName = scanner.next();
System.out.println("请输入密码:");
String password = scanner.next();
//创建一个对象
User user = new User(userName, password);
if(set.add(user)){
System.out.println("注册成功...");
System.out.println("当前的用户有:"+ set);
}else{
System.out.println("注册失败...");
}
}
}
}
(3) 运行结果:
5.实例二
(1) 实例:
public class Demo4 {
public static void main(String[] args) {
String str1 = "hello";
String str2 = new String("hello");
System.out.println("两个是同一个对象吗?"+(str1==str2));
System.out.println("str1的hashCode:"+ str1.hashCode());
System.out.println("str2的hashCode:"+ str2.hashCode());
}
}
(2) 运行结果:
(3) 疑问:为什么不同对象的hashCode有可能会相同?
答:因为hashCode默认情况下表示的是内存地址,String类已经重写了Object的hashCode方法了。
注意: 如果两个字符串的内容一致,那么返回的hashCode 码肯定也会一致的。
九.TreeSet类
1.TreeSet:如果元素具备自然顺序的特性,那么就按照元素自然顺序的特性进行排序存储。
2.疑问:什么是自然顺序?
答:自然顺序就是abcd,1234,只要往treeSet中存储,treeSet就会自动帮你排序存储。我们一输出tree的时候,结果就是排好序的数据。
3.实例:
(1) 实例一:
public class Demo3 {
public static void main(String[] args) {
TreeSet tree = new TreeSet();
tree.add(1);
tree.add(10);
tree.add(7);
tree.add(19);
tree.add(9);
System.out.println(tree);
}
}
运行结果如下图所示:
(2) 实例二:
public class Demo4 {
public static void main(String[] args) {
TreeSet tree = new TreeSet();
tree.add('b');
tree.add('f');
tree.add('a');
tree.add('c');
System.out.println(tree);
}
}
运行结果如下图所示:
4.TreeSet中添加自定义元素
(1) 以下程序运行后会报错,因为TreeSet会对里面的元素进行排序,但是目前我们并没有告诉TreeSet按照什么来排序,并没有告诉它要比较的是什么。
//添加自定义元素
class Emp1{
int id;
String name;
int salary;
public Emp1(int id, String name, int salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
@Override
public String toString() {
return "{ 编号:"+ this.id+" 姓名:"+ this.name+" 薪水:"+ this.salary+"}";
}
}
public class Demo5 {
public static void main(String[] args) {
TreeSet tree = new TreeSet();
tree.add(new Emp1(110, "张三", 100));
tree.add(new Emp1(113, "李四", 200));
tree.add(new Emp1(220, "王五", 300));
tree.add(new Emp1(120, "狗娃", 500));
System.out.println("集合的元素:" + tree);
}
}
运行结果如下图所示:
(2) treeSet添加自定义元素要注意的事项:
① 往TreeSet添加元素的时候,如果元素本身具备了自然顺序的特性,那么就按照元素自然顺序的特性进行排序存储。
② 往TreeSet添加元素的时候,如果元素本身不具备自然顺序的特性,那么该元素所属的类必须要实现Comparable接口,把元素的比较规则定义在compareTo(T o)方法上。
③ 如果比较元素的时候,compareTo方法返回的是0,那么该元素就被视为重复元素,不允许添加.(注意:TreeSet与HashCode、equals方法是没有任何关系。它依赖的是自己的比较规则compareTo)
④ 往TreeSet添加元素的时候, 如果元素本身没有具备自然顺序的特性,而元素所属的类也没有实现Comparable接口,那么必须要在创建TreeSet的时候传入一个比较器。
⑤ 往TreeSet添加元素的时候,如果元素本身不具备自然顺序的特性,而元素所属的类已经实现了Comparable接口, 在创建TreeSet对象的时候也传入了比较器那么是以比较器的比较规则优先使用。
(3) 如何自定义定义比较器: 自定义一个类实现Comparator接口即可,把元素与元素之间的比较规则定义在compare方法内即可。
自定义比较器的格式 :
class 类名 implements Comparator{
}
(4) 推荐使用:使用比较器(Comparator)。
因为如果使用Comparable的话,它的比较规则只能在这个类里面使用,如果用Comparator的话,不仅在这个类里可以用,在其他地方也可以使用。可提高复用性。
(5) 实例:
① 实现Comparable接口
/*
compareTo方法中如果返回的是负整数,那么就是小于
compareTo方法中如果返回的是零,那么就是等于
compareTo方法中如果返回的是正整数,那么就是大于
返回的是负整数、零或正整数,分别表示此对象是小于、等于还是大于指定对象。
*/
//添加自定义元素
class Emp2 implements Comparable<Emp2>{
int id;
String name;
int salary;
public Emp2(int id, String name, int salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
@Override
public String toString() {
return "{ 编号:"+ this.id+" 姓名:"+ this.name+" 薪水:"+ this.salary+"}";
}
//元素与元素之间的比较规则定义在compareTo方法上。
@Override
// 返回的是负整数、零或正整数,根据此对象是小于、等于还是大于指定对象。
public int compareTo(Emp2 o) {
Emp2 e = (Emp2) o;
return this.salary- e.salary;
}
}
public class Demo6 {
public static void main(String[] args) {
// 创建TreeSet的时候传入比较器
TreeSet tree = new TreeSet();
tree.add(new Emp2(110, "张三", 100));
tree.add(new Emp2(113, "李四", 200));
tree.add(new Emp2(220, "王五", 300));
tree.add(new Emp2(120, "狗娃", 500));
System.out.println("集合的元素:" + tree);
}
}
运行结果如下图所示:
② 自定义一个比较器
class Emp5 {
int id;
String name;
int salary;
public Emp5(int id, String name, int salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
@Override
public String toString() {
return "{ 编号:"+ this.id+" 姓名:"+ this.name+" 薪水:"+ this.salary+"}";
}
}
//自定义一个比较器
class MyComparator implements Comparator{
//根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。
@Override
public int compare(Object o1, Object o2) {
Emp5 e1 = (Emp5) o1;
Emp5 e2 = (Emp5) o2;
return e1.id - e2.id;
}
}
public class Demo9 {
public static void main(String[] args) {
// 创建一个比较器对象
MyComparator comparator = new MyComparator();
// 创建TreeSet的时候传入比较器
TreeSet tree = new TreeSet(comparator);
tree.add(new Emp5(110, "张三", 100));
tree.add(new Emp5(113, "李四", 200));
tree.add(new Emp5(220, "王五", 300));
tree.add(new Emp5(120, "狗娃", 500));
System.out.println("集合的元素:" + tree);
}
}
运行结果如下图所示:
5.TreeSet的存储原理
(1) 实例:
class Emp3 implements Comparable<Emp3>{
int id;
String name;
int salary;
public Emp3(int id, String name, int salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
@Override
public String toString() {
return "{ 编号:"+ this.id+" 姓名:"+ this.name+" 薪水:"+ this.salary+"}";
}
//元素与元素之间的比较规则定义在compareTo方法上。
@Override
// 返回的是负整数、零或正整数,根据此对象是小于、等于还是大于指定对象。
public int compareTo(Emp3 o) {
Emp3 e = (Emp3) o;
System.out.println(this.name+"compare"+ e.name);//输出比较的过程
return this.salary- e.salary;
}
}
public class Demo7 {
public static void main(String[] args) {
// 创建TreeSet的时候传入比较器
TreeSet tree = new TreeSet();
tree.add(new Emp3(110, "张三", 200));
tree.add(new Emp3(113, "李四", 300));
tree.add(new Emp3(220, "王五", 100));
tree.add(new Emp3(120, "狗娃", 500));
System.out.println("集合的元素:" + tree);
}
}
(2) 运行结果:
(3) 分析过程:
① 首先张三是第一个添加的元素,没人和他比,那么他就自己和自己比较,现在张三就是树根元素,实际上这一步可以省略掉。
② 当添加李四的时候,首先要和树根元素比较,如果它比树根元素小那么就放在树根元素的左边,如果它比树根元素大就放在树根元素的右边,所以李四和张三比较一次,李四放在张三节点的右边。
③ 当添加王五的时候,它也会从树根进来,先和树根元素进行比较,发现它比树根元素小,那么就放在树根元素的左边,所以王五放在张三的左边。
④ 如果我们添加tree.add(new Emp(320, "狗剩", 500));这时它就会被视为重复元素,因为compareTo方法返回的是0,在TreeSet中它不是根据hashCode也不是根据equals方法来判断是否为重复元素的,它是依靠compareTo方法如果返回0就视为重复元素。这时候狗剩就无法添加进去。
⑤ 当添加狗娃的时候,它也会从树根进来,先和树根元素进行比较,它比树根元素大,那么就放在张三的右边,在右边又遇到李四,所以还需要和李四比较一次,比李四大,放在李四的右边。
(4) 二叉树算法自动调整节点实例: