学习笔记15(泛型深入)

一、泛型方法

1、基本用法

之前我们已经了解了如何创建一个泛型类和如何将一个泛型类的对象传入方法。但是之前我们将泛型对象传入方法的时候,这些方法都没有返回值,这些方法的类型都是void,但是如果方法有返回值呢?如果方法的返回值是一个泛型呢?这时候就需要泛型方法了。

/**
 This program demonstrates a simple generic method.
 */

public class GenericMethodDemo
{
    public static void main(String[] args)
    {
        String[] names = { "Alfonso", "Beatrice", "Celine" };
        displayArray(names);
    }

    /**
     The displayArray method displays each element
     in an array.
     @param array The array to display.
     */

    public static <E> void displayArray(E[] array)
    {
        for (E element : array)
            System.out.println(element);
    }
}

注意到这个方法有一个类型变量E,当调用泛型方法时,编译器将自动确定要使用哪种类型。在这里,因为传入的数组是string类型的数组,所以编译器将自动将E变为string。

你也可以像之前一样对泛型方法的类型参数作一个限定:

public static <E extends Number> void displayArray(E[] array)
{
 for (E element : array)
 System.out.println(element);
}

2、泛型方法与泛型类比较

我们为什么要创建一个泛型类呢?是因为在这个确定的类中,某些字段的类型是不确定的,比如之前的point类,这个类的x,y坐标的类型是不确定的,可能是int,可能是double,而这个类永远都是一个point类。所以我们说这个确定的类中,某些字段的类型是不确定的。

public class Point<T>
{
    private T xCoordinate; // The X coordinate
    private T yCoordinate; // The Y coordinate

    /**
     Constructor
     @param x The X coordinate.
     @param y The Y coordinate.
     */

    public Point(T x, T y)
    {
        xCoordinate = x;
        yCoordinate = y;
    }
... ... ... ... ...

而当我们写一个泛型方法的时候,这个方法的类型并不是确定的(不像泛型类的类型被确定为point<T>,只是T不知道,但是point是确定的),泛型方法可以是int,可以是double,也可以是point<T>。

二、泛型类与继承

继承也可以用于泛型类,假设我们有一个Point3D类,这个类继承自之前的point类,但是多了一个z坐标。

/**
 The Point3D class holds a Z coordinate. The data type
 of the coordinate is generic.
 */

public class Point3D<T extends Number> extends Point<T>
{
    private T zCoordinate; // The z coordinate

    /**
     Constructor
     @param x The X coordinate.
     @param y The Y coordinate.
     @param z The Z coordinate.
     */

    public Point3D(T x, T y, T z)
    {
        // Call the Point class constructor.
        super(x, y);

        // Assign the Z coordinate.
        zCoordinate = z;
    }

    /**
     The setZ method sets the Z coordinate.
     @param z The value for the Z coordinate.
     */

    public void setZ(T z)
    {
        zCoordinate = z;
    }

    /**
     The getZ method returns the Z coordinate.
     @return The value of the Z coordinate.
     */

    public T getZ()
    {
        return zCoordinate;
    }
}

首先我们关注的是类的头:public class Point3D<T extends Number> extends Point<T>,第一部分public class Point3D<T extends Number>表示正在定义一个名叫Point3D的类,同时这个类还有一个类型变量T,<T extends Number>用来定义类型变量;第二部分extends Point<T>表示这个类正在继承一个类,而这个类也是一个泛型类,同时Point3D类和Point类的类型参数都是T,所以这两个类的类型参数是一样的,都被限制为T extends Number

public class MyClass<T extends Number, S extends Date>
{
 class code here...
}

 第二点需要注意的地方是子类的构造器是如何调用超类构造器以及是如何实例化这个类的。并且我们发现这个类只有对z轴的方法,因为对x,y的方法已经写在超类中了,这里并不需要重复。

同样Point3DPoint有一个“is-a” relationship,所以我们可以用一个Point变量来引用Point3D对象:

Point3D<Integer> point = new Point3D<>(10, 20, 30);

最后,泛型类和非泛型类是可以相互继承与被继承的。

三、定义多个类型变量

之前的例子中,我们定义的泛型类只有一个类型变量,当我们的泛型类中有多种类型变量的时候,情况是相似的:

public class MyClass<T extends Number, S extends Date>
{
 class code here...
}

下面我们看一个例子,这个例子接受两个参数,这两个参数的参数类型都是不确定的:

/**
 The Pair class demonstrates a generic class
 with two type parameters.
 */

public class Pair<T, S>
{
    private T first; // The first item
    private S second; // The second item

    /**
     Constructor
     @param firstArg Assigned to the first item.
     @param secondArg Assigned to the second item.
     */

    public Pair(T firstArg, S secondArg)
    {
        first = firstArg;
        second = secondArg;
    }

    /**
     getFirst method
     @return The first item in the pair.
     */

    public T getFirst()
    {
        return first;
    }

    /**
     getSecond method
     @return The second item in the pair.
     */

    public S getSecond()
    {
        return second;
    }
}

下面我们用这个类将两个不同类型的变量拼接起来:

/**
 This program demonstrates the Pair class which
 has two type parameters.
 */

public class PairTest
{
    public static void main(String[] args)
    {
        // Create an Integer to hold an ID number.
        Integer idNumber = new Integer(475);

        // Create a String to hold a name.
        String name = "Smith, Sally";

        // Create a Pair object to hold the ID
        // number and the name.
        Pair<Integer, String> myPair =
                new Pair<>(idNumber, name);

        // Display the pair of items.
        System.out.println("ID Number: " +
                myPair.getFirst());
        System.out.println("Name: " +
                myPair.getSecond());
    }
}

得到的输出:

ID Number: 475
Name: Smith, Sally

 这个例子中,pair类中的两个参数类型是任意的。

四、接口与泛型

1、comparable接口

在java中,除了类可以是泛型的之外,接口也可以是泛型的,比如在java API中有一个接口叫做Comparable,定义如下:

public interface Comparable<T>
{
 int compareTo(T o);
}

此接口定义了一个名为compareTo的方法,它用于将调用对象与作为参数传递到方法中的另一个对象进行比较。接口定义了一个类型参数T,T是o参数的类型。

在java中,很多类都实现了这个接口,比如string类以及一些包装类。如果调用对象小于传递给o的对象,则该方法返回一个负数。如果调用对象等于传递给o的对象,则该方法返回0。如果调用对象大于传递给o的对象,则该方法返回一个正数。

假设我们正在编写一个名为Tree的类,并且我们希望Tree类能够实现Comparable接口:

public class Tree implements Comparable<Tree>
{
 public int compareTo(Tree o)
 {
 Method code here...
 }
 Other class code here...
}

我们把Tree当作类型参数传入了Comparable接口,所以实际上接口实际上变成了:

int compareTo(Tree o)

2、将类型变量限定在实现过某个接口的类中

方法通常的参数是对接口的引用,而不是对类的引用。例如,假设我们希望编写一个名为greatest的泛型方法,它将比较两个对象,并确定哪一个是这两个对象中的“更大”。当然,“更大”的概念完全取决于正在被比较的对象。如果我们正在比较两个豪华车,最昂贵的一个可能会被认为是更大的一个。然而,如果我们比较两个赛车,最高时速最高的物体可能被认为是更大的。任何作为参数传递给greatest方法的对象都必须是一个支持标准比较方法的类型,这样我们就可以确定比较的对象哪个更大。

Comparable接口就是用来确定这个类是不是支持标准的比较方法的,如果这个某个类实现了Comparable接口,那么这个类必然有一个compareTo方法,而对于这个类,这个特定的compareTo方法一定是符合实际情况的(对赛车就比较最高速度,对豪华车就比较价格),所以我们用T extends Comparable<T> 来表示T实现了Comparable接口。T extends Comparable<T>并不只是指继承了这个接口,而也指实现了这个接口)。

这个类将用compareTo方法来搜索一个数组(如果有,根据compareTo方法就会返回0,没找到就返回默认的-1):

/**
 This program uses a generic method to sequentially
 search an array for a value.
 */

public class GenericSearchArray
{
    public static void main(String[] args)
    {
        int position; // To hold a string's position in the array

        // Array of strings to search
        String[] names = { "Jack", "Kelly", "Beth",
                "Chris", "Kenny", "Britainy" };

        // Search the array for Chris.
        position = sequentialSearch(names, "Chris");

        // Determine whether Chris was found.
        if (position == −1)
        System.out.println("Chris is not in the array. ");
 else
        System.out.println("Chris is at position " + position);
    }

    /**
     The sequentialSearch method searches an array for
     a value.
     @param array The array to search.
     @param value The value to search for.
     @return The subscript of the value if found in the
     array, otherwise −1.
     */

    public static < E extends Comparable<E> >
    int sequentialSearch(E[] array, E value)
    {
        int index; // Loop control variable
        int position; // Position the value is found at
        boolean found; // Flag indicating search results

// Position 0 is the starting point of the search.
        index = 0;

        // Store the default values for position and found.
        position = −1;
        found = false;

        // Search the array.
        while (!found && index < array.length)
        {
            if (array[index].compareTo(value) == 0)
            {
                found = true;
                position = index;
            }
            index++;
        }
        return position;
    }
}

五、Erasure

当java编译器在遇到泛型的时候,编译器会根据实际情况,将泛型转换为真正的类型参数,当泛型没有指定上限的时候,java会将泛型自动转换为Object

public class Point<T>
{
 private T xCoordinate;
 private T yCoordinate;
 public Point(T x, T y)
 { code... }

实际上是:

public class Point
{
 private Object xCoordinate;
 private Object yCoordinate;
 public Point(Object x, Object y)
 { code... }

当指定上限的时候,java会将泛型转换为上限的类型:

public class Point<T extends Number>
{
 private T xCoordinate;
 private T yCoordinate;
 public Point

实际上是:

public class Point
{
 private Number xCoordinate;
 private Number yCoordinate;
 public Point(Number x, Number y)
 { code... }

这种操作被叫做Erasure。

当我们使用泛型的时候,很多时候编译器会自动帮你强制转换:

Integer x = new Integer(1);
Integer y = new Integer(2);
Point<Integer> myPoint = new Point<>(x, y);

我们将一个Integer类型的泛型对象传递给Integer类型的变量时:

Integer tempX = myPoint.getX();

而因为在遇到泛型的时候,实际上是将泛型转变为objetc类型进行处理的,所以当我们返回的时候,实际上是这样的:

Integer tempX = (Integer)myPoint.getX();

六、使用泛型的一些限制

1、不能创建泛型类型的实例对象

public class MyClass<T>
{
    public MyClass()
    {
        // The following statement causes an ERROR.
        T myObject = new T();
    }
}

表达式newT()意味着T是构造函数的名称,但它不是。

2、不能创建通用类对象的数组

下面这行语句不会过编:

ArrayList<String>[] a = new ArrayList<>[100];

这里是希望创建一个Arraylist<>类型的数组(注意不是创建一个长为100的Arraylist),在java中数组会包含数组的元素类型,但是由于erasure,泛型对象并不会包含类型,或者说泛型对象的类型要在实例化之后才会被确定,所以在实例化之前,编译器并不知道存在数组里的泛型对象的具体类型,所以编译器不会通过编译。

3、静态字段的类型不能是泛型,也不能将静态方法的类型设置为泛型

public class MyClass<T>
  {
  // The following statement will cause an ERROR.
     private static T value;
  }

因为所有泛型类最终映射到同一个原始类型类,而静态属性是类级别的,类和实例共同拥有它的一份存储,因此一份存储无法安放多个类型的属性。静态方法也是如此。

4、不能在泛型中使用异常

让我们想象一下,为了让Java应用程序使用泛型异常类,需要发生什么。 首先,必须能够在catch子句中使用泛型类型表示法。 然后,当抛出一个泛型异常时,系统必须检查用于创建异常类的类型参数,看它是否与catch子句中指定的类型匹配。 但这无法实现,因为在运行时,泛型类型信息已经被编译器擦除,所以编译器在编译时无法知道异常从哪里来,或者用什么类型参数来创建异常,所以不允许创建泛型异常类。 具体来说,你不允许创建Throwable的泛型子类。 也不允许在catch子句中使用泛型类型表示法。  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值