泛型
Set
TreeSet
01. 泛型-概述
泛型的作用? 是JDK5中引入的新特性, 它提供了编译时对类型安全检测机制 泛型好处? 1. 把运行时可能出现的问题提前到了编译期间 2. 避免了强制类型转换
public class Demo {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add(123);
Iterator it = list.iterator();
while (it.hasNext()) {
//Object next = it.next();
//int length = next.length(); //Object类没有length方法
//强转
String next = (String) it.next();
System.out.println(next);
}
}
}
02 . 泛型类的使用
泛型可以使用的地方 1.类后面: 泛型类 2.方法后面: 泛型方法 3.接口后面: 泛型接口 了解 我们常见的泛型类有ArrayList ArrayList在JDK4之前, 没有泛型, 这样会有弊端 (强转时会有异常、为了解决弊端增加了代码量) 为了解决这些弊端, JDK5后ArrayList被设计为泛型类 泛型代表未知的类型, 这个类型在创建对象时可以被确定 注意 如果一个类后面有, 表示这是一个泛型类 创建泛型类对象时, 必须给这个泛型确定具体的数据类型
03. 泛型-自定义泛型类
泛型类的定义格式 <类型>: 指定一种类型的格式 尖括号中任意写, 按照变量的定义规则即可, 一般写一个大写字母 例如、 、 、 <类型1,类型2>: 指定多种类型的格式 多种类型之间用逗号隔开 例如 、 、 泛型类的定义格式 修饰符 class 类名<类型>{}
//测试类
class Test{
public static void main(String[] args) {
//创建对象,指定类型
Box box = new Box<>();
box.setElement("给女神表白的代码");
System.out.println(box.getElement());
//创建对象,指定类型
Box box2 = new Box<>();
box2.setElement(18);
System.out.println(box2.getElement());
}
}
//自定义泛型类
public class Box {
//成员变量,类型为泛型
private T element;
//提供getXxx和setXxx方法
public T getElement() {
return element;
}
public void setElement(T element) {
this.element = element;
}
}
04. 泛型-泛型方法的使用
Java中泛型方法的使用 查阅API我们发现ArrayList有两个方法 1. Object oArray(); 返回集合的数组表现形式 2.T[] toArray(T[] arr); 返回集合的指定类型数组的表现形式
代码示例
public class Demo {
public static void main(String[] args) {
ArrayList list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
//1. Object toArray(); 返回集合的数组表现形式
Object[] objects = list.toArray();
System.out.println(Arrays.toString(objects)); //获取的类型都是Object类型,如果要调用方法不方便
//2. T[] toArray(T[] arr); 返回集合的指定类型数组的表现形式
String[] strings = list.toArray(new String[list.size()]);
System.out.println(Arrays.toString(strings)); //此时获取的元素都是String类型
}
}
05. 泛型-自定义泛型方法
泛型方法的定义格式 修饰符 <类型> 返回值类型 方法名(类型 变量名) 例如publicvoid show(T t){} 需求: 定义一个方法, 接收一个集合和四个元素(数据类型调用者指定) 将元素添加到集合并返回
代码示例
public class Demo {
public static void main(String[] args) {
//测试1: 元素为String类型
ArrayList list1 = addElement(new ArrayList(), "张飞", "刘备", "关羽", "赵云");
System.out.println(list1);
//测试2: 元素为int类型
ArrayList list2 = addElement(new ArrayList(), 11, 22, 33, 44);
System.out.println(list2);
}
//修饰符 <类型> 返回值类型 方法名(类型 变量名)
public static ArrayList addElement(ArrayList list, T t1, T t2, T t3, T t4) {
list.add(t1);
list.add(t2);
list.add(t3);
list.add(t4);
return list;
}
}
06. 泛型-泛型接口
Java中的泛型接口举例?
public interface List extends Eollection..
使用者是ArrayList, 将该泛型延续下来(方式1)
public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable..
泛型接口的使用方式?
方式1. 实现类不给泛型, 将实现类也定义为泛型类, 创建实现类对象时才确定具体的数据类型
方式2. 实现类确定具体的数据类型
泛型接口的定义格式?
修饰符 interface 接口名{}
代码示例
//测试类
public class Demo {
public static void main(String[] args) {
//测试方式1: 创建实现类对象时要确定具体的数据类型
InterImpl1 i1 = new InterImpl1<>();
i1.method("黑马程序员");
//测试方式2: 由于类型已经确定, 直接创建实现类对象即可, 不需要确定具体的数据类型
InterImpl2 i2 = new InterImpl2();
i2.method(100);
}
}
//自定义泛型接口
interface inter {
public abstract void method(T t);
}
//方式1. 实现类不给泛型, 将实现类也定义为泛型类, 创建实现类对象时才确定具体的数据类型
class InterImpl1 implements inter {
@Override
public void method(T t) {
System.out.println(t);
}
}
//方式2. 实现类确定具体的数据类型
class InterImpl2 implements inter{
@Override
public void method(Integer integer) {
System.out.println(integer);
}
}
07. 泛型-通配符
类型通配符: > ArrayList>: 表示元素类型未知的ArrayList集合, 它的元素可以匹配任何类型 类型通配符上限: extends 类型> ArrayList extends Number>: 表示类型是Number或者Number的子类 类型通配符下限: super 类型> ArrayList super Number>: 表示类型是Number或者Number的父类
代码示例
public class Demo {
public static void main(String[] args) {
//类型是任意类型
method01(new ArrayList());
method01(new ArrayList());
method01(new ArrayList
08. Set-概述
单列集合 Collection接口 List接口(可重复) ArrayList实现类 linkedList实现类 Set接口(不可重复) TreeSet实现类 HashSet实现类
09. Set-基本使用
Set集合特点
1. 可以去重
2. 存取顺序不一致
3. 没有带索引的方法, 所以不能用普通for遍历, 也不能通过索引获取/删除元素
Set集合练习
存储字符串并遍历
代码示例
public class Demo {
public static void main(String[] args) {
Set set = new TreeSet<>();
set.add("cc");
set.add("dd");
set.add("aa");
set.add("aa");
set.add("bb");
set.add("bb");
//遍历1:普通for不行
//for (int i = 0; i < set.size(); i++) {
//System.out.println(set.get(i)); //没有操作索引的方法
//}
//遍历2:迭代器
Iterator it = set.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
System.out.println("-----------");
//遍历3:增强for
for (String s : set) {
System.out.println(s);
}
}
}
10. TreeSet-基本使用
TreeSet集合特点
1. 可以去重
2. 存取顺序不一致
3. 可以将元素按照规则进行排序 (重点关注)
TreeSet集合练习1
存储Integer类型的整数并遍历 (虽然存取顺序不一致,但是有序)
TreeSet集合练习2
存储Student对象并遍历 (报错, 原因是没有规定排序规则)
代码示例
public class Demo {
public static void main(String[] args) {
}
//TreeSet集合练习2
private static void test02() {
//存储Student对象并遍历
TreeSet set = new TreeSet<>();
set.add(new Student("张飞",18));
set.add(new Student("关羽",19));
set.add(new Student("刘备",20));
System.out.println(set); //报错, 原因是没有规定排序规则
}
//TreeSet集合练习1
private static void test01() {
//存储Integer类型的整数并遍历
TreeSet set = new TreeSet<>();
set.add(5);
set.add(3);
set.add(4);
set.add(1);
set.add(2);
System.out.println(set); //[1, 2, 3, 4, 5] 虽然存取顺序不一致,但是有序
}
}
11. TreeSet-自然排序Comparable的使用
Comparable接口API介绍
该接口对它每个实现类强加一个整体排序, 这个排序被称为"自然排序"
类的compareTo方法被称为"自然排序方法"
自然排序Comparable的使用步骤
1. 使用空参构造TreeSet集合
2. 自定义类要实现Comparable接口
3. 重写Comparable接口中的compareTo方法
代码示例
public class Demo {
public static void main(String[] args) {
//1. 使用空参构造TreeSet集合
TreeSet set = new TreeSet<>();
set.add(new Student("刘备",20));
set.add(new Student("张飞",18));
set.add(new Student("关羽",19));
//遍历集合
for (Student stu : set) {
System.out.println(stu);
}
}
}
//2. 自定义类要实现Comparable接口
public class Student implements Comparable {
//3. 重写Comparable接口中的compareTo方法
@Override
public int compareTo(Student o) {
int result = this.age - o.age;
return result;
}
12. 自然排序-练习
需求
按照年龄从小到大排序, 如果年龄一样, 按照姓名首字母排序
如果姓名和年龄都一样, 认为是同一个人, 不存
代码示例
public class Demo {
public static void main(String[] args) {
//创建集合添加元素
TreeSet set = new TreeSet<>();
set.add(new Student("zhangsan", 24));
set.add(new Student("lisi", 18));
set.add(new Student("wangwu", 24));
set.add(new Student("zhaoliu", 24));
set.add(new Student("chenglong", 58));
//遍历集合
for (Student stu : set) {
System.out.println(stu);
}
}
}
class Student implements Comparable {
@Override
public int compareTo(Student s) {
int result = this.age - s.age;
//result为0: 表示重复, 继续判断姓名!
return result == 0 ? this.name.compareTo(s.name) : result;
}
...
}
13. TreeSet-比较器排序Comparator的使用
比较器排序Comparator的使用步骤
1. 使用带参构造, 创建TreeSet集合
2. 比较器排序, 就是让集合构造方法接收Comparator的实现类对象, 重写compare方法
3. 重写compare方法要注意, 排序规则必须按照要求的"主要条件"和"次要条件"来写
需求
自定义Teacher类, 属性为name和age
按照年龄排序, 如果年龄一样, 按照姓名排序
代码示例
public class Demo {
public static void main(String[] args) {
//比较器排序, 就是让集合构造方法接收Comparator的实现类对象, 重写compareTo方法
TreeSet set = new TreeSet<>(new Comparator() {
@Override
public int compare(Teacher t1, Teacher t2) {
//重写compareTo方法要注意, 排序规则必须按照要求的"主要条件"和"次要条件"来写
//[1]主要条件: 比年龄
int result = t1.getAge() - t2.getAge();
//[2]次要条件: 比姓名
return result == 0 ? t1.getName().compareTo(t2.getName()) : result;
}
});
//添加元素,遍历集合
set.add(new Teacher("zhangsan", 24));
set.add(new Teacher("lisi", 18));
set.add(new Teacher("wangwu", 24));
set.add(new Teacher("zhaoliu", 24));
set.add(new Teacher("chenglong", 58));
for (Teacher tea : set) {
System.out.println(tea);
}
}
}
14. TreeSet-两种比较方式的对比
自然排序Comparable:
自定义类实现Comparable接口, 重写compareTo方法, 根据返回值进行排序
比较器排序Comparator
将Comparator接口的实现类作为参数, 传递给TreeSet的带参构造, 重写compare方法, 根据返回值进行排序
默认使用自然排序, 当自然排序不满足当前需求, 使用比较器排序
返回值规则
如果返回值为负数: 表示存入元素为较小值, 存左边
如果返回值为0: 表示重复, 不存
如果返回值为正数: 表示存入元素为较大值, 存右边
练习需求
存入四个字符串, "c","ab","df","qwer"
按照长度升序排序, 长度一样按照首字母排序
代码示例
public class Demo {
public static void main(String[] args) {
TreeSet set = new TreeSet<>(new Comparator() {
@Override
public int compare(String s1, String s2) {
//[1]主要条件: 按照长度升序排列
int result = s1.length() - s2.length();
//[2]次要条件: 按照首字母排序
return result == 0 ? s1.compareTo(s2) : result;
}
});
set.add("df");
set.add("c");
set.add("qwer");
set.add("ab");
System.out.println(set);
}
}
15.数据结构-二叉树
命名约定
TreeSet 树结构 - Set体系的一员
ArrayList 数组结构 - List体系的一员
linkedList 链表结构 - List体系的一员
树的分类?
二叉树
二叉查找树
平衡二叉树
红黑树 (TreeSet集合底层是红黑树)
树结构中每一个元素称为: 节点(对象), 包含四个部分
1. 父节点位置
2. 值
3. 左子节点位置
4. 右子节点位置
注意
如果再没有父节点了, 父节点地址为null
如果再没有子节点了, 子节点地址为null
在树结构中, 每一个节点的子节点数量称为: 度 (没儿子了度就是0, 有两个儿子度就是2)
注意
在二叉树中, 任意一个节点的度, 必须小于等于2
二叉树图形中的层数称为: 树高
最顶层的称为: 根节点
以根节点为中心,
左子节点展开的图形称为: 左子树 (子树也是有树高的)
右子节点展开的图形称为: 右子树 (子树也是有树高的)
注意
普通二叉树, 是没有存储规则的
16. 数据结构-二叉查找树
二叉查找树?
又称为"二叉排序树", 或者"二叉搜索树"
特点?
1. 每一个节点上最多有两个子节点
2. 每一个节点的左子节点, 都小于自己
3. 每一个节点的右子节点, 都大于自己
总结: 任意一个节点从左到右都是依次增大的
17. 数据结构-二叉树找树添加节点
将节点按照二叉查找树的规则存入
07 04 10 05
存储规则?
小的存左边
大的存右边
一样的不存
07 07 -> 没人比较, 自己就是根节点
↓ ↓ 04 -> 和根节点比较, 小于根节点存入左边
04 10 10 -> 和根节点比较, 大于根节点存入右边
↓ ↓
05 05 -> 还是和根节点比较, 小于根节点存入左边, 但是左边有人了(在二叉树中, 任意一个节点的度, 必须小于等于2), 所以和04节点比较, 大于04节点, 存入右边的子节点
思考: 如果现在11进来了, 怎么存?
18. 数据结构-平衡二叉树
平衡二叉树?
二叉树左右两个子树的高度差, 不要超过1 (不要求树高完全一致)
任意节点的左右两个子树, 都是平衡二叉树
19. 平衡二叉树-左旋
保证二叉树平衡的机制
1. 左旋
2. 右旋
旋转触发的机制(重点记忆)
当添加一个节点后, 该树不再是平衡二叉树了, 就会触发旋转
左旋?
将根节点的右侧往左拉, 原来的右子节点变为根节点
并将多余(没人要)的左子节点出让, 给已经降级的根节点当右子节点
20. 平衡二叉树-右旋
左旋? 将根节点的左侧往右拉, 原来的左子节点变为根节点 并将多余(没人要)的右子节点出让, 给已经降级的根节点当右左子节点
21. 平衡二叉树-小结
二叉树 -> 没有存储规律, 查找效率低 ↓ 二叉查找(搜索/排序)树 -> 每个节点的度<=2, 从根节点开始判断, 左子节点小于自己, 右子节点大于自己 ↓ 平衡二叉树 -> 左右两个子树高度差<=1 ↓ 如果新节点的加入打破了平衡 -> 触发保证二叉树平衡的机制 ↓ 左旋/右旋 -> 主要看朝那边拉
22. 平衡二叉树-左左和左右
由于添加数据的位置不同, 旋转的四种情况
左左: 当根节点的左子树的左子树, 有节点插入, 导致平衡二叉树不平衡
左右: 当根节点的左子树的右子树, 有节点插入, 导致平衡二叉树不平衡
右右: 当根节点的右子树的右子树, 有节点插入, 导致平衡二叉树不平衡
右左: 当根节点的右子树的左子树, 有节点插入, 导致平衡二叉树不平衡
注意:
旋转保证平衡二叉树平衡, 不一定仅旋转一次, 有可能旋转多次
以每一个节点为基准, 都可以旋转
23. 平衡二叉树-右右和右左



