一、集合框架之set(HashSet哈希表存储、重复元素存储底层探究)
简单了解set:set存储的元素是无序的,存入和取出的顺序不一定一致,并且set不可以存入重复的元素(不能存放重复元素:字符串、八大基本数据类型)
演示代码:
package com.songwanxi.set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
*1、set集合不能存放重复元素的问题
* 不能存放重复元素 字符串、八大基本数据类型
* set集合中的元素是无序的
* 2、HashSet哈希表存储、重复元素存储底层探究
* list.contains 底层调用了equals方法
* set.add 底层调用了hashCode/equals
* @author 10570
*
*/
public class SetDemo {
public static void main(String[] args) {
Set set = new HashSet<>();
set.add("a"); //存放字符串类型
set.add("b");
set.add("c");
set.add("d");
set.add("a");
System.out.println("set长度:"+set.size());
Iterator it = set.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
附上结果:
问:HashSet是如何保证元素唯一性的呢?
答:是通过元素的两个方法:hashCode与equals方法来完成;
如果hashCode值相同,才会判断equals是否为true;
如果hashCode值不同,那么不会调用equals。
所以关于set存储自定义类时,判断是否重复需要根据需求重写hashCode()和equals(Object obj)方法
大致关系示意图:
代码演示:
package com.songwanxi.set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class SetDemo {
public static void main(String[] args) {
Set set = new HashSet<>();
// set.add("a"); //存放字符串类型
// set.add("b");
// set.add("c");
// set.add("d");
// set.add("a");
//自定义类
set.add(new Person("王五", 12, 1000));
set.add(new Person("王五", 12, 1000));
set.add(new Person("张三", 18, 1900));
set.add(new Person("张三丰", 22, 5000));
set.add(new Person("李四", 90, 1000));
System.out.println("set长度:"+set.size());
Iterator it = set.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
// for (Object obj : set) {
// System.out.println(obj);
// }
}
}
class Person{
private String name;
private int age;
private int money;
public Person(String name, int age, int money) {
super();
this.name = name;
this.age = age;
this.money = money;
}
public Person() {
super();
}
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;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", money=" + money + "]";
}
//重写hashcode方法
@Override
public int hashCode() {
// TODO Auto-generated method stub
//System.out.println("hashcode=="+super.hashCode());//增加时 super.hashcode()的值每次都不一样
int code = this.name.hashCode() + this.age;//计算名字和年龄的值
System.out.println(this.toString()+"-----code:"+code);//查看code值
return code;
}
//重写equals
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
Person p = (Person) obj;
System.out.println("比较名字:"+this.name+"---"+p.name);
System.out.println("比较年龄:"+this.age+"---"+p.age);
return this.name.equals(p.name) && this.age == p.age;//当值code相等时,就比较名字和年龄是否相等
}
}
结果:
注意:对于判断元素是否存在,以及删除等操作,依赖的方法同样是hashCode、equals方法。
问:List与Set判断重复对象的区别?
答:List只依赖于equals方法 , Set依赖于hashCode、equals方法。
二、集合框架TreeSet(自然排序、数据结构二叉树、比较器排序)
2.1 TreeSet自然排序
让元素具备比较性,在对象的类中实现Comparable接口,重写compareTo方法
注意:TreeSet排序的第一种方式,让元素自身具有比较性;
元素需要实现Comparable接口,覆盖compareTo方法;
这种方式也被称为元素的自然顺序,或者叫做默认顺序
例如对存放的int类型排序
演示代码:
package com.songwanxi.set;
import java.util.TreeSet;
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet set = new TreeSet<>();
set.add(12); //存放int类型排序
set.add(11);
set.add(29);
set.add(27);
set.add(50);
System.out.println(set);
}
}
演示结果:
可以看见存放时是乱序 取出来的时候自动进行了排序
对String进行排序
演示代码:
package com.songwanxi.set;
import java.util.TreeSet;
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet set = new TreeSet<>();
// set.add(12); //存放int类型排序
// set.add(11);
// set.add(29);
// set.add(27);
// set.add(50);
set.add("b"); //存放String类型排序
set.add("a");
set.add("ec");
set.add("abc");
set.add("d");
System.out.println(set);
}
}
结果:
对于引用数据类型想要排序,必须实现Comparable接口,否则就会出现错误:java.lang.ClassCastException:com.songwanxi.set.Person cannot be cast to java.lang.Comparable
演示代码:
package com.songwanxi.set;
import java.util.Iterator;
import java.util.TreeSet;
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet<Object> set = new TreeSet<>();
// set.add(12); //存放int类型排序
// set.add(11);
// set.add(29);
// set.add(27);
// set.add(50);
// set.add("b"); //存放String类型排序
// set.add("a");
// set.add("ec");
// set.add("abc");
// set.add("d");
//System.out.println(set);
set.add(new Person("王五", 12, 1000));
set.add(new Person("王五", 12, 1000));
set.add(new Person("张三", 18, 1900));
set.add(new Person("张三丰", 22, 5000));
set.add(new Person("李四", 90, 1000));
Iterator it = set.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
class Person{
private String name;
private int age;
private int money;
public Person(String name, int age, int money) {
super();
this.name = name;
this.age = age;
this.money = money;
}
public Person() {
super();
}
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;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", money=" + money + "]";
}
//重写hashcode方法
@Override
public int hashCode() {
// TODO Auto-generated method stub
//System.out.println("hashcode=="+super.hashCode());//增加时 super.hashcode()的值每次都不一样
int code = this.name.hashCode() + this.age;//计算名字和年龄的值
System.out.println(this.toString()+"-----code:"+code);//查看code值
return code;
}
//重写equals
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
Person p = (Person) obj;
System.out.println("比较名字:"+this.name+"---"+p.name);
System.out.println("比较年龄:"+this.age+"---"+p.age);
return this.name.equals(p.name) && this.age == p.age;//当值code相等时,就比较名字和年龄是否相等
}
}
结果:
如何实现Comparable接口:
class Person implements Comparable<Person>{
private String name;
private int age;
private int money;
public Person(String name, int age, int money) {
super();
this.name = name;
this.age = age;
this.money = money;
}
public Person() {
super();
}
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;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", money=" + money + "]";
}
//重写hashcode方法
@Override
public int hashCode() {
// TODO Auto-generated method stub
//System.out.println("hashcode=="+super.hashCode());//增加时 super.hashcode()的值每次都不一样
int code = this.name.hashCode() + this.age;//计算名字和年龄的值
System.out.println(this.toString()+"-----code:"+code);//查看code值
return code;
}
//重写equals
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
Person p = (Person) obj;
System.out.println("比较名字:"+this.name+"---"+p.name);
System.out.println("比较年龄:"+this.age+"---"+p.age);
return this.name.equals(p.name) && this.age == p.age;//当值code相等时,就比较名字和年龄是否相等
}
/**
* 实现Comparable接口就是让其具备比较性
* 例如:前后的年龄进行比较
*/
@Override
public int compareTo(Person o) {
// TODO Auto-generated method stub
return this.age-o.age;
}
}
当实现了Comparable接口后就不会报错了:
具体代码如下:
package com.songwanxi.set;
import java.util.Iterator;
import java.util.TreeSet;
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet<Object> set = new TreeSet<>();
// set.add(12); //存放int类型排序
// set.add(11);
// set.add(29);
// set.add(27);
// set.add(50);
// set.add("b"); //存放String类型排序
// set.add("a");
// set.add("ec");
// set.add("abc");
// set.add("d");
//System.out.println(set);
set.add(new Person("王五", 12, 1000));
set.add(new Person("王五", 12, 1000));
set.add(new Person("张三", 18, 1900));
set.add(new Person("张三丰", 22, 5000));
set.add(new Person("李四", 90, 1000));
Iterator it = set.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
class Person implements Comparable<Person>{
private String name;
private int age;
private int money;
public Person(String name, int age, int money) {
super();
this.name = name;
this.age = age;
this.money = money;
}
public Person() {
super();
}
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;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", money=" + money + "]";
}
//重写hashcode方法
@Override
public int hashCode() {
// TODO Auto-generated method stub
//System.out.println("hashcode=="+super.hashCode());//增加时 super.hashcode()的值每次都不一样
int code = this.name.hashCode() + this.age;//计算名字和年龄的值
System.out.println(this.toString()+"-----code:"+code);//查看code值
return code;
}
//重写equals
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
Person p = (Person) obj;
System.out.println("比较名字:"+this.name+"---"+p.name);
System.out.println("比较年龄:"+this.age+"---"+p.age);
return this.name.equals(p.name) && this.age == p.age;//当值code相等时,就比较名字和年龄是否相等
}
/**
* 实现Comparable接口就是让其具备比较性
* 例如:前后的年龄进行比较
*/
@Override
public int compareTo(Person o) {
// TODO Auto-generated method stub
return this.age-o.age;
}
}
运行结果:
注意:在做自然排序方法重写的时候,一定先判断主要条件、还要判断次要条件
2.2、TreeSet数据结构(二叉树)
可以对set集合进行排序,底层数据结构是二叉树;
保证元素唯一性的就是compareTo方法return 0
注意:TreeSet排序的第一种方式,让元素自身具有比较性;
元素需要实现Comparable接口,覆盖compareTo方法;
这种方式也被称为元素的自然顺序,或者叫做默认顺序。
问:如何让TreeSet集合中的元素怎么存进去怎么取出来呢?
答:compareTo方法返回值为正数,返回值写死,那么就是怎么存进去怎么取出来。
compareTo方法返回值为负数数,返回值写死,那么就是先进后出。
二叉树的图解:
如果想要了解一下二叉树可以点击这里点我
2.3 TreeSet比较器排序
注意:当元素自身不具备比较性时,或者具备的比较性不是所需要的时候
这时需要让集合自身具备比较性,在集合初始化时,就有了比较方式;
代码如下:
package com.songwanxi.set;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
public class TreeSetDemo {
public static void main(String[] args) {
//TreeSet<Object> set = new TreeSet<>(); //不调用比较器
TreeSet<Person> set = new TreeSet<>(new PersonMoneyAgeComp());//调用比较器
// set.add(12); //存放int类型排序
// set.add(11);
// set.add(29);
// set.add(27);
// set.add(50);
// set.add("b"); //存放String类型排序
// set.add("a");
// set.add("ec");
// set.add("abc");
// set.add("d");
//System.out.println(set);
set.add(new Person("王五", 12, 1000));
set.add(new Person("王五", 12, 1800));
set.add(new Person("张三", 18, 1900));
set.add(new Person("张三丰", 22, 5000));
set.add(new Person("李四", 90, 1000));
Iterator it = set.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
/**
* 比较器排序 需求:money多 age小
*/
class PersonMoneyAgeComp implements Comparator<Person>{
@Override
public int compare(Person o1, Person o2) {
// TODO Auto-generated method stub
int num = o2.getMoney() - o1.getMoney();//比较money
if(num == 0) {//当money相等时
return o1.getAge() - o2.getAge();//比较age
}
return num;
}
}
/**
* age比较
* @author 10570
*
*/
class Person implements Comparable<Person>{
private String name;
private int age;
private int money;
public Person(String name, int age, int money) {
super();
this.name = name;
this.age = age;
this.money = money;
}
public Person() {
super();
}
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;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", money=" + money + "]";
}
//重写hashcode方法
@Override
public int hashCode() {
// TODO Auto-generated method stub
//System.out.println("hashcode=="+super.hashCode());//增加时 super.hashcode()的值每次都不一样
int code = this.name.hashCode() + this.age;//计算名字和年龄的值
System.out.println(this.toString()+"-----code:"+code);//查看code值
return code;
}
//重写equals
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
Person p = (Person) obj;
System.out.println("比较名字:"+this.name+"---"+p.name);
System.out.println("比较年龄:"+this.age+"---"+p.age);
return this.name.equals(p.name) && this.age == p.age;//当值code相等时,就比较名字和年龄是否相等
}
/**
* 让元素具备比较性
* 注意:在做自然排序方法重写的时候,一定先判断主要条件、还要判断次要条件
*/
@Override
public int compareTo(Person o) {
// TODO Auto-generated method stub
return this.age-o.age;
}
}
这里我们对比的是person类里的money和age
需求为:money多 age小
运行结果:
可以看到 主要条件money多的在前面 而当money相等时 age小的在前面
更改条件测试:age大 money少
代码:
/**
* 比较器排序 需求:age大 money少
*/
class PersonAgeMoneyComp implements Comparator<Person>{
@Override
public int compare(Person o1, Person o2) {
// TODO Auto-generated method stub
int num = o2.getAge() - o1.getAge();
if(num == 0) {//当money相等时
return o1.getMoney() - o2.getMoney();
}
return num;
}
}
运行结果:
这次主要条件换成了age大 次要条件换成了money少,所以可以看到年龄大同时钱少的在前,年龄小同时钱多的在后
注:因为当两种排序都存在时,比较器排序优先级更高,所以当两种方法都存在时,比较器定义的排序方法会覆盖上一个方法的排序方法,以比较器定义的排序方法为准。
三、泛型
关于泛型:
好处/为什么出现?
①不使用泛型的情况下,会将未知的错误表现在运行时期:
错误示例:
package com.songwanxi.set;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class FxDemo {
public static void main(String[] args) {
List c = new ArrayList();
c.add(22);
c.add(23);
c.add(26);
c.add(28);
c.add(55);
c.add("这是错误");
Iterator it = c.iterator();
while(it.hasNext()) {
Object obj = it.next();
int num = (int) obj;
if(num % 2 == 0) {
System.out.println(num);
}
}
}
}
上面这些代码在编译期并没有错误,但是在运行后却会报错
结果:
正确示例(加上泛型后):
可以看到在加上泛型后,错误的那行代码直接显示出来了,这样就可以将运行期的异常转换成编译期的错误,让程序员更早的发现,从而解决代码隐患。
②提高了代码健壮性:
健壮性是指软件对于规范要求以外的输入情况的处理能力。所谓健壮的系统是指对于规范要求以外的输入能够判断出这个输入不符合规范要求,并能有合理的处理方式。
也就是说,最初的理解类似在机房收费系统中,输入的文本框是否是规范的值,比如卡号输入一些乱码等,当然这样的理解仅仅停留在了表象上。
泛型对于程序员来说是个节省时间提高效率的好帮手:
再写dao包时我们普通的写法是:
* 购物车项目
* 订单模块OrderDao,用户模块UserDao、商品模块ProductDao
* Class OrderDao{
* public List<Order> list(Order order){
* 增删改查………………
* public int add(Order order){}
* public int edit(Order order){}
* public int del(Order order){}
*
* }
*
* Class UserDao{
* public List<User> list(User User){
*增删改查………………
* public int add(User User){}
* public int edit(User User){}
* public int del(User User){}
*
* }
*Class ProductDao{
* 增删改查………………
* }
这样的写法重复内容高,且技术含量低,容易造成时间浪费
但是用上泛型的写法后:
* Class BaseDao<T>{
* public List<T> list(T t){}
* public int add(T t){}
* public int edit(T t){}
* public int del(T t){}
* }
*
* Class OrderDao extends BaseDao<Order>{}
* Class UserDao extends BaseDao<User>{}
* Class ProductDao extends BaseDao<Product>{}
只要写好一个基础泛型后,其他的都能用上,区别只是改变‘T’而已
泛型类:
Class BaseDao<T>{}
class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
private 泛型标识 /*(成员变量类型)*/ var;
.....
}
}
泛型方法:
public BaseDao(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
this.key = key;
}
静态方法泛型:
public static <T> void add(T t){}
泛型接口:
//定义一个泛型接口public interface BaseDao<T> {
public T next();
}
如果你想要详细了解泛型可以点击点我了解更多