黑马程序员——Java高新技术——JDK1.5版本的新特性泛型

时间:2022-11-28 19:41:22

----------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ----------

                       JDK1.5的新特性——泛型

一、泛型

   1、概念

   泛型:JDK1.5版本以后出现新特性,用于解决安全问题,是一个类型安全机制。

数组容器不会出现这种问题,因为数组定义时,明确了数据类型。

而集合出现这种问题,因为集合,没有定义明确的数据类型。如果我们能像数组那样定义数据类型,问题是不就解决了。这就是泛型的由来。

例如,Map类允许向一个Map集合添加任意类的对象,即使最常见的情况是在给定映射(map)中保存某个特定类型(比如String)的对象。 

因为Map.get()被定义为返回Object,所以一般必须将Map.get()的结果强制类型转换为期望的类型,如下面的代码所示: 

Map m = new HashMap(); 

m.put("key", "blarg"); 

String s = (String) m.get("key"); 

   示例代码

import java.util.*;class GenericDemo{  public static void main(String[]args)  {       ArrayList al=new ArrayList();       al.add("abc01");       al.add("abc0991");       al.add("abc014");       al.add(4);//al.add(new Integer(4))  1.4版本后,可以存入基本数据类型,因为会有自动装箱拆箱工作。Iterator it=al.iterator();while(it.hasNext())  {     String s=(String)it.next(); System.out.println(s+"::"+s.length());  }  }}

编译失败:发生类型转换异常。Interger 无法转换成string类型。怎么造成问题产生呢?是因为存入了不同类型的数据

怎么能在集合定义时,定义数据类型呢?用<数据类型>

例如:

ArrayLisst<String> al=new ArrayList<String> ();

2、泛型的好处

   1)将运行时期出现问题classCastException,转移到了编译时期,方便于程序员解决问题,让运行时期问题减少,安全。

   2)避免了强制转换麻烦

黑马程序员——Java高新技术——JDK1.5版本的新特性泛型 

3、泛型的使用

   泛型格式:通过<>来定义要操作的引用数据类型。在使用java提供的对象时,什么时候写泛型呢?

通常,在集合框架中很常见。只要见到<>,就要定义泛型。

   其实<>就是用来接收类型的。当使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可。   

   定义比较器的时候,comparator<T>,也有<>,也可以接受类型

class LenComparator implements Comparator<String>

{

  public int compare(String o1,String o2)

  {

   ....... 

   }

}

至此,描写一个student类,至少需要做的:

   A  comparable接口实现自然排序

   B  复写hashCode方法,equals方法,适用hashSet集合

   C   复写compareTo方法,适用TreeSet集合的默认排序

   D  复写toString方法

定义一个比较器类,用于扩展其他排序方式

4、泛型类

泛型类:当一个类中,有不明确的数据类型的时候,就用泛型来表示,等待使用它的对象来传入确定的数据类型,这样的类叫做泛型类。

 我们发现,TreeSet ArrayList类都是泛型类。接下来,我们能不能自定义一个类为泛型类,接收类型呢?

class Worker

{

......

}

class Tool  //工具类{  private Worker w;public void setWorker(Worker w)  {  this.w=w; } public Worker getWorker()  {   return w;  }}class  GenericDemo{   public static void main(String[]args)   { Tool  t=new Tool(); t.setWorker(new Worker()); t.getWorker();    } }

但是我们发现,我们每定义一个类,就要定义一个工具类,很麻烦。

我们是不是可以抽取对象的共性内容,定义为泛型类。

工具类定义:

class Tool   //工具类{  private Object obj;public void setObject(Object w)  {  this.obj=obj; } public Object getObject()  {   return obj;  }}class  GenericDemo{   public static void main(String[]args)   { Tool  t=new Tool(); t.setObject(new Student());//不小心,把student类型传进去了 t.getObject();    } }/*编译,没错,运行,类型转换异常这是早期的,泛型前做法泛型后做法工具类  用来操作数据,操作什么数据,不知道,由使用的人来指定*/class Utils<QQ> {   private QQ q;     public void setObject(QQ q)  {  this.q=q; } public QQ getObject()  {   return q;  }
}

我们想拿这个工具类操作下worker,没问题,传下类型就ok

黑马程序员——Java高新技术——JDK1.5版本的新特性泛型 

传错了,编译时就失败了。把错误转移到了编译时期,大大提高了程序的安全性。

总结:

什么时候需要定义泛型类呢?

当类中要操作的引用数据类型不确定的时候,早期定义Object来完成扩展,现在定义泛型来完成扩展。

5、泛型方法

1)泛型除了可以定义类上,也可以定义在方法中。

黑马程序员——Java高新技术——JDK1.5版本的新特性泛型 

编译失败

原因在这条语句 Demo<String>d=new Demo<String>();

已经定义为string,想操作整数类型,不行。

这就是泛型类中的稍微局限性,只要类型固定,方法中的类型也就固定了。

那么如果,我们就需要同时调用泛型类中方法,但用不同数据类型呢?

 泛型类定义的泛型,在整个类中有效,如果被方法是用,那么泛型类的对象明确要操作的具体类型后,所有要操作的类型就已经固定了。为了让不同方法可以操作不同类型,而且类型还不确定,可以将泛型定义在方法上。

class Demo{   public <T>void show(T t)  {   System.out.println("show:"+t);  }  public <Q> print(Q q)//写成t也可以,因为只在方法上有效。  {    System.out.println("print:"+q);
}}class GenericDemo{ public static void main(String[]args) {Demo d =new Demo();d.show("haha");d.show(new Integer(4));d.print("heihei"); } }

2)在泛型类中定义泛型

  黑马程序员——Java高新技术——JDK1.5版本的新特性泛型

6、静态泛型方法

   静态方法也可以使用泛型。特殊之处,静态方法不可以访问类上定义的泛型

如果静态方法操作的引用数据类型不确定,可以将泛型定义在方法上。

 黑马程序员——Java高新技术——JDK1.5版本的新特性泛型

编译失败,无法从静态上下文中引用非静态类 T

T只有在对象建立时,才能被明确。

黑马程序员——Java高新技术——JDK1.5版本的新特性泛型 

 

   注意:泛型定义在方法上,要放在返回值类型的前面,修饰符的后面。

7、泛型接口

1)泛型也可以定义在接口上,如comparable

黑马程序员——Java高新技术——JDK1.5版本的新特性泛型 

 

2)还有一种情况:一个类,实现了泛型接口,可是实现的时候,也不知道该是什么类型

 黑马程序员——Java高新技术——JDK1.5版本的新特性泛型

8、泛型限定

  代码

class GenericDemo{  public static void main(String[]args)  {   ArrayList<String>al=new ArrayList<String>();    al.add("abc1");    al.add("abc2");    al.add("abc3");ArrayList<Integer>al1=new ArrayList<Integer>();    al1.add(4);    al1.add(7);    al1.add(1);//遍历集合,我们是不是定义两个迭代器,可以封装成一个函数  }

1)占位符?

可是,我们怎么才能做到既能操作string的集合,又能操作integer的集合呢

可以用占位符?

public static void printColl(ArrayList<?> al){Itetator<?> it=al.iterator();while(it.hasNext()){System.out.println(it.next());}}


那我们用泛型方法是不是也能实现呢?是的

这两者都可以,但有小小的区别。

泛型方法:<T> 可以在方法里进行操作

     占位符<?>:不可以使用占位符在方法里进行操作。

接下来的问题是:

public static void printColl(ArrayList<?> al) { Itetator<?> it=al.iterator(); while(it.hasNext()) { System.out.println(it.next().length());//可以打印长度吗? } }


不可以。

因为是不明确类型的,如果传的是Integer类型,是没有length()方法的。

泛型的弊端:就是不能使用类型的特有方法。

就像多态,虽有好处,但是弊端就是不能预先使用子类的特有方法。

那么<?>还有什么用处呢?

代码

class Person{  private String name;  Person(String name)   {   this.name=name;   } public String getName()   {   return name;   } } class Demo{public static void Main(String []args) { ArrayList<Person>al=new ArrayList<Person>();  al.add(new Person("abc1"));  al.add(new Person("abc2")); al.add(new Person("abc3"));printColl(al);ArrayList<Person>al1=new ArrayList<Person>();  al1.add(new Person("abc1")); al1.add(new Person("abc2")); al1.add(new Person("abc3");printColl(al1);//ArrayList<Person> al=new ArrayList<Student>(); 这是不允许的。  // 结论:左右两边需要一致。}public static void printColl(ArrayList<Person> al) {  Iterator<Person> it=al.iterator(); while(it.hasNext())  {       System.out.println(it.next().getName());  } }class Student extends Person{   Student(String name)   {     super(name);   }}}

2)泛型限定

如果想不一致,怎么办呢?

<?>.但是<?>是任意类型,我们只想打印Person和它的子类

怎么办呢?泛型限定

public static void printColl(ArrayList<? extends Person> al) {  Iterator<? Extends Person> it=al.iterator(); while(it.hasNext())  {       System.out.println(it.next().getName());  } }

3)泛型限定类型:

泛型限定有两个,一个叫做上限,一个叫下限

通配符,也可以理解为占位符

泛型的限定:

1  ? Extends E:可以接收E类型或者E的子类型。这叫做上限。

2  ?super E:可以接收E类型或者E的父类型。这叫做下限。

  上限举例

如  Collection中 boolean addall(Collection<? extends E> c)  addall(c)

下限举例

TreeSet中的

TreeSet(Comparator<? super E> comparator) 比较器参数

class Student implements Comparable<? Super Studente>{ public int compareTo(<?super Student e>)   {int num= this.getName().compareTo(e.getName());if(num==0){  return this.getAge()-e.getAge();}return num;    }}class Comp implements Comparator<?super Student e>{   public int compare(<?super Student s1>,<?super Student s2>){ return s1.getName().compareTo(s2.getName());}}

对泛型的定义:

        第一、定义泛型:当又不确定的类型需要传入到集合中,需要定义泛型。 

        第二、定义泛型类:如果类型确定后,所操作的方法都是属于此类型,则定义泛型类。

        第三、定义泛型方法:如果定义的方法确定了,里面所操作的类型不确定,则定义泛型方法。

9、类型参数的类型推断

1)编译器判断范型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。

2)根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:

         (1)当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型

        (2)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来

         (3)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题。             

         (4)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,并且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误。   

        (5)参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题。      

泛型总结:

   1)泛型的类型参数只能是类类型,不可以是简单类型。如<A int>,这种泛型定义就是错误的。

   2)泛型的类型个数可以是多个

   3)可以使用extends关键对泛型进行限定

   4)可以使用通配符限制泛型的类型。

 

黑马程序员——Java高新技术——JDK1.5版本的新特性泛型 

------------ Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ------------