`

学习泛型方法的一点注意

阅读更多

一下是网上找的 隔叶黄莺 的文章

前面讲了泛型类的定义,你也可以在一个普通类中单单定义一个泛型方法。也就是说类能够带个类型参数,方法也可以带类型参数的。还是来看个例子(包括如何应用),一个获得数组中间元素的方法,因为数组中存储元素的类型是不定的,所以把该方法定义成泛型的。

package com.unmi;
/**
 * 泛型方法示例
 * @author Unmi
 */
public class ArrayAlg {
   
 //这个就是在普通类 ArrayAlg 中定义的泛型方法
 public static <T> T getMiddle(T[] a){
  return a[a.length/2];
 }
 
 public static void main(String[] args) {
  String[] names = {"Fantasia","Unmi","Kypfos"};
  //String middle = ArrayAlg.<String>getMiddle(names);
  
  //上面那样写是可以,编译器可推断出要调用的方法,所以省去<String>
  String middle = ArrayAlg.getMiddle(names);
  System.out.println(middle);
 }
}
我们之所以说上面的 ArrayAlg 是个普通类,是因为没有在类声明部分引入类型参数(比如声明为 public class ArrayAlg< T>)。同时在理解上面的泛型方法 getMiddel() 时应联想到泛型类是如何定义的。

对比前面泛型类的定义 public class Demo< T>{.......},那么在类的变量、方法参数、返回值等处就可以使用参数类型 T。
这里定义了泛型方法 public static < T> T getMiddle(T[] a){......},同样是用 < T> 的形式为方法引入了一个类型参数,那么这个类型 T 可用作该方法的返回值、参数、或局部变量。注意这里的 < T> T,前部分 < T> 是定义泛型方法的类型参数,后部 T 是该方法的返回值。

泛型类的类型参数(< T>) 是紧贴着类名的后面,而泛型方法的类型参数(< T>) 是紧贴着方法声明的返回类型的前面

我们在使用泛型类,也是在构造的时候类紧贴类名后加上具体的参数类型,如 Demo< String> demo = new Demo< String>();类似的,我们在使用泛型方法时,从代码语法是在紧贴方法名的前面加代换上具体的参数类型,如ArrayAlg.< String>getMiddle(names),调用方法时不能有返回类型了,所以具体参数类型 < String> 靠紧了方法名。

前面代码中,我们说既可以用 ArrayAlg.< String>getMiddle(names);  来调用定义的泛型方法 public static < T> T getMiddle(T[] a),也可省写为
ArrayAlg.getMiddle(names);  来调用该方法。通常我们是这么做的,原因是 Java 编译器通过参数类型、个数等信息能推断出调用哪一个方法。但 Java 编译器也不是完全可靠的,有时候你必须显式的用 ArrayAlg.< String>getMiddle(names);  这种形式去调用明确的方法。

例如,我们在 ArrayAlg 中多定义一个 public static String getMiddle(String[] a){......} 方法,完整代码如下:

package com.unmi;
/**
 * 泛型方法示例,泛型方法的显式调用
 * @author Unmi
 */
public class ArrayAlg {
   
 //这个就是在普通类 ArrayAlg 中定义的泛型方法
 public static <T> T getMiddle(T[] a){
  return a[a.length/2];
 }
 public static String getMiddle(String[] a){
  return "Not Generic Method.";
 }
 
 public static void main(String[] args) {
  String[] names = {"Fantasia","Unmi","Kypfos"};
  
  //必须显式的用 <String> 去调用定义的泛型方法
  String middle1 = ArrayAlg.<String>getMiddle(names);
  System.out.println(middle1); //输入 Unmi,调用了泛型方法
  
  //不指明参数类型 <String> 则调用的是那个普通方法
  String middle2 = ArrayAlg.getMiddle(names);
  System.out.println(middle2); //输出 Not Generic Method

 }
}

   这也有些像我们的 C++ 的模板类,在模板具体化的时候存在 隐式实例化、显式实例化、显式具体化、部分具体化的情况,怎么看 C++ 的模板类还是要比 Java 的泛型复杂。

当然,上面代码只是说明 Java 的泛型方法在语法上会出现这种情况,倘若谁真写出的泛型代码需要用 ArrayAlg.< String>getMiddle(names);  显式的去调用泛型方法,那一定要考虑重构它了。明白了这一点难道就没有半点实际的意义吗,自然也不是,我们可以把它牢记为潜在的 Bug 容身之所。

进一步联系到前一篇,泛型类在定义的时候可以指定多个类型参数(用 < T,U> 形式),在定义泛型方法时同样用 < T,U> 的形式,调用的时候与一个参数时类似,如 ArrayAlg.< String, Date>getByIdx(names, new Date())。也不怕浪费几个字,大致浏览一下多类型参数时泛型方法的定义与使用的代码:

package com.unmi;
import java.util.*;
/**
 * 泛型方法示例,多类型参数的情况
 * @author Unmi
 */
public class ArrayAlg {
   
 //由索引获得
 public static <T,U> T getByIdx(T[] a, U b){
  //依照 HashMap 实现的算法,由 b 得到一个不越界的索引
  int h = b.hashCode();
     h ^= (h >>> 20) ^ (h >>> 12);
     h = h ^ (h >>> 7) ^ (h >>> 4);
  int index = h & (a.length-1);
  
  return a[index];
 }
 
 public static void main(String[] args) {
  String[] names = {"Fantasia","Kypfos","Unmi"};
  
  //显式的用 <String, Object> 去调用定义的泛型方法
  String name = ArrayAlg.<String, Date>getByIdx(names,new Date());
  
  //隐式调用泛型方法
  String name1 = ArrayAlg.getByIdx(names,"GameOver");
  
  //会输出 Unmi:Fantasia,或 Fantasia:Fantasia
  System.out.println(name + ":" + name1);
 }
}

因为现在还不想涉及到调用类型参数的特定方法,所以参照 HashMap 算法,由第二个类型参数算出数组范围内的索引。留意两种调用泛型方法的方式,应用隐式调用在有些情况下也是会产生二义性的。

 

 

分享到:
评论

相关推荐

    C#2.0的泛型编程

    因此,在设计泛型方法时需要小心考虑这一点。 #### 九、总结 通过本文的学习,我们不仅了解了C#2.0中泛型的基础概念,而且还深入了解了泛型的优点、基本使用以及一些高级特性。泛型为C#开发提供了强大的工具,使得...

    三十分钟掌握STL(泛型编程)

    3. **迭代器的概念**:迭代器是STL中的一个重要概念,它提供了访问容器内部元素的方法。迭代器类似于指针,可以用于遍历容器中的元素。通过使用迭代器,STL算法能够适用于各种不同的容器类型,而无需了解容器的具体...

    java开发面试宝典----中级

    - **平台无关性**:Java通过JVM实现了平台无关性,理解这一点对于深入学习Java非常重要。 #### 2. 数据类型与变量 - **基本数据类型**:如int、long、float等,了解它们之间的区别。 - **引用数据类型**:如类、...

    南通的JAVA自学之路(加精).

    ### 南通的JAVA自学之路知识点详述 #### 一、学会选择——市场导向学习法 在学习Java之前,了解市场需求是非常重要的一步。...遵循文章中提到的方法和技巧,相信每位学习者都能够顺利走上Java开发之路。

    JAVA基础知识点

    - **性能:** 泛型在编译后会被擦除,这意味着泛型方法在运行时并没有类型信息,这可能会导致一些性能问题。 - **理解成本:** 对于初学者而言,理解和使用泛型可能有一定的学习曲线。 #### 五、Maven **定义与...

    java迷题

    Java 8引入了默认方法来缓解这个问题,但仍然需要注意版本兼容性和接口之间的冲突。 9. 类反射的安全风险: Java的反射机制允许在运行时访问类、接口、字段和方法,但也可能导致安全漏洞。在使用反射时,应谨慎...

    用Visual C#动态生成组件

    C#的委托和事件机制可以方便地实现这一点,它们允许我们将方法作为参数传递,实现回调或事件处理。 9. **设计模式**:动态生成组件经常与工厂模式、代理模式等设计模式相结合,以提高代码的灵活性和可扩展性。 10....

    给C++初学者的50个建议.txt

    学习泛型编程 类(class)、模板(template)、STL和通用编程(generic programming)是C++的重要组成部分。这些概念和工具能够极大地提高代码的复用性和灵活性。初学者应该尽早接触这些主题,并通过实践来加深理解。 ##...

    Java程序设计语言.(美国)阿诺德.清晰版.pdf

    综上所述,本知识点概括了Java程序设计语言的基本概念、Java编程的学习资源使用注意事项,以及版权保护的重要性。在学习Java编程时,应注意合法使用电子资源,尊重原创作者的劳动成果,以促进技术知识的正当传播和...

    day09-ArrayList集合&学生管理系统.pdf

    ArrayList的特点是基于数组的数据结构,这使得它在随机访问元素时表现良好,但数组的缺点是容量固定,而ArrayList弥补了这一点,它的容量是动态变化的,能够随着元素的增加而自动扩展。 ### ArrayList类概述 ...

    c++ language edition

    需要注意的是,C++对于某些C语言特性有轻微的语法差异,因此,即使你熟悉C语言,也建议阅读这部分内容。 教程分为六个部分,每个部分又细分为多个章节,涵盖不同的主题。你可以直接通过左侧的章节索引访问任何部分...

    Java编写中容易搞错的一些东西.rar

    在Java编程过程中,开发者经常会遇到一些...以上只是Java编程中的一些常见易错点,实际上还有许多细节需要注意,如字符串的操作、IO流的使用、泛型的理解、枚举的应用等。不断学习和实践,才能真正掌握Java编程的精髓。

    Visual Basic 2010入门经典

    由于文档的部分内容是通过OCR扫描技术生成的,可能存在字词识别错误或漏识别的问题,读者在阅读时应该注意到这一点,并根据上下文和自身已有的知识基础进行合理的推测和校正,以便能够顺畅地理解和学习书中的知识。...

    C++傻瓜教程.

    C++提供了new和delete运算符来实现这一点,但需要注意的是,不正确的内存管理可能导致内存泄漏或悬挂指针,这是初学者常犯的错误。 函数指针是C++的另一特色,它们可以作为参数传递或存储在变量中,实现回调和策略...

    前辈的c++50个建议

    无论是为了提升技能还是解决具体问题,都应该围绕着这个目标去选择合适的学习资源和方法。 ### 9. Visual C++不仅仅是为了学习C++ 虽然Visual C++是一个很好的学习平台,但它也支持其他语言和技术栈。因此,在掌握...

    Introduction to Programming Languages and Techniques

    网络上有许多其他书籍和在线材料可供学习,如果你有具体的问题,可以尝试使用Google查询。 本文档中还提到了一个需要注意的点,即这个幻灯片集是用于讲座的幻灯片集合的超集。额外的幻灯片带有深红色的标题,这表示...

    《疯狂VC》srm转txt版

    同时,保持原有的版权信息不变,尊重原作者的权益,这是转换过程中必须注意的一点。 txt格式虽然简单,但它具有通用性,可以在各种设备和操作系统上打开,且易于搜索和复制粘贴代码片段。这对于学习编程的读者来说...

    competition-algorithm:一些竞争性算法学习

    10. **模拟和暴力求解**:在一些简单的题目中,直接模拟问题的过程或尝试所有可能的解决方案(暴力求解)可能是一种有效的方法,但必须注意其时间和空间复杂度限制。 以上只是冰山一角,实际的学习过程需要深入理解...

Global site tag (gtag.js) - Google Analytics