为了能够更好的学习容器,我们首先要先来学习一个概念:泛型。
泛型基本概念
泛型是JDK5.0以后增加的新特性。
泛型的本质就是“数据类型的参数化”,处理的数据类型不是固定的,而是可以作为参数传入。我们可以把“泛型”理解为数据类型的一个占位符(类似:形式参数),即告诉编译器,在调用泛型时必须传入实际类型
参数化类型,白话说就是:
- 把类型当作是参数一样传递。
- <数据类型> 只能是引用类型。
泛型的好处
在不使用泛型的情况下,我们可以使用Object类型来实现任意的参数类型,但是在使用时需要我们强制进行类型转换。这就要求程序员明确知道实际类型,不然可能引起类型转换错误;但是,在编译期我们无法识别这种错误,只能在运行期发现这种错误。使用泛型的好处就是可以在编译期就识别出这种错误,有了更好的安全性;同时,所有类型转换由编译器完成,在程序员看来都是自动转换的,提高了代码的可读性。
总结一下,就是使用泛型主要是两个好处:
类型擦除
编码时采用泛型写的类型参数,编译器会在编译时去掉,这称之为“类型擦除”。
泛型主要用于编译阶段,编译后生成的字节码class文件不包含泛型中的类型信息,涉及类型转换仍然是普通的强制类型转换。类型参数在编译后会被替换成Object,运行时虚拟机并不知道泛型。
泛型主要是方便了程序员的代码编写,以及更好的安全性检测。
定义泛型时,一般采用几个标记:E、T、K、V、N、?。他们约定俗称的含义如下:
泛型标记 | 对应单词 | 说明 |
---|---|---|
E | Element | 在容器中使用,表示容器中的元素 |
T | Type | 表示普通的JAVA类 |
K | Key | 表示键,例如:Map中的键Key |
V | Value | 表示值 |
N | Number | 表示数值类型 |
? | 表示不确定的JAVA类型 |
泛型类的使用
语法结构
public class 类名<泛型标识符号> {
}
public class 类名<泛型标识符号,泛型标识符号> {
}
示例
public class Generic {
private T flag;
public void setFlag(T flag){
this.flag = flag;
}
public T getFlag(){
return this.flag;
}
}
public class Test {
public static void main(String[] args) {
//创建对象时,指定泛型具体类型。
Generic generic = new Generic<>();
generic.setFlag("admin");
String flag = generic.getFlag();
System.out.println(flag);
//创建对象时,指定泛型具体类型。
Generic generic1 = new Generic<>();
generic1.setFlag(100);
Integer flag1 = generic1.getFlag();
System.out.println(flag1);
}
}
泛型接口和泛型类的声明方式一致。
泛型接口的使用
语法结构
public interface 接口名<泛型标识符号> {
}
public interface 接口名<泛型标识符号,泛型标识符号> {
}
示例
public interface IGeneric {
T getName(T name);
}
//在实现接口时传递具体数据类型
public class IgenericImpl implements Igeneric {
@Override
public String getName(String name) {
return name;
}
}
//在实现接口时仍然使用泛型作为数据类型
public class IGenericImpl2 implements IGeneric{
@Override
public T getName(T name) {
return name;
}
}
public class Test {
public static void main(String[] args) {
IGeneric igeneric= new IGenericImpl();
String name = igeneric.getName("oldlu");
System.out.println(name);
IGeneric igeneric1 = new IGenericImpl2<>();
String name1 = igeneric1.getName("itbz");
System.out.println(name1);
}
}
类上定义的泛型,在方法中也可以使用。但是,我们经常需要仅仅在某一个方法上使用泛型,这时候可以使用泛型方法。
调用泛型方法时,不需要像泛型类那样告诉编译器是什么类型,编译器可以自动推断出类型
泛型方法的使用
非静态方法
非静态方法可以使用泛型类中所定义的泛型,也可以将泛型定义在方法上。
语法结构
//无返回值方法
public <泛型标识符号> void getName(泛型标识符号 name){
}
//有返回值方法
public <泛型标识符号> 泛型标识符号 getName(泛型标识符号 name){
}
示例
public class MethodGeneric {
public void setName(T name){
System.out.println(name);
}
public T getAge(T age){
return age;
}
}
public class Test2 {
public static void main(String[] args) {
MethodGeneric methodGeneric = new MethodGeneric();
methodGeneric.setName("oldlu");
Integer age = methodGeneric.getAge(123);
System.out.println(age);
}
静态方法
静态方法中使用泛型时有一种情况需要注意一下,那就是静态方法无法访问类上定义的泛型,所以必须要将泛型定义在方法上。
语法结构
//无返回值静态方法
public static <泛型标识符号> void setName(泛型标识符号 name){
}
//有返回值静态方法
public static <泛型标识符号> 泛型表示符号 getName(泛型标识符号 name){
}
示例
public class MethodGeneric {
public static void setFlag(T flag){
System.out.println(flag);
}
public static T getFlag(T flag){
return flag;
}
}
public class Test4 {
public static void main(String[] args) {
MethodGeneric.setFlag("oldlu");
Integer flag1 = MethodGeneric.getFlag(123123);
System.out.println(flag1);
}
}
在泛型方法中,泛型也可以定义可变参数类型。
语法结构
public <泛型标识符号> void showMsg(泛型标识符号... agrs){
}
示例
public class MethodGeneric {
public void method(T...args){
for(T t:args){
System.out.println(t);
}
}
}
无界通配符
“?”表示类型通配符,用于代替具体的类型。它只能在“<>”中使用。可以解决当具体类型不确定的问题。
语法结构
public void showFlag(Generic> generic){
}
示例
public class Generic {
private T flag;
public void setFlag(T flag){
this.flag = flag;
}
public T getFlag(){
return this.flag;
}
}
public class ShowMsg {
public void showFlag(Generic> generic){
System.out.println(generic.getFlag());
}
}
public class Test3 {
public static void main(String[] args) {
ShowMsg showMsg = new ShowMsg();
Generic generic = new Generic<>();
generic.setFlag(20);
showMsg.showFlag(generic);
Generic generic1 = new Generic<>();
generic1.setFlag(50);
showMsg.showFlag(generic1);
Generic generic2 = new Generic<>();
generic2.setFlag("oldlu");
showMsg.showFlag(generic2);
}
}
对通配符的上限的限定:<? extends 类型>
?实际类型可以是上限限定中所约定的类型,也可以是约定类型的子类型;
语法结构
public void showFlag(Generic extends Number> generic){
}
示例
public class ShowMsg {
public void showFlag(Generic extends Number> generic){
System.out.println(generic.getFlag());
}
}
public class Test4 {
public static void main(String[] args) {
ShowMsg showMsg = new ShowMsg();
Generic generic = new Generic<>();
generic.setFlag(20);
showMsg.showFlag(generic);
Generic generic1 = new Generic<>();
generic1.setFlag(50);
showMsg.showFlag(generic1);
}
}
对通配符的下限的限定:<? super 类型>
?实际类型可以是下限限定中所约定的类型,也可以是约定类型的父类型;
语法结构
public void showFlag(Generic super Integer> generic){
}
示例
public class ShowMsg {
public void showFlag(Generic super Integer> generic){
System.out.println(generic.getFlag());
}
}
public class Test6 {
public static void main(String[] args) {
ShowMsg showMsg = new ShowMsg();
Generic generic = new Generic<>();
generic.setFlag(20);
showMsg.showFlag(generic);
Generic generic1 = new Generic<>();
generic1.setFlag(50);
showMsg.showFlag(generic1);
}
}
泛型局限性和常见错误
泛型主要用于编译阶段,编译后生成的字节码class文件不包含泛型中的类型信息。 类型参数在编译后会被替换成Object,运行时虚拟机并不知道泛型。因此,使用泛型时,如下几种情况是错误的:
基本类型不能用于泛型
Test
这样写法是错误,我们可以使用对应的包装类Test
不能通过类型参数创建对象
T elm = new T();
运行时类型参数T
会被替换成Object
,无法创建T类型的对象,容易引起误解,java干脆禁止这种写法。
容器的结构
结构图
单例集合
双例集合
单例集合
Collection接口介绍
Collection 表示一组对象,它是集中、收集的意思。Collection接口的两个子接口是List、Set接口。
Collection接口中定义的方法
方法 | 说明 |
---|---|
boolean add(Object element) | 增加元素到容器中 |
boolean remove(Object element) | 从容器中移除元素 |
boolean contains(Object element) | 容器中是否包含该元素 |
int size() | 容器中元素的数量 |
boolean isEmpty() | 容器是否为空 |
void clear() | 清空容器中所有元素 |
Iterator iterator() | 获得迭代器,用于遍历所有元素 |
boolean containsAll(Collection c) | 本容器是否包含c容器中的所有元素 |
boolean addAll(Collection c) | 将容器c中所有元素增加到本容器 |
boolean removeAll(Collection c) | 移除本容器和容器c中都包含的元素 |
boolean retainAll(Collection c) | 取本容器和容器c中都包含的元素,移除非交集元素 |
Object[] toArray() | 转化成Object数组 |
由于List、Set是Collection的子接口,意味着所有List、Set的实现类都有上面的方法。
JDK8之后,Collection接口新增的方法(将在JDK新特性和函数式编程中介绍):
新增方法 说明 removeIf 作用是删除容器中所有满足filter指定条件的元素 stream parallelStream stream和parallelStream 分别返回该容器的Stream视图表示,不同之处在于parallelStream()返回并行的Stream,Stream是Java函数式编程的核心类。 spliterator 可分割的迭代器,不同以往的iterator需要顺序迭代,Spliterator可以分割为若干个小的迭代器进行并行操作,可以实现多线程操作提高效率
List接口特点
List是有序、可重复的容器。
**有序:**有序(元素存入集合的顺序和取出的顺序一致)。List中每个元素都有索引标记。可以根据元素的索引标记(在List中的位置)访问元素,从而精确控制这些元素。
**可重复:**List允许加入重复的元素。更确切地讲,List通常允许满足 e1.equals(e2) 的元素重复加入容器。
List接口中的常用方法
除了Collection接口中的方法,List多了一些跟顺序(索引)有关的方法,参见下表:
方法 | 说明 |
---|---|
void add (int index, Object element) | 在指定位置插入元素,以前元素全部后移一位 |
Object set (int index,Object element) | 修改指定位置的元素 |
Object get (int index) | 返回指定位置的元素 |
Object remove (int index) | 删除指定位置的元素,后面元素全部前移一位 |
int indexOf (Object o) | 返回第一个匹配元素的索引,如果没有该元素,返回-1. |
int lastIndexOf (Object o) | 返回最后一个匹配元素的索引,如果没有该元素,返回-1 |
ArrayList是List接口的实现类。是List存储特征的具体实现。
ArrayList底层是用数组实现的存储。 特点:查询效率高,增删效率低,线程不安全。
public class ArrayListTest {
public static void main(String[] args) {
//实例化ArrayList容器
List list = new ArrayList<>();
//添加元素
boolean flag1 = list.add("oldlu");
boolean flag2 = list.add("itbz");
boolean flag3 = list.add("sxt");
boolean flag4 = list.add("sxt");
System.out.println(flag1+"\t"+flag2+"\t"+flag3+"\t"+flag4);
//删除元素
boolean flag4 = list.remove("oldlu");
System.out.println(flag4);
//获取容器中元素的个数
int size = list.size();
System.out.println(size);
//判断容器是否为空
boolean empty = list.isEmpty();
System.out.println(empty);
//容器中是否包含指定的元素
boolean value = list.contains("itbz");
System.out.println(value);
//清空容器
list.clear();
Object[] objects1 = list.toArray();
System.out.println(Arrays.toString(objects1));
}
}
public class ArrayListTest2 {
public static void main(String[] args) {
//实例化容器
List list = new ArrayList<>();
//添加元素
list.add("oldlu");
list.add("itbz");
//向指定位置添加元素
list.add(0,"sxt");
System.out.println("获取元素");
String value1 = list.get(0);
System.out.println(value1);
System.out.println("获取所有元素方式一");
//使用普通for循环
for(int i=0;i
并集
//并集操作:将另一个容器中的元素添加到当前容器中
List a = new ArrayList<>();
a.add("a");
a.add("b");
a.add("c");
List b = new ArrayList<>();
b.add("a");
b.add("b");
b.add("c");
//a并集b
a.addAll(b);
for(String str :a){
System.out.println(str);
}
交集
//交集操作:保留相同的,删除不同的
List a1 = new ArrayList<>();
a1.add("a");
a1.add("b");
a1.add("c");
List b1 = new ArrayList<>();
b1.add("a");
b1.add("d");
b1.add("e");
//交集操作
a1.retainAll(b1);
for(String str :a1){
System.out.println(str);
}
差集
//差集操作:保留不同的,删除相同的
List a2 = new ArrayList<>();
a2.add("a");
a2.add("b");
a2.add("c");
List b2= new ArrayList<>();
b2.add("b");
b2.add("c");
b2.add("d");
a2.removeAll(b2);
for(String str :a2){
System.out.println(str);
}
Vector底层是用数组实现的,相关的方法都加了同步检查,因此“线程安全,效率低”。 比如,indexOf方法就增加了synchronized同步标记。
Vector的使用
Vector的使用与ArrayList是相同的,因为他们都实现了List接口,对List接口中的抽象方法做了具体实现。
public class VectorTest {
public static void main(String[] args) {
//实例化Vector
List v = new Vector<>();
v.add("a");
v.add("b");
v.add("a");
for(int i=0;i
成员变量
/**
* The array buffer into which the components of the vector are
* stored. The capacity of the vector is the length of this array buffer,
* and is at least large enough to contain all the vector's elements.
*
* Any array elements following the last element in the Vector are null.
*
* @serial
*/
protected Object[] elementData;
/**
* The number of valid components in this {@code Vector} object.
* Components {@code elementData[0]} through
* {@code elementData[elementCount-1]} are the actual items.
*
* @serial
*/
protected int elementCount;
/**
* The amount by which the capacity of the vector is automatically
* incremented when its size becomes greater than its capacity. If
* the capacity increment is less than or equal to zero, the capacity
* of the vector is doubled each time it needs to grow.
*
* @serial
*/
protected int capacityIncrement;
构造方法
public Vector() {
this(10);
}
添加元素
/**
* Appends the specified element to the end of this Vector.
*
* @param e element to be appended to this Vector
* @return {@code true} (as specified by {@link Collection#add})
* @since 1.2
*/
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
数组扩容
/**
* This implements the unsynchronized semantics of ensureCapacity.
* Synchronized methods in this class can internally call this
* method for ensuring capacity without incurring the cost of an
* extra synchronization.
*
* @see #ensureCapacity(int)
*/
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
//判断是否需要扩容,数组中的元素个数-数组长度,如果大于0表明需要扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//扩容2倍
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
LinkedList底层用双向链表实现的存储。特点:查询效率低,增删效率高,线程不安全。
双向链表也叫双链表,是链表的一种,它的每个数据节点中都有两个指针,分别指向前一个节点和后一个节点。 所以,从双向链表中的任意一个节点开始,都可以很方便地找到所有节点。
LinkedList的存储结构图
每个节点都应该有3部分内容:
1
class Node {
2
Node previous; //前一个节点
3
E element; //本节点保存的数据
4
Node next; //后一个节点
5
}
List实现类的选用规则
如何选用ArrayList、LinkedList、Vector?
- 需要线程安全时,用Vector。
- 不存在线程安全问题时,并且查找较多用ArrayList(一般使用它)
- 不存在线程安全问题时,增加或删除元素较多用LinkedList
public class LinkedListTest {
public static void main(String[] args) {
//实例化LinkedList容器
List list = new LinkedList<>();
//添加元素
boolean a = list.add("a");
boolean b = list.add("b");
boolean c = list.add("c");
list.add(3,"a");
System.out.println(a+"\t"+b+"\t"+c);
for(int i=0;i
方法 | 说明 |
---|---|
void addFirst(E e) | 将指定元素插入到开头 |
void addLast(E e) | 将指定元素插入到结尾 |
getFirst() | 返回此链表的第一个元素 |
getLast() | 返回此链表的最后一个元素 |
removeFirst() | 移除此链表中的第一个元素,并返回这个元素 |
removeLast() | 移除此链表中的最后一个元素,并返回这个元素 |
E pop() | 从此链表所表示的堆栈处弹出一个元素,等效于removeFirst |
void push(E e) | 将元素推入此链表所表示的堆栈 这个等效于addFisrt(E e) |
public class LinkedListTest2 {
public static void main(String[] args) {
System.out.println("-------LinkedList-------------");
//将指定元素插入到链表开头
LinkedList linkedList1 = new LinkedList<>();
linkedList1.addFirst("a");
linkedList1.addFirst("b");
linkedList1.addFirst("c");
for (String str:linkedList1){
System.out.println(str);
}
System.out.println("----------------------");
//将指定元素插入到链表结尾
LinkedList linkedList = new LinkedList<>();
linkedList.addLast("a");
linkedList.addLast("b");
linkedList.addLast("c");
for (String str:linkedList){
System.out.println(str);
}
System.out.println("---------------------------");
//返回此链表的第一个元素
System.out.println(linkedList.getFirst());
//返回此链表的最后一个元素
System.out.println(linkedList.getLast());
System.out.println("-----------------------");
//移除此链表中的第一个元素,并返回这个元素
linkedList.removeFirst();
//移除此链表中的最后一个元素,并返回这个元素
linkedList.removeLast();
for (String str:linkedList){
System.out.println(str);
}
System.out.println("-----------------------");
linkedList.addLast("c");
//从此链表所表示的堆栈处弹出一个元素,等效于removeFirst
linkedList.pop();
for (String str:linkedList){
System.out.println(str);
}
System.out.println("-------------------");
//将元素推入此链表所表示的堆栈 这个等效于addFisrt(E e)
linkedList.push("h");
for (String str:linkedList){
System.out.println(str);
}
}
}
添加元素
节点类
private static class Node {
E item;
Node next;
Node prev;
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
成员变量
transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node last;
添加元素
/**
* Appends the specified element to the end of this list.
*
* This method is equivalent to {@link #addLast}.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node l = last;
final Node newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
头尾添加元素
addFirst
/**
* Inserts the specified element at the beginning of this list.
*
* @param e the element to add
*/
public void addFirst(E e) {
linkFirst(e);
}
/**
* Links e as first element.
*/
private void linkFirst(E e) {
final Node f = first;
final Node newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
addLast
/**
* Appends the specified element to the end of this list.
*
* This method is equivalent to {@link #add}.
*
* @param e the element to add
*/
public void addLast(E e) {
linkLast(e);
}
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node l = last;
final Node newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
获取元素
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* Tells if the argument is the index of an existing element.
*/
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
/**
* Returns the (non-null) Node at the specified element index.
*/
Node node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
Set接口继承自Collection接口,Set接口中没有新增方法,它和Collection接口保持完全一致。我们在前面学习List接口的使用方式,在Set中仍然适用。因此,学习Set的使用将没有任何难度。
Set接口特点
Set特点:无序、不可重复。无序指Set中的元素没有索引,我们只能遍历查找;不可重复指不允许加入重复的元素。更确切地讲,新元素如果和Set中某个元素通过equals()方法对比为true,则只能保留一个。
Set常用的实现类有:HashSet、TreeSet等,我们一般使用HashSet。
HashSet是Set接口的实现类。是Set存储特征的具体实现。
public class HashSetTest {
public static void main(String[] args) {
//实例化HashSet
Set set = new HashSet<>();
//添加元素
set.add("a");
set.add("b1");
set.add("c2");
set.add("d");
set.add("a");
//获取元素,在Set容器中没有索引,所以没有对应的get(int index)方法
for(String str: set){
System.out.println(str);
}
System.out.println("--------------------");
//删除元素
boolean flag = set.remove("c2");
System.out.println(flag);
for(String str: set){
System.out.println(str);
}
System.out.println("------------------------");
int size = set.size();
System.out.println(size);
}
}
HashSet 是一个不保证元素的顺序且没有重复元素的集合,是线程不安全的。HashSet允许有null 元素。
无序:
在HashSet中底层是使用HashMap存储元素的。HashMap底层使用的是数组与链表实现元素的存储。元素在数组中存放时,并不是有序存放的也不是随机存放的,而是对元素的哈希值进行运算决定元素在数组中的位置。
不重复:
当两个元素的哈希值进行运算后得到相同的在数组中的位置时,会调用元素的equals()方法判断两个元素是否相同。如果元素相同则不会添加该元素,如果不相同则会使用单向链表保存该元素。
要自定义对象的化要添加三个方法
equals
hashcode
创建Users对象
public class Users {
private String username;
private int userage;
public Users(String username, int userage) {
this.username = username;
this.userage = userage;
}
public Users() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Users users = (Users) o;
if (userage != users.userage) return false;
return username != null ? username.equals(users.username) : users.username == null;
}
@Override
public int hashCode() {
int result = username != null ? username.hashCode() : 0;
result = 31 * result + userage;
return result;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getUserage() {
return userage;
}
public void setUserage(int userage) {
this.userage = userage;
}
@Override
public String toString() {
return "Users{" +
"username='" + username + '\'' +
", userage=" + userage +
'}';
}
}
在HashSet中存储Users对象
public class HashSetTest2 {
public static void main(String[] args) {
//实例化HashSet
Set set = new HashSet<>();
Users u = new Users("oldlu",18);
Users u1 = new Users("oldlu",18);
set.add(u);
set.add(u1);
System.out.println(u.hashCode());
System.out.println(u1.hashCode());
for(Users users:set){
System.out.println(users);
}
}
}
https://www.itbaizhan.com/course/id/39439.html
成员变量
private transient HashMap map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
添加元素
/**
* Adds the specified element to this set if it is not already present.
* More formally, adds the specified element e to this set if
* this set contains no element e2 such that
* (e==null ? e2==null : e.equals(e2)).
* If this set already contains the element, the call leaves the set
* unchanged and returns false.
*
* @param e element to be added to this set
* @return true if this set did not already contain the specified
* element
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
TreeSet实现了Set接口,它是一个可以对元素进行排序的容器。底层实际是用TreeMap实现的,内部维持了一个简化版的TreeMap,通过key来存储元素。 TreeSet内部需要对存储的元素进行排序,因此,我们需要给定排序规则。
排序规则实现方式:
public class TreeSetTest {
public static void main(String[] args) {
//实例化TreeSet
Set set = new TreeSet<>();
//添加元素
set.add("c");
set.add("a");
set.add("d");
set.add("b");
set.add("a");
//获取元素
for(String str :set){
System.out.println(str);
}
}
}
String类中有compareTo比较函数
在元素自身实现比较规则时,需要实现Comparable接口中的compareTo方法,该方法中用来定义比较规则。TreeSet通过调用该方法来完成对元素的排序处理。
创建Users类
public class Users implements Comparable{
private String username;
private int userage;
public Users(String username, int userage) {
this.username = username;
this.userage = userage;
}
public Users() {
}
@Override
public boolean equals(Object o) {
System.out.println("equals...");
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Users users = (Users) o;
if (userage != users.userage) return false;
return username != null ? username.equals(users.username) : users.username == null;
}
@Override
public int hashCode() {
int result = username != null ? username.hashCode() : 0;
result = 31 * result + userage;
return result;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getUserage() {
return userage;
}
public void setUserage(int userage) {
this.userage = userage;
}
@Override
public String toString() {
return "Users{" +
"username='" + username + '\'' +
", userage=" + userage +
'}';
}
//定义比较规则
//正数:大,负数:小,0:相等
@Override
public int compareTo(Users o) {
if(this.userage > o.getUserage()){
return 1;
}
if(this.userage == o.getUserage()){
return this.username.compareTo(o.getUsername());
}
return -1;
}
}
Set set1 = new TreeSet<>();
Users u = new Users("oldlu",18);
Users u1 = new Users("admin",22);
Users u2 = new Users("sxt",22);
set1.add(u);
set1.add(u1);
set1.add(u2);
for(Users users:set1){
System.out.println(users);
}
通过比较器定义比较规则时,我们需要单独创建一个比较器,比较器需要实现Comparator接口中的compare方法来定义比较规则。在实例化TreeSet时将比较器对象交给TreeSet来完成元素的排序处理。此时元素自身就不需要实现比较规则了。
创建Student类
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student() {
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
}
创建比较器
public class StudentComparator implements Comparator {
//定义比较规则
@Override
public int compare(Student o1, Student o2) {
if(o1.getAge() > o2.getAge()){
return 1;
}
if(o1.getAge() == o2.getAge()){
return o1.getName().compareTo(o2.getName());
}
return -1;
}
}
public class TreeSetTest3 {
public static void main(String[] args) {
//创建TreeSet容器,并给定比较器对象
Set set = new TreeSet<>(new StudentComparator());
Student s = new Student("oldlu",18);
Student s1 = new Student("admin",22);
Student s2 = new Student("sxt",22);
set.add(s);
set.add(s1);
set.add(s2);
for(Student student:set){
System.out.println(student);
}
}
}
成员变量
/**
* The backing map.
*/
private transient NavigableMap m;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
构造方法
public TreeSet() {
this(new TreeMap());
}
添加元素
/**
* Adds the specified element to this set if it is not already present.
* More formally, adds the specified element e to this set if
* this set contains no element e2 such that
* (e==null ? e2==null : e.equals(e2)).
* If this set already contains the element, the call leaves the set
* unchanged and returns false.
*
* @param e element to be added to this set
* @return true if this set did not already contain the specified
* element
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}