其实早在1999年的JSR 14规范中就提到了泛型概念,知道jdk5泛型的使用才正式发布,在jdk7后,又对泛型做了优化,泛型的推断.

泛型类

public class Pair<T> {
private T first;
private T second; public Pair() {
first = null;
second = null;
} public Pair(T first, T second) {
this.second = second;
this.first = first;
} public void setFirst(T newValue) {
first = newValue;
} public void setSecond(T newValue) {
second = newValue;
}
}

怎么理解泛型类的定义.首先在类名后根上<T>这个T就是任意类型.在 Java 库中, 使用变量 E 表示集合的元素类型, K 和 V 分别表示表的关键字与值的类型。T ( 需要时还可以用临近的字母 U 和 S) 表示“ 任意类型”。然后在类中的成员,都可以使用这个T,你既可以把T当做参数,也可以把T当做返回值.也可以把T当做成员变量的类型.这个T到底存储的什么类型,取决于你在实例化Pair时指定的具体类型.但是以上写法,你一旦指定了一个实际类型,那么这个类中所有的T都会是同一个类型.

public class Main {

    public static void main(String[] args) {
Pair<String> pair = new Pair<>();
pair.setFirst("第一");
pair.setSecond("第二");
}
}

你也可以在一个泛型类上定义多个泛型

public class Pair<T,U> {
private T first;
private U second; public Pair() {
first = null;
second = null;
} public Pair(T first, U second) {
this.second = second;
this.first = first;
} public void setFirst(T newValue) {
first = newValue;
} public void setSecond(U newValue) {
second = newValue;
}
}

但是你需要记得,因为泛型的作用域在类级别.一下写法是错误的.

你在类上定义了个T表示,你所实例化的每一个类都要指定一个类型,现在,现在你试图不做类的实例化,而直接使用T,那么这个T你要从哪里定义呢?记住要使用泛型,先确定泛型的具体类型.

泛型方法

public class Demo3 {
public <T> void show(T t){
System.out.println(t.toString());
}
public static <S> void show2(S s){
System.out.println(s);
}
public class Main {
public static void main(String[] args) {
Demo3 demo3 = new Demo3();
demo3.<String>show("a");
demo3.show("a");
Demo3.show2(1);
}
}

你在一个方法上指定了泛型,即泛型的作用域在方法体上,也就是说,你每次调用方法都要指定具体的类型.当然你不用每次调用都使用<T>语法,因为jdk7的泛型推断.编译器自然可以通过你的实参而推断出你想要的实际类型.将泛型定义到方法上,泛型的T可以用到参数,方法体,返回值.

当然在你指定泛型时,也可以有以下写法

public class Demo1 {
public <String> void add(String t){
}
}

不过这通常是没有任何意义的,否则,你要想表达什么呢?定义了一个泛型方法,并且限定泛型的实际类型是String?

泛型的擦除

泛型的擦除可谓是泛型中的重中之重了.字面意思,泛型类型会被擦除.引起两个问题:1在什么情况下擦除2被擦除后的的类什么样.

文档说明泛型只在编译器用来检测类型,编译时即会擦除泛型.所以泛型是在编译时被擦除的.下面看一下代码

public class Demo4<T> {
private T type; public void add(T t) {
}
}

可以看出在无限定类型时(没有使用extends 或 super 限定泛型)在编译后原来的T被替换成了Object.

泛型表达式

看以下代码

public class Pair<T> {
private T first;
private T second; public Pair() {
first = null;
second = null;
} public Pair(T first, T second) {
this.second = second;
this.first = first;
} public T getFirst() {
return first;
} public void setFirst(T first) {
this.first = first;
} public T getSecond() {
return second;
} public void setSecond(T second) {
this.second = second;
}
}
public class Main {
public static void main(String[] args) {
Pair<Student> pair = new Pair<>();
Student s = pair.getFirst();
}
}

前边已经说过,对于无限定类型,在编译时会擦掉泛型的而变成object.那么以上这个Main中运行的代码,pair通过get()方法的返回值确可以直接赋值给Student这又是怎么回事呢?

这是两个class反编译后的.可以看到,在get()方法后,编译器帮我们自动做了类型转换

泛型与多态的冲突(桥方法)

public class Pair<T> {
private T first;
private T second; public Pair() {
first = null;
second = null;
} public Pair(T first, T second) {
this.second = second;
this.first = first;
} public T getFirst() {
return first;
} public void setFirst(T first) {
this.first = first;
} public T getSecond() {
return second;
} public void setSecond(T second) {
this.second = second;
}
}
public class PairChild extends Pair<Person> {
@Override
public void setFirst(Person first) {
super.setFirst(first);
}
}

我们继承了Pair并且指定了他的具体类型.那么我们覆盖Pair方法时就只能传入Person类型的参数了.那么,在已经编译好的Pair.class中,setFirst()应该还是Object类型.这时我们到底算是覆盖父类的方法了吗?

Pair.class

PairChild.class

其中object参数的方法就是桥方法,当我们使用setFirst时,会先调用这个桥方法,这个桥方法,会将object强制转换成Person然后在调用PairChild自己的setFirst().

打破泛型的约束

有时间泛型对待数据类型也不是绝对安全的.请看一下示例

public class Pair<T> {
private T first;
private T second; public Pair() {
first = null;
second = null;
} public Pair(T first, T second) {
this.second = second;
this.first = first;
} public T getFirst() {
return first;
} public void setFirst(T first) {
this.first = first;
} public T getSecond() {
return second;
} public void setSecond(T second) {
this.second = second;
}
}
public class PairSort  {
public void sort(Pair pair){
pair.setFirst("可是我是字符串");
}
}
public class Main {
public static void main(String[] args) {
PairSort pairSort = new PairSort();
Pair<Person> pair = new Pair();
pairSort.sort(pair);
Person first = pair.getFirst();
}
}

我们有一个PairSort类,这个类接受一个Pair实例,但是并没有指定泛型,也就是说,它现在接受的是一个Object.然后给他的却是一个指定了Person类型的Pair.这时我们在调用Pair就获得了一个错误.

约束与限制

1 不能使用基本类型实例化类型参数,我们不能传递基本数据类型当做泛型的具体类型,原因是当泛型擦除后需要转换成具体的object子类.而基本数据类型,并不能转换成object

2 检查一个对象的类型不能带泛型参数.看一下代码

3 不能创建参数化类型的数组

4 不能实例化类型变量

5 尽管有泛型的擦除,但是在静态中依然不能使用泛型

6 泛型类中不能覆盖父类的方法

通配符

请看以下错误代码

public class Pair<T> {
private T first;
private T second; public Pair() {
first = null;
second = null;
} public Pair(T first, T second) {
this.second = second;
this.first = first;
} public T getFirst() {
return first;
} public void setFirst(T first) {
this.first = first;
} public T getSecond() {
return second;
} public void setSecond(T second) {
this.second = second;
}
}
public class Employee {
private String name; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
}
public class Manager extends Employee {
}
public class Main {

    public static void main(String[] args) {
Pair<Manager> pair = new Pair<>();
printBuddies(pair);//错误的
} public static void printBuddies(Pair<Employee> p) {
Employee first = p.getFirst();
Employee second = p.getSecond();
System.out.println(first.getName());
}
}
printBuddies()需要一个Pair类型参数,我们指定Pair的泛型类型是Employee.当我们传递实参时,传递的是Pair<Manager>类型的实参,这是不正确的.Pair<Employee>和Pair<Manager>没有父子关系.
他们都只是Pair类型.那么对于这种问题有没有解决方案的?
public class Main {

    public static void main(String[] args) {
Pair<Manager> pair = new Pair<>();
printBuddies(pair); Pair<Employee> employeePair = new Pair<>();
printBuddies(employeePair);
} public static void printBuddies(Pair<? extends Employee> p) {
Employee first = p.getFirst();
Employee second = p.getSecond();
System.out.println(first.getName());
}
}

使用 ? extends Employee 指定Pair的泛型类型是Employee或者是其子类.这样就不会出现编译异常

看一下对通配符的使用

public class Main {
public static void main(String[] args) {
Pair<Manager> pair = new Pair<>();
Pair<? extends Employee> pair1 = pair;
pair.setFirst(new Employee());//错误的
pair.setFirst(new Manager());
Employee first = pair.getFirst();
}
}

我们创建一个Manager类型的Pair.将他赋值给Pair<Employee>这是没错的.但是当我们调用set方法则会出现编译异常.原因是编译器知道我们要传入Employee的子类型但是不知道具体传入的是哪个子类型所以

拒绝编译.但是get方法则没有问题,因为返回是一个Employee

超类通配符

public class Main {

    public static void main(String[] args) {
printBuddies(new Pair<Employee>());
printBuddies(new Pair<Manager>());
} public static void printBuddies(Pair<? super Manager> p) {
Object first = p.getFirst();
Object second = p.getSecond();
}
}

我们限定printBuddies的参数为Manager或者其父类.

由于我们希望传入Manager或者其父类所以get方法拒绝我们用一个Manager接收.它无法确定我们到底传入的是Manager还是其父类所以只能用Object接收.

无限定通配符

虽是无限定通配符,但是他的使用限定是最大的.我们甚至无法使用set方法.除非传递一个null.Object都不行.在get方法时我们也只能用Object来接收.那么为什么要有这样一个鸡肋的通配符呢?

<T>泛型,广泛的类型的更多相关文章

  1. 泛型T的类型获取

    T.getClass()或者T.class都是非法的,因为T是泛型变量. 由于一个类的类型是什么是在编译期处理的,故不能在运行时直接在Base里得到T的实际类型. /** * 可以在service层直 ...

  2. 使用C#反射中的MakeGenericType函数,来为泛型方法和泛型类指定(泛型的)类型

    C#反射中的MakeGenericType函数可以用来指定泛型方法和泛型类的具体类型,方法如下面代码所示这里就不多讲了,详情看下面代码一切就清楚了: using System; using Syste ...

  3. Java泛型-内部原理: 类型擦除以及类型擦除带来的问题

    一:Java泛型的实现方法:类型擦除 大家都知道,Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除.Java的泛型基本上都是在编译 ...

  4. C# 泛型多种参数类型与多重约束 示例

    C# 泛型多种参数类型与多重约束 示例 interface IMyInterface { } class Dictionary<TKey, TVal> where TKey : IComp ...

  5. Gson通过借助TypeToken获取泛型参数的类型的方法

    最近在使用Google的Gson包进行Json和Java对象之间的转化,对于包含泛型的类的序列化和反序列化Gson也提供了很好的支持,感觉有点意思,就花时间研究了一下. 由于Java泛型的实现机制,使 ...

  6. Java进阶(四)Java反射TypeToken解决泛型运行时类型擦除问题

    在开发时,遇到了下面这条语句,不懂,然习之. private List<MyZhuiHaoDetailModel> listLottery = new ArrayList<MyZhu ...

  7. Java 8新特性探究(三)泛型的目标类型推断

    简单理解泛型 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.通俗点将就是"类型的变量".这种类型变量可以用在类.接口和方法 ...

  8. 如何在运行时(Runtime)获得泛型的真正类型

    前言 由于Java 的类型擦除机制,在编译时泛型都被转为了Object,例如List<String>经过编译之后将变为类型 List.可以通过以下的方式再运行时获得泛型的真正类型 泛型如何 ...

  9. Gson通过借助TypeToken获取泛型参数的类型的方法(转)

    最近在使用Google的Gson包进行Json和Java对象之间的转化,对于包含泛型的类的序列化和反序列化Gson也提供了很好的支持,感觉有点意思,就花时间研究了一下. 由于Java泛型的实现机制,使 ...

  10. java 泛型没有协变类型, 所以要重用extends, 但使用List<? extends Fruit> 可以是ArrayList<Fruit>()、ArrayList<Apple>()、ArrayList<Orange>(), 因此不能add元素进去

    class Fruit{} class Apple extends Fruit{} class SubApple extends Apple{} class Orange extends Fruit{ ...

随机推荐

  1. AD的故事继续在Sharepoint里续演

    本以为AD的开发深入工作,可能暂时先放放了,这不最近又一次接触了SP的知识领域,又一次见到了久违相识的老朋友AD域控了,让我没有想到其实服务器这块儿微软的份额这么大...不得不深思微软的核心业务是啥, ...

  2. VUE-父组件和子组件

    1.父组件 const cnp2 = Vue.extend({ template: ` <div> <h2>我是构造器2</h2> <cpn1>< ...

  3. SQL Server 使用union all查询多个条件数据合并分组显示,同比统计

    ),a.created_yearmonth,) created_yearmonth, a.countaccount countaccount, a.yxsl yxsl, a.sccdsl sccdsl ...

  4. 解决Vue调用springboot接口403跨域问题

    最近在做一个前后端分离的项目, 前端用的是Vue后端使用的是springboot, 在项目整合的时候发现前端调用后端接口报错403跨域请求问题 前端跨域请求已解决, 那么问题就出在后端了, 找了一些资 ...

  5. [基础]斯坦福cs231n课程视频笔记(二) 神经网络的介绍

    目录 Introduction to Neural Networks BP Nerual Network Convolutional Neural Network Introduction to Ne ...

  6. vue导出文件下载

    项目当中有用到文件的导出功能,以此来总结 request({ /*url: this.exportUrl,*/ url: `************`, method: "GET" ...

  7. 05showLoading配置和 <text>标签的坑 如何发送请求 分享功能和懒加载

    14-电影-列表-需求分析 小程序里面取数据 没有冒号这么一说 加载动画 在对应页面 js文件中 showLoading你可以去看他的配置     // wx.showLoading() 应用在让用户 ...

  8. JS三座大山再学习 ---- 异步和单线程

    本文已发布在西瓜君的个人博客,原文传送门 前言 写这一篇的时候,西瓜君查阅了很多资料和文章,但是相当多的文章写的都很简单,甚至互相之间有矛盾,这让我很困扰:同时也让我坚定了要写出一篇好的关于JS异步. ...

  9. 201871010132--张潇潇--《面向对象程序设计(java)》第十五周学习总结

    博文正文开头格式:(2分) 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.co ...

  10. 【oracle】 months_between(date1,date2)

    (20090228,20080228)====12 (20090228,20080229)====12 (20080229,20070228)====12 (20100331,20100228)=== ...