Java教程

泛型、Set集合、TreeSet集合、二叉树

本文主要是介绍泛型、Set集合、TreeSet集合、二叉树,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

泛型

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();
                /*
                    如果元素中除了String还有其他数据类型,就会报转换异常:
                        java.lang.ClassCastException
                 */
                System.out.println(next);
            }
        }
    }

02 . 泛型类的使用

泛型可以使用的地方
    1.类后面: 泛型类
    2.方法后面: 泛型方法
    3.接口后面: 泛型接口
​
了解
    我们常见的泛型类有ArrayList
    ArrayList在JDK4之前, 没有泛型, 这样会有弊端 (强转时会有异常、为了解决弊端增加了代码量)
    为了解决这些弊端, JDK5后ArrayList被设计为泛型类
    泛型代表未知的类型, 这个类型在创建对象时可以被确定
​
注意
    如果一个类后面有<E>, 表示这是一个泛型类
    创建泛型类对象时, 必须给这个泛型确定具体的数据类型  

03. 泛型-自定义泛型类

泛型类的定义格式
    <类型>: 指定一种类型的格式
    尖括号中任意写, 按照变量的定义规则即可, 一般写一个大写字母
    例如<E>、<T>、<Q>、<M>
​
    <类型1,类型2>: 指定多种类型的格式
    多种类型之间用逗号隔开
    例如<E,T>、<Q,M>、<K,V>
​
​
泛型类的定义格式
    修饰符 class 类名<类型>{}​
//测试类
    class Test{
        public static void main(String[] args) {
            //创建对象,指定类型
            Box<String> box = new Box<>();
            box.setElement("给女神表白的代码");
            System.out.println(box.getElement());
​
            //创建对象,指定类型
            Box<Integer> box2 = new Box<>();
            box2.setElement(18);
            System.out.println(box2.getElement());
        }
    }
    //自定义泛型类
    public class Box<T> {
        //成员变量,类型为泛型
        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> T[] toArray(T[] arr); 返回集合的指定类型数组的表现形式

代码示例 

public class Demo {
    public static void main(String[] args) {
        ArrayList<String> 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> T[] toArray(T[] arr); 返回集合的指定类型数组的表现形式
        String[] strings = list.toArray(new String[list.size()]);
        System.out.println(Arrays.toString(strings)); //此时获取的元素都是String类型
    }
}

05. 泛型-自定义泛型方法

泛型方法的定义格式
    修饰符 <类型> 返回值类型 方法名(类型 变量名)
    例如public<T> void show(T t){}
​
需求:
    定义一个方法, 接收一个集合和四个元素(数据类型调用者指定)
    将元素添加到集合并返回

代码示例 

public class Demo {
        public static void main(String[] args) {
            //测试1: 元素为String类型
            ArrayList<String> list1 = addElement(new ArrayList<String>(), "张飞", "刘备", "关羽", "赵云");
            System.out.println(list1);
​
            //测试2: 元素为int类型
            ArrayList<Integer> list2 = addElement(new ArrayList<Integer>(), 11, 22, 33, 44);
            System.out.println(list2);
        }
​
        //修饰符 <类型> 返回值类型 方法名(类型 变量名)
        public static <T> ArrayList<T> addElement(ArrayList<T> 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<E> extends Eollection<E>..
使用者是ArrayList, 将该泛型延续下来(方式1)
    public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable..
    
泛型接口的使用方式?
    方式1. 实现类不给泛型, 将实现类也定义为泛型类, 创建实现类对象时才确定具体的数据类型
    方式2. 实现类确定具体的数据类型
    
泛型接口的定义格式?
    修饰符 interface 接口名<T>{}

代码示例  

    //测试类
    public class Demo {
        public static void main(String[] args) {
            //测试方式1: 创建实现类对象时要确定具体的数据类型
            InterImpl1<String> i1 = new InterImpl1<>();
            i1.method("黑马程序员");
​
            //测试方式2: 由于类型已经确定, 直接创建实现类对象即可, 不需要确定具体的数据类型
            InterImpl2 i2 = new InterImpl2();
            i2.method(100);
        }
    }
​
    //自定义泛型接口
    interface inter<T> {
        public abstract void method(T t);
    }
​
    //方式1. 实现类不给泛型, 将实现类也定义为泛型类, 创建实现类对象时才确定具体的数据类型
    class InterImpl1<T> implements inter<T> {
        @Override
        public void method(T t) {
            System.out.println(t);
        }
    }
​
    //方式2. 实现类确定具体的数据类型
    class InterImpl2 implements inter<Integer>{
        @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) {
        /*
            Integer 继承自 Number 继承自 Object
         */
        //类型是任意类型
        method01(new ArrayList<Integer>());
        method01(new ArrayList<Number>());
        method01(new ArrayList<Object>());
​
        //类型是Number或者Number的子类
        method02(new ArrayList<Integer>());
        method02(new ArrayList<Number>());
        //method02(new ArrayList<Object>()); //如果传递Number的父类型,报错
​
        //类型是Number或者Number的父类
        //method03(new ArrayList<Integer>()); //如果传递Number的子类型,报错
        method03(new ArrayList<Number>());
        method03(new ArrayList<Object>());
    }
​
    //类型是任意类型
    public static void method01(ArrayList<?> list) {
    }
    //类型是Number或者Number的子类
    public static void method02(ArrayList<? extends Number> list) {
    }
    //类型是Number或者Number的父类
    public static void method03(ArrayList<? super Number> list) {
    }
}

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<String> 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<String> 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<Student> 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<Integer> 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<Student> 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);
                /*
                    Student{name='张飞', age=18}
                    Student{name='关羽', age=19}
                    Student{name='刘备', age=20}
                 */
            }
        }
    }
    //2. 自定义类要实现Comparable接口
    public class Student implements Comparable<Student> {
    //3. 重写Comparable接口中的compareTo方法
    @Override
    public int compareTo(Student o) {
        /*
            return 当前对象的年龄 - 存入对象的年龄
                如果返回值为负数: 表示存入元素为较小值, 存左边
                如果返回值为0: 表示重复, 不存
                如果返回值为正数: 表示存入元素为较大值, 存右边
        */
        int result = this.age - o.age;
        return result;
    }
    

12. 自然排序-练习

需求
    按照年龄从小到大排序, 如果年龄一样, 按照姓名首字母排序
    如果姓名和年龄都一样, 认为是同一个人, 不存

 代码示例 

    public class Demo {
        public static void main(String[] args) {
            //创建集合添加元素
            TreeSet<Student> 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);
                /*
                    Student{name='lisi', age=18}
                    Student{name='wangwu', age=24} //w在z前面
                    Student{name='zhangsan', age=24} //细节: z、h、a都一样,比较n和o,n在o前面
                    Student{name='zhaoliu', age=24}
                    Student{name='chenglong', age=58}
                 */
            }
        }
    }
​
    class Student implements Comparable<Student> {
        @Override
        public int compareTo(Student s) {
            /*
                年龄从小到大排序: 如果当前对象年龄 - 存入对象年龄
                    负数: 表示存入元素为较小值, 存左边
                    为0: 表示重复, 继续判断姓名!
                    正数: 表示存入元素为较大值, 存右边
             */
            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<Teacher> set = new TreeSet<>(new Comparator<Teacher>() {
                @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);
                    /*
                        Student{name='lisi', age=18}
                        Student{name='wangwu', age=24} //w在z前面
                        Student{name='zhangsan', age=24} //细节: z、h、a都一样,比较n和o,n在o前面
                        Student{name='zhaoliu', age=24}
                        Student{name='chenglong', age=58}
                    */
            }
        }
    }

14. TreeSet-两种比较方式的对比

自然排序Comparable:
    自定义类实现Comparable接口, 重写compareTo方法, 根据返回值进行排序
    
比较器排序Comparator
    将Comparator接口的实现类作为参数, 传递给TreeSet的带参构造, 重写compare方法, 根据返回值进行排序
    
默认使用自然排序, 当自然排序不满足当前需求, 使用比较器排序
​
返回值规则
    如果返回值为负数: 表示存入元素为较小值, 存左边
    如果返回值为0: 表示重复, 不存
    如果返回值为正数: 表示存入元素为较大值, 存右边
    
练习需求
    存入四个字符串, "c","ab","df","qwer"
    按照长度升序排序, 长度一样按照首字母排序

 代码示例 

public class Demo {
        public static void main(String[] args) {
            /*
                1. 查看String源码发现String已经实现自然排序接口
                    public final class String
                    implements java.io.Serializable, Comparable<String>, CharSequence,
                       Constable, ConstantDesc {
                2. 但是按照首字母进行排序,不满足题目要求
                3. 所以需要使用比较器排序完成!
             */
            TreeSet<String> set = new TreeSet<>(new Comparator<String>() {
                @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. 平衡二叉树-右右和右左

 


这篇关于泛型、Set集合、TreeSet集合、二叉树的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!