初识java泛型

1 协变数组类型(covariant array type)

  数组的协变性:

  if A IS-A B then A[] IS-A B[]

也就是说,java中的数组兼容,一个类型的数组兼容他的子类类型数组。

  协变数组好处:使得代码的灵活性更强。

  协变数组的坏处:过于灵活导致类型混乱,比如:

  Peron[] arr = new Employee[5]; //Employee IS-A Person 可以执行

  arr[0] = new Student();// Student IS-A Person 可以执行

  上面这一段程序在编译器编译完全通过,都是在实际执行中会报ClassCastException异常,因为arr[0] 实际上时Employee类型的引用,但现在将Student类型的对象赋给它,类型不同因此会报错。由此可以看出,只限制类型继承关系的数组协变性会带来数据类型的混乱。

2 Java5之前的泛型结构特性构件

  面向对象的一个重要目标是实现代码的复用,而泛型机制可以实现在不同类型而具有实现方法时只用一种泛型方法实现这些方法。比如:现在我们需要实现一个简单的类,该类只需要对输入的元素进行命令行输出,此时如果函数的形参限定为String 那我们只能输入输出String类型的元素,同理当为Integer时只能输入Integer类型不能输入String类型,否则会报错,这是可以使用他们共同的父类Object,由于java的多态性,可以将子类赋给父类,由此可以只用一种Object作为形参的函数,而分别处理这这两种类型。

  在java5推出泛型机制之前,程序当要实现泛型一般时使用超类(大部分待处理类型的父类)Object。

 下一段代码就是:

public class MemoryCell
{
  public Object read(){return storeValue; }
  public void write(Object x){storeValue=x;  System.out.println("storevalue="+storedValue);}
  private Object storeValue;
}

  在上面这个程序使用Object类作为storeValue的类型,以及wirte形参的类型,由java的多态机制,可以将Object的子类对象作为实参传入。

class TestMemoryCell
{
  public static void main(String[] args)
  {
     MemoryCell mc  = new MemoryCell();
     mc.write("32");//将一个Object的子类字符串作为实参
     mc.read();//输出val=32  
     MemoryCell mc2  = new MemoryCell();
     mc.write(new Integer(343));//将一个Object的子类字符串作为实参
     mc.read();//输出val=343
} {

 由此,我们可以讲任意形如String等Object的子类,作为实参输入给函数,完成对该元素的打印功能。

3 java5提出的泛型机制

  在java5之前人们想实现java机制都需要通过上述机制,都是上述机制存在一些问题,当用Object作为形参不可避免的带来了和数组协变性相同的问题--数据类型的混乱。可是实际开发又要求java实现泛型特性以完成对代码的复用,怎么办?只能推出新的规范呗,正好大家都喜欢那样做。

  泛型包括泛型类与泛型static方法。

3.1 泛型类

  形式:class ClassName <AnyType1|,AnyType2...>{...} //AnyType

  泛型类的实例化: ClassName<String> cn = new ClassName<String>(); or  ClassName<String> cn = new ClassName<>();//String 只是一个例子,可以为其他类型,如Integer

  注意:AnyType在定义是可以为任意字符串,包括String等,而此时的String只作为一个变量名,而不是说将泛型类型定为字符串;AnyType:可以作为类型在类中使用。

  由此可以看出,泛型类就是在类名后加了对尖括号,在尖括号里可以为类型参数。同样接口也可以定义为泛型。

  java5的泛型机制和java5之前的泛型结构有着一个显著的区别:泛型可以记住数据的类型,而Object类型的元素输出都需要强制转换。记住数据类型还可以保证在数据输入时数据类型得以确定。

  使用泛型的优点:①由(2)可以知道泛型可以实现代码的复用;

  ②可以将原本运行时才能发现的错误提前到编译时发现。例如:数组协变性,同样在java5之前的泛型也存在这种问题,但是使用泛型后可以在编译的时候报错。

  

3.2 类型通配符

  一个简单的泛型类MemoryCell:

class MemoryCell<X>
{
	public X read()
	{
		return storedValue;
	}
	public void write(X x)
	{
		storedValue = x;
		System.out.println("storedValue="+storedValue);
	}
	private X storedValue;
}

  

  MemoryCell<String>与MemoryCell<Object>是不是两个不同的类?

class TestMemoryCell
{
	public static void main(String[] args)
	{
		MemoryCell<String> mc1 = new MemoryCell<String>();
		MemoryCell<Object> mc2 = new MemoryCell<Object>();
		System.out.println("new MemoryCell<String>().ClassName=="+mc1.getClass()); //输出:new MemoryCell<String>().ClassName==class MemoryCell
		System.out.println("new MemoryCell<Object>().ClassName=="+mc2.getClass()); //输出:new MemoryCell<Object>().ClassName==class MemoryCell
		System.out.println(mc1.getClass()==mc2.getClass());//输出:true
	}
}

  由上程序可以看出MemoryCell<String>与MemoryCell<Object>是同一个类。也就是说在内存中只保留了一个MemoryCell类。具体是如何实现数据类型限制的看这篇博客吧http://blog.csdn.net/yi_afly/article/details/52002594,吼吼,是用隐形的重载方法(java中不能有返回值不同的重载,但这里的class就是有两个签名),在运行时让JVM来区别。

  MemoryCell<String>是不是MemoryCell<Object>的子类?

class TestMemoryCell
{
	public static void main(String[] args)
	{
		MemoryCell<String> mc1 = new MemoryCell<String>();
		MemoryCell<Object> mc2 = mc1; //错误: 不兼容的类型: MemoryCell<String>无法转换为MemoryCell<Object>

	}
}

  假设是子类,那mc1可以直接赋给mc1,所以不是子类。但是明显Object时String的父类,为什么他们对应的泛型却不存在这种关系呢?这是设计者有意为之,就是不想出现如图一开始讲的数组协变性带来的编译通过而运行时因为数据类型混乱而报错。

  那么假设有程序如下:

class TestMemoryCell
{
	public static void main(String[] args)
	{
		MemoryCell<String> mc1 = new MemoryCell<String>();
		MemoryCell<Object> mc2 = new MemoryCell<Object>();
		//System.out.println("new MemoryCell<String>().ClassName=="+mc1.getClass());
		//System.out.println("new MemoryCell<Object>().ClassName=="+mc2.getClass());
		//System.out.println(mc1.getClass()==mc2.getClass());
		//MemoryCell<Object> mc3 = mc1;
		mc1.write("haha");
		get(mc1);

	}
	public static void get(MemoryCell<Object> mc)
	{
		System.out.println("storedValue="+mc.storedValue);
	}
}

  则该程序报错,错误: 不兼容的类型: MemoryCell<String>无法转换为MemoryCell<Object>。但是有事我们只需要Object的toString方法,这么严格的限制会影响程序的灵活性,于是提出了类型通配符。

  类型通配符:<?> //? 作为实参传递给泛型类

  类型通配符<?>的意思是,可以为任意通配符。所以,

  对上述程序做如下修改即可顺利输出

	public static void get(MemoryCell<?> mc)
	{
		System.out.println("storedValue="+mc.storedValue);
	}

3.2 类型通配符的上限,类型通配符下限,设定类型形参的上限

  类型通配符的上限:有时,我们希望?代表的是某一类有相同父类的子类,如此便可以保证父类方法的顺利执行,于是使用<? extends AnyType>这是便要求?必须为Anytype的子类。

class TestMemoryCell
{
	public static void main(String[] args)
	{
		MemoryCell<String> mc1 = new MemoryCell<String>();
		MemoryCell<Object> mc2 = new MemoryCell<Object>();
		mc1.write("haha");
		get(mc1);//String extens String 可以执行
		mc2.write("aha");
		get(mc2);//Object IS-NOT-A String 报错,不兼容的类型: MemoryCell<Object>无法转换为MemoryCell<? extends String>

	}
	public static void get(MemoryCell<? extends String> mc)
	{
		System.out.println("storedValue="+mc.storedValue);
	}
}

  同样可以定义下限,<? super AnyType> ?必须为AnyType的父类。

  在类的定义中,同样可以设定类型形参的上限,如此便限制了创建实例时传入的形参。

4 Java5 泛型方法

  何时要用到泛型方法?

  在定义类、接口时没有使用类型形参但是在定义方法的时候却想自己定义类型形参,这是就需要java5提供的泛型方法。

4.1 定义泛型static方法

 

class GenericMothod
{
   static void fromArrayToCollection(Object[ ] a ,Collection<Object> c)
   {
         for( Object o : a )
               c.add( o );
    } 

   public static void main(String [ ] args)
   {
         String[ ] strArr = {"a " , "b"};
          List<String> strList = new ArrayList<>();
         fromArrayToCollection(strArr , strList );//编译报错,无法将Collection<String>转换为Collection<Object>
    }
}

  上一段代码编译无法通过,因为Collection<String> IS-NOT-A Collection<Object>,但是如果不用集合而是使用数组则可以实现,并且执行也不会报错,因为数组具有协变性。那这样岂不是说用泛型会带来很大的不便,或者说降低了代码的复用性,因为若要使用Collection<String>则必须重新定义一个函数,利用java的方法重载来实现。所以java5 提出了新的泛型方法。

  泛型方法格式: 修饰符 <T,S> 返回类型 方法名 (形参列表){...}  //在定义方法的时候定义一个或者多个类型形参;

  如此上述程序的fromArrayToCollection可以变为如下形式:

  

  
import java.util.*;
class GenericMethod
{
    static<T> void fromArrayToCollection(T[ ] a ,Collection<T> c)
   {
         for( T o : a )
               c.add( o );
    } 

   public static void main(String [ ] args)
   {
         String[ ] strArr = {"a " , "b"};
         Object[ ] strArr2 = {"a " , "b"};
          List<String> strList = new ArrayList<String>();
          List<Object> strList2 = new ArrayList<Object>();
         fromArrayToCollection(strArr , strList );//编译通过
         fromArrayToCollection(strArr2 , strList2 );//编译通过
    }
}

  由该程序可以看出泛型方法与泛型类不同,泛型方法的形参无需显示的传入实际参数,而是通过fromToCollection(strArr , strList)编译器根据实参类型推断出类型参数。那么如果前后两个实参推断出来的类型参数不一致呢?那么编译自然错误。例如:

import java.util.*;
class GenericMethod
{
    static<T> void fromArrayToCollection(T[ ] a ,Collection<T> c)
   {
         for( T o : a )
               c.add( o );
    } 

   public static void main(String [ ] args)
   {
         String[ ] strArr = {"a " , "b"};
         Object[ ] strArr2 = {"a " , "b"};
          List<String> strList = new ArrayList<String>();
          List<Object> strList2 = new ArrayList<Object>();
         fromArrayToCollection(strArr , strList );//编译报错,无法判别T类型
         fromArrayToCollection(strArr , strList2 );//编译不报错,T为Object
    }
}

  泛型方法同样也可以使用类型参数通配符,例如:

static<T> void test(Collection <? extend T>from ,Collection<T> to)
{
    for(T ele : from)
        to.add(ele);
}

pubic static void main(String[ ] args)
{
   List<Object> ao =new ArrayList<>();
   List<String> as = new ArrayList<>();
   test(as, ao); //T判别为Object ,编译通过。
}

5 泛型方法与类型通配符的区别

 1 // Collection接口的类型通配符方式定义
 2 public interface Collection<E>
 3 {
 4     boolean containsAll(Collection<?> c);
 5     boolean addAll(Collection<? extend E> c);
 6     ...
 7 }
 8 // Collection接口的泛型函数方式定义
 9 public interface Collection<E>
10 {
11     <T> boolean containsAll(Collection<T> c);
12     <T extends E>boolean addAll(Collection<T> c);
13     ...
14 }

  由上面的例子可以看出来泛型方法和类型通配符可以实现相同的功能。那么我们什么时候使用泛型方法?

  使用泛型方法:运行类型形参用来表示方法的一个或者多个参数之间的依赖关系,或者返回值与参数之间的关系。如果不存在这种关系就应该使用类型通配符。

  泛型方法与类型通配符的显著区别:类型通配符可以在方法签名中定义形参类型,也可以用于定义变量的类型;但是泛型方法的类型形参必须在对应方法中显示说明。

6 泛型的限制

  由于泛型类在编译器会通过类型擦除变为非类型类(泛型类中提到了泛型类在内存中只存一个非泛型类),所以对泛型定义的时候也存在限制。

  ①基本类型不能作为类型参数

  ②instanceof检测不能对泛型类使用

  ③在泛型类中static修饰的类变量类方法均不可以引用类的类型变量,因为类型擦除后类型变量不存在并且由于只存在一个原始类所以static变量在泛型实例中时共享的。

  ④泛型的类型变量不能实例化

  ⑤不存在泛型的数组

  ⑥参数化类型的数组的实例化时非法的。

  

Reference:

1.《数据结构与算法分析 java描述》

2.Java总结篇系列:java泛型 http://www.cnblogs.com/lwbqqyumidi/p/3837629.html

3.《疯狂java讲义》第九章 泛型

 

时间: 2024-10-30 21:34:12

初识java泛型的相关文章

初识JAVA(【面向对象】:pub/fri/pro/pri、封装/继承/多态、接口/抽象类、静态方法和抽象方法;泛型、垃圾回收机制、反射和RTTI)

JAVA特点: 语法简单,学习容易 功能强大,适合各种应用开发:J2SE/J2ME/J2EE 面向对象,易扩展,易维护 容错机制好,在内存不够时仍能不崩溃.不死机 强大的网络应用功能 跨平台:JVM,字节码 两个命令: javac helloworld.java:将Java文件编译为字节码的.class文件 java helloworld:运行Java程序...??暂时这么认为 数据类型: 普通数据类型:int a=3; 对象数据类型:使用关键字new,String s=new String("

Java泛型中的PECS原则

今天在写代码的时候使用到了这样一个方法签名: public void foo(Map<String, String> map); 在写这个参数的时候正好在想一些关于泛型的东西,于是: public void foo(Map<? extends String, ? extends String> map); 这两种写法有什么区别呢?记得以前和同学讨论过这个问题,但后来没有记下来,渐渐又淡忘了.今天又去翻了好多资料,总算找到一些可以参考的,赶紧记在这里方便以后温故知新啦.好了,言归正传

Java泛型的协变

在上篇<Java泛型的基本使用>这篇文章中遗留下面问题,即将子类型也能加入到父类型的泛型中.要实现这样的功能必须借助于协变. 实验准备 如今在上篇文章展示的Decorator类型的基础上,添加一些代码,如代码清单1所看到的. 代码清单1 /** * * 描 述:Exp2使用br/> * 作 者:jiaan.gja<br/> * 历 史: (版本号) 作者 时间 凝视 <br/> * @param itemList */ public void doDecorate

2017.4.5 Java 泛型

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型. 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数. 假定我们有这样一个需求:写一个排序方法,能够对整形数组.字符串数组甚至其他任何类型的数组进行排序,该如何实现? 答案是可以使用 Java 泛型. 使用 Java 泛型的概念,我们可以写一个泛型方法来对一个对象数组排序.然后,调用该泛型方法来对整型数组.浮点数数组.字符串数组等进行排

关于Java泛型的使用

在目前我遇到的java项目中,泛型应用的最多的就属集合了.当要从数据库取出多个对象或者说是多条记录时,往往都要使用集合,那么为什么这么使用,或者使用时有什么要注意的地方,请关注以下内容. 感谢Windstep. 原文链接:http://www.cnblogs.com/lwbqqyumidi/p/3837629.html 原文标题:Java总结篇系列:Java泛型 (我的第一篇水文,233)

java 泛型 窜讲

一.为什么使用泛型      复用性:泛型的本质就是参数化类型,因而使用编写的泛型代码可以被许多不同类型的对象所复用.      安全性:在对类型Object引用的参数操作时,往往需要进行显式的强制类型转换.这种强制类型转换需要在运行时才能被发现是否转换异常,通过引入泛型能将在运行时才能检查类型转换,提前到编译时期就能检查. 二.自定义泛型 java中自定义泛型分为三种:泛型类.泛型接口.泛型方法. 下面使用一个案例演示泛型类.泛型方法,泛型接口类似,所以不再演示. // 自定义泛型类publi

1月21日 - (转)Java 泛型

java泛型 什么是泛型? 泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样. 可以在集合框架(Collection framework)中看到泛型的动机.例如,Map 类允许您向一个 Map 添加任意类的对象,即使最常见的情况是在给定映射(map)中保存某个特定类型(比如 String)的对象. 因为 M

Java泛型_上界extends_下界super

?Java泛型_上界extends_下界super ? 通配符类型 <? extends T> 表示类型的上界,表示参数化类型的可能是T或是T的子类 <? super T> 表示类型下界(Java Core中叫超类型限定),表示参数化类型是此类型(T)的超类型(父类型),直至Object 当使用 Upper Bound 通配符时 如下代码, /**  * 代码中通配符<?> 是 <? extends Object> 的简写  *  * @param list

C++泛型 &amp;&amp; Java泛型实现机制

C++泛型 C++泛型跟虚函数的运行时多态机制不同,泛型支持的静态多态,当类型信息可得的时候,利用编译期多态能够获得最大的效率和灵活性.当具体的类型信息不可得,就必须诉诸运行期多态了,即虚函数支持的动态多态. 对于C++泛型,每个实际类型都已被指明的泛型都会有独立的编码产生,也就是说list<int>和list<string>生成的是不同的代码,编译程序会在此时确保类型安全性.由于知道对象确切的类型,所以编译器进行代码生成的时候就不用运用RTTI,这使得泛型效率跟手动编码一样高.