1.泛型的概述
2.泛型类
2.泛型接口
4.泛型方法
5.泛型通配符
6.泛型数组
在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用
来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
我们举个例子:
package untl2;
import java.util.ArrayList;
import java.util.List;
public class MyType {
public static void main(String[] args) {
List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);
for(int i = 0; i< arrayList.size();i++) {
String item = (String) arrayList.get(i);
System.out.println(item);
}
}
}
运行结果:
程序崩溃并且抛出ClassCastException异常
那么为啥会抛出异常呢:
这个例子就是典型的编译时正常,运行出错
ArrayList可以存放任意类型,例子中添加了一个String类型,添加了一个Integer类型,再使用时都以String的方式使用,因此程序崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。
那么我们用泛型如何解决这个问题呢,看下面代码:
package untl2;
import java.util.ArrayList;
import java.util.List;
public class MyType {
public static void main(String[] args) {
List<String> arrayList = new ArrayList<String>();
arrayList.add("aaaa");
arrayList.add(100);
for(int i = 0; i< arrayList.size();i++) {
String item = arrayList.get(i);
System.out.println(item);
}
}
}
此代码编译不通过,由于泛型已经规定arrayList是String类型,但是100是int类型,所以编译直接报错,这个时候就体现出泛型的优势了,而且仅仅就在List后边加了
就能为所欲为了
那么对于这两段代码时候给出总结了泛型的两大优点:
1.将运行时期的ClassCastException,转移到了编译时期变成了编译失败
2.避免了类型强转的麻烦。
再看一个例子:
package untl2;
import java.util.ArrayList;
public class MyType {
public static void main(String[] args) {
ArrayList<String> arrayList1=new ArrayList();
ArrayList<Integer> arrayList2=new ArrayList();
Class cla1=arrayList1.getClass();
Class cla2=arrayList2.getClass();
if(cla1==cla2)
{
System.out.println("运行后会进行去泛型的操作所以两者相等");
}
else
System.out.println("运行后不会进行去泛型的操作");
}
}
运行结果:
运行时会进行去泛型的操作所以两者相等
通过上面的例子可以证明,在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。泛型的本质就是利用编译器实现的Java语法糖,编译器将java文件转换为class文件前,会进行泛型擦除,所以在反编译的class文件中,是看不到泛型声明的
是时候搬出我泛型的祖传定义和概述了:
泛型,即“
参数化类型
”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
private 泛型标识 /*(成员变量类型)*/ value;
.....
}
}
我们可以看一下我们API中的ArrayList集合源码:
class ArrayList<E>{
public boolean add(E e){ }
public E get(int index){ }
....
}
当然,我们也可以自定义例如:
但是要注意以下两点:
1.泛型的类型参数只能是类类型(包括自定义类),不能是简单类型
2.传入的实参类型需与泛型的类型参数类型相同
package untl2;
public class MyType<T> {
private T key;
public MyType(T t)
{
this.key=t;
}
public T getkey()
{
return this.key;
}
public static void main(String[] args) {
String str="123";
MyType<String> myType=new MyType(str);
//这里可以不写会自动进行检测
}
}
定义的泛型是不需要传入具体的实参的,什么意思呢,还是上边这个例子, MyType
我们指定了泛型为String,当然我们也能不指定泛型类型,随便在构造器中传入任何类型的数据,编译器会自动检测
public interface Generator<T> {
public T next();
}
例如:
package untl2;
public interface MyType<T> {
public T fly();
}
class person implements MyType{
}
那么我们知道实现接口肯定要重写里边的抽象方法,但是接口的泛型你不明确,所以重写方法的返回值类型也不明确,所以我们可以使用Object
作为方法的反回值来解决:
package untl2;
public interface MyType<T> {
public T fly();
}
class person implements MyType{
public Object fly()
{
System.out.println("让我飞");
return null;
}
public static void main(String[] args) {
person p=new person();
p.fly();
}
}
运行结果:
让我飞
分为两种情况,
(1)第一种是实现接口不带泛型
package untl2;
public interface MyType<T> {
public T fly();
}
class person<E> implements MyType{
public E fly()
{
System.out.println("让我飞");
return null;
}
public static void main(String[] args) {
person<String> p=new person();
p.fly();
}
}
运行结果:
让我飞
(2)第二种是两者都带泛型:
package untl2;
public interface MyType<T> {
public T fly();
}
class person<T> implements MyType<T>{
public T fly()
{
System.out.println("让我飞");
return null;
}
public static void main(String[] args) {
person<String> p=new person();
p.fly();
}
}
运行结果:
我要飞
我们必须注意 实现接口所带泛型必须要抽象方法的泛型参数一样
要不然:
package untl2;
public interface MyType<T> {
public T fly();
}
class person<E> implements MyType<T>{
public E fly()
{
System.out.println("让我飞");
return null;
}
public static void main(String[] args) {
person<String> p=new person();
p.fly();
}
}
编译报错
package untl2;
public interface MyType<T> {
public T fly();
}
class person implements MyType<String>{
public String fly()
{
System.out.println("让我飞");
return null;
}
public static void main(String[] args) {
person p=new person();
p.fly();
}
}
泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型
修饰符 <泛型> 返回值类型(可以使用泛型) 方法名(参数列表(可以使用泛型))
{
方法体;
}
package untl2;
public class MyThroad<T>{
private T key;
public MyThroad(T t)
{
this.key=t;
}
public T getKey()
{
return key;
}
public <E> E func(T t)
{
System.out.println("我很开心");
return null;
}
// public E vunc(MyThroad k )
// {
//
// }
public <E,K> E vunc(MyThroad<K> k )
{
return null;
}
public static void main(String[] args) {
}
}
分析之前请先好好看看这一句话:
泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型
首先getkey()
不是一个泛型方法
1.从泛型确定类型的时间:这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。在你实例化对象的时候就会指定T的类型,并不是调用的时候确定的
2.格式上:与泛型方法的定义格式不一致
func
方法是一个泛型方法
被注释的vunc()方法
虽然我们声明了,也表明了这是一个可以处理泛型的类型的泛型方法。
但是只声明了泛型类型T,并未声明泛型类型K,因此编译器并不知道该如何处理E这个类型。
注:在泛型方法里边定义的泛型要不是在调用方法前就确定了类型,要不就要在尖括号里边声明
这里用内部类的例子:
package untl;
public class GenericFruit {
class Fruit{
@Override
public String toString() {
return "fruit";
}
}
class Apple extends Fruit{
@Override
public String toString() {
return "apple";
}
}
class Person{
@Override
public String toString() {
return "Person";
}
}
class GenerateTest<T>{
public void show_1(T t){
System.out.println(t.toString());
}
//在泛型类中声明了一个泛型方法,使用泛型E,
///这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
//由于泛型方法在声明的时候会声明泛型,
//因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
public <E> void show_3(E t){
System.out.println(t.toString());
}
//在泛型类中声明了一个泛型方法,使用泛型T,
//注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
public <T> void show_2(T t){
System.out.println(t.toString());
}
}
public static void main(String[] args) {
GenericFruit frult=new GenericFruit();
Apple apple =frult.new Apple();
Person person = frult.new Person();
GenerateTest<Fruit> generateTest = frult.new GenerateTest<Fruit>();
generateTest.show_1(apple);
//generateTest.show_1(person);
//使用这两个方法都可以成功
generateTest.show_2(apple);
generateTest.show_2(person);
generateTest.show_3(apple);
generateTest.show_3(person);
}
}
运行结果:
apple
apple
Person
apple
Person
package untl;
public class GenericFruit {
public <T> void printf(T...d)
{
for (T t:d)
{
System.out.println(t);
}
}
public static void main(String[] args) {
GenericFruit p=new GenericFruit();
p.printf("张三",123,88.88,true);
}
}
运行结果:
张三
123
88.88
true
如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。
?:代表任意的数据类型
使用方式:
1.不能创建对象使用
2.只能作为方法使用
看下面一段代码:
明显的list2不是Interger类型,所以编译会报错,但是如果我就想使用print方法遍历任意泛型的ArrayList集合,这个时候就可以使用我们的通配符
package untl;
import java.util.ArrayList;
import java.util.Iterator;
public class GenericFruit {
public static void print(ArrayList<?> list)
{
Iterator<?> it=list.iterator();
while (it.hasNext())
{
Object obj=it.next();
System.out.println(obj);
}
}
public static void main(String[] args) {
ArrayList<Integer> list1=new ArrayList<>();
list1.add(1);
list1.add(2);
ArrayList<String> list2=new ArrayList<>();
list2.add("aaa");
list2.add("bbb");
print(list1);
print(list2);
}
}
运行结果:
1
2
aaa
bbb
一定注意通配符必须在定义的时候不能用泛型通配符如:
ArrayList<?> list2=new ArrayList<?>()
;就会报错,但是作为参数传递的时候可以用,就比如以上的例子
在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。
泛型的上下限:
1.上限格式:
类型名称 extends 类> 对象名称
,意义:只能接受该类型及其子类
2.下限格式:类型名称 super 类> 对象名称
,意义:只能接受此类型及其父类、
看下面这个例子:
首先我们知道Number
类是Integer的父类
package untl;
import java.util.ArrayList;
import java.util.Collection;
public class GenericFruit{
public static void main(String[] args) {
Collection<Integer> list1 = new ArrayList<Integer>();
Collection<String> list2 = new ArrayList<String>();
Collection<Number> list3 = new ArrayList<Number>();
Collection<Object> list4 = new ArrayList<Object>();
getElement1(list1);
getElement1(list2);//报错
getElement1(list3);
getElement1(list4);//报错
getElement2(list1);//报错
getElement2(list2);//报错
getElement2(list3);
getElement2(list4);
}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}
}
在java中是”不能创建一个确切的泛型类型的数组”的。
也就是说下面这段代码是报错的:
List<String>[] ls = new ArrayList<String>[10];
这样也是报错的:
List ls = new ArrayList<String>[10];
但是而使用通配符创建泛型数组是可以的,如下面这个例子:
List<?>[] ls = new ArrayList<?>[10];
这样也是可以的:
List<String>[] ls = new ArrayList[10];