泛型
泛型就是一种不确定的数据类型,使用<字母>,可以使用在类上,方法上和接口上。
泛型类
泛型类,指的是在类上有一个确定的数据类型,在创建该类对象时确定其数据类型。
//<E>: 表示一种不确定的数据类型
public class Box<E> {
//定义一个变量
private E element;
public E getElement() {
return element;
}
public void setElement(E element) {
this.element = element;
}
}
//泛型类上的<E>,在创建对象的时候确定数据类型
public class Demo1 {
public static void main(String[] args) {
//确定Box类中<E>的数据类型为String
Box<String> box1=new Box<>();
box1.setElement("hello");
String element = box1.getElement();
System.out.println(element);
///确定Box类中<E>的数据类型为Integer
Box<Integer> box2=new Box<>();
box2.setElement(100);
Integer element2 = box2.getElement();
System.out.println(element2);
}
}
泛型方法
泛型方法,表示在方法上有一个不确定的数据类型,在调用这个方法时来确定其数据类型
public class Demo3{
public static void main(String[] args){
Collection<String> coll1 = addElement(new LinkedList<String>(), "abc", "bbc");
Collection<Integer> coll2 = addElement(new ArrayList<Integer>(), 10, 20,30);
System.out.println(coll1);
System.out.println(coll2);
}
//给一个集合添加元素
//可变参数: 数据类型...变量名
//T...arr 表示可以接收多个T类型的数据,等价于T[] arr
public static <T> Collection<T> addElement(Collection<T> list, T...arr){
for (int i = 0; i < arr.length; i++) {
list.add(arr[i]);
}
return list;
}
}
泛型接口
泛型接口指的是,在接口上有一个不确定的数据类型,可以在接口的实现类上确定其数据类型;‘
也可以把接口的泛型沿用到实现类上,在创建类的对象时确定其数据类型。
//Inter<T>: 在Inter接口上不确定的数据类型T
public interface Inter<T>{
public void method(T t);
}
- 情况1:在实现类上确定Inter的泛型
//Inter<String>: 确定接口上的泛型为String
public class InterImpl implements Inter<String>{
public void method(String t){
....
}
}
- 情况2:把接口的泛型,沿用到实现类上
//把Inter接口上的泛型<T>,沿用到InterImpl2类上
public class InterImpl2<T> implements Inter<T>{
public void method(T t){
....
}
}
- 测试类
public class Demo4{
public static void main(String[] args){
InterImpl impl1=new InterImpl();
impl1.method("hello");
InterImpl2<Integer> impl2=new InterImpl2<Integer>();
impl2.method(20);
InterImpl2<String> impl3=new InterImpl2<String>();
impl3.method("world");
}
}
泛型通配符
<?> : 可以是任意类型
<? extends Number>: Number或者Number的子类
<? super Number>: Number或者Number的父类
注意:一般泛型通配符是使用在方法的参数上,限定参数的数据类型。
Set集合
TreeSet集合可以对元素进行排序,排序有两个方式,分别是自然排序和比较器排序
自然排序
- 自定义的类,实现Comparable接口,复写compareTo方法
public class Student implements Comparable<Student>{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
//告诉TreeSet,排序规则:按照学生的年龄升序排列
int num = this.age - o.age;
//如果年龄相同,就按照姓名比较
if (num == 0) {
num = this.name.compareTo(o.name);
}
return num;
}
}
- 创建TreeSet集合,存储Student元素,每次存储元素的时候,会自动对元素进行排序
public class SetDemo2 {
public static void main(String[] args) {
Set<Student> set = new TreeSet<>();
set.add(new Student("aaa", 18));
set.add(new Student("ccc", 17));
set.add(new Student("bbb", 19));
set.add(new Student("eee", 20));
set.add(new Student("ddd", 20));
for (Student student : set) {
System.out.println(student);
}
}
}
比较器排序
在创建TreeSet集合的时候,指定Comparator比较器,对集合中的元素进行排序
//在创建TreeSet对象时,指定比较器Comparator
Set<Student> set = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge()-o2.getAge(); //升序:从小到大
}
});
set.add(new Student("aaa", 18));
set.add(new Student("ccc", 17));
set.add(new Student("bbb", 19));
set.add(new Student("eee", 20));
set.add(new Student("ddd", 20));
for (Student student : set) {
System.out.println(student);
}
两种排序方式如何选择
1. 如果使用API中已有的类作为TreeSet元素,
当API中的类已经实现了Comparable自然排序的规则,但是这个排序规则不满足我们的要求
这个时候,我们可以选择Comparator比较器排序。
2. 如果是自定义的类作为TreeSet元素,比较器排序或者自然排序,任选一个就可以了。
二叉树结构(了解)
TreeSet集合可以对元素进行排序,是因为底层的数据结构是二叉树。
二叉树:由若干个节点组成的树形结构,一个节点最多有两个子节点
二叉查找树(二叉搜索树,二叉排序树):对于任意一个节点,左边存储较小的节点,右边存储较大的节点
二叉平衡树:对于任意一个节点,左子树和右子树的高度差不超过1
左旋:如果右子树比左子树的高度差超过1,就需要进行左旋,以达到再次平衡
右旋:如果左子树比右子树的高差度超过1,就需要进行右旋,以达到再次平衡
导致二叉树不平衡的几种情况,如何恢复平衡?
1.左左: 在根节点的左子树的左子树上添加节点,直接右旋
2.左右: 在根节点的左子树的右子树上添加节点,先左旋,再右旋
3.右右: 在根节点的右子树的右子树上添加节点,直接左旋
4.右左: 在根节点的右子树的左子树上添加节点,先右旋,再左旋