美文网首页
Java泛型的简单使用

Java泛型的简单使用

作者: 钢镚koala | 来源:发表于2018-05-24 10:23 被阅读0次

一、泛型的定义

泛型这个术语的意思是"适用于许多许多的类型"。它 是JDK 1.5的一项新特性,它的本质是 参数化类型 (Parameterized Type)的应用,也就是说 所操作的数据类型被指定为一个参数,在用到的时候再指定具体的类型。

1. 什么是参数化类型

参数化类型就是一个编译器可以自动定制作用于特定类型上的类 。举例:

List list = new ArrayList();

这是原生类型(未引入参数化类型时)的写法,list集合中可以存储不同类型的元素,如此便有了安全隐患,编译器不能保证你取值时的转型(拆箱)一定正确。
jdk1.5引入了参数化类型(泛型)之后,写法变为

List<类型(例如String)> list = new ArrayList();

这样的话,list中只能存储String类型的元素,编译器在编译时便会验证list中的元素是否全为String类型,否则编译错误。如此一来便不存在安全隐患,读取数据时也不需要自己进行拆箱,编译器会判断其元素类型为String。

简单的说就是, 原本集合中用来处理的通用类型为Object,而使用了参数化类型后,编译器会自动的将Object参数的类型修改为你传递给它的参数化类型,例如此例定义一个只接收和取出String的list容器。

二、 泛型的表达

一对尖括号,中间包含类型信息。常见的如T、E、K、V等形式的参数常用于表示泛型。如常用的集合

ArrayList<Food> foods = new ArrayList<Food>(); //此处的泛型类型是Food
ArrayList<T>  list = new ArrayList<T>(); //此处的泛型类型是T,只是一个泛型标识,可以随意更改

三、 泛型的使用

泛型的参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。下面看看具体是如何定义的。

  1. 泛型类

泛型作用于类上,叫泛型类。泛型类的定义规则:

public class Test<T>{//此处在实例化泛型类时,必须指定T的具体类
      //key这个成员变量的类型为T,   T的类型由外部指定
      private T key;
      public Test(T key) {
          //泛型构造方法形参key的类型也为T,T的类型由外部指定
            this.key = key;
      }
      public T getKey(){
            return key;
      }
  }

传入的实参类型需与泛型的类型参数类型相同,即为String.

Test<String> testStr = new Test("Call me");

传入的实参类型需与泛型的类型参数类型相同,即为Integer.

Test<Integer> testInteger = new Test(123);

System.out.println(testInteger.getKey())

System.out.println(testStr.getKey());

输出结果:

05-22 14:46:50.128 28166-28166/? I/System.out: Call me

05-22 14:46:50.129 28166-28166/? I/System.out: 123

我们在定义泛型类的时候,其实不是必要传入泛型类型实参。这里是为了更好的 体现泛型对类的约束 ,所以在成员变量中或构造方法中传入泛型类实参,从而在泛型类Test中,我们只能使用与泛型类参数一致的变量类型或方法类型。

  1. 泛型接口

泛型也可用于接口。与泛型类的使用和定义基本一致。看下面的例子:

public interface Kitchen<T> {//泛型定义接口
       T cook();
}

class Cooker implemments Kitchen<String> {

        String[] menu =new String[]{"Noodles", "Rice", "Dumplings"};

        @Override
        public String cook() {
                int r = Random().nextInt(3);
                return menu[r];
        }
}

1.假设情景:客人随便来4份主食,通知后厨,厨师在厨房开始做饭。

Cooker cooker = new Cooker();
int i = 0;
while( i < 3){
    Log.i("test","zhushi :"+ cooker.cook());
    i ++;
}

结果:

05-22 18:16:48.676 22270-22270/com.example.papayawp.test I/test: zhushi :Rice
05-22 18:16:48.676 22270-22270/com.example.papayawp.test I/test: zhushi :Noodles
05-22 18:16:48.676 22270-22270/com.example.papayawp.test I/test: zhushi :Noodles
05-22 18:16:48.677 22270-22270/com.example.papayawp.test I/test:  zhushi :Noodles
  1. 1 泛型方法
    到目前为止,我们看到的泛型,都是应用于整个类上。但同样可以在类中包含参数化方法,而这个方法所在的类可以是泛型类,也可以不是泛型类。也就是说,是否拥有泛型方法,与其所在的类是否是泛型没有关系。定义泛型方法时,必须在返回值前边加一个,来声明这是一个泛型方法,持有一个泛型<T>,然后才可以用泛型<T>作为方法的返回值。就像下面这样:
public class TestMethods{
      public <T> void  readBook( T t){//定义泛型方法
               Log.i( "test" ,  t.getClass().getName() );
       }

     public <T>  T  writeBook( T t){//标准定义泛型方法
          Log.i( "test" ,  t.getClass().getName() );
      }

       public static void main(String[] args){
             TestMethods test = new TestMethods();
             test.readBook(" ");
             test.readBook( 1 );
             test.writeBook('c');
             test.writeBook(test);
        }
}

输出结果:

java.lang.String
java.lang.Integer
java.lang.Character
TestMethods

TestMethods并不是参数化的,尽管这个类和其内部的方法可以被同时参数化,但是在这个例子中,只有方法readBook()、writeBook() 拥有类型参数。这是由该方法的返回类型前面的类型参数列表指明的。注意,当使用泛型类时,必须在创建对象的时候指定类型参数的值,而使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体的类型 。

为什么要使用泛型方法呢?因为泛型类要在实例化的时候就指明类型,如果想换一种类型,不得不重新new一次,可能不够灵活;而泛型方法可以在调用的时候指明类型,更加灵活。如我们可以像调用普通方法一样调用readBook(),而且就好像是readBook()被无限次地重载过。

下面介绍几种情况(引用其他人,例子很好),类似泛型方法却不是:

public class Test<T>{ // 这个是上面已经介绍过的泛型类

private T key;

public Test(T key) {

     this.key = key;

}

问题1. 虽然在方法中使用了泛型,但是这并不是一个泛型方法。这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。所以在这个方法中才可以继续使用 T 这个泛型。

public T getKey(){

       return key;

}

问题2. 这也不是一个泛型方法,这就是一个普通的方法,只是使用了Test这个泛型类做形参而已。

public void look(Test test){

    Log.d("test","key is "+ test.getKey());

}

问题3. 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class E", 虽然我们声明了,也表明了这是一个可以处理泛型的类型的泛型方法。但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。

public T watch(Test<E> test){

    ...

}

问题4. 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class T"。对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。所以这也不是一个正确的泛型方法声明。

    public void see(T t){

    }

另外,对于一个static的方法而言,无法访问泛型类的类型参数,所以,如果static 方法需要使用泛型能力,就必须使其成为泛型方法。

public  class  StaticTest<T> {

         ....

    /**

    * 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)

   * 即使静态方法要使用泛型类中已经声明过的泛型也不可以。

   * 如:public static void show( T t ){..},此时编译器会提示错误信息:

   *  "StaticTest cannot be refrenced from static context"

   */

    public static <T> void show( T t){//正确定义,必须在方法上泛型声明

    }

}

3.2 泛型方法与可变参数
再看一个泛型方法和可变参数的例子:

public void printMsg( T... args){
       for(T t : args){
             Log.d("test", "t is "+ t);
        }
}
printMsg("111",222,"aaaa",55.55);

泛型方法使得该方法能够独立与类而产生变化。以下是一个基本的指导原则:无论何时,只要你能做到,就尽量使用泛型方法。也就是说,如果使用泛型方法可以取代将整个类泛型化,那么就只是用泛型方法,因为它可以使事情更清楚明白。

四、泛型通配符

public class Person{}
public class Student extends Person{}

我们知道Student是Person的一个子类。那么在使用Test作为形参的方法中,能否使用Test的实例传入呢?在逻辑上类似于Button和View是否可以看成具有父子关系的泛型类型呢?
为了弄清楚这个问题,我们使用Test这个泛型类继续看下面的例子:

            public void readBook(Test<Person> test){
                     Log.d( "test" , "value is "+ test.getKey());
             }
             Test tStudent =new Test(new Student());
             Test tPerson =new Test(new Student());
             readBook(tPerson);
            // readBook这个方法编译器会为我们报错:inferred type is Test<Student>         but Test<Person> was Expected

通过提示信息我们可以看到Test<Student>不能被看作为Test<Person>的子类。由此可以看出: 同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的 。

回到上面的例子,如何解决上面的问题?总不能为了定义一个新的方法来处理Test<Student>类型的类,这显然与java中的多台理念相违背。因此我们需要一个在逻辑上可以表示 同时 是Test<Student>和Test<Person>父类的引用类型。由此类型通配符应运而生。
我们可以将上面的方法改一下:

        public void readBook( Test<?> test ){

                    Log.d( "test", "value is " +  test.getKey() );

        }

再看一个例子:

        public class ClassA<T> {

                public T t;

                public ClassA(T t) {

                        this.t = t;

                }

                public void read(ClassA<?> classA) {

                        System.out.println("test"+classA.t.getClass().getName());

                }

        }
    调用过程:(Number是抽象类,Integer是它的实现子类)
        ClassA<Integer> aa =new ClassA(123);
        ClassA<Number> vv =new ClassA(333);//如果上面read方法中泛型参数类型是Integer,则vv.read(vv)会传入参数与方法参数类型不匹配错误
        ClassA<Number> cc =new ClassA(555.0f);// Float也是Number的实现子类,在通配符的作用下,可以正常编译

        aa.read(aa);
        cc.read(cc);//输出结果是test java.lang.Integer  test  java.lang.Float
        ClassA<String> ff =new ClassA("hello ,CiCi");
        ff.read(ff) ; //输出结果是 test java.lang.String

类型通配符一般是使用 <?>代替具体的类型实参,注意,此处<?>是类型实参。通俗的讲就是,此处的?和Number、String、Integer一样都是一种实际的类型,可以把?看成所有类型的父类。是一种真实的类型。可以解决当具体类型不确定的时候,这个通配符就是 ? ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。

另外,如果想要只能接收Number类型的数据,不要String类型的数据。那么我们可以增加边界限定,

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

                public void read(ClassA< ? extends Number> classA) {//让?继承Number类,此时方法只能接收Number类型的数据
                        System.out.println("test"+classA.t.getClass().getName());
                }

        }

测试:

 ClassA<Integer> a =new ClassA(123);
 a.read(a);
 ClassA<Float> b =new ClassA(999.0f);
 b.read(b);
 ClassA<Double> c =new ClassA(199.99);
 c.read(c);
 ClassA<String> d =new ClassA("hello world");
 d.read(d);   //这一行代码编译器会提示错误,因为String类型并不是Number类型 的子类

五、总结

泛型的使用使我们的代码更加具有通用性,不会导致定义了一种类型之后其他的类型都无法使用该代码。通过泛型可以定义类型安全的数据结构(类型安全),而无须使用实际的数据类型(可扩展)。这能够显著提高性能并得到更高质量的代码(高性能),因为我们可以重用数据处理算法,而无须复制类型特定的代码(可重用)。

相关文章

  • JDK1.5后增加了泛型,那么为什么要有泛型呢?我们该如何自定义

    本篇主要讲解java泛型的理解、集合中简单使用泛型、自定义泛型结构(包括类、接口、方法)。 一、什么是泛型? 通俗...

  • Java基础之泛型

    Java基础之泛型 泛型基本介绍 简单介绍在jdk1.6之前,还没有使用到泛型,Java类型分为原始类型、复杂类型...

  • java泛型、泛型的基本介绍和使用

    现在开始深入学习java的泛型了,以前一直只是在集合中简单的使用泛型,根本就不明白泛型的原理和作用。泛型在java...

  • [转载] java泛型(一)、泛型的基本介绍和使用

    现在开始深入学习java的泛型了,以前一直只是在集合中简单的使用泛型,根本就不明白泛型的原理和作用。泛型在java...

  • Kotlin中的泛型

    Kt中的泛型是一大特色!和Java不太相似,或者说是补齐了Java的坑! 简单的使用泛型,不再赘述! 在Java中...

  • 泛型

    与Java泛型相同,Kotlin同样提供了泛型支持。对于简单的泛型类、泛型函数的定义,Kotlin 与 Java ...

  • Android 开发也要掌握的Java知识 - Java泛型

    如果需要看泛型擦除Java泛型擦除 1.Java泛型有什么用?为啥要使用泛型? Java中数组的类型是定义的时候就...

  • 泛型,复习

    Java泛型详解:和Class的使用。泛型类,泛型方法的详细使用实例 - LZJWXJ树袋熊 - CSDN博客

  • kotlin中的reified关键字

    说kotlin中这个关键字之前先简单说下Java中的泛型,我们在编程中,出于复用和高效的目的,经常使用泛型。泛型是...

  • java 泛型解析

    Java 泛型 1、泛型的精髓是什么 2、泛型方法如何使用 概述: 泛型在java中具有重要地位,在面向对象编程模...

网友评论

      本文标题:Java泛型的简单使用

      本文链接:https://www.haomeiwen.com/subject/kgtdjftx.html