17-Collection集合、泛型

第一章 Collection集合

1.1 集合概述

在前面基础班我们已经学习过并使用过集合ArrayList ,那么集合到底是什么呢?

  • 集合:集合是java中提供的一种容器,可以用来存储多个数据。

集合和数组既然都是容器,它们有啥区别呢?

  • 数组的长度是固定的。集合的长度是可变的。
  • 数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象。而且对象的类型可以不一致。在开发中一般当对象多的时候,使用集合进行存储。

1.2 集合框架

JAVASE提供了满足各种需求的API,在使用这些API前,先了解其继承与接口操作架构,才能了解何时采用哪个类,以及类之间如何彼此合作,从而达到灵活应用。

集合按照其存储结构可以分为两大类,分别是单列集合java.util.Collection和双列集合java.util.Map,今天我们主要学习Collection集合,在后面讲解Map集合。

  • Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是java.util.Listjava.util.Set。其中,List的特点是元素有序、元素可重复。Set的特点是元素无序,而且不可重复。List接口的主要实现类有java.util.ArrayListjava.util.LinkedListSet接口的主要实现类有java.util.HashSetjava.util.TreeSet

从上面的描述可以看出JDK中提供了丰富的集合类库,为了便于初学者进行系统地学习,接下来通过一张图来描述整个集合类的继承体系。


其中,橙色框里填写的都是接口类型,而蓝色框里填写的都是具体的实现类。这几天将针对图中所列举的集合类进行逐一地讲解。

集合本身是一个工具,它存放在java.util包中。在Collection接口定义着单列集合框架中最最共性的内容。

1.3 Collection 常用功能

Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:

  • public boolean add(E e): 把给定的对象添加到当前集合中 。
  • public void clear() :清空集合中所有的元素。
  • public boolean remove(E e): 把给定的对象在当前集合中删除。
  • public boolean contains(E e): 判断当前集合中是否包含给定的对象。
  • public boolean isEmpty(): 判断当前集合是否为空。
  • public int size(): 返回集合中元素的个数。
  • public Object[] toArray(): 把集合中的元素,存储到数组中。

方法演示:

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

public class Demo1Collection {
    public static void main(String[] args) {
		// 创建集合对象 
    	// 使用多态形式
    	Collection<String> coll = new ArrayList<String>();
    	// 使用方法
    	// 添加功能  boolean  add(String s)
    	coll.add("小李广");
    	coll.add("扫地僧");
    	coll.add("石破天");
    	System.out.println(coll);

    	// boolean contains(E e) 判断o是否在集合中存在
    	System.out.println("判断  扫地僧 是否在集合中"+coll.contains("扫地僧"));

    	//boolean remove(E e) 删除在集合中的o元素
    	System.out.println("删除石破天:"+coll.remove("石破天"));
    	System.out.println("操作之后集合中元素:"+coll);
    	
    	// size() 集合中有几个元素
		System.out.println("集合中有"+coll.size()+"个元素");

		// Object[] toArray()转换成一个Object数组
    	Object[] objects = coll.toArray();
    	// 遍历数组
    	for (int i = 0; i < objects.length; i++) {
			System.out.println(objects[i]);
		}

		// void  clear() 清空集合
		coll.clear();
		System.out.println("集合中内容为:"+coll);
		// boolean  isEmpty()  判断是否为空
		System.out.println(coll.isEmpty());  	
	}
}

tips: 有关Collection中的方法可不止上面这些,其他方法可以自行查看API学习。

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

/*
    java.util.Collection接口
        所有单列集合的最顶层的接口,里边定义了所有单列集合共性的方法
        任意的单列集合都可以使用Collection接口中的方法


    共性的方法:
      public boolean add(E e):  把给定的对象添加到当前集合中 。
      public void clear() :清空集合中所有的元素。
      public boolean remove(E e): 把给定的对象在当前集合中删除。
      public boolean contains(E e): 判断当前集合中是否包含给定的对象。
      public boolean isEmpty(): 判断当前集合是否为空。
      public int size(): 返回集合中元素的个数。
      public Object[] toArray(): 把集合中的元素,存储到数组中。
 */
public class Demo01Collection {
    public static void main(String[] args) {
        //创建集合对象,可以使用多态
        //Collection<String> coll = new ArrayList<>();
        Collection<String> coll = new HashSet<>();
        System.out.println(coll);//重写了toString方法  []

        /*
            public boolean add(E e):  把给定的对象添加到当前集合中 。
            返回值是一个boolean值,一般都返回true,所以可以不用接收
         */
        boolean b1 = coll.add("张三");
        System.out.println("b1:"+b1);//b1:true
        System.out.println(coll);//[张三]
        coll.add("李四");
        coll.add("李四");
        coll.add("赵六");
        coll.add("田七");
        System.out.println(coll);//[张三, 李四, 赵六, 田七]

        /*
            public boolean remove(E e): 把给定的对象在当前集合中删除。
            返回值是一个boolean值,集合中存在元素,删除元素,返回true
                                集合中不存在元素,删除失败,返回false
         */
        boolean b2 = coll.remove("赵六");
        System.out.println("b2:"+b2);//b2:true

        boolean b3 = coll.remove("赵四");
        System.out.println("b3:"+b3);//b3:false
        System.out.println(coll);//[张三, 李四, 田七]

        /*
            public boolean contains(E e): 判断当前集合中是否包含给定的对象。
            包含返回true
            不包含返回false
         */
        boolean b4 = coll.contains("李四");
        System.out.println("b4:"+b4);//b4:true

        boolean b5 = coll.contains("赵四");
        System.out.println("b5:"+b5);//b5:false

        //public boolean isEmpty(): 判断当前集合是否为空。 集合为空返回true,集合不为空返回false
        boolean b6 = coll.isEmpty();
        System.out.println("b6:"+b6);//b6:false

        //public int size(): 返回集合中元素的个数。
        int size = coll.size();
        System.out.println("size:"+size);//size:3

        //public Object[] toArray(): 把集合中的元素,存储到数组中。
        Object[] arr = coll.toArray();
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }

        //public void clear() :清空集合中所有的元素。但是不删除集合,集合还存在
        coll.clear();
        System.out.println(coll);//[]
        System.out.println(coll.isEmpty());//true
    }
}

第二章 Iterator迭代器

2.1 Iterator接口

在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.IteratorIterator接口也是Java集合中的一员,但它与CollectionMap接口有所不同,Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被称为迭代器。

想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作,下面介绍一下获取迭代器的方法:

  • public Iterator iterator(): 获取集合对应的迭代器,用来遍历集合中的元素的。

下面介绍一下迭代的概念:

  • 迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。

Iterator接口的常用方法如下:

  • public E next():返回迭代的下一个元素。
  • public boolean hasNext():如果仍有元素可以迭代,则返回 true。

接下来我们通过案例学习如何使用Iterator迭代集合中元素:

public class IteratorDemo {
  	public static void main(String[] args) {
        // 使用多态方式 创建对象
        Collection<String> coll = new ArrayList<String>();

        // 添加元素到集合
        coll.add("串串星人");
        coll.add("吐槽星人");
        coll.add("汪星人");
        //遍历
        //使用迭代器 遍历   每个集合对象都有自己的迭代器
        Iterator<String> it = coll.iterator();
        //  泛型指的是 迭代出 元素的数据类型
        while(it.hasNext()){ //判断是否有迭代元素
            String s = it.next();//获取迭代出的元素
            System.out.println(s);
        }
  	}
}

tips::在进行集合元素取出时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会发生java.util.NoSuchElementException没有集合元素的错误。

2.2 迭代器的实现原理

我们在之前案例已经完成了Iterator遍历集合的整个过程。当遍历集合时,首先通过调用t集合的iterator()方法获得迭代器对象,然后使用hashNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已到达了集合末尾,停止遍历元素。

Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素,为了让初学者能更好地理解迭代器的工作原理,接下来通过一个图例来演示Iterator对象迭代元素的过程:

在调用Iterator的next方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。

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

/*
    java.util.Iterator接口:迭代器(对集合进行遍历)
    有两个常用的方法
        boolean hasNext() 如果仍有元素可以迭代,则返回 true。
            判断集合中还有没有下一个元素,有就返回true,没有就返回false
        E next() 返回迭代的下一个元素。
            取出集合中的下一个元素
    Iterator迭代器,是一个接口,我们无法直接使用,需要使用Iterator接口的实现类对象,获取实现类的方式比较特殊
    Collection接口中有一个方法,叫iterator(),这个方法返回的就是迭代器的实现类对象
        Iterator<E> iterator() 返回在此 collection 的元素上进行迭代的迭代器。

    迭代器的使用步骤(重点):
        1.使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态)
        2.使用Iterator接口中的方法hasNext判断还有没有下一个元素
        3.使用Iterator接口中的方法next取出集合中的下一个元素
 */
public class Demo01Iterator {
    public static void main(String[] args) {
        //创建一个集合对象
        Collection<String> coll = new ArrayList<>();
        //往集合中添加元素
        coll.add("姚明");
        coll.add("科比");
        coll.add("麦迪");
        coll.add("詹姆斯");
        coll.add("艾弗森");

        /*
            1.使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态)
            注意:
                Iterator<E>接口也是有泛型的,迭代器的泛型跟着集合走,集合是什么泛型,迭代器就是什么泛型
         */
        //多态  接口            实现类对象
        Iterator<String> it = coll.iterator();


        /*
            发现使用迭代器取出集合中元素的代码,是一个重复的过程
            所以我们可以使用循环优化
            不知道集合中有多少元素,使用while循环
            循环结束的条件,hasNext方法返回false
         */
        while(it.hasNext()){
            String e = it.next();
            System.out.println(e);
        }
        System.out.println("----------------------");
        for(Iterator<String> it2 = coll.iterator();it2.hasNext();){
            String e = it2.next();
            System.out.println(e);
        }


       /* //2.使用Iterator接口中的方法hasNext判断还有没有下一个元素
        boolean b = it.hasNext();
        System.out.println(b);//true
        //3.使用Iterator接口中的方法next取出集合中的下一个元素
        String s = it.next();
        System.out.println(s);//姚明

        b = it.hasNext();
        System.out.println(b);
        s = it.next();
        System.out.println(s);

        b = it.hasNext();
        System.out.println(b);
        s = it.next();
        System.out.println(s);

        b = it.hasNext();
        System.out.println(b);
        s = it.next();
        System.out.println(s);

        b = it.hasNext();
        System.out.println(b);
        s = it.next();
        System.out.println(s);

        b = it.hasNext();
        System.out.println(b);//没有元素,返回false
        s = it.next();//没有元素,在取出元素会抛出NoSuchElementException没有元素异常
        System.out.println(s);*/
    }
}

2.3 增强for(JDK5新特性)

增强for循环(也称for each循环)是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的。它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。

格式:

for(元素的数据类型  变量 : Collection集合or数组){ 
  	//写操作代码
}

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

练习1:遍历数组

public class NBForDemo1 {
    public static void main(String[] args) {
		int[] arr = {3,5,6,87};
       	//使用增强for遍历数组
		for(int a : arr){//a代表数组中的每个元素
			System.out.println(a);
		}
	}
}

练习2:遍历集合

public class NBFor {
    public static void main(String[] args) {        
    	Collection<String> coll = new ArrayList<String>();
    	coll.add("小河神");
    	coll.add("老河神");
    	coll.add("神婆");
    	//使用增强for遍历
    	for(String s :coll){//接收变量s代表 代表被遍历到的集合元素
    		System.out.println(s);
    	}
	}
}

tips: 新式for循环必须有被遍历的目标。目标只能是Collection或者是数组。新式for仅仅作为遍历操作出现。

import java.util.ArrayList;

/*
    增强for循环:底层使用的也是迭代器,使用for循环的格式,简化了迭代器的书写
    是JDK1.5之后出现的新特性
    Collection<E>extends Iterable<E>:所有的单列集合都可以使用增强for
    public interface Iterable<T>实现这个接口允许对象成为 "foreach" 语句的目标。因为foreach底层就是使用的迭代器实现的,因此,只要继承了Iterable接口的接口或者实现了Iterable接口的实现类的对象都可以使用foreach循环。

    增强for循环:用来遍历集合和数组

    格式:
        for(集合/数组的数据类型 变量名: 集合名/数组名){
            sout(变量名);
        }
 */
public class Demo02Foreach {
    public static void main(String[] args) {
        demo02();
    }

    //使用增强for循环遍历集合
    private static void demo02() {
        ArrayList<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ddd");
        for(String s : list){
            System.out.println(s);
        }
    }

    //使用增强for循环遍历数组
    private static void demo01() {
        int[] arr = {1,2,3,4,5};
        for(int i:arr){
            System.out.println(i);
        }
    }
}

第三章 泛型(JDK5新特性)

3.1 泛型概述

在前面学习集合时,我们都知道集合中是可以存放任意对象的,只要把对象存储集合后,那么这时他们都会被提升成Object类型。当我们在取出每一个对象,并且进行相应的操作,这时必须采用类型转换。

大家观察下面代码:

public class GenericDemo {
	public static void main(String[] args) {
		Collection coll = new ArrayList();
		coll.add("abc");
		coll.add("itcast");
		coll.add(5);//由于集合没有做任何限定,任何类型都可以给其中存放
		Iterator it = coll.iterator();
		while(it.hasNext()){
			//需要打印每个字符串的长度,就要把迭代出来的对象转成String类型
			String str = (String) it.next();
			System.out.println(str.length());
		}
	}
}

程序在运行时发生了问题java.lang.ClassCastException。为什么会发生类型转换异常呢? 我们来分析下:由于集合中什么类型的元素都可以存储。导致取出时强转引发运行时 ClassCastException。 怎么来解决这个问题呢?Collection虽然可以存储各种对象,但实际上通常Collection只存储同一类型对象。例如都是存储字符串对象。因此在JDK5之后,新增了泛型(Generic)语法,让你在设计API时可以指定类或方法支持泛型,这样我们使用API的时候也变得更为简洁,并得到了编译时期的语法检查。即将运行阶段才能够发现的类型转换问题提早到编译阶段发现了。

  • 泛型:可以在类或方法中预支地使用未知的类型。

tips:一般在创建对象时,将未知的类型确定具体的类型。当没有指定泛型时,默认类型为Object类型。

3.2 使用泛型的好处

上一节只是讲解了泛型的引入,那么泛型带来了哪些好处呢?

  • 将运行时期的ClassCastException,转移到了编译时期变成了编译失败。
  • 避免了类型强转的麻烦。

通过我们如下代码体验一下:

public class GenericDemo2 {
	public static void main(String[] args) {
        Collection<String> list = new ArrayList<String>();
        list.add("abc");
        list.add("itcast");
        // list.add(5);//当集合明确类型后,存放类型不一致就会编译报错
        // 集合已经明确具体存放的元素类型,那么在使用迭代器的时候,迭代器也同样会知道具体遍历元素类型
        Iterator<String> it = list.iterator();
        while(it.hasNext()){
            String str = it.next();
            //当使用Iterator<String>控制元素类型后,就不需要强转了。获取到的元素直接就是String类型
            System.out.println(str.length());
        }
	}
}

tips:泛型是数据类型的一部分,我们将类名与泛型合并一起看做数据类型。

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

public class Demo01Generic {
    public static void main(String[] args) {
        show02();
    }

    /*
        创建集合对象,使用泛型
        好处:
            1.避免了类型转换的麻烦,存储的是什么类型,取出的就是什么类型
            2.把运行期异常(代码运行之后会抛出的异常),提升到了编译期(写代码的时候会报错)
         弊端:
            泛型是什么类型,只能存储什么类型的数据
     */
    private static void show02() {
        ArrayList<String> list = new ArrayList<>();
        list.add("abc");
        //list.add(1);//add(java.lang.String)in ArrayList cannot be applied to (int)

        //使用迭代器遍历list集合
        Iterator<String> it = list.iterator();
        while(it.hasNext()){
            String s = it.next();
            System.out.println(s+"->"+s.length());
        }
    }

    /*
        创建集合对象,不使用泛型
        好处:
            集合不使用泛型,默认的类型就是Object类型,可以存储任意类型的数据
        弊端:
            不安全,会引发异常
     */
    private static void show01() {
        ArrayList list = new ArrayList();
        list.add("abc");
        list.add(1);

        //使用迭代器遍历list集合
        //获取迭代器
        Iterator it = list.iterator();
        //使用迭代器中的方法hasNext和next遍历集合
        while(it.hasNext()){
            //取出元素也是Object类型
            Object obj = it.next();
            System.out.println(obj);

            //想要使用String类特有的方法,length获取字符串的长度;不能使用  多态 Object obj = "abc";
            //需要向下转型
            //会抛出ClassCastException类型转换异常,不能把Integer类型转换为String类型
            String s = (String)obj;
            System.out.println(s.length());
        }
    }
}

3.3 泛型的定义与使用

我们在集合中会大量使用到泛型,这里来完整地学习泛型知识。

泛型,用来灵活地将数据类型应用到不同的类、方法、接口当中。将数据类型作为参数进行传递。

3.4 定义和使用含有泛型的类

定义格式:

修饰符 class 类名<代表泛型的变量> {  }

例如,API中的ArrayList集合:

class ArrayList<E>{ 
    public boolean add(E e){ }

    public E get(int index){ }
   	....
}

使用泛型: 即什么时候确定泛型。

在创建对象的时候确定泛型

例如,ArrayList<String> list = new ArrayList<String>();

此时,变量E的值就是String类型,那么我们的类型就可以理解为:

class ArrayList<String>{ 
     public boolean add(String e){ }

     public String get(int index){  }
     ...
}

再例如,ArrayList<Integer> list = new ArrayList<Integer>();

此时,变量E的值就是Integer类型,那么我们的类型就可以理解为:

class ArrayList<Integer> { 
     public boolean add(Integer e) { }

     public Integer get(int index) {  }
     ...
}

举例自定义泛型类

public class MyGenericClass<MVP> {
	//没有MVP类型,在这里代表 未知的一种数据类型 未来传递什么就是什么类型
	private MVP mvp;
     
    public void setMVP(MVP mvp) {
        this.mvp = mvp;
    }
     
    public MVP getMVP() {
        return mvp;
    }
}

使用:

public class GenericClassDemo {
  	public static void main(String[] args) {		 
         // 创建一个泛型为String的类
         MyGenericClass<String> my = new MyGenericClass<String>();    	
         // 调用setMVP
         my.setMVP("大胡子登登");
         // 调用getMVP
         String mvp = my.getMVP();
         System.out.println(mvp);
         //创建一个泛型为Integer的类
         MyGenericClass<Integer> my2 = new MyGenericClass<Integer>(); 
         my2.setMVP(123);   	  
         Integer mvp2 = my2.getMVP();
    }
}
/*
    定义一个含有泛型的类,模拟ArrayList集合
    泛型是一个未知的数据类型,当我们不确定什么什么数据类型的时候,可以使用泛型
    泛型可以接收任意的数据类型,可以使用Integer,String,Student...
    创建对象的时候确定泛型的数据类型
 */
public class GenericClass<E> {
    private E name;

    public E getName() {
        return name;
    }

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


public class Demo02GenericClass {
    public static void main(String[] args) {
        //不写泛型默认为Object类型
        GenericClass gc = new GenericClass();
        gc.setName("只能是字符串");
        Object obj = gc.getName();

        //创建GenericClass对象,泛型使用Integer类型
        GenericClass<Integer> gc2 = new GenericClass<>();
        gc2.setName(1);

        Integer name = gc2.getName();
        System.out.println(name);

        //创建GenericClass对象,泛型使用String类型
        GenericClass<String> gc3 = new GenericClass<>();
        gc3.setName("小明");
        String name1 = gc3.getName();
        System.out.println(name1);
    }
}



重写一个toString方法。

可以看到42行这里报错了。原因是泛型中不能使用基本数据类型的。因此,37行应该是Integer。


现在这个工具类写好了,但是每个方法前面都需要声明这个自定义的泛型,很繁琐。

如果能够声明一次,然后,每个方法都能够共用就好了。

此时就需要使用泛型类,也就是在类上自定义泛型。


现在把这个泛型声明到类上,那么下面的这些方法上,就都可以不用声明了。可以直接随意的使用这个T类型了。

这就是在类上自定义泛型。

那么在类上自定义了泛型,这个T具体的类型是什么时候确定的呢?

这样在一创建对象的时候,就确定了类上自定义泛型的那个T是什么了。将类中所有的T就都替换成Integer了。



此时62行就不能接受String类型的arr2了,因为在57行的时候,创建对象的时候,就已经确定了T是什么类型了,这里就是Integer类型了。因此,String的arr2就传不进去的。

因此,可以这样,再创建一个对象,传入String类型给T即可。


我们创建对象的时候,如果没有指定定义泛型的类型,那么这个T默认的就是object类型了。

现在在类上声明了自定义泛型,类中的实例方法上就不用再声明自定义泛型了。

但是工具类,里面的方法一般都是static的。

定义一个static的方法,然后使用类上声明的自定义泛型。可以看到报错了。

提示静态的不能引用非静态的类型T。

3.在类上自定义泛型不能作用于静态的方法,如果静态的方法需要使用自定义泛型,那么需要在方法上自己声明使用。

因此,类上自定义的泛型T不能作用与类中静态方法的。

只能静态方法自己来声明泛型使用。

因为类上声明的自定义泛型T,是在创建对象的时候传入类型,从而确定T是什么类型的,而static的成员方法,不需要对象就能通过类调用,因此,static上不能使用类上声明的自定义泛型T。

注意,static方法上声明的自定义泛型T跟类上声明的T不冲突的,就类似成员变量和方法中的局部变量一样的概念。并且static方法上的T不受创建对象时传入的类型的影响的。Static方法上的T是在调用这个方法的时候,传入的类型来确定的。

有了类上声明的自定义泛型,那么集合就可以79行这样写了。

可以看到是在类上声明的自定义泛型。此时79行就给E传递为String类型了。

此时这里面的方法也是String类型了。

import java.util.ArrayList;

/*
 需求: 编写一个数组 的工具类
 
 泛型类:
 
泛型类的定义格式:
	
	class 类名<声明自定义泛型>{
	
	}
 
泛型类要注意的事项:
 	1. 在类上自定义泛型的具体数据类型是在使用该类的时候创建对象时候确定的。
 	2. 如果一个类在类上已经声明了自定义泛型,如果使用该类创建对象 的时候没有指定 泛型的具体数据类型,那么默认为Object类型
 	3.在类上自定义泛型不能作用于静态的方法,如果静态的方法需要使用自定义泛型,那么需要在方法上自己声明使用。
 
 
 
 */

class MyArrays<T>{
	

	//元素翻转
	public void reverse(T[] arr){
		for(int startIndex = 0, endIndex = arr.length-1 ; startIndex<endIndex ; startIndex++,endIndex--){
			T temp  = arr[startIndex];
			arr[startIndex] = arr[endIndex];
			arr[endIndex] = temp;
		}
		
	}
	
	//
	public String toString(T[] arr){
		StringBuilder sb = new StringBuilder();
		for(int i = 0 ; i < arr.length ; i++){
			if(i==0){
				sb.append("["+arr[i]+",");
			}else if(i==arr.length-1){
				sb.append(arr[i]+"]");
			}else{
				sb.append(arr[i]+",");
			}
		}
		return sb.toString();
	}
	
	
	public static <T>void print(T[] t){
		
	
		
	}
	
	
}



public class Demo3 {
	
	public static void main(String[] args) {
		Integer[] arr = {10,12,14,19};
		
		MyArrays<Integer> tool = new MyArrays<Integer>();
		tool.reverse(arr);
		System.out.println("数组的元素:"+tool.toString(arr));
		
		MyArrays<String> tool2 = new MyArrays<String>();
		String[] arr2 = {"aaa","bbb","ccc"};
		tool2.reverse(arr2);
				
		
		ArrayList<String> list = new ArrayList<String>();
		
	}
	
}

3.5 定义含有泛型的方法

定义格式:

修饰符 <代表泛型的变量> 返回值类型 方法名(参数){  }

例如,

public class MyGenericMethod {	  
    public <MVP> void show(MVP mvp) {
    	System.out.println(mvp.getClass());
    }
    
    public <MVP> MVP show2(MVP mvp) {	
    	return mvp;
    }
}

使用格式:调用方法时,确定泛型的类型

public class GenericMethodDemo {
    public static void main(String[] args) {
        // 创建对象
        MyGenericMethod mm = new MyGenericMethod();
        // 演示看方法提示
        mm.show("aaa");
        mm.show(123);
        mm.show(12.45);
    }
}
结果:
class java.lang.String
class java.lang.Integer
class java.lang.Double
/*
    定义含有泛型的方法:泛型定义在方法的修饰符和返回值类型之间

    格式:
        修饰符 <泛型> 返回值类型 方法名(参数列表(使用泛型)){
            方法体;
        }

    含有泛型的方法,在调用方法的时候确定泛型的数据类型
    传递什么类型的参数,泛型就是什么类型
 */
public class GenericMethod {
    //定义一个含有泛型的方法
    public <M> void method01(M m){
        System.out.println(m);
    }

    //定义一个含有泛型的静态方法
    public static <S> void method02(S s){
        System.out.println(s);
    }
}



/*
    测试含有泛型的方法
 */
public class Demo03GenericMethod {
    public static void main(String[] args) {
        //创建GenericMethod对象
        GenericMethod gm = new GenericMethod();

        /*
            调用含有泛型的方法method01
            传递什么类型,泛型就是什么类型
         */
        gm.method01(10);
        gm.method01("abc");
        gm.method01(8.8);
        gm.method01(true);

        gm.method02("静态方法,不建议创建对象使用");

        //静态方法,通过类名.方法名(参数)可以直接使用
        GenericMethod.method02("静态方法");
        GenericMethod.method02(1);
    }
}



现在ArrayList是一个容器,因此,什么东西都可以放进去的。

运行,此时就报错了。

此时就是在运行的时候,jvm才知道123不能转成字符串的。因此,我们希望在编译的时候,就及早发现到这个错误。。让123加入不进去。

现在就相当于是给这个ArrayList容器贴上了一个标签一样,说这个容器中只能存放String类型的数据,其它的不能存放。此时19行就编译报错了。

并且加了泛型之后,那么就已经告诉了,只能往里面存String类型的数据,那么取出来的时候,也肯定就是String类型的数据了,因此,22行就无需进行强制类型转换了。

30行泛型写法正确。

37行错误,因为,编译的时候,只看等号左边的。也就是说,30行,编译的时候,发现list没有泛型,因此,37行get方法拿出来的就是object了,因此,就必须强转才行了。

现在这个类,假设是一个老程序员写的。当时没有泛型概念。

现在新的jdk中,支持了泛型,那么为了使用老程序员写的工具,因此,就有了两边,一边指定泛型即可的情况出现了,为了兼容老系统的程序。现在51行右边工具类返回的对象是没有泛型的。



现在传进去的对象加了泛型。而工具类中老程序写的没有泛型,因此,两边,可以只有一边写泛型,就是为了兼容。

import java.util.ArrayList;
/*
 泛型是jdk1.5使用的新特性。
 
 
  泛型的好处:
  	1. 将运行时的异常提前至了编译时。
  	2. 避免了无谓的强制类型转换 。
  	
  
泛型在集合中的常见应用:
  	
  	ArrayList<String>  list = new ArrayList<String>();  true   正确写法  推荐使用。
  	
  	ArrayList<Object>  list = new ArrayList<String>();  false  错误写法
  	ArrayList<String>  list = new ArrayList<Object>();  false  错误写法
  	
  	//以下两种写法主要是为了兼顾新老系统的兼用性问题。
  	 * 
    ArrayList<String>  list = new ArrayList();           true   正确写法
  
    ArrayList    list = new ArrayList<String>();   true   正确写法
 
注意: 泛型没有多态的概念,左右两边的数据 类型必须 要一致,或者只是写一边的泛型类型。

推荐使用: 两边都写泛型。
 
 
需求: 把一个集合中元素全部转成大写。 
 
 */


public class Demo1 {

	public static void main(String[] args) {
		ArrayList<String>  list = new ArrayList<String>();  //<String> 表示该容器只能存储字符串类型 的数据。
		
		list.add("aa");
		list.add("bb");
		list.add("cc");
		
		for(int i = 0 ; i < list.size() ; i++){
			String str =  list.get(i);
			System.out.println("大写:"+ str.toUpperCase());
		}
		
		
		MyUtil.print(list);
		
		
		ArrayList<String> list2 = MyUtil.getList();
		
		
		
	}
	
}

import java.util.ArrayList;

//是老程序员写 的。  jdk1.4的时候写的。
public class MyUtil {
	
	public static ArrayList getList(){
		
		return new ArrayList();
	}
	
	public static void print(ArrayList list){
	
		for (int i = 0; i < list.size() ; i++) {
			System.out.println(list.get(i));
		}
	}
	
}

方法上自定义泛型的使用

这样就是形参是什么类型,返回值就是什么类型,使用Object,目前看可以。

但是不满足需求,因为传进来的是字符串类型,那么返回类型也应该是字符串类型。传进来的是整型,那么返回的也应该是整型才行。

现在还是需要强制类型转换。现在的目的就是不要强制类型转换,直接返回的就是String。

这个T到底是什么呢?

T就是数据类型的一个变量。数据类型的一个占位符。而变量是先声明再使用的。而现在T这个变量,没有声明呢。那么就在返回值类型前面<>来进行声明。

可以看到,方法的返回值类型是String了。

可以看到返回的是Integer类型。

注意,在泛型中,不能使用基本数据类型的。如果要使用基本数据类型,那么要使用基本数据类型对应的包装类型。

这里的T就是一个类型的占位符,那么这个T到底是什么类型肯定是在某个时刻确定下来的。

就是调用getData方法的时候,传入实参的时候,就知道是什么类型的了,然后就将T替换成对应的类型了。

这个类型占位符符合标识符命名规则就行。

/*
 需求: 定义一个方法可以接收任意类型的参数,而且返回值类型必须 要与实参的类型一致。
 
 自定义泛型:  自定义泛型就是一个数据类型的占位符或者是一个数据类型的变量。
 
 方法上自定义泛型:
 	
 	修饰符  	<声明自定义的泛型,也就是声明自定义的返回类型>返回值类型    函数名(使用自定义泛型 ...){
 	
 	}

 
在泛型中不能使用基本数据类型,如果需要使用基本数据类型,那么就使用基本数据类型对应的包装类型。

 byte----> Byte
 short---> Short 
 int----> Integer
 long----> Long 
 
 double ----> Double 
 float -----> Float
 
 boolean-----Boolean
 
 char-------》 Character 
 
 
 方法泛型注意的事项:
 	1. 在方法上自定义泛型,这个自定义泛型的具体数据类型是在调用该 方法的时候传入实参时确定具体的数据类型的。
 	2. 自定义泛型只要符合标识符 的命名规则即可, 但是自定义泛型我们一般都习惯使用一个大写字母表示。  T Type  E Element
 	
 */


public class Demo2 {

	public static void main(String[] args) {
		String str = getData("abc");
		Integer i = getData(123);
	}
	
	
	public static <abc>abc getData(abc o){
		
		return o;
	}
}

3.6 定义含有泛型的接口

定义格式:

修饰符 interface接口名<代表泛型的变量> {  }

例如,

public interface MyGenericInterface<E>{
	public abstract void add(E e);
	
	public abstract E getE();  
}

使用格式:

1、定义类时确定泛型的类型

例如

public class MyImp1 implements MyGenericInterface<String> {
	@Override
    public void add(String e) {
        // 省略...
    }

	@Override
	public String getE() {
		return null;
	}
}

此时,泛型E的值就是String类型。

2、始终不确定泛型的类型,直到创建对象时,确定泛型的类型

例如

public class MyImp2<E> implements MyGenericInterface<E> {
	@Override
	public void add(E e) {
       	 // 省略...
	}

	@Override
	public E getE() {
		return null;
	}
}

确定泛型:

/*
 * 使用
 */
public class GenericInterface {
    public static void main(String[] args) {
        MyImp2<String>  my = new MyImp2<String>();  
        my.add("aa");
    }
}
/*
    定义含有泛型的接口
 */
public interface GenericInterface<I> {
    public abstract void method(I i);
}


/*
    含有泛型的接口,第一种使用方式:定义接口的实现类,实现接口,指定接口的泛型
    public interface Iterator<E> {
        E next();
    }
    Scanner类实现了Iterator接口,并指定接口的泛型为String,所以重写的next方法泛型默认就是String
    public final class Scanner implements Iterator<String>{
        public String next() {}
    }
 */
public class GenericInterfaceImpl1 implements GenericInterface<String>{
    @Override
    public void method(String s) {
        System.out.println(s);
    }
}


/*
    含有泛型的接口第二种使用方式:接口使用什么泛型,实现类就使用什么泛型,类跟着接口走
    就相当于定义了一个含有泛型的类,创建对象的时候确定泛型的类型
    public interface List<E>{
        boolean add(E e);
        E get(int index);
    }
    public class ArrayList<E> implements List<E>{
        public boolean add(E e) {}
        public E get(int index) {}
    }
 */
public class GenericInterfaceImpl2<I> implements GenericInterface<I> {
    @Override
    public void method(I i) {
        System.out.println(i);
    }
}


/*
    测试含有泛型的接口
 */
public class Demo04GenericInterface {
    public static void main(String[] args) {
        //创建GenericInterfaceImpl1对象
        GenericInterfaceImpl1 gi1 = new GenericInterfaceImpl1();
        gi1.method("字符串");

        //创建GenericInterfaceImpl2对象
        GenericInterfaceImpl2<Integer> gi2 = new GenericInterfaceImpl2<>();//new后面的<>中的类型可省略
        gi2.method(10);

        GenericInterfaceImpl2<Double> gi3 = new GenericInterfaceImpl2<>();
        gi3.method(8.8);
    }
}

泛型接口(在接口上自定义泛型)

1. 接口上自定义的泛型的具体数据类型是在实现一个接口的时候指定 的。

实现接口的时候来指定泛型T具体是什么类型。

此时25行就不能写object类型了。因为T已经确定为String了。

2. 在接口上自定义的泛型如果在实现接口的时候没有指定具体的数据类型,那么默认为Object类型。

此时25行就不能使用String。必须是Object了。

也就是在实现类,创建对象的时候,再指定泛型T的具体类型。

25行在实现接口的时候,那么就要告诉Demo4,我这个接口还有自定义泛型没有传类型。

图中这样就是告诉Demo4,接口中还有泛型T没有指定类型呢。

就相当于是31行这样了,此时类Demo4中又出现了T泛型。

此时在类Demo4上在声明一下自定义泛型T即可了。因为此时类中的方法要使用泛型T,因此,需要在类上进行声明了。

此时就可以将这个T延迟到创建对象的时候来指定了。

28行创建对象的时候指定泛型的具体类型,此时类Demo4中的T就会替换为String了。

如果要延长接口自定义泛型 的具体数据类型,那么格式如下:

格式:

public class Demo4 implements Dao{

}



实现这个接口的时候,没有延续自定义泛型T。

因此,实现的这个接口中的方法的参数类型都是Object的。

可以这样。

此时对于o就不需要强制类型转换了。

/*
 泛型接口
 
泛型接口的定义格式: 

	interface 接口名<声明自定义泛型>{
	
	}

泛型接口要注意的事项:
	1. 接口上自定义的泛型的具体数据类型是在实现一个接口的时候指定 的。
	2. 在接口上自定义的泛型如果在实现接口的时候没有指定具体的数据类型,那么默认为Object类型。
	
需求: 目前我实现一个接口的时候,我还不明确我目前要操作的数据类型,我要等待创建接口实现类 对象的时候我才能指定泛型的具体数据类型。
	
	如果要延长接口自定义泛型 的具体数据类型,那么格式如下:
	格式:  
		public class Demo4<T> implements Dao<T>{
		
		}
	
 */

interface Dao<T>{
	
	public void add(T t);	
}

public class Demo4<T> implements Dao<T> {

	public static void main(String[] args) {
		Demo4<String> d = new Demo4<String>();
	}

	public void add(T t){
		
	}


}

3.7 泛型通配符及上下限

当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。

通配符基本使用

泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。

此时只能接受数据,不能往该集合中存储数据。

举个例子大家理解使用即可:

public static void main(String[] args) {
    Collection<Intger> list1 = new ArrayList<Integer>();
    getElement(list1);
    Collection<String> list2 = new ArrayList<String>();
    getElement(list2);
}
public static void getElement(Collection<?> coll){}
//?代表可以接收任意类型

tips:泛型不存在继承关系 Collection list = new ArrayList();这种是错误的。

通配符高级使用----受限泛型

之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在JAVA的泛型中可以指定一个泛型的上限下限

泛型的上限

  • 格式类型名称 <? extends 类 > 对象名称
  • 意义只能接收该类型及其子类

泛型的下限

  • 格式类型名称 <? super 类 > 对象名称
  • 意义只能接收该类型及其父类型

比如:现已知Object类,String 类,Number类,Integer类,其中Number是Integer的父类

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>();
    
    getElement(list1);
    getElement(list2);//报错
    getElement(list3);
    getElement(list4);//报错
  
    getElement2(list1);//报错
    getElement2(list2);//报错
    getElement2(list3);
    getElement2(list4);
  
}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}
import java.util.ArrayList;
import java.util.Iterator;

/*
    泛型的通配符:
        ?:代表任意的数据类型
    使用方式:
        不能创建对象使用
        只能作为方法的参数使用
 */
public class Demo05Generic {
    public static void main(String[] args) {
        ArrayList<Integer> list01 = new ArrayList<>();
        list01.add(1);
        list01.add(2);

        ArrayList<String> list02 = new ArrayList<>();
        list02.add("a");
        list02.add("b");

        printArray(list01);
        printArray(list02);

        //ArrayList<?> list03 = new ArrayList<?>();//error,不能创建对象时使用。只能作为方法参数传递时候使用通配符。
    }

    /*
        定义一个方法,能遍历所有类型的ArrayList集合
        这时候我们不知道ArrayList集合使用什么数据类型,可以泛型的通配符?来接收数据类型
        注意:
            泛型没有继承概念的
     */
	public static void printArray(ArrayList<Integer> list){//这里设置为Integer,那么上面printArray(list02);就会报错。反之协程String的话,那么printArray(list01);报错。写成Object的话,两个都报错,因为泛型没有继承概念的。因此,这里写?,表示什么类型都能够接收。
	//注意,这里说的泛型没有继承概念,是指ArrayList<Integer>不是继承ArrayList<Object>的。而不是说下面的情况:
		//ArrayList<Object> al = new ArrayList<>();此时al中可以放任意类型的对象。
        //al.add(1);
        //al.add("abc");
        //使用迭代器遍历集合
        //Iterator<?> it = list.iterator();
        //while(it.hasNext()){
        //    //it.next()方法,取出的元素是Object,可以接收任意的数据类型
        //    Object o = it.next();
        //    System.out.println(o);
        //}
    }
	
    public static void printArray(ArrayList<?> list){
        //使用迭代器遍历集合
        Iterator<?> it = list.iterator();//集合是什么泛型类型,那么对应的迭代器就是什么泛型类型。也就是到时候?就是那个对应的泛型类型。
        while(it.hasNext()){
            //it.next()方法,取出的元素是Object,可以接收任意的数据类型
            Object o = it.next();//多态,针对Integer,那么o类型是Object,但是指向的是Integer的对象。
            System.out.println(o);
			//结果:此时任意泛型的集合都能够遍历了。
			//1 2 a b
        }
    }
}
import java.util.ArrayList;
import java.util.Collection;

/*
    泛型的上限限定: ? extends E  代表使用的泛型只能是E类型的子类或者本身
    泛型的下限限定: ? super E    代表使用的泛型只能是E类型的父类或者本身
 */
public class Demo06Generic {
    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);

        /*
            类与类之间的继承关系
            Integer extends Number extends Object
            String extends Object
         */

    }
    // 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
    public static void getElement1(Collection<? extends Number> coll){}
    // 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
    public static void getElement2(Collection<? super Number> coll){}
}


现在的目的就是print方法接收的集合对象只能是Integer或者是Number的。不能是String的这个。

此时就需要使用泛型中的通配符,?,匹配任意的数据类型。

现在不知道接收的集合中存储的是什么数据类型,那么就用?来表示。

?就表示任意的数据类型。但是,写不写这个?没必要,因为不写的话,就是T,那么可以是Object,也可以接收任意的数据类型的。

因此,通配符,不会单独的来使用的。

现在集合中存储什么数据类型的对象不知道,但是知道存储的元素的数据类型和Integer有关系。父亲的关系,那么就是super。

这样就是传进来的集合c,里面存储的元素的数据类型要么是Integer,要么就是Integer的父类。

看Collection<? Super Integer>,将这个看成整体,可以知道接收的是集合,这个集合里面<>存的元素的数据类型就是Integer或者Integer的父类。

? super Integer,限制了下面最低用到哪块,因此,是泛型的下限。

泛型的上限,String不是Number的子类。

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

/*
 泛型的上下限:
 
需求1: 定义一个函数可以接收接收任意类型的集合对象, 要求接收的集合对象只能存储Integer或者是Integer的父类类型数据。

需求2: 定义一个函数可以接收接收任意类型的集合对象, 要求接收的集合对象只能存储Number或者是Number的子类类型数据。

泛型中通配符: ? 
	
	? super Integer : 只能存储Integer或者是Integer父类元素。  泛型 的下限
 	
 	? extends Number : 只能存储Number或者是Number类型的子类数据。 泛型上限
 	
 */
public class Demo5 {

	public static void main(String[] args) {
		ArrayList<Integer> list1 = new ArrayList<Integer>();
		ArrayList<Number> list2 = new ArrayList<Number>();
		
		HashSet<String> set = new HashSet<String>();
		//getData(set);
		
	}
	
	//泛型的上限
	public static void getData(Collection<? extends Number> c){
		
		
	}
	
	//泛型的下限
	public static void print(Collection<? super Integer> c){
		
	}
	
}


原文地址:https://www.cnblogs.com/Leo101018/p/13858710.html