文章目录
泛型
引入
为什么需要泛型?
我们知道Object类型可以接收任意类型的对象类型,但在实际使用的过程中,我们总是会遇到有类型转换的问题,这样就会存在一些隐患(比如说当我们需要解决向下转型的问题时,会出现类型不匹配的情况),所以java提供了泛型来解决这个问题。
概述
泛型,即 " 参数化类型 " ,参数化类型就是将具体的类型参数化,泛指一种类型,类型不确定,可以以参数的形式传入,类似于方法中的变量参数,此时的类型也定义为了参数类型(也可以称之为类型形参),然后在使用或者调用时传入具体的类型(类型实参)。
注意:
1、泛型类型必须是类类型(引用类型),包括自定义的类。
2、泛型的参数可以有多个。
3、若是没有指定具体的类型,默认为Object类型。
泛型的使用
事实上,在集合这里以及之后的学习中,我们会频繁使用到泛型,就像我们在定义一个Arraylist时总会看到后面提示一个 <> ,而在这个尖括号中要填入具体的引用类型,就比如说我们需要定义一个存放int型的ArrayList集合,我们就要这样声明:ArrayList list = new ArrayList( ) ;。而当我们要声明一个泛型的类时也需要有一个代指任意类型的大写字母存在,比如说:public class Main< T >{…} 当然这里的<>中用来代指泛型的字母可以是任意字母。
代码示例
import java.util.ArrayList;
public class Main<T,E> {
T name;
E age;
public Main(T name,E age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Main{" +
"name=" + name +
", age=" + age +
'}';
}
public static void main(String[] args) {
Main<String,Double> m1 = new Main<>("Jim",19.5);
Main<String,Double> m2 = new Main<>("Tom",18.0);
Main<String,Double> m3 = new Main<>("Jer",19.0);
Main<String,Double> m4 = new Main<>("Mac",16.5);
Main<String,Double> m5 = new Main<>("Lu",22.0);
ArrayList<Main> list = new ArrayList<>();
list.add(m1);
list.add(m2);
list.add(m3);
list.add(m4);
list.add(m5);
System.out.println(list);
}
}
Set接口
Set接口和List接口一样,都继承了Collection接口,因此它能够使用Collection接口的所有方法。
Set接口中不允许包含重复元素。
Set接口有两种实现类:HashSet(无序)和TreeSet(有序)
代码示例
import java.util.HashSet;
import java.util.Set;
public class SetDemo {
public static void main(String[] args) {
Set<Integer> set = new HashSet<>();
set.add(23);
set.add(1);
set.add(3);
set.add(4);
System.out.println(set);//[1, 3, 4, 23]
}
}
实现类:HashSet类
概述
1、HashSet是无序的,这里的无序指不是按照添加顺序排列的,而是按照底层计算的Hash值排列的。
2、HashSet类的底层实现是HashMap。而Map集合是双列存储的,即以键值对的方式存储,键不可以重复,而值可以重复,所以我们会把元素传到HashMap中的键的位置,值的位置有一个静态常量,永远不变。
3、如果一个类中没有重写hashCode(),那么就会调用Object类中的hashcode方法。举一个例子:如下代码,这里输出数组a是[I@1b6d3586 这就是一个哈希(hash)值,默认调用Object类中的toString()调用其中的hashCode方法计算出一个hash值1b6d3586。
int [] a = {
1,2,3};
System.out.println(a);//[I@1b6d3586
4、HashSet类对添加进去的元素根据内容的hash值,再经过hash函数计算得到元素在hash表中的存储的位置,存储方式为hash表+链表的形式。如图所示:
5、假如创建两个内容相同的对象实例,Object类中的hashCode方法计算的hash值一定是不一样的,因为它们的位置就不一样,但是我们在实际使用当中一定是要把内容相同的元素排除掉的,因此我们就需要重写Object类中的hashCode方法来计算hash值,还需要重写equals方法来确定添加元素的内容是否相同,使用这样的方法来满足Set集合中元素不重复的条件。
6、哈希表的默认长度是16,若是hash表下的链表元素达到一定的高度,就会对哈希表进行扩容,加载因子(loadFactor)为0.75,而这个容器的临界值=容量(长度)*加载因子,当存储的元素到了这个临界值,南无容器就会自动扩容。
HashSet的常用方法
返回值类型 | 方法名 | 功能 |
---|---|---|
构造方法 | HashSet() | 创建一个大小为16的容器 |
构造方法 | HashSet(Collection c) | 构造一个包含指定元素的HashSet集合 |
构造方法 | HashSet(int initialCapacity) | 构造一个指定容量和默认负载因子的HashSet集合 |
构造方法 | HashSet(int i,float l) | 构造一个指定容量和指定负载因子的HashSet集合 |
boolean | add(E a) | 将指定元素添加到此集合,这里的E指泛型 |
boolean | remove(object o) | 删除指定元素 |
boolean | contains(Object obj) | 判断此集合中是否包含某元素 |
boolean | isEmpty() | 判断此集合是否为空 |
Iterator | iterator() | 返回此集合中元素的迭代器 |
int | size() | 返回集合长度 |
void | clear() | 清空此集合中的所有元素 |
代码示例
import java.util.HashSet;
public class HashSetDemo {
public static void main(String[] args) {
//给定容量的构造方法
HashSet<String> set = new HashSet<>(20);
//添加元素
/*
* hash函数 通过return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
* 将输入进去的hashCode值计算出来,表示元素应该添加在hash表中的什么位置
*/
set.add("a");
set.add("a");
set.add("b");
set.add("c");
set.add("d");
System.out.println(set);//[a, b, c, d] 很明显a没有被添加进去
//删除元素
set.remove("c");
System.out.println(set);//[a, b, d]
//判断是否包含某元素
System.out.println(set.contains("a"));
//判断是否为空
set.contains(set.isEmpty());
//获取元素内容的长度
System.out.println(set.size());
//清空集合
set.clear();
System.out.println(set);
}
}
HashSet的使用
首先解决一个问题:像HashSet中添加元素时如何判断重复元素?
答:HashSet的底层是双保险的:即能够提高效率,又安全可靠。
1、首先会获得添加元素的Hash值,判断hash值在集合中是否存在,但是当添加元素内容不同时,它们的hash值又有可能是一致的,这就会造成不同元素没有被添加进去,因此这样的一个判断够快但不够可靠,所以我们还需要一个帮手。
2、在hash值相同的情况下,我们调用equals方法来比较它们的内容是否一致。
代码示例
import java.util.HashSet;
import java.util.Objects;
public class HashSetDemo1 {
public static void main(String[] args) {
Car c1 = new Car("宝马",300000);
Car c2 = new Car("宝马",300000);
Car c3 = new Car("奔驰",900000);
Car c4 = new Car("现代",90000);
Car c5 = new Car("吉利",40000);
HashSet<Car> set = new HashSet<>();
set.add(c1);
set.add(c2);
set.add(c3);
set.add(c4);
set.add(c5);
System.out.println(set);
}
}
class Car{
private String type;
private int money;
public Car(){
}
public Car(String type,int money){
this.type = type;
this.money = money;
}
//第二道保险: 重写equals方法,比较内容是否一致
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return money == car.money &&
Objects.equals(type, car.type);
}
//第一到保险: 判断hash值是否一致
@Override
public int hashCode() {
return Objects.hash(type, money);
}
//重写一个输出方法
@Override
public String toString() {
return "Car{" +
"type='" + type + '\'' +
", money=" + money +
'}';
}
}
实现类:TreeSet
概述
TreeSet集合是有序的,这里的有序同样不是指按照元素的添加顺序排列,而是按照元素的自然顺序排列(比如说:存4231 排序1234 存cbda 排序abcd),底层依靠红黑树实现。
创建:TreeSet set = new TreeSet();
代码示例
import java.util.TreeSet;
public class TreeSetDemo1 {
public static void main(String[] args) {
TreeSet<Integer> set = new TreeSet<>();
set.add(3);
set.add(2);
set.add(4);
set.add(1);
set.add(1);
System.out.println(set);//[1, 2, 3, 4] 按照自然顺序排列 没有重复元素
}
}
TreeSet的特点
1、不可以存储重复元素。
2、有序(按照内容的自然顺序排列)。
3、向TreeSet中添加元素时会调用comparaTo()方法比较大小,==0 >0 <0
4、底层使用的是红黑树。
5、使用自定义数据类型时,必须实现Comparable接口,重写comparaTo方法,TreeSet添加元素时调用,比较大小,作用于排序。
代码示例
import java.util.TreeSet;
public class TreeSetDemo2 {
public static void main(String[] args) {
Work w1 = new Work("程序员",20000);
Work w2 = new Work("医生",15000);
Work w3 = new Work("CEO",10000000);
Work w4 = new Work("teacher",9000);
TreeSet<Work> set = new TreeSet<>();
set.add(w1);
set.add(w2);
set.add(w3);
set.add(w4);
System.out.println(set);
}
}
class Work implements Comparable<Work>{
private String type;
private int money;
public Work(){
}
public Work(String type,int money){
this.type = type;
this.money = money;
}
//重写toString方法
@Override
public String toString() {
return "Work{" +
"type='" + type + '\'' +
", money=" + money +
'}';
}
//重写比较方法
@Override
public int compareTo(Work o) {
return this.money-o.money;
}
}
Set接口集合迭代
因为Set集合是没有索引的,因此只有两种遍历方法
foreach循环遍历
语法格式:for(Object obj:set){…} 或者 set.forEach(e->System.out.println(e));
import java.util.TreeSet;
public class forEachDemo {
public static void main(String[] args) {
TreeSet<Integer> set = new TreeSet<>();
set.add(3);
set.add(2);
set.add(4);
set.add(1);
System.out.println(set);
for (Integer i: set) {
System.out.print(i+"\t");
}
System.out.println();
set.forEach(integer -> System.out.print(integer+"\t"));
}
}
迭代器
语法格式:Iterator<E> iterator = set.iterator();
While(iterator.hasNext()){
......
}
代码示例
import java.util.Iterator;
import java.util.TreeSet;
public class IteratorDemo {
public static void main(String[] args) {
TreeSet<Integer> set = new TreeSet<>();
set.add(3);
set.add(2);
set.add(4);
set.add(1);
System.out.println(set);
Iterator<Integer> iterator = set.iterator();
while(iterator.hasNext()){
Integer i = iterator.next();
System.out.print(i+"\t");
}
}
}