Java泛型是 jdk5.0 以后的版本才有的特性,也是 Sun 公司将 JDK1.5. 更名为 JDK5.0 的一个重要原因,因为历时 5 年的开发,是 JDK1.5 的功能更为健全和完善。
重用度高,安全系数高是Java 的追求的目标,而泛型的运用正是这一个内容的重要体现。在学习 Java 的过程当中 Java 泛型感觉有些晦涩难懂。起先以为自己的泛型学的还不错,可是在很长一段时间研究 Java 泛型毫无进展的情况下,开始有些崩溃了,于是硬着头皮看啊看,终于觉得有点感觉了,所以,今天在这里总结一些 Java 泛型的一些知识,希望以后自己以后开发生涯中能够用上今天的经验,写下来的原因确实很多,我不想在遇到同样的问题而束手无策,而且记忆了有时候并不是十分的可靠,所以我准备了 4 天的时间来写这篇总结。希望能详尽的表述出来,这个非常适用的机制,并方便以后读到这篇文章的读者,能够提出你们对泛型的一些看法,而不仅仅是一个“顶”字,我也相信你们有比这更好的学习泛型的经验和教训。
什么是泛型? 泛型实质上是叫做泛型程序设计。运用泛型意味着编写的代码可以被很多类型不同的对象所 重用 。
为什么使用泛型?泛型是一种全新的程序设计手段,使用泛型的机制编写的程序代码,要比那些杂乱的使用Object 变量然后进行强制类型转化的代码明显的具有以下特点: 1 更好的安全性 2 更好的可读性。
了解这个两个问题才能从根本上了解泛型的好处,也才能真正的了解甚至熟练的运用Java 泛型。
先前受到c++ 模板的影响,所以一时还难以转过弯来。后来发现 C++ 的模板比 Java 的泛型做得好一些。一般情况下, Java 的特点是“懒加载”(源自 Hibernate 中的 lazy 这里只是为了说明),即运行时才动态的调用方法和生成新的类(除了 static 块和域)。所以 Java 并不知道你所指的泛型到底是什么类型。 Java 是一门纯面向对象的语言,所有的东西必须用类来表示(除了基本的数据类型外),所以类型可以泛化但是不能不让编译器始终不知道你所要泛化的东西是何种类型。因此有一定 C++ 基础的人千万要将模板和 Java 的泛型区分开来。
泛型最初的目的是希望类或者方法能够具备更加广泛的表达能力。了解泛型最主要的是要了解泛型的边界,以及它可以用来做什么,不能够做什么。我想这点是最重要的。
在此之前,我们用的最多的就是多态。所以经常性的让类来持有Object 类型的对象。
package cn.ccsu.cooole.generics;
/**
* @author CoolPrince
* 这里讲的是一个类持有某一个类的对象本案例中用的是代理的机制
* 即Holder_1类持有AObject的对象ao;
* 这个通常情况下只能持有单个对象的类
*/
public class Holder_1 {
private AObject ao = null ;
public Holder_1(AObject ao) {
this . ao = ao;
}
AObject get(){ return ao ; }
}
class AObject{}
在举个例子(解释详见注释部分):
package cn.ccsu.cooole.generics;
/**
*
* @author CoolPrince
* 描述:此案例讲的是以前我们所用到过的动态绑定,此时只用了一个Holder_2的对象
* 就实现了存储两个不同的类型的对象的功能。
* 有些情况下我们主要运用泛型是将其运用到容器当中,告诉指定容器要持有什么样类型 * 的对象 由编译器来保证类型的正确性,举个简单的例子 List
* new ArrayList
*/
public class Holder_2 {
private Object a = null ;
public Holder_2(Object a ) { this . a = a; }
public void set(Object a){ this . a = a; }
public Object get(){ return a ; }
public static void main(String[] args) {
Holder h = new Holder();
Holder_2 h2 = new Holder_2(h);
print ( "Test Holder result is :" + h2.get());
String str = new String( "Name is \" String\"" );
h2.set(str);
print ( "Test String result is :" + h2.get());
}
private static void print(Object o){
System. out .println(o);
}
}
class Holder{
public String toString(){ return "Name is \"Holder\"" ;}
}
以上便是使用Object 数据类型的来进行动态绑定,但是与其指定为 Object 还不如暂时不指定类型,然后决定具体使用何种类型。
下面就真正开始归纳泛型的基本用法和简要的总结:
package cn.ccsu.cooole.generics;
/**
*
* @author CoolPrince
* @param
* 泛型示例 主要是看简单泛型的基本语法格式
*/
public class Holder_3
public Holder_3(T a){ this . a = a;}
public T get(){ return a ;}
public void set(T a){ this . a = a;}
public static void main(String[] args) {
Holder h = new Holder();
Holder_3
System. out .println( h3 );
Holder hh = h3 .get();
System. out .println(hh);
/* h3 .set("cooole"); //h3已经限定了其类型为Holder所以其他类型
h3 .set(new Integer(1));//比如String、Integer还是其他非Holder类 型
都是不允许传进来类型参数的
*/
}
private T a ;
}
Java泛型的核心概念:告诉编译器像使用什么类型,然后编译器帮你处理一切细节。在使用泛型时只需要指定它们的名称和类型即可。
在通常的系统开发当中,我们一定会遇到经常使用一次方法调用,就能返回多个对象的情况。但是return 只能返回一个对象。因此解决办法就就是:创建一个对象用这个对象来持有想要返回的多个对象。当然在每次需要的时候就会专门创建这样的类。万一系统庞大,那么就会出现各种各样的问题,类就会过于庞大,同时也不能很好的体现 OOP 的思想。有一种好的设计思想就是利用泛型来一次性来解决问题,同时也可以利用泛型来在编译期间进行检查确保类型的安全,同时泛型也不是十全十美的会有一定的性能下降。
在学习泛型的时候要注意到元组的问题,实质上在通常的情况下可以这样来理解,元组就是类型的列表外加<> 。
泛型也可以运用到接口上这样的接口称为泛型接口。
public interface GenericsInterface
Java泛型“基本类型无法作为类型参数”的局限性已经是众所周知了,不过从 J2SE5
就具备了自动打包和解包的功能,所以基本的数据类型会也应该转换为对应的对象类型,再进行下一步的处理。
泛型到现在的示例为止都是运用于整个类。但是从实际开发的过程中的规范或者说是好的习惯来看, 在能力的范围内,能用泛型方法代替泛型类的就应该尽量代替 。所以在这里就要讲到泛型方法的基本语法结构和一些必须非常熟悉的基本原理,这样才能站在更高的层次上来理解Java 泛型。
先介绍语法:
package cn.ccsu.cooole.generics;
/**
* @author CoolPrince
* 本案例的输出情况:
* SampleName: String | Name: java.lang.String
* SampleName: Float | Name: java.lang.Float
* SampleName: Integer | Name: java.lang.Integer
* SampleName: Integer | Name: java.lang.Integer
* 从输出情况可以了解到两个重要的点:
* 1:从i0和i1这两个地方我们就能很清晰地看到: Java泛型“基本类
* 型无法作为类型参数”的局限性已经是众所周知了,不过从J2SE5就具
* 备了自动打包和解包的功能,所以基本的数据类型会也应该转换为对应
* 的对象类型,再进行下一步的处理。
* 2:定义泛型方法只需要将泛型 参数列表 至于返回值之前
*/
public class GenericsMethods {
// 简单的示例输出对应的类名
public
System. out .println( "SampleName: " + x.getClass().getSimpleName()
+ " | Name: " + x.getClass().getName());
}
public static void main(String[] args) {
GenericsMethods gm = new GenericsMethods();
String s = "cooole" ;
int i0 = 23;
Integer i1 = 13;
gm.method(s);
gm.method(10.F);
gm.method(i0);
gm.method(i1);
}
}
值得特别注意的是: 如果static 方法需要使用泛型能力,就必须使其成为泛型方法。
当使用泛型类时,必须在创建对象的时候指定参数的值,而使用泛型方法时,通常不必指明参数的类型,因为编译器有个功能叫做:类型参数推断,此时编译器会为我们找出具体的类型。
类型参数推断举例:
package cn.ccsu.cooole.generics;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* @since 2009 - 7
* @author CoolPrince
* @describe :
* 该示例展示的是类型参数推断的一个简单的示例
* 以及泛化方法的基本形式
* 泛型方法使得该方法能够独立于类而产生变化,先辈给的一个经验就是:
* 无论何时何地,只要你能做到的,就应该使用泛型的方法。也就是说能够将
* 用泛型方法取代整个类的就应该使用泛型方法。这样做会使得事情更加清晰明了。
* 另外值得注意的是:对于一个static的方法而言,无法访问泛型类的类型参数,
* 所以如果static方法需要使用的能力,就必须使其成为泛型方法。
* 就类型参数推断而言,那这个例子来讲,假设用户不知道GuessParaType中的方法
* 或者说是隐含的功能,花的时间也没有减少。工作效率也没有得到提升
*
* 需要特别特别注意的是: 类型推断只对赋值操作有效,其他时候并不起作用
* (比如作为参数传递给方法)。 所以function(GuessParaType.list())
* 的用法编译时候无法获得通过。
* 如果是定义在该方法类的内部就需要使用this而静态方法则需要添加类名。
*/
public class GuessParaType {
public static
return new HashMap
}
public static
return new ArrayList
}
public static
return new LinkedList
}
public static void function(List extends Holder> s){
System. out .println(s.getClass());
}
public static void main(String[] args) {
@SuppressWarnings ( "unused" )
Map
@SuppressWarnings ( "unused" )
List
@SuppressWarnings ( "unused" )
LinkedList
System. out .println(sls.getClass());
//function(GuessParaType.list());//此中用法不能用上面有说明
function (GuessParaType.
}
}
class Holder_T extends Holder{}
和c++ 一样 Java 也具有可变参数列表:
package cn.ccsu.cooole.generics;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author CoolPrince
* @描述 :
* 可变参数的泛型
* 输出:
* [, a, b, c, d, e, f, g, h, j, k, l, m, n, o, p, q, r, s, t, u, * v, w, x, y, z]
*/
public class GenericVarargs {
public static
List
for (T item : args )
{
t.add(item);
}
return t;
}
public static void main(String[] args) {
List
System. out .println(list);
}
}
泛型运用于接口当中,只要泛型方法在参数声明为接口,都可以用到该接口的泛型实现的类的对象。方法的泛型在运用于接口的同时也可以运用到内部类当中来,甚至是匿名的类当中。比如一个非常经典的例子银行出纳员服务的问题(这是一个典型的泛型匿名类):
return new Generator
public Teller next() {
return new Teller();
}
};
"好你个擦除,尽然让我这番折腾。 "在学习泛型一章的时候,最感到晕的就是擦除,这个是 Java 泛型的难点,也是泛型的重点的重点。竟然没弄清这个感觉就是学习 Java 遇到了一个大大的障碍。学这个的时候很晕,可以这样说吧,以前很少见到这种情况,主要是没有什么实践经验,这个内容很抽象,初一看,看不出什么名堂,但是硬着头皮学完了这个泛型,就可以说才真正的开始了解(我的实战项目很少,到目前为止觉得自己连菜鸟还不算,也就是一个还为成鸟的蛋,所以不敢大言熟练)。 " 好你个擦除! " 。
首先擦除初一看很神秘,在一看的确很神秘,作死的看还是能解开她的一层神秘的面纱。
探秘擦除:
1:了解先决条件。什么是擦除?在使用泛型的这个场景的时候,任何具体的类型信息都无法看到,而你唯一知道的就是你在使用一个对象。这个是我个人的理解,可以想象一下,为什么叫擦除。从这问题出发,我个人觉得,就是这个 T 会被别的类型反复的覆盖,主要原因是 Java 运行的是 .class 字节码文件。而且又是一门纯面向对象的语言。因而会产生引用的现象,然而有些时候我们只需要运用到这个通用的方法(只是参数是不定的可以被反复运用),在同一个方法中的同一个参数列表的位置,每执行一次对其后续的传入对象的影响,每个对象的引用传进来时也不受先前的对象的引用的影响,所以就把参数列表的参数进行一次擦除。而在这个过程当中参数列表中的参数只是占了个符号进行标识。(这个是我个人的理解,术语还欠专业,只是为了讲明白这个意思)。
2:分析需求。为什么要用擦除?擦除的目的实质上时很简单了,从 JDK1.4 到 JDK1.5 的版本内容飞跃, Sun 公司历时 5 年将类库和虚拟机进行完善的这个角度上来分析,也可以从将 JDK1.5 改名为 JDK5.0 。或者从微软的 windows 操作系统来对比。就不难看出,擦除的最初目的就是为了 向后兼容 。因为此前也开发了不少产品,擦除的目的就是让泛型和非泛型进行结合。而擦除的机制使得先后迁移或者说是兼容成为可能,到目前为止也是最好的一种解决方案,以后的话或许还是最好的,但是就不敢说绝对最好了,毕竟技术还在前进。
3:分析优缺点缺点。擦除有哪些好的理由让它存在比成为设计类库的一个重要机制?还有擦除有哪些实际的问题?先回答第一个:擦除的优点上面 2 有讲到,主要从非泛化到泛化的转变过程,以及在不破坏现有的类库的情况下,将泛型融入到 Java 里边来。擦除使得现有的非泛化的客户端代码能在不改变的情况下继续使用,直到客户端重写了这些代码。例子很容易举,假设有家银行系统是 Java 的非泛型写的,但是它系统又要运行,对于银行的客户或者其他不同权限的系统使用者来讲,要不改变原来的东西,但是在升级的时候将代码重写时就适当运用泛型进行一个过渡。再是第二个问题:擦除的代价也是显而易见的,泛型不能显示的运用于运行时的类型当中。因为此时所有的关于参数类型的信息已经丢失。
讲到这先看个具体示例:验证擦除后的类型信息的获取。
/**
* @author CoolPrince
* @since 2009 - 7
* @描述 :擦除的神秘之处
* output:~
* c1 = class java.util.ArrayList
* c2 = class java.util.ArrayList
* c1== c2?true
* class java.util.ArrayList
* :~
* 形式可以是:Class c2 = new ArrayList
* 虽然可以申明为:ArrayList.class
* 但是不能为 Class c2 = new ArrayList
* 也不能为ArrayList
*/
public class ErasedTypeEquivalence {
/**
* @param args
*/
public static void main(String[] args) {
Class c1 = new ArrayList
Class c2 = new ArrayList
print ( "c1 = " + c1);
print ( "c2 = " + c2);
print ( "c1== c2? " + (c1== c2));
print (ArrayList. class );
}
public static void print(Object o){
System. out .println(o);
}
}
从上面的注释可以看到,c1 和 c2 的 class 都是 java.util.ArrayList ,所以关于参数 String 和 Integer 的类型信息已经丢失。
在c++ 模板当中会有这样的几句话:
template
{
T object;
public mothod(){object.f();}
};
在c++ 中只管定义,在调用的时候会自动检查 T 类型是否拥有 f() 方法如果没有就会发出异常。
而在Java 中不能支持这种形式,编译器报错。这个也是 c++ 在类型的参数化方面比 Java 强大的主要原因之一。
那么Java 中如何实现上述的 C++ 内容呢?具体请看代码及其注释。
package cn.ccsu.cooole.generics;
/**
*
* @author CoolPrince
* @param
*
* 在这个示例当中首先要告诉编译器T是什么类型,下面两句
* FunMethodExtendsClass fmec= new FunMethodExtendsClass();
* new HasFunMethod
* 其中传入参数fmec告诉编译器其传的是FunMethodExtendsClass
*
* 如果将public HasFunMethod(T x){object = x;}去掉的话
* 并且将构造器改为默认的,那么调用hf.testFun()编辑器不能检查到错误
* 但是运行的时候就会有空指针异常(java.lang.NullPointerException)
*/
public class HasFunMethod
private T object ;
public void testFun() {
object .fun();
}
public HasFunMethod(T x) {
object = x;
}
public static void main(String[] args) {
FunMethodExtendsClass fmec = new FunMethodExtendsClass();
HasFunMethod
new HasFunMethod
hf.testFun();
fmec.fun();
}
}
class FunMethodClass {
public void fun() {
System. out .println( "fun()" );
}
}
class FunMethodExtendsClass extends FunMethodClass {}
验证泛型数据的擦除后类型的一致性:
package cn.ccsu.cooole.generics;
import java.util.ArrayList;
import java.util.List;
/**
* @author CoolPrince
*
* @param
*
* 将对象传入List 尽管编译器无法知道有关create()中的T的任何类型的信息,
* 但是它仍然可以确保 * 你放位置到list的对象具有T类型,使其适合
* ArrayList
* 编译器仍然可以确保在方法或者类中 使用的类型的内部一致性。
*
*/
public class FillList
public List
List
for ( int i = 0; i < n; i++) {
list.add(t);
}
return list;
}
public static void main(String[] args) {
FillList
List
System. out .println(list);
FillList
List
System. out .println(list1);
}
}
class Make {
@Override
public String toString() {
return "make" ;
}
}
既然擦除有类型信息丢失的现象,我们是否有补救措施?答案当然是有。
下面是通过引入类型标签解决判定是否可以动态的实例化类的对象。
package cn.ccsu.cooole.generics;
/**
*
* @author CoolPrince
*
* 输出结果:
* true
* true
* false
* true
*
* 描述:泛型因为擦除的原因其类型信息就已经擦除了,然而在引入类型标签的话,
* 就可以转而使用动态的isInstance()来进行类型的实例化的判断。编译器将确保
* 类型标签可以匹配泛型参数。
*
*/
public class ClassTypeCapture
Class
public ClassTypeCapture(Class
this . kind = kind;
}
public boolean f(Object o){
return kind .isInstance(o);
}
public static void main(String[] args) {
ClassTypeCapture
System. out .println(ctc1.f( new Building()));
System. out .println(ctc1.f( new House()));
ClassTypeCapture
System. out .println(ctc2.f( new Building()));
System. out .println(ctc2.f( new House()));
}
}
class Building{}
class House extends Building{}
既然判定对象的是否可以实例化的问题已经解决下面的问题就是实例化对象的操作。
package cn.ccsu.cooole.generics;
public class ClassInstantiateGenericType {
public ClassInstantiateGenericType() {
}
public static void main(String[] args) {
@SuppressWarnings ( "unused" )
ClassAsFactory
Employee. class );
System. out
.println( "new ClassAsFactory
try {
@SuppressWarnings ( "unused" )
ClassAsFactory
Integer. class );
} catch (Exception e) {
System. out
.println( "new ClassAsFactory
}
}
}
class ClassAsFactory
T x ;
public ClassAsFactory(Class
try {
kind.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
class Employee {
}
这个示例运解决的是一个Java 创建类型实例的一个运用例子,这个是工厂模式。
而工厂的对象就是Class 对象,所以使用类型标签是十分合理的,而此时只需要将 new Intstance()来实例化并创建这个类型的新对象。
Java泛型的参数要求所传递类型的构造器是公有默认的构造器。而上面示例 Integer 类没有默认的构造器。 会报J ava.lang.InstantiationException 的错误。 而这个错误并不是在编译期间捕获的所以不建议使用这种方式,下面是这个示例的改良。
package cn.ccsu.cooole.generics;
/**
*
* @author CoolPrince
* 描述:显示工厂的方法解决Java进行泛型类的实例化的问题。
*
* 主要是限定其类型使得只能接受实现了这个工厂的类
* 此时已经获得了编译期间的检查。所以也是比较稳定和安全的
*/
public class FactoryConstaint {
public static void main(String[] args) {
new Foo2
new Foo2< Widget >( new Widget .Factory());
}
}
interface FactoryInterface
T create();
}
class Foo2
@SuppressWarnings ( "unused" )
private T x ;
public
x = factory.create();
}
}
class IntegerFactory implements FactoryInterface
public Integer create() {
return new Integer(10);
}
}
class Widget {
public static class Factory implements FactoryInterface< Widget >{
public Widget create() {
return new Widget ();
}
}
}
既然显示工厂可以解决此问题,那么还有其他的解决途径没有?模板方法设计模式。
package cn.ccsu.cooole.generics;
/**
*
* @author CoolPrince
* 模板模式解决Java泛型类额实例化问题
* 通过这种方式可以获取类型的信息
*/
public class CreatGenerics {
public static void main(String[] args) {
Createor c = new Createor();
c.f();
}
}
abstract class GenericWithCreate
final T elements ;
GenericWithCreate(){ elements = create();
}
abstract T create();
}
class ClassX{}
class Createor extends GenericWithCreate
@Override
ClassX create() {
return new ClassX();
}
public void f(){
System. out .println( elements . getClass ().getSimpleName());
}
}
泛型在数组中的运用: 一般的解决方案都是在任何想要创建泛型数组的地方都用 ArrayLis 进行替代。这样既能获得数组的行为也能获得由编译器提供的编译器类型的安全。
直接创建数组不会发出任何警告。却也永远无法创建这个确切的数据类型数组,直接进行转化就会抛出异常。
有一种方式。也是唯一的一中方式就是:利创建一个被擦除类型的新数组。然后转型。
具体示例见下文:
private T[] array ;
public GenericsArray( int size ){
array = (T[]) new Object[size];
}
这种形式是不行的尽管它在初学者看来和直观,当其对返回值进行转型的时候就会有一个叫做ClassCastException; 此时实际运行的是 Object[];
因为有了擦除,数组的运行时类型就只能是Object[], 如果我们立即将其转型为 T[], 那么编译期间的实际类型就会丢失,而编译器可能会错过某些潜在的错误检查。所以最好的方式是在集合内部使用 Object[], 然后当你使用数组元素的时候,添加一个 T 的转型。
package cn.ccsu.cooole.generics;
/**
*
* @author CoolPrince
*
* @param
*
* 输出: [Ljava.lang.Object;@de6ced
* 所以还是存在类型的丢失问题,当调用这个gai.getArray()的其他方法的时候
* 编译器也会报告异常。
*/
public class GenericsArray2
private Object[] array ;
@SuppressWarnings ( "unchecked" )
public GenericsArray2( int size){
array = (T[]) new Object[size];
}
@SuppressWarnings ( "unchecked" )
public T[] getArray(){
return (T[]) array ;
}
public void put( int index, T item){
array [index] = item;
}
public static void main(String[] args) {
GenericsArray2
for ( int i = 0;i<10;i++){
gai.put(i, i);
}
System. out .println(gai.getArray());
}
}
难道真的没有其他办法来解决这一难题。
先看看下面的反射再说:
package cn.ccsu.cooole.generics;
import java.lang.reflect.*;
/**
* @author CoolPrince
*
* @param
*
* 输出结果:
* class [Ljava.lang.Integer;
* 0 1 2 3 4 5 6 7 8 9
*
* 类型标记Class
* 尽管@SuppressWarnings("unchecked")压制警告,一旦获得实际的类型就可以进行返回
*
*/
public class GenerArrayWithTypeToken
@SuppressWarnings ( "unused" )
private T[] array ;
@SuppressWarnings ( "unchecked" )
public GenerArrayWithTypeToken(Class
array = (T[]) Array. newInstance (type, size);
}
public void put( int index, T item) {
array [index] = item;
}
public T get( int index) {
return array [index];
}
public T[] getArray(){
return (T[]) array ;
}
public static void main(String[] args) {
GenerArrayWithTypeToken
Integer. class , 10);
for ( int i = 0;i<10;i++){
gawti.put(i, i);
}
Integer [] ia = gawti.getArray();
System. out .println( ia .getClass());
for ( int i = 0;i<10;i++){
System. out .print( " " + ia [i]);
}
}
}
既然Java 的泛型可以如此神通广大,我们有什么方法对其进行一定的限制提高它的性能呢 ? 答案便是边界!
package cn.ccsu.cooole.generics;
import java.awt.Color;
/**
* @author CoolPrince
*
* @param
*
*这个事例主要是分析边界及其继承关系的相关理论。
* 边界实质上就是在泛型上设置限制条件。这个限制强制了所要使用的类型范围
* 但是一个潜在的好处就是可以按照自己定义的边界来调用方法。比如说:
* int weigth(){
* return item.weigth();
* }
*在此时已经保障了inem.weigth()的存在性。 因为这条语句
*
*要求必须实现Weight接口
*这个地方也有个小的策略,就是先将类放到extends的后面然后就是接口口,尽量将方
* 法多的,
*或者是子类放到靠前面一些这样就会有一定的效率提高,其次限制时没有implements
* 而是将
*extends代替,这个是Java的设计者考虑到extends能够更好的表意所以统一采用这种 * 形式
*此时的extends和平时的用法是完全不同的。
*/
public class GenericInheritBaseBounds
public static void main(String[] args) {
@SuppressWarnings ( "unused" )
Solid
System. out .println(sb.getX());
System. out .println(sb. weigth ());
System. out .println(sb.getItem().getClass());
}
}
interface HasColor{java.awt.Color getColor();}
interface Weigth{ int weigth();}
class HoldItem
T item ;
HoldItem(T item){ this . item = item;}
T getItem(){ return item ;}
}
class Dimension{ public int x , y , z ;}
class Colored
Colored(T item){ super (item);}
}
class ColorDimension
ColorDimension(T item) { super (item);}
int getX(){ return item . x ;}
}
class Solid
Solid(T item) { super (item);}
int weigth (){
return item .weigth();
}
}
class Bounded extends Dimension implements HasColor,Weigth{
public Color getColor() {
return Color. black ;
}
public int weigth() {
return 10;
}
}
泛型通配符:
泛型一个主要目标是将运行时可以发现插入不正确的数据类型的错误信息检测移入到编译期。
下面的几个示例就是讨论通配符的相关的问题:主要是总结那些情况下可以用,那些情况下有缺陷,哪些情况下可以进行优化。
首先简述数组的一种特殊的行为:可以向导出类型的数组赋予基类型数组引用。假设有这样一种情况:基类蔬果(Fruit), 其有两个子类苹果( Apple )、桔子( Orange ) , 另外又有苹果类又有一个子类 --- 李子苹果( PlumApple )的杂交苹果。
Fruit [] fruit = new Apple[10]; 那么 fruit[0] = new Apple();
fruit[0] = new PlumApple(); 都是合法的因为他们都是Apple 的子类,然而 new Fruit (),或者是非苹果的子类都会报告数组存储错误的异常。为什么会有这种情况的出现呢?虽然有个 Fruit [] fruit 数组的引用,在编译期间就算你new Orange() 也不会报告异常,但是在运行期间 Java 还是会根据数组的基本机制将其处理成 Apple[], 因此在数组中放入异构型的数组时就会有异常。数组有一个重大的好处就是他可以保留有关他们的包含的对象的类型的信息规则。一般这样说,数组对其持有的对象是有意识,因此在编译期间一定要做检查,否则就会造成滥用的情况的发生。那么用 List
现在假设有另一种情况假设缴入了边界的限制又会怎样呢?形式如 List extends Fruit> = new ArrayList
说白了泛型要知道具体的持有的对象的参数,至少也要通过反射来告诉编译器是个什么东东,在泛化的过程中利用类型标签来实现工厂模式,模板模式来解决泛型是非常有用的。
既然有继承的统配符是否有超类的通配符来进行类型的限定?
超类的通配符的语法 super SuperClassName>, s extends SubClassName> 是上界,而超类的通配符就是我们所说的下界。另外还有个叫无界 >, 下面也将讲到。
package cn.ccsu.cooole.generics;
import java.util.Arrays;
import java.util.List;
/**
*
* @author CoolPrince
*
* 输出:
* fun1() f.getClass(): class cn.ccsu.cooole.generics.Apple
* fun1() a.getClass(): class cn.ccsu.cooole.generics.Apple
* fun2() f.getClass(): class cn.ccsu.cooole.generics.Fruit
* fun3() f.getClass(): class cn.ccsu.cooole.generics.Fruit
* fun3() a.getClass():class cn.ccsu.cooole.generics.Apple
*
* 先从输出来对其原理进行深层次分析:
* fun1()中调用的readExact();参数已经进行类限制,并没有进行通配
* 此时编译编译器就会根据实际的类型进行调用和传参,只是此时在编译期间
* 进行强制的类型检查,避免了许多的错误,而不会出现可以是任何事物的
* 情况而使得编译器不知道如何处理。
* 在fun2()中由于Reader
* 的限制而无法将Apple 作为类型进行检查,此时的注释部分是不能运行的(编译期
* 间就有错误)。
* 在fun3()中则运用了?extends进行通配 此时只要是Fruit的子类就可以传进来只
* 要将其返回值为Fruit就行,其实此时已经是Apple的引用。故而有上述的显示结果。
*/
public class GenericReading {
static class CovariantReader
T readCovariant(List extends T> list) {
return list.get(0);
}
}
static class Reader
T readExact(List
return list.get(0);
}
}
static List
static List
static void fun1() {
@SuppressWarnings ( "unused" )
Apple a = readExact ( apples );
Fruit f = readExact ( fruits );
f = readExact ( apples );
System. out .println( "fun1() f.getClass(): " + f.getClass());
System. out .println( "fun1() a.getClass(): " + a.getClass());
}
static void fun2() {
Reader
Fruit f = fruitReader.readExact( fruits );
System. out .println( "fun2() f.getClass(): " + f.getClass());
// Fruit a = fruitReader.readExact(apples);
}
static void fun3() {
CovariantReader
Fruit f = crf.readCovariant( fruits );
Fruit a = crf.readCovariant( apples );
System. out .println( "fun3() f.getClass(): " + f.getClass());
System. out .println( "fun3() a.getClass():" + a.getClass());
}
public static void main(String[] args) {
fun1 ();
System. out .println();
fun2 ();
System. out .println();
fun3 ();
}
static
return list.get(0);
}
}
>可以笨认为是一种装饰,但是它仍然具有一定的实际价值,在实际看来: " 我想用Java 的泛型编写这段代码 , 我这里并不是要用原生类型,但是在当前的情况下泛型可以持有任何类型。 "
泛型通配符,以List 为例 List
public Holder_3(T a){ this . a = a;}
public T get(){ return a ;}
public void set(T a){ this . a = a;}
public Holder_3(){}
public static void main(String[] args) {
Holder h = new Holder();
Holder_3
System. out .println(h3);
Holder hh = h3.get();
System. out .println(hh);
/*h3.set("cooole"); //h3已经限定了其类型为Holder所以其他类型
h3.set(new Integer(1));//比如StringInteger还是其他非Holder类 型都是不允许传进来类型参数的
*/
}
private T a ;
}
package cn.ccsu.cooole.generics;
/**
*
* @author CoolPrince
* 捕获转化举例:
* 先看输出结果:
* Integer
*Integer
*Object
*Double
* 再看 main方法:
* Holder_3 h3t = new Holder_3
* 定义一个Holder_3 h3t 类型参数为Integer
* 此时调用方法method1();就会输出类型名:Integer
* 调用方法menthod2()的时候里面传进来方法method1 的参数
* 此时就会被擦除从而带入到method2中,故而会有上面的结果。
* 这个过程通过参数的传递来进行捕获转化。此时的通配符就派上了用场
*
*/
public class CaptureConversion {
static
T t = holder.get();
System. out .println(t.getClass().getSimpleName());
}
static void method2(Holder_3> holder){
method1 (holder);
}
@SuppressWarnings ( "unchecked" )
public static void main(String[] args) {
Holder_3 h3t = new Holder_3
method1 (h3t);
method2 (h3t);
Holder_3 h3t2 = new Holder_3();
h3t2.set( new Object());
method2 (h3t2);
Holder_3> wildcard = new Holder_3
method2 (wildcard);
}
}
泛型没有消除对对象转型的需要。
package cn.ccsu.cooole.generics;
public class FixedStack
private int index = 0;
private Object[] storage ;
public FixedStack( int size){
storage = new Object[size];
}
public void push(T item){
storage [ index ] = item;
index ++;
}
@SuppressWarnings ( "unchecked" )
public T pop(){
return (T) storage [ index --];
}
public static void main(String[] args) {
FixedStack
for ( int i = 1;i<=10;i++){
ii.push(2*i);
}
ii.pop();
System. out .println(ii.pop().getClass().getSimpleName());
}
}
泛型没有消除对对象转型的需要,从pop ()中的强制转化,没转化前还是 Object 。
实现参数化接口:
interface Payable
class Emplyee implements Payable
c lass Hourly extends Emplyee implements Payable< Hourly >{}
在这个实例当中最后一条语句由于擦除的原因,这两个变体会成为相同的借口而产生冲突。如果将 Payable
对于泛型重载,由于擦除的缘故,重载类型的方法将产生类型相同的签名,当被擦除的参数不能产生唯一的参数列表时,必须提供有明显区别的方法名。
Java自限定类型:
package cn.ccsu.cooole.generics;
/**
*
* @author CoolPrince
* 自限定的基本语法如同:
* class SelfBounded
* 自限定限制只能强制作用于继承关系,如果使用自限定就必须了解这个
* 类所用的类型参数将与使用这个参数的类具有相同的基类型。
*
*/
public class SelfBounding {
public static void main(String[] args) {
A a = new A();
System. out .println(a.getClass());
a.set( new A());
System. out .println(a.getClass());
a = a.set( new A()).get();
System. out .println(a.getClass());
B b = new B();
System. out .println(b.getClass());
C c = new C();
c = c.setAndGet( new C());
System. out .println(c.getClass());
}
}
class SelfBounded
T element ;
SelfBounded
element = arg ;
return this ;
}
T get(){ return element ;}
}
class A extends SelfBounded{}
class B extends SelfBounded{}
class C extends SelfBounded
C setAndGet(C arg){
set(arg);
return get();
}
}
其实Java 的自限定类型的价值在于他们可以产生协变参数类型——方法参数类型会随着子类而进行变化(类型雨来与严格也就是擦除第一边界所引起的),自限定类型还可以产生于基类类型相同的返回类型。
package cn.ccsu.cooole.generics;
/**
*
* @author CoolPrince 输出结果:
* cn.ccsu.cooole.generics.ImplmentsGetter@61de33
* cn.ccsu.cooole.generics.ImplmentsGetter@61de33
*
* cn.ccsu.cooole.generics.ImplmentsGetter_1@ca0b6
* cn.ccsu.cooole.generics.ImplmentsGetter_1@ca0b6
*
* 此时从输出可以见到我们所谓的动态绑定的效果
* 这个时候就可以看到参数协变的效果。
* 和普通的继承一样了,不信得话可以讲类型数去掉后在去观察结果
* 已经重载了方法get();
*/
public class GenericsAndReturnType {
public static void main(String[] args) {
ImplmentsGetter ig = new ImplmentsGetter();
test (ig);
System. out .println();
ImplmentsGetter_1 ig1 = new ImplmentsGetter_1();
test (ig1);
}
static void test(Getter g) {
@SuppressWarnings ( "unused" )
Getter res = g.get();
System. out .println(res);
@SuppressWarnings ( "unused" )
GenericGetter gg = g.get();
System. out .println(gg);
}
}
interface GenericGetter
T get();
}
interface Getter extends GenericGetter
}
class ImplmentsGetter implements Getter {
public Getter get () {
return this ;
}
}
class ImplmentsGetter_1 extends ImplmentsGetter {
}
动态类型安全问题分析:
受检查的容器在你试图插入类型不正确的数据时就会,抛出ClassCastExcption, 这与泛型之前的原生的容器形成了鲜明的对比,对于后者来说,当你将对象从容器中取出的时候才会通知你出现了问题,此时尽管你直到出现了问题但是你不知道问题在哪,如果使用受检查的容器就会在编译期间发现插入不良的对象。
既然泛型在编译期间的安全问题自行可以解决,那么在运行期间的异常又该如何处置呢?
可以采用这种方式:
Class TestClass
public void f() throws E{}
}
当然参数E 也可以是其他自定义继承非 Throwable 的类。
其他的和上面的基本相同。
在擦除忘记其比本类型,所以泛型类不能直接继承自一个泛型的参数。
故而产生了另外一种解决方案,说白了就是用类来继承混合接口的实现,来实现混型的机制。
例子很简单,只是代码稍微有些长:
package cn.ccsu.cooole.generics;
import java.util.Date;
/**
* @author CoolPrince
*/
public class Mixins {
public static void main(String[] args) {
Minin minin1 = new Minin(), minin2 = new Minin();
minin1.set( "测试 String 1" );
minin2 .set( "测试 String 2" );
System. out .println(minin1.get()+ " " +minin1.getSerialNumber()+minin1.getStamped());
System. out .println( minin2 .get()+ " " + minin2 .getSerialNumber()+ minin2 .getStamped());
}
}
interface TimeStamped{ public long getStamped();}
interface SerialNumbered{ public long getSerialNumber();}
interface Basic{
public String get();
public void set(String val);
}
class TimeStampImp implements TimeStamped{
private final long timeStamp ;
TimeStampImp(){
timeStamp = new Date().getTime();
}
public long getStamped() {
return timeStamp ;
}
}
class BasicImp implements Basic{
String value ;
public String get() {
return value ;
}
public void set(String val) {
value = val;
}
}
class SerialNumberedImp implements SerialNumbered{
private static long counter = 1;
public long getSerialNumber() {
return SerialNumber ;
}
private final long SerialNumber = counter ++;
}
/**
*
* @author CoolPrince
* 典型的混型代理
*/
class Minin extends BasicImp implements TimeStamped,SerialNumbered{
private TimeStamped timeStamp = new TimeStampImp();
private SerialNumberedImp serialNumberedImp = new SerialNumberedImp();
public long getStamped() {
return timeStamp .getStamped();
}
public long getSerialNumber() {
return serialNumberedImp .getSerialNumber();
}
}
似乎从上面的这个例子就好像与泛型搭不上边了,在这里先要特别注意泛型是数据类型的一个基本要素,下面介绍一下与泛型或者混型有关的几个基本的设计模式:
1:装饰器模式(这个模式在类库里用的非常多,不知你对装饰器有无了解,其中一个重要的库的主要思想就是装饰器模式,在尚学堂的视频里讲的那个管道就是装饰器模式的一种说法,我也是后来看源码才知道的),那就先不分析那些 Buffered 和 Data ,留到下篇文章讲 I/O 的时候来分析部分源码来讲解装饰器模式。
先看个简单的例子:
package cn.ccsu.cooole.generics.decorator;
import java.util.Date;
/**
*
* @author CoolPrince 这个就是一个典型的装饰器设计模式的运用
* 仔细看是不是一层套一层。
* 装饰器模式是使用的分层对象来态透明的向单个对象中添加责任。装饰器指定
* 包装在最初的对象的周围所有的对象具有相同的接口。某些事物是可以装饰的
* 可以通过将其他类包装在可装饰的对象的四周,来将其分层。所以无论对象是
* 否有装饰你都可以拥有一个可以向对象发送公共消息集。当然装饰类也可以
* 添加新的方法,但是这也是受限的。 混型和装饰器模式及其类似但是装饰器
* 并不需要装饰器的继承结构(运用代理)
*
*/
public class DecorationMode {
public static void main(String[] args) {
TimeStamped t = new TimeStamped( new Basic());
TimeStamped t2 = new TimeStamped( new SerialNumbered( new Basic()));
SerialNumbered s = new SerialNumbered( new Basic());
SerialNumbered s2 = new SerialNumbered( new TimeStamped( new Basic()));
System. out .println(t);
System. out .println(t2.getStamp());
System. out .println(s.getSerialNumber());
System. out .println(s2);
}
}
/**
*
* @author CoolPrince 基类型
*/
class Basic {
private String value ;
public void set(String val) {
value = val;
}
public String get() {
return value ;
}
}
/**
*
* @author CoolPrince 注意 protected这个修饰符表示的是具有包以及继承层次的子类拥有访问权限
* 这个地方Decrator拥有类Basic 属于代理
*/
class Decrator extends Basic {
protected Basic basic ;
public Decrator(Basic basic) {
this . basic = basic;
}
public void set(String val) {
basic .set(val);
}
public String get() {
return basic .get();
}
}
/**
*
* @author CoolPrince 必须拥有构造器 getStamp()是新添加的 这个地方TimeStamped拥有类Decrator 属于代理
*/
class TimeStamped extends Decrator {
public TimeStamped(Basic basic) {
super (basic);
time = new Date().getTime();
}
public long getStamp() {
return time ;
}
private final long time ;
}
class SerialNumbered extends Decrator {
public SerialNumbered(Basic basic) {
super (basic);
}
private static long counter = 1;
public long getSerialNumber() {
return SerialNumber ;
}
private final long SerialNumber = counter ++;
}
Java 并不支持潜在的类型机制,所以看上去不是非常的泛化。在c++ 中即便是一个不知道的类型 T 也可以调用它的方法只不过在传入类型参数的时候会进行检查看是否具有该方法。而 Java 对这个并不支持。它主要还是通过实现接口来继承类来实现这个机制的功能。尽管 Java 缺乏潜在类型的机制,但是还是有多种补救的措施: 反射 。上面也讲过通过反射来进行类型标签。
第二个就是将一个方法运用于序列(略)
适配器模式举例:
package cn.ccsu.cooole.generics;
import java.util.*;
/**
*
* @author CoolPrince
* 适配器模式举例:
* 运行结果:
* Adpter.num = 0
* Adpter.num = 1
* Adpter.num = 2
* Adpter.num = 3
* Adpter.num = 4
* Adpter.num = 5
* Adpter.num = 6
* Adpter.num = 7
* 这个例子主要是通过Collection来进行适配
*
*/
public class AdapterMode1 {
public static void main(String[] args) {
List
Fill2. fill ( new AddableCollectionnAddapter
Fill2. fill (Adapter. collectionAdapter (li), AdpterTmp. class , 5);
for (AdpterTmp i:li){
System. out .println(i);
}
}
}
interface Addable
class Fill2{
public static
for ( int i = 0; i
addable.add(classToken.newInstance());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
class AddableCollectionnAddapter
private Collection
AddableCollectionnAddapter(Collection
this . c = c;
}
public void add(T t) {
c .add(t);
}
}
class Adapter{
public static
return new AddableCollectionnAddapter
}
}
class AdpterTmp{
static int counter = 0;
private final int num = counter ++;
public String toString(){
return "Adpter.num = " + num ;
}
}
这个例子描述了适配器创建了真正泛化的代码。
策略设计模式
package cn.ccsu.cooole.generics;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
/**
*
* @author CoolPrince
* 策略设计模式:
*
*/
public class StrategyModel {
public static
Iterator
T result = null ;
if (it.hasNext()){
result = it.next();
while (it.hasNext()){
result = combiner.combine(result, it.next());
}
}
return result;
}
public static
for (T t:seq){
func.function(t);
}
return func;
}
public static
List
for (T t:seq){
result.add(func.function(t));
}
return result;
}
public static
List
for (T t:seq){
if (pred.test(t)){
result.add(t);
}
}
return result;
}
static class IntegerAdder implements Combiner
public Integer combine(Integer x, Integer y) {
return x+y;
}
}
static class IntegerSubtracter implements Combiner
public Integer combine(Integer x, Integer y) {
return x-y;
}
}
static class BigDecimalAdder implements Combiner
public BigDecimal combine(BigDecimal x, BigDecimal y) {
return x.add(y);
}
}
static class BigIntegerAdder implements Combiner
public BigInteger combine(BigInteger x, BigInteger y) {
return x.add(y);
}
}
static class AtomicLongAdder implements Combiner
public AtomicLong combine(AtomicLong x, AtomicLong y) {
return new AtomicLong(x.addAndGet(y.get()));
}
}
static class BigDeimalUlp implements UnaryFunction
public BigDecimal function(BigDecimal x) {
return x.ulp();
}
}
static class Greaterthan
private T bound ;
public Greaterthan(T bound){
this . bound = bound;
}
public boolean test(T x) {
return x.compareTo( bound ) > 0;
}
}
static class MultiplyingIntegerCollector implements Collector
private Integer val = 1;
public Integer function(Integer x){
val *= x;
return val ;
}
public Integer result() {
return val ;
}
}
public static void main(String[] args) {
List
Integer res = reduce (list, new IntegerAdder());
System. out .println(res);
res = reduce (list, new IntegerSubtracter());
print (res);
print ( filter (list, new Greaterthan
print ( forEach (list, new MultiplyingIntegerCollector()).result());
print ( forEach ( filter (list, new Greaterthan
print ( "" );
}
public static void print(Object obj){
System. out .println(obj);
}
}
interface Combiner
interface UnaryFunction
interface Collector
interface UnaryPredicate
后话: Java 的泛型机制最吸引人的地方就是在使用容器类的地方,泛型的核心是重用和解决一些多态无法解决的问题,泛型的表达意识很强,所以又很好的可读性。泛