Java-泛型小解


参考了 疯狂Java讲义java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一

1. java泛型

java泛型是1.5增加的特性,本人新手上路,自己总结看,有错误希望大家多多指正!

2. 泛型的出现(摘取自疯狂Java讲义)

因为JDK1.5 增加泛型支持很大程度上都是为了让集合能记住其元素的数据类型。在没有泛型之前,一旦把一个对象“丢进”Java集合中,集合就会忘记对象的类型,把所有的对象当成Object类型处理。当程序从集合中取出对象后,就需要进行强制类型转换,这种强制类型转换不仅代码臃肿,而且容易引起ClassCastExeception异常。增加了泛型支持后的集合,完全可以记住集合中元素的类型,并可以在编译时检查集合中元素的类型,如果试图向集合中添加不满足类型要求的对象,编译器就会提示错误。增加泛型后的集合,可以让代码更加简洁、程序更加健壮(Java 泛型可以保证如果程序在编译时候没有警告,运行时就不会产生ClassCastException异常)。除此之外,Java 泛型还增加了枚举类、反射等方面的功能,泛型在反射中的用法,本书将在第18章介绍。本章不仅会介绍如何通过泛型来实现编译时检查集合元素的类型,更会深入介绍Java泛型的详细用法:包括定义泛型类、泛型接口,以及类型通配符、泛型方法等知识。

3.泛型入门

3.1 编译时不检查类型的异常

看下面的程序:

package generic;

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

public class ListErr {
    public static void main(String[] args) {
        //创建一个只想保存字符串的List集合
        List strList = new ArrayList();
        strList.add("Struts2权威指南");
        strList.add("基于J2EE的Ajax宝典");
        strList.add("轻量级J2EE企业应用实战");
        //"不小心”把一个Integer对象"丢进"了集合
        strList.add(5);// ①
        for (int i = 0; i < strList.size(); i++) {
        //因为List里取出的全部是object,所以必须强制类型转换
        //最后一个元素将出现ClassCastException异常
            String str = (String) strList.get(1); // ②
        }
    }
}

上面程序创建了一个List集合,而且只希望该List对象保存字符串对象一但我们没有办法进行任何限制,所以程序在①处“不小心”把一个 Integer 对象“丢进”了List集合中,这将导致程序在②处引发ClassCastException异常:因为程序试图把一一个 Integer对象转化为String类型。

3.2 手动实现编译时检查类型

如果希望创建一个List 对象,且该List对象中只能保存字符串类型,那我们可以扩展ArrayList,
下面程序创建了一个StrList集合类,该集合里只能存放String对象。

package generic;
import java.util.ArrayList;
import java.util.List;
public class StrList {
    private List strList = new ArrayList();

    //定义StrList的add方法
    public boolean add(String ele) {
        return strList.add(ele);
    }
    //重写get方法,将get方法的返回值类型改为String类型
    public String get(int index) {
        return (String) strList.get(index);
    }
    public int size() {
        return strList.size();
    }
    @Override
    public String toString() {
        return strList.toString();
    }
    public static void main(String[] args) {
        //创建一个指向保存字符串的List集合
        StrList strList = new StrList();
        strList.add("Struts2权威指南");
        strList.add("基于J2EE的Ajax宝典");
        strList.add("轻量级J2EE企业应用实战");
        //下面的语句不能吧Integer对象“丢进”集合中,将引起编译错误
        strList.add(5);// ①
        System.out.println(strList);
        for (int i = 0; i < strList.size(); i++) {
            //因为StrList里的元素类型就是String类型,所以无需强制类型转换
            String s = strList.get(i);
        }
    }
}

上面程序中定义的StrList 类就实现了编译时的异常检查,当程序在①处试图将一个Integer 对象添加到StrList集合中时,程序将无法通过编译。因为StrList 只接受String对象作为元素,所以①处代码可以在编译时得到错误提示。
从代码的健壮性角度来看,该方法极其有用,而且使用get0方法返回集合元素时,无须进行类型转换。
这种做法虽然有效,但局限性非常明显:程序员需要定义大量的List子类,这是一件让人沮丧的事情。

从JDK1.5以后,Java 引入了“参数化类型(parameterized type)"的概念,允许我们在创建集合时指定集合元素的类型,比如List <String>,这表明该List只能保存字符串类型的对象。Java 的参数化类型被称为泛型(Generic)。

3.3 使用泛型

对于前面的ListEr.java程序,可以使用泛型改进这个程序:

package generic;

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

public class GenericList {
    public static void main(String[] args) {
        //创建一个只想保存自古穿的List集合
        List<String> strList = new ArrayList<String>(); //①
        strList.add("Struts2权威指南");
        strList.add("基于J2EE的Ajax宝典");
        strList.add("轻量级J2EE企业应用实战");
        //下行代码将引起编译错误
        strList.add(5);   // ②
        System.out.println(strList);
        for (int i = 0; i < strList.size(); i++) {
            //下面代码无需强转
            String s = strList.get(i);  // ③
        }
    }

}

上面程序成功创建了一个特殊的List: strList, 这个List集合只能保存字符串对象,不能保存其他类型的对象。创建这种特殊集合的方法是:在集合接口、类后增加尖括号后,尖括号里放一个数据类型,即表明这个集合接口、集合类只能保存特定类型的对象。注意①处的类型声明,它指定strList不是一个任意的List,而是一个String 的List,写作: List<String>. 我们说List是带一个类型参数的泛型接口,在本例中,类型参数是String。在创建这个ArayList对象的时候也指定了一个类型参数。
上面程序将在②处引起编译异常,因为strList集合只能添加Sring对象,所以不能将Integer对象“丢进”该集合。
而且程序在③处不需要进行强制类型转换,因为strList对象可以“记住”它的所有集合元素都是String类型。
上面代码不仅更加健壮,程序再也不能“不小心”把其他对象“丢进”strList 集合中:而且程序
更加简洁,集合自动记住所有集合元素的数据类型,从而无须对集合元素进行强制类型转换。这一切,都是因为JDK1.5提供的泛型支持。下面将深入介绍泛型相关知识。

4. 深入泛型

所谓泛型:就是允许在定义类、接口时指定类型形参,这个类型形参将在声明变量、创建对象时确定(即传入实际的类型参数,也可称为类型实参)。 JDK1.5 改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参,这就是前而程序看到ListString>和ArrayList两种类型。
泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法

4.1 泛型类

泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。
泛型类的最基本写法(这么看可能会有点晕,会在下面的例子中详解):

class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
  private 泛型标识 /*(成员变量类型)*/ var; 
  .....
  }
} 

一个最普通的泛型类:

package generic;
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{ 
    //key这个成员变量的类型为T,T的类型由外部指定  
    private T key;
    public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
        this.key = key;
    }
    public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
        return key;
    }
}

或者:

class test1<String>{//定义时指定了形参  应该也算是吧(不确定是什么,但是可以这样玩)
}

测试:

package generic;

//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{ 
    //key这个成员变量的类型为T,T的类型由外部指定  
    private T key;
    public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
        this.key = key;
    }
 
    public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
        return key;
    }
    public static void main(String[] args) {
        //泛型的类型参数只能是类类型(包括自定义类),不能是简单类型
        //传入的实参类型需与泛型的类型参数类型相同,即为Integer.
        Generic<Integer> genericInteger = new Generic<Integer>(123456);
        //传入的实参类型需与泛型的类型参数类型相同,即为String.
        Generic<String> genericString = new Generic<String>("key_vlaue");
        Integer i = genericInteger.getKey();
        String str = genericString.getKey();
        System.out.println("泛型测试,key is " + i);
        System.out.println("泛型测试, key is " +str);
    }
}

结果:

泛型测试, key is 123456
泛型测试, key is key_vlaue

注意

包含泛型声明的类型可以在定义变量、创建对象时传入一个类型实参,从而可以动 态生成无数多个逻辑上的子类,但这种子类在物理上并不存在。(详见4.1.1和4.1.3)

4.1.1 类型擦除

把上面的测试案例反编译一下:

package generic;
public class Generic {
   private Object key;
   public Generic(Object key) {
      this.key = key;
   }
   public Object getKey() {
      return this.key;
   }
   public static void main(String[] args) {
      Generic genericInteger = new Generic(Integer.valueOf(123456));
      Generic genericString = new Generic("key_vlaue");
      Integer i = (Integer)genericInteger.getKey();
      String str = (String)genericString.getKey();
      System.out.println("test key is " + i);
      System.out.println("test key is " + str);
   }
}

可以看到泛型形参、实参都不见了,这叫做泛型擦除,类中的形参变成了Object(或者有的是类型形参的上限,比如public class Generic<T extends Number>{},那么所有的T擦除后都会变成Number)。返回值处给咱们加上了相对应的强制转换。

4.1.2 类型形参 类型实参 传入的实参类型

注意
定义的泛型类,在new对象时就一定要传入泛型类型实参么?并不是这样,在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。
这里我按照自己的理解解释下这几个名词:

public class Generic<T>{} 这里的T大家可以理解为 类型形参

Generic<Integer> genericInteger = new Generic<Integer>(123456);
这里的Integer 大家可以理解为 类型实参,而123456大家可以理解为 传入的实参类型

所以上面说的不一定传入类型实参大家就应该理解了,例子如下:

Generic generic = new Generic("111111");
Generic generic1 = new Generic(4444);
Generic generic2 = new Generic(55.55);
Generic generic3 = new Generic(false);

注意

泛型的类型参数只能是类类型,不能是简单类型。
不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。

if(ex_num instanceof Generic<Number>){   
}

4.1.3 特性

泛型只在编译阶段有效。看下面的代码:

package generic;

//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{ 
    //key这个成员变量的类型为T,T的类型由外部指定  
    private T key;
    public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
        this.key = key;
    }
    public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
        return key;
    }
    public static void main(String[] args) {
        Generic<Integer> genericInteger = new Generic<Integer>(123456);
        Generic<String> genericString = new Generic<String>("key_vlaue");
       System.out.println(genericInteger.getClass()==genericString.getClass());
    }
}

大家可能会认为输出false,但是结果是true

这即体现了泛型擦除,也说明了并不存在泛型类。

4.2 泛型接口

泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,可以看一个例子:

//定义一个泛型接口
package generic;

public interface Generator<T> {
    public T next();
}

当实现泛型接口的类,未传入泛型实参时:

/**
 * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
 * 即:class FruitGenerator<T> implements Generator<T>{
 * 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"
 */
package generic;

public class FruitGenerator<T> implements Generator<T>{
    private T t;
    @Override
    public T next() {
        return t;
    }
}

当实现泛型接口的类,传入泛型实参时:

package generic;
import java.util.Random;

/**
 * 传入泛型实参时:
 * 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T>
 * 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
 * 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
 * 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。
 */
public class FruitGenerator2 implements Generator<String> {
    private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
    @Override
    public String next() {
        Random rand = new Random();
        return fruits[rand.nextInt(3)];
    }
}

4.3 泛型通配符

4.3.1通配符的出现

我们知道Ingeter是Number的一个子类,同时在特性章节中我们也验证过Generic<Ingeter>Generic<Number>实际上是相同的一种基本类型。那么问题来了,在使Generic<Number>作为形参的方法中,能否使用Generic<Ingeter>实例传入呢?在逻辑上类似于Generic<Number>Generic<Ingeter>是否可以看成具有父子关系的泛型类型呢?
为了弄清楚这个问题,我们使用Generic<T>这个泛型类继续看下面的例子:

package generic;

//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{ 
	//key这个成员变量的类型为T,T的类型由外部指定  
	private T key;
	public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
		this.key = key;
	}

	public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
		return key;
	}

	public static void showKeyValue1(Generic<Number> obj){
		System.out.println("test"+"key value is " + obj.getKey() );
	}
	public static void main(String[] args) {
		Generic<Integer> gInteger = new Generic<Integer>(123);
		Generic<Number> gNumber = new Generic<Number>(456);
		showKeyValue1(gNumber);
		/**
		 * 下面这个方法编译器会为我们报错:
		 * The method showKeyValue1(Generic<Number>) in the type Generic<T>
		 *  is not applicable for the arguments (Generic<Integer>)
		 */
		//showKeyValue1(gInteger);
	}
}

通过提示信息我们可以看到Generic不能被看作为`Generic的子类。由此可以看出:同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。

注:如果FooBar的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口
那么G<Foo>G<Bar>的子类型并不成立!这一-点非常值得注意,因为它与我们的习惯看
法不同。

回到上面的例子,如何解决上面的问题?总不能为了定义一个新的方法来处理Generic<Integer>类型的类,这显然与java中的多态理念相违背。因此我们需要一个在逻辑上可以表示同时是Generic<Integer>Generic<Number>父类的引用类型。由此类型通配符应运而生。
我们可以将上面的方法改一下:

package generic;

public class Generic<T>{  
	private T key;
	public Generic(T key) { 
		this.key = key;
	}
	public T getKey(){
		return key;
	}
	public static void showKeyValue1(Generic<?> obj){
		System.out.println("test"+"key value is " + obj.getKey() );
	}
	public static void main(String[] args) {
		Generic<Integer> gInteger = new Generic<Integer>(123);
		Generic<Number> gNumber = new Generic<Number>(456);
		showKeyValue1(gNumber);
		showKeyValue1(gInteger);
	}
}

类型通配符一般是使用?代替具体的类型实参,注意了,此处’?’是类型实参,而不是类型形参 。重要说三遍!此处’?’是类型实参,而不是类型形参 ! 此处’?’是类型实参,而不是类型形参 !再直白点的意思就是,此处的?和Number、String、Integer一样都是一种实际的类型,可以把?看成所有类型的父类。是一种真实的类型。
可以解决当具体类型不确定的时候,这个通配符就是 ? ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。

注:
数组和泛型有所不同,假设Foo是Bar的一个子类型(子类或者子接口),那么Foo[] 依然是Bar[]的子类型;但G<Foo>不是G<Bar>的子类型。

4.3.2通配符(?)的使用(主要是出现在方法参数列表中中,不要和泛型的定义搞混了)

比如想要表示各种泛型List的父类,我们可以使用类型通配符, 将一个问号作为类型实参传给List 集合,写作: List<?> (意思是未知类型元素的List)。这个问号(?)被称为通配符,它的元素类型可以匹配任何类型。

package generic;

import java.util.ArrayList;
import java.util.List;
public class TestList {
    public static void get(List<?> list) {
        Object o = list.get(0);
		System.out.println(o);
    }
    public static void main(String[] args) {
        List<String> str = new ArrayList<>();
        str.add("string1");
        List<Integer> integer = new ArrayList<>();
        integer.add(521);
		get(str);
		get(integer);
    }
}

可以正确输出!
可是却无法向其中添加元素,比如:

package generic;

import com.fjl.TestUp;

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

public class TestList {
  /*...*/
	public static void add(List<?> list) {
		list.add(new Object());//编译器报错
		/*Error:(16, 21) java: 对于add(java.lang.Object), 找不到合适的方法
    方法 java.util.Collection.add(capture#1, 共 ?)不适用
      (参数不匹配; java.lang.Object无法转换为capture#1, 共 ?)
    方法 java.util.List.add(capture#1, 共 ?)不适用
      (参数不匹配; java.lang.Object无法转换为capture#1, 共 ?)*/
	}
 /*...*/
}

编译器会报错,因为编译器根本不知道传过来的集合里到底存储的到底是什么类型,(运行时,其实无论是什么,都会变成Object(或者其泛型上限)对象,可以实现动态改变)。根据List<E>接口定义的代码可以发现: add 方法有类型参数E作为集合的元素类型,所以我们传给add的参数必须是E类的对象或者其子类的对象。但因为在该例中不知道E是什么类型,所以程序无法将任何对象“丢进”该集合。唯一的例外是null,它是所有引用类型的实例。
另一方面,程序可以调用get()方法来返回List<?>集合指定索引处的元素,其返回值是一个未知类型,但可以肯定的是:它总是一个Object。因此把get()的返回值赋值给一个 Object类型的变量,或者放在任何希望是Object类型的地方都可以。

4.4 泛型方法

在java中,泛型类的定义非常简单,但是泛型方法就比较复杂了。

尤其是我们见到的大多数泛型类中的成员方法也都使用了泛型,有的甚至泛型类中也包含着泛型方法,这样在初学者中非常容易将泛型方法理解错了。

java泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。

/**
 * 泛型方法的基本介绍
 * @param tClass 传入的泛型实参
 * @return T 返回值为T类型
 * 说明:
 *     1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
 *     2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
 *     3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
 *     4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
 */
public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
  IllegalAccessException{
        T instance = tClass.newInstance();
        return instance;
}

Object obj = genericMethod(Class.forName(“com.test.test”));

4.4.1 泛型方法的基本用法

光看上面的例子有的同学可能依然会非常迷糊,我们再通过一个例子,把泛型方法再总结一下。

package generic;
public class GenericTest {
    //这个类是个泛型类,在上面已经介绍过
    public class Generic<T>{
        private T key;
        public Generic(T key) {
            this.key = key;
        }
        //我想说的其实是这个,虽然在方法中使用了泛型,但是这并不是一个泛型方法。
        //这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
        //所以在这个方法中才可以继续使用 T 这个泛型。
        public T getKey(){
            return key;
        }
        /**
         * 这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E"
         * 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。
         public E setKey(E key){
         this.key = keu
         }
         */
    }
    /**
     * 这才是一个真正的泛型方法。
     * 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T
     * 这个T可以出现在这个泛型方法的任意位置.
     * 泛型的数量也可以为任意多个
     *    如:public <T,K> K showKeyName(Generic<T> container){
     *        ...
     *        }
     */
    public <T> T showKeyName(Generic<T> container){
        System.out.println("container key :" + container.getKey());
        //当然这个例子举的不太合适,只是为了说明泛型方法的特性。
        T test = container.getKey();
        return test;
    }
    //这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic<Number>这个泛型类做形参而已。
    public void showKeyValue1(Generic<Number> obj){
        System.out.println("test,key value is " + obj.getKey());
    }
    //这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符?
    //同时这也印证了泛型通配符章节所描述的,?是一种类型实参,可以看做为Number等所有类的父类
    public void showKeyValue2(Generic<?> obj){
        System.out.println("test,key value is " + obj.getKey());
    }
    /**
     * 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
     * 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。
     * 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。
     public <T> T showKeyName(Generic<E> container){
     ...
     }
     */
    /**
     * 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' "
     * 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。
     * 所以这也不是一个正确的泛型方法声明。
     public void showkey(T genericObj){
     }
     */
    public static void main(String[] args) {

    }
}

4.4.2 类中的泛型方法

当然这并不是泛型方法的全部,泛型方法可以出现杂任何地方和任何场景中使用。但是有一种情况是非常特殊的,当泛型方法出现在泛型类中时,我们再通过一个例子看一下:

public class GenericFruit {
    class Fruit{
        @Override
        public String toString() {
            return "fruit";
        }
    }
 
    class Apple extends Fruit{
        @Override
        public String toString() {
            return "apple";
        }
    }
 
    class Person{
        @Override
        public String toString() {
            return "Person";
        }
    }
 
    class GenerateTest<T>{
        public void show_1(T t){
            System.out.println(t.toString());
        }
 
        //在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
        //由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
        public <E> void show_3(E t){
            System.out.println(t.toString());
        }
 
        //在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
        public <T> void show_2(T t){
            System.out.println(t.toString());
        }
    }
 
    public static void main(String[] args) {
        Apple apple = new Apple();
        Person person = new Person();
 
        GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
        //apple是Fruit的子类,所以这里可以
        generateTest.show_1(apple);
        //编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person
        //generateTest.show_1(person);
 
        //使用这两个方法都可以成功
        generateTest.show_2(apple);
        generateTest.show_2(person);
 
        //使用这两个方法也都可以成功
        generateTest.show_3(apple);
        generateTest.show_3(person);
    }
}

4.4.3 泛型方法与可变参数

再看一个泛型方法和可变参数的例子:

public <T> void printMsg( T... args){
    for(T t : args){
       System.out.println("test is " + t);
    }
}

printMsg(“111”,222,“aaaa”,“2323.4”,55.55);

4.4.4 静态方法与泛型

静态方法有一种情况需要注意一下,那就是在类中的静态方法使用泛型:静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上(即定义为泛型方法)。
即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。

4.4.5 泛型方法和通配符(?)的区别

大多数时候都可以使用泛型方法来代替类型通配符,例如对于JDK的Collection接口中两个方法
定义:

public interface Collection<E> extends Iterable<E> {
 boolean containsAll(Collection<?> c);
 boolean addAll(Collection<? extends E> c);
}

上面集合中两个方法的形参都采用了类型通配符的形式,也可以采用泛型方法的形式来代替它,
如下所示:

public interface Collection<E> extends Iterable<E> {
 boolean <T> containsAll(Collection<T> c);
 boolean <T extends E> addAll(Collection<T> c);
}

上面方法使用了<T extends E>泛型形式,这是定义类型形参时设定上限(其中E是Collection接口里定义的类型形参,在该接口里E可当成普通类型使用)。
上面两个方法中类型形参T只使用了一次,类型形参T的唯一效果是可以在不同的调用点传入不同的实际类型。对于这种情况,应该使用通配符:通配符就是被设计用来支持灵活的子类化的。
泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系。如果没有这样的类型依赖关系,不应该使用泛型方法。

提示:如果某个方法中一个形参(a)的类型或返回值类型依赖于另一个形参(b)的类型,则形参(b)的类型声明不应该使用通配符一因为形参(a).或返回值的类型依赖于该形参(b)的类型,如果形参(b)的类型无法确定,程序无法定义形参(a)的类型。在这种情况下,只能考虑使用在方法签名中声明类型形参。

4.4.6 泛型方法总结

泛型方法能使方法独立于类而产生变化,以下是一个基本的指导原则:

无论何时,如果你能做到,你就该尽量使用泛型方法。也就是说,如果使用泛型方法将整个类泛型化,那么就应该使用泛型方法。另外对于一个static的方法而已,无法访问泛型类型的参数。所以如果static方法要使用泛型能力,就必须使其成为泛型方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java泛型Java 5引入的新特性,可以提高代码的可读性和安全性,降低代码的耦合度。泛型是将类型参数化,实现代码的通用性。 一、泛型的基本语法 在声明类、接口、方法时可以使用泛型泛型的声明方式为在类名、接口名、方法名后面加上尖括号<>,括号中可以声明一个或多个类型参数,多个类型参数之间用逗号隔开。例如: ```java public class GenericClass<T> { private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } } public interface GenericInterface<T> { T getData(); void setData(T data); } public <T> void genericMethod(T data) { System.out.println(data); } ``` 其中,`GenericClass`是一个泛型类,`GenericInterface`是一个泛型接口,`genericMethod`是一个泛型方法。在这些声明中,`<T>`就是类型参数,可以用任何字母代替。 二、泛型的使用 1. 泛型类的使用 在使用泛型类时,需要在类名后面加上尖括号<>,并在括号中指定具体的类型参数。例如: ```java GenericClass<String> gc = new GenericClass<>(); gc.setData("Hello World"); String data = gc.getData(); ``` 在这个例子中,`GenericClass`被声明为一个泛型类,`<String>`指定了具体的类型参数,即`data`字段的类型为`String`,`gc`对象被创建时没有指定类型参数,因为编译器可以根据上下文自动推断出类型参数为`String`。 2. 泛型接口的使用 在使用泛型接口时,也需要在接口名后面加上尖括号<>,并在括号中指定具体的类型参数。例如: ```java GenericInterface<String> gi = new GenericInterface<String>() { private String data; @Override public String getData() { return data; } @Override public void setData(String data) { this.data = data; } }; gi.setData("Hello World"); String data = gi.getData(); ``` 在这个例子中,`GenericInterface`被声明为一个泛型接口,`<String>`指定了具体的类型参数,匿名内部类实现了该接口,并使用`String`作为类型参数。 3. 泛型方法的使用 在使用泛型方法时,需要在方法名前面加上尖括号<>,并在括号中指定具体的类型参数。例如: ```java genericMethod("Hello World"); ``` 在这个例子中,`genericMethod`被声明为一个泛型方法,`<T>`指定了类型参数,`T data`表示一个类型为`T`的参数,调用时可以传入任何类型的参数。 三、泛型的通配符 有时候,我们不知道泛型的具体类型,可以使用通配符`?`。通配符可以作为类型参数出现在方法的参数类型或返回类型中,但不能用于声明泛型类或泛型接口。例如: ```java public void printList(List<?> list) { for (Object obj : list) { System.out.print(obj + " "); } } ``` 在这个例子中,`printList`方法的参数类型为`List<?>`,表示可以接受任何类型的`List`,无论是`List<String>`还是`List<Integer>`都可以。在方法内部,使用`Object`类型来遍历`List`中的元素。 四、泛型的继承 泛型类和泛型接口可以继承或实现其他泛型类或泛型接口,可以使用子类或实现类的类型参数来替换父类或接口的类型参数。例如: ```java public class SubGenericClass<T> extends GenericClass<T> {} public class SubGenericInterface<T> implements GenericInterface<T> { private T data; @Override public T getData() { return data; } @Override public void setData(T data) { this.data = data; } } ``` 在这个例子中,`SubGenericClass`继承了`GenericClass`,并使用了相同的类型参数`T`,`SubGenericInterface`实现了`GenericInterface`,也使用了相同的类型参数`T`。 五、泛型的限定 有时候,我们需要对泛型的类型参数进行限定,使其只能是某个类或接口的子类或实现类。可以使用`extends`关键字来限定类型参数的上限,或使用`super`关键字来限定类型参数的下限。例如: ```java public class GenericClass<T extends Number> { private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } } public interface GenericInterface<T extends Comparable<T>> { T getData(); void setData(T data); } ``` 在这个例子中,`GenericClass`的类型参数`T`被限定为`Number`的子类,`GenericInterface`的类型参数`T`被限定为实现了`Comparable`接口的类。 六、泛型的擦除 在Java中,泛型信息只存在于代码编译阶段,在编译后的字节码中会被擦除。在运行时,无法获取泛型的具体类型。例如: ```java public void genericMethod(List<String> list) { System.out.println(list.getClass()); } ``` 在这个例子中,`list`的类型为`List<String>`,但是在运行时,`getClass`返回的类型为`java.util.ArrayList`,因为泛型信息已经被擦除了。 七、泛型的类型推断 在Java 7中,引入了钻石操作符<>,可以使用它来省略类型参数的声明。例如: ```java List<String> list = new ArrayList<>(); ``` 在这个例子中,`ArrayList`的类型参数可以被编译器自动推断为`String`。 八、总结 Java泛型是一个强大的特性,可以提高代码的可读性和安全性,降低代码的耦合度。在使用泛型时,需要注意它的基本语法、使用方法、通配符、继承、限定、擦除和类型推断等问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值