目录
泛型
泛型,即广泛的类型,泛型很像C++里的模板,记住广泛的类型和模板和参数化这三个词,我们一起往下看看。
为什么要用泛型
有一天,我给int类型的数据写了一个非常完美的方法,刚得意洋洋地打算歇会。
char不高兴了,说它也要一个,我大手一挥,Ctrl+c后Ctrl+v,int全改char,土豪地说,拿去!这个时候String跳了出来,哭诉着说它也要一个,我一脸黑线,刚打算再操作一次,各种各样的类型突然全跑了出来。我仰天大喊:“太麻烦了!啊!”。我灵机一动,对啊,我可以用一个通用的数据类型Object来实现。这时候处理器大哥出来说:“小老弟,你这样乱用Object变量,会出现装箱、拆箱的强制转换,还没有错误检查,我负担很重啊,你钱给那么少,我很难办事啊。”那可怎么办呢?
大哥接着说:“多大点事,你想编写的代码可以被很多不同类型的对象重用,用泛型呗。又安全又方便读,来来来,教你个好东西:类型参数(type parameters)。”
“啥玩意?它咋就比我的Object更安全更好读啦。”
ArrayList files = new ArrayList():
ArrayList<String> files = new ArrayList<String>():
上面和下面一比,多了两个<String>,但你是不是就知道下面的数组列表里包含的是String对象啦。
(合着泛型就是一对尖括号呀<>,这里注意,可以有多个的哟~)
编译器还知道 ArrayList<String> 中 add方法有一个类型为 String 的参数。若插入错误类型的对象,编译器还能指出来,是不是要比Object类型的参数要安全一些呀,比在运行时出现类的强制类型转换异常方便一些吧。
咱们从另一个角度切入:
大家都知道容器是一个很方便的东东,例如ArrayList。但问题是:容器保存的是Object类型,也就意味着:咱们可以往ArrayList里放很多不同的类型对象,比如Apple、Orange。为什么呢?因为在存入的过程中,容器会把这些对象全部转型为Object。可是转去容易,转回难,当我们需要从容器里取出Apple时,我们取出的是一个Object对象,转型呗,问题不大。但是转成什么呢?所有的类型都乱放进去,拿出来的时候就很难辨别,这个Object对象,之前是啥类型的来着?参考生活中的例子,要是罐子里什么口味的糖都放进去,想取牛奶味的糖出来可就不敢说百发百中咯。于是乎,咱们多准备几个罐子,规定了一号罐子装牛奶味的糖,二号装草莓味的糖,这样取牛奶味糖果的时候就可以直奔一号罐去咯。于是乎,咱们的”泛型“登场!~
泛型的好处
大哥这样说,好像的确比我的想法好一些,我决定好好学学这个叫“泛型”的玩意。
那么我们先小结一下泛型的好处:避免了强制转换的麻烦,无需装箱和拆箱,将运行时期的类的强制类型转换异常转到了编译时期,更加安全方便。
什么时候用泛型
好是好,那啥时候用呀,毕竟我不知道这东西的时候,代码敲的也挺好的不是么。
泛型什么时候用?当操作的引用数据类型不确定的时候。就使用<>将要操作的引用数据类型传入即可。
<>就是一个用于接收具体引用数据类型的参数范围。
怎么用泛型
咋用呢?那可就讲究了
泛型类
泛型类:具有一个或多个类型变量的类
public class Pair<T> //引入类型变量T 这里就把类型给参数化了
{
//类定义中的类型变量指定方法的返回类型以及域和局部变量的类型
private T first;
private T second;
public Pair()
{
first = null;
second = null;
}
public Pair(T first, T second)
{
this,first = first;
this.second = second;
}
public T getFirstO { return first; }
public T getSecondO { return second; }
public void setFirst(T newValue) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
}
用具体的类型替换类型变量就可以实例化泛型类型,如Pair<String>。之后T可全视为String,无法传入int,真是个专一的好泛型!
泛型方法
class ArrayAlg //普通类
{
public static <T> T getMiddle(T a) //泛型方法
//注意,类型变量放在修饰符(这里是 public static) 的 后面,返回类型的前面。
//第二个T是使用了泛型
{
return a[a.length / 2];
}
}
当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型
String middle = ArrayAlg.<String>getMiddle("]ohnM, "Q.n, "Public");
泛型接口
//泛型接口,将泛型定义在接口上。
interface Inter<T>{
public void show(T t);
}
//泛型接口,将泛型定义在接口上。
interface Inter<T>{
public void show(T t);
}
class InterImpl2<Q> implements Inter<Q>
{
public void show(Q q){
System.out.println("show :"+q);
}
}
类型变量的限定
我们知道,不同的类的对象有着不同的方法,既然我们用到了泛型,在用之前我们就不知道传进来的对象是谁,难道我们用了泛型之后,只能去操作所有对象都拥有的方法吗?那会是多大的限制啊!显然不是,如果真的这样,那我们可亏大了。比如我们来看看下面的代码。
//找出数组中最小的元素
class ArrayAIg
{
public static <T> T min(T[] a)
{
if (a==null || a.length == 0)
return null;
T smallest = a[0];
for (int i = 1 ; i < a.length; i++)
if (smallest.compareTo(a[i]) > 0)
smallest = a[i];
return smallest;
}
}
我们发现,smallest的类型是T,后来又使用了:smallest.compareTo(a[i]) > 0) 。我怎么知道T类型有没有compare方法呢?这不是扯么。
所以我们得对T进行限制,为了实现这个功能,咱就和T说:“我不管,听我的,你必须有compareTo,自己想办法解决这个问题!”
T就很无奈,没办法了,我和Comparable 套套近乎吧。
public static <T extends Comparable> T min (T[] a)
这一继承,不就对T进行了限定了么,现在泛型的 min方法只能被实现了 Comparable 接口的类(如 String、 LocalDate 等)的数 组调用了。而Rectangle类没有实现 Comparable 接口, 所以调用 min将会产生一个编译错误。
(奇怪的是,Comparable接口为什么用 extends 而不是 implements呢 ? <T extends BoundingType> 表示 T 应该是绑定类型的子类型(subtype)。T 和绑定类型可以是类, 也可以是接口。所以我们就用extends吧,因为在这里,我们的做法更加接近子类的概念。)
一个类型变量或通配符可以有多个限定,限定类型用&符号隔开
T extends Comparable & Serializable
通配符:?
泛型的通配符:? 未知类型。
为什么有了参数类型T,还要来个“?”未知类型呢?难道是T是已知类型?
不是这样滴,Zi 是 Fu 的子类,但这不代表 Pair<Zi>和Pair<Fu>有继承关系。
但我们有的确有这方面的需求,希望泛型能够处理这样有继承关系的东西,于是引入了通配符的概念。
通配符有三种:
1、? :无限定通配符
2、? extends E :有上限的通配符
3、? super E:有下限的通配符
? extends E: 接收E类型或者E的子类型对象。上限
一般存储对象的时候用。比如 添加元素 addAll.
一般在存储元素的时候都是用上限,因为这样取出都是按照上限类型来运算的。不会出现类型安全隐患。
? super E: 接收E类型或者E的父类型对象。 下限。
一般取出对象的时候用。比如比较器。
//迭代并打印集合元素
private static void IteratorCollection(Collection<?> a1)//所有集合的所有对象都可传
{
Iterator<?> it= a1.iterator();
while(it.hasNext())
System.out.println(it.next().toString());
}
private static void IteratorCollection2(Collection<? extends Person> a1)//所有集合的person对象的子类对象都可传
{
Iterator< ? extends Person> it=a1.iterator();
while(it.hasNext())
{
Person person =it.next();
System.out.println(person.getName()+":"+person.getAge() );
}
}
类型擦除
泛型这个概念在jdk1.5以后才提出来,那是不是说,在这之前不能用泛型这个东西呢?当然不是,毕竟我们有兼容性这个好东西。但是我们有更好的想法:泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语就叫做类型擦除。结果就会变成一个普通的类,就好像没有泛型这回事一样。
无论何时定义一个泛型类型, 都自动提供了一个相应的原始类型 ( raw type)。原始类型的名字就是删去类型参数后的泛型类型名。擦除(erased) 类型变M, 并替换为限定类型(无限定的变量用 Object)。
public class Interval<T extends Comparable & Serializable>implements Serializable
{
private T lower;
private T upper;
public Interval(T first, T second) { . . . }
}
原始类型 Interval 如下所示:
public class Interval implements Serializable
{
private Comparable lower;
private Coiparable upper;
public Interval(Comparable first, Comparable second) { . . . }
}
----------------------------------------------------------------------
public class Interval<T>implements Serializable
{
private T lower;
private T upper;
public Interval(T first, T second) { . . . }
}
原始类型 Interval 如下所示:
public class Interval implements Serializable
{
private Object lower;
private Object upper;
public Interval(Object first, Object second) { . . . }
}
但类型擦除也带来了几个问题:会抹掉很多继承相关的特性
泛型的约束与局限性
泛型那么好,但也肯定不是完美滴,所以他还是有所限制的,多数的限制是由类型擦除引起的。
1、不能用8种基本类型实例化类型参数,而应该用它们对应的包装类。
2、运行时类型查询只适用于原始类型。
3、不能创建参数化类型的数组。
4、不能实例化类型变量。