Generics Types 泛型学习笔记<三>

Generics Types 泛型学习笔记<三>

作者:冰云
时间:
2004-02-29
联系:
icecloud(AT)sina.com
Bloghttp://icecloud.51.net

    真不好意思,这么久才提交上来,有些事情耽搁了。

8 类文字?Class Literals)作为运行时类型记号(Type Tokens

1.5中,java.lang.Class是泛型的,即有一个类型参数T。如String.class,的类型就是Class<String>。这样的好处是,当你使用 reflect构造一个类的时候,可以得到更精确的类别而不是泛泛的Object

 

 

    Collection <EmployeeInfo> emps =

       sqlUtility.select(EmployeeInfo.class, "select name,id from emps");

   

    public static <T> Collection<T> select(Class<T> c, String sqlStatement) {

       Collection<T> result = new ArrayList<T>();

       // run sql query using JDBC

       for (/* 遍历ResultSet */){

           T item = c.newInstance();

           /* set all item's fields using reflection*/

           result.add(item);

       }

       return result;

    }

 

 

上面的这个select方法适合于所有的类。这样就免去了类型转换。

 

Note 10: Class作为运行时的记号是个很有用的技巧。在新的annotation API中广泛的应用了这种技术。

文:This technique of using class literals as run time type tokens is a very useful trick to know. It is used extensively in the new APIs for manipulating annotations.

 

9 通配符更多的作用

 

来看下面的例子

 

   

    public interface Sink<T>{

       flush(T t);

    }

   

    public static<T> T writeAll (Collection<T> coll, Sink<T> sink){

       T last ;

       for(T t : coll){

           last = t;

           snk.flush(last);

       }

       return last;

    }

   

    Sink<Object> s;

    Collection<String> cs;

    String str = writeAll(cs,s); // 非法调用

 

 

由于调用时,编译器无法确定<T>是什么类型,String还是Object,所以会错误。根据前面的知识,可以使用通配符类型作如下修改:

 

   

    public static<T> T writeAll (Collection<? extends T> coll, Sink<T> sink)

 

    Sink<Object> s;

    Collection<String> cs;

    String str = writeAll(cs,s); // 调用合法,但返回值是Object

 

 

<? extends T>根据Sink<Object>TObject类型,返回的T不能安全的给String类型。

这时,就引入了另一种通配符类型:下限通配符(lower bound):<? super T>

 

   

    public static<T> T writeAll (Collection<T> coll, Sink<? super T> sink)

    String str = writeAll(cs,s); // OK

 

 

 

Note 11: 下限通配符? super T表示一个未知类型是T的超类型,就象? extends T表示未知类型是T的子类型一样

文:The solution is to use a form of lower bounded wildcard. The syntax ? super T denotes an unkmown type that is supertype of T. it is the twin of the bounded wildcard we use ? extends T to denote an unknown type that is a subtype of T.

 

文中后又举了一个例子,是关于Collection中,元素比较大小的方法。例如在TreeSet中,应该提供一个Comparator来比较TreeSet中的元素顺序。并在构造函数中接受。

 

   

        public interface Comparator<T>{

       int compareTo(T fst, T snd);

    }

   

    TreeSet(Comparator<E> c);

 

 

我们提供Comparator<String>可以正常的比较,然而,如果提供Comparator<Object>,也应该正常的工作。因此,TreeSet构造函数应该修改为:

 

   

    TreeSet(Comparator<? super E> c);

 

 

同样,在Collection中的max方法,返回一个最大值。这些元素必须实现Comparable接口。Collection.max(Comparable<Object>)应该能够工作。而对于 class Foo implements Comparable<Object>如果提供Comparable<Foo>,应该也能够正常运行。因此,这里也引入下限通配符。

 

   

    // T必须是实现Comparable

    public static <T extends Comparable<T>>

           T max(Collection<T> coll);

   

    // T还应该能够接受一个实际的类型,所以修改为

    public static <T extends Comparable<? super T>

           T max(Collection<T> coll);

 

 

那么,如何正确的使用通配符呢?

 

Note 12: 一般的,如果你的API仅仅使用类型参数T作为参数,它应该利用下限通配符;相反,如果你的API返回T,你应该用上限通配符给你的客户端更多地便利。

文:In general if you have an API that only uses a type parameter T as an argument, its uses should take advantage of lower bounded wildcard. Conversely, if the API only returns T, youll give your clients more flexibility by using upper bounded wildcards.

 

通配符捕获,Wildcard capture

 

下面的例子应该已经很明确的不能执行:

 

   

    Set<?> unknownSet = new HashSet<String>();

    ...

    public static <T> void addToSet(Set<T> s , T t);

    addToSet(unknownSet, "abc"); // 非法调用

 

 

由于unknownSet是未知类型,因此不能接受任何实际的类型。如String。考虑下面的代码:

 

   

    class Collections{

       <T> public static Set<T> unmodifiableSet(Set<T> set);

    }

    Set<?> s = Collections.unmodifiableSet(unknownSet); // works!

 

 

看起来这段代码不应该被允许,但是它是合法的。这是因为通配符捕获原则。

 

Note 13: 这种情形出现的很频繁,因而有一个特殊的规则允许这样的代码出现,这被证明是安全的:通配符捕获,允许编译器判断未知通配符类型作为类型参数的范型方法。通配符捕获仅仅允许那些在方法参数列表中出现一次的类型参数。

文:Because this situation arises relatively frequently, there is a special rule that allows such code under very specific circumstances in which the code can be proven to be safe. This rule, known as wildcard capture, allows the compiler to infer the unknown type of a wildcard as a type argument to a generic method. Wildcard capture is only allowed if the type parameter being inferred only appears once in the methods argument list.

 

 

10 转换旧代码到范型

 

这称之为范型化(generifying)。如果要转换旧代码到范型,请仔细的考虑如何修改。如java.util.Collection

 

 

   

    public interface Collection {

       public boolean containsAll(Collection c);

       public boolean addAll(Collection c);

    }  

    public interface Collection<E> {

       public boolean containsAll(Collection<E> c);

       public boolean addAll(Collection<E> c);

    }

 

这是类型安全的,但是并不兼容旧代码。因为范型代码只能接受E类型的Collectioni

    必须考虑到,addAll能够添加任何E的子类型。containsAll也要能够接受不同的类型等等。

 

    9节提到了max方法。

 

   

    public static <T extends Comparable<? super T>

           T max(Collection<T> coll);

 

 

这存在一个问题,就是和旧的代码无法吻合:

 

   

    public static Comparable max(Collection coll); //

    public static Object max(Collection coll); //

 

 

    通过显式声明一个超类,可以强迫他们一致。并且,我们知道,max仅从他的输入Colelction读取,所以适用于任何T的子类,修改如下:

 

   

    public static <T extends Object & Comparable<? super T>

           T max(Collection<? extends T> coll);

 

 

    这种情况比较少见,但是如果设计一个library,就应该准备认真考虑转换他们。

   

Note 14: 转换一套既有的API时,你必须确定范型API不会过度的限制,并且继续支持原来的API。。

文:When converting existing APIs, you should think carefully to make certain that the generic API is not unduly restrictive and continue to support the original contract of the API.

 

另一种需要当心的问题是:返回值协变(covariant returns)。即改进(refine)子类方法的返回值。旧代码如下:

 

   

    public class Foo {

       public Foo create) {} // 工厂方法

    }

    public class Bar extends Foo {

       public Foo create() {} // 实际上建立的Bar

    }

 

 

考虑返回值协变的优点:

 

   

    public class Foo {

       public Foo create) {} // 工厂方法

    }

    public class Bar extends Foo {

       public Bar create() {} // 实际上建立的Bar

    }

 

 

Note 13: 这种特性并不被JVM直接支持,而是被编译器所支持。

文:The JVM does not directly support overriding of methods with different return types. This feature is supported by the compiler.

 

    在此我引用我朋友udoo的一段话作为总结: http://udoo.51.net

Java 1.5中很重要的部分就是genericsjava generics的诞生大概与.net的逼迫有关系,之前的多个版本从未看到有如此大的变化。

java这种强类型的语言中,type casting是很难控制的一个事情,编译器可以在编译时解决一部分,在运行时刻遇到这种问题,是程序潜在的隐患。reflection技术是解决应用的灵活配置的一个重要手段,但只能在运行时刻才能知道究竟是哪个类,可能会有casting的问题。

C++中的模板技术的目的是提供快捷、易于使用的工具箱,STLc++编程必须掌握的技术。java generics看起来与c++模板很象,但通过这篇文章我们知道,java generics更着重在类型检查上,可以去掉看起来很难猜测的强制类型转换,大概只有程序编写者自己最清楚到底这里是什么类型。使用java generics,程序可能会更好读一些,也降低了运行时刻的风险,但这里提到的几个概念和技术,却也并不易于理解(文中也有几处笔误)。theserverside.com对于是否需要这种技术有争论,不管怎样,又给我们提供了一个手段,需要的时候是用的上的。

 

    花了3天看完的,花了1个星期才写完。真是费力啊。感谢各位一直捧场支持。有什么问题欢迎来我blog讨论.

    


版权声明:
本文由冰云完成,首发于CSDN,作者保留中文版权。
未经许可,不得使用于任何商业用途。
欢迎转载,但请保持文章及版权声明完整。
如需联络请发邮件:icecloud(AT)sina.com


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
### 回答1: Java 泛型Generics)可以提供编译时类型安全检查以及运行时更强大的类型转换能力。它可以减少代码的编写量,提高代码的可读性和灵活性。使用泛型可以定义一个通用的方法,当传入不同的参数类型时,可以做出不同的动作,例如可以定义一个操作数组的简单方法,无论传入的是什么类型的参数,都能做出正确的操作。 ### 回答2: Java中的"<T>"是一种泛型的表示方式,它可以用于定义一个方法。使用泛型可以使方法在接受不同类型的参数时具有更好的复用性和扩展性。 泛型方法的定义需要在返回值类型之前加上"<T>",表示该方法可以接受类型为T的参数。例如,可以这样定义一个泛型方法: public <T> void printArray(T[] array) { for (T element : array) { System.out.println(element); } } 在这个示例中,方法名为printArray,它接受一个类型为T的数组作为参数,并遍历打印数组中的每个元素。这里的T可以是任何数据类型,例如Integer、String等。 在实际调用该方法时,需要传入与T对应的具体类型的数组。例如,可以这样调用该方法: Integer[] intArray = {1, 2, 3, 4, 5}; String[] stringArray = {"Hello", "World"}; printArray(intArray); printArray(stringArray); 通过使用泛型方法,可以在不同的场景中复用同一个方法,而不需要针对不同的数据类型编写多个重载的方法。这样可以提高代码的可维护性和重用性。 总之,Java中的"<T>"可以用于定义一个泛型方法,使方法能够接受不同类型的参数,在不同的场景中具有更好的复用性和扩展性。 ### 回答3: 在Java中,"<T>"是泛型的表示方式,用于定义一个可以接收任意类型的方法或者类。通过在方法或者类的声明中使用"<T>",我们可以在使用时指定具体的类型。 下面是一个示例,展示了如何使用"<T>"在Java中定义一个方法: ```java public class GenericMethod { // 使用泛型"<T>"定义一个方法,该方法可以接收任意类型的参数,并返回该参数 public static <T> T printValue(T value) { System.out.println(value); return value; } public static void main(String[] args) { // 调用printValue方法,并指定参数为整数类型 int intValue = GenericMethod.printValue(10); System.out.println("返回的整数值为:" + intValue); // 调用printValue方法,并指定参数为字符串类型 String stringValue = GenericMethod.printValue("Hello World"); System.out.println("返回的字符串值为:" + stringValue); // 调用printValue方法,并指定参数为自定义类型 MyClass myObject = new MyClass(); MyClass returnedObject = GenericMethod.printValue(myObject); System.out.println("返回的对象值为:" + returnedObject); } } // 定义一个自定义类 class MyClass { // 省略其他代码... } ``` 在上述代码中,我们定义了一个名为`printValue`的方法,使用"<T>"表示该方法可以接收任意类型的参数。在`printValue`方法内部,我们使用了`System.out.println`语句打印参数值,并返回了该值。 在`main`方法中,我们调用了`printValue`方法次,每次传入不同的参数类型(整数、字符串和自定义类型),并打印了返回的值。 总的来说,通过在Java中使用"<T>"定义一个方法,我们可以实现通用的、可以接收任意类型参数的方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

icecloud

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值