目录
一、泛型
1、泛型简介
1.1 泛型基本概念
泛型是JDK1.5以后增加的,它可以帮助我们建立类型安全的集合。
泛型的本质就是“数据类型的参数化”,处理的数据类型不是固定的,而是可以作为参数传入。我们可以把“泛型”理解为数据类型的一个占位符(类似:形式参数),即告诉编译器,在调用泛型时必须传入实际类型。这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
1.2 泛型的好处
1.3 类型擦除
编码时采用泛型写的类型参数,编译器会在编译时去掉,这称之为“类型擦除”。
泛型主要用于编译阶段,编译后生成的字节码class文件不包含泛型中的类型信息,涉及类型转换仍然是普通的强制类型转换。类型参数在编译后会被替换成Object,运行时虚拟机并不知道泛型。(在class类里无法看见泛型,但是可以使用反编译工具可以看见泛型的代码)
泛型主要是方便了程序员的代码编写,以及更好的安全性检测。
2、泛型的使用
2.1定义泛型
泛型类使用时如果未明确类型,调用时默认为Object类型
2.2 泛型类
泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来。泛型类的具体使用方法是在类的名称后添加一个或多个类型参数声明,如:<T>、<T,K,V>
语法结构:
public class 类名<泛型表示符号>{
}
package studyweek5;
public class 泛型初识<T> {//T相当于一个占位符,用户确定后占位符被替换为用户所确定的类型
private T flag;
public void setFlag(T flag){
this.flag=flag;
}
public T getFlag(){
return flag;
}
}
package studyweek5;
import sun.net.www.content.text.Generic;
public class 泛型测试 {
public static void main(String[] args) {
//只有在用户使用的时候才会确定类型
/*
泛型初识 generic1=new 泛型初识<>();
generic1.setFlag();//还未明确类型,在用户调用时默认为Object类型
*/
泛型初识<String> generic2=new 泛型初识<>();
generic2.setFlag("admin");//由于已经明确为String类型,在这里就会是String类型。
String flag=generic2.getFlag();//如果这里没有定义泛型,而使用的是Object等类型,这里就需要强制类型转换,而泛型就不用。
System.out.println(flag);
泛型初识<Integer> generic3=new 泛型初识<>();//注意这里不能用基本数据类型比如int等,而要使用对象类型Integer等
generic3.setFlag(30);
Integer flag1= generic3.getFlag();
System.out.println(flag1);
}
}
2.3 泛型接口
泛型接口和泛型类的声明方式一致。泛型接口的具体类型需要在实现类中进行声明。
语法结构:
public interface 接口名<泛型表示符号>{
}
package studyweek5;
public interface 泛型接口<T> {
T getName(T name);
}
package studyweek5;
public class 泛型接口实现类 implements 泛型接口<String>{//泛型接口实现类在这里传类型
@Override
public String getName(String name) {//给定确定类型
return name;
}
}
package studyweek5;
public class 泛型接口测试 {
public static void main(String[] args) {
//1、接口实现类类型定义
//注意:由于泛型接口实现类已经在实现时确定了类型,因此是可以直接用的
泛型接口实现类 igeneric = new 泛型接口实现类();
String name = igeneric.getName("泛型");
System.out.println(name);
//2、接口定义
//注意:由于泛型接口上并没有确定了类型,因此在new对象时要带上类型
泛型接口<String> igeneric1 = new 泛型接口实现类();
igeneric1.getName("泛型");
System.out.println(name);
}
}
2.4 泛型方法
一旦在方法当中定义了泛型,那么在方法的参数和方法的返回值都可以使用泛型
非静态方法语法结构:
有返回值: public <泛型表示符号> void getName(泛型表示符号 name){
}
无返回值: public <泛型表示符号> 泛型表示符号 getName(泛型表示符号 name){
}
静态方法语法结构:
有返回值: public static <泛型表示符号> void getName(泛型表示符号 name){
}
无返回值: public static <泛型表示符号> 泛型表示符号 getName(泛型表示符号 name){
}
静态方法中使用泛型时有一种情况需要注意一下,那就是静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
package studyweek5泛型;
public class 泛型方法 {//不想在类上面使用泛型,直接在方法上使用泛型即可
public <T> void setName(T name){//无返回值
System.out.println(name);
}
public <T> T getName(T name){//有返回值,将返回值改为T即可
return name;
}
public static <T> void setFlag(T flag){
System.out.println(flag);
}
public static <T> T getFlag(T flag){
return flag;
}
}
package studyweek5泛型;
public class 泛型方法测试 {
public static void main(String[] args) {
泛型方法 methodGeneric=new 泛型方法();//没有在类上使用泛型,因此正常实例化就行
methodGeneric.setName("泛型方法");//编译器自动判断类型,因此你传参数传的什么类型,编译器就判定为什么类型
methodGeneric.setName(123123);
泛型方法 methodGeneric2=new 泛型方法();
String name=methodGeneric2.getName("泛型方法2");
System.out.println(name);
Integer name2=methodGeneric2.getName(123123);
System.out.println(name2);
泛型方法.setFlag("静态泛型方法");
泛型方法.setFlag(123123);
String Flag=泛型方法.getFlag("静态泛型方法");
System.out.println(Flag);
Integer Flag2=泛型方法.getFlag(123123);
System.out.println(Flag2);
}
}
2.5 泛型方法与可变参数
在泛型方法中,泛型也可以定义可变参数类型。(传入参数个数不确定)
语法结构:
public <泛型表示符号> void showMsg(泛型表示符号...agrs){
}
//定义
public <T> void method(T...args){
for(T t:args){
System.out.println(t);
}
package studyweek5泛型;
public class 泛型方法测试 {
public static void main(String[] args) {
泛型方法 methodGeneric3=new 泛型方法();
String arr[]=new String[]{"a","b","c"};
methodGeneric3.method(arr);
Integer arr2[]=new Integer[]{1,2,3,4};
methodGeneric3.method(arr2);
}
}
2.6 无界通配符
“?”表示类型通配符,用于代替具体的类型。它只能在“<>”中使用。可以解决当具体类型不确定的问题。
语法结构:
public void showFlag(Generic<?> genneric)
package studyweek5泛型;
public class 无界通配符 {
public void showFlag(泛型初识<?> generic){//这个方法专门输出泛型初识中的flag的值
System.out.println(generic.getFlag());
}
}
package studyweek5泛型;
import sun.net.www.content.text.Generic;
public class 无界通配符测试 {
public static void main(String[] args) {
无界通配符 a=new 无界通配符();
泛型初识<Integer> generic=new 泛型初识<>();
generic.setFlag(20);
a.showFlag(generic);
//泛型无视默认继承
泛型初识<Number> generic1=new 泛型初识<>();
generic1.setFlag(50);
a.showFlag(generic1);//无界通配符里 public void showFlag(泛型初识<Integer> generic)已经确定参数是Integer了,因此传入参数就不能为Number类。
}
}
2.7 通配符的上限限定
如果觉得<?>范围太广,那么就可以对通配符进行限定。
上限限定表示通配符的类型是T类以及T类的子类或者T接口以及T接口的子接口。该方式同样适用于与泛型的上限限定。
通配符的上限限定语法结构:
public void showFlag(Generic<? extends Number> generic){ //?类型只能是number或者number的子类。
}
泛型的上限限定语法结构:
public class Generic<T extends Number>{}
2.8 通配符的下限限定
下限限定表示通配符的类型是T类以及T类的父类或者T接口以及T接口的父接口。
注意:该方法不适用泛型类。
通配符的下限限定语法结构:
public void showFlag(Generic<? super Number> generic){ //?类型只能是number或者number的子类。
}
3、泛型总结
二、容器
1、容器简介
程序中的“容器”的功能,用来容纳和管理数据。
开发和学习中需要时刻和数据打交道,如何组织这些数据是我们编程中重要的内容。我们一般通过“容器”来容纳和管理数据。事实上,我们前面所学的数组就是一种容器,可以在其中放置对象或基本类型数据。
数组的优势:是一种简单的线性序列,可以快速地访问数组元素,效率高。如果从效率13和类型检查的角度讲,数组是最好的。
数组的劣势:不灵活。容量需要事先定义好,不能随着需求的变化而扩容。比如:我们在一个用户管理系统中,要把今天注册的所有用户取出来,那么这样的用户有多少个?我们在写程序时是无法确定的。因此,在这里就不能使用数组。
基于数组并不能满足我们对于“管理和组织数据的需求”,所以我们需要一种更强大、更灵活、容量随时可扩的容器来装载我们的对象。这就是我们今天要学习的容器。容器(Collection)也称之为集合。
2、容器的结构
2.1 结构图
左侧是单例集合,右侧是双例集合
2.2 单例集合
单例集合:将数据一个一个的进行存储。
2.3 双例集合
双例集合:基于Key与Value的结构存储数据。一个Key(x)只能和一个Value(y)对应。
3、单例集合的使用
3.1 Collection接口介绍
Collection是单例集合根接口,它是集中、收集的意思。Collection接口的两个子接口是List、Set接口。
3.2 Collection接口中的抽象方法
由于List、Set是Collection的子接口,意味着所有List、Set的实现类都有上面的方法。我们下一节中,通过ArrayList实现类来测试上面的方法。
4、List接口介绍
4.1 List 接口特点
有序:有序(元素存入集合的顺序和取出的顺序一致)。List中每个元素都有索引标记。可以根据元素的索引标记(在List中的位置)访问元素,从而精确控制这些元素。
可重复:List允许加入重复的元素。更确切地讲,List通常允许满足e1.equals(e2)的元素重复加入容器。
4.2 List 的常用方法
除了Collection接口中的方法,List多了一些跟顺序(索引)有关的方法,参见下表:
set,remove的返回值分别是所修改、所删除的元素
5、ArrayList容器类
ArrayList是List接口的实现类。是List存储特征的具体实现。
ArrayList底层是用数组实现的存储。特点:查询效率高,增删效率低,线程不安全。
5.1 添加元素
//实例化ArrayList容器
List<String> list=new ArrayList();
//索引是从0开始的
boolean flag=list.add("ArrayList");//增加元素ArrayList
System.out.println(flag);//添加成功,返回true
//指定位置添加索引时,索引不能大于元素个数
list.add(0,"java");//在索引(下标)为0处增加元素java
System.out.println(list);元素
5.2 获取元素
size:求list中元素个数,可以用在for循环遍历中
System.out.println(list.get(0));
System.out.println(list.get(1));
//循环遍历
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
5.3 删除元素
根据索引删除元素:
删除指定元素:
//删除元素
//1、删除索引位置
String value=list.remove(1);
System.out.println("删除的元素是"+value);
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
//2、删除指定元素
//如果给定元素在容器中不存在,则返回false,否则返回true
boolean flag1=list.remove("添加元素");
System.out.println(flag1);
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
5.4 替换元素
String value2=list.set(0,"替换元素");
System.out.println("替换的元素是"+value2);
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
System.out.println("----------------");
5.5 清空容器
list.clear();
System.out.println(list.size());//返回元素个数0
5.6 判断容器是否为空
boolean flag2=list.isEmpty();
System.out.println(flag2);
list.add("java");
list.add("ArrayList");
boolean flag3=list.isEmpty();
System.out.println(flag3);
5.7 判断容器中是否包含指定元素
boolean flag4=list.contains("java");
System.out.println(flag4);
5.8 查找元素的位置
查找元素第一次出现的位置:
查找元素最后一次出现的位置:
//查找元素第一次出现的位置
list.add("java");
int index=list.indexOf("java");
System.out.println(index);
//查找元素最后一次出现的位置
int lastIndex= list.lastIndexOf("java");
System.out.println(lastIndex);
5.9 将单例集合转换为数组
转换为Object数组:
转换为泛型类型数组:
//将单例集合转化为Object类型的数组。
Object arr[]=list.toArray();
for(int i=0;i< arr.length;i++){
//System.out.println(arr[i]);
//想要强制类型转换,只能在每一次循环时进行强转。
String str=(String)arr[i];
System.out.println(str);
}
//将单例集合转换为指定类型的数组。(使用较多)
//由于泛型已经确定是String类型,即元素存放时是String类型,因此转换为数组时就不能时Integer等类型,只能是String类型了。
String arr2[]=list.toArray(new String[list.size()]);
for(int i=0;i<arr2.length;i++){
System.out.println(arr2[i]);
}
5.10 容器的并集操作
注意点:
1、参数一定要是实现了List接口的对象。
2、无法对空集进行并集操作,会返回false
//容器的并集操作
List<String> a=new ArrayList<>();
a.add("a");
a.add("b");
a.add("c");
List<String> b=new ArrayList<>();
b.add("b");
b.add("c");
b.add("d");
boolean flag5=a.addAll(b);//a并b。
System.out.println(flag5);
for(String str:a){
System.out.println(str);
}
5.11 容器的交集操作
//容器的交集操作
List<String> a1=new ArrayList<>();
a1.add("a");
a1.add("b");
a1.add("c");
List<String> b1=new ArrayList<>();
b1.add("b");
b1.add("c");
b1.add("d");
boolean flag6=a1.retainAll(b1);
System.out.println(flag6);
for(String str:a1){
System.out.println(str);
}
5.12 容器的差集操作
将交集删除,保留A容器中有的,B容器中没有的。
//容器的差集操作
List<String> a2=new ArrayList<>();
a2.add("a");
a2.add("b");
a2.add("c");
List<String> b2=new ArrayList<>();
b2.add("b");
b2.add("c");
b2.add("d");
boolean flag7=a2.removeAll(b2);
System.out.println(flag7);
for(String str:a2){
System.out.println(str);
}
5.13 ArrayList全代码
package studyweek5容器;
import java.util.ArrayList;
import java.util.List;
public class ArrayListTest {
public static void main(String[] args) {
//实例化ArrayList容器
List<String> list=new ArrayList<>();//后面的<>可以不加
//添加元素
//添加的索引是从0开始的
boolean flag=list.add("ArrayList");//增加元素ArrayList
System.out.println(flag);//添加成功,返回true
//指定位置添加索引时,索引不能大于元素个数
list.add(0,"java");//在索引(下标)为0处增加元素java
list.add(0,"添加元素");
System.out.println(list);
System.out.println(list.get(0));
System.out.println(list.get(1));
//循环遍历
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
System.out.println("----------");
//删除元素
//1、删除索引位置
String value=list.remove(1);
System.out.println("删除的元素是"+value);
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
//2、删除指定元素
//如果给定元素在容器中不存在,则返回false,否则返回true
boolean flag1=list.remove("添加元素");
System.out.println(flag1);
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
System.out.println("----------------");
//替换元素
String value2=list.set(0,"替换元素");
System.out.println("替换的元素是"+value2);
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
System.out.println("----------------");
//清空容器
list.clear();
System.out.println(list.size());//返回元素个数0
System.out.println("----------------");
//判断容器是否为空
//如果容器为空返回true,否则返回false
boolean flag2=list.isEmpty();
System.out.println(flag2);
list.add("java");
list.add("ArrayList");
boolean flag3=list.isEmpty();
System.out.println(flag3);
System.out.println("----------------");
//判断容器中是否包含指定元素
//如果在容器中包含指定元素,则返回true,否则返回false
boolean flag4=list.contains("java");
System.out.println(flag4);
System.out.println("----------------");
//查找元素位置
//如果元素不存在,则返回-1,存在,则返回索引位置
//查找元素第一次出现的位置
list.add("java");
int index=list.indexOf("java");
System.out.println(index);
//查找元素最后一次出现的位置
int lastIndex= list.lastIndexOf("java");
System.out.println(lastIndex);
System.out.println("----------------");
//将单例集合转换为数组
//将ArrayList转换为Object[]。
/**
* 泛型只是在编译的时候起到了校验代码的规则,而不是真正把元素转换为String类型
* 因此,元素在存放时是String类型,而在编译完运行的时候还是Object类型
* 数组里每一个元素仍然是Object类型
* 注意:java里的强制类型转换只是对某一个对象进行强转,而不能对数组里所有元素都转换为指定类型。
*/
//将单例集合转化为Object类型的数组。
Object arr[]=list.toArray();
for(int i=0;i< arr.length;i++){
//System.out.println(arr[i]);
//想要强制类型转换,只能在每一次循环时进行强转。
String str=(String)arr[i];
System.out.println(str);
}
//将单例集合转换为指定类型的数组。(使用较多)
//由于泛型已经确定是String类型,即元素存放时是String类型,因此转换为数组时就不能时Integer等类型,只能是String类型了。
String arr2[]=list.toArray(new String[list.size()]);
for(int i=0;i<arr2.length;i++){
System.out.println(arr2[i]);
}
System.out.println("----------------");
//容器的并集操作
List<String> a=new ArrayList<>();
a.add("a");
a.add("b");
a.add("c");
List<String> b=new ArrayList<>();
b.add("b");
b.add("c");
b.add("d");
boolean flag5=a.addAll(b);//a并b。
System.out.println(flag5);
for(String str:a){
System.out.println(str);
}
System.out.println("----------------");
//容器的交集操作
List<String> a1=new ArrayList<>();
a1.add("a");
a1.add("b");
a1.add("c");
List<String> b1=new ArrayList<>();
b1.add("b");
b1.add("c");
b1.add("d");
boolean flag6=a1.retainAll(b1);
System.out.println(flag6);
for(String str:a1){
System.out.println(str);
}
System.out.println("----------------");
//容器的差集操作
List<String> a2=new ArrayList<>();
a2.add("a");
a2.add("b");
a2.add("c");
List<String> b2=new ArrayList<>();
b2.add("b");
b2.add("c");
b2.add("d");
boolean flag7=a2.removeAll(b2);
System.out.println(flag7);
for(String str:a2){
System.out.println(str);
}
System.out.println("----------------");
}
}
6、Vector 容器类
与ArrayList一样,Vector也是List的接口实现类。
Vector底层是用数组实现的,相关的方法都加了同步检查,因此“线程安全,效率低”。比如,indexOf方法就增加了synchronized同步标记。
6.1 Vector 的使用
package studyweek5容器;
import java.util.List;
import java.util.Vector;
public class VectorTest {
public static void main(String[] args) {
//Vector和ArrayList所有方法一致
List<String> v=new Vector<>();
v.add("a");
v.add("b");
v.add("a");
for(int i=0;i<v.size();i++){
System.out.println(v.get(i));
}
System.out.println("----------------");
for(String str:v){
System.out.println(str);
}
}
}
7、Stack 容器
7.1 Stack容器介绍
Stack栈容器,是Vector的一个子类,它实现了一个标准的后进先出(LIFO:LastInFristOut)的栈。
7.2 Stack容器特点
后进先出。它通过5个操作方法对Vector进行扩展,允许将向量视为堆栈。
7.3 操作栈的方法
注意:栈的操作索引是从1开始的
package studyweek5容器;
import java.util.Stack;
public class StackTest {
public static void main(String[] args) {
//实例化栈容器
Stack<String> stack=new Stack<>();
//将元素添加到栈容器中
stack.push("a");
stack.push("b");
stack.push("c");
//栈的操作(只能从栈顶开始)
//获取栈容器的元素(会移除栈顶元素)
String p1=stack.pop();
System.out.println(p1);//c
String p2=stack.pop();
System.out.println(p2);//b
String p3=stack.pop();
System.out.println(p3);//a
//判断栈是否为空
System.out.println(stack.empty());//栈里元素都被删除,因此返回true
//查看栈顶元素(不会移除栈顶元素)
stack.push("a");
stack.push("b");
stack.push("c");
System.out.println(stack.peek());//c
//返回元素在栈容器中的位置
//注意:栈的操作索引是从1开始的
System.out.println(stack.search("a"));
}
}
7.4 Stack使用案例:判断元素对称性
//匹配符号的对称性
public void symmetry(){
String str="...{.....[....(....)...]....}..(....)..[...]...";
//实例化Stack
Stack<String> stack = new Stack<>();
//假设修正法
boolean flag=true;//假设是匹配的
for(int i=0;i<str.length();i++) {
char c = str.charAt(i);
if (c == '{') {
stack.push("}");
}
if (c == '[') {
stack.push("]");
}
if (c == '(') {
stack.push(")");
}
//判断符号是否匹配
if (c == '}'||c == ']'||c == ')') {
if(stack.empty()){
flag=false;
break;
}
String x=stack.pop();
if(x.charAt(0)!=c){
flag=false;
break;
}
}
}
if(!stack.empty()){
flag=false;
}
System.out.println(flag);
}
}
8、LinkedList 容器类
LinkedList底层用双向链表实现的存储。特点:查询效率低,增删效率高,线程不安全。
双向链表也叫双链表,是链表的一种,它的每个数据节点中都有两个指针,分别指向前一个节点和后一个节点。所以,从双向链表中的任意一个节点开始,都可以很方便地找到所有节点。
8.1 双向链表介绍(之后在数据结构里会讲)
8.2 LinkedList的使用(List标准)
package studyweek5容器;
import java.util.LinkedList;
import java.util.List;
public class LInkedListTest {
public static void main(String[] args) {
List<String> list=new LinkedList<>();
//添加元素
list.add("a");
list.add("b");
list.add("c");
list.add("d");
//获取元素(链表没有下标,返回的是位置)
for (int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
System.out.println("-------------");
for (String str:list){
System.out.println(str);
}
}
}
8.3 LinkedList的使用(非List标准)
LinkedList底层是双线链表,因此以下方法不属于List标准
package studyweek5容器;
import java.util.LinkedList;
import java.util.List;
public class LInkedListTest {
public static void main(String[] args) {
//LinkedList基于List标准
List<String> list=new LinkedList<>();
//添加元素
list.add("a");//add默认往尾部加
list.add("b");
list.add("c");
list.add("d");
//获取元素(链表没有下标,返回的是位置)
for (int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
System.out.println("-------------------");
for (String str:list){
System.out.println(str);
}
System.out.println("-------------------");
//LinkedList非List标准
//添加元素:注意!addFirst是在头部加,因此下列代码输出c、b、a
LinkedList<String> linkedList=new LinkedList<>();
linkedList.addFirst("a");
linkedList.addFirst("b");
linkedList.addFirst("c");
for(String str:linkedList){
System.out.println(str);
}
System.out.println("-------------------");
//addLast在尾部加,因此下列代码输出a、b、c
LinkedList<String> linkedList2=new LinkedList<>();
linkedList2.addLast("a");
linkedList2.addLast("b");
linkedList2.addLast("c");
for(String str:linkedList2){
System.out.println(str);
}
System.out.println("-------------------");
//获得元素
System.out.println(linkedList.getFirst());//c
System.out.println(linkedList.getFirst());//a
System.out.println(linkedList2.getFirst());//a
System.out.println(linkedList2.getLast());//c
System.out.println("-------------------");
//删除元素
System.out.println(linkedList.removeFirst());//获得删除的c
System.out.println(linkedList.removeLast());//获得删除的a
for(String str:linkedList){
System.out.println(str);
}
System.out.println("-------------------");
//将列表竖起来当栈来看
linkedList.pop();//移除顶部元素,而如果linkedList只有b,就会移除b
linkedList.add("a");
for(String str:linkedList){
System.out.println(str);
}
System.out.println("-------------------");
//入栈,向链表的头添加元素
linkedList.push("h");
for(String str:linkedList){
System.out.println(str);
}
System.out.println("-------------------");
System.out.println(linkedList.isEmpty());
}
}
9、Set 接口介绍
Set接口继承自Collection,Set接口中没有新增方法,方法和Collection保持完全一致。我们在前面通过List学习的方法,在Set中仍然适用。
9.1 Set 接口特点
Set特点:无序、不可重复。无序指Set中的元素没有索引,我们只能遍历查找;不可重复指不允许加入重复的元素。更确切地讲,新元素如果和Set中某个元素通过equals()方法对比为true,则只能保留一个。
Set常用的实现类有:HashSet、TreeSet等,我们一般使用HashSet
9.2 HashSet 容器类
HashSet是一个没有重复元素的集合,不保证元素的顺序。而且HashSet允许有null元素。HashSet是采用哈希算法实现,底层实际是用HashMap实现的(HashSet本质就是一个简化版的HashMap),因此,查询效率和增删效率都比较高。
9.3 Hash 算法原理(重点)
Hash算法也称之为散列算法。
9.4 HashSet 的使用
package studyweek5容器;
import java.util.HashSet;
import java.util.Set;
public class HashSetTest {
public static void main(String[] args) {
//实例化HashSet
Set<String> set=new HashSet<>();
//添加元素
set.add("a");
set.add("b1");
set.add("c2");
set.add("d");
set.add("a");//不能有重复的元素出现,因此这个a不会存入,可见HashSet是一个没有重复元素的集合、
//获取元素。注意:在Set容器中没有索引,因此没有对应的get(int index)方法,要遍历只能用下面的增强for循环
for(String str:set){//每一次取出set里的元素赋值给String
System.out.println(str);
}//打印结果 a,d,b1,c2,可见HashSet,不保证元素的顺序,因此add进来的元素也不保证顺序
System.out.println("-------------------------");
//删除元素
System.out.println(set.remove("c2"));
for(String str:set) {
System.out.println(str);
}
System.out.println("-------------------------");
int size=set.size();
System.out.println(size);
}
}
9.5 HashSet 存储特征分析
HashSet是一个不保证元素的顺序且没有重复元素的集合,是线程不安全的。HashSet允许有null元素
无序:在HashSet中底层是使用HashMap存储元素的。HashMap底层使用的是数组与链表实现元素的存储。元素在数组中存放时,并不是有序存放的也不是随机存放的,而是对元素的48哈希值进行运算决定元素在数组中的位置。
不重复:当两个元素的哈希值进行运算后得到相同的在数组中的位置时,会调用元素的equals()方法判断两个元素是否相同。如果元素相同则不会添加该元素,如果不相同则会使用单向链表保存该元素。(本质上是把元素的节点放在数组里)
9.6 使用HashSet存储自定义对象
!!!注意,如果想要存储自定义对象的内容不重复,必须要重写equals和hashcode方法
package studyweek5容器;
import java.util.Objects;
public class HashSetUsers {
private String username;
private int userage;
public HashSetUsers(String username, int userage) {
this.username = username;
this.userage = userage;
}
public HashSetUsers(){}
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 "HashSetUsers{" +
"username='" + username + '\'' +
", userage=" + userage +
'}';
}
@Override//存储自定义对象的时候一定要重写
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HashSetUsers that = (HashSetUsers) o;
return userage == that.userage && Objects.equals(username, that.username);
}
@Override
public int hashCode() {
return Objects.hash(username, userage);
}
}
public class HashSetTest {
public static void main(String[] args) {
//实例化HashSet
Set<HashSetUsers> set1=new HashSet<>();
HashSetUsers u=new HashSetUsers("张三",18);
HashSetUsers u1=new HashSetUsers("张三",18);
set1.add(u);
set1.add(u1);
System.out.println(u.hashCode());
System.out.println(u1.hashCode());//u和u1Hashcode值不一样,因此u1和u2都会存储进去
/**
* !!!注意,如果想要存储自定义对象的内容不重复,必须要重写equals和hashcode方法
*/
for(HashSetUsers users:set1){
System.out.println(users);
}
}
}
10、TreeSet 容器类
TreeSet是一个可以对元素进行排序的容器。底层实际是用TreeMap实现的,内部维持了一个简化版的TreeMap,通过key来存储Set的元素。TreeSet内部需要对存储的元素进行排序,因此,我们需要给定排序规则。
排序规则实现方式:
通过元素自身实现比较规则。
通过比较器指定比较规则。
10.1 TreeSet 的使用
package studyweek5容器;
import java.util.Set;
import java.util.TreeSet;
public class TreeSetTest {
public static void main(String[] args) {
Set<String> set=new TreeSet<>();
//添加元素
set.add("c");
set.add("a");
set.add("b");
set.add("d");
set.add("a");
//获取元素
//TreeSet对元素进行了排序,因此输出a,b,c。
for(String str:set){
System.out.println(str);
}
}
}
10.2 通过元素自身实现比较规则
在元素自身实现比较规则时,需要实现Comparable接口中的compareTo方法,该方法中用来定义比较规则。TreeSet通过调用该方法来完成对元素的排序处理。
//定义比较规则
//正数,大于;负数,小于;0,等于;
@Override
public int compareTo(HashSetUsers o) {
if(this.userage>o.getUserage()){ //由大到小
return 1;
}
if(this.userage==o.userage){
return this.username.compareTo(o.getUsername());
}
return -1;
}
package studyweek5容器;
import java.util.Set;
import java.util.TreeSet;
public class TreeSetTest {
public static void main(String[] args) {
Set<HashSetUsers> set1 = new TreeSet<>();
HashSetUsers u = new HashSetUsers("张三", 18);
HashSetUsers u1 = new HashSetUsers("李四", 20);
HashSetUsers u2 = new HashSetUsers("王五", 18);
set1.add(u);
set1.add(u1);
set1.add(u2);
for (HashSetUsers users : set1) {
System.out.println(users);
}
}
}
10.3 通过比较器实现比较规则
通过比较器定义比较规则时,我们需要单独创建一个比较器,比较器需要实现Comparator接口中的compare方法来定义比较规则。在实例化TreeSet时将比较器对象交给TreeSet来完成元素的排序处理。此时元素自身就不需要实现比较规则了。
package studyweek5容器;
import java.util.Comparator;
public class StudentComparator implements Comparator<Student> {
@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 TreeSetTest {
public static void main(String[] args) {
// 通过比较器实现比较规则
Set<Student> set2=new TreeSet<>(new StudentComparator());
Student s=new Student("张三",18);
Student s1=new Student("李四",20);
Student s2=new Student("王五",20);
set2.add(s);
set2.add(s1);
set2.add(s2);
for(Student student:set2){
System.out.println(student);
}
}
}
11、单例集合使用案例
需求:产生1-10之间的随机数([1,10]闭区间),将不重复的10个随机数放到容器中。
11.1 使用List类型容器实现
package studyweek5容器;
import java.util.ArrayList;
import java.util.List;
public class ListDemo {
public static void main(String[] args) {
List<Integer> list=new ArrayList<>();
while(true){
//产生随机数
int num=(int)(Math.random()*10+1);
//判断是否重复
if(!list.contains(num)){
list.add(num);
}
if(list.size()==10){
break;
}
}
for(Integer a:list){
System.out.println(a);
}
}
}
11.2 使用Set类型容器实现
package studyweek5容器;
import java.util.HashSet;
import java.util.Set;
public class SetDemo {
public static void main(String[] args) {
Set<Integer> set=new HashSet<>();
while (true){
int num=(int)(Math.random()*10+1);
//添加元素,由于Set类型容器不允许重复元素,因此不需要判断
set.add(num);
//结束循环
if(set.size()==10){
break;
}
}
//Integer类型的值的HashCode值返回的就是本身,因此会出现假排序,从小到大排
for(Integer a:set){
System.out.println(a);
}
}
}