• 4.泛型


     一、何为泛型,为什么要使用泛型。

    第一个问题何为泛型?

    首先泛型是一个类型(类似于int double),这就解释了泛型中的型字。我们再看泛型中的泛字,泛指广泛。

    两者结合一起,泛型就代表广泛的类型,即它可以代不同的类型具有多样性。

    接着我们来看第二个问题,为什么要使用泛型。

    举个例子,现在我要录入学生的成绩,我们大家都知道表示学生成绩有多种方式。

    可以是的分数制(77,89,95),也可是是等级制(A、B、C、D)。

    那么问题来了,就当我抽疯了,一会想用分数制,一会想用等级制。

    那我们该怎么办???

    我们想一想,好像可以建两个Studetn类,一个类里面的成绩用String类型,一个类里面的成绩用float。

    好像可以,确实也可以,但是一次你建两个类,不嫌麻烦吗?万一需求多了成百上千个类,还不得累死。

    我们继续想别的方法,可不可以把成绩设为Object类型用于接受所有类型,后续我们再通过强制转换转成对应输入的类型。

    想一想这个好像可以好像还不错,说干就干,实践出真知,我们先来试一下。

     1 public class Test{
     2     public static void main(String[] args) {
     3         Student s1 = new Student(90); 
     4         Student s2 = new Student("A");
     5         System.out.println((int)s1.score);//此处强制转型过程 :Object——>Integer——>int
     6         System.out.println((String)s2.score);
     7     }
     8 }
     9 
    10 class Student{
    11     Object score;
    12     public Student(Object score){
    13         this.score = score;
    14     }
    15 }
    运行结果:
    90
    A

    发现这样做不错哎,可以实现需求,Obeject好像也可以作为“泛型”。泛型好像没有存在的必要了。

    我们接着来看泛型的例子,对比下两者的区别。

    举例之前先要了解泛型的基本使用

    class Studetn <T> {  //<T>就是声明一个泛型T,可以用任意字母表示可以是A、B、C..只不过约定俗成用T表示类型,K,V表示键值相关,E表示元素
       T score;       //这时T就是我们定义好的一个泛型(泛型也是类型,只不过是特殊的类型),用T声明score,此时的T是类型。
       public Studetn(T score){ //代表接受时的类型,具体是什么类型需要在使用时决定。
    this.score = score;
    } }

    注意有一点,泛型要在使用时指定类型。

     1 public class Test{
     2     public static void main(String[] args) {
     3         Student<Integer> s1 = new Student<Integer>(90); //使用时指定类型,这里指定为Integer就可以接受Integer类型的数据
     4         Student <String> s2 = new Student<String>("A"); //这里同样指定了String,
     5         System.out.println(s1.score);                   //注意这里不能直接指定八种基本类型(int,float..),只能指定它们的包装类(Integer, Float)。  
    6
    System.out.println(s2.score);
    7
    }
    8
    }
    9

    10 class Student<T>{
    11 T score;
    12 public Student(T score){
    13 this.score = score;
    14 }
    15 }
    运行结果:
    90
    A

    看了上面使用泛型的例子,好像和Object接受所有类型差不多,看不出使用泛型有什么优点。

    下面举一个例子就看出泛型的优点。

    例如我现在设置一个学生的成绩是等级制(A,B,C)使用Obejct接受,在打印的时候不小心将其强制转换成了int。

    我们看程序编写完没有报任何错误警告。

    但是我们运行下看下结果。

    运行后出现了类转换异常,字符串是不能强制转换为int的。

    大家可能会说,我没那么智障将字符串强制转换为int,是的一般人不会这样做,但我要说的不是强制转换的问题了,而是类型转换安全的问题。

    这里字符串确实不能强制转换为int,但是我们用Objec接受,之后将Object强制转换为别的类,编译器认为是合法的,不会提前报出任何错误和警告。

    如果是泛型遇到这种情况会出现什么效果呢?我们来看下。

     

    泛型是一开始就指定好类型,然后会对传递的参数类型做检查,如果类型不匹配就会直接在一开始报错。

    这样我们可以做到防范于未然。

    Object接受:可以接受任意类,也可以将其强制转换成对应类,这在语法上是符合逻辑的,不合法的转换(例如String->int)编译器一开始不会报错,只有在运行时才会报错。

    使用泛型:一开始就指定了类型,会对类型做检查,不正确直接会报错警告,在源头解决了问题。

    编译器对泛型:检查下进来的类型和指定类型匹不匹配,匹配就过去不匹配就警告,警告解除才能过去。

    编译器对Object:什么类型都可以过来我都能接受,最后你想转什么类型都可以,你问我这个类型转换合不合法?我不知道我不管,语法上是合法的我就让你通过。我只体提醒语法错误,类型合不合法你去运行就知道了,我不负责类型检查这事。

    大家对比下这两个,明显泛型更安全,在一开始就进行了检查。

    二.泛型的继承

    1.父类指定具体属性,子类不是泛型。

     1 public class Test{
     2     public static void main(String[] args) {
     3         Children1 c1 = new Children1();
     4         c1.name = "hcf";
     5         c1.output(c1.name);
     6     }
     7 }
     8 
     9 class Father<T>{
    10     T name;
    11     void fun(T t){    
    12     }
    13 }
    14 
    15 class Children1 extends Father<String>{//继承泛型时指定类型。
    16     void output(String t) {    
    17         System.out.println(t);
    18     }
    19 }
    运行结果;
    hcf

    我看Father是一个泛型类,在Children1继承泛型类Father时,Father指定了具体类型,这时Children1就相当于。

    class Children1 {
            String name;
        void output(String t) {    
            System.out.println(t);
        }
    }

    虽然Children1继承了泛型类,但它本身不是泛型类,它只是继承了泛型类Father指定属性之后的类。

    2.父类泛型,子类泛型。

     1 public class Test{
     2     public static void main(String[] args) {
     3         Children2<String>c1 = new Children2<String>();
     4         c2.name = "hcf";
     5         c2.output(c2.name);
     6     }
     7 }
     8 
     9 class Father<T>{
    10     T name;
    11     void fun(T t){    
    12     }
    13 }
    14 
    15 class Children2<T> extends Father<T>{//这里的T也是继承过去的。如果Father<T1>那么同样Children2中的T要变成T2。
    16     void output(T t) {               //重写方法时类型的参数要和父类一致,所以这里类型是泛型T。
    17         System.out.println(t);
    18     }
    19 }
    运行结果:
    hcf

    Children2可以看做继承了泛型类Father的属性及方法,以及泛型T。这时Children2类是一个泛型类,泛型T具体的属性由使用时确定。

    这里的T是继承自Father的。Children2就相当于:

    class Children2<T>{
        T name;
        void output(T t) {    
            System.out.println(t);
        }
    }

    3.父类单个泛型,子类多个泛型。

     1 public class Test{
     2     public static void main(String[] args) {
     3         Children3<String,Integer>c3 = new Children3<String,Integer>();
     4         c3.name = "hcf";
     5         c3.number = 20;
     6         c3.output(c3.name);    
     7         System.out.println(c3.number);
     8     }
     9 }
    10 
    11 class Father<T>{
    12     T name;
    13     void fun(T t){    
    14     }
    15 }
    16 
    17 class Children3<T,T1> extends Father<T>{//children3除了继承Father外,自身还有一个T1.
    18     T1 number;
    19     void output(T t) {    
    20         System.out.println(t);
    21     }
    22 }

    这里的Children3不仅继承了Father中的属性和方法还包含自身的泛型T1,Chidren3中T,T1的具体属性由使用时确定。此时的Children3就相当于:

    class Children3<T,T1> {
        T name;
        T1 number;
        void output(T t) {    
            System.out.println(t);
        }
    }

    4.子类父类同时擦除泛型

     1 public class Test{
     2     public static void main(String[] args) {
     3         Children3 c3 = new Children3();
     4         c3.name = "hcf";
     5         c3.output(c3.name);    
     6     }
     7 }
     8 
     9 class Father<T>{
    10     T name;
    11     void fun(T t){    
    12     }
    13 }
    14 
    15 class Children3 extends Father{//这里泛型被擦除了,则属性默认为Object
    16 
    17     void output(Object t) {
    18         System.out.println(t);
    19     }
    20     
    21 }
    运行结果:
    hcf

    此时父类和子类同时擦除,这时子类继承父类的类型默认为Object。

    继承自Father的name由于擦除的原因,所以默认为Object类。

    三、泛型的多态问题。

    我们回顾下实现多态的三个条件。

    1.继承

    2.方法重写。

    3.父类引用执向子类对象。

    举个小例子:

     1 public class Test{
     2     public static void main(String[] args) {
     3         Children1 c1 = new Children1("11");
     4         Children2 c2 = new Children2("22");
     5         output(c1);
     6         output(c2);
     7     }
     8     static void output(Object f){
     9         System.out.println(f.toString());
    10     }
    11 }
    12 
    13 class Children1 extends Object{
    14     String name;
    15     public Children1(String name){
    16         this.name = name;
    17     }
    18     
    19     public String toString(){
    20         return name;
    21     }
    22 }
    23 
    24 class Children2 extends Object{
    25     String name;
    26     public Children2(String name){
    27         this.name = name;
    28     }
    29     
    30     public String toString(){
    31         return name;
    32     }
    33 }
    运行结果;
    11
    22

    这里面用Object作为父类接受子类来实现多态。

    倘若我们把这里换做是泛型,也用同样的思路来实现下:

    我们看用泛型的<Object>类型接受泛型<String>时会出现问题,泛型的Object不完全等同于Object类。

    要实现泛型的多态,我们就需要用<?>来接受,?代表可以接受任意指定泛型对象。

     1 public class Test{
     2     public static void main(String[] args) {
     3         Children1<String> c1 = new Children1 <String>("11");
     4         Children2<String> c2 = new Children2 <String>("22");
     5         output(c1);
     6         output(c2);
     7     }
     8     static void output(Father<?> f){   //此处用?就可以实现接受任意类型的泛型。
     9         f.print();
    10     }
    11 }
    12 class Father<T>{
    13     void print(){
    14         System.out.println();
    15     }
    16 }
    17 
    18 class Children1<T> extends Father<T>{
    19     T name;
    20     public Children1(T name){
    21         this.name = name;
    22     }
    23     void print(){                    //重写父类中的方法。
    24         System.out.println(name);
    25     }
    26 }
    27 
    28 class Children2<T> extends Father<T>{
    29     T name;
    30     public Children2(T name){
    31         this.name = name;
    32     }
    33     void print(){
    34         System.out.println(name);
    35     }
    36 }
    运行结果:
    11
    22

     用<?>接受任意类型就可以实现泛型的多态。

    四、泛型方法

    之前使用的:

    1 class Father<T>{
    2      T name;
    3      void fun(T t){    
    4      }

    其中的fun并不能称为泛型方法,它是依赖于Fatehr类的,如果Father不是泛型类,则不能写void fun(T t);

    泛型方法定义:

    class Father{
        public static <T> void fun(T t){ //加上<T>标识后就代表是泛型方法,
                                         //具体类型由使用时决定
        }                                //加上fun就是一个单独的泛型方法
    }                                    //不需要依赖于泛型类,和泛型类的T是分开的。
    
    class Father{
        public static <T> T fun(T t){  //返回值为T,fun函数的参数也是T,都是在使用时决定。
            return t;
        }
    }

     例子:

     1 public class Test{
     2     public static void main(String[] args) {
     3         Father <String>f1 = new Father<String>();
     4         f1.setNmae("hcf");
     5         f1.out1(1);
     6         System.out.println(f1.name);
     7         System.out.println(f1.out2("String"));
     8     }
     9 }
    10 
    11 class Father<T>{
    12     T name;
    13     public void setNmae(T name){
    14         this.name = name;
    15     }
    16     @SuppressWarnings("hiding")
    17     public <T> void out1(T t){
    18         System.out.println(t);
    19     }
    20     
    21     @SuppressWarnings("hiding")
    22     public <T>  T out2(T t){
    23         return t;
    24     }
    25 }
    运行结果:
    1
    hcf
    String

    上图中Father泛型类的对象在创建时指定为String类型,但是out1和out2方法参数的一个是String类型,一个是Integer类型。此处想说明的是输入Father,out1,out2

    的泛型标识都是T,但是他们不是同一T,他们是不同的类型。泛型方法的类型由使用该方法时决定,泛型类的类型也是使用该类时决定,但它们的类型是分开的。

       

    out1和out2的类型由使用时传递进去的类型所决定。

    五、泛型的嵌套

     1 public class Test{
     2     public static void main(String[] args) {
     3         Output <Info<String,Integer>> o = new Output<Info<String,Integer>>();
     4         Info <String,Integer> i = new Info<String,Integer>();
     5         i.setName("hcf");        //如上<Info<String,Interge>>就是一个泛型嵌套。
     6         i.setNum(1001);
     7         o.set(i);
     8         System.out.println(o.print().getName() + o.print().getNum());
     9         
    10     }
    11 }
    12 class Output<T>{
    13     T i;
    14     public void set(T i) {
    15         this.i = i;
    16     }
    17     T print(){
    18         return i;
    19     }
    20 }
    21 
    22 class Info<T,T1>{
    23     T1 num;
    24     T name;
    25     public T1 getNum() {
    26         return num;
    27     }
    28     public void setNum(T1 num) {
    29         this.num = num;
    30     }
    31     public T getName() {
    32         return name;
    33     }
    34     public void setName(T name) {
    35         this.name = name;
    36     }    
    37 }
    运行结果:
    hcf1001

    上述代码中<Info<String,Integer>>可以看成首先是一个info类型,然后info类型中的泛型设置为<String,Interge>.

    那么Output中的T应该是Info类型的,Info中的T1,T应该是Interge和String类型。

    使用时一层一层解开就可以了。

    六、受限泛型

    泛型类型的设置不仅可以是具体的属性,还可以是一个范围。

    这里的范围主要体现在设置泛型的上限或下限。

    设置上限用extends,设置下限用super。

    例如。Info<? extends Number> 就设置了泛型的上限是Number,这个设置了上限的泛型可接收Number和它的子类。

     1 public class Test{
     2     public static void main(String[] args) {
     3         Info<Integer> Inte = new Info<Integer>();
     4         Inte.number = 11;
     5         Info<Float> Flo = new Info<Float>();
     6         Flo.number = 10.1f;
     7         Info<Double> Dou = new Info<Double>();
     8         Dou.number = 10.01;
     9         outputNum(Inte);
    10         outputNum(Flo);
    11         outputNum(Dou);
    12     }
    13     static void outputNum(Info<? extends Number> i){//此处设置了上限Number
    14         System.out.println(i.number);               //可以接受Number的子类及自身。
    15         
    16     }
    17 }
    18 
    19 class Info<T>{
    20     T number;
    21 }
    运行结果:
    11
    10.1
    10.01

    可以看出我们将outputNum函数中的参数类型设置为<? extends Number>,由于Double,Float,Integer都是它的子类,所以可接受所有数字。

     可以理解为在继承关系上<=Number类。

    Info<? super String> 就是设置了下限,即类型要>=String类型,所以只能接受String或Object类型(String是继承Object的,所以大于String的只有Objcet)。

     1 public class Test{
     2     public static void main(String[] args) {
     3         Info<String> str = new Info<String>();
     4         str.info = "hello";
     5         Info<Object> obj = new Info<Object>();
     6         obj.info = "object";
     7         output(str);
     8         output(obj);
     9     }
    10     static void output(Info<? super String> i){
    11         System.out.println(i.info);    
    12     }
    13 }
    14 
    15 class Info<T>{
    16     T info;
    17 }
    运行结果:
    hello
    object
  • 相关阅读:
    简单爬虫架构解析
    三种urllib实现网页下载,含cookie模拟登陆
    MySQL 从入门到删库
    Python Set
    Python dict
    Python tuple
    Python List
    死锁问题
    线程通信之生产者和消费者案例
    多线程安全和线程同步
  • 原文地址:https://www.cnblogs.com/huang-changfan/p/9538159.html
Copyright © 2020-2023  润新知