`
tianya84
  • 浏览: 25771 次
社区版块
存档分类
最新评论

[转]JAVA泛型总结

 
阅读更多

JAVA泛型总结

Java集合有一个缺点,就是把一个对象“丢进”集合里后,集合就会“忘记”这个对象的数据类型,当再次取出该对象时,该对象的编译类型就变成了Object类型(其运行时类型没变)。

之所以这样设计是因为设计集合的程序员不会知道我们要用它来保存什么类型的对象,所以这样设计具有很好的通用性。但是这样做带来如下两个问题:

l  集合对与元素类型没有限制,如一个集合能保存一个苹果对象也能保存一个香蕉对象;

l  由于对象进入集合后失去了其状态信息,所以取出来时需要进行强制类型转换。

看下面的程序:

{   //….

     List list = new ArrayList();

     list.add(1);

     list.add(2);

     list.add("五号");//一不小心插入了String

     for (Object object : list){

        //取出“五号”时报ClassCastException

        Integert = (Integer)object;

  }

上面是一个集合没有使用泛型时出现的错误,那么当我们使用泛型时,该错误就会避免,例如:

     List<Integer>list = newArrayList<Integer>();

     list.add(1);

     list.add(2);

     list.add("五号");//插入String,引起编译错误

 1、认识泛型

Java的参数化类型被称为泛型,即允许我们在创建集合时就指定集合元素的类型,该集合只能保存其指定类型的元素。

泛型允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定。例如:

//定义一个接口

interface Money<E>{

   Eget(intindex);

   boolean add(E e);

}

//定义一个类

public classApple<T>{

   private T info;

   public Apple(T info) {

     this.info = info;

   }

//定义一个接口  

   public T getInfo(){

     return this.info;

       }  

   public void setInfo(T info){

     this.info = info;

   }    

   public static void main(String[] args) {

     Apple<String>ap1 = newApple<String>("小苹果");

     System.out.println(ap1.getInfo());

     Apple<Double>ap2 = newApple<Double>(1.23);

     System.out.println(ap2.getInfo());

   }

}

需要注意的是,在静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用类型形参。因为不管为泛型的类型形参传入哪一种类型实参,对于Java来说,它们依然被当成同一个类处理,在内存中也只占用一块内存空间。不管泛型的实际类型参数是什么,它们在运行时总有同样的类(class),例如下面的程序将输出true

     List<String> l1 = new ArrayList<String>();

     List<Double> l2 = new ArrayList<Double>();

     System.out.println(l1.getClass() == l2.getClass());

 

2、类型通配符

如果我们想定义一个方法,这个方法的参数是一个集合形参,但是集合形参的元素类型是不确定的。可能会想到下面两种定义方法:

        public void test1(List l){ }

        public void test2(List<Object> l){ }

test1可以使用,但是会报泛型警告;

test2在传入List<Object>时无法使用,会报“test2List<Object>)对于参数(List<String>)不适用”;

为了表示各种泛型List的父类,我们需要使用类型通配符,类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作List<?>,那么我们就可以这样定义上面的方法:

         public void test3(List<?> l){ }

此时我们就可以传入以任何元素类型为集合形参的List了,就算是自定义类也可以。

但还有一种特殊的情形,我们不想这个List<?>是任何泛型List的父类,只想表示它是某一类泛型List的父类,例如我们有一个圆形类(Circle)、矩形类(Rectangle),这两个类都继承了Shape抽象类,那么我们就可以这样设计:

public class Test{

   public static void main(String[] args) {

     List<Circle>list = new ArrayList<Circle>();

     list.add(new Circle());

     Canvasc = new Canvas();

     c.drawAll(list);

   }

}

abstract class Shape{

   public abstract void draw(Canvas c);

}

class Canvas{

   public String toString() {

     return "Canvas";

   }

   public void drawAll(List<? extends Shape> shapes){

     for (Shape shape : shapes){

        shape.draw(this);

      }

   }

}

class Circle extends Shape{

   public void draw(Canvas c) {

     System.out.println("在画布" + c + "上画一个圆");

   }

}

class Rectangle extends Shape{

   public void draw(Canvas c) {

     System.out.println("在画布" + c + "上画一个矩形");

   }

}

List<? extends Shape>是受限制通配符的例子,此处的问号(?)代表一个未知的类型,就想前面看到的通配符一样。但是此处的这个未知类型一定是Shape的子类或Shape本身,因此我们可以把Shape称为这个通配符的上限

这里需要注意的是,因为不知道这个受限制通配符的具体类型,所以不能把Shape对象或其子类的对象加入这个泛型集合中,例如下面的代码会报编译错误:

   public void drawAll(List<? extends Shape> shapes){

     //此处会报编译错误

     shapes.add(new Rectangle());

}

Java泛型不仅允许在使用通配符形参时设定上限,而且可以在定义类型形参时设定上限,用于表示传给该类型形参的实际类型要么是该上限类型,要么是该上限类型的子类,例如:

public class Apple<T extends Number>{

   T colT;

   public static void main(String[]args) {

     Apple<Integer> ai = new Apple<Integer>();

     Apple<Double> ad = new Apple<Double>();

     //下面代码将引发编译错误,因为String类不是Number的子类型

     Apple<String> as = new Apple<String>();

   }

}

 

3、泛型方法

所谓泛型方法,就是在声明方法时定义一个或多个类型形参。泛型方法的用法格式如下:

修饰符<T,S>  返回值类型 方法名(形参列表){

   //方法体

}

把上面方法的格式和普通方法的格式进行对比,不难发现泛型方法的方法签名比普通方法的方法签名多了类型形参声明,类型形参声明以尖括号包括起来,多个类型形参之间以逗号(,)隔开,所有的类型形参声明方法方法修饰符和方法返回值类型之间。

例如,我们要写一个这样的方法,用于将一个Object数组的所有元素添加到一个Collection集合中:

  static <T> void fromArrayToCollection(T[] a,Collection<T> c){

     for (T t : a) {

        c.add(t);

     }

}

上面的方法是正确的,没有错误,但是在我们实际使用的过程中,我们只能将数组中的元素加入Collection中,想要将一个Collection中的元素加入到另一个Collection中,上面的方法就不适用了。那我们可不可以这样改进上面的方法呢?

   static <T> void fromArrayToCollection(Collection<T> a,Collection<T> b){

     for (T t : a) {

        b.add(t);

     }

}

  public static void main(String[] args) {

     List<String> a = new ArrayList<String>();

     List<Object> b = new ArrayList<Object>();

     fromArrayToCollection(a, b);

   }

显然,在我们调用fromArrayToCollection时会引发编译错误,这是因为编译器无法准确地推断出泛型方法中类型形参的类型,不知道应该用String还是用Object。为了避免这种错误,可以将方法改为这样:

  static <T> void fromArrayToCollection(Collection<? extends T> a, Collection<T> b){

     for (T t : a) {

        b.add(t);

     }

   }

   public static void main(String[]args) {

     List<String> a = new ArrayList<String>();

     List<Object> b = new ArrayList<Object>();

     fromArrayToCollection(a, b);

}

上面的方法中,将该方法前一个形参类型改为Collection<? extends T>,这种采用类型通配符的表示方式,只要该方法的前一个Collection集合里的元素类型是后一个Collection集合里的元素类型的子类即可。 
 4
、泛型方法和类型通配符的区别

大多数时候都可以使用泛型方法来代替类型通配符。例如对于JavaCollection接口中两个方法的定义:

public interface Collection<E>{

   boolean containsAll(Collection<?>c);

   booleanaddAll(Collection<? extends E> c);

   ...

}

上面集合中的两个方法的形参都采用了类型通配符的形式,也可以采用泛型方法的形式,如下所示:

public interface Collection<E>{

   <T> boolean containsAll(Collection<T> c);

   <T extends E> boolean addAll(Collection<T> c);

   ...

}

上面方法使用了<Textends E>泛型形式,这时定义类型形参时设定上限(其中ECollection接口里定义的类型形参,在该接口里E可当成普通类型使用)。

上面两个方法中类型形参T只使用了一次,类型形参T产生的唯一效果是可以在不同的调用点传入不同的实际类型。对于这种情况,应该使用通配符:通配符就是被设计用来支持灵活的子类化的。

泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系。如果没有这样的类型依赖关系,就不应该使用泛型方法。

 

原文:http://blog.csdn.net/zhai56565/article/details/40503565

 

 

 

 

分享到:
评论

相关推荐

    很好的Java泛型的总结

    Java泛型机制详解 Java泛型是Java语言中的一种机制,用于在编译期检查类型安全。Java泛型的出现解决了Java早期版本中类型安全检查的缺陷。Java泛型的好处是可以在编译期检查类型安全,避免了运行时的...

    Java泛型总结(2)进阶篇

    Java泛型是Java SE 5.0引入的一个重要特性,极大地提高了代码的类型安全性和重用性。在本文中,我们将深入探讨Java泛型的进阶概念,包括通配符、边界、类型擦除以及在实际开发中的应用。 1. 通配符 通配符在Java...

    思维导图之Java泛型详解

    思维导图之Java泛型详解

    java泛型技术之发展

    总结,Java泛型技术的发展极大地提升了Java编程的效率和安全性。它通过编译时检查和类型安全保证,帮助开发者编写出更加健壮和易于维护的代码。理解并熟练掌握Java泛型,是每一个Java开发者必备的技能。

    java 泛型方法使用示例

    下面我们将深入探讨Java泛型方法的概念、语法以及使用示例。 **一、泛型方法概念** 泛型方法是一种具有类型参数的方法,这些类型参数可以在方法声明时指定,并在方法体内部使用。与类的泛型类似,它们提供了编译时...

    Java泛型编程最全总结

    Java泛型是Java编程语言中的一个关键特性,它在2004年随着JDK 5.0的发布被引入,极大地增强了代码的类型安全性和重用性。泛型允许我们在编写类、接口和方法时指定参数化类型,使得代码在编译时期就能捕获类型错误,...

    java泛型总结.docx

    以下是对Java泛型的详细解释: 1. **泛型类型**:泛型类型允许我们在定义类、接口或集合时引入类型参数。比如`List&lt;String&gt;`就是一个泛型类型,其中String是类型参数,代表了列表中的元素类型。这样,当我们将字符...

    java 泛型的使用 详细讲解

    ### Java泛型的使用详细讲解 #### 一、引言 在Java开发中,泛型是一种重要的语言特性,它能够帮助开发者在不增加代码量的情况下处理多种数据类型,同时还能保持代码的清晰度和可读性。本文将详细介绍Java泛型的...

    全面总结Java泛型--实例

    标题与描述均提到了“全面总结Java泛型--实例”,这表明文章旨在深入解析Java泛型的概念,并通过具体示例来展示其应用。Java泛型是Java编程语言的一个强大特性,它允许在编译时检查类型安全,并且所有的强制转换都是...

    Java泛型类型擦除后的补偿

    总结来说,Java泛型的类型擦除虽然在运行时消除了类型信息,但通过编译时的类型检查、桥接方法、通配符等补偿机制,仍然实现了强大的类型安全和便利性。开发者应理解这些补偿机制,以便更好地利用Java泛型进行类型...

    全面总结Java泛型

    Java 泛型是 Java 编程语言中一个强大的特性,它允许在类、接口和方法中使用类型参数,从而增加了代码的复用性和安全性。在本文中,我们将深入探讨 Java 泛型的各种方面。 首先,普通泛型允许我们在定义类时引入一...

    Java 泛型总结(一):基本用法与类型擦除

    "Java 泛型总结(一):基本用法与类型擦除" Java 泛型是 Java 语言中的一种强大功能,它可以使代码更加简洁、安全。下面是对 Java 泛型的基本用法和类型擦除机制的介绍。 泛型的基本用法 ------------- 泛型是...

    Java泛型与容器详细笔记.pdf (带书签)

    总结来说,Java泛型与容器详细笔记提供了关于如何使用Java中的泛型和容器类的深入理解,涵盖了Java集合框架的核心组件,泛型的类型安全机制,以及一些提高开发效率的第三方库。文档通过实例代码展示了如何在实际项目...

    java泛型类和函数

    ### Java泛型类和函数详解 #### 泛型概述 在Java中,泛型是一种允许开发者在类、接口和方法中使用类型参数的功能。通过使用泛型,可以在编写代码时指定一个或多个类型参数,从而使得编写的代码更加灵活且重用性更高...

    Java深度历险之Java泛型.docx

    ### Java泛型详解 #### 一、Java泛型概述 Java泛型(Generics)是Java SE 5.0引入的一项重要新特性,它允许开发者在定义类、接口或方法时使用类型参数(Type Parameters)。类型参数在使用时可以用具体的类型来...

Global site tag (gtag.js) - Google Analytics