地址   http://blog.csdn.net/lirx_tech/article/details/51570138

1. 设计泛型的初衷:

1) 主要是为了解决Java容器无法记忆元素类型的问题:

i. 由于Java设计之初并不知道会往容器中存放什么类型的元素,因此元素类型都设定为Object,这样就什么东西都能放了!

ii. 但是这样设计有明显的缺点:

a. 取出元素的时候必须进行强制类型转换(尽管集合在运行时里面元素的“运行时类型”不变,即元素的getClass返回的还是最初自己的类型而不是Object);

b. 如果不小心往集合里加了不相同类型的元素可能会导致类型异常(进行equals、compare比较的时候尤为明显);

c. 由于没有类型就需要在很多地方进行强制类型转换,但是这样做增加了编程的复杂度,并且代码也不美观(臃肿),维护起来也更加困难;

2) 泛型的概念定义:

i. 从Java5开始,引入了参数化类型(Parameterized Type)的概念,改造了所有的Java集合,使之都实现泛型,允许程序在创建集合时就可以指定集合元素的类型,比如List<String>就表名这是一个只能存放String类型的List;

ii. 泛型(Generic):就是指参数化类型,上面的List<String>就是参数化类型,因此就是泛型,而String就是该List<String>泛型的类型参数;

3) 泛型的好处:

i. 使集合可以记住元素类型,即取出元素的时候无需进行强制类型转化了,可以直接用原类型的引用接收;

ii. 一旦指定了性参数那么集合中元素的类型就确定了,不能添加其他类型的元素,否则会直接编译保存,这就可以避免了“不小心放入其他类型元素”的可能;

iii. 上述保证了如果在编译时没有发出警告,则在运行时就一定不会产生类型转化异常(ClassCastException);

iv. 显然,泛型使编程更加通用,并且代码也更加简洁,代码更加容易维护;

2. 创建泛型对象——自动类型推断的菱形语法:

1) 首先,定义泛型引用一定要使用尖括号指定类型参数,例如:List<String> list、Map<String, Integer>等,其中的String、Integer之类的就是类型参数;

2) 其次,使用构造器构造泛型对象的时候可以指定类型参数也可以不指定,例如:

i. List<String> list = new List<String>();  // 这当然是对的

ii. List<String> list = new List<>();  // 这样对,因为List的类型参数可以从引用推断出!

!!但是引用的类型参数是一定要加的,否则无法推断;

3) 由于<>很像菱形,因此上面的语法也叫做菱形语法;

4) 错误提示:引用无类型参数但构造器有类型参数的写法是不对的!例如,List list = new List<String>();

!!至于为什么不对,这会在泛型原理的章节中详细介绍,这里先记住这样写不对就行了!

!反正就是一个原则,泛型引用是一定要指定类型参数的!!

5) 示例:

  1. public class Test {
  2. public static void main(String[] args) {
  3. ArrayList<String> list = new ArrayList<>();
  4. list.add("lala");
  5. list.add("haha");
  6. // list.add(5); // 类型不符,直接报错!!
  7. list.forEach(ele -> System.out.println(ele)); // 可以看到取出的ele无需强制类型转换,直接就是String类型的
  8. // 说明泛型集合能记住元素的类型,代码简洁了很多
  9. HashMap<String, Integer> map = new HashMap<>();
  10. map.put("abc", 15);
  11. map.put("def", 88);
  12. map.forEach((key, value) -> System.out.println(key + " : " + value)); // 可以看到key、value同样无需强制类型转化
  13. }
  14. }

3. 定义泛型类、接口:

1) 不仅Java的集合都定义成了泛型,用户自己也可以定义任意泛型的类、接口,只要在定义它们时用<>来指定类型参数即可;

2) 例如:public class Fruit<T> { ... },其中<T>指定了该泛型的类型参数,这个T是一个类型参数名,用户可以任意命名(就像方法参数的形参名一样),只有在定义该泛型的对象时将T替换成指定的具体类型从而产生一个实例化的泛型对象,例如:Fruit<String> fruit = new Fruit<>(...);

3) 类型形参可以在整个接口、类体内当成普通类型使用,集合所有可使用普通类型的地方都可以使用类型形参,例如:

  1. public interface MyGneric<E> {
  2. E add(E val);
  3. Set<E> makeSet();
  4. ...
  5. }

!!可以看到,在接口内/类体内甚至还可以使用该类型形参运用泛型!例如上面makeSet方法返回一个泛型Set;

4) 定义泛型构造器:泛型的构造器还是类名本身,不用使用菱形语法,例如

  1. public class MyGenric<T> {
  2. MyGeneric(...) { ... }
  3. ...
  4. }

!定义构造器无需MyGeneric<T>(...) { ... }了,只有在new的时候需要用到菱形语法;

4. 实现/继承泛型接口/泛型类:

1) 定义泛型和使用泛型的概念:主要区别就是定义和使用

i. 那Java的方法做类比,Java的方法在定义的时候使用的都是形参(虚拟参数),但是在调用方法(使用方法)的时候必须传入实参;

ii. 同样泛型也有这个特点,泛型的类型参数和方法的参数一样,也是一种参数,只不过是一种特殊的参数,用来表示未知的类型罢了;

iii. 因此,泛型也是在定义的时候必须使用形参(虚拟参数,用户自己随意命名),但是在使用泛型的时候(比如定义泛型引用、继承泛型)就必须使用实参,而泛型的实参就是具体的类型,像String、Integer等具体的类型(当然也可以是自定义类型);

2) 泛型定义的时候使用形参,例如:public class MyGeneric<T> { ... }  // T就是一个自己随意命名的类型形参

3) 使用泛型的时候必须传入实参:

i. 定义引用(对象)的时候毫无疑问,肯定需要传实参:ArrayList<String> list = ...;   // 必须用具体的类型,像这里就是String来代替形参,即实参

ii. 实现/继承一个泛型接口/类的时候:

!!你在实现/继承一个接口/类的时候实际上是在使用该接口/类,比如:public class Son extends Father { ... }中Father这个类就是正在被使用,毫无疑问,必定是在使用;

!!因此泛型其实无法继承/实现,因为在实现/继承的时候必须为泛型传入类型实参,给定实参后它就是一个具体的类型了,就不再是泛型了

!!示例:public class MyType extends MyGeneric<String> { ... } // implements、extends的时候必须传入类型实参,因为实在使用泛型!!

!!原则上,任何编程语言都不允许泛型模板层层继承!!

4) 继承之后,父类/接口中的所有方法中的类型参数都将变成具体的类型,你在子类中覆盖这些方法的时候一定要用具体的类型,不能继续使用泛型的类型形参了,例如:

  1. class Father<T> {
  2. T info;
  3. public Father(T info) {
  4. this.info = info;
  5. }
  6. public T get() {
  7. return info;
  8. }
  9. public T set(T info) {
  10. T oldInfo = this.info;
  11. this.info = info;
  12. return this.info;
  13. }
  14. }
  15. class Son extends Father<String> { // 所有从父类继承来的方法的类型参数全部都确定为String了
  16. // 因此在覆盖的时候都要使用具体的类型实参了!
  17. public Son(String info) {
  18. super(info);
  19. }
  20. @Override
  21. public String get() {
  22. return "haha";
  23. }
  24. @Override
  25. public String set(String info) {
  26. return "lala";
  27. }
  28. }

!!这一定能保证,这三个方法都是从父类中继承来的,只不过类型形参T被实例化成了String;

5. 泛型参数继承:

1) 上面派生出来的类不是泛型,是一个实体类型,因为其继承的泛型是具有类型实参的,而Java还支持一种特殊的语法,可以让你从泛型继续派生出泛型,而泛型的类型参数可以继续传承下去;

2) 语法如下:

  1. class Father<T> { ... }
  2. class Son<T> extends Father<T> { ... }

!即子泛型可以传承父泛型的泛型参数,那么在子类中泛型参数T就和父类的完全相同,还是照常使用(和父类一样正常使用);

3) 注意:

i. 这里extends Father<T>了,因此父类泛型Father就是被使用了,而按照之前讲的规则,使用给一个泛型是必须要指定类型实参的!因此这里的这个语法是一种特殊语法,Java专门为这种语法开了后门,这种语法只有在类型参数传承的时候才会用到(即上面这种应用);

ii. 一旦使用了这种语法,就表示要进行类型参数的传承了(即父类的T传递给子类继续使用,因此子类也是一个跟父类一样的泛型);

iii. 并且一旦使用了这种语法,那么子类定义中的Son<T>和extends Father<T>中的类型参数必须和定义父类时的类型参数名完全一样!!

a. 以下三种情况全部错误(全部发生编译报错):

  1. class Father<T> { }
  2. class Son<E> extends Father<T> { }
  3. class Father<T> { }
  4. class Son<T> extends Father<E> { }
  5. class Father<T> { }
  6. class Son<E> extends Father<E> { }

!!必须全部使用和父类定义相同的类型参数名(T)!才行,这是Java语法的特殊规定;

4) 其实Java容器中很多类/接口都是通过类型参数传承来定义的:

i. 最典型的例子就是:public interface List<T> extends Collection<T> { ... }

ii. 虽然"如果A是B的父类,但Generic<A>不是Generic<B>"的父类,但"如果A是B的父类,那A<T>一定是B<T>的父类"!这是一定的;

iii. 因为类型参数传承的定义方式本身就是:Son<T> extends Father<T>,那Father<T>一定是Son<T>的父类咯!

6. 在使用泛型的时候可以不使用菱形语法指定实参,直接裸用类型名:

1) 例如:

i. 定义引用(对象)时裸用类名:ArrayList list = new ArrayList(); // 当然也可以写成ArrayList list = new ArrayList<>();

ii. 实现/继承:public class MyType extends MyGeneric { ... }

!!上面使用的类型或者接口在定义的时候都是泛型!!但是使用它们的时候忽略类型参数(都不用加菱形);

2) Java规定,一个泛型无论如何、在任何地方、不管如何使用,它永远都是泛型,因此这里既是你忽略类型实参它底层也是一个泛型,那么它的类型实参会是什么呢?既然我们没有显式指定,那么Java肯定会隐式为其指定一个类型实参吧?

3) 答案是肯定的,如果使用泛型的时候不指定类型实参,那么Java就会用该泛型的“原生类型“来作为类型实参传入!

!!那么“原生类型“是什么呢?这里先不介绍,会在下一章的”泛型原理“里详细分解;

!!但是我们这里可以先透露一下,Java集合的原生类型基本都是Object,因此像上面的ArrayList list = new ArrayList();写法其实传入的是Object类型实参,即ArrayList<Object>!

Java泛型:泛型的定义(类、接口、对象)、使用、继承的更多相关文章

  1. 优雅地创建未定义类PHP对象

    在PHP中,如果没有事先准备好类,需要创建一个未定义类的对象,我们可以采用下面三种方式: new stdClass() new class{} (object)[] 首先是stdClass,这个类是一 ...

  2. 我所了解的关于JavaScript定义类和对象的几种方式

    原文:http://www.cnblogs.com/hongru/archive/2010/11/08/1871359.html 在说这个话题之前,我想先说几句题外话:最近偶然碰到有朋友问我“hois ...

  3. JavaScript定义类与对象的一些方法

    最近偶然碰到有朋友问我"hoisting"的问题.即在js里所有变量的声明都是置顶的,而赋值则是在之后发生的.可以看看这个例子: 1 var a = 'global'; 2 (fu ...

  4. Java中直接输出一个类的对象

    例如 package com.atguigu.java.fanshe; public class Person { String name; private int age; public Strin ...

  5. javascript定义类或对象的方式

    本文介绍的几种定义类或对象的方式中,目前使用最广泛的是:混合的构造函数/原型方式.动态原型方式.不要单独使用经典的构造函数或原型方式. 工厂方式 构造器函数 原型方式 混合的构造函数/原型方式 动态原 ...

  6. Javascript学习6 - 类、对象、继承

    原文:Javascript学习6 - 类.对象.继承 Javasciprt并不像C++一样支持真正的类,也不是用class关键字来定义类.Javascript定义类也是使用function关键字来完成 ...

  7. 大数据学习day14-----第三阶段-----scala02------1. 元组 2.类、对象、继承、特质 3.函数(必须掌握)

    1. 元组 映射是K/V对偶的集合,对偶是元组的最简单的形式,元组可以装着多个不同类型的值 1.1 特点 元组相当于一个特殊的数组,其长度和内容都可变,并且数组中可以装任何类型的数据,其主要用处就是存 ...

  8. 类和对象:继承 - 零基础入门学习Python038

    类和对象:继承 让编程改变世界 Change the world by program 上节课的课后作业不知道大家完成的怎样?我们试图模拟一个场景,里边有一只乌龟和十条鱼,乌龟通过吃鱼来补充体力,当乌 ...

  9. 黑马程序员——【Java基础】——面向对象(一)概述、类与对象、继承、抽象类、接口、多态、内部类

    ---------- android培训.java培训.期待与您交流! ---------- 一.面向对象概述 1.面向对象:是一个很抽象的概念,它相对面向过程而言,是一种程序设计的思想. 2.面向对 ...

  10. JAVA基础第三章-类与对象、抽象类、接口

    业内经常说的一句话是不要重复造轮子,但是有时候,只有自己造一个轮子了,才会深刻明白什么样的轮子适合山路,什么样的轮子适合平地! 我将会持续更新java基础知识,欢迎关注. 往期章节: JAVA基础第一 ...

随机推荐

  1. 解决div嵌套时IE8和FF无法自适应高度

    解决div嵌套时IE8和FF无法自适应高度 还是做类似新浪评论回复的时候,将回复的DIV嵌套在一个DIV中,然后点击回复的时候显示子DIV,这是父DIV的高度是会变化的,于是我将父DIV的高度设置为h ...

  2. springMvc架构简介

    什么是spring 关于spring的定义无论是从官方还是市面上已经很多能够清晰明了的做出解释了.我姑且简单定义它为一个轻量级的控制反转(IoC)和面向切面(AOP)的容器,Java 开发框架,至于控 ...

  3. 【Leetcode 167】Two Sum II - Input array is sorted

    问题描述:给出一个升序排列好的整数数组,找出2个数,它们的和等于目标数.返回这两个数的下标(从1开始),其中第1个下标比第2个下标小. Input: numbers={2, 7, 11, 15}, t ...

  4. [转载]树莓派新版系统上使用mjpg-streamer获取USB摄像头和树莓派专用摄像头RaspiCamera图像

    树莓派新版系统上使用mjpg-streamer获取USB摄像头和树莓派专用摄像头RaspiCamera图像 网上有很多关于mjpg-stream移植到树莓派的文章,大部分还是使用的sourceforg ...

  5. java代码----数据类型的转换-----int --->String

    总结:int ----->String package com.a.b; //测试..char--->int // int--->String public class Yue2 { ...

  6. 初学者手册-Sublime Text常用快捷键

    Alt + F3 :找出当前文档中所有被划选的词语,若文档很大的话,可能会导致Sublime Text崩溃. Ctrl + kkk :删除当前行光标至行尾的所有内容. End: 光标跳至行尾. Hom ...

  7. orzdba_monitor.sh脚本使用

    1.orzdba_monitor.sh脚本使用 ./orzdba_monitor.sh 主要是用nohup同时在后台调用orzdba,启动下面三个命令 [root@node02 scripts]# p ...

  8. JDBC的复习

    什么是JDBC JDBC(Java DataBase Connectivity)就是Java数据库连接,说白了就是用Java语言来操作数据库.原来我们操作数据库是在控制台使用SQL语句来操作数据库,J ...

  9. CDH5.10 添加kafka服务

    简介: CDH的parcel包中是没有kafka的,kafka被剥离了出来,需要从新下载parcel包安装.或者在线安装,但是在线安装都很慢,这里使用下载parcel包离线安装的方式. PS:kafk ...

  10. Gson:自定义TypeAdapter

    当前项目解析json用的工具是google的gson,原因嘛,因为有GsonFormat插件,可以直接把服务端传回的json字符串转成Bean对象.不过在实际使用中出现了以下两个问题: 传回的字符串或 ...