阿里云【名师课堂】Java高级开发9 ~ 13:泛型
泛型是一个很重要的技术,它可以帮助解决程序的参数转换问题。
课时9:泛型问题引出
假设:需要定义一个描述坐标的程序类Point,而这个类中需要提供有两个属性:横纵坐标x、y,对于这两个属性的内容可能有如下的几种选择:
- x = 10,y = 20(int型)
- x = 10.1,y = 20.2(double型)
- x = 北纬xx度,y = 东经xx度(String型)
那么现在首先要解决的就是Point类中的x或y的属性类型问题。
- 在Java中只有一种类型可以保存所有类型:
Object
- int自动装箱为Integer,Integer向上转型为Object;
- double自动装箱为Double,Double向上转型为Object;
- String向上转型为Object。
范例:定义Point类
class Point {
private Object x ;
private Object y ;
public Object getX() {
return x;
}
public void setX(Object x) {
this.x = x;
}
public Object getY() {
return y;
}
public void setY(Object y) {
this.y = y;
}
}
范例:设置整型坐标
public class TestDemo {
public static void main(String[] args) {
// 第一步:设置数据
Point p = new Point() ;
p.setX(10) ;
p.setY(20) ;
//第二步:取出数据
int x = (Integer)p.getX() ; // Object不会自动变为int,需要强制转换
int y = (Integer)p.getY() ;
System.out.println("x = " + x + ",y = " + y) ;
}
}
范例:设置String型坐标
public class TestDemo {
public static void main(String[] args) {
// 第一步:设置数据
Point p = new Point() ;
p.setX("北纬10度") ;
p.setY("东经20度") ;
//第二步:取出数据
String x = (String)p.getX() ; // Object不会自动变为int,需要强制转换
String y = (String)p.getY() ;
System.out.println("x = " + x + ",y = " + y) ;
}
}
现在看起来已经解决了问题,但是现在解决问题的关键是Object,所以问题也出现在Object上。
范例:观察程序的问题
将坐标设置为了double和String,但是接收方(get)不知道,仍然认为是String,所以在程序执行之后:
ClassCastException
指的是两个没有关系的对象进行强制转换所带来的异常。本程序中在赋值时语法不会有任何限制,但是在执行时出现了问题:向下转型是不安全的操作,有安全隐患。
课时10:泛型实现
简单地说,泛型就是在类定义的时候并不会设置类中的属性或方法中的参数的具体类型,而是在类使用的时候再进行定义。所以如果要想进行这种泛型的操作,就必须做一个类型标记的声明。
范例:定义Point类
// 在Point类中定义的时候完全不知道x和y的属性是什么类型,由使用者来决定
class Point <T> {
private T x ;
private T y ;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
public class TestDemo {
public static void main(String[] args) {
// 第一步:设置数据
Point<String> p = new Point<>() ; // 相当于把Point中的T换为String
p.setX("北纬10度") ; // 这时就一定要设置为String类
p.setY("东经20度") ;
//第二步:取出数据
String x = p.getX() ; // 不再需要强制转换
String y = p.getY() ;
System.out.println("x = " + x + ",y = " + y) ;
}
}
课时11:通配符
通配符是本节重点。
虽然通过泛型避免了ClassCastException
的问题,但是也带来了新的问题:参数的统一。
范例:观察如下程序
class Message <T> {
private T note ;
public T getNote() {
return note;
}
public void setNote(T note) {
this.note = note;
}
}
public class TestDemo {
public static void main(String[] args) {
Message<String> msg = new Message<>() ;
msg.setNote("HelloWorld!") ;
method(msg) ;
}
public static void method(Message<String> temp) {
System.out.println(temp.getNote());
}
}
但是这样会带来新的问题:
- 如果泛型的类型设置的不是String,而是Integer:
msg.setNote(69) ;
,则method(msg) ;
会出现错误,因为只能够接收String。- 泛型类型只允许设置引用类型:类、接口,而不能使用基本数据类型。
既然泛型的类型问题造成了这个困扰,那么:
- 可以不设置泛型的类型吗?
- 不可以,不设置类型就是Object类,会带来转型问题、覆盖问题。
- 真正的解决方法:
- 可以接收所有的泛型类型,但是又不能够让用户任意修改。可以利用通配符
"?"
来实现。
- 可以接收所有的泛型类型,但是又不能够让用户任意修改。可以利用通配符
public class TestDemo {
public static void main(String[] args) {
Message<Integer> msg = new Message<>() ;
msg.setNote(69) ;
method(msg) ;
}
// 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
public static void method(Message<?> temp) {
// temp.setNote("HelloWorld!") ; // 不能修改
System.out.println(temp.getNote());
}
}
而在"?"的基础上又产生了两个子通配符:
? extends 类
:设置泛型上限- 如:? extends Number,表示只能设置Number或其子类,例如:Number、Integer、Double等;
- 一般使用在类的声明和方法的参数上
? super 类
:设置泛型下限- 如:? super String,表示只能设置String或其父类,比如String、Object。
- 一般只使用在方法的参数上
范例:观察泛型上限
设置上限为Number后,定义为String型时会有明显的错误提示。
范例:观察泛型下限
设置下限为String后,定义为Integer型时会有错误提示。
课时12:泛型接口
泛型除了可以定义在类中,也可以定义在接口之中,这种情况称为泛型接口。
范例:定义一个泛型接口
interface IMessage <T> { // 在接口上定义了泛型
public void print(T t) ;
}
但是对于这个接口的实现子类有两种做法:
- 一、在子类定义时继续使用泛型
class MessageImpl<T> implements IMessage<T> {
public void print(T t) {
System.out.println(t);
}
}
public class TestDemo {
public static void main(String[] args) {
IMessage<String> msg = new MessageImpl<>() ;
msg.print("HelloWorld") ;
}
}
- 二、在子类实现接口时明确给出具体类型
class MessageImpl implements IMessage<String> { // 子类类型确定了
public void print(String t) {
System.out.println(t);
}
}
public class TestDemo {
public static void main(String[] args) {
IMessage<String> msg = new MessageImpl() ; // 接口有泛型,需要带上<>,子类没有泛型
msg.print("HelloWorld") ;
}
}
课时13:泛型方法
在之前学习类或接口上发现都可以在里面的方法中继续使用泛型,这种方法就称作泛型方法。但是泛型方法不一定非要定义在泛型类或泛型接口中,也可以单独定义。
public class TestDemo {
public static void main(String[] args) {
Integer[] data = method(1 , 2 , 3 , 4) ;
for (int temp : data) { // 迭代和自动拆箱
System.out.println(temp) ;
}
}
// <T>描述的是泛型标记的声明
public static <T> T[] method(T ... args) {
return args ;
}
}
用起来很别扭,一般情况下还是不要使用。