JavaSE高级开发-泛型:
JDK1.5以后引入的三大常用新特性:
- 泛型:解决参数的转换问题;
- 枚举:enum;
- 注释:Annotation;
一、泛型问题引出:
假设需要定义一个可以描述坐标点的类Point,需要提供两个属性x,y。对于这两个属性的内容可以有如下的选择:
- x = 10; y = 20;
- x = 10.1; y = 20.1;
- x = 东经80度; y = 北纬20度;
在Java中,只有Object型可以保存所有的类型。
【相关代码如下】:
class Point{
private Object x;
private Object y;
public void setX(Object x) {
this.x = x;
}
public Object getX() {
return x;
}
public void setY(Object y) {
this.y = y;
}
public Object getY() {
return y;
}
}
public class test{
public static void main(String[] args) {
Point point1 = new Point();
//自动装箱并且向上转型为Object:
point1.setX(10);
point1.setY(20);
//由Object强制向下转型为int,并且自动拆箱:
int x = (Integer)point1.getX();
int y = (Integer)point1.getY();
System.out.println("整型坐标:("+x+","+y+")");
Point point2 = new Point();
//自动装箱并且向上转型为Object:
point2.setX(10.1);
point2.setY(20.1);
//由Object强制向下转型为double,并且自动拆箱:
double z = (Double)point2.getX();
double w = (Double)point2.getY();
System.out.println("浮点型坐标:("+z+","+w+")");
Point point3 = new Point();
//自动装箱并且向上转型为Object:
point3.setX("东经20度");
point3.setY("北纬40度");
//由Object强制向下转型为String,并且自动拆箱:
String a = (String)point3.getX();
String b = (String)point3.getY();
System.out.println("字符串坐标:("+a+","+b+")");
//与point4代码对比:
Point point5 = new Point();
point5.setX(10);
point5.setY("北纬40度");
System.out.println("字符串坐标:("+point5.getX()+","+point5.getY()+")");
//错误代码:在主方法中,是用户输入,但是这里用户并不知道x的
//坐标是double,接收方也不知道用户的输入,就会产生问题:
//ClassCastException(指两个没有关系的对象因为强转而出现的异常),所以说
//向下转型并不安全,会带来隐患。
Point point4 = new Point();
point4.setX(10);
point4.setY("北纬40度");
String c = (String)point4.getX();
String d = (String)point4.getY();
System.out.println("字符串坐标:("+c+","+d+")");
}
}
【注意】:
- 在进行向下转型的过程中,会出现程序错误的问题,因此向下转型是不安全的操作,会带来隐患;
- 当开发中的程序可以避免向下转型(通过泛型),也就意味着安全隐患被消除,尽量不要使用向下转型;
二、泛型类的基本使用:
(1)泛型的定义:
- 指的是在类定义的时候,并不会设置类中的属性或方法中的参数的具体类型,而是在类使用的时候在进行定义。
(2)如何进行泛型的操作?
- 做一个类型标记的声明。
(3)泛型类的基本语法:
class MyClass<T>{
T value1;
}
【注意】:
- <>中的T称为类型参数,用于指代任何类型;
- 实际上这个T你可以任意写,但是出于规范的目的,还是建议我们用单个大写字母来代替类型参数,常见的有:
- T:代表一般的任意类;
- E:代表Element的意思,或者Exception异常的意思;
- K:代表Key的意思;
- V:代表value的意思,通常与K一起配合使用;
- S:代表Subtype的意思; - 如果一个类被的形式定义,那么它就被称为泛型类;
(4)泛型类的使用:
- MyClass myClass = new MyClass;
- MyClass myClass = new MyClass;
【注意】:
- 泛型只能接受类,所以的基本数据类型必须使用包装类!
- 泛型类可以接收多个类型参数;
三、泛型方法:
泛型不仅可以定义类,还可以单独来定义方法。
(1)泛型方法的定义格式:
class MyClass{
public <T> void testMethod(T t){
System.out.println(t);
}
}
【注意】:
- 泛型类与泛型方法不同的是,放在public static 之后,返回值的前面;
- <>中的T,在类中是类型参数,但是在方法中,T是参数化类型,并不是运行时真正的参数;
- 声明的类型参数,也可以当做返回值的类型,如下代码所示:
class MyClass{
public static <T> T testMethod(T t){
return t;
}
}
public class Test1{
public static void main(String[] args) {
MyClass myClass = new MyClass();
System.out.println(myClass.testMethod(10));
}
}
泛型方法与泛型类可以同时存在,如下代码所示:
class MyClass <T>{
//泛型方法:使用别的类型参数名,避免与泛型类混淆
public <E> E printT(E e){
return e;
}
public void ptintOthers(){
//整型:
System.out.println(123);
}
}
public class test{
public static void main(String[] args) {
MyClass myClass = new MyClass();
myClass.ptintOthers();
//String类型:
System.out.println(myClass.printT("123木头人"));
}
}
【注意】:
- 泛型类中的类型参数与泛型方法中的类型参数没有相应的联系,泛型方法始终以自己定义的类型参数为准;
- 如果在泛型类中定义了一个泛型方法,为了避免与泛型类中的参数类型混淆,可以重新使用一个类型参数名,尽量使泛型类与泛型方法不重名;
四、通配符(重点)
(1)问题引入:
使用泛型类很好的避免了向下转型,使得程序变得安全,不在出现ClassCastException的问题,但是,使用泛型会带来参数不统一的问题,观察下面的代码:
class Message <T>{
private T message;
public T getMessage(){
return message;
}
public void setMessage(T message){
this.message = message;
}
}
public class test{
public static void main(String[] args) {
Message<String> message1 = new Message<String>();
message1.setMessage("hello world");
fun(message1);
/**
* 注意:如果用户输入的不是String类型,就会出现新的问题
* Message<String> message2 = new Message<String>();
* message2.setMessage(123);
* fun(message2);//这里就会出现错误
*/
}
public static void fun(Message<String> temp){
System.out.println(temp.getMessage());
}
}
(2)解决泛型类型带来的参数不统一的问题——使用通配符
现在需要的解决方案:可以接收所有类型的泛型,但是又不能够让用户随意修改,这时候就可以通过通配符“?”解决此问题,示例代码如下所示:
class Message <T>{
private T message;
public T getMessage(){
return message;
}
public void setMessage(T message){
this.message = message;
}
}
public class test{
public static void main(String[] args) {
Message<String> message1 = new Message<String>();
message1.setMessage("hello world");
fun(message1);
Message<Integer> message2 = new Message<Integer>();
message2.setMessage(123);
fun(message2);
}
//注意:此时使用通配符“?”代表它可以接收任意类型,但是由于
//类型不确定,所以无法修改。
public static void fun(Message<?> temp){
System.out.println(temp.getMessage());
}
}
(3)在“?”的基础上又产生的两个子通配符:
? extends 类:设置泛型上限:
[例如]:
? extends Number,表示只能够设置Number或其子类,比如:Integer、Double等;
? super 类:设置泛型下限:
[例如]:
? super String,表示只能够设置String及其父类Object;
▼观察设置泛型上限:可以用在声明,不能修改
class Message<T extends Number>{
private T message;
public T getMessage(){
return message;
}
public void setMessage(T message){
this.message = message;
}
}
public class test{
public static void main(String[] args) {
Message<Integer> message = new Message<Integer>();
message.setMessage(123);
fun(message);
}
//表示只能够设置Number类及其子类:
//此时使用通配符“?”表示它可以接收任何类型,但是由于不确定类型,所以无法修改
public static void fun(Message<? extends Number> temp){
//temp.setMessage(100);依然无法修改
System.out.println(temp.getMessage());
}
}
▼观察设置泛型下限:只能够用在方法参数中,可以修改
class Message<T>{
private T message;
public T getMessage() {
return message;
}
public void setMessage(T message){
this.message = message;
}
}
public class test{
public static void main(String[] args) {
Message<String> message = new Message();
message.setMessage("123木头人");
fun(message);
}
public static void fun(Message<? super String> temp){
//此时可以修改:
temp.setMessage("123");
System.out.println(temp.getMessage());
}
}
五、泛型接口
泛型可以定义在类中,也可以定义在接口里面,这就是所谓的泛型接口。
(1)泛型接口的定义:
Interface IMessage<T> {
public void print(T t);
}
(2)对于这个接口,实现子类的两种做法:
【在子类定义的时候继续使用泛型】:
interface IMessage<T>{
public void print(T t);
}
class MessageImpl<T> implements IMessage<T>{
@Override
public void print(T t) {
System.out.println(t);
}
}
public class test{
public static void main(String[] args) {
IMessage<String> msg = new MessageImpl<>();
msg.print("123木头人");
}
}
【在子类实现接口的时候明确给出具体的做法:
interface IMessage<T>{
public void print(T t);
}
class MessageImpl implements IMessage<String>{
@Override
public void print(String t) {
System.out.println(t);
}
}
public class test{
public static void main(String[] args) {
IMessage<String> msg = new MessageImpl();
msg.print("123木头人");
}
}
六、类型擦除
泛型是在JDK1.5之后才引进的概念,由上述可以看出,泛型代码可以与之前的版本很好的兼容,这是因为,泛型信息仅仅存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除,专业属于就叫做类型擦除,也就是说,泛型与普通的类在Java虚拟机内没有什么不同。
class MyClass<T>{
private T message;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
public void testMethod1(T t){
System.out.println(t);
}
}
public class test{
public static void main(String[] args) {
MyClass<String> myclass1 = new MyClass<>();
MyClass<Integer> myClass2 = new MyClass<>();
//注意:getClass() class<? extends MyClass>设置了泛型类的上限。
System.out.println(myclass1.getClass() == myClass2.getClass());
//结果是:true,因为MyClass<String>和MyClass<Integer>在JVM中的Class
//都是MyClass.class
}
}
【观察泛型中的类型擦除】:
import java.lang.reflect.Field;
class MyClass<T, E>{
private T message;
private E text;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
public E getText() {
return text;
}
public void setText(E text) {
this.text = text;
}
public void print(T t, E e){
System.out.println(t+","+e);
}
}
public class test{
public static void main(String[] args) {
MyClass<String, Integer> myClass = new MyClass<>();
//取得类对象
Class cls = myClass.getClass();
Field[] field = cls.getDeclaredFields();
for (Field field1: field) {
System.out.println(field1.getType());
}
}
}
【注意】:
在泛型类被类型擦除的时候,之前的泛型类参数部分如果没有指定上限,如则会被转译成普通的Object类型,如果指定了上限如则类型参数就会被替换为类型上限。