• JavaSE基础day15 泛型、双向集合、无序集合、通配符



    一.泛型

    (一)泛型的概述和使用

    1、泛型:广泛的类型,在定义一个类的时候,类型中有些方法参数、返回值类型不确定,可以使用一个符号,来表示那些尚未确定的类型,这个符号,就称为泛型。泛型是java5中引入的特性,它提供了编译时类型安全检测机制

    2、使用:在集合当中,我们去创建集合对象的时候,后面可以添加一个<引用数据类型> ,表示泛型,表示的是该集合当中,存储元素的数据类型。在集合的创建中添加了泛型之后,该集合只能存储该类型的数据,或者是该类型子类的数据,不能存储不是该类型,并且不能用该类型接受的数据。

           例如:ArrayList<Integer> al = new ArrayList<Integer>();

    3、泛型的好处:

    (1) 提高了数据的安全性,将运行时的问题,提前暴露在编译时期

    (2) 避免了强转的麻烦

    4、注意事项:

    (1) 前后一致:在创建对象时,赋值符号前面和后面的类型的泛型,必须一致

    (2) 泛型推断:如果前面的引用所属的类型已经写好了泛型,后面创建对象的类型就可以只写一个尖括号,尖括号中可以不写任何内容。<>特别像菱形,称为“菱形泛型”,jdk1.7特性

    import java.util.ArrayList;
    public class FanXing使用 {
        public static void main(String[] args) {
            // 1. 没有泛型时集合的使用: 缺点 1) 存储在同一个容器中的数据类型可以不一致,导致数据获取发生类型转换错误
            // 2) 当需要获取集合元素时,做多态向下转型,麻烦
            ArrayList list = new ArrayList();
            list.add("hello");
            list.add(15);
            list.add(3.14);
    
           /* for(Object obj : list){
               String s = (String)obj;
               System.out.println(s);
            }*/
    
            // 2. 有泛型集合使用: 当创建一个集合对象时, 可以设定出集合中具体的泛型类型
            // list1集合中只能存储Integer类型和其子类类型
            ArrayList<Integer> list1 = new ArrayList<>();
            list1.add(15);
            // 泛型好处: 1) 将没有泛型时,运行时期的异常前置, 在编译时期不允许其他无关类型数据存储在集合中
            // 2) 不需要再进行多态的向下转型了
            list1.add(-18);
    
            for(Integer i : list1){
                System.out.println(i);
            }
    
            ArrayList<String> list2 = new ArrayList<>();
            list2.add("hi");
    
            // 3. 泛型写作注意事项
            // 1. 前后一致
            ArrayList<Double> list3 = new ArrayList<Double>();
            // ArrayList<Object> list4 = new ArrayList<Double>();
            // 2. 到JDK1.7版本, 简化了后面泛型写作, 后面泛型可以不写, 默认与前面泛型保持一致
            ArrayList<String> list5 = new ArrayList<>();
            list5.add("hi");
            String str = list5.get(0);
            System.out.println(str);// hi
            ArrayList<String> list6 = new ArrayList();
        }
    }

    迭代器中的泛型

    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.ListIterator;
    
    public class FanXing在迭代器中的使用 {
        public static void main(String[] args) {
            ArrayList<Integer> list1 = new ArrayList<>();
            list1.add(15);
            list1.add(18);
            list1.add(-9);
    
            Iterator<Integer> it = list1.iterator();
            while(it.hasNext()){
                int i = it.next();
                System.out.println(i);
            }
    
            ListIterator<Integer> it1  = list1.listIterator();
            while(it1.hasNext()){
                int i = it1.next();
                System.out.println(i);
            }
        }
    }

    (二)泛型类的定义

    1、泛型类:带着泛型定义的类

    2、格式:

           class 类名<泛型类型1, 泛型类型2, .....>  {

      }

    3、说明:

      (1) 类名后面跟着的泛型类型,是泛型的声明,一旦泛型声明出来,就相当于这个类型成为了已知类型,这个类型就可以在整个类中使用

      (2) 泛型的声明名称,只需要是一个合法的标识符即可,但是通常我们使用单个大写字母来表示,常用字母:T、W、Q、K、V、E

      (3) 泛型确定的时机:将来在使用和这个类,创建对象的时候

    import java.util.ArrayList;
    
    // 1. 类上的泛型在类型中当做一个已知的数据类型进行使用(泛型未来可以代表一种引用数据类型)
    // 菱形尖括号中的E, 就表示泛型类型
    // 泛型使用一个大写英文字母进行表示: 英文字母随意; 通常使用一些常见英文单词的首字母  K(key) V(value) E(element) T(type), W, Q, A...
    public class FanXingClass<E> {
        // 2. 类上的泛型E在类型中可以进行使用
        ArrayList<E> list = new ArrayList<>();
    
        public void add(E e){
            list.add(e);
            System.out.println(list);
        }
    
        public E get(int index){
            return list.get(index);
        }
    }
    public class TestFanXing {
        public static void main(String[] args) {
            // 2. 类上的泛型具体类型确定时机: 在创建这个类对象同时(实例化对象同时)
            FanXingClass<String> fxc = new FanXingClass();
            fxc.add("857");
            fxc.add("你好");
    
            String first = fxc.get(0);
            System.out.println(first);// 857
        }
    }

    (三)泛型方法的定义

    1、在方法声明中,带着泛型声明的方法,就是泛型方法

    2、格式:

           修饰符 <泛型声明1, 泛型声明2,.....> 返回值类型 方法名称(参数列表) {

          }

    3、说明:

      (1) 在方法上声明的泛型,可以在整个方法中,当做已知类型来使用

      (2) 如果【非静态】方法上没有任何泛型的声明,那么可以使用类中定义的泛型

      (3) 如果【静态】方法上没有任何的泛型声明,那么就不能使用泛型,连类中定义的泛型,也不能使用,因为类中的泛型需要在创建对象的时候才能确定。所以【静态】方法想使用泛型,就必须在自己的方法上单独声明。

    import java.util.ArrayList;
    
    // 1. 类上的泛型在类型中当做一个已知的数据类型进行使用(泛型未来可以代表一种引用数据类型)
    // 菱形尖括号中的E, 就表示泛型类型
    // 泛型使用一个大写英文字母进行表示: 英文字母随意; 通常使用一些常见英文单词的首字母  K(key) V(value) E(element)
    // T(type), W, Q, A...
    public class FanXingClass<E> {
        // 2. 类上的泛型E在类型中可以进行使用
        ArrayList<E> list = new ArrayList<>();
    
        public void add(E e){
            list.add(e);
            System.out.println(list);
        }
    
        public E get(int index){
            return list.get(index);
        }
    
        // 静态方法不能使用类上泛型: 如果想使用泛型只能在静态方法上自己定义
        // 类上泛型具体数据类型确定时机是创建对象的同时; 而静态优先于对象存在, 可以使用静态方法的时候,E类型还没确定
        // 因此不能用
        // 功能: 将任意类型数组中的指定两个索引位置元素进行交换
        // 方法上定义的泛型就只能在当前方法中使用
        public static<W> void changeArray(W[] arr, int index, int index1){
            // 记录下index索引位置数据的原值
            W temp = arr[index];
            arr[index] = arr[index1];
            arr[index1] = temp;
    
            for(W ele : arr){
                System.out.print(ele + " ");
            }
        }
    }
    public class TestFanXing {
        public static void main(String[] args) {
            // 2. 类上的泛型具体类型确定时机: 在创建这个类对象同时(实例化对象同时)
            FanXingClass<String> fxc = new FanXingClass();
            fxc.add("857");
            fxc.add("你好");
    
            String first = fxc.get(0);
            System.out.println(first);// 857
    
            // 3. 测试静态方法上的泛型
            Integer[] arr = {12,13};
            String[] arr1 = {"122","133"};
            // changeArray(W[] arr, int index, int index1);
            FanXingClass.changeArray(arr,0,1);
    
        }
    }

    (四)泛型通配符

    1、使用泛型的时候,没有使用具体的泛型声明T,而是使用了和声明过的某个泛型T有关的一类类型,就称为泛型的通配符。三种形式:

    2、第一种形式,使用?来表示可以是任意类型,例如:

           Collection<E>接口中的removeAll(Collection<?> c),表示可以接收任意泛型类型的集合,作为该方法的实际参数,参数集合的泛型,可以是与E没有任何关系

    3、第二种形式,使用? extends E来表示必须是某个泛型类型或是该泛型类型的子类,例如:

           Collection<E>接口中的addAll(Collection<? extends E> c),表示可以接收泛型类型是调用者泛型类型或者其子类的集合,作为该方法的实际参数。参数的泛型和调用者的泛型,必须有关(相同或者是子父类)。确定了泛型的上边界。

    4、第三种形式,使用? super E来表示必须是某个泛型类型或者是该泛型类型的父类,例如:

           Arrays工具类中,排序方法static <T> void sort(T[] a, Comparator<? super T> c),T是该方法的泛型,T表示的是数组中元素的类型,<? super T>表示可以接收泛型类型是数组元素类型T或者是元素类型T的父类的比较器,作为sort方法的参数。参数的泛型和方法的泛型,必须有关(相同或者是子父类)。确定了泛型的下边界。

    import java.util.ArrayList;
    public class 泛型通配符 {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            list.add("hi");
            ArrayList<Integer> list1 = new ArrayList<>();
            list1.add(15);
            ArrayList<String> list2 = new ArrayList<>();
            list.add("hello");
    
            // removeAll(Collection<?> c): 泛型?表示任意数据类型均可,从list集合中将list1集合中的每一个元素都删除
            // 包含就删除,不包含不删除
            list.removeAll(list1);
    
            // addAll(Collection<? extends E> c) : 参数Collection集合提供的泛型类型?表示, 必须是E类型本身
            // 或者是E类型的子类类型
            //list.addAll(list1);
            list.addAll(list2);
    
            // ? super T : 提供的泛型类型?, 必须是T类型本身或者是T类型的父类类型
        }
    }

    二. 无序单列集合Set

    (一)  概述

    1. java.util.Set接口和java.util.List接口一样,同样继承自Collection接口,它与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的扩充,只是比Collection接口更加严格了。与List接口不同的是,Set接口中元素无序,并且都会以某种规则保证存入

    的元素不出现重复。

     

    2. 特点:

      (1) 无序:没有任何前后的分别,存入的顺序和取出的顺序不一定一致

      (2) 没有索引:集合中没有任何位置,元素也就没有位置的属性

      (3) 不重复:没有位置的区分,相同值的元素没有任何分别,所以不能重复

     

    3. Set的常用实现类:HashSet

     

    4. Set集合的遍历方式: toArray(), 迭代器遍历, 增强for遍历

    import java.util.HashSet;
    public class SetMethod {
        public static void main(String[] args) {
            HashSet<String> set = new HashSet<>();
            // 1. set集合中不存储重复元素
            set.add("hi");
            set.add("hello");
            set.add("19");
            set.add("19");
            set.add("66");
            // 2. Set无序集合: 存入数据的顺序和取出数据的顺序不保证一致
            System.out.println(set);// [66, hi, 19, hello]
         // 3. addAll(Collection c): 将参数c集合中的每一个元素都添加到方法调用的set集合中
            HashSet<String> set1 = new HashSet<>();
            set1.add("66");
            set1.add("world");
            set1.add("你好");
    
           /* set.addAll(set1);
            System.out.println(set);// [66, hi, world, 你好, 19, hello]*/
    
            // 4. containsAll(Collection<?> c) : 验证方法调用集合中是否完全包含参数集合中c中的每一个元素
            // System.out.println(set.containsAll(set1));// false
    
            // 5. removeAll(Collection<?> c) : 将参数c集合中的每一个元素都从方法调用集合中删除
            // 换言之: 将两个集合的交集从方法调用集合中删除
            /*set.removeAll(set1);
            System.out.println(set);// [hi, 19, hello]*/
    
            // 6. retainAll(Collection<?> c) : 将两个集合的交集保留在方法调用集合中
            set.retainAll(set1);
            System.out.println(set);// [66]
        }
    }

    练习1

    使用合适的容器存储5个不重复的1-99之间的任意随机整数

    分析 :

       容器: 数组, List(有序,有索引,可重复), Set(无序,无索引,不重复)

    import java.util.HashSet;
    import java.util.Random;
    
    public class Set案例 {
        public static void main(String[] args) {
            // 使用合适的容器存储5个不重复的1-99之间的任意随机整数
            HashSet<Integer> set = new HashSet<>();
            Random ran = new Random();
            while(set.size() < 5){
               int number = ran.nextInt(99) + 1;
               set.add(number);
            }
            System.out.println(set);
        }
    }

    (二)  数据结构之哈希表

    1. 哈希值

      (1) 是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值

      (2) 如何获取哈希值: Object类中的public int hashCode()返回对象的哈希码值

      (3) 哈希值的特点

               同一个对象多次调用hashCode()方法返回的哈希值是相同的

             默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同

     

    2. 哈希表结构

      (1)  JDK1.8以前: 数组 + 链表

    (2) JDK1.8以后:

      a. 节点个数少于等于8个: 数组 + 链表

      b. 节点个数多于8个: 数组 + 红黑树

    (二)  HashSet保证元素唯一源码分析

    1、某个对象obj,在即将要存储到HashSet集合的时候,首先计算obj的hashCode值

    2、在集合中的所有元素的哈希值,都和obj的哈希值不同,说明在集合中不存在obj,可以直接将obj存储到HashSet中

    3、在集合中有若干元素的哈希值,和obj的哈希值相同,并不能说明obj已经存在于集合中,需要使用equals判断obj是否和那些与自己哈希值相同的元素是否相等

    4、如果这些元素所有的和obj比较equals之后,都不相等,那么就说明obj不存在于集合中,可以将obj存储到HashSet中

    5、如果这些元素有任意一个和obj比较equals之后,发现相等,那么就说明obj已经存在于集合中,所以obj就不能存储,存储失败

     

    结论: 自定义引用数据类型想通过成员变量区分是否是重复对象, 需要使用alt+insert快捷键重写hashCode和equals方法

    import java.util.Objects;
    public class Person {
        private String name;
        private int age;
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Person person = (Person) o;
            return age == person.age && Objects.equals(name, person.name);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(name, age);
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        public Person() {
        }
    
        public Person(String name, int age) {
            this.name = name;
            this.age = 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;
        }
    }
    import java.util.HashSet;
    public class HashSet保证元素唯一 {
        public static void main(String[] args) {
            HashSet<Integer> set = new HashSet<>();
            set.add(15);
            set.add(18);
            set.add(15);
            // 1. HashSet存储JDK定义类型,直接保证元素唯一性: JDK的类,都已经重写过了HashCode和equals方法
            System.out.println(set);// [18,15]
            
    // 2. HashSet存储自定义的引用数据类型: 想通过成员变量保证对象唯一性需重写hashCode和equals方法
    
            HashSet<Person> set1 = new HashSet<>();
            Person p = new Person("张三",19);
            System.out.println(p.hashCode());// 24022539
            set1.add(p);
            Person p2 = new Person("李四",19);
            System.out.println(p2.hashCode());// 26104871
            set1.add(p2);
            Person p3 = new Person("张san",20);
            System.out.println(p3.hashCode());// 1018270485
            set1.add(p3);
            Person p1 = new Person("张三",19);
            System.out.println(p1.hashCode());// 24022539
            System.out.println(p.equals(p1));// true
            set1.add(p1);
            // [Person{name='张san', age=20}, Person{name='张三', age=19}, Person{name='李四', age=19}]
            System.out.println(set1);
        }
    }

    扩展: LinkedHashSet : 类是HashSet类的子类,在保证元素唯一和功能上, 与父类HashSet完全一致, 唯一不同,LinkedHashSet底层是双向链表, 记录每一个元素前后数据的其他元素,因此效果可以保证元素迭代顺序与添加顺序一致

    import java.util.HashSet;
    import java.util.LinkedHashSet;
    
    public class LinkedHashSetDemo {
        public static void main(String[] args) {
            HashSet<Integer> set = new HashSet<>();
            set.add(15);
            set.add(1);
            set.add(99);
            System.out.println(set);// [1, 99, 15]
    
            LinkedHashSet<Integer> set1 = new LinkedHashSet<>();
            set1.add(15);
            set1.add(1);
            set1.add(99);
            System.out.println(set1);// [15, 1, 99]
        }
    }

    三. 双列集合

    (一)  Map概述

    1、体系位置:双列集合的顶层接口

    2、类比理解:map单词含义,地图,地图上的每个点,都表示了生活中的一个具体位置。地图的点和生活中的位置,有一个一一对应的关系,这种关系是通过穷举的方式来描述的。

    3、数据结构:描述的就是一个数据(key)到另一个数据(value)的映射关系(对应关系)

    4、Map<K,V>的特点:称为键值对映射关系(一对一)

           Key(键)是唯一的(不重复),value(值)不是唯一的

           每个键都只能对应确定唯一的值

    5. Map集合没有索引, 因此存储的元素不能保证顺序

     

    (二)  Map常用的子类

    通过查看Map接口描述,看到Map有多个实现类,这里我们主要讲解常用的HashMap集合 LinkedHashMap集合.

     

    HashMap<K,V>:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。

     

    LinkedHashMap<K,V>:HashMap下有个子类LinkedHashMap,存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。

    (三)  Map接口中常用的方法

    1. public V put(K key, V value):

       1) 如果添加的key值在map集合中不存在, 那么put方法表示将键值对添加到map集合中

    2) 如果添加的key值在map集合中存在, 那么put方法表示修改value值

    2. public V remove(Object key): 把指定的键所对应的键值对元素在Map集合中删除,返回被删除元素的值。

    3. public V get(Object key):根据指定的键,在Map集合中获取对应的值。

    4. boolean containsKey(Object key): 判断集合中是否包含指定的键。

    5. void clear() : 表示清空Map集合中所有键值对数据

    6. boolean isEmpty() : 验证Map集合中是否还是键值对数据, 如果没有返回true, 如果有返回false

    7. int size() : 获取到Map集合中键值对数据个数(求Map集合的长度)

    import java.util.HashMap;
    import java.util.Map;
    public class MapMethod {
        public static void main(String[] args) {
            Map<Integer,String> map = new HashMap<>();
            // 1. public V put(K key, V value): 如果添加到Map集合中的key值不存在,put表示新增键值对数据
            map.put(1,"a");
            map.put(2,"b");
            map.put(3,"c");
            System.out.println(map);// {1=a, 2=b, 3=c}
    
            // 2. public V put(K key, V value): 如果添加到Map集合中的key值已经存在,表示value值修改(替换)
            map.put(2,"jack");
            System.out.println(map);// {1=a, 2=jack, 3=c}
    
            // 3. HashMap实现类底层哈希表结构,因此元素存取无序; Map集合没有索引
            map.put(11,"d");
            map.put(6,"d");
            System.out.println(map);// {1=a, 2=jack, 3=c, 6=d, 11=d}
    
            // 4. public V remove(Object key): 把指定的键所对应的键值对元素在Map集合中删除,返回被删除元素的值。
            String value = map.remove(6);
            System.out.println(value);// d
            System.out.println(map);// {1=a, 2=jack, 3=c, 11=d}
    
            // 5. public V get(Object key):根据指定的键,在Map集合中获取对应的值。
            System.out.println(map.get(3));// c
            System.out.println(map.get(99));// null
    
            // 6. boolean containsKey(Object key): 判断集合中是否包含指定的键。
            System.out.println(map.containsKey(11));// true
            System.out.println(map.containsKey(19));// false
            System.out.println(map.size());// 4
    
            // 7.void clear() : 表示清空Map集合中所有键值对数据
            map.clear();
            System.out.println(map);// {}
    
            // 8.boolean isEmpty() : 验证Map集合中是否还是键值对数据, 如果没有返回true, 如果有返回false
            System.out.println(map.isEmpty()); // true
    
            // 9.int size() : 获取到Map集合中键值对数据个数(求Map集合的长度)
            System.out.println(map.size()); // 0
        }
    }

    (四)  Map集合的遍历方式

    1.1 键遍历

    1、获取Map集合中的所有键,放到一个Set集合中,遍历该Set集合,获取到每一个键,根据键再来获取对应的值。

    2、获取Map集合中的所有键

           Set<K> keySet()

    3、遍历Set集合的两种方法:

           迭代器

           增强for循环

    4、拿到每个键之后,获取对应的值

           V get(K key)

     

    import java.util.HashMap;
    import java.util.Set;
    
    public class MapKeySet {
        public static void main(String[] args) {
            HashMap<Integer,String> map = new HashMap<>();
            map.put(1,"a");
            map.put(2,"hello");
            map.put(3,"world");
    
            // 1. 获取到map集合中的所有key值集合
            Set<Integer> set = map.keySet();
            // 2. 遍历set集合,获取出每一个key值
            for(Integer key : set){
                // 3. 通过唯一的key值获取到对应的value值
                String value = map.get(key);
                System.out.println(key + "--" + value);
            }
        }
    }

  • 相关阅读:
    Django contenttypes 组件
    CPU 的工作原理
    Redis基础
    面向对象中的__slots__
    DRF 的解析器和渲染器
    DRF的认证、权限 和 限制
    DRF中的版本控制
    REST framework 视图
    Java多线程(1):3种常用的实现多线程类的方法
    Java多线程(2):线程加入/join()
  • 原文地址:https://www.cnblogs.com/lw1095950124/p/16010066.html
Copyright © 2020-2023  润新知