JAVA泛型是什么!

目录

 

泛型

为什么要用泛型

泛型的好处

什么时候用泛型

怎么用泛型

泛型类

泛型方法

泛型接口

 类型变量的限定

类型擦除


泛型

泛型,即广泛的类型,泛型很像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、不能实例化类型变量。

 

发布了38 篇原创文章 · 获赞 6 · 访问量 1922

猜你喜欢

转载自blog.csdn.net/weixin_43827227/article/details/98335160