其他分享
首页 > 其他分享> > 学习笔记14(初见泛型)

学习笔记14(初见泛型)

作者:互联网

一、泛型简介

回顾之前我们已经用烂了的arraylist,它可以根据元素的个数自动增加长度,它还可以储存不同种类的数据,相较于普通的数组,这是arraylist两个主要的优势,下面我们看一个例子:

import java.util.ArrayList;

/**
 This program demonstrates that an ArrayList can accept
 a mixture of object types as its elements.
 */

public class UnsafeArrayList
{
    public static void main(String[] args)
    {
        // Create an ArrayList object.
        ArrayList list = new ArrayList();

        // Store a variety of objects in the list.
        list.add("Java is fun!"); // Add a String
        list.add(new Double(2.5)); // Add a Double
        list.add(new Rectangle(10, 12)); // Add a Rectangle

        // Retrieve a reference to each object in the list.
        // Note that the reference returned from the get
        // method must be cast to the correct type.
        String s = (String)list.get(0);
        Double d = (Double)list.get(1);
        Rectangle r = (Rectangle)list.get(2);
    }
}

注意看调用get方法的那几行,在每一次调用get方法是时候,都将实例强制转化了,这是必须的,如果不进行强制转化java就会在编译的时候报错。

不过即使你加上了强制转化,程序虽然在编译的时候不报错了,但是在运行的时候依旧有可能报错,将上面的代码稍作更改:

// Create an ArrayList object.
 ArrayList list = new ArrayList();

 // Store a variety of objects in the list.
 list.add("Java is fun!"); // Store a String at element 0
 list.add(new Double(2.5)); // Store a Double at element 1
 list.add(new Rectangle(10, 12)); // Store a Rectangle at element 2

 // Retrieve a reference to each object in the list.
 Double d = (Double)list.get(0); // Error! Element 0 is a String
 String s = (String)list.get(1);
 Rectangle r = (Rectangle)list.get(2);

这时强制转化的类型出错了,在运行的时候程序报错,一个ClassCastException异常会被扔出。

幸运的是,Java提供了一个被称为泛型的特性,通常被称为泛型类,泛型允许编译器执行类型检查和报告类型不兼容性。与其在运行时发现代码中的类型不匹配,在编译的时候发现异常显然是更好的选择。

泛型类或方法是一种 指定该类或方法使用时 面向的对象类型 的方法。如果尝试将类或方法用于不允许类型的对象,则编译时发生错误。在array list中,它就可以指定你储存在array list中的数据的类型,比如只允许储存string类的对象到arraylist中:

ArrayList<String> myList = new ArrayList<String>();

这时,编译器就已经知道array list中储存的数据类型,所以再调用get方法指定对象的引用的时候,就不需要强制转化了:

ArrayList<String> myList = new ArrayList<String>();
myList.add("Java is fun!");
String str = myList.get(0);

当我们使用泛型的时候,我们可以让编译器自己推断数据的类型,要达到这个目的我们可以使用   "<>"符号,这个符号作为一个整体。比如原来array list这么写:

ArrayList<String> list = new ArrayList<String>();

现在可以省略右侧的string:

ArrayList<Student> list = new ArrayList<>();

string两边都有。左侧用来声明引用变量的类型,右侧用来调用ArrayList构造函数的部分。

二、创建自己的泛型类

类似于array list,java提供了一些自带的泛型类,当然你也可以自己写一个泛型类:

/**
 The Point class holds X and Y coordinates. The data type
 of the coordinates is generic.
 */

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;
    }

    /**
     The setX method sets the X coordinate.
     @param x The value for the X coordinate.
     */

    public void setX(T x)
    {
        xCoordinate = x;
    }

    /**
     The setY method sets the Y coordinate.
     @param y The value for the Y coordinate.
     */

    public void setY(T y)
    {
        yCoordinate = y;
    }

    /**
     The getX method returns the X coordinate.
     @return The value of the X coordinate.
     */

    public T getX()
    {
        return xCoordinate;
    }

    /**
     The getY method returns the Y coordinate.
     @return The value of the Y coordinate.
     */

    public T getY()
    {
        return yCoordinate;
    }
}

Point类的实例有两个值,分别存储两个坐标。注意到这个类的头,出现了“<T>”,这是一个类型参数,类型参数是表示实际参数类型的标识符。当你实例化这个类的时候,你可以使用真正的参数类型(Integer啊balabala):

Integer intX = new Integer(10);
Integer intY = new Integer(20);
Point<Integer> myPoint = new Point<>(intX, intY);

 对于Point类中的实例,T就变成了Integer:

 如果我们将整数作为类型参数传递给T,这些声明实际上会变成如下所示的样子:

public Point(Integer x, Integer y)
public void setX(Integer x)
public void setY(Integer y)
public Integer getX()
public Integer getY()

下面的代买创建了两个Point实例,在第一个实例中,Double传递给类型参数T,在第二个实例中,Integer类型传递给T:


/**
 This program demonstrates the Point class.
 */

public class TestPoint
{
    public static void main(String[] args)
    {
        // Create two Double objects to use as coordinates.
        Double dblX = new Double(1.5);
        Double dblY = new Double(2.5);

        // Create a Point object that can hold Doubles.
        Point<Double> dPoint = new Point<>(dblX, dblY);

        // Create two Integer objects to use as coordinates.
        Integer intX = new Integer(10);
        Integer intY = new Integer(20);

        // Create a Point object that can hold Integers.
        Point<Integer> iPoint = new Point<>(intX, intY);

        // Display the Double values stored in dPoint.
        System.out.println("Here are the values in dPoint.");
        System.out.println("X Coordinate: " + dPoint.getX());
        System.out.println("Y Coordinate: " + dPoint.getY());
        System.out.println();

        // Display the Integer values stored in iPoint.
        System.out.println("Here are the values in iPoint.");
        System.out.println("X Coordinate: " + iPoint.getX());
        System.out.println("Y Coordinate: " + iPoint.getY());
    }
}

输出如下:

Here are the values in dPoint. X Coordinate: 1.5 Y Coordinate: 2.5 Here are the values in iPoint. X Coordinate: 10 Y Coordinate:    20

当“Point<Double> dPoint = new Point<>(dblX, dblY);” 执行的时候,Double作为参数传递给T,那么对于这个实例,Point类的构造器就变成了:

public Point(Double x, Double y)

此时如果x,y不是Double类型,编译器将报错。

三、类型参数

1、autoboxing和unboxing

当你创建了一个泛型类的时候,你只能将引用类型当作变量传入类型参数,把基础数据类型传入参数是不允许的。之前的例子中,我们使用了DoubleInteger而非double and int

不过虽然泛型只能接受引用类型作为自己的类型参数,我们依旧可以使用java中的autoboxing来简化我们的代码,之前的例子也可以写成这样:

Point<Double> dPoint = new Point<>(1.5, 2.5);

这里的1.5和2.5就由double被autoboxing成Double,所以这里的语句和合法的。

与autoboxing相对的是Unboxing,看下面的例子:

// Use autoboxing to pass doubles to the constructor.
Point<Double> dPoint = new Point<>(1.5, 2.5);
// Use unboxing to retrieve the X and Y coordinates
// and assign them to double variables.
double x = dPoint.getX();
double y = dPoint.getY();

最后的两句话中,get方法返回的应该是一个Double类型的对象,但是变量类型却是double,所以java自动将Double Unboxing成double。

2、不指定类型参数实例化泛型类

虽然不建议使用,但可以不提供类型参数地创建一个泛型类的实例。

Point myPoint = new Point(x, y);

在这里并没有提供类型参数,当泛型类实例化提时没有供类型参数时,Java默认情况下将提供Object作为类型参数,所以实际上,myPoint类中的代码是这样:

private Object xCoordinate; // The X coordinate
private Object yCoordinate; // The Y coordinate

同样地,在类方法中出现的每一次T都将成为Object

当这样使用时,编译器将显式如下警告:

TestPoint2.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details.

所以不写参数类型是非常不推荐的,因为这样放弃了本来代码中类型的安全性 。但是这样做也有一个好处,即你可以让变量引用不同类型的实例。因为根据多态性,Object类可以引用任何类型的变量。

同时,如果不写参数类型,当你返回泛型类的实例的时候,你需要将Object类的实例强制转化成你希望的类:

// Create an Integer and a Double.
Integer x = new Integer(1);
Double y = new Double(7.5);
// Create a Point object with no type argument.
Point myPoint = new Point(x, y);
// Retrieve the X and Y coordinates.
Integer xx = (Integer) myPoint.getX();
Double yy = (Double) myPoint.getY();

3、常用的类型参数名

本例中所用的T并非确定的或唯一的,你可以用任何合法的标识符来表示类型参数名,以下是常用的类型参数名称:

四、将泛型对象传递给方法 

假设我们有一个方法用来打印Point的x坐标和y坐标,方法如下:

public static void printPoint(Point<Integer> point)
{
 System.out.println("X Coordinate: " + point.getX());
 System.out.println("Y Coordinate: " + point.getY());
}

这个方法接受一个Point<Integer>对象作为参数,但是问题是,如果我们传递的参数是一个Point<Double>类型的变量呢?从道理上来说,这种传递显然是合理的,坐标当然有可能为小数,但是这个方法没办法接受作为Point<Double>参数。重载这个方法又太过麻烦,毕竟不论什么类型的参数你都只希望打印出坐标就好,对于完全一样的操作,我们能不能对其他类型的参数“复用”这个方法呢?这时你可能想到了继承和多态。

// Will this do what we want?
public static void printPoint(Point<Number> point)
{
 System.out.println("X Coordinate: " + point.getX());
 System.out.println("Y Coordinate: " + point.getY());
}

既然不管什么类型的数字包装类都继承自Number类,那我们直接把参数类型改成Number不久行了吗?遗憾的是,如果这么写,编译器将只接受Number类的对象,而尝试将其子类当作参数传入都会报错。

那我们到底应该怎么办呢?解决方法如下:

public static void printPoint(Point<?> point)
{
 System.out.println("X Coordinate: " + point.getX());
 System.out.println("Y Coordinate: " + point.getY());
}

注意到参数的类型是Point<?>,在括号里的是?通配符,现在我们就可以向方法里传递任何参数了:

Point<Integer> iPoint = new Point<>(1, 2);
Point<Double> dPoint = new Point<>(1.5, 2.5);
printPoint(iPoint);
printPoint(dPoint);

但是新的问题出现了:原来的方法太过限制,而现在的方法又太开放了,它可以接受任何类型的参数作为其变量,这显然也不是我们想要的。我们想要的是这个方法可以接受任何数字类型的参数作为变量,那我们如何限制参数类型的范围呢?这时我们就需要extends关键字了。

public static void printPoint(Point<? extends Number> point)
{
 System.out.println("X Coordinate: " + point.getX());
 System.out.println("Y Coordinate: " + point.getY());
}

在这个版本的方法中,参数的类型是Point<? extends Number>。这意味着点对象的类型参数可以是“Number”,或扩展“Number”的任何子类,所以前两个方法调用都是合法的,第三个方法调用会报错:

Point<Integer> iPoint = new Point<>(1, 2);
Point<Double> dPoint = new Point<>(1.5, 2.5);
printPoint(iPoint);
printPoint(dPoint);

Point<String> sPoint = new Point<>("1", "2");
printPoint(sPoint); // Error!

如果一个方法有很多参数,那么按之前的写法写起来就会非常麻烦:

public static void doSomething(Point<? extends Number> arg1,
                               Point<? extends Number> arg2,
                               Point<? extends Number> arg3)

不过Java提供了另外一种规定参数类型的语法:

public static <T extends Number> void doSomething(Point<T> arg1,
                                                  Point<T> arg2,
                                                  Point<T> arg3)

注意,在staticvoid的单词之间,出现了<T extends Number>。这定义了一个名为T的类型参数,并指定T可以接受Number或Number的子类。方法参数的类型是Point<T>。这意味着该方法可以接受任何类型参数为Number或数字子类的点对象。

 五、关键字super和extends

当使用在泛型中使用extends关键词时,实际上是在为类型参数限制一个上界。例如<T extends Number>这个例子中,T的上界就是Number类。这意味着T可以是类层次结构中低于Number的任何类型(或Number本身)的类型,但它不能是类层次结构中高于Number的类型。

除了extends关键字外,还可以使用关键字super来限制类型参数。下面是一个例子:

public static void doSomething(Point<? super Integer> arg)

 和之前的extends关键词相反,这里的super设定了下界,这里的参数可以是任何Integer类之上的  类型的参数。

标签:14,Point,Double,list,类型,初见,Integer,泛型,new
来源: https://blog.csdn.net/qq_52315213/article/details/120948388