java泛型详解

java泛型详解

泛型: 泛型的本质是类型参数化,解决不确定具体对象类型的问题。在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型。

	在java1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。举个例子:
//洗衣机类
class Washing{

    public static Object washing(Object obj){
        System.out.println("washing "+obj);
        return obj;
    }

}

public class GenericityTest {

    public static void main(String[] args) {

        Clothing clothing = new Clothing();
        clothing = (Clothing) Washing.washing(clothing);


    }
}

洗衣机最重要的功能是清洗衣物,当然也可以洗帽子,洗鞋子,洗袜子等。为了避免为每一种可以清洗的衣物都定义一个洗的方法,将方法的参数和返回值都是Object,极大的增强了这个方法的灵活性。但正是因为这种极高的灵活度,反而让这个方法漏洞百出。

①不是所有的东西(object)都可以放入洗衣机的;
②每次方法返回的对象需要进行强制类型转换,才能知道放入洗衣机的是什么,这和认知中衣服放入洗衣机取出来的必定是衣服的逻辑有冲突;
③在实际开发中,这样强制类型转换不仅使代码变得臃肿,还容易引起ClassCastExeception。

从java1.5开始增加的泛型有效的解决了这些问题。泛型是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类/接口/方法。泛型很大程度上减少了强制类型转换的使用。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。在增加灵活性的同时,也限制某些不合理的参数以及返回类型地使用。泛型可以保证在编译时没有发出警告,则运行时不会产生ClassCastExeception,使得代码更加简洁,程序更健壮。

在泛型的初始实现中,编译器将强制类型转换插入生成的字节码中。所有工作都在编译器中完成,编译器生成类似于没有泛型(和强制类型转换)时所写的代码,只是更能确保类型安全而已。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。

public class Washing<T>{
	public T washing(T t){
        System.out.println("washing "+t);
        return t;
    }
}
public class GenericityTest {

    public static void main(String[] args) {

        Clothing clothing = new Clothing();
        Washing<Clothing> washing = new Washing<>();
        clothing = Washing.washing(clothing);


    }
}

泛型的定义

首先,定义泛型必须要使用尖括号<>,编译器通过识别尖括号和尖括号里的字母来解析泛型,如:

//泛型类
public class Washing<T>{
	public T washing(T t){
        System.out.println("washing "+t);
        return t;
    }
}
public class GenericityTest {

    public static void main(String[] args) {

        Clothing clothing = new Clothing();
        Washing<Clothing> washing = new Washing<>();
        clothing = Washing.washing(clothing);


    }
}

泛型的定义方式就好像我们在定义方法时使用的形式参数,在使用的时候才传入实际参数,在方法的其它地方都可以使用。泛型也是如此,定义后就可以在整个接口,类内,方法内都可以使用。但是泛型的定义位置非常讲究,必须是在类名或接口名之后,方法返回值前。因此在类,接口和方法这些位置有<>才能被冠以泛型二字。而形如T add(T t),仅仅只是T指代的类的类型,并不是我们说的泛型。而T这种通配符有很多,下面我来详细说明这些符号。

符号

符号英文含义
1TType of object
2EElement元素
3KKey
4VValue
5NNumber数字
6?不确定的java类型
	本质上这些个都是通配符,可以用 A-Z的任何一个字母都可以,并不会影响程序的正常运行。
	但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。
public class GenericDefinitionDemo<T>{
	public static <String,T> String get(String string,T t){
		return string;
	}
}
返回值是String类型的吗?

很遗憾的是,这个方法的返回值类型并不是String类型。准确的来说,不是java.lang.String类型,而是泛型标识<String>。所以随意使用字符表示,容易使人造成不必要的误解。

泛型在定义的时候,尖括号<>内出现的符号,就不是它原有的意思,仅仅充当一个代号。并且泛型方法和泛型类、泛型接口中定义的泛型虽然通样都是使用相同的符号,但是他们是两个指代,可以完全不同,互不影响。

复杂的通配符

上面说的泛型符号都比较简单,只要是学习了java一段时间后或多或少都能明白其中的含义 。实际上还存在一种名为通配符的泛型来表示一种未知类型,并且对这种未知类型存在约束关系。由于通配符不好理解,所以初学者最好谨慎使用。

? 无界通配符

?这个通配符可以指代不确定或者不关心实际要操作的类型,可以使用无界通配符,表示可以持有任何类型。主要针对泛型类的限制, 无法像 T类型参数一样单独存在。

public class Biology{
	private static List<?> list;
	...
}
List<?>是某种具有特定类型的非原生List,只是我们不知道它的具体类型是什么,所以我们就不允许往里存放数据。这和List<Object>和List还是有些区别的:
	1.List跟List<Object>是等价的,因此List表示的是类型参数是Object的List。
	2.List<?>,表示我这里的类型参数其实是特定的,但是,只是一时没有确定下来。
	3.List能存放任意类型的数据,List<?>不能存放数据。

永远不要在方法返回中使用无界通配符?,在方法中不会报错,但是方法的接收者将无法正常使用返回值.因为它返回了一个不确定的类型

<? extends T> 上边界通配符(upper bounded wildcard)

表示 ? 是继承自 T的任意子类型,只能提供数据,不能接收数据。

public class Test{
	private static List<? extends Object> list1;
	
	public static void test1(List list) {
		list1.addAll(list);// unchecked
	}

}
错误代码!Unchecked assignment: 'java.util.List' to 'java.util.List<? extends java.lang.Object>'
<? super T>下边界通配符(lower bounded wildcard)

表示 ? 是 T的任意父类型.也表示一种约束关系,只能接收数据,不能提供你数据

public class Test{

	private static List<? super Washing> list2;
	public static void test3(List<? super Washing> list) {
		list2 = list;
	}
}
1.? 主要表示使用泛型,T表示声明泛型
2.? 主要针对 泛型类的限制, 无法像T类型参数一样单独存在
3.? 表示一个未知类型, T 是表示一个确定的类型. 因此,无法使用 ? 像 T 声明变量和使用变量
4.如果即需要提供数据, 又需要接收数据, 就不要使用通配符
5.谨慎使用无界通配符?
实现/继承泛型接口/泛型类

java在实现一个接口或者继承一个类,比如:

public class Father{
	...
}

public class Son extends Father{
	...
}

其中这个父类Father就是正在被使用的。实现泛型接口或继承泛型类必须为泛型传入类型实参,而拥有了类型实参后的,泛型就是一个具体类型,而不再是泛型。所以原则上java是不允许泛型层层继承!

为了解决这个问题,java准备了一种特殊语法,使得泛型能继承下去。

public class Father<T>{
	...
}

public class Son<T> extends Father<T>{
	...
}

父类Father持有的类型T传递给子类Son继续使用,因此子类泛型必须要和父类保持一致,否则会产生编译异常!!!!!!实现泛型接口同理。在jdk1.8的源码里,很多类,接口都是通过泛型继承来实现的,比较典型的就是集合这部分。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
	...
}

泛型的使用

使用一个泛型类,泛型接口,泛型方法很简单,只要在使用时传入类型参数甚至不传都可以使用,典型的使用比如:

List list = new ArrayList();
List<String> list1 = new ArrayList<String>();
List<String> list2 = new ArrayList<>();
List<User> list3 = new ArrayList<>();

list1和list2都是只能存放String对象的集合,自然从里面获取的元素都是String类型。list3和list2的不同点差不多就只有泛型的不同,但是能存放的对象却是完全不一样。

  泛型在使用中还有一些规则和限制:
    1、泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
    2、同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
    3、泛型的类型参数可以有多个。
    4.泛型的参数类型还可以是通配符类型。

总结

Java的泛型是伪泛型。在编译期间,所有的泛型信息都会被擦除掉。对于编译后的字节码文件,其实并没有这些花里胡哨的尖括号。因为在编译时java编译器通过泛型擦除将源码编译成字节码把泛型版本转换成非泛型版本。由于类型参数在运行时并不存在,所以使用泛型并不会添加任何的时间或者空间上的负担。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值