对于Java中泛型的理解

对Java中泛型的理解

在面向对象的开发过程之中,除了有面向对象的三大特征以外,实际上还留有一个最为重要的对象转型的概念,如果说现在要通过向上转型来编写程序,那么一般不会有太大的语法问题,但是反过来,如果说是要以向下转型为例,这个时候就有可能出现类型的转换异常(ClassCastException),如果想要彻底解决这个问题,在JDK 1.5之后就引出了泛型的概念。

一、泛型问题的引出

在Java编程之中,通过之前的一些列分析我们可以发现,Object类型可以实现任意数据类型的传递,于是,现在假设有这样的一个需求:定义一个消息的程序类,要发送的消息可能有字符串、整型、浮点型以及其他的各种类型。

那么这个时候首先需要解决的设计上的关键性的技术问题就在于:如何确认要保存的数据类型是什么呢?按照我们之前的设计来讲会发现,如果现在要通过属性进行具体消息的保存,则就必须为属性定义一个专属的类型,但是现在既然需要保存的类型有三种,所以此时唯一可以想到的解决方案就是通过Object类来解决,因为存在有如下的转换关系:

  • int==>Object:int -> 自动装箱 -> Object向上转型;
  • double==>Object:double ->自动装箱 -> Object向上转型;
  • String==>Object:String对象实例 -> Object向上转型;

范例:通过Object实现消息类型的描述

private Object content;//可以实现各种类型数据的存储
	public Object getContent() {
		return this.content;
	}
	public void setContent(Object content) {
		this.content = content;
	}
	public void send() {
		System.out.println("【消息发送】" + this.content);
	}

那么随后就可以通过如下的代码对当前的Message类的功能进行完整的测试。

范例:保存整型数据信息

public class Message {
	private Object content;//可以实现各种类型数据的存储
	public Object getContent() {
		return this.content;
	}
	public void setContent(Object content) {
		this.content = content;
	}
	public void send() {
		System.out.println("【消息发送】" + this.content);
	}
	
	public static void main(String[] args) throws Exception {
		Message message = new Message();
		message.setContent(10);
		message.send();
	}
}

/**
	程序执行结果:【消息发送】10
**/

范例:实现字符串数据的发送

public class Message {
	private Object content;//可以实现各种类型数据的存储
	public Object getContent() {
		return this.content;
	}
	public void setContent(Object content) {
		this.content = content;
	}
	public void send() {
		System.out.println("【消息发送】" + this.content);
	}
	
	public static void main(String[] args) throws Exception {
		Message message = new Message();
		message.setContent("www.baidu.com");
		message.send();
	}
}

/**
	程序执行结果:【消息发送】www.baidu.com
**/

通过以上的代码测试,发现通过Object类型解决了数据类型的统一问题,但是随后又出现了另一个情况,这个情况主要是由Object类型所引起的。

范例:观察Object所可能带来的问题

public class Message {
	private Object content;//可以实现各种类型数据的存储
	public Object getContent() {
		return this.content;
	}
	public void setContent(Object content) {
		this.content = content;
	}
	public void send() {
		System.out.println("【消息发送】" + this.content);
	}
	
	public static void main(String[] args) throws Exception 	{
		Message message = new Message();
		message.setContent(99.99);//原始设置的数据应该是整型
		String value = (String) message.getContent();
		System.out.println("【message中的消息内容】"+ value);
	}
}

/**
	程序执行结果:Exception in thread "main" java.lang.ClassCastException
**/

此时的程序原始就是打算设置有一个double类型的消息,但是由于开发者的失误,将整个的程序由Double强制转为了String,所以最终会产生ClassCastException异常,而最让所有开发者不爽的地方在于,此时的问题并不是在程序编译的时候发现的,而是在程序执行的时候发现的。或者换个角度来说:正是因为有了这种强制性的类型转换,所以才导致了整个程序存在有严重的安全隐患,所以为了解决这样的问题,才在JDK 1.5之后正式引入了泛型技术,利用泛型技术解决程序中可能存在的ClassCastException异常。

二、泛型的基本定义

在JDK 1.5之后JDK里面支持有泛型的使用,而所谓的泛型最大的特点在于:声明一个属性或者是变量的时候不设置其具体的数据类型,而是在指定结构使用的时候才进行动态的配置,而使用泛型主要是为了解决强制类型转换的设计缺陷。

范例:定义泛型

//如果要定义泛型,则采用"<泛型标记>"的方式来定义,泛型标记可以随便编写,现在的T描述的是Type
public class Message<T> {
	private T content;//可以实现各种类型数据的存储
	public T getContent() {
		return this.content;
	}
	public void setContent(T content) {
		this.content = content;
	}
	public void send() {
		System.out.println("【消息发送】" + this.content);
	}
	
	public static void main(String[] args) throws Exception {
		//在使用Message类的时候,手工的为泛型定义具体的数据类型
		Message<Integer> message = new Message<Integer>();
		message.setContent(99);//原始设置的数据应该是整型
		int value = message.getContent();//getContent此时直接返回泛型类型,Integer自动拆箱为int
		System.out.println("【message中的消息内容】"+ value);
	}
}

/**
	程序执行结果:【message中的消息内容】99
**/

此时的Message类上提供有一个泛型的标记,这样一来对于整个程序来讲就需要在这个类使用的时候为其动态的设置有一个具体的泛型,这个泛型类型就将成为Message类中content属性、setter方法中的content变量、getter方法中的返回值类型。如果说此时设置的数据类型有错误,那么就会在程序编译的时候自动的帮助用户进行检测,从而避免了数据类型错误的局面,由于具体的getContent()方法返回的是具体的Integer类型,那么也就避免了强制性的向下转型,从而解决了ClassCastException发生的问题,但是在使用泛型的过程中,也存在有三个注意点:

  • 如果在定义泛型类型的时候只允许设置类,即:如果要设置为基本数据类型则就必须通过包装类来进行处理;

  • 随着JDK版本的不同,对于泛型也有了简化的处理,在JDK 1.7之后可以使用如下的简化定义:

    Message message = new Message<>();

  • 如果使用一个拥有了泛型定义类的时候,并且没有设置泛型的类型,为了保证程序不出现错误,则会使用Object作为默认的泛型类型。

    //如果要定义泛型,则采用"<泛型标记>"的方式来定义,泛型标记可以随便编写,现在的T描述的是Type
    
    public class Message<T> {
    	private T content;//可以实现各种类型数据的存储
    	public T getContent() {
    		return this.content;
    	}
    	public void setContent(T content) {
    		this.content = content;
    	}
    	public void send() {
    		System.out.println("【消息发送】" + this.content);
    	}
    	
    	public static void main(String[] args) throws Exception {
    		//在使用Message类的时候,手工的为泛型定义具体的数据类型
    		Message message = new Message();
    		message.setContent(99);//原始设置的数据应该是整型
    		int value = (Integer)message.getContent();
    		System.out.println("【message中的消息内容】"+ value);
    	}
    }
    /**
    	程序编译结果:产生了不安全的操作
    **/
    /**
    	程序执行结果:【message中的消息内容】99
    **/
    

由于此时在使用Message类进行对象实例化的时候并没有定义具体的泛型类型,那么在程序编译时就会出现有警告信息,在一些开发比较严格的环境下,只要程序代码有警告信息都不允许发布。

三、泛型通配符

利用泛型技术的确可以解决项目中存在的ClassCastException安全隐患,但是一旦对象使用了泛型的方式进行定义之后会带来另外一个问题:对象引用传递的问题。

范例:观察一个对象引用传递的问题

public class Message<T> {
	private T content;//可以实现各种类型数据的存储
	public T getContent() {
		return this.content;
	}
	public void setContent(T content) {
		this.content = content;
	}
	public void send() {
		System.out.println("【消息发送】" + this.content);
	}
	
	public static void main(String[] args) throws Exception {
		//在使用Message类的时候,手工的为泛型定义具体的数据类型
		Message<String> message = new Message<>();
		message.setContent("www.baidu.com");
		info(message);
	}
	public static void info(Message<String> temp) {
		System.out.println(temp.getContent());
	}
}

/**
	程序执行结果:www.baidu.com
**/

此时程序没有任何问题,因为所传递的message对象和info()方法之中的temp参数的类型是完全匹配的,但是在Message上可以使用的泛型类型实际上有很多种,所有数据类型都可以设置为泛型,那么如果说此时当前的message对象采用的是Integer的泛型呢?

范例:使用Integer作为Message类对象的泛型类型

public class Message<T> {
	private T content;//可以实现各种类型数据的存储
	public T getContent() {
		return this.content;
	}
	public void setContent(T content) {
		this.content = content;
	}
	public void send() {
		System.out.println("【消息发送】" + this.content);
	}
	
	public static void main(String[] args) throws Exception {
		//在使用Message类的时候,手工的为泛型定义具体的数据类型
		Message<Integer> message = new Message<>();
		message.setContent(50);
		info(message);
	}
	public static void info(Message<String> temp) {
		System.out.println(temp.getContent());
	}
}

/**
	程序编译结果:Unresolved compilation problem: 
	The method info(Message<String>) in the type Message<T> is not applicable for the arguments (Message<Integer>)
**/

本处的错误在于:info()方法上所需要接收到的temp参数类型为“Message”,所以现在如果传递的类型为“Message”则无法实现正常的引用传递,这时我们可能会想到用重载的方法,但是重载编译时就会出错,此时出现错误的原因非常好理解:因为方法重载中的参数的类型不包括泛型,因为这个时候两个info()方法对应的都是Message类型,虽然泛型类型不同,但是其基本的类型却是相同的。

于是这个时候有人说了,那么干脆将info()方法中的泛型取消了是不是就可以实现参数接收了呢?

public class Message<T> {
	private T content;//可以实现各种类型数据的存储
	public T getContent() {
		return this.content;
	}
	public void setContent(T content) {
		this.content = content;
	}
	public void send() {
		System.out.println("【消息发送】" + this.content);
	}
	
	public static void main(String[] args) throws Exception {
		//在使用Message类的时候,手工的为泛型定义具体的数据类型
		Message<String> message = new Message<>();
		message.setContent("www.baidu.com");
		info(message);
	}
	public static void info(Message temp) {
		temp.setContent(99.88);
		System.out.println(temp.getContent());
	}
}

/**
	程序执行结果:99.88
**/

如果在info()方法上的Message参数不追加具体的泛型,那么此时就描述的是Object类型,那么就意味着之前所做的泛型操作设计就全部毁灭了。所以对于此时泛型引用传递的操作来讲最佳的实现方案就是,可以接收所有的泛型对象,但是不允许进行内容的修改,而为了实现这样的要求,可以使用“?”作为通配符进行控制。

范例:泛型的通配符定义

public class Message<T> {
	private T content;//可以实现各种类型数据的存储
	public T getContent() {
		return this.content;
	}
	public void setContent(T content) {
		this.content = content;
	}
	public void send() {
		System.out.println("【消息发送】" + this.content);
	}
	
	public static void main(String[] args) throws Exception {
		//在使用Message类的时候,手工的为泛型定义具体的数据类型
		Message<String> message = new Message<>();
		message.setContent("www.baidu.com");
		info(message);
	}
	public static void info(Message<?> temp) {
		//temp.setContent(99.88);   //编译错误"不兼容的类型:double无法转换为CAP#1"
		System.out.println(temp.getContent());
	}
    
/**
	程序执行结果:www.baidu.com
**/

此时在info()方法中使用了“?”作为了泛型的通配符,这样就可以接收所有的泛型类型的对象实例,但是不允许进行内容的修改,而只是完成内容的获取,而在“?”通配符的操作基础之上,Java又给出了两个子通配符的处理格式:

  • 【类和方法】设置泛型的上限(?extends 类):只能够使用当前类或者当前类的子类作为泛型类型;
    • “? extends Number”:可以使用Number或者是Number子类实现泛型类型的定义。
  • 【方法】设置泛型的下限(? super 类):只能够设置指定类或者其父类;
    • “? super String”:可以使用String或者其父类作为泛型类型。

范例:设置泛型上限

public class Message<T extends Number> {//设置上限,不能大于Number类,可以使用Integer、Double等
	private T content;//可以实现各种类型数据的存储
	public T getContent() {
		return this.content;
	}
	public void setContent(T content) {
		this.content = content;
	}
	public void send() {
		System.out.println("【消息发送】" + this.content);
	}
	
	public static void main(String[] args) throws Exception {
		Message<Integer> message = new Message<>();
		message.setContent(190);
		info(message);
	}
	public static void info(Message<? extends Number> temp) {
		System.out.println(temp.getContent());
	}
}

/**
	程序执行结果:190
**/

如果此时在使用Message类对象进行处理的时候,所定义的泛型类型不是Number或者其子类,则程序将会出现语法错误。

范例:设置泛型下限

public class Message<T> {//设置上限,不能大于Number类,可以使用Integer、Double等
	private T content;//可以实现各种类型数据的存储
	public T getContent() {
		return this.content;
	}
	public void setContent(T content) {
		this.content = content;
	}
	public void send() {
		System.out.println("【消息发送】" + this.content);
	}
	
	public static void main(String[] args) throws Exception {
		Message<String> message = new Message<>();
		message.setContent("www.baidu.com");
		info(message);
	}
	public static void info(Message<? super String> temp) {
		System.out.println(temp.getContent());
	}
}

/**
	程序执行结果:www.baidu.com
**/

通过这种上限和下限的处理可以使得在进行泛型参数接收的操作中更加方便地实现数据类型的控制。

四、泛型接口

在之前所接触到的泛型定义全部是在类上完成的,而在Java里面接口中也可以实现泛型的定义,对于这种接口的泛型有两种不同的实现模式,下面通过代码具体说明。

范例:定义泛型接口

interface IMessage<T>{//定义接口的同时配置有泛型声明
	public void send(T t);//方法中定义有泛型
}

对与此时的泛型接口在以后继续学习Java后续部分的过程中实际上也会大量的见到,而对于泛型接口的两种实现方式也都有可能在实际的开发中使用。

方式一:在定义实现子类的时候继续采用泛型

interface IMessage<T>{//定义接口的同时配置有泛型声明
	public void send(T t);//方法中定义有泛型
}
//如果现在MessageImpl没有声明泛型,则IMessage接口实现时候的泛型是无法使用的
class MessageImpl<T> implements IMessage<T>{
	@Override
	public void send(T t) {
		System.out.println("【消息发送】"+t);
	}
}
public class Test {
	public static void main(String[] args) throws Exception {
		IMessage<String> message = new MessageImpl<>();//使用的时候将String作为泛型
		message.send("www.baidu.com");
	}
}

/**
	程序执行结果:【消息发送】www.baidu.com
**/

由于此时MessageImpl子类定义的时候继续使用了一个泛型定义,所以在实现IMessage接口的时候才可以继续通过泛型的方式进行定义,而在最终使用的时候接口和子类的泛型也需要得到统一。

方式二:在子类实现接口的时候不定义泛型,但是会明确的为实现的父接口设置一个具体的泛型类型。

interface IMessage<T>{//定义接口的同时配置有泛型声明
	public void send(T t);//方法中定义有泛型
}
class MessageImpl implements IMessage<String>{

	@Override
	public void send(String t) {
		System.out.println("【消息发送】"+t);
	}

}
public class Test {
	public static void main(String[] args) throws Exception {
		IMessage<String> message = new MessageImpl();//使用的时候将String作为泛型
		message.send("www.baidu.com");
	}
}

/**
	程序执行结果:【消息发送】www.baidu.com
**/

此时由于MessageImpl子类没有设置具体的泛型,所以在实现IMessage接口的时候就必须明确的为其定义一个具体的泛型类型,这样才可以正确的实现泛型接口。

五、泛型方法

在之前不管是定义的泛型类还是泛型接口,都可以发现在这些类或接口的方法里面也可以利用泛型作为参数类型:

interface IMessage{//定义接口的同时配置有泛型声明
public void send(T t);//方法中定义有泛型
}

但是Java中泛型技术的设计非常的高级,明确的告诉了所有的开发者,即便你现在没有处于一个泛型定义的结构中,那么只要有需要也可以在方法上定义泛型。

范例:定义泛型方法

public class Test {
	public static void main(String[] args) throws Exception {
		Integer num[] = fun(1,2,3);
		for(int temp : num) {
			System.out.print(temp + "、");
		}
	}
	//如果此时没有追加"<T>"的声明,则当前的T描述的就是一个类或者接口等结构
	public static <T> T[] fun(T ... args) {//通过外部的参数来决定最终T的类型
		return args;//args为一个动态参数,可以实现数组内容的返回
	}
}

/**
	程序执行结果:1、2、3、
**/

通过以上的演示,我们大概清楚了泛型方法的使用,但是这样的泛型方法有什么样的实际意义呢?在日后的项目开发过程之中,对于泛型方法的设计与使用实际上有大量的应用,但是这些应用想要彻底使用开来,那么就必须结合各种繁琐的Java底层处理机制才能够编写。

等慢慢学习到后面的反射机制的时候,对于泛型方法就非常好用了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jackson Xi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值