集合与泛型

一、复习回顾

问:集合分为两大类?

java 复制代码
Collection:存储一组对象
Map:存储一组键值对(key,value),键值对又称为映射关系。

问:Collection有Set和List等子接口,那么Set和List有啥区别?

java 复制代码
List:有序(可以按照索引操作),可重复
Set:无序(不能按照索引操作),不可重复

问:Set有一些常见的实现类,它们是谁?

java 复制代码
HashSet:散列无规律
LinkedHashSet:按照元素添加的顺序排列
TreeSet:按照元素的大小顺序排列

问:Set的实现类有什么区别?

关于底层,后面讲Map再细说

问:Collection接口的一些方法?

1、添加

  • add(E e):添加一个元素
  • addAll(Collection c):添加一组对象。集合1.addAll(集合2),将集合2的元素添加到集合1中,谁.影响谁里面的元素。集合1变成两个集合的并集。

2、删除

  • clear():清空
  • remove(Object e):删除一个对象
  • removeAll(Collection c):删除一组对象,删除两个集合的交集。集合1.removeAll(集合2),删除的是集合1中的元素,谁.删谁里面的元素。
  • retainAll(Collection c):删除一组对象,删除两个集合的非交集部分,留下两个集合的交集部分。集合1.removeAll(集合2),删除的是集合1中的元素,谁.删谁里面的元素。
  • removeIf(Predicate p):删除一组对象。重写Predicate 接口中的test方法,当test方法判断该元素返回true,这个元素就会被删除。

3、查询/查看

  • boolean contains(Object obj):判断某个对象是不是在当前集合中
  • boolean containsAll(Collection c):判断c集合中的所有元素是不是都在当前集合中,即判断c集合是不是当前集合的子集。
  • boolean isEmpty():是否为空
  • int size():元素个数
  • Object\[\] toArray():把元素放到数组中

4、遍历方式

  • 增强foreach循环(它底层也是使用迭代器)
  • Iterator迭代器
    • Iterator iterator()

问:Iterator迭代器遍历用的方法?

  • boolean hasNext():判断迭代器当前位置是不是还有元素可迭代
  • Object next():先返回迭代器当前位置的元素,然后迭代器向下一个元素移动。

问:迭代器在遍历集合元素的过程中,切记不要做哪些事?

不要对当前集合调用集合的add,remove等方法,进行元素个数的修改。

问:如果迭代器要删除元素,应该调用谁的什么方法?

调用迭代器的remove方法。

问:foreach里面能不能调用当前集合的add,remove等方法?能不能调用迭代器的remove方法?

不能。

不能。

结论:foreach里面,针对当前集合的add,remove等都是无法操作。

问:List接口的一些方法?

除了从Collection接口继承的方法之外,List还扩展了哪些方法?

1、添加

add(int index, Object obj):指定位置添加一个元素。

addAll(int index, Collection c):指定位置添加一组元素。

2、删除

remove(int index):删除指定位置的元素。

3、查询

Object get(int index):返回index位置的元素

List subList(int start, int end):截取[start, end)位置的一组元素

int indexOf(Object obj):返回首次出现的索引值。不存在返回-1。

int lastIndexOf(Object obj):返回末次出现的索引值。不存在返回-1。

4、修改

set(int index, Object obj):替换index位置的元素

replaceAll(UnarayOperator u):要重写UnarayOperator 接口的apply方法,方法的参数是元素的原值,方法的返回值是元素的新值。

问:hashCode和equals方法

  • boolean equals(Object obj):比较两个对象是不是相等。怎么比较?

    • 如果重写了,通常是比较两个对象的内容/属性值
    • 如果没有重写,Object类中定义的equals方法是比较对象的地址值
  • int hashCode():计算对象的身份证号

    • 通常该值用于计算对象在HashSet,HashMap等与哈希有关的集合中的存储位置

hashCode方法重写有一些要求?

  • 和重写equals方法选择的属性一样(快捷键Alt + insert)
  • 同一个对象,属性值不变的话,调几次它们的hashCode值应该相同
  • 如果equals返回true,即相同,要求它们的hashCode值也相同
  • 如果equals返回false,即不同,它们的hashCode值可能相同,也可能不同。当然是最好是不同。
  • 如果两个对象的hashCode值不同,两个对象equals一定是false,即不同。

重写equals方法有什么要求吗?

  • 自反性:x.equals(x)一定是true
  • 一致性:如果x和y两个对象的属性没有变化,多次调用x.equals(y)返回的结果要一致
  • 传递性:x.equals(y)相同,y.equals(z)也相同,那么应该得出x.equals(z)也相同
  • 对称性:x.equals(y)相同,反过来,y.equals(x)也相同,或者x.equals(y)不同,反过来,y.equals(x)也不同
  • 非空对象与null比较一定是false:x!=null,那么x.equals(null)一定是false
java 复制代码
package com.atguigu.list;

import org.junit.Test;

public class TestEmployee {
    @Test
    public void test1()throws Exception{
        //手动重写equals或hashCode方法的问题,这里只演示equals方法。
        //需求:如下3个对象,我希望比较equals都相同
        Employee e1 = new Employee(1,"张三","男");//编号,姓名,性别
        Employee e2 = new Employee(1,"张三","man");
        Employee e3 = new Employee(1,"张三","Man");

        System.out.println(e1.equals(e2));
        System.out.println(e1.equals(e3));
        System.out.println(e2.equals(e3));

        System.out.println(e1);
        System.out.println(e2);
        System.out.println(e3);
    }
}
java 复制代码
package com.atguigu.list;

import java.util.Objects;

public class Employee {
    private int id;
    private String name;
    private String gender;

    public Employee(int id, String name, String gender) {
        this.id = id;
        this.name = name;
        this.gender = gender;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Employee employee = (Employee) o;

        if (id != employee.id) return false;
        if (!Objects.equals(name, employee.name)) return false;
//        return Objects.equals(gender, employee.gender);
        return equalsGender(gender, employee.gender);
    }

    private boolean equalsGender(String gender1, String gender2){
        if(gender1 == gender2){
            return true;
        }
        if(gender1!=null && gender1.equals("男")){
            gender1 = "man";
        }
        if(gender2!=null && gender2.equals("男")){
            gender2 ="man";
        }
        return gender1.equalsIgnoreCase(gender2);//不区分大小写比较两个单词
    }

    @Override
    public int hashCode() {
        int result = id;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        result = 31 * result + (gender != null ? gender.hashCode() : 0);
        return result;
    }
}

二、ListIterator

2.1 ListIterator是什么

ListIterator也是一种迭代器,它是Iterator接口的子接口,而且是专门用于List系列的集合的迭代器。

2.2 ListIterator的对象如何获取

List系列的集合.listIterator()可以获取ListIterator的对象。一开始迭代器在0位置。

List系列的集合.listIterator(int index)也可以获取ListIterator的对象。一开始迭代器在index位置。

2.3 API方法

Iterator接口有的方法,ListIterator也有。(因为会继承过来)

ListIterator接口又扩展了一些方法:

  • 获取元素的下标

    • int nextIndex()
    • int previousIndex()
  • 遍历的方向

    • 除了 hasNext() + next() 可以实现从左往右遍历
    • 还可以用下面两个方法实现从右往左遍历
      • boolean hasPrevious()
      • Object previous()
  • 支持的操作

    • 除了remove()(这是迭代器的remove)之外
    • 还增加了迭代过程中的如下方法,它们是迭代器的add和set
      • add(Object obj)
      • set(Object obj)

2.4 演示代码

java 复制代码
package com.atguigu.list;

import org.junit.Test;

import java.util.ArrayList;
import java.util.ListIterator;

public class TestListIterator {
    @Test
    public void test1()throws Exception{
        ArrayList list = new ArrayList();
        list.add("hello");
        list.add("world");
        list.add("java");

        ListIterator listIterator = list.listIterator();//返回的是ListIterator接口的实现类的对象
        //从左往右遍历
        while(listIterator.hasNext()){
            int index = listIterator.nextIndex();//迭代器当前指向的元素的下标
            int preIndex = listIterator.previousIndex();
            Object obj = listIterator.next();
            System.out.println("前一个元素的下标:" + preIndex);
            System.out.println("当前元素下标:" + index + ":" + obj);
        }
    }

    @Test
    public void test2()throws Exception{
        ArrayList list = new ArrayList();
        list.add("hello");
        list.add("world");
        list.add("java");

//        ListIterator listIterator = list.listIterator();//迭代器一开始在[0]位置
        ListIterator listIterator = list.listIterator(list.size());//迭代器一开始在[list.size()]位置
        //这里size=3,迭代器在[3]的位置
        //从右往左遍历
        while(listIterator.hasPrevious()){
            int index = listIterator.previousIndex();
            Object obj = listIterator.previous();
            System.out.println("前一个元素的下标:" + index);
            System.out.println("前一个元素:" + obj);
        }
    }

    @Test
    public void test3()throws Exception{
        ArrayList list = new ArrayList();
        list.add("hello");
        list.add("world");
        list.add("java");

        //需求:在hello后面添加一个atguigu
        /*
        方式一:不用迭代器
         */
        /*int index = list.indexOf("hello");//查询hello的位置
        list.add(index + 1,"atguigu");//在hello的后面添加新元素atguigu
        System.out.println(list);*/

        /*
        方式二:使用ListIterator迭代器
         */
        ListIterator listIterator = list.listIterator();
        while(listIterator.hasNext()){
            Object obj = listIterator.next();
            if("hello".equals(obj)){
                listIterator.add("atguigu");
            }
        }
        System.out.println(list);
    }

    @Test
    public void test4()throws Exception {
        ArrayList list = new ArrayList();
        list.add("hello");
        list.add("haha");
        list.add("world");
        list.add("java");

        //需求:在hello的前面添加atguigu
        /*
        方式一:不用迭代器
         */
       /* int index = list.indexOf("hello");//查询hello的位置
        list.add(index ,"atguigu");//在hello的前面添加新元素atguigu,插入到hello原来的位置,然后自动把hello及其后面的元素右移
        System.out.println(list);*/

        /*
        方式二:使用ListIterator迭代器
         */
        ListIterator listIterator = list.listIterator(list.size());
        while(listIterator.hasPrevious()){
            Object obj = listIterator.previous();
            if("hello".equals(obj)){
                listIterator.add("atguigu");
            }
        }
        System.out.println(list);
    }

    @Test
    public void test5()throws Exception{
        ArrayList list = new ArrayList();
        list.add("hello");
        list.add("haha");
        list.add("world");
        list.add("java");
        //Hello,Haha,World,Java

        //需求:把所有单词的首字母改为大写
        ListIterator listIterator = list.listIterator();
        while(listIterator.hasNext()){
            Object obj = listIterator.next();
            String str = (String) obj;

            char first = str.charAt(0);
            first = Character.toUpperCase(first);
            String after = str.substring(1);//从[1]到最后
            listIterator.set(first + after);//替换原来的元素
        }
        System.out.println(list);

    }
}

三、List接口的常用实现类

3.1 实现类有哪些?

  • ArrayList(最常用):动态数组
  • Vector:动态数组
  • LinkedList:双向链表
  • Stack:栈

3.2 ArrayList和Vector的区别

3.3 动态数组与双向链表的区别

详细请看

3.4 栈等数据结构3

3.4.1 栈、队列、双端队列

3.4.2 Queue队列接口

3.4.2 Deque双端队列接口

3.4.3 演示代码

java 复制代码
package com.atguigu.list;

import org.junit.Test;

import java.util.*;

public class TestListImpl {
    @Test
    public void test1()throws Exception{
        //演示栈类型Stack
        Stack stack = new Stack();
        //它自己扩展的方法,为了体现它和列表的不同,突然栈的特点
        stack.push("hello");//压栈
        stack.push("world");
        stack.push("java");

        System.out.println(stack.pop());//弹出栈  java
        System.out.println(stack.pop());//world
        System.out.println(stack.pop());//hello
        System.out.println(stack.pop());//java.util.EmptyStackException 空栈异常
    }

    @Test
    public void test2()throws Exception {
        //演示栈类型Stack
        Stack stack = new Stack();
        //它自己扩展的方法,为了体现它和列表的不同,突然栈的特点
        stack.push("hello");//压栈
        stack.push("world");
        stack.push("java");

        while(!stack.empty()){//非空判断
            System.out.println(stack.pop());
        }
    }

    @Test
    public void test3()throws Exception{
        //演示栈类型Stack
        Stack stack = new Stack();
        //它自己扩展的方法,为了体现它和列表的不同,突然栈的特点
        stack.push("hello");//压栈
        stack.push("world");
        stack.push("java");

        System.out.println(stack.peek());//peek偷偷看一眼,现在栈顶是谁,不敢拿
        System.out.println(stack.peek());
        System.out.println(stack.peek());
        System.out.println(stack.peek());
    }

    @Test
    public void test4()throws Exception{
        //演示ArrayList  用作栈使用
        ArrayList list = new ArrayList();
        //关键点:要实现先进后出的效果
        list.add("hello");
        list.add("world");
        list.add("java");
        list.add("atguigu");

        //出来的顺序:atguigu,java,world,hello   取走
        while(list.size()>0){//有元素可取
            System.out.println(list.remove(list.size()-1));
            //list.size()-1是列表中最后一个元素的索引
        }

        System.out.println(list);//空的
    }

    @Test
    public void test5()throws Exception{
        //演示Queue接口的使用
        Queue queue = new LinkedList();//这么写是希望大家知道它们的关系
        queue.add("hello");
        queue.add("world");
        queue.add("java");
        queue.add("atguigu");

        //出来的顺序:hello,world,java,atguigu

        System.out.println(queue.element());//hello  偷偷查看第一个元素,队头,谁带头
        System.out.println(queue.element());//hello
        System.out.println(queue.element());//hello

        //关键点:先进先出
        while(!queue.isEmpty()){//非空判断
            System.out.println(queue.remove());
        }
        System.out.println(queue);//空的
    }


    @Test
    public void test6()throws Exception{
        Queue queue = new LinkedList();
//        System.out.println(queue.element());//java.util.NoSuchElementException
        System.out.println(queue.remove());//java.util.NoSuchElementException
    }

    @Test
    public void test7()throws Exception{
        Queue queue = new LinkedList();
        System.out.println(queue.peek());//null
        System.out.println(queue.poll());//null
    }

    @Test
    public void test8()throws Exception{

        //双端队列
        Deque deque = new LinkedList();
        //关键词:两头都可以进、出
        deque.addFirst("hello");
        deque.addLast("world");
        deque.addFirst("world");
        deque.addFirst("atguigu");
        deque.addLast("chai");
        //顺序:左边是first的话   atguigu,world,hello,world,chai
        System.out.println(deque);

        System.out.println(deque.removeLast());//chai
        System.out.println(deque.removeFirst());//atguigu

    }

}

四、Collection系列集合关系图

五、泛型

5.1 什么是泛型?

泛型:泛指某个类型,代码中看到、、等都是和泛型有关的。

5.2 泛型的好处

JDK1.5引入的泛型。

为了解决:

  • 避免类型的转换,主要是向下转型
  • 把很多场景的类型检查从运行时提前到编译时

泛型带来的好处:

  • 简化了代码:通过减少向下转型
  • 更安全:编译时就已经进行类型检查
java 复制代码
package com.atguigu.generic;

import org.junit.Test;

import java.util.ArrayList;

public class TestGeneric {
    @Test
    public void test1NoGeneric()throws Exception{
        ArrayList list = new ArrayList();
        list.add("hello");
        list.add("java");
        list.add("world");
        list.add("atguigu");
        //需求:输出元素,并且求字符串元素的长度
        for (Object o : list) {
            String str = (String) o;
            System.out.println(str+"的长度:" + str.length());
        }
    }

    @Test
    public void test1UseGeneric()throws Exception{
        ArrayList<String> list = new ArrayList<String>();
        list.add("hello");
        list.add("java");
        list.add("world");
        list.add("atguigu");

        //快捷键iter
        for (String str : list) {//避免的向下转型
            System.out.println(str+"的长度:" + str.length());
        }
    }

    @Test
    public void test2NoGeneric()throws Exception{
        //给list添加元素,但是只能添加String,不能添加其他类型的对象
        ArrayList list = new ArrayList();
        list.add("hello");
        list.add("java");
        list.add("world");
        list.add(1);//编译器不给提示,它不满足我们的要求
        for (Object o : list) {
            if(o instanceof String) {
                String str = (String) o;
                System.out.println(str + "的长度:" + str.length());
            }
        }
    }

    @Test
    public void test2UseGeneric()throws Exception{
        //给list添加元素,但是只能添加String,不能添加其他类型的对象
        ArrayList<String> list = new ArrayList<String>();
        list.add("hello");
        list.add("java");
        list.add("world");
//        list.add(1);//编译器立刻给出提示,它不满足我们的要求
    }
}

5.3 泛型的使用场景有哪些?

泛型可以在两个位置定义和使用?

  • 在类或接口名后面定义和使用泛型,这样的类或接口称为泛型类或泛型接口
  • 在方法的返回值类型的前面定义泛型,在当前方法中使用泛型,这样的方法称为泛型方法

5.4 泛型类或泛型接口

5.4.1 回忆之前学习的类或接口

java.lang.Comparable:自然比较接口,它有一个抽象方法:int compareTo(T o),这里的T代表要比较大小的对象类型,T代表Type

java.util.Comparator:定制比较器接口,它也有一个抽象方法:int compare(T o1,T o2) ,这里的T代表要比较大小的对象类型

java.util.Collection:Collection集合根接口,这里的E代表的是集合元素的类型,E代表element元素

java.util.List、Set、Queue、Deque等子接口。

java.lang.Iterable:可迭代的接口

java.util.Iterator:迭代器接口

java.util.ArrayList、Vector、LinkedList、Stack:List系列集合类型

java.util.HashSet、LinkedHashSet、TreeSet:Set系列集合的类型。

5.4.2 如何使用一个泛型类或泛型接口?

如果一个类或接口名后面声明(定义)了泛型,那么使用该类或接口时,最好指定该泛型的具体类型,否则会有警告。

注意:

  • 为泛型指定具体类型时,必须指定为引用数据类型。不支持基本数据类型。

1、演示泛型类的使用

java 复制代码
package com.atguigu.generic;

import org.junit.Test;

import java.util.ArrayList;

public class TestUseGenericClass {
    @Test
    public void test1()throws Exception{
        //ArrayList<E>,这里<E>代表的是元素的类型
        //存储整数
//        ArrayList<int> list = new ArrayList<int>();//不支持基本数据类型
        ArrayList<Integer> list = new ArrayList<Integer>();//只支持基本数据类型
        list.add(1);
//        list.add(1.0);//报错,它们都不能自动转为Integer对象
//        list.add("hello");//报错,它们都不能自动转为Integer对象
    }
    
    @Test
    public void test2()throws Exception{
        //存储小数
        ArrayList<Double> list = new ArrayList<Double>();
//        list.add(1);//错误,1是int类型,它可以自动转为double(自动类型提升),但是不能自动转为Double(自动装箱)
        list.add(1.0);
        list.add((double)1);
        list.add(1d);//在数字后面加d或D表示double
    }
}

2、演示泛型接口的使用

(1)Comparable
java 复制代码
package com.atguigu.generic;

/*
需求:要求员工类Employee实现Comparable<T>接口
        T是Type单词的缩写,是要比较大小的对象的类型,
        这里是两个员工对象要比较大小,T是Employee类型

    员工对象默认按照id比较大小
 */
public class Employee implements Comparable<Employee>{
    private int id;
    private String name;
    private double salary;

    public Employee() {
    }

    public Employee(int id, String name, double salary) {
        this.id = id;
        this.name = name;
        this.salary = salary;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", salary=" + salary +
                '}';
    }

    @Override
    public int compareTo(Employee o) {
        return id - o.id;
    }
}
(2)Comparator
java 复制代码
package com.atguigu.generic;

import java.util.Comparator;

/*
需求:定制一个比较器,用于比较两个员工对象的大小,
   要求:先按照薪资比较大小,如果薪资相同,再按照编号比较大小

   实现Comparator<T>接口,这里的T代表要比较大小的对象的类型,这里是员工类
 */
public class EmployeeSalaryComparator implements Comparator<Employee> {
    @Override
    public int compare(Employee o1, Employee o2) {

//       return o1.getSalary() - o2.getSalary();//double - double,结果是double,返回值类型是int
//       return (int)(o1.getSalary() - o2.getSalary());//语法上没毛病,  15000.56  15000.42 看业务层面是否允许这样误差

/*        if(o1.getSalary() > o2.getSalary()){
            return 1;
        }else if(o1.getSalary() < o2.getSalary()){
            return -1;
        }else{
            return o1.getId() - o2.getId();
        }*/

        int result = Double.compare(o1.getSalary(), o2.getSalary());
        //如果o1.getSalary() 大于  o2.getSalary(),该方法结果返回正整数
        //如果o1.getSalary() 小于  o2.getSalary(),该方法结果返回负整数
        //如果o1.getSalary() 等于  o2.getSalary(),该方法结果返回零
        /*
        Integer.compare(o1.getId(),o2.getId())比较两个int值,
        //如果o1.getId() 大于  o2.getId(),该方法结果返回正整数
        //如果o1.getId() 小于  o2.getId(),该方法结果返回负整数
        //如果o1.getId() 等于  o2.getId(),该方法结果返回零

        等价于   o1.getId() - o2.getId()
         */
        return result == 0 ? Integer.compare(o1.getId(),o2.getId()): result;
    }
}
(3)迭代器等其他接口
java 复制代码
package com.atguigu.generic;

import org.junit.Test;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.function.Predicate;

public class TestGenericInterface {
    @Test
    public void test1()throws Exception{
        ArrayList<String> list = new ArrayList<String>();
        list.add("hello");
        list.add("world");
        list.add("java");
        list.add("atguigu");

        //需求:使用Iterator遍历集合的元素
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            String str = iterator.next();
            System.out.println(str);
        }
    }

    @Test
    public void test2()throws Exception{
        //根据条件删除元素
        ArrayList<String> list = new ArrayList<String>();
        list.add("hello");
        list.add("world");
        list.add("java");
        list.add("atguigu");

        //需求:删除包含o字母的元素
        /*
        方式一:removeIf
         */
        list.removeIf(new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.contains("o");
            }
        });
        System.out.println(list);
    }

    @Test
    public void test3()throws Exception{
        //根据条件删除元素
        ArrayList<String> list = new ArrayList<String>();
        list.add("hello");
        list.add("world");
        list.add("java");
        list.add("atguigu");

        //需求:删除包含o字母的元素
        /*
        方式二:迭代器删除
         */
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            String str = iterator.next();
            if(str.contains("o")){
                iterator.remove();
            }
        }
        System.out.println(list);
    }
}

5.4.3 自定义泛型类或泛型接口

1、语法规范

java 复制代码
【修饰符】 class 类名<泛型字母列表>{
    
}

【修饰符】 interface 接口名<泛型字母列表>{
    
}

注意:

  • <泛型字母列表>,可以是1个也可以是多个字母。
  • <泛型字母列表>,不推荐用单词,建议使用单个的大写字母
    • 为什么不用单词?容易引起误会

需求:

声明一个泛型类Coordinate坐标,坐标类中应该包含两个属性,分别是x和y,代表经度和维度值。

这里的x和y的类型不确定,可能是Integer,Double,String等。

2、演示定义1个泛型

java 复制代码
package com.atguigu.generic;

/*
声明一个泛型类Coordinate坐标,坐标类中应该包含两个属性,分别是x和y,代表经度和维度值。
这里的x和y的类型不确定,可能是Integer,Double,String等。

 */
public class Coordinate<T> {
    private T x;
    private T y;
    //同一个对象的x,y的类型一定相同

    /*public static void method(T t){//在类名后面定义的泛型,不能用于静态成员
                                //因为类名后面的泛型T,要确定具体类型,通常在new对象时,而静态方法和对象无关。

    }*/

    public Coordinate() {
    }

    public Coordinate(T x, T y) {
        this.x = x;
        this.y = y;
    }

    public T getX() {
        return x;
    }

    public void setX(T x) {
        this.x = x;
    }

    public T getY() {
        return y;
    }

    public void setY(T y) {
        this.y = y;
    }

    @Override
    public String toString() {
        return "Coordinate{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }
}

3、演示定义2个泛型

java 复制代码
package com.atguigu.generic;

//T,U代表任意类型
public class ZuoBiao<T,U> {
    private T x;
    private U y;
    //同一个对象的x,y可能类型不同,也可能相同

    public ZuoBiao() {
    }

    public ZuoBiao(T x, U y) {
        this.x = x;
        this.y = y;
    }

    public T getX() {
        return x;
    }

    public void setX(T x) {
        this.x = x;
    }

    public U getY() {
        return y;
    }

    public void setY(U y) {
        this.y = y;
    }

    @Override
    public String toString() {
        return "ZuoBiao{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }
}

3、演示使用自定义泛型类

java 复制代码
package com.atguigu.generic;

import org.junit.Test;

public class TestCoordinate {
    @Test
    public void test1()throws Exception{
        //创建坐标类的对象
/*        Coordinate<String> c1 = new Coordinate<String>("东经35.6","北纬25.6");
        Coordinate<Double> c2 = new Coordinate<Double>(35.6,25.6);*/

        //从JDK1.7开始,左边写了<>中的类型,右边可以省略,但是<>不要省略
        Coordinate<String> c1 = new Coordinate<>("东经35.6","北纬25.6");
        //c1对象的x,y都是String类型
        Coordinate<Double> c2 = new Coordinate<>(35.6,25.6);
        //c1对象的x,y都是Double
        System.out.println(c1);
        System.out.println(c2);
    }

    @Test
    public void test2()throws Exception{
        //x是String,y是Double
        ZuoBiao<String,Double> z1 = new ZuoBiao<>("东经35.6",25.6);
        //z1对象的x,y分别是String和Double
        ZuoBiao<String,String> z2 = new ZuoBiao<>("东经35.6","北纬25.6");
        //z2对象的x,y都是String
    }
}

5.5 泛型方法

5.5.1 什么情况下使用泛型方法呢?

  • 当某个方法的形参类型等不确定,可以在方法的返回值类型前面加泛型的声明。而且这个泛型只在当前方法有效,和其他方法无关。
  • 当某个静态方法的形参类型等不确定,可以在方法的返回值类型前面加泛型的声明。

总结:

  1. 如果某个类中,多个地方(多个成员变量,多个成员方法,都有未知的类型,而且它们是统一,那么就用泛型类或泛型接口)
  2. 如果是类或接口中单个方法某个类型未知,那么就使用泛型方法。
java 复制代码
package com.atguigu.generic;

/*
Example:例子。
 */
public class Example<T> {
    public void m1(T t){
        //...
    }

    public void m2(T t){
        //...
    }
    //以上写法,表示同一个对象的m1和m2方法的形参类型一定是一样的
    //问题?如果希望同一个对象的m1和m2方法的形参类型不一样,怎么办?
    //方式一:多定义几个字母
    //方式二:每个方法单独自己定义泛型
}
java 复制代码
package com.atguigu.generic;

public class Demo{
    public <T> void m1(T t){

    }

    public <T> void m2(T t){

    }
    //以上写法,m1和m2方法的形参类型是独立
	//m3的形参类型也是独立的
    public static <T> void m3(T t){

    }
}

5.5.2 之前的API中哪些方法是泛型方法?

java.util.Collection接口中有如下两个方法:

  • Object\[\] toArray():不管元素什么类型,统一返回Object\[\]数组
  • T\[\] toArray(T\[\] a):可以返回不同类型的数组

java.util.Arrays数组工具类中有一个这样的方法:

  • public static List asList(T... a):将多个元素放到一个List集合中
java 复制代码
package com.atguigu.generic;

import org.junit.Test;

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

public class TestGenericMethod {
    @Test
    public void test1()throws Exception{
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("world");

        //Object[] toArray():不管元素什么类型,统一返回Object[]数组
        Object[] objects = list.toArray();
        //进一步遍历objects数组
        for (int i = 0; i < objects.length; i++) {
            String str = (String) objects[i];//还得强转,有点麻烦
        }
    }

    @Test
    public void test2()throws Exception{
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("world");

        //<T> T[] toArray(T[] a):可以返回不同类型的数组
//        String[] strings = new String[list.size()];
        String[] strings = new String[0];//这里长度是多少不重要,因为集合的toArray方法中只是为了得到它的类型

        System.out.println(strings.getClass());//它里面用的是这个信息,数组的类型

        String[] array = list.toArray(strings);
        //进一步遍历array数组
        for (int i = 0; i < array.length; i++) {
            System.out.println(array[i] +",长度:" + array[i].length());
        }
    }

    @Test
    public void test3()throws Exception{
        List<String> stringList = Arrays.asList("hello", "java", "world");
        List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
        //顺便多说一句,asList方法返回的集合类型不是咱们上午学的ArrayList,Vector等类型,而是Arrays里面的一个内部类类型
        System.out.println(stringList.getClass());//class java.util.Arrays$ArrayList
        //而且这个内部类的集合,不支持添加元素,删除元素等操作
        stringList.add("chai");//java.lang.UnsupportedOperationException不支持该操作异常
    }
}

5.6 定义泛型时,能不能限定范围?

5.6.1 泛型上限

定义泛型时,能不能限定范围?

答案:可以,可以限定上限。

语法格式:

java 复制代码
<T extends 上限>  :这里的extends表示 T类型 <= 上限类型
                   子类 < 父类
    			   实现类 < 父接口
    			   子接口 < 父接口
 
<T extends 上限1  & 上限2 & 上限3>:这里&,表是要同时满足
    			T要同时 <= 上限1,上限2,上限3,而且相当于T同时继承或实现它们
    			上限1,上限2,上限3这些上限中,只能出现1个是类,其余只能是接口,而且类要在最左边,接口在后面。

1、演示一个上限

需求:声明一个学生类,这个学生类与我们以往声明的学生类不一样,它的属性有姓名和成绩,但是这个成绩,可能是Integer,Double,BigInteger等数字类型,但是不能是String等其他类型。

java 复制代码
package com.atguigu.generic;

/*
需求:声明一个学生类,这个学生类与我们以往声明的学生类不一样,它的属性有姓名和成绩,
但是这个成绩,可能是Integer,Double,BigInteger等数字类型,但是不能是String等其他类型。
 */
public class Student <T extends Number>{
    private String name;
    private T score;

    public Student() {
    }

    public Student(String name, T score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

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

    public T getScore() {
        return score;
    }

    public void setScore(T score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}
java 复制代码
package com.atguigu.generic;

import org.junit.Test;

import java.util.ArrayList;

public class TestStudent {
    @Test
    public void test1()throws Exception{
        Student<Integer> s1 = new Student<>();
        Student<Double> s2 = new Student<>();
//        Student<String> s3 = new Student<>();//报错,因为String不满足 String <= Number条件
    }


}

2、演示多个上限

java 复制代码
package com.atguigu.generic;

/*
需求:声明一个学生类,这个学生类与我们以往声明的学生类不一样,它的属性有姓名和成绩,
但是这个成绩,可能是Integer,Double,BigInteger等数字类型,但是不能是String等其他类型。
同时成绩的类型,必须实现Comparable接口,Cloneable接口等
 */
public class XueSheng <T extends Number & Comparable & Cloneable>{
}
java 复制代码
package com.atguigu.generic;

import org.junit.Test;

public class TestXueSheng {
    @Test
    public void test1()throws Exception{
//        XueSheng<Integer> x = new XueSheng<Integer>();
        //错误,因为Integer没有实现Cloneable接口
    }
}

5.6.2 泛型擦除

泛型的擦除:一个泛型类或泛型接口在使用时,不去指定它的泛型,就称为泛型擦除。

泛型擦除后,泛型按什么类型处理呢?

如果泛型擦除了,默认按照第一个上限处理,如果没有指定上限,上限就是Object。

java 复制代码
    @Test
    public void test2()throws Exception{
        //Student<T extends Number>,本来应该指定T的类型,现在不指定了,称为泛型擦除
         Student s1 = new Student();
        Number score = s1.getScore();
    }

    @Test
    public void test3()throws Exception{
        //ArrayList<E>,本来要指定E的类型,现在不指定了,称为泛型擦除
        ArrayList list = new ArrayList();
        list.add("hello");
    }

5.7 泛型通配符(了解)

5.7.1 什么情况下使用泛型通配符

当使用一个已经声明好的泛型类或泛型接口,例如:Collection、ArrayList,Comparable等,用它们声明方法的形参,或变量时,仍然无法确定,该指定为什么类型,就可以使用通配符<?>。

5.7.2 使用泛型通配符有几种形式

答案:使用形式有3种,以ArrayList

  • ArrayList<?>:这里?表示任意类型
  • ArrayList<? extends 上限>:这里的?表示 <= 上限的任意类型
  • ArrayList<? super 下限>:这里的?表示 >= 下限的任意类型
java 复制代码
package com.atguigu.wild;

import org.junit.Test;

import java.util.ArrayList;

public class TestWildCard {
    @Test
    public void test1()throws Exception{
        //调用m1方法,可以传入的集合是什么样的呢?
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("hello");
        list1.add("world");

        ArrayList<Integer> list2 = new ArrayList<>();
        list2.add(666);
        list2.add(888);

        ArrayList<Object> list3 = new ArrayList<>();
        list3.add("尚硅谷");
        list3.add(666);

        m1(list1);
        m1(list2);
        m1(list3);
    }

    public void m1(ArrayList<?> list){
        for (Object o : list) {
            System.out.println(o);
        }

        //编译时无法确定?是啥类型,编译器认为它添加什么都是有风险的,全部报错
/*        list.add("hello");
        list.add(1);
        list.add(1.0);*/
        list.add(null);
    }

    @Test
    public void test2()throws Exception{
        //调用m2方法,可以传入的集合是什么样的呢?
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("hello");
        list1.add("world");

        ArrayList<Integer> list2 = new ArrayList<>();
        list2.add(666);
        list2.add(888);

        ArrayList<Object> list3 = new ArrayList<>();
        list3.add("尚硅谷");
        list3.add(666);

//        m2(list1);//错误, list1的泛型<String>不满足 String <= Number条件
        m2(list2);//对
//        m2(list3);//错误, list3的泛型<Object>不满足 Object <= Number条件
    }

    public void m2(ArrayList<? extends Number> list){
        for (Number number : list) {
            System.out.println(number);
        }
    }

    @Test
    public void test3()throws Exception{
        //调用m3方法,可以传入的集合是什么样的呢?
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("hello");
        list1.add("world");

        ArrayList<Integer> list2 = new ArrayList<>();
        list2.add(666);
        list2.add(888);

        ArrayList<Object> list3 = new ArrayList<>();
        list3.add("尚硅谷");
        list3.add(666);

//        m3(list1);//错误, list1的泛型<String>不满足 String >= Number条件,String和Number没关系
//        m3(list2);//错误, list2的泛型<Integer>不满足 Integer >= Number条件,Integer<Number的关系
        m3(list3);//对
    }

    public void m3(ArrayList<? super Number> list){
        for (Object o : list) {
            System.out.println(o);
        }
    }

    @Test
    public void test4()throws Exception{
        //演示m4方法调用
        //调用m4方法,可以传入的集合是什么样的呢?
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("hello");
        list1.add("world");

        ArrayList<Integer> list2 = new ArrayList<>();
        list2.add(666);
        list2.add(888);

        ArrayList<Object> list3 = new ArrayList<>();
        list3.add("尚硅谷");
        list3.add(666);

//        m4(list1);//错误,m4方法的形参已经明确是ArrayList<Object>,那么实参的泛型只能是<Object>
//        m4(list2);
        m4(list3);
    }

    public void m4(ArrayList<Object> list){
        for (Object o : list) {
            System.out.println(o);
        }
    }

    @Test
    public void test5()throws Exception{
        //调用m5方法,可以传入的集合是什么样的呢?
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("hello");
        list1.add("world");

        ArrayList<Integer> list2 = new ArrayList<>();
        list2.add(666);
        list2.add(888);

        ArrayList<Object> list3 = new ArrayList<>();
        list3.add("尚硅谷");
        list3.add(666);

        m5(list1);
        m5(list2);
        m5(list3);
    }

    public void m5(ArrayList list){//啥都不写,泛型擦除,唯一的小瑕疵,有警告
        for (Object o : list) {
            System.out.println(o);
        }
    }

    @Test
    public void test6()throws Exception{
        //调用m6方法,可以传入的集合是什么样的呢?
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("hello");
        list1.add("world");

        ArrayList<Integer> list2 = new ArrayList<>();
        list2.add(666);
        list2.add(888);

        ArrayList<Object> list3 = new ArrayList<>();
        list3.add("尚硅谷");
        list3.add(666);

        m6(list1,"chai");
        m6(list2,1);
        m6(list3,1.0);
    }

    public <T> void m6(ArrayList<T> list,T t){
        for (T o : list) {
            System.out.println(o);
        }

        list.add(t);
    }

    public void m7(ArrayList<?> list1, ArrayList<?> list2){//这里?独立的

    }
    public <T> void m8(ArrayList<T> list1, ArrayList<T> list2){//这里T是同一种类型

    }
}

m4(list3);

}

复制代码
public void m4(ArrayList<Object> list){
    for (Object o : list) {
        System.out.println(o);
    }
}

@Test
public void test5()throws Exception{
    //调用m5方法,可以传入的集合是什么样的呢?
    ArrayList<String> list1 = new ArrayList<>();
    list1.add("hello");
    list1.add("world");

    ArrayList<Integer> list2 = new ArrayList<>();
    list2.add(666);
    list2.add(888);

    ArrayList<Object> list3 = new ArrayList<>();
    list3.add("尚硅谷");
    list3.add(666);

    m5(list1);
    m5(list2);
    m5(list3);
}

public void m5(ArrayList list){//啥都不写,泛型擦除,唯一的小瑕疵,有警告
    for (Object o : list) {
        System.out.println(o);
    }
}

@Test
public void test6()throws Exception{
    //调用m6方法,可以传入的集合是什么样的呢?
    ArrayList<String> list1 = new ArrayList<>();
    list1.add("hello");
    list1.add("world");

    ArrayList<Integer> list2 = new ArrayList<>();
    list2.add(666);
    list2.add(888);

    ArrayList<Object> list3 = new ArrayList<>();
    list3.add("尚硅谷");
    list3.add(666);

    m6(list1,"chai");
    m6(list2,1);
    m6(list3,1.0);
}

public <T> void m6(ArrayList<T> list,T t){
    for (T o : list) {
        System.out.println(o);
    }

    list.add(t);
}

public void m7(ArrayList<?> list1, ArrayList<?> list2){//这里?独立的

}
public <T> void m8(ArrayList<T> list1, ArrayList<T> list2){//这里T是同一种类型

}

}

复制代码
相关推荐
小江的记录本1 分钟前
【Spring AI】Spring AI中RAG误触发与系统提示词泄露问题解决方案(完整版+代码方案)
java·人工智能·spring boot·后端·python·spring·面试
YY&DS7 分钟前
Qt Designer 自定义控件已提升后,如何修改提升类
开发语言·qt
勇往直前plus9 分钟前
Python 属性访问与操作全解析:内置函数、魔法方法与描述符深度指南
java·网络·python
Arenaschi15 分钟前
关于GPT的版特点
java·网络·人工智能·windows·python·gpt
人道领域15 分钟前
【LeetCode刷题日记】108.将有序数组转换为二叉搜索树
java·算法·leetcode
右耳朵猫AI17 分钟前
Rust技术周刊 2026年第19周
开发语言·后端·rust
橙淮22 分钟前
并发编程(五)
java
Leweslyh27 分钟前
基于 Confucius 架构的无人集群网络控制原语解析
开发语言·网络·php
过期动态29 分钟前
【LeetCode 热题 100】无重复字符的最长子串
java·数据结构·spring boot·算法·leetcode·职场和发展
就叫飞六吧31 分钟前
MySQL 驱动里那个 `cj` 到底是什么?
数据库·mysql