Java基础之泛型

简介:

目录(?)[+]

一、泛型的理解与简单使用

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

在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。

1.1、泛型在接口上的使用:

package com.luo.test;

public interface TestInterface<T> {

    String objectToString(T o); 

}

   
   

    对应实现类可以这样:

    package com.luo.test;
    
    public class TestInterfaceImpl<T> implements TestInterface<T> {
    
        public String objectToString(T o) {
            return o.toString();
        }
    
        public static void main(String args[]){
            TestInterfaceImpl<Integer> testInterfaceImpl = new TestInterfaceImpl<Integer>();
            Integer integer = new Integer(123);
            System.out.println(testInterfaceImpl.objectToString(integer));
        }
    
    }
    
    
     
     


      运行结果:123

      1.2、泛型在类上单独使用(不实现接口):

      package com.luo.test;
      
      public class ClassTest<T> {
      
          private T ob; // 定义泛型成员变量
      
          public T getOb() {
              return ob;
          }
      
          public void setOb(T ob) {
              this.ob = ob;
          }
      
          public void showObType() {
              System.out.println("T的实际类型是: " + ob.getClass().getName());
          }
      
          public static void main(String args[]){
              ClassTest<Integer> classTest = new ClassTest<Integer>();
              classTest.setOb(123);
              classTest.showObType();
          }
      
      }
      
      
       
       

        运行结果:T的实际类型是: java.lang.Integer

        1.3、泛型在方法上单独使用:

        例如想要实现:

        public List<String> ArrayToList(String[] array);
        public List<Double> ArrayToList(Double[] array);
        
         
         

          那么你可以使用泛型如下:

          package com.luo.test;
          
          import java.util.ArrayList;
          import java.util.List;
          
          public class MyTest {
          
              public <T> List<T> write(T[] array){
                  List<T> list = new ArrayList<T>();
                  for (int i = 0; i < array.length; i++) {
                      list.add(array[i]);
                  }
                  return list;
              }
          
              public static void main(String args[]){
          
              }
          }
          
          
           
           

            二、泛型的高级使用

            2.1、通配符“?”

            先看如下代码:

            这里写图片描述

            我们都知道,Object所有类的基类,但是需要注意的是

            Collection<Object>并不是所有集合的超类。
            List<Object>, List<String>是两种不同的类型,他们之间没有继承关系,即使String继承了Object。
            这就是泛型的强大之处,引入范型后,一个复杂类型如(List),就可以在细分成更多的类型。
            
             
             

              为解决上面代码报错问题,引入通配符“?”:

              这里写图片描述

              这样就不会编译出错啦。这里使用了通配符“?”指定可以使用任何类型的集合作为参数。

              2.2、边界通配符“?extends”

              假定有一个画图的应用,可以画各种形状的图形,如矩形和圆形等。为了在程序里面表示,定义如下的类层次:

              public abstract class Shape {
                  public abstract void draw(Canvas c);
              }
              
              public class Circle extends Shape {
                  private int x,y,radius;
                  public void draw(Canvas c) { ... }
              }
              
              public class Rectangle extends Shape
                  private int x,y,width,height;
                  public void draw(Canvas c) { ... }
              }
              
               
               
                为了画出集合中所有的形状,我们可以定义一个函数,该函数接受带有泛型的集合类对象作为参数。但是不幸的是,我们只能接收元素类型为Shape的List对象,而不能接收类型为List<Cycle>的对象,这在前面已经说过。为了解决这个问题,所以有了边界通配符的概念。这里可以采用public void drawAll(List<? extends Shape> shapes)来满足条件,这样就可以接收元素类型为Shape子类型的列表作为参数了。
                
                 
                 


                  //使用边界通配符的版本
                  public void drawAll(List<?exends Shape> shapes) {
                      for (Shapes:shapes) {
                          s.draw(this);
                      }
                  }
                  
                   
                   

                    这里就又有个问题要注意了,如果我们希望在List<?exends Shape> shapes中加入一个矩形对象,如下所示:

                    shapes.add(0, new Rectangle()); //compile-time error

                    那么这时会出现一个编译时错误,原因在于:我们只知道shapes中的元素时Shape类型的子类型,具体是什么子类型我们并不清楚,所以我们不能往shapes中加入任何类型的对象。不过我们在取出其中对象时,可以使用Shape类型来取值,因为虽然我们不知道列表中的元素类型具体是什么类型,但是我们肯定的是它一定是Shape类的子类型。

                    为解决添加问题,引入通配符“?super”

                    2.3、通配符“?super”

                    List<Shape> shapes = new ArrayList<Shape>();
                    List<? super Cicle> cicleSupers = shapes;
                    cicleSupers.add(new Cicle()); //OK, subclass of Cicle also OK
                    cicleSupers.add(new Shape()); //ERROR
                    
                     
                     

                      这表示cicleSupers列表存储的元素为Cicle的超类,因此我们可以往其中加入Cicle对象或者Cicle的子类对象,但是不能加入Shape对象。这里的原因在于列表cicleSupers存储的元素类型为Cicle的超类,但是具体是Cicle的什么超类并不清楚。但是我们可以确定的是只要是Cicle或者Circle的子类,则一定是与该元素类别兼容。

                      2.4、通配符总结

                      如果你想从一个数据类型里获取数据,使用 ? extends 通配符 
                      如果你想把对象写入一个数据结构里,使用 ? super 通配符 
                      如果你既想存,又想取,那就别用通配符。

                      三、总结

                      3.1、 类型擦除概念

                      类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上,因此泛型类型中的静态变量是所有实例共享的。此外,需要注意的是,一个static方法,无法访问泛型类的类型参数,因为类还没有实例化,所以,若static方法需要使用泛型能力,必须使其成为泛型方法。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。在使用泛型时,任何具体的类型都被擦除,唯一知道的是你在使用一个对象。比如:List和List在运行事实上是相同的类型。他们都被擦除成他们的原生类型,即List。因为编译的时候会有类型擦除,所以不能通过同一个泛型类的实例来区分方法,如下面的例子编译时会出错,因为类型擦除后,两个方法都是List类型的参数,因此并不能根据泛型类的类型来区分方法。

                      /*会导致编译时错误*/ 
                       public class Erasure{
                           public void test(List<String> ls){
                               System.out.println("Sting");
                           }
                           public void test(List<Integer> li){
                               System.out.println("Integer");
                           }
                        }
                      
                       
                       

                        那么这就有个问题了,既然在编译的时候会在方法和类中擦除实际类型的信息,那么在返回对象时又是如何知道其具体类型的呢?如List编译后会擦除掉String信息,那么在运行时通过迭代器返回List中的对象时,又是如何知道List中存储的是String类型对象呢?

                        擦除在方法体中移除了类型信息,所以在运行时的问题就是边界:即对象进入和离开方法的地点,这正是编译器在编译期执行类型检查并插入转型代码的地点。泛型中的所有动作都发生在边界处:对传递进来的值进行额外的编译期检查,并插入对传递出去的值的转型。

                        3.2、方法重载

                        在JAVA里面方法重载是不能通过返回值类型来区分的,比如代码一中一个类中定义两个如下的方法是不容许的。但是当参数为泛型类型时,却是可以的。如下面代码二中所示,虽然形参经过类型擦除后都为List类型,但是返回类型不同,这是可以的。

                        /*代码一:编译时错误*/ 
                        public class Erasure{
                           public void test(int i){
                                System.out.println("Sting");
                            }
                            public int test(int i){
                                System.out.println("Integer");
                            }
                        }
                        
                         
                         
                          /*代码二:正确 */
                           public class Erasure{
                              public void test(List<String> ls){
                                  System.out.println("Sting");
                              }
                              public int test(List<Integer> li){
                                  System.out.println("Integer");
                              }
                          }
                          
                           
                           

                            3.3、泛型类型是被所有调用共享的

                            所有泛型类的实例都共享同一个运行时类,类型参数信息会在编译时被擦除。因此考虑如下代码,虽然ArrayList和ArrayList类型参数不同,但是他们都共享ArrayList类,所以结果会是true。

                            List<String>l1 = new ArrayList<String>();
                            List<Integer>l2 = new ArrayList<Integer>();
                            System.out.println(l1.getClass() == l2.getClass()); //True
                            
                             
                             

                              3.4、instanceof

                              不能对确切的泛型类型使用instanceOf操作。如下面的操作是非法的,编译时会出错。

                              Collection cs = new ArrayList<String>();
                              if (cs instanceof Collection<String>){…}// compile error.如果改成instanceof Collection<?>则不会出错。
                              
                               
                               

                                3.5、泛型数组问题

                                不能创建一个确切泛型类型的数组。如下面代码会出错。

                                List<String>[] lsa = new ArrayList<String>[10]; //compile error.
                                
                                 
                                 

                                  因为如果可以这样,那么考虑如下代码,会导致运行时错误。

                                  List<String>[] lsa = new ArrayList<String>[10]; // 实际上并不允许这样创建数组
                                  Object o = lsa;
                                  Object[] oa = (Object[]) o;
                                  List<Integer>li = new ArrayList<Integer>();
                                  li.add(new Integer(3));
                                  oa[1] = li;// unsound, but passes run time store check
                                  String s = lsa[1].get(0); //run-time error - ClassCastException
                                  
                                   
                                   
                                    因此只能创建带通配符的泛型数组,如下面例子所示,这回可以通过编译,但是在倒数第二行代码中必须显式的转型才行,即便如此,最后还是会抛出类型转换异常,因为存储在lsa中的是List<Integer>类型的对象,而不是List<String>类型。最后一行代码是正确的,类型匹配,不会抛出异常。
                                    
                                     
                                     
                                      List<?>[] lsa = new List<?>[10]; // ok, array of unbounded wildcard type
                                      Object o = lsa;
                                      Object[] oa = (Object[]) o;
                                      List<Integer>li = new ArrayList<Integer>();
                                      li.add(new Integer(3));
                                      oa[1] = li; //correct
                                      String s = (String) lsa[1].get(0);// run time error, but cast is explicit
                                      Integer it = (Integer)lsa[1].get(0); // OK&nbsp;
                                      
                                       
                                       




                                        相关文章
                                        |
                                        安全 Java 编译器
                                        揭秘JAVA深渊:那些让你头大的最晦涩知识点,从泛型迷思到并发陷阱,你敢挑战吗?
                                        【8月更文挑战第22天】Java中的难点常隐藏在其高级特性中,如泛型与类型擦除、并发编程中的内存可见性及指令重排,以及反射与动态代理等。这些特性虽强大却也晦涩,要求开发者深入理解JVM运作机制及计算机底层细节。例如,泛型在编译时检查类型以增强安全性,但在运行时因类型擦除而丢失类型信息,可能导致类型安全问题。并发编程中,内存可见性和指令重排对同步机制提出更高要求,不当处理会导致数据不一致。反射与动态代理虽提供运行时行为定制能力,但也增加了复杂度和性能开销。掌握这些知识需深厚的技术底蕴和实践经验。
                                        233 2
                                        |
                                        11月前
                                        |
                                        存储 缓存 Java
                                        java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
                                        这篇文章详细介绍了Java中的IO流,包括字符与字节的概念、编码格式、File类的使用、IO流的分类和原理,以及通过代码示例展示了各种流的应用,如节点流、处理流、缓存流、转换流、对象流和随机访问文件流。同时,还探讨了IDEA中设置项目编码格式的方法,以及如何处理序列化和反序列化问题。
                                        293 1
                                        java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
                                        |
                                        Java 编译器 容器
                                        Java——包装类和泛型
                                        包装类是Java中一种特殊类,用于将基本数据类型(如 `int`、`double`、`char` 等)封装成对象。这样做可以利用对象的特性和方法。Java 提供了八种基本数据类型的包装类:`Integer` (`int`)、`Double` (`double`)、`Byte` (`byte`)、`Short` (`short`)、`Long` (`long`)、`Float` (`float`)、`Character` (`char`) 和 `Boolean` (`boolean`)。包装类可以通过 `valueOf()` 方法或自动装箱/拆箱机制创建。
                                        132 9
                                        Java——包装类和泛型
                                        |
                                        安全 Java API
                                        【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
                                        String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
                                        【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
                                        |
                                        11月前
                                        |
                                        存储 安全 Java
                                        🌱Java零基础 - 泛型详解
                                        【10月更文挑战第7天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
                                        100 1
                                        |
                                        11月前
                                        |
                                        Java 语音技术 容器
                                        java数据结构泛型
                                        java数据结构泛型
                                        91 5
                                        |
                                        11月前
                                        |
                                        存储 Java 编译器
                                        Java集合定义其泛型
                                        Java集合定义其泛型
                                        68 1
                                        |
                                        11月前
                                        |
                                        存储 Java 编译器
                                        【用Java学习数据结构系列】初识泛型
                                        【用Java学习数据结构系列】初识泛型
                                        86 2
                                        |
                                        存储 安全 搜索推荐
                                        Java中的泛型
                                        【9月更文挑战第15天】在 Java 中,泛型是一种编译时类型检查机制,通过使用类型参数提升代码的安全性和重用性。其主要作用包括类型安全,避免运行时类型转换错误,以及代码重用,允许编写通用逻辑。泛型通过尖括号 `&lt;&gt;` 定义类型参数,并支持上界和下界限定,以及无界和有界通配符。使用泛型需注意类型擦除、无法创建泛型数组及基本数据类型的限制。泛型显著提高了代码的安全性和灵活性。
                                        143 8
                                        |
                                        存储 算法 Java
                                        14 Java集合(集合框架+泛型+ArrayList类+LinkedList类+Vector类+HashSet类等)
                                        14 Java集合(集合框架+泛型+ArrayList类+LinkedList类+Vector类+HashSet类等)
                                        130 2
                                        14 Java集合(集合框架+泛型+ArrayList类+LinkedList类+Vector类+HashSet类等)