常用对象API(集合框架Collection)★★★★★
概述
数组也可以存对象,但是数组长度是固定的,使用集合长度是可变的,不确定长度用集合好一些
注意:集合里面是保存的对象
集合类的由来:
对象用于封装特有数据,对象多了需要存储,如果对象的个数不确定。
就使用集合容器进行存储。
集合的特点:
-
对象封装数据,对象多了也需要存储。集合用于存储对象的容器。
-
对象的个数确定可以使用数组,但是不确定怎么办?可以用集合。因为集合是可变长度的。
-
集合中不可以存储基本数据类型值。
集合和数组的区别:
集合可变长度的。
- 数组可以存储基本数据类型,也可以存储引用数据类型;
集合只能存储引用数据类型。
- 数组存储的元素必须是同一个数据类型;
集合存储的对象可以是不同数据类型。
体系&共性功能
数据结构:就是容器中存储数据的方式。
集合容器因为内部的数据结构不同,有多种具体容器。
集合容器在不断的向上抽取,就形成了集合框架。
在使用一个体系时,原则:参阅顶层内容。建立底层对象。
框架的顶层就是Collection接口:
Collection接口的常见方法:
- 添加。
boolean add(Object obj)://添加元素。
boolean addAll(Collection coll)://添加某集合所有元素
//创建一个集合容器。使用Collection接口的子类。ArrayList
ArrayList c1 = new ArrayList();
//1,添加元素。
c1.add("abc1");//add(Object obj);
c1.add("abc2");
c1.add("abc3");
c1.add("abc4");
打印:["abc1","abc2"....]
ArrayList c2 = new ArrayList();
c2.add("abc5");//add(Object obj);
c2.add("abc6");
c2.add("abc7");
c1.addAll(c2); //将c2中的元素添加到c1中
- 删除。
boolean remove(object obj):
boolean removeAll(Collection coll);
//将两个集合中的相同元素从调用removeAll的集合中删除。
void clear();//清空集合。
//演示removeAll
boolean b =c1.removeAll(c2);
//将两个集合中的相同元素从调用removeAll的集合中删除。
System.out.println("removeAll:"+b);
- 判断:
boolean contains(object obj):
//如果此集合包合此元素,返回true。
boolean containsAll(Colllection coll);
//如果此集合包含集合所有元素,返回true。
boolean isEmpty();//判断集合中是否有元素。
//演示containsAll
boolean b =c1.containsAll(c2);
//如果此集合包含集合所有元素,返回true。
System.out.println("containsAll:"+b);
//输出:containsAll:false
- 获取:
int size();//返回此集合中的元素数。
// 如果此集合包含Integer.MAX_VALUE元素,则返回Integer.MAX_VALUE 。
- 取交集:
boolean retainAll(Collection coll);//取交集。
//演示retainAll
boolean b = c1.retainAll(c2);
//取交集,保留和指定的集合相同的元素,而删除不同的元素。
//和removeAll功能相反。
System.out.println("retainAll:"+b);
- 获取集合中所有元素:
Iterator iterator();//取出元素的方式:迭代器。
/*该对象必须依赖于具体容器,因为每一个容器的数据结构都不同。
所以该迭代器对象是在容器中进行内部实现的。
对于使用容器者而言,具体的实现不重要,只要通过容器获取到该实现的迭代器的对象即可,
也就是iterator方法。
Iterator接口就是对所有的Collection容器进行元素取出的公共接口。
其实就是抓娃娃游戏机中的夹子!
*/
7.将集合变成数组
Object[] toArray()://将集合转成数组。
迭代器的使用Iterator
Interface Iterator
Iterator
是一个接口。作用:用于取集合中的元素。
boolean | [hasNext] 如果仍有元素可以迭代,则返回 true。 |
---|---|
E | [next] 返回迭代的下一个元素。 |
void | [remove]从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。 |
Collection coll = new ArrayList();
coll.add("abc1");
coll.add("abc2");
coll.add("abc3");
coll.add("abc4");
// System.out.println(coll);
//使用了Collection中的iterator()方法。 调用集合中的迭代器方法,是为了获取集合中的迭代器对象。
// Iterator it =coll.iterator();
// while(it.hasNext()){
// System.out.println(it.next());
// }
for(Iterator it = coll.iterator(); it.hasNext(); ){
System.out.println(it.next());
}
//实际开发中用这种,for循环释放内存
//但是如果你想循环结束以后迭代器还能用就使用while,for与while的区别不就是在这儿吗。
迭代器原理:
- 取元素,无论是什么数据结构,最终共性的取出方式:
一个一个取,取之前判断。有,取一个。没有,结束 - 这种共性的方式称为:迭代。
List和Set的特点
List(列表)和set(集)
Collection
|--List:有序(存入和取出的顺序一致),该集合体系元素都有索引(角标),元素可以重复。
|--Set:元素不能重复,无序(LinkedHashSet集合有序)。
所以有序不有序不重要,重要的是唯不唯一,需要唯一走Set,不需要走List
使用集合的一些技巧
需要唯一吗?
- 需要:用Set
- 需要制定顺序:
- 需要TreeSet
- 不需要HashSet
- 但是想要一个和存储一致的顺序(有序):LinkeHashSet
- 需要制定顺序:
- 不需要List
- 需要频繁增删吗?
- 需要:LinkeList
- 不需要:ArrayList
- 需要频繁增删吗?
如何记录每个容器的结构和所属体系呢?
List
- ArrayList
- LinkedList
Set
- HashSet
- TreeSet
后缀名就是该集合所属的体系
前缀名就是该集合的数据结构
- 看到array:就要像到数组,就要想到查询快,有角标。
- 看到Link:就要想到链表,就要想到增删快,就要想到add,get,remove+frist、last方法。
- 看到hash:就要想到哈希表,就要想到唯一性,就要想到元素需要覆盖hashCode方法和equals方法。
- 看到tree:就要想到二叉树,就要想到排序,就要想到两个接口Comparable,Comparator。
List接口
List接口的常见方法
List特有的常见方法:有一个共性方法就是操作角标
List本身是Collection接口的子接口,具备了Collection的所有方法。现在学习List体系特有的共性方法,查阅方法发现List的特有方法都有索引,这是该集合最大的特点。
List:有序(元素存入集合的顺序和取出的顺序一致),元素都有索引。元素可以重复。
List常用子类的特点:
- Vector:底层的数据结构就是数组,线程同步的,被ArrayList替代了,Vector无论查询和增删都巨慢。
- ArrayList:底层的数据结构是数组,线程不同步,ArrayList替代了Vector,查询元素的速度非常快,但是增删稍慢。
- LinkedList:底层的数据结构是链表,线程不同步,增删元素的速度非常快,查询稍慢
可变长度数组的原理:
当元素超出数组长度,会产生一个新数组,将原数组的数据复制到新数组中,再将新的元素添加到新数组中。
ArrayList:是按照原数组的50%延长。构造一个初始容量为 10 的空列表。
Vector:是按照原数组的100%延长。
注意:对于list集合,底层判断元素是否相同,其实用的是元素自身的equals方法完成的。所以建议元素都要复写equals方法,建立元素对象自己的比较相同的条件依据。
说说ArrayList为什么查询快,为什么LinkList为什么查询慢的原因吗?(ps按ctrl键跳转
List集合支持对元素的增、删、改、查。
- 添加
void |
add(int index, E element) |
在此列表中的指定位置插入指定的元素。 |
---|---|---|
boolean |
add(E e) |
将指定的元素追加到此列表的末尾。 |
add(index,element) :在指定的索引位插入元素。
addAll(index,collection) :在指定的索引位插入一堆元素。
- 删除
remove(index) :删除指定索引位的元素。 返回被删的元素。
- 获取
Object get(index) :通过索引获取指定元素。
int indexOf(obj) :获取指定元素第一次出现的索引位,如果该元素不存在返回—1;
所以,通过—1,可以判断一个元素是否存在。
int lastIndexOf(Object o) :反向索引指定元素的位置。
List subList(start,end) :获取子列表。包含头不包含尾。
- 修改
Object set(index,element) :对指定索引位进行元素的修改。返回被修改的元素
- 获取所有元素
ListIterator
listIterator
():list集合特有的迭代器。
List集合因为角标有了自己的获取元素的方式: 遍历。
for(int x=0; x<list.size(); x++){
sop("get:"+list.get(x));
}
在进行list列表元素迭代的时候,如果想要在迭代过程中,想要对元素进行操作的时候,比如满足条件添加新元素。会发生.ConcurrentModificationException
并发修改异常。
导致的原因是:
集合引用和迭代器引用在同时操作元素,通过集合获取到对应的迭代器后,在迭代中,进行集合引用的元素添加,迭代器并不知道,所以会出现异常情况。
如何解决呢?
既然是在迭代中对元素进行操作,找迭代器的方法最为合适.可是Iterator中只有hasNext,next,remove方法.通过查阅的它的子接口,ListIterator,发现该列表迭代器接口具备了对元素的增、删、改、查的动作。
代码实现:
import java.util.*;
class ListDemo
{
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void method()
{
ArrayList al = new ArrayList();
//添加元素
al.add("java01");
al.add("java02");
al.add("java03");
sop("原集合是:"+al);
//在指定位置添加元素。
al.add(1,"java09");
//删除指定位置的元素。
//al.remove(2);
//修改元素。
//al.set(2,"java007");
//通过角标获取元素。
sop("get(1):"+al.get(1));
sop(al);
//获取所有元素。
for(int x=0; x<al.size(); x++)
{
System.out.println("al("+x+")="+al.get(x));
}
Iterator it = al.iterator();
while(it.hasNext())
{
sop("next:"+it.next());
}
//通过indexOf获取对象的位置。
sop("index="+al.indexOf("java02"));
List sub = al.subList(1,3);
sop("sub="+sub);
}
ListIterator
接口
ListIterator
是List集合特有的迭代器。ListIterator
是Iterator的子接口。
ListIterator it = list.listIterator;//取代Iterator it = list.iterator;
在迭代时,不可以通过集合对象的方法操作集合中的元素。
因为会发生ConcurrentModificationException
异常。
所以,在迭代器时,只能用迭代器的放过操作元素,可是Iterator方法是有限的,
只能对元素进行判断,取出,删除的操作,
如果想要其他的操作如添加,修改等,就需要使用其子接口,ListIterator
。
该接口只能通过List集合的listIterator方法获取。
-
- | Modifier and Type | 方法 | 描述 |
| ----------------- | ----------------- | ------------------------------------------------------------ |
|void
|add(E e)
| 将指定的元素插入列表(可选操作)。 |
|boolean
|hasNext()
| 如果此列表迭代器在向前方向遍历列表时具有更多元素,则返回true
。 |
|boolean
|hasPrevious()
| 如果此列表迭代器在相反方向遍历列表时具有更多元素,则返回true
。 |
|E
|next()
| 返回列表中的下一个元素,并且前进光标位置。 |
|int
|nextIndex()
| 返回由后续调用返回的元素的索引next()
。 |
|E
|previous()
| 返回列表中的上一个元素,并向后移动光标位置。 |
|int
|previousIndex()
| 返回由后续调用返回的元素的索引previous()
。 |
|void
|remove()
| 从列表中删除next()
或previous()
(可选操作)返回的最后一个元素。 |
|void
|set(E e)
| 用指定的元素(可选操作)替换next()
或previous()
返回的最后一个元素。 |
- | Modifier and Type | 方法 | 描述 |
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
//演示列表迭代器。
ArrayList al = new ArrayList();
//添加元素
al.add("java01");
al.add("java02");
al.add("java03");
sop(al);
ListIterator li = al.listIterator();
//sop("hasPrevious():"+li.hasPrevious());
//正向遍历
while(li.hasNext())
{
Object obj = li.next();
if(obj.equals("java02"))
//li.add("java009");
li.set("java006");
}
//逆向遍历
while(li.hasPrevious())
{
sop("pre::"+li.previous());
}
//sop("hasNext():"+li.hasNext());
//sop("hasPrevious():"+li.hasPrevious());
sop(al);
/*
//在迭代过程中,准备添加或者删除元素。
Iterator it = al.iterator();
while(it.hasNext())
{
Object obj = it.next();
if(obj.equals("java02"))
//al.add("java008");
//java.util.ConcurrentModificationException 故障快速迭代器异常
一个线程通常不允许修改集合,而另一个线程正在遍历它。
it.remove();//将java02的引用从集合中删除了。
sop("obj="+obj);
}
sop(al);
*/
}
ConcurrentModificationException:
数组和链表
为什么快:因为空间是连续的所以快
为什么慢:空间不连续吗,挨个去判断吗
LinkedList
有角标吗?
先不说原因,它是不是list接口的子类,是啊,list最大的特点是不是有角标啊,所以也它有啊
Vector集合(了解)
只是讲解了Vector迭代的特有方法而已,但是我们现在一般都不用了,只是作为了解而已
该接口的功能由Iterator接口复制。 此外, Iterator添加了可选的删除操作,并且具有较短的方法名称。 新的实现应该考虑使用Iterator ,而不是Enumeration 。 因此能够适应一个Enumeration到Iterator通过使用asIterator()方法。
/*
枚举就是Vector特有的取出方式。
发现枚举和迭代器很像。
其实枚举和迭代是一样的。
因为枚举的名称以及方法的名称都过长。
所以被迭代器取代了。
枚举郁郁而终了。
*/
class VectorDemo
{
public static void main(String[] args)
{
Vector v = new Vector();
v.add("java01");
v.add("java02");
v.add("java03");
v.add("java04");
Enumeration en = v.elements();
while(en.hasMoreElements())
{
System.out.println(en.nextElement());
}
//用迭代器代替
Iterator it =v.iterator();
while(it.hasNext()){
System.out.println("next:"+it.next());
}
}
}
}
LinkedList
集合
void addFirst(); //在该列表开头插入指定的元素。
void addLast(); //将指定的元素追加到此列表的末尾。
//JDK1.6后
boolean offerFirst();//在此列表的前面插入指定的元素。
boolean offerLast(); //在该列表的末尾插入指定的元素。
返回null的作用是可以加判断
e getFirst();//获取链表中的第一个元素。如果链表为空,抛出NoSuchElementException;
e getLast();
//在jdk1.6以后。返回null的作用是可以加判断
E peekFirst();//获取链表中的第一个元素。如果链表为空,返回null。
E peekLast();
E removeFirst()://获取链表中的第一个元素,但是会删除链表中的第一个元素。如果链表为空,抛出NoSuchElementException
E removeLast();
//在jdk1.6以后。
E pollFirst();//获取链表中的第一个元素,但是会删除链表中的第一个元素。如果链表为空,返回null。
E pollLast();
LinkedList link =new LinkedList();
link.addFirst("abc1");
link.addFirst("abc2");
link.addFirst("abc3");
link.addFirst("abc4");
// System.out.println(link);
// System.out.println(link.getFirst());//获取第一个但不删除。
// System.out.println(link.getFirst());
// System.out.println(link.removeFirst());//获取元素但是会删除。
// System.out.println(link.removeFirst());
while(!link.isEmpty()){
System.out.println(link.removeLast());
}
System.out.println(link);
// Iterator it =link.iterator();
// while(it.hasNext()){
// System.out.println(it.next());
// }
}
练习#(堆栈和队列)
★★★面试考点:
面讲一个在面试中经常会被出的面试题
/*
* 请使用LinkedList来模拟一个堆栈或者队列数据结构。
* 堆栈:先进后出 First In Last Out FILO
* 队列:先进先出 First In First Out FIFO
* 我们应该描述这样一个容器,给使用提供一个容器对象完成这两种结构中的一种。
*/
并不是需要你把这样一个效果演示完,而是需要你做一个这样的容器出来,
是我要给你提供一个对象,这个对象就能实现,你怎么存进去,你调用我的方法时候就能怎么取出来,你得去把这种结构封装起来
这里面的代码就不贴出来了,先自己去做一下,
LinkedList:
addFirst();
addLast():
jdk1.6
offerFirst();//在此列表的开头插入指定的元素
offetLast();
getFirst();.//获取但不移除,如果链表为空,抛出NoSuchElementException.
getLast();
jdk1.6
peekFirst();//获取但不移除,如果链表为空,返回null.
peekLast():
removeFirst();//获取并移除,如果链表为空,抛出NoSuchElementException.
removeLast();
jdk1.6
pollFirst();//获取并移除,如果链表为空,返回null.
pollLast();
ArrayList
集合存储自定对象
定义一个ArrayList
,然后往这里面存入Person,然后再把这里面的数据遍历出来
import java.util.*;
/*
将自定义对象作为元素存到ArrayList集合中,并去除重复元素。
比如:存人对象。同姓名同年龄,视为同一个人。为重复元素。
思路:
1,对人描述,将数据封装进人对象。
2,定义容器,将人存入。
3,取出。
List集合判断元素是否相同,依据是元素的equals方法。
*/
class Person
{
private String name;
private int age;
Person(String name,int age)
{
this.name = name;
this.age = age;
}
public boolean equals(Object obj)
{
if(!(obj instanceof Person))
return false;
Person p = (Person)obj;
//System.out.println(this.name+"....."+p.name);
return this.name.equals(p.name) && this.age == p.age;
}
/**/
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
}
class ArrayListTest2
{
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
ArrayList al = new ArrayList();
al.add(new Demo());
al.add(new Person("lisi01",30));//al.add(Object obj);//Object obj = new Person("lisi01",30);
//al.add(new Person("lisi02",32));
al.add(new Person("lisi02",32));
al.add(new Person("lisi04",35));
al.add(new Person("lisi03",33));
//al.add(new Person("lisi04",35));
//al = singleElement(al);
sop("remove 03 :"+al.remove(new Person("lisi03",33)));//remove方法底层也是依赖于元素的equals方法。
Iterator it = al.iterator();
while(it.hasNext())
{
Person p = (Person)it.next();
sop(p.getName()+"::"+p.getAge());
}
}
public static ArrayList singleElement(ArrayList al)
{
//定义一个临时容器。
ArrayList newAl = new ArrayList();
Iterator it = al.iterator();
while(it.hasNext())
{
Object obj = it.next();
if(!newAl.contains(obj))
newAl.add(obj);
}
return newAl;
}
}
**堆内存图: **PS:集合中装的全是对象的引用,不可能直接把对象装进去。
Set接口
Set接口特点
Set:元素不可以重复,是无序。
Set接口中的方法和Collection中方法一致的。Set接口取出方式只有一种,迭代器。
-
HashSet:底层数据结构是哈希表,线程是不同步的。无序,高效;
-
HashSet是如何保证元素唯一性的呢?
- 是通过元素的两个方法,hashCode和equals来完成。
- 如果元素的hashcode值不同,不会调用equals,就直接存储到哈希表中。
- 如果元素的HashCode值相同,才会判断equals是否为true。
- 如果为true,视为相同元素,不存。如果为false,那么视为不同元素,就进行存储。
-
注意,对于判断元素是否存在,以及删除等操作,依赖的方法是元素的hashcode和equals方法。
-
一般情况下,如果定义的类会产生很多对象,比如人,学生,书,通常都需要覆盖equals,hashCode方法。
建立对象判断是否相同的依据。
-
-
LinkedHashSet:有序,hashSet的子类,此线程不同步。
-
TreeSet:对Set集合中的元素的进行指定顺序的排序。不同步。TreeSet底层的数据结构就是二叉树。
-
判断元素唯一性的方式,就是根据比较方法的返回结果是否为0;是0,就是相同元素。
-
TreeSet对元素进行排序的方式一:
让元素自身具备比较功能,元素就需要实现Comperable接口。覆盖compareTo方法。
-
HashSet
集合
-
- | Modifier and Type | 方法 | 描述 |
| ----------------- | -------------------- | ------------------------------------------------------------ |
|boolean
|add(E e)
| 将指定的元素添加到此集合(如果尚未存在)。 |
|void
|clear()
| 从此集合中删除所有元素。 |
|Object
|clone()
| 返回此HashSet
实例的浅拷贝:元素本身不被克隆。 |
|boolean
|contains(Object o)
| 如果此集合包含指定的元素,则返回true
。 |
|boolean
|isEmpty()
| 如果此集合不包含元素,则返回true
。 |
|Iterator<E>
|iterator()
| 返回此集合中元素的迭代器。 |
|boolean
|remove(Object o)
| 如果存在,则从该集合中删除指定的元素。 |
|int
|size()
| 返回此集合中的元素数(其基数)。 |
|Spliterator<E>
|spliterator()
| 在此集合中的元素上创建late-binding和故障快速Spliterator
。 |
- | Modifier and Type | 方法 | 描述 |
package com.practice;
import java.util.HashSet;
import java.util.Iterator;
public class Demo {
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
HashSet hs = new HashSet();
sop(hs.add("java01"));
sop(hs.add("java01"));
hs.add("abc1");
hs.add("abc2");
hs.add("abc3");
hs.add("abc4");
Iterator it = hs.iterator();
while(it.hasNext())
{
sop(it.next());
}
}
}
哈希表
在使用HashSet在存储元素的时候也,有一个hash算法
hashCode判断元素相同的方式
哈希表的原理:
1,对对象元素中的关键字(对象中的特有数据),进行哈希算法的运算,并得出一个具体的算法值,这个值 称为哈希值。
2,哈希值就是这个元素的位置。
3,如果哈希值出现冲突,再次判断这个关键字对应的对象是否相同。如果对象相同,就不存储,因为元素重复。如果对象不同,就存储,在原来对象的哈希值基础 +1顺延。
4,存储哈希值的结构,我们称为哈希表。
5,既然哈希表是根据哈希值存储的,为了提高效率,最好保证对象的关键字是唯一的。
这样可以尽量少的判断关键字对应的对象是否相同,提高了哈希表的操作效率。
哈希表确定元素是否相同
是通过元素的两个方法,hashCode和equals来完成。
-
判断的是两个元素的哈希值是否相同
- 如果元素的HashCode值相同,才会判断equals是否为true。
-
判断哈希值相同,其实判断的是对象的hashCode的方法。
-
判断内容相同,用的是equals方法。
- 如果为true,视为相同元素,不存。
- 如果为false,那么视为不同元素,就进行存储。
-
注意:如果哈希值不同,是不需要判断equals。
-
一般情况下,如果定义的类会产生很多对象,比如人,学生,书,通常都覆盖equals,hashCode方法。
建立对象判断是否相同的依据:
对于ArrayList集合,判断元素是否存在,或者删元素底层依据都是equals方法。
对于HashSet集合,判断元素是否存在,或者删除元素,底层依据的是hashCode方法和equals方法。
HashSet存储自定义对象
package com.practice;
import java.util.HashSet;
import java.util.Iterator;
public class Demo {
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
HashSet hs = new HashSet();
/*
* HashSet集合数据结构是哈希表,所以存储元素的时候
* 使用的元素的hashCode方法来确定位置,如果位置相同,在通过元素的equals来确定是否相同。
*
* */
hs.add(new Person("shishuo01",18));
hs.add(new Person("shishuo02",19));
hs.add(new Person("shishuo03",19));
hs.add(new Person("shishuo03",19));
hs.add(new Person("shishuo04",20));
// sop("a1:"+hs.contains(new Person("shishuo04",20)));//如果此集合包含指定的元素,则返回 true
// hs.remove(new Person("shishuo04",20));//移除
Iterator it = hs.iterator();
while(it.hasNext())
{
Person p = (Person)it.next(); //add()方法的参数是Object,所以这里要做类型提升
sop(p.getName()+"::"+p.getAge());
}
}
}
class Person
{
private String name;
private int age;
Person(String name,int age)
{
this.name = name;
this.age = age;
}
@Override
public int hashCode()
{
// System.out.println(this.name+"....hashCode");
return name.hashCode()+age*37;
//age乘一个数字,减少哈希冲突,然后去调用equals,
//尽量保持哈希值唯一,提升效率
}
@Override
public boolean equals(Object obj)
{
//健壮性判断
if(this == obj){
return true;
}
if(!(obj instanceof Person))//instanceof可以用来判断继承中的子类的实例是否为父类的实现
throw new ClassCastException("类型错误");
Person p = (Person)obj;
// System.out.println(this.name+"...equals.."+p.name);
return this.name.equals(p.name) && this.age == p.age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
}
练习(消除ArrayList中重复元素 )
package com.Set;
import java.util.ArrayList;
import java.util.Iterator;
/*
将自定义对象作为元素存到ArrayList集合中,并去除重复元素。
比如:存人对象。同姓名同年龄,视为同一个人。为重复元素。
思路:
1,对人描述,将数据封装进人对象。
2,定义容器,将人存入。
3,取出。
List集合判断元素是否相同,依据是元素的equals方法。
*/
public class LinkedHashSet {
public static void main(String[] args)
{
ArrayList arrayList = new ArrayList();
arrayList.add(new Person("shishuo01",18));
arrayList.add(new Person("shishuo02",19));
arrayList.add(new Person("shishuo03",19));
arrayList.add(new Person("shishuo03",19));
arrayList.add(new Person("shishuo03",19));
arrayList.add(new Person("shishuo03",19));
arrayList.add(new Person("shishuo04",20));
System.out.println(arrayList);
sop("a1:"+arrayList.contains(new Person("shishuo04",20)));//如果此集合包含指定的元素,则返回 true
arrayList.remove(new Person("shishuo04",20));//移除
arrayList = getSingleElement(arrayList);
System.out.println(arrayList);
}
public static ArrayList getSingleElement(ArrayList arrayList) {
//1.定义一个临时容器
ArrayList temp = new ArrayList();
//2.迭代元素
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()){
Object obj = iterator.next();
//3.判断被迭代的元素是否存在于临时容器中
if (!temp.contains(obj))//因为contains方法也是用equals的基础来判断的,所以要重写equals方法
temp.add(obj);
}
return temp;
}
private static void sop(String s) {
System.out.println(s);
}
}
class Person
{
private String name;
private int age;
Person(String name,int age)
{
this.name = name;
this.age = age;
}
// @Override
// public int hashCode()
// {
//// System.out.println(this.name+"....hashCode");
// return name.hashCode()+age*37;
// //age乘一个数字,减少哈希冲突,然后去调用equals,
// //尽量保持哈希值唯一,提升效率
// }
//对于ArrayList集合,判断元素是否存在,或者删元素底层依据都是equals方法。
@Override
public boolean equals(Object obj)
{
//健壮性判断
if(this == obj){
return true;
}
if(!(obj instanceof Person))//instanceof可以用来判断继承中的子类的实例是否为父类的实现
throw new ClassCastException("类型错误");
Person p = (Person)obj;
// System.out.println(this.name+"...equals.."+p.name);
return this.name.equals(p.name) && this.age == p.age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
public String toString(){
return name+":"+age;
}
}
LinkedHashSet集合
所以有序不有序不重要,重要的是唯不唯一,需要唯一走Set,不需要走List
HashSet hs = new LinkedHashSet();
//多态指向子类的add
hs.add("java02");
hs.add("java03");
hs.add("java03");
hs.add("java04");
Iterator it = hs.iterator();
while(it.hasNext())
{
System.out.println(it.next());
}
TreeSet集合
TreeSet概述特点
TreeSet:可以对Set集合中的元素的指定顺序排序。是不同步的。排序需要依据元素自身具备的比较性。
如果元素不具备比较性,在运行时会发生ClassCastException异常。
所以需要元素实现Comparable接口,强制让元素具备比较性,复写compareTo方法。
依据compareTo方法的返回值,确定元素在TreeSet数据结构中的位置。
TreeSet方法保证元素唯一性的方式:就是参考比较方法的返回值结果是否为0,如果return 0,视为两个对象重复,不存。
注意:
在进行比较时,如果判断元素不唯一,比如,同姓名,同年龄,才视为同一个人。
在判断时,需要分主要条件和次要条件,当主要条件相同时,再判断次要条件,按照次要条件排序。
TreeSet集合排序两种方式
Comparable和Comparator区别:
1. 让元素自身具备比较性,需要元素对象实现Comparable接口,覆盖compareTo方法。
- 如果不要按照对象中具备的自然顺序进行排序。如果对象中不具备自然顺序。也就是说对象是别人的,你无法进去覆盖compareTo,只能当个舔狗,适应人家的对象,怎么办?
2. 让集合自身具备比较性,需要定义一个实现了Comparator接口的比较器,并覆盖compare方法,并将该类对象作为实际参数传递给TreeSet集合的构造函数。
第二种方式较为灵活,Comparator优先。
package com.Set;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
/*
*按Person_01类中年龄排序
*
*/
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet(new ComparatorByName());
treeSet.add(new Person_0("shishuo_01",18));
treeSet.add(new Person_0("shishuo_02",18));
treeSet.add(new Person_0("chenyufan",18));
treeSet.add(new Person_0("shiying",20));
treeSet.add(new Person_0("chaiyun",21));
treeSet.add(new Person_0("cwhat",22));
Iterator iterator = treeSet.iterator();
while (iterator.hasNext()){
Person_0 p = (Person_0)iterator.next();
System.out.println("姓名:"+p.getName()+" 年龄:"+p.getAge());
}
}
}
class Person_0 implements Comparable
{
private String name;
private int age;
Person_0(String name,int age)
{
this.name = name;
this.age = age;
}
//方法一:按Person_01类中年龄排序,元素对象实现Comparable接口,覆盖compareTo方法
@Override
public int compareTo(Object o) {
Person_0 p = (Person_0) o;
int temp = this.age - p.age;
return temp==0?this.name.compareTo(p.name):temp; //漂亮代码
/* 烂透了代码
if (this.age > p.age)
return 1;
if (this.age < p.age)
return -1;
else
return this.name.compareTo(p.name);
*/
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
public String toString(){
return name+":"+age;
}
}
//第二种方法实现Comparator接口的比较器
class ComparatorByName implements Comparator
{
public int compare(Object o1,Object o2)
{
Person_0 s1 = (Person_0)o1;
Person_0 s2 = (Person_0)o2;
int temp = s1.getName().compareTo(s2.getName());
return temp == 0 ?s1.getAge()-s2.getAge():temp;
// return 1;//有序
// return -1;//倒序
}
}
/*
姓名:chaiyun 年龄:21
姓名:chenyufan 年龄:18
姓名:cwhat 年龄:22
姓名:shishuo_01 年龄:18
姓名:shishuo_02 年龄:18
姓名:shiying 年龄:20
TreeSet集合——二叉树
小插曲:
怎样实现怎么存进去就怎样取出来(有序)
让Comparator接口比较器的compare返回值为1,就可以,怎样比较都是1,结果为正数,就会有序输出。
反之返回值改为-1,就是倒序。优雅,实在是太优雅了
TreeSet练习
/*
* 对字符串进行长度排序
*/
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet(new CompareByLength());
treeSet.add("shishuo_01");
treeSet.add("shiying");
treeSet.add("shishuo_02");
treeSet.add("chenyufan");
treeSet.add("chaiyun");
treeSet.add("cwhat");
Iterator iterator = treeSet.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
class CompareByLength implements Comparator {
@Override
public int compare(Object o1, Object o2) {
String s1 = (String) o1;
String s2 = (String) o2;
int temp = s1.length() - s2.length();
return temp == 0 ? s1.compareTo(s2) : temp;
}
}
/*
输出:
cwhat
chaiyun
shiying
chenyufan
shishuo_01
shishuo_02
泛型
概述
泛型:JDK1.5 出现的安全机制。表现格式:<>
好处:
- 将运行时期的问题ClassCastException()转到了编译时期。
- 避免了强制转换的麻烦。
<>:什么时候用?
当操作的引用数据类型不确定的时候。就使用<>。将要操作的引用数据类型转入即可。
其实<>就是一个用于接收具体引用数据类型的参数范围。
在程序中,只要用到了带有<>的类或者接口,就要明确传入的具体引用数据类型。
当使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可。
小知识:Class ArrayList
擦除&补偿
泛型技术:其实应用在编译时期,是给编译器使用的技术,到了运行时期,泛型就不存在了。
为什么? 因为泛型的擦除:
也就是说,编辑器检查了泛型的类型正确后,在生成的类文件中是没有泛型的。
在运行时,如何知道获取的元素类型而不用强转呢?
泛型的补偿:因为存储的时候,类型已经确定了是同一个类型的元素,所以在运行时,只要获取到该元素的类型,在内部进行一次转换即可,所以使用者不用再做转换动作了。
import java.util.*;
class GenericDemo2
{
public static void main(String[] args)
{
TreeSet<String> ts = new TreeSet<String>(new LenComparator());
ts.add("abcd");
ts.add("cc");
ts.add("cba");
ts.add("aaa");
ts.add("z");
ts.add("hahaha");
Iterator<String> it = ts.iterator();
while(it.hasNext())
{
String s = it.next();
System.out.println(s);
//补偿:Class c = "abcd".getClass();获取到该元素的类型
// c.getName(); 元素类型名称。
}
}
}
class LenComparator implements Comparator<String>
{
public int compare(String o1,String o2)
{
int num = new Integer(o2.length()).compareTo(new Integer(o1.length()));
if(num==0)
return o2.compareTo(o1);
return num;
}
}
泛型类
什么时候用泛型类呢?
当类中的操作的引用数据类型不确定的时候,以前用的Object来进行扩展的,现在可以用泛型来表示。这样可以避免强转的麻烦,而且将运行问题转移到的编译时期。
class Worker{}
class Student{}
//泛型前做法。
class Tool
{
private Object obj;
public void setObject(Object obj)
{
this.obj = obj;
}
public Object getObject()
{
return obj;
}
}
//泛型类。
//在jdk1.5后,使用泛型来接收类中要操作的引用数据类型。
/*
什么时候定义泛型类?
当类中要操作的引用数据类型不确定的时候,
就用泛型类来表示。
*/
class Utils<QQ>
{
private QQ q;
public void setObject(QQ q)
{
this.q = q;
}
public QQ getObject()
{
return q;
}
}
class GenericDemo3
{
public static void main(String[] args)
{
Utils<Worker> u = new Utils<Worker>();
u.setObject(new Worker());
Worker w = u.getObject();;
/*
Tool t = new Tool();
t.setObject(new Student());
Worker w = (Worker)t.getObject();
*/
}
}
泛型方法
/**
*
* 将泛型定义在方法上。
*/
public <W> void show(W str){
System.out.println("show : "+str.toString());
}
public void print(QQ str){
System.out.println("print : "+str);
}
/**
* 当方法静态时,不能访问类上定义的泛型。如果静态方法使用泛型,
* 只能将泛型定义在方法上。
* @param obj
*/
//静态方法上的泛型:静态方法无法访问类上定义的泛型。
//如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
public static <Y> void method(Y obj){
System.out.println("method:"+obj);
}
Tool<String> tool = new Tool<String>();
tool.show(new Integer(4));
tool.show("abc");
tool.print("hahah");
// tool.print(newInteger(8));
Tool.method("haha");
Tool.method(new Integer(9));
泛型接口
package com.Generic;
public class GenericDemo_2 {
public static void main(String[] args) {
InterImp interImp = new InterImp();
interImp.show("陈宇凡");
InterImp_02<Integer> imp_02 = new InterImp_02<Integer>();//在调用的时候才明确数据类型
imp_02.show(1228);
}
}
interface Inter<T>{public void show(T t);}
//第一种开始就确定了数据类型
class InterImp implements Inter<String>{
@Override
public void show(String s) {
System.out.println(s);
}
}
//第二种,不明确数据类型,将问题抛给调用者
class InterImp_02<W> implements Inter<W>{
@Override
public void show(W w) {
System.out.println(w);
}
}
泛型限定 ——上下限
泛型中的通配符:可以解决当具体类型不确定的时候,这个通配符就是 ? ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。
泛型限定:
上限:?extends E:可以接收E类型或者E的子类型对象。
下限:?super E:可以接收E类型或者E的父类型对象。
private static void PrintCollection(Collection<? extends Person> al)
private static void PrintCollection(Collection<? super Student> al)
上限什么时候用:往集合中添加元素时,既可以添加E类型对象,又可以添加E的子类型对象。为什么?因为取的时候,E类型既可以接收E类对象,又可以接收E的子类型对象。
下限什么时候用:当从集合中获取元素进行操作的时候,可以用当前元素的类型接收,也可以用当前元素的父类型接收。
泛型的细节:
-
泛型到底代表什么类型取决于调用者传入的类型,如果没传,默认是Object类型;
-
使用带泛型的类创建对象时,等式两边指定的泛型必须一致;
原因:编译器检查对象调用方法时只看变量,然而程序运行期间调用方法时就要考虑对象具体类型了;
-
等式两边可以在任意一边使用泛型,在另一边不使用(考虑向后兼容);
ArrayList<String> al = new ArrayList<Object>(); //错
//要保证左右两边的泛型具体类型一致就可以了,这样不容易出错。
ArrayList<? extends Object> al = new ArrayList<String>(); al.add("aa"); //错
因为集合具体对象中既可存储String,也可以存储Object的其他子类,所以添加具体的类型对象不合适,类型检查会出现安全问题。
?extends Object 代表Object的子类型不确定,怎么能添加具体类型的对象呢?
public static void method(ArrayList<? extends Object> al) { al.add("abc"); //错 }
只能对al集合中的元素调用Object类中的方法,具体子类型的方法都不能用,因为子类型不确定。
package com.Generic;
/*
* 上下限演示:
*/
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class GenericAdvanceDemo {
public static void main(String[] args) {
ArrayList<Person> arrayList = new ArrayList<Person>();
arrayList.add(new Person("石硕",12));
arrayList.add(new Person("陈宇凡",13));
ArrayList<Student_> shi = new ArrayList<Student_>();
shi.add(new Student_("shishuo",14));
shi.add(new Student_("chenyufan",15));
ArrayList<String> shuo = new ArrayList<String>();
shuo.add("wflafl");
shuo.add("fhsalfla");
PrintCollection(arrayList);
PrintCollection(shi);
// PrintCollection(shuo);
}
private static void PrintCollection(Collection<? extends Person> al) {
//Collection<Person> al =new ArrayList<Person>();
Iterator<? extends Person> it = al.iterator();
while (it.hasNext()){
// T t = it.next();
// System.out.println(it.next());
Person p = it.next();
System.out.println(p);
}
}
// private static<T> void PrintCollection(Collection<T> al) {
//
// Iterator<T> it = al.iterator();
// while (it.hasNext()){
// T t = it.next();
// System.out.println(t);
// }
// }
}
class Person
{
private String name;
private int age;
Person(String name,int age)
{
this.name = name;
this.age = age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
public String toString(){
return name+":"+age;
}
}
class Student_ extends Person{
Student_(String name, int age) {
super(name, age);
}
}
泛型限定——上限的体现
public static void main(String[] args) {
ArrayList<Person>al1 = new ArrayList<Person>();
al1.add(new Person("abc",30));
al1.add(new Person("abc4",34));
ArrayList<Student>al2 = new ArrayList<Student>();
al2.add(new Student("stu1",11));
al2.add(new Student("stu2",22));
ArrayList<Worker>al3 = new ArrayList<Worker>();
al3.add(new Worker("stu1",11));
al3.add(new Worker("stu2",22));
ArrayList<String>al4 = new ArrayList<String>();
al4.add("abcdeef");
// al1.addAll(al4);//错误,类型不匹配。
al1.addAll(al2);
al1.addAll(al3);
System.out.println(al1.size());
// printCollection(al2);
// printCollection(al);
}
}
/*
* 一般在存储元素的时候都是用上限,因为这样取出都是按照上限类型来运算的。不会出现类型安全隐患。
*/
class MyCollection<E>{
public void add(E e){
}
public void addAll(MyCollection<? extends E> e){
}
}
泛型限定——下限的体现
public static void main(String[] args) {
TreeSet<Person> al1 = new TreeSet<Person>(new CompByName());
al1.add(new Person("abc4",34));
al1.add(new Person("abc1",30));
al1.add(new Person("abc2",38));
TreeSet<Student>al2 = new TreeSet<Student>(new CompByName());
al2.add(new Student("stu1",11));
al2.add(new Student("stu7",20));
al2.add(new Student("stu2",22));
TreeSet<Worker>al3 = new TreeSet<Worker>();
al3.add(new Worker("stu1",11));
al3.add(new Worker("stu2",22));
TreeSet<String>al4 = new TreeSet<String>();
al4.add("abcdeef");
// al1.addAll(al4);//错误,类型不匹配。
// al1.addAll(al2);
// al1.addAll(al3);
// System.out.println(al1.size());
Iterator<Student>it = al2.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
/*
* class TreeSet<Worker>
* {
* Tree(Comparator<? super Worker> comp);
* }
* 什么时候用下限呢?通常对集合中的元素进行取出操作时,可以是用下限。
*/
class CompByName implements Comparator<Person>{
@Override
public int compare(Person o1, Person o2) {
int temp = o1.getName().compareTo(o2.getName());
return temp==0? o1.getAge()-o2.getAge():temp;
}
}
class CompByStuName implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
int temp = o1.getName().compareTo(o2.getName());
return temp==0? o1.getAge()-o2.getAge():temp;
}
}
class CompByWorkerName implements Comparator<Worker> {
@Override
public int compare(Worker o1, Worker o2) {
int temp = o1.getName().compareTo(o2.getName());
return temp==0? o1.getAge()-o2.getAge():temp;
}
}