大多数人,都低估了编程学习的难度,而高估了自己的学习能力和毅力。
当前系列: J&C 修改讲义

为什么需要?

之前我们完成了作业:通过Object可以实现某种重用(reuse),比如:让一个数组/栈可以装任何类型的对象(int,String,Student……)

很多时候,我们“潜意识”里面,期望的是:

  • 这个数组/栈可以装各种类型的元素,
  • 但是,这些所有的元素应该是同一种类型的
Object[] students = { 32, 18, 19 };
Object[] students = { "lang", "atai", "bo" }; 
不然的话,
Object[] students = { 32, "48", true }; 
把数组里面元素取出来用的时候,你不知道取出来的对象究竟是什么类型,没法用!
System.out.println((int)students[1] * 2);

@想一想@:这种说法能不能成立?为什么?

数组里装着什么类型的数据,开发人员自己知道的呀……


泛型类

在普通类上加一个<T>就诞生了泛型(Generic)
class MimicStack<T> {
	public T pop() {    //注意返回的是T,不是Object或者int
		return null;    //模拟实现
	}	
}
语法点:
  • T:类型参数(Type parameter)/占位符(placeholder),可以理解为“斗地主”的花牌/癞子,参数命名惯例:
    • E:element,元素
    • T:type,类型(C#中总是使用T开头)
    • K:key,键
    • V:value,值
    • N:number,数字
  • 定义构造函数时不需要加上泛型参数
    public MimicStack() { //... }
  • 可以有多个类型参数,用,分开,但最好不要太多……
    class MimicStack<T1,T2> {

具象化

“泛型”类还不是一个类,声明一个泛型类,实际上并没有确定一个类型

MimicStack<T> stack = null;    //报错

只有在指定了泛型类型参数的时候,才确定了一个类型(我将其称之为具象化

MimicStack<Integer> stack = null;

PS:因为Java的“伪”泛型特征,Java中的基本类型(比如:int),不能作为泛型参数,所以这里用Integer代替;C#中没有这种限制。

使用了不同类型参数的泛型类,不是同一个类:

MimicStack<Integer> stack = new MimicStack<Integer>();    //OK
//error: can not convert
//MimicStack<String> stack = new MimicStack<Integer>();    


#常见面试题:为什么需要泛型?

类型安全(type safety):

MimicStack<Integer> stack = new MimicStack<Integer>();		
  • 输入:编译时检查,尽早的暴露问题(相对于object而言)
    stack.push("32");  //error: not applicable
  • 输出:直接了当的确定泛型类型
    Integer out = stack.pop();
    //不再是 Object out = stack.pop();
    


泛型接口和方法

泛型还可以用于接口/方法(以及我们后面要学的其他语法部分),但没有泛型enum。

  • 泛型接口:
    interface MimicStack<T> {
    	public void push(T element);
    	public T pop();
    }
  • 泛型方法:
    static <T> T grow(T year) {    //泛型参数的声明在返回前
    	return null;
    }
    int result = grow(23);
    注意区别:泛型方法 vs 泛型类中使用了泛型参数的方法。

PS:C#中泛型方法的写法和Java略有不同


类型约束/界限

之前我们的泛型类型参数可以是任何类型,但有时候我们希望该参数类型有所约束,比如:必须是Student类及其子类。

class MimicStack<T extends Student> {
这样的话:
MimicStack<Student> stack = new MimicStack<Student>();    //OK
MimicStack<OnlineStudent> stack = new MimicStack<OnlineStudent>();    //OK
MimicStack<Integer> stack = new MimicStack<Integer>();    //和Student无关,不行
MimicStack<Person> stack = new MimicStack<Person>();    //是Student的基类,不行

还可以约束参数必须实现了某个接口。

class Person implements IMove {}
class MimicStack<T extends IMove> {
MimicStack<Person> stack = new MimicStack<Person>();    //OK

注意:无论是基类还是接口,统一使用extends

可以为多个类型参数定义不同的约束

还可以要求类型参数既继承了某个基类,还实现了某个接口:

class MimicStack<T extends Student&IMove> {    //使用&连接基类和接口
class OnlineStudent extends Student implements IMove{
MimicStack<OnlineStudent> stack = new MimicStack<OnlineStudent>();


仅仅是Type parameter不同,不算是不同的泛型类

class Person<T extends Major> { }
//class Person<T extends SQL> { }  



@想一想@:为什么需要这种约束呢?提示:

<T> T Add(T a, T b) {
    return a + b;  //为什么会报错?
}


PS:C#中泛型约束写法略有不同


泛型类的继承

一个已经具象化的泛型类可以认为就是一个非泛型类,非泛型类和泛型类之间可以互相继承:

class MimicStack<T> extends Person {
class Person extends MimicStack<Integer>{

一个非泛型类不能继承一个还没有具象化的泛型类

class Person extends MimicStack<T>{  //编译错误:T不是类
但是,泛型类可以继承一个非泛型类(又被称之为延迟具象
class Student<T> extends MimicStack<T> {
这时候,如果父类的泛型参数有约束:
class MimicStack<T extends Person> {

那么,

//父子泛型类可以有相同的约束
class Student<T extends Person> extends MimicStack<T> {
//子类可以有比父类更“严格”的约束(Teacher是Person的子类)
class Student<T extends Teacher> extends MimicStack<T> {

(泛型)父类变量一样可以装(泛型)子类对象

MimicStack<Student> stack = new Student()<Student>();
注意:父子类两者都使用了相同的泛型参数。

更深的迷茫

@想一想@:使用父子类作为泛型参数,具象化同一个泛型类,得到的两个泛型对象,能有啥关系不?

class MimicStack<T extends Person>  {
MimicStack<Person> stack = new MimicStack<Student>();

会报错,但实际上,还是有救的……详见:


作业

分别使用C#/Java各自语法后完成:

  1. 改造Entity类,让其Id可以为任意类型
  2. 声明一个泛型的Repository(仓库)类,可以存储任意Entity对象,在里面添加一个静态方法Load(id),方法返回一个new出来的entity对象(模拟id返回对应的entity)
  3. 用泛型改造:
    1. 双向链表和MimicStack栈:存取元素类型扩展
    2. 取数组中最大值(提示:利用Comparable<T>进行比较)
    3. 二分查找
  4. 选择上题中的链表,分别用代码实现:
    1. 改造链表本身,使其只能存储Content对象(及其子对象,如Article等)
    2. 链表本身不改变,但具象化的时候,指定链表中元素(仅有Java实现
      1. 只能是Content对象
      2. 至少是Article对象
学习笔记
源栈学历
今天学习不努力,明天努力找工作

作业

觉得很 ,不要忘记分享哟!

任何问题,都可以直接加 QQ群:273534701

在当前系列 J&C 中继续学习:

下一课: J&C:Lambda表达式

多快好省!前端后端,线上线下,名师精讲

  • 先学习,后付费;
  • 不满意,不要钱。
  • 编程培训班,我就选源栈

更多了解 加:

QQ群:273534701

答疑解惑,远程debug……

B站 源栈-小九 的直播间

写代码要保持微笑 (๑•̀ㅂ•́)و✧

公众号:源栈一起帮

二维码