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 泛型方法与类型通配符的区别

 // Collection接口的类型通配符方式定义
public interface Collection<E>
{
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extend E> c);
...
}
// Collection接口的泛型函数方式定义
public interface Collection<E>
{
<T> boolean containsAll(Collection<T> c);
<T extends E>boolean addAll(Collection<T> c);
...
}

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

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

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

6 泛型的限制

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

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

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

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

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

  ⑤不存在泛型的数组

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

  

Reference:

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

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

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

 

初识java泛型的更多相关文章

  1. 初识Java泛型以及桥接方法

    泛型的由来 在编写程序时,可能会有这样的需求:容器类,比如java中常见的list等.为了使容器可以保存多种类型的数据,需要编写多种容器类,每一个容器类中规定好了可以操作的数据类型.此时可能会有Int ...

  2. 【Java心得总结三】Java泛型上——初识泛型

    一.函数参数与泛型比较 泛型(generics),从字面的意思理解就是泛化的类型,即参数化类型.泛型的作用是什么,这里与函数参数做一个比较: 无参数的函数: public int[] newIntAr ...

  3. 【Java心得总结四】Java泛型下——万恶的擦除

    一.万恶的擦除 我在自己总结的[Java心得总结三]Java泛型上——初识泛型这篇博文中提到了Java中对泛型擦除的问题,考虑下面代码: import java.util.*; public clas ...

  4. Java入门——初识Java

    Java入门——初识Java 摘要:本文主要对Java这门编程语言进行简单的介绍. Java简介 说明 Java语言历时十多年,已发展成为人类计算机史上影响深远的编程语言,从某种程度上来看,它甚至超出 ...

  5. Java泛型的历史

    为什么Java泛型会有当前的缺陷? 之前的章节里已经说明了Java泛型擦除会导致的问题,C++和C#的泛型都是在运行时存在的,难道Java天然不支持“真正的泛型”吗? 事实上,在Java1.5在200 ...

  6. 浅析Java 泛型

    泛型是JavaSE5引入的一个新概念,但是这个概念在编程语言中却是很普遍的一个概念.下面,根据以下内容,我们总结下在Java中使用泛型. 泛型使用的意义 什么是泛型 泛型类 泛型方法 泛型接口 泛型擦 ...

  7. Java:泛型基础

    泛型 引入泛型 传统编写的限制: 在Java中一般的类和方法,只能使用具体的类型,要么是基本数据类型,要么是自定义类型.如果要编写可以应用于多种类型的代码,这种刻板的限制就会束缚很多! 解决这种限制的 ...

  8. java泛型基础

    泛型是Java SE 1.5的新特性, 泛型的本质是参数化类型, 也就是说所操作的数据类型被指定为一个参数. 这种参数类型可以用在类.接口和方法的创建中, 分别称为泛型类.泛型接口.泛型方法.  Ja ...

  9. 使用java泛型设计通用方法

    泛型是Java SE 1.5的新特性, 泛型的本质是参数化类型, 也就是说所操作的数据类型被指定为一个参数. 因此我们可以利用泛型和反射来设计一些通用方法. 现在有2张表, 一张user表和一张stu ...

随机推荐

  1. November 3rd Week 45th Thursday 2016

    Shared joy is a double joy, the same as your gift and idea. 与人分享,欢乐.灵感加倍. As a coder, I must work wi ...

  2. Hangfire入门(任务调度)

    一.简介 英文官网:http://hangfire.io/ 开源地址:https://github.com/HangfireIO Hangfire 不依赖于具体的.NET应用类型,包含.NET 和.N ...

  3. 已知空间三个点,解算外接圆圆心坐标,C++编程实现

    struct PT3 { double x, y, z; }; int solveCenterPointOfCircle(std::vector<PT3> pt, double cente ...

  4. [LeetCode] Longest Repeating Character Replacement 最长重复字符置换

    Given a string that consists of only uppercase English letters, you can replace any letter in the st ...

  5. 详解C#中的反射

    反射(Reflection) 2008年01月02日 星期三 11:21 两个现实中的例子:1.B超:大家体检的时候大概都做过B超吧,B超可以透过肚皮探测到你内脏的生理情况.这是如何做到的呢?B超是B ...

  6. DataTable转List

    Invoke : DataTableToList<City>.ConvertToModel(ds.Tables[0]).ToList<City>(); using System ...

  7. UICollectionViewCell 网格显示数据

    using System; using System.Collections.Generic; using Foundation; using UIKit; namespace ddd { publi ...

  8. 18. class

    Class 基本用法 class n { constructor(x,y) { this.x = x; this.y = y; console.log(x,y) } proint() { consol ...

  9. 10 Symbol

    Symbol 书中讲了2部分. Symbol() Symbol 属性值. 完全两种画风的东西. 1. Symbol 首先他是一种全新的值. 不属于以前的任何一种 ES6引入了一种新的原始数据类型Sym ...

  10. python网络编程

    Socket是网络编程的一个抽象的概念. 通常我们用一个Socket表示"打开了一个网络链接",而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可. 套 ...