Java集合二

Java集合二

一、Set

1.1、Set的概述和特点

  • Set集合是不包含重复元素的集合
  • 没有带索引的方法,所以使用普通for循环遍历
java.util Interface Set<E>
E - 由此集合维护的元素的类型
public interface Set<E> extends Collection<E>

1.2、哈希值

  • 哈希值:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
  • Object类中有一个方法可以获取对象的哈希值:public int hashCode():返回对象的哈希码值
  • 对象的哈希值特点:
    • 同一个对象多次调用hashCode()方法返回的哈希值是i相同的
    • 默认情况下,不同对象的哈希值是不同的,而重写hashCode()方法,可以实现让不同对象的哈希值相同

二、HashSet

2.1、HashSet集合概念及特点

  • 底层数据结构是哈希表
  • 对于集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素的顺序一致
  • 没有索引的方法,所以不能使用普通for循环遍历
  • 由于是Set集合,所以不包含重复元素的集合
import java.util.HashSet;
import java.util.Iterator;

public class hashsetdemo {
    public static void main(String[] args) {
        //创建HashSet集合对象
        HashSet<String> hs = new HashSet<String>();
        //添加元素
        hs.add("hello");
        hs.add("hello");
        hs.add("world");
        hs.add("meet");
        //遍历集合
        //方法一
//        for(String s:hs){
//            System.out.println(s);//world meet hello
//        }
        //方法二
        Iterator<String> it = hs.iterator();
        while(it.hasNext()){
            String s = it.next();
            System.out.println(s);//world meet hello
        }
    }
}
import gather.Collection.Student;
import java.util.HashSet;
import java.util.Iterator;

//HashSet集合存储学生对象并遍历
public class hashdemo02 {
    public static void main(String[] args) {
        //1、创建学生类-------->Student
        //2、创建集合
        HashSet<Student> hs = new HashSet<Student>();
        //3、创建学生对象
        Student s1 = new Student(12,"王");
        Student s2 = new Student(13,"小");
        Student s3 = new Student(15,"虎");
        //4、添加学生元素
        hs.add(s1);
        hs.add(s2);
        hs.add(s3);
        //5、遍历集合
        //方法一
        Iterator<Student> it = hs.iterator();
//        while(it.hasNext()){
//            Student s = it.next();
//            System.out.println(s.getAge()+","+s.getName());
//        }
        //方法二
        for(Student s:hs){
            System.out.println(s.getAge()+","+s.getName());
        }
    }
}

2.2、HashSet集合保证元素唯一性源码分析

2.3、哈希表

  • JDK8之前,哈希表底层采用数组+链表实现,可以说是一个元素为链表的数组
  • JDK8之后,在长度比较长的时候,底层实现了优化

三、LinkedHashSet

3.1、LinkedHashSet集合概述和特点

  • 哈希表和链表实现Set接口,具有可预测的迭代次序
  • 由链表保证元素有序,也就是说元素的存储和取出的顺序是一致的
  • 由哈希表保证元素唯一,也就是说没有重复的元素
import java.util.Iterator;
import java.util.LinkedHashSet;

public class LinkedHashSetdemo01 {
    public static void main(String[] args) {
        //创建集合对象
        LinkedHashSet<String> lh = new LinkedHashSet<String>();

        //添加元素
        lh.add("hello");
        lh.add("hello");
        lh.add("good");
        lh.add("perfer");

        //遍历集合
        //方法一
//        for (String s:lh){
//            System.out.println(s);
//        }
        //方法二
        Iterator<String> it = lh.iterator();
        while(it.hasNext()){
            String s = it.next();
            System.out.println(s);
        }
    }
}

四、TreeSet

4.1、TreeSet集合概述和特点

  • 元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方式取决于构造方法
    • TreeSet():根据其元素的自然排序进行排序
    • TreeSet(Compatator comparator):根据指定的比较器进行排序
  • 没有带索引方法,所以不能用普通for循环遍历
  • 由于是Set集合,所以不包含重复元素的集合
import java.util.TreeSet;

public class treesetdemo01 {
    public static void main(String[] args) {
        //创建TreeSet集合
        TreeSet<Integer> ts = new TreeSet<Integer>();
        //添加元素
        ts.add(11);
        ts.add(16);
        ts.add(11);
        ts.add(10);
        //遍历集合
        //方法一
        for (Integer i:ts){
           System.out.println(i);
       }
        //方法二
//        Iterator<Integer> it = ts.iterator();
//        while(it.hasNext()){
//            Integer i = it.next();
//            System.out.println(i);
//        }
    }
}

4.2、自然排序Comparable的使用

  • 对实现它的每个类的对象强加一个整体排序。 这个排序被称为类的自然排序 ,类的compareTo方法被称为其自然比较方法
  • TreeSet():构造一个新的,空的树组,根据其元素的自然排序进行排序。
//案例:存储学生对象并遍历,创建TreeSet集合使用无参构造方法
//要求:按照年龄从小到大排序,年龄相同时,按照姓名字母排序
public class Student implements Comparable<Student>{
    private int age;
    private String name;

    public Student(){};

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

    public void setAge(int age){
        this.age=age;
    }

    public void setName(String name){
        this.name=name;
    }

    public int getAge(){
        return age;
    }

    public String getName(){
        return name;
    }


    @Override
    public int compareTo(Student s) {
//        return 0;//返回0,默认之后的值与前面的值相同
//        return 1;//返回1时,这里默认之后的值比前一个值大,所以时按照存储顺序输出
//        return -1;//返回-1时,这里默认之后的值比前一个值小,所以会输出结果与存储顺序相反

        //按照年龄从小到大来排序
//        int num = this.age-s.age;//从大到小排序,降序
        int num = this.age-s.age;//从小到大排序,升序
        //年龄相同时,按照姓名的字母顺序排序
        int num2 = num==0?this.name.compareTo(s.name):num;
        return num2;
    }
}

import java.util.TreeSet;

public class treesetdemo02 {
    public static void main(String[] args) {
        //1、创建学生类---------》Student
        //2、创建TreeSet集合的无参构造
        TreeSet<Student> ts = new TreeSet<Student>();
        //3、创建学生对象
        Student s1 = new Student(16,"王华");
        Student s2 = new Student(13,"李明");
        Student s3 = new Student(15,"钱军");
        Student s4 = new Student(9,"文华");
        Student s5 = new Student(9,"郑国");
        Student s6 = new Student(9,"郑国");
        //把学生对象添加到集合
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);
        ts.add(s6);
        //遍历集合
        for (Student s:ts){
            System.out.println(s.getAge()+","+s.getName());//java.lang.ClassCastException:类转换异常
        }
    }
}
  • 结论:

    • 用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的

    • 自然排序,就是让元素所属的类实现Comparable接口,重写comparaTo(To)方法

    • 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写

4.3、比较器排序Comparator的使用

  • 比较功能,对一些对象的集合施加了一个整体排序 。 可以将比较器传递给排序方法(如Collections.sortArrays.sort ),以便对排序顺序进行精确控制。 比较器还可以用来控制某些数据结构(如顺序sorted setssorted maps ),或对于不具有对象的集合提供的排序natural ordering
  • TreeSet(Comparator<? super E> comparator):构造一个新的,空的树集,根据指定的比较器进行排序。
//案例:存储学生对象并遍历,创建TreeSet集合使用带参构造方法
//要求:按照年龄从小到大排序,年龄相同时,按照姓名字母排序
public class Student{
    private int age;
    private String name;

    public Student(){};

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

    public void setAge(int age){
        this.age=age;
    }

    public void setName(String name){
        this.name=name;
    }

    public int getAge(){
        return age;
    }

    public String getName(){
        return name;
    }

}

import java.util.Comparator;
import java.util.TreeSet;

public class treesetdemo03 {
    public static void main(String[] args) {
        //1、创建学生类---------》Student
        //2、创建TreeSet集合的带参构造
       TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
           @Override
           public int compare(Student s, Student s0) {
               int num = s.getAge()-s0.getAge();
               int num2 = num==0?s.getName().compareTo(s0.getName()):num;
               return num2;
           }
       });
        //3、创建学生对象
        Student s1 = new Student(16,"王华");
        Student s2 = new Student(13,"李明");
        Student s3 = new Student(15,"钱军");
        Student s4 = new Student(9,"文华");
        Student s5 = new Student(9,"郑国");
        Student s6 = new Student(9,"郑国");
        //把学生对象添加到集合
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);
        ts.add(s6);
        //遍历集合
        for (Student s:ts){
            System.out.println(s.getAge()+","+s.getName());//java.lang.ClassCastException:类转换异常
        }
    }
}
  • 结论:
    • 用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
    • 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(To1,To2)方法
    • 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写

4.4、例子

import java.util.TreeSet;

//成绩排序
public class treesetdemo04 {
    public static void main(String[] args) {
        //1、创建学生成绩类------->StudentDegree
        //2、创建TreeSet集合
        //方式一:采用的是------------->比较器排序Comparator
//        TreeSet<StudentDegree> ts = new TreeSet<StudentDegree>(new Comparator<StudentDegree>() {
//            @Override
//            public int compare(StudentDegree t, StudentDegree t1) {
//                double num = (t1.getCd()+t1.getMd())-(t.getCd()+t.getMd());//此种写法到学科很多时就很麻烦,所以我们可以在学生成绩类中添加一个总分方法
//        		  double num = t1.getsum()-t.getsum();//主要条件
//        		  double num2 = num==0?t1.getCd()-t.getCd():num;//次要条件
//					次要条件二大家可以自己去试着补充
//                return (int) num2;
//            }
//        });

        //方式二:采用的是---------->自然排序Comparable,具体操作内容见学生成绩类
        TreeSet<StudentDegree> ts = new TreeSet<StudentDegree>();

        //3、创建学生成绩对象
        StudentDegree sd1 = new StudentDegree("李明",96.5,80.0);
        StudentDegree sd2 = new StudentDegree("钱军",99.0,85.0);
        StudentDegree sd3 = new StudentDegree("文华",86.0,93.5);
        StudentDegree sd4 = new StudentDegree("刘乐",95.5,100.0);
        StudentDegree sd5 = new StudentDegree("刘明",96.5,99.0);
        //4、添加到集合
        ts.add(sd1);
        ts.add(sd2);
        ts.add(sd3);
        ts.add(sd4);
        ts.add(sd5);
        //5、遍历集合(按总分从高到低排序)
        //方法一
//        Iterator<StudentDegree> it = ts.iterator();
//        while(it.hasNext()){
//            StudentDegree sd = it.next();
//            System.out.println(sd.getName()+","+sd.getCd()+","+sd.getMd()+"总分:"+ sd.getsum());
//        }
        //方法二
        for (StudentDegree sd:ts){
            System.out.println(sd.getName()+","+sd.getCd()+","+sd.getMd()+"总分:"+ sd.getsum());
        }
    }
}

//方式一比较器排序Comparator的学生成绩类
public class StudentDegree{
    private String name;
    private double cd;//语文成绩
    private double md;//数学成绩
    
   。。。省略掉重写类和set、get方法
}

//方式二自然排序Comparable的学生成绩类
public class StudentDegree implements Comparable<StudentDegree>{
    private String name;
    private double cd;//语文成绩
    private double md;//数学成绩
    
    public double getsum(){
        return this.cd+this.md;
    }
   。。。省略掉重写类和set、get方法
       
    @Override
    public int compareTo(StudentDegree s) {
        double num = (s.cd+s.md)-(this.cd+this.md);//总成绩由高到低排序
        double num2 = num==0?s.cd-this.cd:num;
        return (int)num2;
    }
}
//不重复随机数:获取10个1~20之间的随机数,要求随机数不能重复,并在控制台输出
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

public class Setdemo {
    public static void main(String[] args) {
        //创建Set集合
//        Set<Integer> set = new HashSet<Integer>();
        Set<Integer> set = new TreeSet<Integer>();//这里采用TreeSet的结果是有序的
        //创建随机数对象
        Random random = new Random();
        //判断集合长度是否小于10,若是,则产生一个随机数;
        while (set.size() < 10) {
            int num = random.nextInt(20) + 1;
            set.add(num);
        }
        for (Integer i : set) {
            System.out.println(i);
        }
    }
}

五、泛型

5.1、泛型的概述和好处

  • 泛型:是JDK5引入的特性,它提供了编译类型安全检测机制,该机制允许在编译时检测到非法的类型;它的本质是参数化类型,也就是说操作的数据类型被指定为一个参数
  • 参数化类型:就是将类型由原来具体的类型参数化,然后在使用/调用时传入具体的类型
  • 这种参数类型可以用类、方法和接口中,分别称为泛型类、泛型方法、泛型接口

泛型定义格式:

  • <类型>:指定一种类型的格式,这里的类型可以看成为形参
  • <类型1,类型2...>:指定多种类型的格式,多种类型之间用逗号隔开,这里的类型可以看成为形参
  • 将来具体调用时,给定的类型可以看成为实参,并且实参的类型只能是引用数据类型

泛型的好处:

  • 把运行时期的问题提前到了编译期间
  • 避免了强制类型转换

5.2、泛型类

泛型类的定义格式:

  • 格式:修饰符 class 类名<类型>
  • 范例:public class Generic
    • 此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
public class Genericdemo01<T> {//T---->可以指定任何引用类型
    private T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
}

public class test {
    public static void main(String[] args) {
        student s = new student();
        s.setName("刘华");
        System.out.println(s.getName());

        teacher t = new teacher();
        t.setAge(30);//自动装箱 int---->Integer
//      t.setAge("30");//这里只能接收单一类型,不能有其他类型,
        System.out.println(t.getAge());
        System.out.println("---------------");

        //要想实现多种类型,可以使用泛型类来完成----->Generic
        Genericdemo01<String> g1 = new Genericdemo01<String>();
        g1.setT("张飞");
        System.out.println(g1.getT());
        Genericdemo01<Integer> g2 = new Genericdemo01<Integer>();
        g2.setT(30);
        System.out.println(g2.getT());
    }
}

5.3、泛型方法

泛型方法的定义格式:

  • 格式:修饰符 <类型> 返回值类型 方法名(类型 变量名)
  • 范例:public void show(T t)
/*public class Gernericdemo02 {
    public void show(String s){
        System.out.println(s);
    }
    public void show(int i){
        System.out.println(i);
    }
    public void show(boolean b){
        System.out.println(b);
    }
}*/

/*//泛型类改进
public class Gernericdemo02 <T> {
    public void show(T t){
        System.out.println(t);
    }
}//虽然用泛型类改进了但是每次都需要用它单独创建一个其对象,这样有点麻烦了
*/

//泛型方法改进
public class Gernericdemo02 {
    public <T> void show(T t){
        System.out.println(t);
    }
}

public class test {
    public static void main(String[] args) {
        //普通类
//        Gernericdemo02 g = new Gernericdemo02();
//        g.show("林东");
//        g.show(30);
//        g.show(true);
//    g.show(3.14);当需要别的类型时,就必须在方法类中在去定义一个相关类型

//        //泛型类
//        Gernericdemo02<Double> g = new Gernericdemo02<Double>();
//        g.show(3.14);
//        Gernericdemo02<Integer> g1 = new Gernericdemo02<Integer>();
//        g1.show(30);

        //泛型方法
        Gernericdemo02 g = new Gernericdemo02();
        g.show(12);
        g.show("李浩");
        g.show("3.14");
    }
}

5.4、泛型接口

泛型接口的定义格式:

  • 格式:修饰符 interface 接口名 <类型>
  • 范例:public interface Generic
public interface Genericdemo03 <T> {//接口无法实例化,所以我们需要给出实现类
        void show(T t);
}

public class Genericdemo03Impl <T> implements Genericdemo03<T> {
    @Override
    public void show(T t) {
        System.out.println(t);
    }
}

public class test {
    public static void main(String[] args) {
        Genericdemo03Impl<String> g1 = new Genericdemo03Impl<String>();
        g1.show("李明");
        Genericdemo03Impl<Integer> g2 = new Genericdemo03Impl<Integer>();
        g2.show(30);
        Genericdemo03Impl<Boolean> g3 = new Genericdemo03Impl<Boolean>();
        g3.show(true);
    }
}

5.5、类型通配符

为了表示各种泛型List的父类,可以使用类型通配符

  • 类型通配符:<?>
  • List<?>:表示元素类型未知的List,它的元素可以匹配任何类型
  • 这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素添加到其中

如果说我们不希望List<?>是任何泛型List的父类,只希望代表某一类泛型List的父类,可以使用类型通配符的上限

  • 类型通配符上限:<? extends 类型>
  • List<? extends Number>:它表示的类型是NUmber或者其子类型

除了可以指定类型通配符的上限,我们也可以指定类型通配符的下限

  • 类型通配符下限:<? super 类型>
  • List<? super Number>:它表示的类型是Number或者其父类型
import java.util.ArrayList;
import java.util.List;

public class Genericdemo04 {
    public static void main(String[] args) {
        //类型通配符:<?>
        List<?> list1 = new ArrayList<Object>();
        List<?> list2 = new ArrayList<Number>();
        List<?> list3 = new ArrayList<Integer>();
        System.out.println("------------------");
        //类型通配符上限:<? extends 类型>
//      List<? extends Number> list4 = new ArrayList<Object>();//其上限为Number,后面当然不能为其上限的父类
        List<? extends Number> list5 = new ArrayList<Number>();
        List<? extends Number> list6 = new ArrayList<Integer>();
        System.out.println("------------------");
        //类型通配符下限:<? super 类型>
        List<? super Number> list7 = new ArrayList<Object>();
        List<? super Number> list8 = new ArrayList<Number>();
//      List<? super Number> list9 = new ArrayList<Integer>();//Number为其下限,不能在写Number的子类
    }
}

5.6、可变参数

可变参数又称为参数个数可变,用作方法的形参出现,那么方法参数个数就是可变的了

  • 格式:修饰符 返回值类型 方法名(数据类型...变量名)
  • 范例:public static int sum(int ...a)
public class changenumber {
    public static void main(String[] args) {
        System.out.println(sum(10,20));
        System.out.println(sum(10,20,30,40,50));
    }

//    public static int sum(int b,int... a){ return 0; }//当有多个参数时,可变参数一定要放在最后面的位置
    public static int sum(int... a){//这里a的本质就是一个数组
//        System.out.println(a);//[I@2d98a335
//        return 0;
        int sum=0;
        for (int i:a){
            sum+=i;
        }
        return sum;
    }
}

注意:1、这里参数的本质就是一个数组;2、当有多个参数时,可变参数一定要放在最后面的位置

5.7、可变参数的使用

Arrays工具类中的一个静态方法:

  • public static List asList(T... a):返回由指定数组支持的固定大小的列表
  • 返回的集合不能作增删操作,可以做修改操作

List接口中有一个静态方法:

  • public static List of(E... elements):返回包含任意数量元素的不可变列表
  • 返回的集合不能做增删改操作

Set接口中有一个静态方法:

  • public staitc Set of(E... elements):返回一个包含任意数量元素的不可变集合
  • 在给元素的时候,不能给重复的元素
  • 返回的集合不能做增删操作,没有修改方法
public class Argsdemo {
    public static void main(String[] args) {
//        //public static <T> List <T> asList(T... a):返回由指定数组支持的固定大小的列表
//        List<String> list = Arrays.asList("hello", "30", "world");
////        list.add("java");//java.lang.UnsupportedOperationException:表示不支持请求的操作
////        list.remove("world");//java.lang.UnsupportedOperationException:表示不支持请求的操作
//        list.set(1,"java");//[hello, java, world]
//        System.out.println(list);

//        //public static <E> List <E> of(E... elements):返回包含任意数量元素的不可变列表
//        List<String> list = List.of("hello", "3.14", "oracal","hello");
////        list.add("javaee");//java.lang.UnsupportedOperationException:表示不支持请求的操作
////        list.remove("3.14");//java.lang.UnsupportedOperationException:表示不支持请求的操作
////        list.set(1,"java");//java.lang.UnsupportedOperationException:表示不支持请求的操作
//        System.out.println(list);//[hello, 3.14, oracal,hello]

          //public staitc <E> Set<E> of(E... elements):返回一个包含任意数量元素的不可变集合
//        Set<String> set = Set.of("hello", "30", "java","hello");//java.lang.IllegalArgumentException:表示一种方法已经通过了非法或不正确的参数。
          Set<String> set = Set.of("hello", "30", "java");
//        set.add("javaee");//java.lang.UnsupportedOperationException:表示不支持请求的操作
//        set.remove("java");//java.lang.UnsupportedOperationException:表示不支持请求的操作
        System.out.println(set);//[30, hello, java]
    }
}
posted @ 2022-01-25 20:59  Devin-Y  阅读(24)  评论(0编辑  收藏  举报