其实早在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. Eureka服务下线源码解析

    我们知道,在Eureka中,可以使用如下方法使Eureka主动下线,那么本篇文章就来分析一下子这个下线的流程 public synchronized void shutdown() { if (isS ...

  2. 「白帽黑客成长记」Windows提权基本原理(下)

    上一篇文章我们介绍了信息收集方法和WMIC,今天我们将跟随作者深入学习Windows提权基本原理的内容,希望通过这两篇文章的讲解,大家能够真正掌握这个技能. 推荐阅读:「白帽黑客成长记」Windows ...

  3. SimpleTagSupport 获取request、session

    开发jsp系统时,我们经常会用到tag来写java的逻辑代码,一般会继承两个类,一个是SimpleTagSupport,另一个是TagSupport,由于TagSupport书写配置比较复杂(我个人才 ...

  4. pandas - groupby 深入及数据清洗案例

    import pandas as pd import numpy as np 分割-apply-聚合 大数据的MapReduce The most general-purpose GroupBy me ...

  5. 【DB_MySQL】MySQL日志分析

    MySQL数据库常见的日志有:错误日志(log_error).慢查询日志(slow_query_log).二进制日志(bin_log).通用日志(general_log) 开启慢查询日志并分析 开启慢 ...

  6. python关于SSL的认证--pycurl模块使用

    今天在做微信支付退款接口的时候,因为需要使用到双向证书的认证,所以一开始是没有头绪的,后来在网上找到了相类似的教程,发现了pycurl模块,才成功实现了证书认证,教程链接:http://blog.cs ...

  7. linux 的 expect 自动交互

    https://www.jianshu.com/p/0194cbd70d39 https://www.cnblogs.com/saneri/p/10819348.html  参考 expect是一个自 ...

  8. Error: EACCES: permission denied, mkdir

    今天在全局安装飞冰的时候,出现标题的错误 想到是权限不够的问题,其实飞冰官网也有解决的办法,就是更改npm的默认路径.我之前就是用了更改默认路径的方法,然后后来又恢复了默认路径,所以还是用默认路径加权 ...

  9. 每次都能让人头大的 Shader -- 从一次简单的功能说起

    最近有个功能, 要渲染从主相机视角看到的另一个相机的可视范围和不可见范围, 大概如下图 : 简单来说就是主相机视野和观察者相机视野重合的地方, 能标记出观察者相机的可见和不可见, 实现原理就跟 Sha ...

  10. jmeter压测学习9-响应断言

    前言 使用jmeter做接口压测的时候,如何能保证接口的每次返回结果都是我们预期的呢?这就需要添加检查点,也就是添加断言. 添加断言就是为了检查返回的结果与我们的预期是一致的,不用去一个个检查结果. ...