Java13-----------Collection接口的set子接口

泛型机制

泛型:是JDK1.5之后引入的一个概念,它的存在是指将类型明确工作推迟到创建对象或调用方法时才明确;
语法格式:<数据类型,数据类型,……>
泛型可以用在类、接口、方法上
泛型的好处:1、避免了向下转型;2、将问题提前到编译期 ;3、提高代码的扩展性
泛型的特点:泛型只在编译期有效,在运行期间就擦除了;

泛型集合

集合都要加入泛型,来限制存储数据类型, 使用get方法取集合元素时返回也是泛型定义的数据类型,而不是Object对象。

  ArrayList<Student> list = new ArrayList<>();
        list.add(new Student("scq",12));
        list.add(new Student("张家辉", 23));
        list.add(new Student("张震岳", 23));
        list.add(new Student("张子豪", 23));
         //定义一个泛型为Student的迭代器,获取的元素类型为Student类型,
         //无需向下转型
        Iterator<Student> iterator = list.iterator();
        while (iterator.hasNext()){
            //定义一个泛型为Student的迭代器,获取的元素类型为Student类型,无需向下转型
            Student next = iterator.next();
        }

如果集合想要存储各种类型使用Object或者不定义泛型

泛型类

提高扩展性又不需要向下转型:将一个类设计为泛型类
定义格式: public class 类名<泛型类型1,…>
注意事项: 泛型类型必须是引用类型

  class Myclass<T,R>{
            //定义一个不确定类型通过方法确定类型的私有变量
            private T  t;
            private R  r;
            // 定义一个不确定类型调用方法才确定返回值类型的方法
            public R getR(){return r;}
            public void setR(R r){this.r=r;}
            public T getT(){return t;}
            public void setT(T t){this.t=t;}

        }

        Myclass<String,Integer> stringIntegerMyclass = new Myclass<>();
        stringIntegerMyclass.setT("aaa");
        String t = stringIntegerMyclass.getT();
        stringIntegerMyclass.setR(100);
        Integer r = stringIntegerMyclass.getR();

泛型方法

不知道成员变量类型,使用泛型代替;当使用者创建对象时明确成员变量的具体类型,get、set、方法都会返回已经确定的类型,也不需要向下转型

定义格式: public <泛型类型> 返回类型 方法名(泛型类型 变量名)

  test3 t3 = new test3();
        //可以定义各种类型数据
        t3.show(100);
        t3.show("aaa");

//定义泛型方法
    public<T>  void show(T num) {
        System.out.println(num);
    }
}

泛型接口

定义格式: public interface 接口名<泛型类型>

//定义泛型接口,子类实现接口时,可以明确接口上的泛型具体是什么类型
interface Myinterface<A,B>{
    //定义一个传入参数类型与接口泛型相同的抽象方法
    public abstract void test(A a);
    public abstract B test1(B b);
}

 //子类实现接口时,可以明确接口上的泛型具体是什么类型
        class MyA implements Myinterface<Integer,String>{

            @Override
            public void test(Integer integer) {
                System.out.println("Integer");
            }
            @Override
            public String test1(String s) {
                return s;
            }
        }
//采用匿名 内部类的这种方式,在创建接口的子类对象时,可以明确接口上的泛型具体是什么类型。
        new Myinterface<Student,String>(){

            @Override
            public void test(Student student) {

            }
            @Override
            public String test1(String s) {
                return s;
            }
        };

泛型高级之通配符

泛型通配符<?>: 任意类型,如果没有明确,那么就是Object以及任意的Java类了
? extends E: 向下限定,E及其子类
? super E: 向上限定,E及其父类

//泛型通配符,定义一个父类2个子类
class Animal{}
class Dog extends Animal{}
class  Cat extends Animal{}

        ArrayList<?> objects = new ArrayList<Dog>();
        ArrayList<?> objects1 = new ArrayList<Cat>();
        ArrayList<?> objects2 = new ArrayList<Animal>();
        //向上限定 类型为Cat的父类:Cat、Animal或者Object
        ArrayList<? super Cat> list1 = new ArrayList<Animal>();
        //向上限定,Animal的父类:Animal或者Object
        ArrayList<? super Animal> list3 = new ArrayList<Animal>();
        ArrayList<? super Animal> list4 = new ArrayList<Object>();
        //向下限定 类型为Animal或子类
        ArrayList<? extends Animal> list5 = new ArrayList<Cat>();
        ArrayList<? extends Animal> list6 = new ArrayList<Animal>();

增强for

新式for循环用于遍历数组和集合、底层用于迭代器实现,因此如果在迭代中添加删除元素,会发生并发修改异常;因此新式for循环遍历途中不可以添加删除元素,只可以遍历,修改元素也可以,只要不改变长度;如果想要添加删除元素使用老式for循环

格式:

for(元素数据类型 变量 : 数组或者Collection集合) {
		使用变量即可,该变量就是元素
	}
int[] arr={2,3,4,5};
        //for(容器中的元素的数据类型 变量名:容器名)
        for (int i : arr) {
            System.out.println(i);
        }

可变参数

定义方法的时候不知道该定义多少个参数时使用。
格式: 修饰符 返回值类型 方法名(数据类型… 变量名){}

注意事项:可变参数用于接收多个同类型的参数,因此如果一个方法有多个参数,可变参数必须放在最后一位用于接收剩余所有的参数

  • a: 这里的变量其实是一个数组
  • b: 如果一个方法有可变参数,并且有多个参数,那么,可变参数肯定是最后一个
int sum=add(1,2,3);


//可变参数的方法
    private static int add(int a,int...b) {
        int sum=a;
        for (int i : b) {
            sum+=i;
        }
        return sum;
    }

Arrays工具类的asList()

将数组转换成集合
asList():

  • 基本数据类型数组转换为集合存入的是地址值;传入引用数据类型数组转换集合存入的是每一个元素;传入多个引用类型数组转换集合存入多个引用类型数组的地址值
  • 此方法得到的集合是Arrays的内部类,没有增删方法不支持这些操作,不能改变长度,只可以修改元素。
 //把一个数组转换集合。
        int[] arr1={20,30,40};
        //传的是一个基本类型的数组,他是把这个数组对象,放到集合中
        List<int[]> ints = Arrays.asList(arr1);
        //ints是arr1的地址值
        System.out.println(ints.get(0)[0]);//20
        Integer[] arr2 = {20, 30, 40};
        //我传入的是一个包装类型的数组,他是吧数组中的元素,取出来放到集合
        List<Integer> integers = Arrays.asList(arr2);
        //integers就是一个集合  打印出这个集合
        System.out.println(integers);
        Integer[] arr3 = {20, 30, 40};
        Integer[] arr4 = {20, 30, 400};
        //如果你传入多个包装类型的数组,那么他是把多个数组对象,放到集合中。
        List<Integer[]> integers1 = Arrays.asList(arr3, arr4);
        //获取arr3的第一个元素
        Integer integer = integers1.get(0)[0];//20
        //也可以这样得到一个集合。
        // 注意:通过Arrays.asList() 这种方式的得到集合,不能再次改变集合的长度,也就是说,你不能增删元素。
        List<Integer> integers2 = Arrays.asList(20, 30, 40);
        System.out.println(integers2);

需求:
我们班有学生,每一个学生是不是一个对象。所以我们可以使用一个集合表示我们班级的学生。ArrayList
但是呢,我们是不是还有班级,每个班级是不是也是一个ArrayList。
而我现在有多个ArrayList。也要用集合存储,怎么办呢 ?

  • 集合嵌套之ArrayList嵌套ArrayList
Student s1 = new Student("张三", 23);
        Student s2 = new Student("李四", 23);
        Student s3 = new Student("王五", 23);

        Student s4 = new Student("赵六", 23);
        Student s5 = new Student("田七", 23);
        Student s6 = new Student("刘八", 23);

        ArrayList<Student> Java_class = new ArrayList<>();
        Java_class.add(s1);
        Java_class.add(s2);

        ArrayList<Student> Python_class = new ArrayList<>();
        Python_class.add(s3);
        Python_class.add(s4);

        ArrayList<ArrayList<Student>> school = new ArrayList<>();
        school.add(Java_class);
        school.add(Python_class);

set集合

元素唯一,不允许重复。

HashSet

  • 底层数据结构是哈希表(JDK1.7 数组+链表 JDK1.8 优化 数组+链表+红黑树)
  • 元素无序(存取元素的顺序不一致)且唯一(元素不允许重复)
  • HashSet集合保证元素的唯一性,是靠元素重写 hashCode和equals() 方法来保证的,如果元素不重写,则无法保证元素的 唯一性。
    原因如下:
    • 首先根据对象的hashcode算出索引值,确定元素放在哈希表的位置;由于new对象的地址值不同,hashcode值不同在哈希表的位置也不同,如果将hashcode值固定为一个数,那么所有对象计算出的在哈希表中的位置是相同的,但是对象的地址值不同,就会在哈希表的同一个位置形成链表结构,那么会大量调用equals方法判断两个元素地址值是否相等,由于new出来的对象地址值都不相同,因此即使是相同的成员变量的对象,均是重复存储;

    • 解决方案:通过成员变量值来重写hashcode方法,那么不同对象的成员变量值不同重写的hashcode计算出的在哈希表的位置就不同,就不会形成链式结构;如果不同对象计算出索引值相等,就再次调用重写的equals方法判断成员变量是否相等,不相等就存储;基本上通过第一层重写的hashcode方法就可以区分开不同的对象,无需调用equals方法,只有少机率会出现成员变量值不同单计算的索引值相等的情况,此时才需要调用重写的equals方法继续判断两个对象的成员变量值是否相等。

    • 目的: 尽量减少链式结构与equals调用的次数;JDK1.8将链表数超过80会转为红黑树来判断,减少判断次数

在这里插入图片描述

HashSet<Student> set1 = new HashSet<>();
        set1.add(new Student("张三",12));
        set1.add(new Student("scq",12));
        set1.add(new Student("scq",12));
        set1.add(new Student("李四", 24));
        set1.add(new Student("王五", 25));
        for (Student student : set1) {
            System.out.println(student);

        }
       /* Student{name='李四', age=24}
        Student{name='scq', age=12}
        Student{name='张三', age=12}
        Student{name='王五', age=25}*/

student类如下:

import java.util.Objects;
public class Student
{
    private String name;
    private int 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;
    }

    public Student() {}

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public boolean equals(Object o)
    {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode()
    {
        return Objects.hash(name, age);
    }
}

Hashset对于集合的去重

ArrayList<Integer> list = new ArrayList<>();
        list.add(100);
        list.add(100);
        list.add(100);
        list.add(100);
        list.add(100);

        HashSet<Integer> integers = new HashSet<>(list);
        System.out.println(integers);//[100]

LinkedHashSet

元素有序,且唯一,底层数据结构是链表+哈希表 链表保证元素有序,哈希表保证元素唯一

 LinkedHashSet<Integer> integers1 = new LinkedHashSet<>();
        integers1.add(100);
        integers1.add(200);
        integers1.add(200);
        integers1.add(300);

        for (Integer integer : integers1) {
            System.out.println(integer);

        }
      /*  100
        200
        300*/

TreeSet

TreeSet 底层数据结构是二叉树,元素有序唯一。

二叉树存储数据保证元素唯一性原理图:
在这里插入图片描述

Treeset对于储Integer类型的元素并遍历

 TreeSet<Integer> treeSet = new TreeSet<>();
        treeSet.add(20);
        treeSet.add(18);
        treeSet.add(23);
        treeSet.add(22);
        treeSet.add(17);
        treeSet.add(24);

        for (Integer integer : treeSet) {
            System.out.println(integer);
        }
        /*17
        18
        20
        22
        23
        24 */

给TreeSet类的集合存学生对象的时候,一存就会报错,为什么?

前面说这个集合可以根据你的需要进行排序,但是它在存储学生对象的时候,没有办法进行比较;

排序分为自然排序和比较器排序:因此如果存储学生对象时需要重写排序方法

1、自然排序。如果new一个TreeSet对象的时候采用空参构造,就默认为是自然排序。你给集合当中添加Integer类型的数据时,就不会报错,这是因为Integer类当中实现了Comparable接口,重写了这个接口中的compare()。因此自定义的Student类如果想要添加到树集当中,就必须实现接口Comparable,并重写compare()。

2、比较器排序。采用有参构造,你给这个TreeSet对象的构造方法中传递一个比较器,实现接口Comparator,并重写compare()。

compare()的返回值代表元素应该添加到哪,后面添加的元素与根节点相比较,为0说明集合当中存在同样的对象,就存不进去了;因此如果你在代码当中将compare()返回值写死为0,就会出现一种现象,集合中只存进去根节点元素,其余都存不进去。为正数添加到红黑树的左边,为负数添加到红黑树的右边;如果compare()的返回值写成任意正负数,全部的元素都存储在了一个数组当中。

  • 自然排序
    学生类继承 Comparable接口并重写 compareTo 方法:
public class Student implements Comparable<Student> {
 @Override
    public int compareTo(Student s) {
        /*按照姓名的长度来排序*/
        //先比较姓名长度是否相等,返回正负数或0 排序
        int num = this.name.length()-s.name.length();
        //如果姓名长度相等判断姓名内容是否相等 ,姓名长度不相等说明是不同的对象直接返回num值给num2
        int num2=num==0?this.name.compareTo(s.name):num;
        //如果姓名长度和内容都相等判断年龄是否相等,否则返回num2
        int num3=num2==0?this.age-s.age:num2;
        return num3;
        //根据年龄大小来排序
        /*int num=this.age-s.age;
        //如果年龄相同,并不能说明他们是同一个对象,还得比较姓名是否相同。
        //compareTo返回一个数值 来作为二叉树的位置
        int num2=num==0?this.name.compareTo(s.name):num;
        return num2;*/
}
 //按照学生的年龄大小来排序
        TreeSet<Student> treeSet2 = new TreeSet<>();
        treeSet2.add(new Student("张庭aaaaaadfdfdfasdfasdf",23));
        treeSet2.add(new Student("张庭2aaa", 23));
        treeSet2.add(new Student("张柏芝aaa", 18));
        treeSet2.add(new Student("张惠妹aaa", 20));
        treeSet2.add(new Student("张惠妹", 20));
        treeSet2.add(new Student("张惠妹", 22));
        treeSet2.add(new Student("张余生", 20));
        treeSet2.add(new Student("张曼玉aaaaaaa", 24));
        treeSet2.add(new Student("林青霞aaaaaaaaaa", 26));
        for (Student student : treeSet2) {
            System.out.println(student.getName()+"=="+student.getAge());
        }
/*张余生==20
张惠妹==20
张惠妹==22
张庭2aaa==23
张惠妹aaa==20
张柏芝aaa==18
张曼玉aaaaaaa==24
林青霞aaaaaaaaaa==26
张庭aaaaaadfdfdfasdfasdf==23
*/
  • 比较器排序
    采用有参构造,给这个TreeSet对象的构造方法中传递一个比较器,实现接口Comparator,并重写compare()。
class MyCompareator implements Comparator<Student> {
    @Override
    public int compare(Student s1, Student s2) {
        //安装年龄大小 来排序
        int num = s1.getAge() - s2.getAge();
        int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
        return num2;
    }
}
       
        TreeSet<Student> treeSet1 = new TreeSet<>(new MyCompareator());
        treeSet1.add(new Student("张庭aaaaaadfdfdfasdfasdf", 23));
        treeSet1.add(new Student("张庭2aaa", 23));
        treeSet1.add(new Student("张柏芝aaa", 18));
        treeSet1.add(new Student("张惠妹aaa", 20));
        treeSet1.add(new Student("张惠妹", 20));
        treeSet1.add(new Student("张惠妹", 22));
        treeSet1.add(new Student("张余生", 20));
        treeSet1.add(new Student("张曼玉aaaaaaa", 24));
        treeSet1.add(new Student("林青霞aaaaaaaaaa", 26));

        for (Student student : treeSet1) {
            System.out.println(student.getName() + "==" + student.getAge());
        }



//方法二: 使用匿名内部类来传参
/* TreeSet(Comparator < ? super E > comparator)
        构造一个新的空 TreeSet,它根据指定比较器进行排序。
        Comparator 比较器*/
    
      TreeSet<Student> treeSet3=  new TreeSet<>(new Comparator<Student>() {
            @Override
            public int compare(Student s1, Student s2) {
                //安装年龄大小 来排序
                int num = s1.getAge() - s2.getAge();
                int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
                return num2;
            }
        });

不光是TreeSet集合有比较器这一说,其它集合也会用到,比如ArrayList里面的sort(),就用到了;

  //使用比较器对于Integer类型的ArrayList集合进行排序
        ArrayList<Integer> integers2 = new ArrayList<>();
        integers2.add(2);
        integers2.add(23);
        integers2.add(24);
        integers2.add(25);
        integers2.add(26);

        integers2.sort(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1-o2;
            }
        });
        System.out.println(integers2);//[2, 23, 24, 25, 26]

数组的比较器排序:

 Integer[] arr={10,2,3,4};
        Arrays.sort(arr, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1-o2;
            }
        });
        System.out.println(Arrays.toString(arr));//[2, 3, 4, 10]
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页