前面我已经介绍过了,我们可以在定义类和接口的时候使用类型形参,在该类的方法定义中,成员变量定义中,这些类型形参都可以被当成普通类型来使用。但是如果我们在定义类和接口的时候没有使用类型形参,但是在定义方法的时候想自己定义自己的类型形参,这样子也是可以的,这里也就是我们说的泛型方法。

想了解泛型方法,首先就要知道为什么会出现这种泛型方法的原因,我们先来考虑下面的情景。我们现在要实现这样一个方法,该方法负责将一个Object数组添加到一个Collection集合中,OK,现在我们来写代码:

public class Test
{ //定义一个方法,将一个数组里面的元素复制到一个集合中
public static void fromArrayToCollection(Object[] array, Collection<Object> c)
{
for (Object object : array)
{
c.add(object);
}
} public static void main(String[] args) throws Exception
{
//下面的使用没有问题
Object[] array = {1,2,3};
Test.fromArrayToCollection(array, new ArrayList<Object>());
//下面的使用有问题,报错
String[] array1 = {"1","2","3"};
Test.fromArrayToCollection(array, new ArrayList<String>());
}
}

我们前面已经说过了,List<String>并不是List<Object>的子类,所以说上面的代码调用报错,也就是说我们前面定义的那个复制的方法真的是限制太大,如果我们想复制一种特定的类型的数据就要重新定义一个方法,这样子显然是不合理的,那么我们考虑下如果我们使用通配符List<?>是否可行呢?明显的也是不行的,因为java不允许把对象放进一个未知类型的集合中。

  • 1,定义泛型方法

OK,现在就让我们使用泛型方法(generic method)吧,使用泛型方法可以很好的绕开这个问题。所谓的泛型方法就是申明方法定义的时候同时也定义了一个或者多个类型形参。具体语法如下:

修饰符 <T,S> 返回值类型 方法名(形参列表)
{
具体的方法体。。。
}

上面的泛型方法的语法值得注意的就是:所有的类型形参申明都应该放在方法修饰符和方法返回值类型之间。

现在我们使用泛型方法修改下前面我们的代码,实现可以将一个任意类型的数组赋值到一个相同类型的集合中。

//定义一个方法,将一个数组里面的元素复制到一个集合中

public static <T> void fromArrayToCollection(T[] array, Collection<T> c)
{
for (T object : array)
{
c.add(object);
}
} public static void main(String[] args) throws Exception
{
//下面的使用没有问题
Object[] array = {1,2,3};
Test.fromArrayToCollection(array, new ArrayList<Object>());
//下面的使用没有问题,集合可以省去类型
String[] array1 = {"1","2","3"};
Test.fromArrayToCollection(array1, new ArrayList<>());
//下面的使用没有问题,集合使用父类也同样没问题
Integer[] array2 = {1,2,3};
Test.fromArrayToCollection(array2, new ArrayList<Number>());
}
  • 2,调用泛型方法

与类和接口中使用泛型参数不同的是,方法中的泛型参数无须显式传入实际类型参数,编译器会根据实参推断类型参数的值,推断出最直接的类型参数。比如我们调用上面的方法,直接fromArrayToCollection(array2, new ArrayList<Number>())就可以。

//定义一个方法,将一个数组里面的元素复制到一个集合中
public static <T> void fromArrayToCollection(T[] array, Collection<T> c)
{
for (T object : array)
{
c.add(object);
}
} public static void main(String[] args) throws Exception
{
//下面的使用没有问题
Object[] array = { 1, 2, 3 };
Test.fromArrayToCollection(array, new ArrayList<Object>());
//下面的使用没有问题,集合可以省去类型
String[] array1 = { "1", "2", "3" };
Test.<String> fromArrayToCollection(array1, new ArrayList<>());
//下面的使用没有问题,集合使用父类也同样没问题
Integer[] array2 = { 1, 2, 3 };
Test.<Number> fromArrayToCollection(array2, new ArrayList<Number>());
}



  • 3,泛型方法和类型通配符的区别

在大部分情况下都可以使用泛型方法来代替类型通配符。比如java中集合有2个方法:

boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);

上面的2个方法的形参都采用了类型通配符的形式,我们当然也可以采用泛型方法的形式:

<T> boolean containsAll(Collection<T> c)
{
return false; };
<T> boolean addAll(Collection<T> c){
return false;
};

对比一下上面的2种实现,我们发现我们在使用泛型方法这种形式时,上面的类型参数T只使用了一次,类型参数T这里只是产生了一个效果,就是说可以在不同的调用点传入不同的实际类型,显然这个时候应该用类型通配符更加合适,通配符就是被设计用来支持灵活的子类化的。





总结一下,什么时候使用泛型方法,什么时候使用通配符?

1),通配符用来支持灵活的子类化,泛型方法允许类型参数被用来表示方法的一个或者多个参数之间的类型依赖关系,或者方法返回值与参数之间的关系。比如没有这样子的依赖关系就应该使用泛型方法。

2),当然我们也可以同时使用泛型方法和通配符,比如下面代码:

public static <T> void copy(List<T> dest,List<? extends T> src){}

3),类型通配符既可以在方法签名中定义形参的类型,还可以用于定义变量的类型,但是泛型方法中的类型形参必须在对应方法中显式声明。

  • 4,菱形语法与泛型构造器

前面我们看到了泛型方法允许在方法签名中声明类型参数,java同时也允许在构造器签名中申明类型参数,这样就产生了所谓的泛型构造器。在实际的调用过程中,我们不仅可以让java根据参数的类型来推断类型参数的类型,也可以显式的为构造器的类型形参指定实际的类型。比如下面的代码:

public class Test
{
//泛型构造器
public <T> Test(T name)
{
System.out.println(name.toString());
} public static void main(String[] args) throws Exception
{
//泛型构造器的T参数类型是Integer
new Test(1);
//显式指定构造器的T参数类型是Integer,实际传入构造器的参数也是Integer类型,下面代码正确
new<Integer> Test(1);
//显式指定构造器的T参数类型是String,实际传入构造器的参数是Inte类型,代码报错
new<String> Test(1);
}
}

在前面的博客里面我们介绍过java7新增的菱形语法,我们在调用构造器时构造器后可以使用一对尖括号来代表泛型信息,但是如果现在程序显式指定了泛型构造器中声明的类型参数的实际类型时,就不能使用菱形语法了呢。现在我们来看下面的代码:

public class Test<S>
{
//泛型构造器
public <T> Test(T name)
{
System.out.println(name.toString());
} public static void main(String[] args) throws Exception
{
//没有显式指定构造器类型参数类型,可以使用菱形语法
Test<String> test1 = new Test<>(1);
//显式指定构造器类型参数类型,不可以再使用菱形语法,下面代码报错
Test<String> test2 = new<Integer> Test<>(1);
//显式指定构造器类型参数类型,不可以再使用菱形语法,应该也同时指定菱形里面的构造器类型
Test<String> test3 = new<Integer> Test<String>(1);
}
}
  • 5,泛型方法和泛型重载

在写泛型的方法时,要是只是泛型的类型参数不同,那么这2个方法就算是同一个方法。因为编译后的泛型就去掉了,所以当然是一个方法。但是我现在在用java1.8撸码,方法的返回值不同,编译也报错,说已经存在了同一个方法了,所以以后还是尽量绕开泛型方法的重载好了。比如下面的代码都报错:

public <T> String test(T name)
{
return "";
} public <T> void test(T name)
{ } public static void show(List<? extends Number> l){
}
public static void show(List<? super String> l){
}
  • 6,java8改进的类型推断

java8改进了泛型方法的类型推断能力,具体的要记住下面2句话:

1),可通过调用方法的上下文来推断类型参数的目标类型

2),可在方法调用链中,将推断得到的类型参数传递给最后一个方法。

具体的我们看下面的代码,下面代码定义几个方法:

public class Test<S>
{
public static <T> Test<T> test()
{
return null;
} public static <T> Test<T> test1(T head, Test<T> test)
{
return null;
} S head()
{
return null;
} public static void main(String[] args) throws Exception
{
Test<String> test1 = Test.test();
Test<String> test2 = Test.<String> test();
Test.test1(1, Test.test());
Test.test1("", Test.<String> test());
}
}

我们现在来研究下上面main方法里面的代码,第一行和第二行效果和作用完全一样,在第一行代码中我们无须显式指定类型参数为String,因为程序需要将该方法的返回值赋值给了Test<String>类型的test1变量,所以系统自动推断出了这里的类型参数T应该是String类型。第三行和第四行的效果和作用也完全一样,同样的我们在使用第3行代码的时候也无须显式指定test方法的类型参数T是Integer类型的,因为程序在调用test1这个方法的时候从第一个参数就可以推断出后面第2个参数的类型了,也就是后面的Test.test()这个test方法的类型参数T了呢。

当然系统的自动的推断泛型的能力也不是万能的,比如我们调用上面类的方法就需要强转:

String huhu = Test.test().head();

除非我们这样子写:

String huhu = Test.<String> test().head();

java里程碑之泛型--泛型方法的更多相关文章

  1. java里程碑之泛型--类型通配符

    1,泛型与数组在子类上面的对比 在整理java泛型的类型通配符之前,我们先来研究下java在数组设计上的一个不合理.我们来看下面代码: public class Test { public stati ...

  2. java里程碑之泛型--擦除和转换

    在严格的泛型代码里,带泛型声明的类总应该带着泛型参数.但是为了和古老的java代码保持一致,也就是说为了向下兼容,也允许在使用带泛型声明的类时不指定实际的类型参数.如果没有为这个泛型类指定实际的参数类 ...

  3. java里程碑之泛型--泛型基本语法

    1,java7提供的泛型菱形语法 在java7之前,如果使用带泛型的接口和类定义变量初始化对象的时候,构造器后面也必须带上泛型,这有点恶心的.以前我在公司一直使用的java6,所以我也已经习惯了这种写 ...

  4. java里程碑之泛型--深入理解泛型

    所谓泛型,就是允许在定义类,接口,方法时使用类型形参,这个类型形参将在声明变量,创建对象,调用方法的时候动态的指定.JAVA5之后修改了集合中所有的接口和类,为这些接口和类都提供了泛型的支持. 关于泛 ...

  5. java里程碑之泛型--使用泛型

    现在重新整理下泛型,前面的整理好多的底层的东西理解不深.泛型很重要的,作为java的一个程碑式的改进,还是很有必要认真的理解下人家的JDK的良苦用心的. 1,什么是泛型?为什么要使用泛型? 一定要记住 ...

  6. Java编程思想-泛型-泛型方法

    代码示例如下: package generics; //: generics/GenericMethods.java public class GenericMethods<A> { // ...

  7. java里程碑之泛型--泛型注意的几点

    1,泛型的基本语法:类名<具体类> 对象名 = new 类名<具体类>().类型参数规范如下: 1),K键,比如映射的键,key的类型 2),V值,比如Map的值,value类 ...

  8. Java 泛型 泛型方法

    Java 泛型 泛型方法 @author ixenos 泛型方法可以定义在普通类中,也可以定义在泛型类中 类型变量放在修饰符(如public static)后面,返回类型的前面 一个static方法无 ...

  9. Java中的泛型 (上) - 基本概念和原理

    本节我们主要来介绍泛型的基本概念和原理 后续章节我们会介绍各种容器类,容器类可以说是日常程序开发中天天用到的,没有容器类,难以想象能开发什么真正有用的程序.而容器类是基于泛型的,不理解泛型,我们就难以 ...

随机推荐

  1. Java学习笔记9(面向对象二:this、继承、抽象类)

    就近原则: 类中的方法中的变量和成员变量重名时,调用类的方法时候,生效的是方法中的变量,如果方法中没有定义变量,才会去成员变量中寻找 于是,提出了this关键字,为了区分重名问题 public cla ...

  2. C#互操作处理(一)

    C#互操作的类型基本位于System.Runtime.InteropServices命名空间下,本系列随笔主要记录本人在开发过程中使用的到一些类型函数.技巧及工具 计算类型的大小 int size = ...

  3. JavaScript(四)操作符

      6种表达式 原始表达是  初始化表达式 函数定义表达式 函数调用表达式 属性访问表达式 对象创建表达式 操作符 操作符的优先级 属性访问[] .  >  一元操作符 >  */ > ...

  4. 001_JS基础_JavaScript简介

    1.1 历史 JS的发展历史: http://www.w3school.com.cn/js/pro_js_history.asp 1.2 JavaScript简介 以下摘自维基百科对javascrip ...

  5. 【mp3】洗脑循环了!龙珠超 自在极意功 【究极の圣戦】串田アキラ 背景纯音乐

    自在极意功情况下发动自带bgm 曲名:究极の圣戦 首先是视频: http://dwz.cn/6Tj2Lq 然后是mp3:度盘下载>> 老外翻唱: 个人翻唱:全民k歌>>

  6. 前端学习:html基础学习五

    9.HTML表单设计(主要内容<form><input><select>标记) 表单标记 <form>...</form> <form ...

  7. AssertionError while merging cells with xlwt (Python)

    产生这一错误的原因是,行列数字的赋值有问题,三行数字大于下行数字,左列数字大于右列数字. sheet.write_merge(top_row, bottom_row, left_column, rig ...

  8. (转)关于docker的15个小tip

    转自:https://www.cnblogs.com/elnino/p/3899136.html 1. 获取最近运行容器的id 这是我们经常会用到的一个操作,按照官方示例,你可以这样做(环境ubunt ...

  9. BZOJ:3911: SGU383 Caravans(三角剖分)

    原题链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3911 直接求最小生成树显然边太多,考虑少用点边. 连出来的边肯定是没相交的,我们需要做一下 ...

  10. hdu_1754I Hate It(线段树)

    hdu_1754I Hate It(线段树) 标签: 线段树 题目链接 题意: 中文题意...不多说了,线段树基础题 直接上代码: #include<cstdio> #include< ...