为了账号安全,请及时绑定邮箱和手机立即绑定

第不知道多少天 泛型

标签:
Java

什么是泛型

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。

 

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

 

广泛的类型,类,接口和方法代码可以应用于非常广泛的类型,代码与他们能够操作的数据类型不再绑定在一起,同一套代码,可以用于多种数据类型,这样不仅可以复用代码,降低耦合,同时可以提高代码的可读性和安全性.

 

使用场景:数据类型不确定

例如:现在要存储一个学生的成绩,但是成绩有可能想存为数字,小数,或者字符串(优秀,良好,好)之类的数据。这种数据都是类型不确定的。 
可以使用Object来存储该成绩,但是这样存储的话会把所有类型都当做Object来对待。从而”丢失”了实际的数据类型。获取数据的时候也需要转换类型,效率低,还容易出错。

 

一个简单的泛型类

public class Pair<T> {

 

    T first;

    T second;

    

    public Pair(T first, T second){

        this.first = first;

        this.second = second;

    }

    

    public T getFirst() {

        return first;

    }

    

    public T getSecond() {

        return second;

    }

}

Pair类引入了类型变量T 用<>括起来,并放在类名之后.

类的属性的类型也是T

T也是类方法的中返回类型

 

怎么使用这个泛型类

用具体的类型代替类型变量就可以实例化泛型

Pair<Integer> minmax = new Pair<Integer>(1,100);

Integer min = minmax.getFirst();

Integer max = minmax.getSecond();

<Integer>表示传入的类型是Integer

这个类型是可以变化的

Pair<String> kv = new Pair<String>("name",”nm");

泛型类可以有多个类型变量

public class Pair<U, V> {

 

    U first;

    V second;

    

    public Pair(U first, V second){

        this.first = first;

        this.second = second;

    }

    

    public U getFirst() {

        return first;

    }

 

    public V getSecond() {

        return second;

    }

}

使用

Pair<String,Integer> pair = new Pair<String,Integer>("name",100);

简单来说,泛型类可以看作普通类的工厂

 

泛型类型的命名

泛型类型变量使用大写形式,并且比较短  在java中变量E表示集合的元素类型,K和V分别表示表的关键字与值的类型. T(需要时还可以使用临近的字母U和S)表示任意类型

注意:

Jdk1.7以后 构造函数中可以省略泛型类型 ArrayList<String> files  = new ArrayList<>();

泛型不能使用在静态属性或者静态方法上也不能用在全局常量上

泛型的原理

Java有Java编译器和Java虚拟机,编译器将Java源代码转换为.class文件,虚拟机加载并运行.class文件。对于泛型类,Java编译器会将泛型代码转换为普通的非泛型代码,就像上面的普通Pair类代码及其使用代码一样,将类型参数T擦除,替换为Object,插入必要的强制类型转换。Java虚拟机实际执行的时候,它是不知道泛型这回事的,它只知道普通的类及代码。

泛型的好处

既然泛型是实现原理是将泛型变为了普通类和Object就可以的,而且泛型最后也变成了普通类,那么为什么还是要使用泛型呢?

 

 

 

栗子

只使用Object,代码写错的时候,开发环境和编译器不能帮我们发现问题,看代码:

Pair pair = new Pair("name",1);

Integer id = (Integer)pair.getFirst();

String name = (String)pair.getSecond();

写代码时,不小心,类型弄错了,不过,代码编译时是没有任何问题的,但,运行时,程序抛出了类型转换异常ClassCastException。

但是如果使用泛型,那么开发环境如Eclipse会提示你类型错误,即使没有好的开发环境,编译时,Java编译器也会提示你。

1,类型安全。 泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。

将运行时期的ClassCastException,转移到了编译时期变成了编译失败。

2,消除强制类型转换。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。提高了代码的可读性


泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法

 

泛型方法

是不是泛型方法和他在不在泛型类中没有关系

只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法

public static <T> int indexOf(T[] arr, T elm){

    for(int i=0; i<arr.length; i++){

        if(arr[i].equals(elm)){

            return i;

        }

    }

    return -1;

}

类型参数在方法修饰符之后,返回类型之前

调用indexOf(new Integer[]{1,3,5}, 10)

或者indexOf(new String[]{"hello","老马","编程"}, "老马")

和泛型类一样泛型方法也可以有多个参数

public static <U,V> Pair<U,V> makePair(U first, V second){

    Pair<U,V> pair = new Pair<>(first, second);

    return pair;

}

在大多数情况下,方法调用中可以省略<T>参数的,编译器有足够的信息推断出所调用的方法

例如makePair(1,”a”);

类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。

类中的泛型方法转载https://blog.csdn.net/s10461/article/details/53941091

 

public class GenericFruit {

class Fruit {

@Override

public String toString() {

return "fruit";

}

}

class Apple extends Fruit {

@Override

public String toString() {

return "apple";

}

}

class Person {

@Override

public String toString() {

return "Person";

}

}

class GenerateTest<T> {

public void show_1(T t) {

System.out.println(t.toString());

}

// 在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。

// 由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。

public <E> void show_3(E t) {

System.out.println(t.toString());

}

 

// 在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。

public <T> void show_2(T t) {

System.out.println(t.toString());

}

}

 

public static void main(String[] args) {

Apple apple = new Apple();

Person person = new Person();

 

GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();

// apple是Fruit的子类,所以这里可以

generateTest.show_1(apple);

// 编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person

// generateTest.show_1(person);

 

// 使用这两个方法都可以成功

generateTest.show_2(apple);

generateTest.show_2(person);

 

// 使用这两个方法也都可以成功

generateTest.show_3(apple);

generateTest.show_3(person);

}

}

什么时候使用泛型方法

无论何时,如果你能做到,你就该尽量使用泛型方法。也就是说,如果使用泛型方法将整个类泛型化,那么就应该使用泛型方法。另外对于一个static的方法而已,无法访问泛型类型的参数。所以如果static方法要使用泛型能力,就必须使其成为泛型方法。

 

 

泛型接口

public interface Comparable<T> {

    public int compareTo(T o);

}

public interface Comparator<T> {

    int compare(T o1, T o2);

    boolean equals(Object obj);

}

和泛型类差不多

泛型接口的实现

      public interface List <E>{

          abstract boolean add(E e);

        }

       接口里的常量不能是用泛型修饰,静态方法也不能.

    实现类,先实现接口,不理会泛型

        public class ArrayList<E> implements List<E>{

        }

        调用者 : new ArrayList<String>() 后期创建集合对象的时候,指定数据类型

实现类,实现接口的同时,也指定了数据类型

       public class XXX implements List<String>{

        }

       new XXX()

泛型的限定

java中用通配符和边界来限制T的范围

extends:可读不可写  限制父类 上限限制   可以传递T,传递他的子类对象

super :可写不可读   限制子类  下限限制 可以传递T,传递他的父类对象

都没有 :没有继承很开心,一遇继承就完蛋

java是单继承,所有继承的类构成一棵树。
T<? super B>和T<? extends B>

假设A和B都在一颗继承树里(否则super,extend这些词没意义)。
A super B 表示A是B的父类或者祖先,在B的上面。
A extend B 表示A是B的子类或者子孙,在B下面。

由于树这个结构上下是不对称的,所以这两种表达区别很大。假设有两个泛型写在了函数定义里,作为函数形参(形参和实参有区别):

1) 参数写成:T<? super B>,对于这个泛型,?代表容器里的元素类型,由于只规定了元素必须是B的超类,导致元素没有明确统一的“根”(除了Object这个必然的根),所以这个泛型你其实无法使用它,对吧,除了把元素强制转成Object。所以,对把参数写成这样形态的函数,你函数体内,只能对这个泛型做插入操作,而无法读

2) 参数写成: T<? extends B>,由于指定了B为所有元素的“根”,你任何时候都可以安全的用B来使用容器里的元素,但是插入有问题,由于供奉B为祖先的子树有很多,不同子树并不兼容,由于实参可能来自于任何一颗子树,所以你的插入很可能破坏函数实参,所以,对这种写法的形参,禁止做插入操作,只做读取
链接:https://www.zhihu.com/question/20400700/answer/117624335
来源:知乎
?无限定通配符

所有的都可以传,只能读,不能写

泛型的约束和局限性

不能使用基本数据类型实例化类型参数

运行时类型信息不使用与泛型

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

类型擦除可能会引发一些冲突

不能通过类型参数创建对象

不能用于静态变量

(好多没很好的理解先往后面学着应给能理解了吧)

 

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消