强烈推荐阅读:

夯实 JAVA 基本之一 —— 泛型详解

夯实 JAVA 基本之二——高级进阶

为什么需要泛型?

如一个问题:Set set1 = new Set();定义了一个Cat类;向set1集合中添加cat对象;当遍历set1时候,需要用到Iterator迭代器;Iterator it =set1.iterator();其中需要用到的it.next()的方法的返回值是Object类型的,如果要调用Cat类中定义的方法就需要把返回值转型为Cat类型。但如果错误地写成:String c1 = (String)it.next();编译是不会报错的,只有在运行时才会报错。

可以发现,如果不使用泛型:集合中添加的是Object类的对象,就会(1)涉及强制类型转换;(2)可向集合中添加任意类型的对象,存在风险;

为了避免上述问题:需要在创建Set的时候就制定Set中存入对象的类型:Set<Cat> set1 = new Set<Cat>();


泛型的使用语法:

(1)如 List<String> list = new ArrayList<String>();

(2)Java SE7及以后的版本中,构造方法中可以省略泛型类型,即也可以写作: List<String> list = new ArrayList<>();


多态和泛型:

(1)错误示例:父类 Animal,子类Cat继承Animal类, List <Animal> list = new ArrayList<Cat>();这样写是错误的;即前后的类型必须要一致。

(2) List <Object> list = new ArrayList<String>();和 ~~List <Number> numbers = new ArrayList<Integer>()~~也是错误的;前后的类型必须要一致,也就是变量声明的类型必须匹配传递给实际对象的类型。

疑问:List<Animal> list = new ArrayList<Animal>();如果Cat是Animal的子类,Cat类型的对象可不可以存入list? 实测发现是可以的,但好像不提倡这么做。


1.泛型作为方法参数

父类:Goods类;

子类:Book类、Clothes类、Shoes类;

工具类:GoodsSeller类;

测试类:GoodsTest;

(注:(1)这其中也涉及了多态的应用;(2)核心是 ** sellGoods(List <? extends Goods> goods) **)

public abstract class Goods {
 
	public abstract void sell();
 
}

public class Book extends Goods {
 
	@Override
	public void sell() {
		// TODO Auto-generated method stub
		System.out.println("Book类");
	}
 
}

public class Clothes extends Goods {
 
	@Override
	public void sell() {
		// TODO Auto-generated method stub
		System.out.println("Clothes类");
 
	}
 
}

public class Shoes extends Goods {
 
	@Override
	public void sell() {
		// TODO Auto-generated method stub
		System.out.println("Shoes类");
 
	}

测试类GoodsSeller,注意其 sellGoods(List <? extends Goods> goods) 的写法:

注:(1) sellGoods(List <? extends 接口> 接口的实现类对象): 即extends后面也可以跟一个接口的。

(2) sellGoods(List <? super Goods> goods):泛型参数可以是Goods或Goods的超类。)

public class GoodsSeller {
 
	public void sellGoods(List<? extends Goods> goods){
		// 遍历Set集合
		for(Goods g : goods){
			g.sell();
		}
	}
 
}

public class GoodsTest {
 
	public static void main(String[] args) {
 
		List<Book> books = new ArrayList<Book>();
		books.add(new Book());
		books.add(new Book());
		List<Clothes> clothes = new ArrayList<Clothes>();
		clothes.add(new Clothes());
		clothes.add(new Clothes());
		List<Shoes> shoes = new ArrayList<Shoes>();
		shoes.add(new Shoes());
		shoes.add(new Shoes());
 
		GoodsSeller goodsSeller = new GoodsSeller();
		goodsSeller.sellGoods(books);
	}
 
}

以上写法是正确写法。

注:

(1)如果GoodsSeller类里,sellGoods方法参数如下时:

测试类里的方法调用会出错:

出现了上面的错误,如果把List <Book> books = new ArrayList <Book>();改成List <Goods> books = new ArrayList <Goods>();。不会报错,而且会正常执行。

但是,如果这样,books集合中也可以添加Clothes和Shoes类型的对象了,失去了泛型本来的意义。

所以为了根本解决这个问题,在测试类里面改成如上所示的: sellGoods(List <? extends Goods> goods) 就能解决:


2.【泛型作为方法参数】和【方法重载】的关联

这儿到底想表达什么,至今还不明白,,,,,。这是自定义泛型方法。

(1)一个新的写法:public <T extendx Number> void peintValue(T t):该方法的参数类型不仅可以是Number类型的,也可以是Number的子类类型的。这就是泛型之所以为 _ _ 型的意义吗?后续,需要在实际应用中继续加深对泛型的理解。这种写法,应该是自定义泛型了,下篇博客会讲到

相较于涉及泛型的方法,和具体类型的方法,实际调用时候,还是会选择更亲近的那一个。


3.?和T的区别

自定义泛型类

可以发现形如:ArrayList<String> list = new ArrayList<String>();

或者ArrayList<Integer> list = new ArrayList<Integer>();

或者ArrayList<Cat> list = new ArrayList<Cat>();

ArrayList(这个类的功能就是存储数据)是一个类,这个类后面的<>中可以添加任意的类型;ArrayList就是一个泛型类,通过ArrayList的源码发现,在定义ArrayList类的时候,其参数使用了E,即并不确定该类到底存储什么样类型的数据,,用了E暂时占着这个坑,做一个虚假的代表,表示ArrayList可以存储E类型的数据或E的子类类型的数据。

通过ArrayList这个泛型类的定义,可以结合E这个占坑的魔法字符,构建自己的泛型类。


1.NumGeneric类使用了<T>这种形式,表示类中的T可以代表任意类型的数据,从而使得该类为泛型类。这个类中的public T num;中T到底是什么类型的数据暂不确定,等到具体使用的时候,给他赋什么类型的数据,这个类型的数据就会替换掉T这个占坑符。

自然,不同于ArrayList类,NumGeneric类的功能并不是存储数据,但该类广泛的数据类型也体现了泛型类的特点。

​ ​

public class NumGeneric <T>{
	public T num;
 
	public T getNum(){
		return num;
	}
	public void setNum(T num){
		this.num = num;
	}
 
	// 测试
	public static void main(String[] aegs){
		NumGeneric<Integer> intNum = new NumGeneric<Integer>();
		intNum.setNum(10);
		System.out.println(intNum.getNum());
 
		NumGeneric<Float> floatNum = new NumGeneric<Float>();
		floatNum.setNum(5.7f);
		System.out.println(floatNum.getNum());
	}
 
}

2.NumGenericTwo类,演示的是两个 <T,X>的形式。

	  public class NumGenericTwo<T,X> {
private T num1;
private X num2;

 
	public void genNum(T num1,X num2){
		this.num1 = num1;
		this.num2 = num2;
	}
 
	public T getNum1() {
		return num1;
	}
 
	public void setNum1(T num1) {
		this.num1 = num1;
	}
 
	public X getNum2() {
		return num2;
	}
 
	public void setNum2(X num2) {
		this.num2 = num2;
	}
 
	// 测试
	public static void main(String[]args){
		NumGenericTwo<Integer,Float> numObj = new NumGenericTwo<>();
		numObj.genNum(25, 6.8f);
		System.out.println(numObj.getNum1()+"  "+numObj.getNum2());
	}
}


注:

(1)具体为什么需要自定义泛型类,还不清楚,以前也没有遇到过,等得到具体业务场景使用时,可能会加深理解;

自定义泛型方法

自定义泛型方法:

(1)自定义泛型类:是类中的属性的类型可以为多种;

(2)自定义泛型方法,则是方法的参数类型可以为多种;

(3)泛型方法不一定写在泛型类里;

(4) 自定义泛型方法的实际应用常见还不清楚,知道定义形式后,以防以后遇到不认识;后续加深理解

public class GenericMethod {
 
	// 想打印某个对象的值,但预先并不知道该对象的是什么类型的
	// (1)泛型方法不一定写在泛型类里;(2)<T>必须写在访问修饰符和返回值之间;(3)知道有这种形式,以后看到不会感到陌生;
	public <T> void printValue(T t){
		System.out.println(t);
	}

	// 测试
	public static void main(String[] args){
		GenericMethod genericMethod = new GenericMethod();
		genericMethod.printValue("hello");
		genericMethod.printValue(123);
		genericMethod.printValue(89f);
 
	}
 
}

运行结果:

hello
123
89.0

注:

(1) 形如<T extends Number>指定一个参数类型的范围。

(2) 如上图,尚未执行程序就报错了,显示,自定义泛型方法如果采用<T extends Number>这样指定了类型的范围,如果不在这个范围内,在程序编译阶段就会报错。