集合与泛型

一、复习回顾

问:集合分为两大类?

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是同一种类型

}

}

复制代码
相关推荐
通信仿真实验室13 分钟前
(10)MATLAB莱斯(Rician)衰落信道仿真1
开发语言·matlab
勿语&16 分钟前
Element-UI Plus 暗黑主题切换及自定义主题色
开发语言·javascript·ui
ok!ko3 小时前
设计模式之原型模式(通俗易懂--代码辅助理解【Java版】)
java·设计模式·原型模式
2402_857589363 小时前
“衣依”服装销售平台:Spring Boot框架的设计与实现
java·spring boot·后端
吾爱星辰4 小时前
Kotlin 处理字符串和正则表达式(二十一)
java·开发语言·jvm·正则表达式·kotlin
ChinaDragonDreamer4 小时前
Kotlin:2.0.20 的新特性
android·开发语言·kotlin
IT良4 小时前
c#增删改查 (数据操作的基础)
开发语言·c#
哎呦没4 小时前
大学生就业招聘:Spring Boot系统的架构分析
java·spring boot·后端
Kalika0-05 小时前
猴子吃桃-C语言
c语言·开发语言·数据结构·算法
_.Switch5 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j