学习笔记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
当你创建了一个泛型类的时候,你只能将引用类型当作变量传入类型参数,把基础数据类型传入参数是不允许的。之前的例子中,我们使用了Double和Integer而非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)
注意,在static和void的单词之间,出现了<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