Set集合
一、Set集合的概述
public interface Set
extends Collection
1.Set集合是一个接口,所以就不能直接创建对象,只能使用多态的方式创建对象
2.set集合是一个单列集合
二、特点
(1)无序:元素的存取顺序
(2)没有索引
//所以set集合不能使用普通for循环遍历
(3)唯一:集合中没有重复的元素
//既然set集合中的元素是唯一的,那么我向set集合中添加已有元素会报错吗?
不会报错
三、哈希值
1.什么是哈希值
就是根据"地址值"或者"内部元素属性值"计算出来的一个int数值
(1)什么时候根据"地址值"计算?
Object类中的hashCode方法就是根据"地址值"来计算的。
(2)什么时候又根据"内部元素的属性值"计算?
就是在任意一个已经重写了hashCode方法后的类
//比如:String、Integer、Character。。。。。
//问题:如果元素的属性值不同,那么hashCode的返回值会不会相同?
有可能,只不过几率较低。 //"通话" "重地"
因为hashCode方法的返回值是一个int类型的值,而int类型是有范围的(正负21亿),也就是说哈希值最多只有42亿左右个,但是我可以写
50亿个字符串,那么这个时候肯定至少会有将近8亿个哈希值是重复的
四、hashset集合的概述和特点
1.hashset集合的特点和set集合一毛一样
2.底层结构
哈希表(数组+链表)
3.set集合是怎么保证唯一性的 //非常常见的面试题
依赖了hashCode()和equals()方法
1.首先计算要添加的元素的hashCode值
(1)哈希表中不存在该哈希值
直接将元素添加到哈希表中
(2)哈希表中已经存在该哈希值
使用equals方法和该哈希值下面"桶"中的数据逐一比较
*发现里面有要添加的元素,覆盖
*发现里面没有要添加的元素,在"桶"中继续添加该元素
五、HashSet集合存储学生对象并遍历
1.在创建标准的Student的Javabean类的时候,不要忘记添加toString方法
2.一定要在Student类中重写hashCode和equals方法
//因为如果不重写这两个方法,那么调用hashcode方法时,会去使用Student的父类Object的hashCoe,这时是用地址值去计算的。不能根据属性值相同就认为是同一个对象
3.怎么重写hashCode和equals方法
idea自动生成
快捷键 ALT + insert ----------- 选择hashCode() and equals 自动生成即可
六、LinkedHashSet的概述和特点
1.底层结构
链表 + 哈希表
2.特点
唯一:由哈希表保证
存取有序:由链表保证的
3.应用
//去重
代码实现:
public class Demo4 {
public static void main(String[] args) {
LinkedHashSet<String> h1 = new LinkedHashSet<>();
h1.add("hello");
h1.add("world");
h1.add("java");
h1.add("hello");
System.out.println(h1);
}
}
TreeSet
一、TreeSet集合的概述和特点
1.概念
能够对元素按照 “某种规则” 进行排序的集合
2.特点
(1)唯一 (元素不能重复)
(2)有序 (按照某种规则) //不是存取顺序
3.TreeSet集合的两种排序方式
//前提:就是我们必须给出一个排序的规则
(1)自然排序
//要求:在创建TreeSet集合对象的时候,构造方法的参数为空
TreeSet<Student> ts = new TreeSet<>();
//怎么给出排序的规则(接口)
TreeSet集合创建对象时,<>里面的泛型类型必须去实现 Comparable,重写里面的compareTo方法
代码实现:
public class Demo5 {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>();
Student s1 = new Student("王小二",12);
Student s2 = new Student("王小三",13);
ts.add(s1);
ts.add(s2);
System.out.println(ts);
}
}
----------------------------------------------------------------------------
public class Student implements Comparable<Student> {
private String name;
private int age;
@Override
public int compareTo(Student o) {
return this.age - o.age; //这里的this指的是添加进来的Student对象, o代表的二叉树的节点
}
}
(2)比较器排序
//要求:在创建TreeSet集合对象的时候,构造方法的参数必须传入参数
TreeSet(Comparator<? super E> comparator) 构造一个新的,空的树集,根据指定的比较器进行排序。 //常用匿名内部类
//注意:这个时候排序规则在Comparator接口的compare方法中,所以<>泛型中的类就不需要再去实现 Comparable
代码实现:
public class Demo5 {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
int num = o1.getAge() - o2.getAge();
/* if (num==0){
num = this.name.compareTo(o.name);
}*/
int num2 = num == 0?o1.getName().compareTo(o2.getName()):num;
return num2;
}
});
Student s1 = new Student("王小二",12);
Student s2 = new Student("王小三",13);
Student s3 = new Student("王小四",14);
Student s4 = new Student("王小五",13);
Student s5 = new Student("王小三",15);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
System.out.println(ts);
}
}
(3)自然排序和比较器排序的区别
//区别主要在于传入排序规则的方式不同
自然排序 :排序规则是在<>泛型中实现 Comparable ,然后在里面compareTo中定义的
TreeSet<Student> ts = new TreeSet<>(); // *构造方法必须为空 * <> 中的Student必须实现Comparable
比较器排序:在创建TreeSet集合的时候,将规则以Comparator接口中compare中定义的
TreeSet<Student> ts = new TreeSet<>(new Comparator(){xxx}); // *构造方法必须传入Comparator对象, * <>中的Student不需要实现接口
(4)成绩排序案例
代码实现:(自然排序方式)
public class Student implements Comparable<Student> {
private String name;
private int chinese;
private int math;
public Student() {
}
public Student(String name, int chinese, int math) {
this.name = name;
this.chinese = chinese;
this.math = math;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getChinese() {
return chinese;
}
public void setChinese(int chinese) {
this.chinese = chinese;
}
public int getMath() {
return math;
}
public void setMath(int math) {
this.math = math;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", chinese=" + chinese +
", math=" + math +
'}';
}
public int getSum() {
return chinese + math;
}
@Override
public int compareTo(Student s) {
//先比较总分
int num1 = s.getSum() - this.getSum();
//总分相同,在按照语文成绩比
if (num1 == 0) {
num1 = s.getChinese() - this.getChinese();
}
//语文成绩相同,按照姓名比
if (num1 == 0) {
num1 = s.getName().compareTo(this.getName());
}
return num1;
}
}
---------------------------------------------------------------------------------------
public class Demo1 {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet();
Student s1 = new Student("王小二",99,89);
Student s2 = new Student("王小三",96,83);
Student s3 = new Student("王小四",90,100);
Student s4 = new Student("王小五",89,99);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
for (Student s : ts){
System.out.println(s.getName()+"==="+s.getSum());
}
}
}
//set集合的唯一性分类
*hashset
(1)底层结构是哈希表(数组 + 链表)
(2)保证唯一性的方式
//依赖了hashCode()和equals()方法
*treeset
//TreeSet集合和hashCode()和equals()没有直接的关系
(1)底层结构是二叉树
(2)保证唯一性的方式
就是当比较的规则返回值为0时,我们会认为是同一个元素,不添加
4.不重复的随机数案例
因为要的是不重复的随机数,所以考虑使用set集合
*hashset :不推荐 存取无序
*TreeSet :按照从小到大
*LinkedHashSet:按照存取的顺序
泛型 //了解
一、泛型的概述
1.什么是泛型?
泛:泛指
型:某一种"引用数据"类型
泛型:就是泛指某一种引用数据类型,但是又不确定是哪一种数据类型,一旦明确,就只能是一种数据类型(不能是多种)
public class ArrayList
明确的时机:
(1)创建对象的时候
ArrayList<String> arr = new ArrayList<>();
(2)调用方法的时候
(3)一个类实现接口的时候
2.泛型的作用
限定了放入集合中的元素的类型,将类型限定为同一种类型
3.好处
(1)把运行时期的问题提前到了编译期间
(2)避免了强制类型转换
4.注意事项
泛型中的类型只能是引用数据类型
二、泛型类 //掌握
1.什么是泛型类?
就是在普通类上加一个<E> ,就变成了泛型类
2.格式
class 类名<E>{
}
举例:
class Student<E>{ }
<E>里面的字母有什么要求?
这里的字母可以随意给,A、B、C 常用的E(Element)
3.明确时机
//在创建对象的时候
泛型类:public class ArrayList<E>
明确时机:ArrayList<String> arr = new ArrayList<>();
三、泛型方法
1.什么是泛型方法
就是在普通方法上加一个<E> ,就变成了泛型方法
2.格式
修饰符 <T> 返回值 方法名 (T t){
}
举例:
public <A> void method(A a){
System.out.println(a);
}
3.明确的时机
//在调用方法的时候
代码实现:
public static void main(String[] args) {
StudentFX sfx = new StudentFX();
sfx.method("王小二"); //明确为String
sfx.method(15); //明确为Integer
sfx.method(12.58); //明确为Double
sfx.method(false); //明确为Boolean
}
四、泛型接口
1.什么是泛型接口
就是在普通接口上加一个<E> ,就变成了泛型接口
2.格式
public interface 接口名<T>{
void show(T t);
}
3.明确时机
(1)就是在类实现接口的时候
public class MathImpl implements Math<String> {
@Override
public void study(String s) {
System.out.println(s);
}
}
class Test3{
public static void main(String[] args) {
MathImpl mi1 = new MathImpl();
mi1.study("王小二");
}
}
(2)将泛型传递下去
public class MathInpl2<T> implements Math<T> {
@Override
public void study(T t) {
System.out.println(t);
}
}
class Test4{
public static void main(String[] args) {
MathInpl2<Integer> mi2 = new MathInpl2<>();
mi2.study(13);
}
}
类型通配符 (泛型约束) //了解
1.类型通配符:<?>
List<?>:表示元素类型未知的List,它的元素可以匹配"任何的类型"
这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素添加到其中
举例:
ArrayList<?> arr = new ArrayList();
ArrayList<?> arr1 = new ArrayList();
ArrayList<?> arr2 = new ArrayList();
2.类型通配符上限:<? extends 类型>
List<? extends Number>:它表示的类型是Number或者其子类型 //小于等于的关系
举例:
ArrayList<? extends Number> arr = new ArrayList<Object>(); //报错: 因为object是number的父类。而这里限定的是number及其子类
ArrayList<? extends Number> arr1 = new ArrayList<Number>();
ArrayList<? extends Number> arr2 = new ArrayList<Integer>();
3.类型通配符下限:<? super 类型>
List<? super Number>:它表示的类型是Number或者其父类型 //大于等于的关系
举例:
ArrayList<? super Number> arr = new ArrayList<Object>();
ArrayList<? super Number> arr1 = new ArrayList<Number>();
ArrayList<? super Number> arr2 = new ArrayList<Integer>(); //报错:因为Integer是number的子类,而这里限定的是number及其父类
//泛型约束到底是约束谁的?
ArrayList<? extends Number> arr1 = new ArrayList<Number>();
约束的其实就是=后面new的那个对象的泛型
java.lang.Object
java.lang.Number
java.lang.Integer
可变参数 //熟悉 因为在后期我们看源码的时候会经常遇到
一、概述
1.什么是可变参?
就是我们在定义方法的时候,不知道要定义多少个参数,这个时候我们就可以考虑使用可变参数
2.格式
修饰符 返回值 方法名 (int... a){
xxxx
}
3.本质
//其实可变参数就是一个数组
数组: int[] ints = new int[10];
可变参: int... ints // 这里的int相当于数组中的元素类型,ints相当于数组的对象名称
4.注意事项
如果一个方法有多个参数,包含可变参数,可变参数要放在最后
//如果方法的参数中有多个可变参,那么需要把前面的可变参都改成数组,最后一个保留可变参就行