18.Java泛型

时间:2022-12-01 10:00:06

1.为什么需要泛型

List list1=new ArrayList(Arrays.asList(new String("string"),new Integer(20)));
String str=(String)list.get(0);//强制向下转型
System.out.println(str.matches("y\\w+"));

说明:

  • Java在1.5版本的时候引入了泛型机制,list1就是没有使用泛型的容器,可以向容器中添加任何类的对象
  • 传入容器的对象都是Object对象,从容器中取出对象的时候可以强制向下转型(很有可能出错,只有在运行时才会报错)

引入泛型的目的:

  • 1.限制添加进容器的元素的类型
  • List<String> list=new ArrayList();
    list.add(new String("Hello world"));
    list.add(new Integer(12));//编译时就会报错
    String str=list.get(0);//不需要转型
  • 2.代码复用(或者说减少冗余的代码)

2.泛型的写法

2.1 泛型类

语法:class ClassName<E>{//code}

class Cell<T>{
private T t;
public Cell(T t){
this.t=t;
}
}

说明:

  • 1.泛型并不是一种新的类型,Cell<Integer>和Cell<String>都是Cell类,原始类Cell,Cell<Integer>,Cell<String>是Cell的两种不同的泛型调用。
  • 即Cell和Cell<E>是同一个类,后者称为有类型参数的泛型类。
  • Cell<String> cell2=new Cell<String>("Hello");
    System.out.println(cell1.getClass()==cell2.getClass());//true,Class对象相同所以是同一种类型
  • 2.静态函数、静态成员不能使用类的类型参数、静态内部类不能使用外部类的类型参数
  • 理由是静态函数只能使用静态属性,静态函数属性和内部类不依赖于类的对象,也就是说使用静态函数的时候,参数化类型还没有被初始化,因此不能使用。
  • 3.泛型的类型参数不存在继承关系前后必须一致
  • List<Number> list=new ArrayList<Integer>();//wrong
    List<Integer> list=new ArrayList<Integer>();//right

2.2 泛型函数

语法:public<E> E f(E e){//code}

public class Main {
public static void main(String[] atgs) {
System.out.println(new MyClass().f(20));//调用泛型函数
}
} class MyClass{
public<E> E f(E e){
return e;
}
}

问题:怎么使用

  • 方式1不指定泛型:Serializable b=Main.dosomthing(new String(),new Integer(1));//最小公共父类是 Serializable
  • 方式2指定泛型:Number a=Main.<Number>dosomthing(1,2.2);//指定参数化类型为Number
  • 例子:
  • public static void main(String[] args) {
    /**不指定泛型的时候*/
    int i=Test2.add(1, 2); //这两个参数都是Integer,所以T为Integer类型
    Number f=Test2.add(1, 1.2);//这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级,为Number
    Object o=Test2.add(1, "asd");//这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级,为Object /**指定泛型的时候*/
    int a=Test2.<Integer>add(1, 2);//指定了Integer,所以只能为Integer类型或者其子类
    int b=Test2.<Integer>add(1, 2.2);//编译错误,指定了Integer,不能为Float
    Number c=Test2.<Number>add(1, 2.2); //指定为Number,所以可以为Integer和Float
    } //这是一个简单的泛型方法
    public static <T> T add(T x,T y){
    return y;
    }

3.泛型进阶

3.1有界的类型参数—对类型参数的类型进行限制

  • List<T> T没有限制
  • List<T extends Number> Number是T的上界,传入容器中的类必须是Number或者Number的子类
  • List<T extends Integer & Comparable<T>> 可以有多个上界,但是上界里只能有一个类,可以有多个接口,而且它们中的函数都可以使用。
  • 备注:List<Number> List<Integer>是没有继承关系的

3.2 参数化类型的数组

  • 1.泛型和数组之间有一个矛盾,数组必须知道具体的类型,但是泛型的参数化类型恰恰确定不了具体的类型,不能直接创建泛型数组
  • E[] arrays=new E[10];//wrong
  • E[] arrays=null;//right
  • 2.不能实例化带有参数化类型的数据
  • ArrayList<String>[] arrays=new ArrayList<String>[10];//wrong
  • ArrayList<String>[] arrays=new ArrayList[10]; //right----arrays[0]中只能存放String类型的对象

3.3 通配符

1.问题:

  • List<Number> 与 List<Integer>之间没有任何继承关系,所以sum函数不能接受List<Integer>作为参数
public class Main {
public static void main(String[] atgs) {
ArrayList<Integer> arrays=new ArrayList<Integer>();
arrays.add(new Integer(1));
arrays.add(new Integer(1));
arrays.add(new Integer(1));
System.out.println(Main.sum(arrays));//wrong
} public static double sum(List<Number> arrays){
int sum=0;
for(Number integer:arrays){
sum+=integer.doubleValue();
}
return sum;
}
}

 解决

  • 使用通配符?可疑解决
  •     public static double sum(List<? extends Number> arrays){
    int sum=0;
    for(Number integer:arrays){
    sum+=integer.doubleValue();
    }
    return sum;

2.通配符的界限----有点难理解

  • 通配符与类型参数并不相同,只能有一个边界,也就是说不能这么写,List<? extends Number &Comparable>//wrong
  • a.*通配符List<?> ------不能存入元素除了null
  • 取出来的元素是Object,List<?> 相当于 List<? extends Object>不是List<Object>
  • 不能像其中添加任何的对象
  • b.有上届的通配符List<? extends Number> 取出来的元素是Number----不能存入元素除了null
  • List<? extends Animal>的子类是List<Animal> List<Bird> List<Cat>
  • 但是传入容器中的参数类型是不定的(假设可以),可能是Animal,Bird,Cat
  • 传入的参数是未知的,还会有冲突,所以java为了保护其类型一致,禁止向这种容器中添加除了null之外的所有类型数据
  • c.有下界的通配符List<? Super Number>  取出来的元素是Object-----可以存入Number以及子类对象,但是不能存入Number的父类
  • List<? super Animal> 是 List<? super Bird>的子类型
  • List<? super Animal> 只能添加Animal及其子类,原因是Animal的子类可以向上转型为Animal类的对象,但是Animal的父类的类型不确定,所以不能添加到容器中。

总结:

  • 通配符修饰的容器,向其中添加元素的限制很多,比如List<?>,List<? extends Number>都不能向里面添加元素非null元素,List<? super Number>只能添加Number以及它的子类。
  • 一般用作函数的参数
  • public void f(List<?>){//code}
  • public void f(List<? extends Number>){//code}   传入List<Number>、List<Integer>都是可以的
  • public void f(List<? super Integer>){//code}  传入List<? super Number>可以的,List<? super Number>是List<? super Integer>的子类(子集)
  • 例子:
  • public static void main(String[] atgs) {
    List<? super Number> list=new ArrayList<>();
    list.add(new Integer(1));
    list.add(new Double(3.9));
    System.out.println(sum(list));
    } public static double sum(List<? super Integer> arrays){
    double sum=0;
    for(Object integer:arrays){
    Number number=(Number) integer;
    sum+=number.doubleValue();
    }
    return sum;
    }

    备注:List<? super Number> 是 List<? super Integer>的子类,所以可以执行

4.泛型擦除

4.1泛型擦除概述

Java的泛型是伪泛型。为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉。正确理解泛型概念的首要前提是理解类型擦除(type erasure)。 Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。

4.2 类型擦除的表现

  • 1.List<Integer>和List<Number>的类型相同
  • 原因就是编译的时候,由于类型擦除,均变成了List<Object>,这里给了我们一个信息,那么运行的时候是不是就可以向容器中添加任意类型的数据了,因为他们都是Object的子类.
  • 2.运行时向容器中添加任意元素
  • public static void main(String[] atgs) {
    List<String> list=new ArrayList<>();
    //list.add(new Integer(1));编译时添加wrong
    try {
    Method add=list.getClass().getMethod("add",Object.class);
    add.invoke(list,new Integer(1));//运行时添加可以
    add.invoke(list,new String("yangyun"));
    add.invoke(list,new Double(1.23));
    add.invoke(list,new Float(1.1234));
    } catch (Exception e) {
    e.printStackTrace();
    }
    System.out.println(list);
    }
  • 利用反射在运行的时候,可以向list中添加任何元素

4.3 泛型的写法

  • a.泛型的写法
  • 1.ArrayList list=new ArrayList<Integer>();------这里的参数化类型没有作用(有什么作用?)
  • 2.ArrayList<Integer> list=new ArrayList<>();---类型参数有作用
  • 3.new ArrayList<String>().add("yangyun");-----类型参数有作用
  • 真正涉及类型检查的是它的引用,因为我们是使用它引用 list 来调用它的方法,比如说调用add()方法。所以arrayList1引用能完成泛型类型的检查

4.4 运行时取值-自动类型转换

  • List<String>编译期擦除之后变为原始类型 List<Object>,那么取值的时候的类型自动转换是怎么做的?
  • 通过参考文献的3.2可以知道,转型是在调用get函数的地方进行的。

4.5 泛型与函数覆盖

class Father<E>{
private E e; public void setE(E e) {
this.e = e;
} public E getE() {
return e;
}
} class Son extends Father<String>{
@Override
public void setE(String string){
super.setE(string);
} @Override
public String getE(){
return super.getE();
}
}

说明上边的继承关系中,Father是一个泛型类,Son继承自Father<String>,并覆盖了父类中的函数。

问题:覆盖应该是参数一样的,父类中的函数的参数编译后擦除应该是Object,子类的中的函数参数是String,为什么构成覆盖?

解决:J于是JVM采用了一个特殊的方法,来完成这项功能,那就是桥方法。

桥方法:就是子类中,参数为Object的函数,我们自己写的函数回去调用桥方法,间接的实现重载

说明:参考文献2中有具体的例子

5.泛型知识点补充

5.1 泛型与内部类

内部类可以使用外部类的一切数据,包括类型参数,反过来外部类不可以使用内部类的类型参数,但是外部类可以利用内部类的对象使用内部类的private属性(在外部类的范围内)

5.2 使用泛型类的时候应该指出具体的类型参数的类型

5.3 类型

System.out.println(list instanceof ArrayList<?>);//可以,但是通配符不能设置界限
System.out.println(list instanceof ArrayList<String>);//不行

5.4 利用反射创建泛型数组

说明:我们知道参数类型数组不能直接实例化,那么我们如何返回泛型数组,答案就是利用反射

public class Main<E>{
public static void main(String[] atgs) {
Main<String> obj=new Main<>();
String[] arrays=obj.getArrays(2);
Array.set(arrays,0,"123");
Array.set(arrays,1,"yangyun");
Array.set(arrays,2,"Hello"); System.out.println(Arrays.toString(arrays));
} public E[] getArrays(int length) {
return (E[])Array.newInstance(String.class,3);
}
}

参考文献

http://blog.csdn.net/baple/article/details/25056169

https://my.oschina.net/fuyong/blog/719013

http://www.cnblogs.com/penghongwei/p/3300094.html   //特别好的Java反射资料

18.Java泛型的更多相关文章

  1. java泛型 8 泛型的内部原理:类型擦除以及类型擦除带来的问题

    参考:java核心技术 一.Java泛型的实现方法:类型擦除 前面已经说了,Java的泛型是伪泛型.为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉.正确理解泛型概念的首 ...

  2. Java泛型介绍!!!

    Java总结篇系列:Java泛型  转自:http://www.cnblogs.com/lwbqqyumidi/p/3837629.html 一. 泛型概念的提出(为什么需要泛型)? 首先,我们看下下 ...

  3. Java 泛型 协变式覆盖和泛型重载

    Java 泛型 协变式覆盖和泛型重载 @author ixenos 1.协变式覆盖(Override) 在JDK 1.4及以前,子类方法如果要覆盖超类的某个方法,必须具有完全相同的方法签名,包括返回值 ...

  4. java 泛型基础问题汇总

    泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法. Java语言引 ...

  5. Java泛型type体系

    最近看开源代码,看到里面很多Java泛型,并且通过反射去获取泛型信息.如果说要看懂泛型代码,那还是比较容易,但是如果要自己利用泛型写成漂亮巧妙的框架,那必须对泛型有足够的了解.所以这两三天就不在不断地 ...

  6. Java 泛型完全解读

    对于泛型的使用我想大家都非常熟悉,但是对于类型擦除,边界拓展等细节问题,可能不是很清楚,所以本文会重点讲解一下:另外对泛型的了解其实可以看出,一个语言特性的产生逻辑,这对我们平时的开发也是非常有帮助的 ...

  7. Java——泛型

    前言 一般的类和方法,使用的都是具体的类型:基本类型或者自定义的类.如果我们要编写出适用于多种类型的通用代码,那么肯定就不能使用具体的类型.前面我们介绍过多态,多态算是一种泛化机制,但是也会拘泥于继承 ...

  8. java泛型(二)、泛型的内部原理:类型擦除以及类型擦除带来的问题

    微信公众号[程序员江湖] 作者黄小斜,斜杠青年,某985硕士,阿里 Java 研发工程师,于 2018 年秋招拿到 BAT 头条.网易.滴滴等 8 个大厂 offer,目前致力于分享这几年的学习经验. ...

  9. Java泛型之类型擦除

    类型擦除 学过C++模板的,在使用Java泛型的时候,会感觉到有点不疑问,例如:(1)无法定义一个泛型数组.无法调用泛型参数对象中对应的方法(当然,通过extends关键字是可以做到,只是比较麻烦): ...

随机推荐

  1. 图片访问实时处理的实现&lpar;nodejs和php&rpar;

    我在访问时光网.网易云音乐等网站时,发现将它们页面中的一些图片URL修改一下就可以得到不同尺寸的图片,于是思考了其实现方案,我的思路是:URL Rewrite + 实时处理 + 缓存,对用户请求的UR ...

  2. MVC项目中ExecutionTimeout不生效的解决方案

    我们做web服务器端开发时,经常会遇到一个需求场景,因为某些耗时处理造成页面的响应处理时间超长,技术角度就想能否给页面处理程序一个指定的超时时间,服务端处理程序执行时间超过这个指定的超时时间则中断处理 ...

  3. MySQL5&period;7&period;11安装

    1.官网下载mysql-5.7.11-winx64.zip 2.将压缩包解压至D:\Program Files,配置环境变量

  4. C&sol;C&plus;&plus;&colon;提升&lowbar;头文件的使用

    C/C++:提升_头文件的使用 ◇写在前面 学到现在,很多人编写程序时只会使用一个文件.这样在代码量较小的时候,更利于表达程序,但是随着代码量的逐步增加,程序的思维逻辑不是我们一下子就可以完全理清的, ...

  5. JavaScript变量声明

    javascript是一种无类型语言,无类型只是意味着用户不必显示地声明变量的数据类型,但是javascript仍然将根据需要自动进行数据类型转换的. javascript的数据类型可以分为简单数据类 ...

  6. 【转】android中Uri&period;parse&lpar;&rpar;用法

    1,调web浏览器 Uri myBlogUri = Uri.parse("http://xxxxx.com"); returnIt = new Intent(Intent.ACTI ...

  7. python 读取文本

    将文本转换到NumPy 数组中,做机器学习或其他任何任务,文本处理的技能必不可少.python 实现实现了很精简强大的文本处理功能: 假设 文件 traindata.csv 中有数据 1000行,3列 ...

  8. Visual Studio2012 Lua插件--BabeLua

    之前,找了好久VS2012的Lua插件,没有找到. 今天在http://www.cocoachina.com/bbs/read.php? tid-205043.html 看到了.cocos2dx-qu ...

  9. IEnumerable

    C#基础之IEnumerable 1.IEnumerable的作用 在使用Linq查询数据时经常以IEnumerable<T>来作为数据查询返回对象,在使用foreach进行遍历时需要该对 ...

  10. 再起航,我的学习笔记之JavaScript设计模式29&lpar;节流模式&rpar;

    节流模式 概念介绍 节流模式(Throttler): 对重复的业务逻辑进行节流控制,执行最后一次操作并取消其他操作,以提高性能. 优化滚动事件 有的时候我们再为滚动条添加动画的时候,会发现滚动条不停的 ...