Collection集合2
泛型
泛型概述
1.什么是泛型
泛型就是参数化的数据类型,也可以称为类型参数.格式:<类型>.(ArrayList)
2.为什么要使用泛型
1.它提供了编译时类型安全检测机制,把运行时期的问题提前到了编译期间
2.避免了强制类型转换
public class MyCollectionDemo8 {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add(123);
arrayList.add(12.3);
arrayList.add("xj");
arrayList.add('a');
// 上面的代码我没有写泛型,所有类型的元素都可以添加进去
// 我们在编译阶段无法发现是否有错误,直到我们运行的时候,才会出现错误,那时候再进行修改,或强制类型转换是一件很麻烦的事
}
}
泛型的使用
1.泛型用在什么地方
- 类后面 ------> 泛型类
- 方法声明上------>泛型方法
- 接口后面------>泛型接口
2.什么是泛型类
如果一个类的后面有,表示这是一个泛型类
3.如何使用泛型类
创建泛型类对象时,必须要给这个泛型确定具体的数据类型
自定义泛型类
1.如何定义泛型
1.<类型>:指定一种类型的格式
尖括号里面可以任意书写,一般只写一个字母
例如:,,,
2.<类型1,类型2,…>:指定多种类型的格式,多种类型之间用逗号隔开
例如<E,T><K,V><Q,M>
泛型字母总结
E: 元素(Element),多用于java集合框架
K: 关键字,(Key)
N:数字,(Number)
T:类型 (Type)
V:值(Value)
2.如何定义泛型类
定义格式修饰符 class 类名<类型>{}
例如: public class Genneric<T>{}
,此处的T可以推荐使用常见的T,E,K,V等形式参数来表示泛型
- 示例代码
public class MyCollectionDemo9 {
public static void main(String[] args) {
Box<String> box = new Box<>();
box.setElement("爱生活,爱JAVA");
String str = box.getElement();
System.out.println(str);
}
}
// 泛型类
class Box<E>{
private <E> element;
// 泛型方法
public <E> getElement(){
return this.element;
}
public void setElement(E element){
this.element = element;
}
// 泛型接口
interface Dx<E>{
// 仅仅为了展示格式
}
}
泛型方法的使用
1.什么是泛型方法
方法声明中有泛型的方法,就是一个泛型方法
2.泛型方法的格式
public <T> T[] toArray(T[] arr){}
T是数组的运行时的参数类型
arr 是要存储列表元素的数组;如果它不够大,为此目的分配T类型的新数组
public class MyETDemo1 {
public static void main(String[] args) {
ArrayList<String>arrayList = new ArrayList<>();
arrayList.add("张三");
arrayList.add("李四");
arrayList.add("王五");
arrayList.add("赵六");
// 如果我们没写ArrayList的泛型
// 我们在把这个集合转换成数组的时候,类型需要写成object类型
Object[] objects = arrayList.toArray();
System.out.println(Arrays.toString(objects));
// 写了泛型之后
// 代码解析:
// 调用了ArrayList类下toArray方法的重载方法,形参是一个数组,
// 这里我把创建数组和传入形参写在一起了,
// 因为在这里这个形参和这个实参只使用这一次,所以我直接省略了创建过程
String[] str = arrayList.toArray(new String[arrayList.size()]);
// 因为传入的值是一个字符串数组,对于数组我们要么用for遍历,要么使用toString方法打印它
// 但是这个数组用的是object下的toString方法,String中没有对toString方法进行重写,
// 而object下的toString方法,返回的是地址
// 所以我去数组的工具类Arrays中,寻找toString方法,发现他的返回值是String类型的值,而不是地址,所以选择使用它
System.out.println(Arrays.toString(str));
}
}
自定义泛型方法
1.定义格式
修饰符 <类型> 返回值类型 方法名(类型 变量名){}
例如:public <T> void show(){}
泛型接口
定义格式
修饰符 interface 接口名<类型>{}
例如:interface Gu<E>{}
类型通配符
泛型通配符的使用
1.类型通配符<?>
ArrayList<?>:表示元素类型未知的ArrayList,它的元素可以匹配任何的类型,但是并不能把元素添加到ArrayList中了,获取出来的也是父类类型
public class MyETDemo2 {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("张三");
arrayList.add("李四");
arrayList.add("王五");
arrayList.add("赵六");
ArrayList<?> arrayList1 =arrayList;
// arrayList1中不能再添加元素,因为它的类型是?
// 注意这里用增强for遍历的数据类型是object
for (Object o : arrayList1) {
System.out.println(o);
}
}
}
类型通配符上限:<? extends 类型>
ArrayList<? extends Number>
:它表示的类型是Number或者其子类类型
类型通配符下限:<? super 类型>
ArrayList<? super Number>
:它表示的类型是Number或者其父类型
public class MyETDemo3 {
public static void main(String[] args) {
ArrayList<Number> arrayListTest1 = new ArrayList<>();
ArrayList<Integer> arrayListTest2 = new ArrayList<>();
ArrayList<String> arrayListTest3 = new ArrayList<>();
ArrayList<Object> arrayListTest4 = new ArrayList<>();
method1(arrayListTest1);
method1(arrayListTest2);
// method1(arrayListTest3); // String类不是Number的子类,所以报错
// method1(arrayListTest4);// String类不是Number的子类,所以报错
method2(arrayListTest1);
// method2(arrayListTest2); // Iteger不是Number的父类
// method2(arrayListTest3);// String不是Number的父类
method2(arrayListTest4);
// method3(arrayListTest1); // Number不是String的子类
// method3(arrayListTest2); // Inteage不是String的子类
method3(arrayListTest3);
// method3(arrayListTest4); // Object不是String的子类
// 所有的类都是继承于object的
method4(arrayListTest1);
method4(arrayListTest2);
method4(arrayListTest3);
method4(arrayListTest4);
}
// 泛型的上限
public static void method1(ArrayList<? extends Number> number){}
// 泛型的下限
public static void method2(ArrayList<? super Number > number){}
public static void method3(ArrayList<? extends String> number){}
public static void method4(ArrayList<? extends Object> number){}
}
小结
1.为什么要使用泛型
使用泛型的目的就是为了类型的安全,将类型的问题从运行时期,提到编译时进行处理
2.什么是泛型
它提供了编译时类型安全检测机制
3.怎么使用
在创建声明或创建时给泛型类型指定具体的类型
4.用在哪里
泛型类,泛型方法,泛型接口
5.泛型通配符
<?>
,<? extends 类/接口> // 上限
,<? super 类型/接口> //下限
Set集合
Set概述
1.Set集合的特点
- 不可以存储重复元素
- 存取顺序不一致
- 没有索引
2.Set集合的使用
public class MySetTree {
public static void main(String[] args) {
TreeSet<String> treeSet = new TreeSet<>();
treeSet.add("主宰");
treeSet.add("默默");
treeSet.add("三千");
treeSet.add("三千");
// set集合是没有索引的,所以不能通过索引获取元素
// 增强for遍历
for (String s : treeSet) {
System.out.println(s);
}
// 迭代器遍历
Iterator<String> iterator = treeSet.iterator();
while(iterator.hasNext()){
String result = iterator.next();
System.out.println(result);
}
}
}
TreeSet集合
TreeSet集合元素的特点
- 不易存储重复元素
- 没有索引
- 可以将元素按照规则进行排序
自然排序Comparable的使用
- 案例需求
- 存储学生对象并遍历,创建TreeSet集合,使用无参的构造方法
- 要求:按照年龄从小到大排序
- 示例代码
// 学生类
public class Student implements Comparable<Student> {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.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) {
if (age >= 18 && age <= 25) {
this.age = age;
} else {
throw new IndexOutOfBoundsException("年龄超出范围");
}
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
// 通过实现Comparable接口,重写方法compareTo方法,来完成排序
@Override
public int compareTo(Student o) {
// o.age代表存入的值,返回结果为正,存入右边,为负存入左边/this.age表示现在要存入的
int result =this.age-o.age;
result = result == 0?this.name.compareTo(o.getName()):result;
return result;
}
}
public class MyTreeSetDemo3 {
public static void main(String[] args) {
TreeSet<Student> treeSet = new TreeSet<>();
Student student1 = new Student("张三",21);
Student student2 = new Student("张1",23);
Student student3 = new Student("张2",27);
Student student4 = new Student("张4",22);
treeSet.add(student1);
treeSet.add(student2);
treeSet.add(student3);
treeSet.add(student4);
treeSet.forEach(s->{
System.out.println(s);
});
}
}
比较器排序Comparator的使用
- 案例需求
- 存储老师对象并遍历,创建TreeSet集合使用带参构造方法
- 要求:按照年龄从小到大排序,年龄相同时,按照名字的字母排序
提示
int compare(T O1,T O2)
比较两个参数,返回的是负整数,零或正整数.
// teacher类
public class Teacher {
private String name;
private int age;
public Teacher() {
}
public Teacher(String name, int age) {
this.name = name;
this.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 String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
// 测试类
public class TeacherTreeSet {
public static void main(String[] args) {
Teacher teacher = new Teacher("wang",24);
Teacher teacher1 = new Teacher("li",31);
Teacher teacher2 = new Teacher("zhang",24);
// 重写方法,来规定排序的规则
/*TreeSet<Teacher> teacherTreeSet = new TreeSet<>(new Comparator<Teacher>() {
@Override
public int compare(Teacher o1, Teacher o2) {
int result = o1.getAge()-o2.getAge();
result = result==0?o1.getName().compareTo(o2.getName()):result;
return result;
}
});*/
// 采用lambda方法设置排序规则
TreeSet<Teacher> teacherTreeSet = new TreeSet<>(((o1, o2) -> {
int result = o1.getAge()-o2.getAge();
result = result==0?o1.getName().compareTo(o2.getName()):result;
return result;
}));
teacherTreeSet.add(teacher);
teacherTreeSet.add(teacher1);
teacherTreeSet.add(teacher2);
for (Teacher teacher3 : teacherTreeSet) {
System.out.println(teacher3);
}
}
}
两种比较方式总结
1.不同点
用到的接口不同
自然排序: 自定义类实现Comparable接口,重写compareTo方法,根据返回值进行排序
比较器排序:创建TreeSet对象的时候,传递Comparator的实现类对象,重写compare方法,根据返回值进行排序
使用场景不同
自然排序能满足大部分情况
存储没有修改权限的类时,可以使用(例如:改变String类型比较规则,包装类)比较器排序
2.相同点
返回值的规则
如果返回值为负,表示当前值是较小值,存左边
如果返回值是0,表示当前元素跟存入的元素重复了,不存
如果返回值是正,表示当前值是较大值,存右边
3.任意被Object类实现的方法都不是抽象方法,因此可以使用匿名内部类,也可以使用Lambda表达式
{一个非常有特点的例子,就是Comparator接口中有equals和compare两个方法,却能使用Lambda表达式,原因是因为equals方法被object类实现了}当然,源码中也有这样一句话
Note that it is always safe not to override