尚硅谷JavaSE笔记(三)

JavaSE(硅谷视频学习笔记 硅谷30天Java笔记,包括JavaSE基础和高级部分 阅读详情

系列文章目录

尚硅谷JavaSE笔记(一)

尚硅谷JavaSE笔记(二)

尚硅谷JavaSE笔记(三)


文章目录

前言

十四、集合框架

1、Java集合框架体系

2、Collection接口中的方法的使用

3、Iterator(迭代器)接口

4、Collection 子接口 1:List

5、Collection 子接口 2:Set

6、Map接口

7、Collections 工具类

十五、泛型

1、泛型概述

2、集合中使用泛型

3、比较器中使用泛型

4、自定义泛型结构

5、泛型在继承上的体现

6、通配符的使用


前言

本笔记为B站尚硅谷Java入门视频教程(在线答疑+Java面试真题)的笔记,是笔者对于已经学过的大二Java课程的查缺补漏及巩固。部分C语言学过的基本语法将跳过。


十四、集合框架

1、Java集合框架体系

Java 集合可分为 Collection 和 Map 两大体系:
•Collection 接口:用于存储一个一个的数据,也称 单列数据集合
        –List 子接口:用来存储有序的、可以重复的数据(主要用来替换数组,"动态"数组)
                •实现类:ArrayList(主要实现类)、LinkedList、Vector
        –Set 子接口:用来存储无序的、不可重复的数据(类似于高中讲的"集合")
                •实现类:HashSet(主要实现类)、LinkedHashSet、TreeSet
•Map 接口:用于存储具有映射关系“key-value 对”的集合,即一对一对的数据,也称 双列数据集合 。(类似于高中的函数、映射。(x1,y1),(x2,y2) ---> y = f(x) )
        –HashMap(主要实现类)、LinkedHashMap、TreeMap、Hashtable、Properties
•JDK 提供的集合 API 位于 java.util 包内

2、Collection接口中的方法的使用

添加:
(1)add(E obj):添加元素对象到当前集合中
2)addAll(Collection other): 添加 other 集合中的所有元素对象到当前集合中,即 this = this ∪ other
package Test_2.collection;

import org.junit.Test;

import java.util.ArrayList;
import java.util.Collection;

public class TestCollectionAdd {
    @Test
    public void testAdd(){
        //ArrayList 是 Collection 的子接口 List 的实现类之一。
        Collection coll = new ArrayList();
        coll.add("小李广");
        coll.add("扫地僧");
        coll.add("石破天");
        System.out.println(coll);
    }
    @Test
    public void testAddAll(){
        Collection c1 = new ArrayList();
        c1.add(1);
        c1.add(2);
        System.out.println("c1 集合元素的个数:" + c1.size());//2
        System.out.println("c1 = " + c1);
        Collection c2 = new ArrayList();
        c2.add(1);
        c2.add(2);
        System.out.println("c2 集合元素的个数:" + c2.size());//2
        System.out.println("c2 = " + c2);
        Collection other = new ArrayList();
        other.add(1);
        other.add(2);
        other.add(3);
        System.out.println("other 集合元素的个数:" + other.size());//3
        System.out.println("other = " + other);
        System.out.println();
        c1.addAll(other);
        System.out.println("c1 集合元素的个数:" + c1.size());//5
        System.out.println("c1.addAll(other) = " + c1);
        c2.add(other);
        System.out.println("c2 集合元素的个数:" + c2.size());//3
        System.out.println("c2.add(other) = " + c2);
    }
}

判断:

(3)int size():获取当前集合中实际存储的元素个数
4)boolean isEmpty():判断当前集合是否为空集合
(5)boolean contains(Object obj):判 断当前集合中是否存在一个与 obj 对象 equals 返回 true 的元素
(6)boolean containsAll(Collection coll):判断 coll 集合中的元素是否在当前集合中都存在。 即 coll 集合是否是当前集合的“子集”
(7)boolean equals(Object obj):判断当 前集合与 obj 是否相等
package Test_2.collection;

import org.junit.Test;

import java.util.ArrayList;
import java.util.Collection;

public class TestCollectionContains {
    @Test
    public void test01() {
        Collection coll = new ArrayList();
        System.out.println("coll 在添加元素之前,isEmpty = " + coll.isEmpty());
        coll.add("小李广");
        coll.add("扫地僧");
        coll.add("石破天");
        coll.add("佛地魔");
        System.out.println("coll 的元素个数" + coll.size());
        System.out.println("coll 在添加元素之后,isEmpty = " + coll.isEmpty());
    }
    @Test
    public void test02() {
        Collection coll = new ArrayList();
        coll.add("小李广");
        coll.add("扫地僧");
        coll.add("石破天");
        coll.add("佛地魔");
        System.out.println("coll = " + coll);
        System.out.println("coll 是否包含“小李广” = " + coll.contains("小李广"));
        System.out.println("coll 是否包含“宋红康” = " + coll.contains("宋红康"));
        Collection other = new ArrayList();
        other.add("小李广");
        other.add("扫地僧");
        other.add("尚硅谷");
        System.out.println("other = " + other);
        System.out.println("coll.containsAll(other) = " + coll.containsAll(other));
    }

    @Test
    public void test03(){
        Collection c1 = new ArrayList();
        c1.add(1);
        c1.add(2);
        System.out.println("c1 集合元素的个数:" + c1.size());//2
        System.out.println("c1 = " + c1);
        Collection c2 = new ArrayList();
        c2.add(1);
        c2.add(2);
        System.out.println("c2 集合元素的个数:" + c2.size());//2
        System.out.println("c2 = " + c2);
        Collection other = new ArrayList();
        other.add(1);
        other.add(2);
        other.add(3);
        System.out.println("other 集合元素的个数:" + other.size());//3
        System.out.println("other = " + other);
        System.out.println();
        c1.addAll(other);
        System.out.println("c1 集合元素的个数:" + c1.size());//5
        System.out.println("c1.addAll(other) = " + c1);
        System.out.println("c1.contains(other) = " + c1.contains(other));
        System.out.println("c1.containsAll(other) = " + c1.containsAll(other));
        System.out.println();
        c2.add(other);
        System.out.println("c2 集合元素的个数:" + c2.size());
        System.out.println("c2.add(other) = " + c2);
        System.out.println("c2.contains(other) = " + c2.contains(other));
        System.out.println("c2.containsAll(other) = " + c2.containsAll(other));
    }

}

删除:

(8)void clear():清空集合元素
9) boolean remove(Object obj) :从当前 集合中删除第一个找到的与 obj 对象 equals 返回 true 的元素。
(10)boolean removeAll(Collection coll):从当前集合中删除所有与 coll 集合中相同的元素。
即 this = this - this ∩ coll
(11)boolean retainAll(Collection coll):从当前集合 中删除两个集合中不同的元素,使得当前集合仅保留与 coll 集合中的元素相同 的元素,即当前集合中仅保留两个集合的交集,即 this = this ∩ coll;

package Test_2.collection;

import org.junit.Test;

import java.util.ArrayList;
import java.util.Collection;

public class TestCollectionRemove {
    @Test
    public void test01(){
        Collection coll = new ArrayList();
        coll.add("小李广");
        coll.add("扫地僧");
        coll.add("石破天");
        coll.add("佛地魔");
        System.out.println("coll = " + coll);
        coll.remove("小李广");
        System.out.println("删除元素\"小李广\"之后 coll = " + coll);

        coll.clear();
        System.out.println("coll 清空之后,coll = " + coll);
    }

    @Test
    public void test02() {
        Collection coll = new ArrayList();
        coll.add("小李广");
        coll.add("扫地僧");
        coll.add("石破天");
        coll.add("佛地魔");
        System.out.println("coll = " + coll);
        Collection other = new ArrayList();
        other.add("小李广");
        other.add("扫地僧");
        other.add("尚硅谷");
        System.out.println("other = " + other);
        coll.removeAll(other);
        System.out.println("coll.removeAll(other)之后,coll = " + coll);
        System.out.println("coll.removeAll(other)之后,other = " + other);
    }
    @Test
    public void test03() {
        Collection coll = new ArrayList();
        coll.add("小李广");
        coll.add("扫地僧");
        coll.add("石破天");
        coll.add("佛地魔");
        System.out.println("coll = " + coll);
        Collection other = new ArrayList();
        other.add("小李广");
        other.add("扫地僧");
        other.add("尚硅谷");
        System.out.println("other = " + other);
        coll.retainAll(other);
        System.out.println("coll.retainAll(other)之后,coll = " + coll);
        System.out.println("coll.retainAll(other)之后,other = " + other);
    }

}

其他:

(12)Object[] toArray():返回包含当前集合中所有元素的数组
(13) hashCode():获取集合对象的哈希值
(14)iterator():返回迭代器对象,用于 集合遍历

 @Test
    public void test01() {
        Collection coll = new ArrayList();
        coll.add("小李广");
        coll.add("扫地僧");
        coll.add("石破天");
        coll.add("佛地魔");
        //集合转换为数组:集合的 toArray()方法
        Object[] objects = coll.toArray();
        System.out.println("用数组返回 coll 中所有元素:" + Arrays.toString(objects));
        //对应的,数组转换为集合:调用 Arrays 的 asList(Object ...objs)
        Object[] arr1 = new Object[]{123,"AA","CC"};
        Collection list = Arrays.asList(arr1);
        System.out.println(list);
    }

3、Iterator(迭代器)接口

Iterator 接口
•在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK 专门提供了一 个接口 java.util.Iterator Iterator 接口也是 Java 集合中的一员,但它与 Collection Map 接口有所不同。
        –Collection 接口与 Map 接口主要用于存储 元素
        –Iterator,被称为迭代器接口,本身并不提供存储对象的能力,主要用于 遍历 Collection 中的元素

•Collection 接口继承了 java.lang.Iterable 接口,该接口有一个 iterator()方法,那么所有 实现了 Collection 接口的集合类都有一个 iterator()方法,用以返回一个实现了 Iterator 接口的对象。
        –public Iterator iterator(): 获取集合对应的迭代器,用来遍历集合 中的元素的。
        –集合对象每次调用 iterator()方法都得到一个全新的迭代器对象,默认游标 都在集合的第一个元素之前。

•Iterator 接口的常用方法如下:
        –public E next():返回迭代的下一个元素。
        –public boolean hasNext():如果仍有元素可以迭代,则返回 true。
•注意:在调用 it.next()方法之前必须要调用 it.hasNext()进行检测。若不调用,且下一 条记录无效,直接调用 it.next()会抛出 NoSuchElementException 异常

package Test_2.collection;

import org.junit.Test;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class TestIterator {
    @Test
    public void test01(){
        Collection coll = new ArrayList();
        coll.add("小李广");
        coll.add("扫地僧");
        coll.add("石破天");

        Iterator iterator = coll.iterator();
        System.out.println(iterator.next());
        System.out.println(iterator.next());
        System.out.println(iterator.next());
        System.out.println(iterator.next());//报 NoSuchElementException 异常
    }


    @Test
    public void test02(){
        Collection coll = new ArrayList();
        coll.add("小李广");
        coll.add("扫地僧");
        coll.add("石破天");
        Iterator iterator = coll.iterator();//获取迭代器对象
        while (iterator.hasNext()){   //判断是否还有元素可迭代
            System.out.println(iterator.next());   //取出下一个元素
        }
    }
    
}

迭代器的执行原理
Iterator 迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素,接下来通过一个图例来演示 Iterator 对象迭代元素的过程:

使用 Iterator 迭代器删除元素 

java.util.Iterator 迭代器中有一个方法:void remove() ;
Iterator iter = coll . iterator (); // 回到起点
while ( iter . hasNext ()){
        Object obj = iter . next ();
        if ( obj . equals ( "Tom" )){
                 iter . remove ();
        }
}

注意:
•Iterator 可以删除集合的元素,但是遍历过程中通过迭代器对象的 remove 方法,不是 集合对象的 remove 方法。
•如果还未调用 next()或在上一次调用 next() 方法之后已经调用了 remove() 方法,再 调用 remove()都会报 IllegalStateException。
•Collection 已经有 remove(xx)方法了,为什么 Iterator 迭代器还要提供删除方法呢?因 为迭代器的 remove()可以按指定的条件进行删除

例如:要删除以下集合元素中的偶数
package Test_2.collection;

import org.junit.Test;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class TestIteratorRemove {
    @Test
    public void test01(){
        Collection coll = new ArrayList();
        coll.add(1);
        coll.add(2);
        coll.add(3);
        coll.add(4);
        coll.add(5);
        coll.add(6);

        Iterator iterator = coll.iterator();
        while (iterator.hasNext()){
            Integer element = (Integer) iterator.next();
            if(element % 2 == 0){
                iterator.remove();
            }
        }
        System.out.println(coll);
    }

}

foreach 循环

•foreach 循环(也称增强 for 循环)是 JDK5.0 中定义的一个高级 for 循环,专门用来 遍历数组和集合 的。
•foreach 循环的语法格式:
for ( 元素的数据类型 局部变量 : Collection 集合或数组 ){
// 操作局部变量的输出操作
}
// 这里局部变量就是一个临时变量,自己命名就可以

package Test_2.collection;

import org.junit.Test;

import java.util.ArrayList;
import java.util.Collection;

public class TestForeach {
    @Test
    public void test01(){
        Collection coll = new ArrayList();
        coll.add("小李广");
        coll.add("扫地僧");
        coll.add("石破天");
        //foreach 循环其实就是使用 Iterator 迭代器来完成元素的遍历的。
        for(Object obj : coll){
            System.out.println(obj);
        }
    }

    @Test
    public void test02(){
        int[] nums = {1, 2, 3, 4, 5};
        for (int num : nums) {
            System.out.println(num);
        }
        System.out.println("-------------");
        String[] names = {"张三","李四","王五"};
        for (String name : names) {
            System.out.println(name);
        }

    }

}
•对于集合的遍历,增强 for 的内部原理其实是个 Iterator 迭代器。如下图。

•它用于遍历 Collection 和数组。通常只进行遍历元素,不要在遍历的过程中对集合元 素进行增删操作。

4、Collection 子接口 1:List

List 接口特点
•鉴于 Java 中数组用来存储数据的局限性,我们通常使用 java.util.List 替代数组
•List 集合类中 元素有序 、且 可重复 ,集合中的每个元素都有其对应的顺序索引。
•JDK API 中 List 接口的实现类常用的有: ArrayList LinkedList Vector

List 接口方法
List 除了从 Collection 集合继承的方法外,List 集合里添加了一些 根据索引 来操 作集合元素的方法。

•插入元素
        –void add(int index, Object ele):在 index 位置插入 ele 元素
        –boolean addAll(int index, Collection eles):从 index 位置开始将 eles 中的所有 元素添加进来
•获取元素
        –Object get(int index):获取指定 index 位置的元素
        –List subList(int fromIndex, int toIndex):返回从 fromIndex 到 toIndex 位置的 子集合
•获取元素索引
        –int indexOf(Object obj):返回 obj 在集合中首次出现的位置
        –int lastIndexOf(Object obj):返回 obj 在当前集合中末次出现的位置
•删除和替换元素
        –Object remove(int index):移除指定 index 位置的元素,并返回此元
        –Object set(int index, Object ele):设置指定 index 位置的元素为 ele

package Test_2.collection;


import java.util.ArrayList;
import java.util.List;

public class TestListMethod {
    public static void main(String[] args) {
        // 创建 List 集合对象
        List list = new ArrayList();
        // 往 尾部添加 指定元素
        list.add("图图");
        list.add("小美");
        list.add("不高兴");
        System.out.println(list);
        // add(int index,String s) 往指定位置添加
        list.add(1,"没头脑");
        System.out.println(list);
        // String remove(int index) 删除指定位置元素 返回被删除元素
        // 删除索引位置为 2 的元素
        System.out.println("删除索引位置为 2 的元素");
        System.out.println(list.remove(2));
        System.out.println(list);
        // String set(int index,String s)
        // 在指定位置 进行 元素替代(改)
        // 修改指定位置元素
        list.set(0, "三毛");
        System.out.println(list);
        // String get(int index) 获取指定位置元素
        // 跟 size() 方法一起用 来 遍历的
        for(int i = 0;i<list.size();i++){
            System.out.println(list.get(i));
        }
        //还可以使用增强 for
        for (Object obj : list) {
            System.out.println(obj);
        }

    }
}
注意:在 JavaSE 中 List 名称的类型有两个,一个是 java.util.List 集合接口,一个是 java.awt.List 图形界面的组件,别导错包了

List 接口主要实现类:ArrayList
•ArrayList 是 List 接口的 主要实现类
•本质上,ArrayList 是对象引用的一个”变长”数组
•Arrays.asList(…) 方法返回的 List 集合,既不是 ArrayList 实例,也不是 Vector 实 例。Arrays.asList(…) 返回值是一个固定长度的 List 集合

List 的实现类之二:LinkedList
•对于频繁的插入或删除元素的操作,建议使用 LinkedList 类,效率较高。这是由底层 采用链表(双向链表)结构存储数据决定的。

•特有方法:
–void addFirst(Object obj)
–void addLast(Object obj)
–Object getFirst()
–Object getLast()
–Object removeFirst()
–Object removeLast()

List 的实现类之三:Vector
•Vector 是一个 古老 的集合,JDK1.0 就有了。大多数操作与 ArrayList 相同,区别之处 在于 Vector 是 线程安全 的。
•在各种 List 中,最好把 ArrayList 作为默认选择 。当插入、删除频繁时,使用 LinkedList;Vector 总是比 ArrayList 慢,所以尽量避免使用。

5、Collection 子接口 2:Set

Set 接口概述
•Set 接口是 Collection 的子接口,Set 接口相较于 Collection 接口没有提供额外的方法
•Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个 Set 集合中, 则添加操作失败。
•Set 集合支持的遍历方式和 Collection 集合一样:foreach 和 Iterator。
•Set 的常用实现类有:HashSet、TreeSet、LinkedHashSet。

Set 主要实现类:HashSet

•HashSet 是 Set 接口的主要实现类,大多数时候使用 Set 集合时都使用这个实现 类。

•HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存储、查找、删除性
•HashSet 具有以下 特点
        –不能保证元素的排列顺序
        –HashSet 不是线程安全的
        –集合元素可以是 null

•HashSet 集合 判断两个元素相等的标准 :两个对象通过 hashCode() 方法得到的 哈希值相等,并且两个对象的 equals() 方法返回值为 true。

•对于存放在 Set 容器中的对象, 对应的类一定要重写 hashCode()和 equals(Object obj)方法 ,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。

•HashSet 集合中元素的无序性,不等同于随机性。这里的无序性与元素的添加位置有 关。具体来说:我们在添加每一个元素到数组中时,具体的存储位置是由元素的 hashCode()调用后返回的 hash 值决定的。导致在数组中每个元素不是依次紧密存放的,表现出一定的无序性。

HashSet 中添加元素的过程:

•第 1 步:当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法得到该对象的 hashCode 值,然后根据 hashCode 值,通过某个散 列函数决定该对象在 HashSet 底层数组中的存储位置。
•第 2 步:如果要在数组中存储的位置上没有元素,则直接添加成功。
•第 3 步:如果要在数组中存储的位置上有元素,则继续比较:
        –如果两个元素的 hashCode 值不相等,则添加成功;
        –如果两个元素的 hashCode()值相等,则会继续调用 equals()方法:
                •如果 equals()方法结果为 false,则添加成功。
                •如果 equals()方法结果为 true,则添加失败。

第 2 步添加成功,元素会保存在底层数组中。
第 3 步两种添加成功的操作,由于该底层数组的位置已经有元素 了,则会通过 链表 的方式继续链接,存储。

package Test_2.collection;

import org.junit.Test;

import java.util.HashSet;
import java.util.Objects;

class MyDate{
    private int year;
    private int month;
    private int day;

    public MyDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof MyDate)) return false;
        MyDate myDate = (MyDate) o;
        return year == myDate.year &&
                month == myDate.month &&
                day == myDate.day;
    }

    @Override
    public int hashCode() {
        return Objects.hash(year, month, day);
    }

    @Override
    public String toString() {
        return "MyDate{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }
}




public class TestHashSet {
    @Test
    public void test01(){
        HashSet set = new HashSet();
        set.add("张三");
        set.add("张三");
        set.add("李四");
        set.add("王五");
        set.add("王五");
        set.add("赵六");
        System.out.println("set = " + set);//不允许重复,无序
    }

    @Test
    public void test02(){
        HashSet set = new HashSet();
        set.add(new MyDate(2021,1,1));
        set.add(new MyDate(2021,1,1));
        set.add(new MyDate(2022,2,4));
        set.add(new MyDate(2022,2,4));
        System.out.println("set = " + set);//不允许重复,无序
    }


}
重写 hashCode() 方法的基本原则

•在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。
•当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返 回值也应相等。
•对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。

注意:如果两个元素的 equals() 方法返回 true,但它们的hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置, 但依然可以添加成功

重写 equals()方法的基本原则

•重写 equals 方法的时候一般都需要同时复写 hashCode 方法。通常参与计算 hashCode 的对象的属性也应该参与到 equals()中进行计算。
•推荐:开发中直接调用 Eclipse/IDEA 里的快捷键自动重写 equals()和 hashCode()方法 即可。
–为什么用 Eclipse/IDEA 复写 hashCode 方法,有 31 这个数字?
        首先,选择系数的时候要选择尽量大的系数。因为如果计算出来的 hash 地址 越大,所谓的 冲突 就越少,查找起来效率也会提高。(减少冲突)
        其次,31 只占用 5bits, 相乘造成数据溢出的概率较小。
        再次,31 可以 由 i*31== (i<<5)-1 来表示 , 现在很多虚拟机里面都有做相关 优化。(提高算法效率)
        最后,31 是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么 最终出来的结果只能被素数本身和被乘数还有 1 来整除! ( 减少冲突 )

练习 1: 在 List 内去除重复数字值,要求尽量简单
package Test_2.collection;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

public class TestSet_1 {
    public static List duplicateList(List list){
        HashSet set = new HashSet();
        set.addAll(list);
        return new ArrayList(set);
    }

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(new Integer(1));
        list.add(new Integer(2));
        list.add(new Integer(2));
        list.add(new Integer(4));
        list.add(new Integer(4));
        List list1 = duplicateList(list);
        for(Object integer : list1){
            System.out.println(integer);
        }
    }


}

Set 实现类之二:LinkedHashSet
•LinkedHashSet 是 HashSet 的子类,不允许集合元素重复。
•LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用 双向 链表 维护元素的次序,这使得元素看起来是以 添加顺序 保存的。
•LinkedHashSet 插入性能略低 于 HashSet,但在 迭代访问 Set 里的全部元素时有很 好的性能。

Set 实现类之三:TreeSet

•TreeSet 是 SortedSet 接口的实现类,TreeSet 可以按照添加的元素的指定的属性的 大小顺序进行遍历。
•TreeSet 底层使用 红黑树 结构存储数据

•新增的方法如下:
        –Comparator comparator()
        –Object first()
        –Object last()
        –Object lower(Object e)
        –Object higher(Object e)
        –SortedSet subSet(fromElement, toElement)
        –SortedSet headSet(toElement)
        –SortedSet tailSet(fromElement)

•TreeSet 特点:不允许重复、实现排序(自然排序或定制排序)

•TreeSet 两种排序方法: 自然排序 定制排序 。默认情况下,TreeSet 采用自然排 序。

自然排序 :TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比 较元素之间的大小关系,然后将集合元素按升序(默认情况)排列。
        •如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable 接口。
        •实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通compareTo(Object obj) 方法的返回值来比较大小。

定制排序 :如果元素所属的类没有实现 Comparable 接口,或不希望按照 升序(默认情况)的方式排列元素或希望按照其它属性大小进行排序,则考虑 使用定制排序。定制排序,通过 Comparator 接口来实现。需要重写 compare(T o1,T o2)方法。
        •利用 int compare(T o1,T o2)方法,比较 o1 和 o2 的大小:如果方法 返回正整数,则表示 o1 大于 o2;如果返回 0,表示相等;返回负 整数,表示 o1 小于 o2。
        •要实现定制排序,需要将实现 Comparator 接口的实例作为形参传 递给 TreeSet 的构造器。

•因为只有相同类的两个实例才会比较大小,所以向 TreeSet 中添加的应该是同一个 类的对象

•对于 TreeSet 集合而言,它判断 两个对象是否相等的唯一标准 是:两个对象通过 compareTo(Object obj) compare(Object o1,Object o2) 方法比较返回 值。返回值为 0,则认为两个对象相等。

package Test_2.collection;

import org.junit.Test;

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

public class TreeSetTest {

    /*
     * 自然排序:针对 String 类的对象
     * */
    @Test
    public void test01(){
        TreeSet set = new TreeSet();
        set.add("MM");
        set.add("CC");
        set.add("AA");
        set.add("DD");
        set.add("ZZ");
        //set.add(123); //报 ClassCastException 的异常
        Iterator iterator = set.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }

    /*
     * 自然排序:针对 User 类的对象
     * */
    @Test
    public void test02(){
        TreeSet set = new TreeSet();
        set.add(new User("Tom",12));
        set.add(new User("Rose",23));
        set.add(new User("Jerry",2));
        set.add(new User("Eric",18));
        set.add(new User("Tommy",44));
        set.add(new User("Jim",23));
        set.add(new User("Maria",18));
        //set.add("Tom");
        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
        System.out.println(set.contains(new User("Jim", 23))); //true
    }


    /*
     * 定制排序
     * */
    @Test
    public void test03() {
        //按照 User 的姓名的从小到大的顺序排列
        Comparator comparator = new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof User && o2 instanceof User){
                    User u1 = (User)o1;
                    User u2 = (User)o2;
                    return u1.name.compareTo(u2.name);
                }
                throw new RuntimeException("输入的类型不匹配");
            }
        };
        TreeSet set = new TreeSet(comparator);
        set.add(new User("Tom",12));
        set.add(new User("Rose",23));
        set.add(new User("Jerry",2));
        set.add(new User("Eric",18));
        set.add(new User("Tommy",44));
        set.add(new User("Jim",23));
        set.add(new User("Maria",18));
        //set.add(new User("Maria",28));
        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }



    }

}





class User implements Comparable{
        String name;
        int age;

    public User() {
    }

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

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

    /*
    举例:按照 age 从小到大的顺序排列,如果 age 相同,则按照 name 从大到小的顺序排列
    * */
    @Override
    public int compareTo(Object o) {
        if(this == o){
            return 0;
        }
        if(o instanceof User){
            User user = (User)o;
            int value = this.age - user.age;
            if(value != 0){
                return value;
            }
            return -this.name.compareTo(user.name);
        }
        throw new RuntimeException("输入的类型不匹配");
    }
}

6、Map接口

现实生活与开发中,我们常会看到这样的一类集合:用户 ID 与账户信息、学生姓名与考试成绩、IP 地址与主机名等,这种一一对应的关系,就称作映射。 Java 提供了专门的集合框架用来存储这种映射关系的对象,即 java.util.Map 接口。

Map 接口概述
•Map 与 Collection 并列存在。用于保存具有 映射关系 的数据:key-value
        –Collection 集合称为单列集合,元素是孤立存在的(理解为单身)。
        –Map 集合称为双列集合,元素是成对存在的(理解为夫妻)。
•Map 中的 key 和 value 都可以是任何引用类型的数据。但常用 String 类作为 Map 的“键”。
•Map 接口的常用实现类: HashMap LinkedHashMap TreeMap `Properties 。其中,HashMap 是 Map 接口使用 频率最高 的实现类。

 Map 中 key-value 特点

这里主要以 HashMap 为例说明。HashMap 中存储的 key、value 的特点如下:

•Map 中的 key Set 来存放 不允许重复 ,即同一个 Map 对象所对应的类,须 重写 hashCode()和 equals()方法

•key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到唯一的、确定 的 value,不同 key 对应的 value 可以重复 。value 所在的类要重写 equals()方法。
•key 和 value 构成一个 entry。所有的 entry 彼此之间是 无序的 不可重复的

Map 接口的常用方法

添加、修改操作:
        –Object put(Object key,Object value):将指定 key-value 添加到(或修改)当前 map 对象中
        
        –void putAll(Map m):将 m 中的所有 key-value 对存放到当前 map 中

删除操作:
        –Object remove(Object key):移除指定 key 的 key-value 对,并返回 value
        –void clear():清空当前 map 中的所有数据

元素查询的操作:
        –Object get(Object key):获取指定 key 对应的 value
        –boolean containsKey(Object key):是否包含指定的 key
        –boolean containsValue(Object value):是否包含指定的 value
        –int size():返回 map 中 key-value 对的个数
        –boolean isEmpty():判断当前 map 是否为空
        –boolean equals(Object obj):判断当前 map 和参数对象 obj 是否相等

元视图操作的方法:
        –Set keySet():返回所有 key 构成的 Set 集合
        –Collection values():返回所有 value 构成的 Collection 集合
        –Set entrySet():返回所有 key-value 对构成的 Set 集合
package Test_2.collection;

import org.junit.Test;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class TestMapMethod {
    @Test
    public void test01(){
        HashMap map = new HashMap();
        map.put("黄晓明", "杨颖");
        map.put("李晨", "李小璐");
        map.put("李晨", "范冰冰");
        map.put("邓超", "孙俪");
        System.out.println(map);
        System.out.println(map.remove("黄晓明"));
        System.out.println(map);
        System.out.println(map.get("邓超"));
        System.out.println(map.get("黄晓明"));
    }

    @Test
    public void test02(){
        HashMap map = new HashMap();
        map.put("许仙", "白娘子");
        map.put("董永", "七仙女");
        map.put("牛郎", "织女");
        map.put("许仙", "小青");
        System.out.println("所有的 key:");
        Set keySet = map.keySet();
        for(Object key : keySet){
            System.out.println(key);
        }

        System.out.println("所有的value:");
        Collection values = map.values();
        System.out.println(values);
        for(Object value : values){
            System.out.println(value);
        }

        System.out.println("所有的映射关系:");
        Set entrySet = map.entrySet();
        System.out.println(entrySet);
        for(Object mapping : entrySet){
            Map.Entry entry = (Map.Entry) mapping;
            System.out.println(entry.getKey() + "->" + entry.getValue());
        }
    }
}
Map 的主要实现类:HashMap
•HashMap 是 Map 接口 使用频率最高 的实现类。
•HashMap 是线程不安全的。允许添加 null 键和 null 值。
•存储数据采用的哈希表结构,底层使用 一维数组 + 单向链表 + 红黑树 进行 key-value 数据的存储。与 HashSet 一样,元素的存取顺序不能保证一致。
•HashMap 判断两个 key 相等的标准 是:两个 key 的 hashCode 值相等,通过 equals() 方法返回 true。
•HashMap 判断两个 value 相等的标准 是:两个 value 通过 equals() 方法返回 true。

Map 实现类之二:LinkedHashMap
•LinkedHashMap 是 HashMap 的子类
•存储数据采用的哈希表结构+链表结构,在 HashMap 存储结构的基础上,使用了一对 双向链表 记录添加元素的先后顺序 ,可以保证遍历元素时,与添加的顺序一致。
•通过哈希表结构可以保证键的唯一、不重复,需要键所在类重写 hashCode()方法、 equals()方法。

package Test_2.collection;

import org.junit.Test;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

public class TestLinkedHashMap {
    @Test
    public void test1(){
        LinkedHashMap map = new LinkedHashMap();
        map.put("王五", 13000.0);
        map.put("张三", 10000.0);
        //key 相同,新的 value 会覆盖原来的 value
        //因为 String 重写了 hashCode 和 equals 方法
        map.put("张三", 12000.0);
        map.put("李四", 14000.0);
        //HashMap 支持 key 和 value 为 null 值
        String name = null;
        Double salary = null;
        map.put(name, salary);

        System.out.println(map);
        
        Set entrySet = map.entrySet();
        
        for(Object obj : entrySet){
            System.out.println(obj);
        }
        System.out.println("------------------");
        for(Object obj : entrySet){
            Map.Entry entry = (Map.Entry) obj;
            System.out.println(entry);
        }

    }
}

Map 实现类之三:TreeMap
TreeMap 存储 key-value 对时,需要根据 key-value 对进行排序。TreeMap 可以保 证所有的 key-value 对处于 有序状态
•TreeSet 底层使用 红黑树 结构存储数据
•TreeMap 的 Key 的排序:
        –自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所 有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
        – 定制排序 :创建 TreeMap 时,构造器传入一个 Comparator 对象,该对 象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口
•TreeMap 判断 两个 key 相等的标准 :两个 key 通过 compareTo()方法或者 compare() 方法返回0。

package Test_2.collection;

import org.junit.Test;

import java.util.Comparator;
import java.util.Set;
import java.util.TreeMap;

public class TestTreeMap {
    /*
     * 自然排序举例
     * */
    @Test
    public void test1(){
        TreeMap map = new TreeMap();
        map.put("DD",26);
        map.put("CC",45);
        map.put("GG",89);
        map.put("MM",78);
        map.put("JJ",99);

        Set entryset = map.entrySet();
        for(Object entry : entryset){
            System.out.println(entry);
        }

        System.out.println("--------------------");
        TreeMap map1 = new TreeMap();
        map1.put(new User("Tom",12),67);
        map1.put(new User("Rose",23),"87");
        map1.put(new User("Jerry",2),88);
        map1.put(new User("Eric",18),45);
        map1.put(new User("Tommy",44),77);
        map1.put(new User("Jim",23),88);
        map1.put(new User("Maria",18),34);
        Set entryset1 = map1.entrySet();
        for(Object o : entryset1){
            System.out.println(o);
        }

    }

    /*
     * 定制排序
     *
     * */
    @Test
    public void test2(){
        //按照 User 的姓名的从小到大的顺序排列

        TreeMap map = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof User && o2 instanceof User){
                    User u1 = (User)o1;
                    User u2 = (User)o2;
                    return u1.name.compareTo(u2.name);
                }
                throw new RuntimeException("输入的类型不匹配");
            }
        });
        map.put(new User("Tom",12),67);
        map.put(new User("Rose",23),"87");
        map.put(new User("Jerry",2),88);
        map.put(new User("Eric",18),45);
        map.put(new User("Tommy",44),77);
        map.put(new User("Jim",23),88);
        map.put(new User("Maria",18),34);
        Set entrySet = map.entrySet();
        for(Object entry : entrySet){
            System.out.println(entry);
        }
    }
}


class User implements Comparable{
        String name;
        int age;

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

    public User() {
    }

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

    /*
     举例:按照 age 从小到大的顺序排列,如果 age 相同,则按照 name 从大到小的顺序排列
      * */
    @Override
    public int compareTo(Object o) {
        if(this == o){
            return 0;
        }

        if(o instanceof User){
            User user = (User)o;
            int value = this.age - user.age;
            if(value != 0){
                return value;
            }
            return -this.name.compareTo(user.name);
        }
        throw new RuntimeException("输入的类型不匹配");
    }
}
Map 实现类之四:Hashtable
•Hashtable 是 Map 接口的 古老实现类 ,JDK1.0 就提供了。不同于 HashMap, Hashtable 是线程安全的。
•Hashtable 实现原理和 HashMap 相同,功能相同。底层都使用哈希表结构(数组+单 向链表),查询速度快。
•与 HashMap 一样,Hashtable 也不能保证其中 Key-Value 对的顺序
•Hashtable 判断两个 key 相等、两个 value 相等的标准,与 HashMap 一致。
•与 HashMap 不同,Hashtable 不允许使用 null 作为 key 或 value。

Map 实现类之五:Properties
•Properties 类是 Hashtable 的子类,该对象用于处理属性文件
•由于属性文件里的 key、value 都是字符串类型,所以 Properties 中要求 key 和 value 都是字符串类型
•存取数据时,建议使用 setProperty(String key,String value)方法和 getProperty(String key)方法

7、Collections 工具类

参考操作数组的工具类:Arrays,Collections 是一个操作 Set、List 和 Map 等集合的工具类。

Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法(均为 static 方法):
排序操作:
•reverse(List):反转 List 中元素的顺序
•shuffle(List):对 List 集合元素进行随机排序
•sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行 排序
•swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
查找
•Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
•Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合 中的最大元素         
•Object min(Collection):根据元素的自然顺序,返回给定集合中的最小元素
•Object min(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合 中的最小元素
•int binarySearch(List list,T key)在 List 集合中查找某个元素的下标,但是 List 的元素必 须是 T 或 T 的子类对象,而且必须是可比较大小的,即支持自然排序的。而且集合也 事先必须是有序的,否则结果不确定。
•int binarySearch(List list,T key,Comparator c)在 List 集合中查找某个元素的下标,但是 List 的元素必须是 T 或 T 的子类对象,而且集合也事先必须是按照 c 比较器规则进行 排序过的,否则结果不确定。
•int frequency(Collection c,Object o):返回指定集合中指定元素的出现次数
复制、替换
•void copy(List dest,List src):将 src 中的内容复制到 dest 中
•boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象 的所有旧值
•提供了多个 unmodifiableXxx()方法,该方法返回指定 Xxx 的不可修改的视图。
添加
•boolean addAll(Collection c,T... elements)将所有指定元素添加到指定 collection 中。
同步
•Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成 线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题:

package Test_2.collection;

import org.junit.Test;

import java.util.*;

public class TestCollections {
    @Test
    public void test01(){
        Collection<Object> coll = new ArrayList<>();
        Collections.addAll(coll,"hello","Java");
        Collections.addAll(coll, 1234, 1, 2);
        System.out.println(coll);

        Collection<String> coll2 = new ArrayList<>();
        Collections.addAll(coll2, "hello ", "world");
        System.out.println(coll2);
    }

    @Test
    public void test02(){
        List<Man> list = new ArrayList<>();
        list.add(new Man("张三", 16));
        list.add(new Man("李四", 17));
        list.add(new Man("王五", 18));

        /*
         * Man max = Collections.max(list);//要求 Man 实现 Comparable 接口,或者父类实现
         */

        Man max = Collections.max(list, new Comparator<Man>() {
            @Override
            public int compare(Man o1, Man o2) {
                return o1.getAge()-o2.getAge();
            }
        });
        System.out.println(max);
    }

    

}

class Man implements Comparable{
    String name;
    int age;

    public Man(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;
    }

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

    @Override
    public int compareTo(Object o) {
        return 0;
    }

}

十五、泛型

1、泛型概述

Java中的泛型,就类似于生活场景中出现的用于分类的那种标签。

泛型的引入

在 Java 中,我们在声明方法时,当在完成方法功能时如果有 未知的数据 需要参与,这些未知的数据需要在调用方法时才能确定,那么我们把这样的数据通过 形参 表示。在方法体中,用这个形参名来代表那个未知的数据,而调用者在调用时,对应的传入实参 就可以了。

受以上启发,JDK1.5 设计了泛型的概念。泛型即为“ 类型参数 ”,这个类型参数在声明它的类、接口或方法中,代表未知的某种通用类型。

所谓泛型,就是允许在定义类、接口时通过一个 标识 表示类中某个 性的类型 或者是某个方法的 返回值或参数的类型 。这个类型参数将在使 用时(例如,继承或实现这个接口、创建对象或调用方法时)确定 (即传入实际的类型参数,也称为类型实参)。

2、集合中使用泛型

集合中没有使用泛型时:

集合中使用泛型时:

Java 泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生 ClassCastException 异常。即,把不安全的因素在编译期间就排除了,而不是运行期;既然通过了编译,那么类型一定是符合要求的,就避免了类型转换。

package Test_2.generic;

import org.junit.Test;

import java.util.*;

public class genericTest1 {
    //泛型在 List 中的使用
    @Test
    public void test1(){
        //举例:将学生成绩保存在 ArrayList 中
        //标准写法:
        //ArrayList<Integer> list = new ArrayList<Integer>();
        //jdk7 的新特性:类型推断
        ArrayList<Integer> list = new ArrayList<>();
        list.add(56);//自动装箱
        list.add(76);
        list.add(88);
        list.add(89);
        //当添加非 Integer 类型数据时,编译不通过
        //list.add("Tom");//编译报错
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()){
            //不需要强转,直接可以获取添加时的元素的数据类型
            Integer score = iterator.next();
            System.out.println(score);
        }
    }

    //泛型在 Map 中的使用
    @Test
    public void test2(){
        HashMap<String, Integer> map = new HashMap<>();

        map.put("Tom",67);
        map.put("Jim",56);
        map.put("Rose",88);
        //编译不通过
        // map.put(67,"Jack");

        //遍历 key 集
        Set<String> keySet = map.keySet();
        for(String str : keySet){
            System.out.println(str);
        }

        //遍历 value 集
        Collection<Integer> values = map.values();
        for(Integer val : values){
            System.out.println(val);
        }

        //遍历 entry 集
        Set<Map.Entry<String,Integer>> entrySet = map.entrySet();
        for(Map.Entry entry : entrySet){
            System.out.println(entry);
        }
    }
}

3、比较器中使用泛型

package com . atguigu . generic ;
public class Circle {
private double radius ;
public Circle ( double radius ) {
super ();
this . radius = radius ;
}
public double getRadius () {
return radius ;
}
public void setRadius ( double radius ) {
this . radius = radius ;
}
@Override
public String toString () {
return "Circle [radius=" + radius + "]" ;
}
}
使用泛型之前:
package com . atguigu . generic ;
import java . util . Comparator ;
class CircleComparator implements Comparator {
@Override
public int compare ( Object o1 , Object o2 ) {
// 强制类型转换
Circle c1 = ( Circle ) o1 ;
Circle c2 = ( Circle ) o2 ;
return Double . compare ( c1 . getRadius (), c2 . getRadius ()); }
}
// 测试:
public class TestNoGeneric {
public static void main ( String [] args ) {
CircleComparator com = new CircleComparator ();
System . out . println ( com . compare ( new Circle ( 1 ), new Circle
( 2 )));
System . out . println ( com . compare ( " 1" , " 2" )); // 运行时异常: Cl
assCastException
}
}
使用泛型之后:
package com . atguigu . generic ;
import java . util . Comparator ;
class CircleComparator1 implements Comparator < Circle > {
@Override
public int compare ( Circle o1 , Circle o2 ) {
// 不再需要强制类型转换,代码更简洁
return Double . compare ( o1 . getRadius (), o2 . getRadius ());
}
}
// 测试类
public class TestHasGeneric {
public static void main ( String [] args ) {
CircleComparator1 com = new CircleComparator1 ();
System . out . println ( com . compare ( new Circle ( 1 ), new Circle
( 2 )));
//System.out.println(com.compare(" 1", " 2"));
// 编译错误,因为 " 1", " 2" 不是 Circle 类型,是 String 类型,编译
器提前报错,
// 而不是冒着风险在运行时再报错。
}
}
相关使用说明
• JDK7.0 时,有新特性,可以简写为: List list = new ArrayList<>(); //类型推断
• 泛型,也称为泛型参数,即参数的类型,只能使用引用数据类型进行赋值。(不能使用基本数据类型,可以使用包装类替换)
• 集合声明时,声明泛型参数。在使用集合时,可以具体指明泛型的类型。一旦指明,类或接口内部,凡是使用泛型参数的位置,都指定为具体的参数类型。如果没有指明的话,看做是 Object 类型。

4、自定义泛型结构

泛型的基础说明
1、<类型>这种语法形式就叫泛型。
  • 尖括号 <> 中的 泛型标识被称作是类型参数,用于指代任何数据类型。

  • 泛型标识是任意设置的(如果你想可以设置为 Hello都行),Java 常见的泛型标识以及其代表含义如下:

  T :代表一般的任何类。
  E :代表 Element 元素的意思,或者 Exception 异常的意思。
  K :代表 Key 的意思。
  V :代表 Value 的意思,通常与 K 一起配合使用。
  S :代表 Subtype 的意思
2、 在哪里可以声明类型变量<T>

• 声明类或接口时,在类名或接口名后面声明泛型类型,我们把这样的类或接口称为 型类 泛型接口
【修饰符】 class 类名 < 类型变量列表 > extends 父类】 【 implements 接口 们】 {
}
【修饰符】 interface 接口名 < 类型变量列表 > implements 接口们】 {
}
// 例如:
public class ArrayList < E >
public interface Map < K , V >{
... .
}
• 声明方法时,在【修饰符】与返回值类型之间声明类型变量,我们把声明了类型变量的方法,称为泛型方法。
[ 修饰符 ] < 类型变量列表 > 返回值类型 方法名 ([ 形参列表 ])[ throws 异常列表 ]{
//...
}
// 例如: java.util.Arrays 类中的
public static < T > List < T > asList ( T ... a ){
... .
}
自定义泛型类或泛型接口
当我们在类或接口中定义某个成员时,该成员的相关类型是不确定的,而这个类型需要在使用这个类或接口时才可以确定,那么我们可以使用泛型类、泛型接口。
说明
① 我们在声明完自定义泛型类以后,可以在类的内部(比如:属性、方法、构造器中)使用类的泛型。
② 我们在创建自定义泛型类的对象时,可以指明泛型参数类型。一旦指明,内部凡是使用类的泛型参数的位置,都具体化为指定的类的泛型类型。
③ 如果在创建自定义泛型类的对象时,没有指明泛型参数类型,那么泛型将被擦除,泛型对应的类型均按照 Object 处理,但不等价于 Object。
• 经验:泛型要使用一路都用。要不用,一路都不要用。
④ 泛型的指定中必须使用引用数据类型。不能使用基本数据类型,此时只能使用包装类替换。
⑤ 除创建泛型类对象外,子类继承泛型类时、实现类实现泛型接口时,也可以确定泛型结构中的泛型参数。
如果我们在给泛型类提供子类时,子类也不确定泛型的类型,则可以继续使用泛型参数。
我们还可以在现有的父类的泛型参数的基础上,新增泛型参数。
注意
① 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如: <E1,E2,E3>
② JDK7.0 开始,泛型的简化操作:ArrayList flist = new ArrayList<>();
③ 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
④ 不能使用 new E[]。但是可以:E[] elements = (E[])new Object[capacity];
参考:ArrayList 源码中声明:Object[] elementData,而非泛型参数类型数组。
⑤ 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,但不可以在静态方法中使用类的泛型。
⑥ 异常类不能是带泛型的。
举例
举例 1:
class Person<T> {
 // 使用 T 类型定义变量
 private T info;
 // 使用 T 类型定义一般方法
 public T getInfo() {
 return info;
 }
 public void setInfo(T info) {
 this.info = info;
 }
 // 使用 T 类型定义构造器
 public Person() {
 }
 public Person(T info) {
 this.info = info;
 }
 // static 的方法中不能声明泛型
 //public static void show(T t) {
 //
 //}
 // 不能在 try-catch 中使用泛型定义
 //public void test() {
 //try {
 //
 //} catch (MyException<T> ex) {
 //
 //}
 //}
}
举例 2:
class Father<T1, T2> {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son1 extends Father {// 等价于 class Son extends Father<Object,O
bject>{
}
// 2)具体类型
class Son2 extends Father<Integer, String> {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2> extends Father<T1, T2> {
}
// 2)部分保留
class Son4<T2> extends Father<Integer, T2> {
}
举例 3:
class Father<T1, T2> {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son<A, B> extends Father{//等价于 class Son extends Father<Objec
t,Object>{
}
// 2)具体类型
class Son2<A, B> extends Father<Integer, String> {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2, A, B> extends Father<T1, T2> {
}
// 2)部分保留
class Son4<T2, A, B> extends Father<Integer, T2> {
}
自定义泛型方法
如果我们定义类、接口时没有使用<泛型参数>,但是某个方法形参类型不确定时,这个方法可以单独定义<泛型参数>。
说明
• 泛型方法的格式:
[ 访问权限 ] < 泛型 > 返回值类型 方法名 ([ 泛型标识 参数名称 ]) [ 抛出的异常 ]{
}
• 方法,也可以被泛型化,与其所在的类是否是泛型类没有关系。
• 泛型方法中的泛型参数在方法被调用时确定。
• 泛型方法可以根据需要,声明为 static 的。
举例
举例 1:
public class DAO {
 public <E> E get(int id, E e) {
 E result = null;
 return result;
 }
}
举例 2:
public static <T> void fromArrayToCollection(T[] a, Collection<T> c)
{
 for (T o : a) {
 c.add(o);
 }
}
public static void main(String[] args) {
 Object[] ao = new Object[100];
 Collection<Object> co = new ArrayList<Object>();
 fromArrayToCollection(ao, co);
 String[] sa = new String[20];
 Collection<String> cs = new ArrayList<>();
 fromArrayToCollection(sa, cs);
 Collection<Double> cd = new ArrayList<>();
 // 下面代码中 T 是 Double 类,但 sa 是 String 类型,编译错误。
 // fromArrayToCollection(sa, cd);
 // 下面代码中 T 是 Object 类型,sa 是 String 类型,可以赋值成功。
 fromArrayToCollection(sa, co);
}

举例3:

class MyArrays {
 public static <T> void sort(T[] arr){
 for (int i = 1; i < arr.length; i++) {
 for (int j = 0; j < arr.length-i; j++) {
 if(((Comparable<T>)arr[j]).compareTo(arr[j+1])>0){
 T temp = arr[j];
 arr[j] = arr[j+1];
 arr[j+1] = temp;
             }
         }
     }
  }
}
public class MyArraysTest {
 public static void main(String[] args) {
 int[] arr = {3,2,5,1,4};
// MyArrays.sort(arr);//错误的,因为 int[]不是对象数组
 String[] strings = {"hello","java","song"};
 MyArrays.sort(strings);
 System.out.println(Arrays.toString(strings));
 Circle[] circles = {new Circle(2.0),new Circle(1.2),new Circl
e(3.0)};
 MyArrays.sort(circles); //编译通过,运行报错,因为 Circle 没有实现 Comparable 接口
 }
}

5、泛型在继承上的体现

如果 B 是 A 的一个子类型(子类或者子接口),而 G 是具有泛型声明的类或接口,G 并不是 G 的子类型!
比如:String 是 Object 的子类,但是 List 并不是 List 的子类。
public void testGenericAndSubClass() {
 Person[] persons = null;
 Man[] mans = null;
 //Person[] 是 Man[] 的父类
 persons = mans;
 Person p = mans[0];
 // 在泛型的集合上
 List<Person> personList = null;
 List<Man> manList = null;
 //personList = manList;(报错)
}

6、通配符的使用

当我们声明一个变量/形参时,这个变量/形参的类型是一个泛型类或泛型接口,例如:Comparator 类型,但是我们仍然无法确定这个泛型类或泛型接口的类型变量的具体类型,此时我们考虑使用类型通配符 <?> 
通配符的理解
使用类型通配符:?
比如: List<?> Map<?,?>
List<?> List<String> List<Object> 等各种泛型 List 的父类。
通配符的读与写
写操作:
将任意元素加入到其中不是类型安全的:
Collection <?> c = new ArrayList < String >();
c . add ( new Object ()); // 编译时错误
因为我们不知道 c 的元素类型,我们不能向其中添加对象。add 方法有类型参数 E 作为集合的元素类型。我们传给 add 的任何参数都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去。
唯一可以插入的元素是 null,因为它是所有引用类型的默认值。
读操作:
另一方面,读取 List<?>的对象 list 中的元素时,永远是安全的,因为不管 list 的真实类型是什么,它包含的都是 Object。
举例 1:
public class TestWildcard {
 public static void m4(Collection<?> coll){
 for (Object o : coll) {
 System.out.println(o);
         }
     }
}
举例 2:
public static void main(String[] args) {
 List<?> list = null;
 list = new ArrayList<String>();
 list = new ArrayList<Double>();
 // list.add(3);//编译不通过
 list.add(null);
 List<String> l1 = new ArrayList<String>();
 List<Integer> l2 = new ArrayList<Integer>();
 l1.add("尚硅谷");
 l2.add(15);
 read(l1);
 read(l2);
}
public static void read(List<?> list) {
 for (Object o : list) {
 System.out.println(o);
 }
}
使用注意点
注意点 1:编译错误:不能用在泛型方法声明上,返回值类型前面<>不能使用?
public static <?> void test ( ArrayList <?> list ){
}
注意点 2:编译错误:不能用在泛型类的声明上
class GenericTypeClass <?>{
}
注意点 3:编译错误:不能用在创建对象上,右边属于创建集合对象
ArrayList <?> list2 = new ArrayList <?>();
有限制的通配符
<?>
        – 允许所有泛型的引用调用
• 通配符指定上限:<? extends / 接口 >
        – 使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
• 通配符指定下限:<? super / 接口 >
        – 使用时指定的类型必须是操作的类或接口,或者是操作的类的父类或接口的父接口,即>=
• 说明:
<? extends Number > //( 无穷小 , Number]
// 只允许泛型为 Number Number 子类的引用调用
<? super Number > //[Number , 无穷大 )
// 只允许泛型为 Number Number 父类的引用调用
<? extends Comparable >
// 只允许泛型为实现 Comparable 接口的实现类的引用调用
举例 1
class Creature{}
class Person extends Creature{}
class Man extends Person{}
class PersonTest {
 public static <T extends Person> void test(T t){
 System.out.println(t);
 }
 public static void main(String[] args) {
 test(new Person());
 test(new Man());
 //The method test(T) in the type PersonTest is not 
 //applicable for the arguments (Creature)
 test(new Creature());
 }
}
举例 2:
public static void main(String[] args) {
 Collection<Integer> list1 = new ArrayList<Integer>();
 Collection<String> list2 = new ArrayList<String>();
 Collection<Number> list3 = new ArrayList<Number>();
 Collection<Object> list4 = new ArrayList<Object>();
 
 getElement1(list1);
 getElement1(list2);//报错
 getElement1(list3);
 getElement1(list4);//报错
 
 getElement2(list1);//报错
 getElement2(list2);//报错
 getElement2(list3);
 getElement2(list4);
 
}
// 泛型的上限:此时的泛型?,必须是 Number 类型或者 Number 类型的子类
public static void getElement1(Collection<? extends Number> col
l){}
// 泛型的下限:此时的泛型?,必须是 Number 类型或者 Number 类型的父类
public static void getElement2(Collection<? super Number> coll)
{}
举例 3:
public static void printCollection1(Collection<? extends Person> coll) {
 //Iterator 只能用 Iterator<?>或 Iterator<? extends Person>.why?
 Iterator<?> iterator = coll.iterator();
 while (iterator.hasNext()) {
 Person per = iterator.next();
 System.out.println(per);
 }
}
public static void printCollection2(Collection<? super Person > coll) {
 //Iterator 只能用 Iterator<?>或 Iterator<? super Person>.why?
 Iterator<?> iterator = coll.iterator();
 while (iterator.hasNext()) {
 Object obj = iterator.next();
 System.out.println(obj);
 }
}

风控IV、AUC、KS python计算代码 df_temp:数据集,Y:是否逾期标签,feas_list:需要计算IV的 变量。2、 按月和不同Y 下的 IV计算。 阅读详情

相关推荐

ESP32-CAM与Thonny的完美搭配:从烧录到通信的完整流程

本文详细介绍了如何将ESP32-CAM与Thonny IDE完美结合,从固件烧录到通信测试的完整流程。通过硬件连接、MicroPython固件烧录、Thonny环境配置以及摄像头功能开发等步骤,帮助开发者快速搭建高效的物联网开发环境,实现图像采集与无线通信功能。

tomato的博客 1090

硅谷JavaSE笔记(五)

JavaSE反射,Java8新特性

guanze1的博客 580

Intel-Visual-Fortran安装+使用详细讲解.doc

Intel-Visual-Fortran安装+使用详细讲解.doc

JavaSE目录(硅谷学习笔记)

跳转到总目录 01、版本一:视频教程笔记 这个版本是我跟着视频教程做的笔记 教程来源:java基础到高级_零基础自学Java硅谷–宋红康教程视频地址:https://www.bilibili.com/video/BV1ny4y1Y7CWMarkdown版笔记下载:h...

scy的技术博客 4245

硅谷JavaSE笔记(二)

笔记为B站硅谷Java入门视频教程(在线答疑+Java面试真题)笔记,是笔者对于已经学过的大二Java课程的查缺补漏及巩固。部分C语言学过的基本语法将跳过。异常:在Java语言中,将程序执行中发生的不正常情况称为“异常”(开发过程中的语法错误和逻辑错误不是异常)Java程序在执行过程中所发生的异常事件可分为两类:>Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError和OOM。一般不编写针对性的代码进行处理。

guanze1的博客 707

硅谷JavaSE笔记

对第一个java程序进行总结java程序编写-编译-运行的过程编写: 编写java文件编译: javac命令编译java文件运行: java命令运行字节码文件在一个java源文件可以声明多个class,但是,只能最多有一个类声明为public。并且要求声明为public的类的类名必须与源文件名相同。程序的入口是main方法,格式是固定的先输出数据,然后换行只输出数据每一行执行语句都以“;”结束。编译的过程:编译以后,会生成一个或多个字节码文件。

qq_44038801的博客 714

硅谷JavaWEB学习笔记1(自整理,自用)

JavaWEB学习笔记 1、HTML 1.1、B/S软件的结构 Javase C/S结构 Client Server客户端----->服务器 B/S结构 Brower Server结构 浏览器----->服务器 1.2、前端的开发流程 美术实现---------------------------------------------->前端工程师------------------------------------------->java后台 网页设计师根据需求设计

qq_43130076的博客 6480

硅谷JavaSE笔记(一)

Java基础,到面向对象部分

guanze1的博客 866

硅谷经典Java面试题(第1季)

01_硅谷_JavaSE面试题:自增变量 运行结果为i=4;j=1;k=11. 分析: 参考资料: ①Eclipse打开字节码指令:选择Text.class文件,点击Open With->Class File Viewer ②i++和++i:i++,先运算,后加一;++i,先加一,后运算。 ③https://mbd.baidu.com/ma/landingpage?t=smartapp...

qq_42466244的博客 8285

硅谷JavaSE笔记(四)

笔记为B站硅谷Java入门视频教程(在线答疑+Java面试真题)笔记,是笔者对于已经学过的大二Java课程的查缺补漏及巩固。部分C语言学过的基本语法将跳过。Java 提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在 Java 的本机安装系统里,由 JVM 进行控制。并且 Java 实现了一个跨平台的 网络库,程序员面对的是一个统一的网络编程环境。软件架构C/S 架构:全称为 Client/Server 结构,是指客户端和服务器结构。

guanze1的博客 619

笔记硅谷案例代码大全资源集锦

笔记硅谷案例代码大全资源集锦 去发现同类优质开源项目:https://gitcode.com/ 欢迎来到笔记硅谷案例代码大全资源集锦!这是一个全面的案例代码集合,涵盖了前端、后端以及全栈开发所需的多项技术。以下是本资源集锦的详细内容介绍: 资源包含 前端基础教程 前端html+css零基础教程:适合初学者的html和css入门教程,助您快速掌握前端基础。 2023最新前端开发html5+cs...

gitblog_06773的博客 941

硅谷Java视频教程_Java面试题第一季

互联网寒冬来临,打铁还需自身硬,嘟嘟这里分享一套最新Java面试题视频,祝你更上一层楼。硅谷Java学科面试题正式出炉,且将持续更新!视频针对企业高频面试技术题目,涵盖...

嘟爷java超神学堂 1254

笔记硅谷案例代码大全资源集锦:全面技术资源助力编程学习

笔记硅谷案例代码大全资源集锦:全面技术资源助力编程学习 去发现同类优质开源项目:https://gitcode.com/ 项目核心功能/场景 编程学习者的全方位技术资源库。 项目介绍 在编程学习的海洋中,是否经常感到缺少一个全面、系统的学习资源?笔记硅谷案例代码大全资源集锦正是为此而生,它是一个集结了前端、后端及全栈开发所需多项技术的案例代码集合。无论是初涉编程的新手,还是希望提升技能的资深开...

gitblog_06724的博客 434

硅谷JavaSE基础

1.3第一个JAVA应用程序 1.3.1 JAVA程序框架 public class 类名{ public static void main(String[] args){ //命令语句 } } 1.3.2 JAVA程序的开发步骤 步骤: 将 Java 代码编写到扩展名为 .ja...

MObaiYUhai的博客 547

硅谷spring笔记

Spring框架介绍 Spring是一个轻量开源的JavaEE的框架 Spring框架可以解决企业应用开发的复杂性 Spring中的两个核心:IOC和AOP (1)IOC:控制反转,把创建对象过程交给spring进行管理 (2)AOP:面相切面,不修改源代码的情况下进行功能增强 Spring特点 方便解耦,简化开发 Aop编程支持 方便程序测试 方便和其他框架进行整合 方便进行事务操作 降低API开发难度 实例 编写一个User类,之后使用Spring配置文件的方式创建对象 配置文件

m0_51654746的博客 1520

Javase硅谷笔记

自动类型提升: 结论:当容量小的数据类型的变量与容量大的数据类型的变量做运算时,结果自动提升为容量大的数据类型。 bytechar 、short –>int _-> 1ong –> float -> double 9 10 特别的:当byte、char、 short种类型的变量做运算时,结果为int型 ...

weixin_52270713的博客 477

硅谷JavaSE基础课堂笔记

面向对象的重点是类的设计->类的成员设计属性:该类事物的状态信息,对应成员变量也对应field行为:要做什么样的操作,对应成员方法也对应method面向对象完成具体功能的步流程创建类,并设计类的内部成员(属性,方法)创建类的对象,比如,Phone p1 = new Phone();通过对象调用内部属性和方法,完成相关功能。虚拟机栈(栈):用于存放方法中声明的变量。堆:new出来的结构。比如:数组实体,对象实体,包括对象中的属性。方法区:存放类的模板,比如:Phone类的模板。

weixin_75181956的博客 657

Spring 源码系列(23): SpringApplication.run() 启动全流程

本文深入解析了SpringApplication.run()的核心流程,通过源码拆解揭示了从启动到容器就绪的完整生命周期。文章重点分析了个关键阶段:prepareEnvironment的配置加载逻辑(系统属性>环境变量>配置文件)、createApplicationContext的容器类型推断机制(基于classpath判断Web/Reactive/普通应用),以及callRunners的启动后回调特性(按@Order执行初始化任务)。同时澄清了常见误区,如内嵌Tomcat实际在refresh阶段的onR

不才不才不不才的博客 427
上一篇: 尚硅谷JavaSE笔记(一)
下一篇: 尚硅谷JavaSE笔记(四)
guanze1
博客等级 码龄6年 17粉丝 11原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值