Java8 _ 方法引用 ::


参考文章: http://zh.lucida.me/blog/java-8-lambdas-insideout-language-features/


Java8 的方法引用 是基于 lambda 表达式的,可以认为是对 lambda 表达式的一种扩展,如果对 lambda 表达式不太了解,可以参考我的文章。  http://blog.csdn.net/u010003835/article/details/76833247

1. 方法引用(Method references)

lambda 表达式允许我们定义一个匿名方法,并允许我们以函数式接口的方式使用它。我们也希望能够在 已有的 方法上实现同样的特性。

方法引用和 lambda 表达式拥有相同的特性(例如,它们都需要一个目标类型,并需要被转化为函数式接口的实例),不过我们并不需要为方法引用提供方法体,我们可以直接通过方法名称引用已有方法。

以下面的代码为例,假设我们要按照 name 或 age 为 Person 数组进行排序:

     
     
1
2
3
4
5
6
7
8
9
10
11
12
     
     
class Person {
private final String name;
private final int age;
public int getAge() { return age; }
public String getName() { return name; }
...
}
Person[] people = ...
Comparator<Person> byName = Comparator.comparing(p -> p.getName());
Arrays.sort(people, byName);

在这里我们可以用方法引用代替lambda表达式:

     
     
1
     
     
Comparator<Person> byName = Comparator.comparing(Person::getName);

这里的 Person::getName 可以被看作为 lambda 表达式的简写形式。尽管方法引用不一定(比如在这个例子里)会把语法变的更紧凑,但它拥有更明确的语义——如果我们想要调用的方法拥有一个名字,我们就可以通过它的名字直接调用它。

因为函数式接口的方法参数对应于隐式方法调用时的参数,所以被引用方法签名可以通过放宽类型,装箱以及组织到参数数组中的方式对其参数进行操作,就像在调用实际方法一样:

     
     
1
2
3
4
     
     
Consumer<Integer> b1 = System::exit; // void exit(int status)
Consumer<String[]> b2 = Arrays:sort; // void sort(Object[] a)
Consumer<String> b3 = MyProgram::main; // void main(String... args)
Runnable r = Myprogram::mapToInt // void main(String... args)

2. 方法引用的种类(Kinds of method references)

方法引用有很多种,它们的语法如下:

  • 静态方法引用:ClassName::methodName
  • 实例上的实例方法引用:instanceReference::methodName
  • 超类上的实例方法引用:super::methodName
  • 类型上的实例方法引用:ClassName::methodName
  • 构造方法引用:Class::new
  • 数组构造方法引用:TypeName[]::new

对于静态方法引用,我们需要在类名和方法名之间加入 :: 分隔符,例如 Integer::sum

对于具体对象上的实例方法引用,我们则需要在对象名和方法名之间加入分隔符:

     
     
1
2
     
     
Set<String> knownNames = ...
Predicate<String> isKnown = knownNames::contains;

这里的隐式 lambda 表达式(也就是实例方法引用)会从 knownNames 中捕获 String 对象,而它的方法体则会通过Set.contains使用该 String 对象。

有了实例方法引用,在不同函数式接口之间进行类型转换就变的很方便:

     
     
1
2
     
     
Callable<Path> c = ...
Privileged<Path> a = c::call;

引用任意对象的实例方法则需要在实例方法名称和其所属类型名称间加上分隔符:

     
     
1
     
     
Function<String, String> upperfier = String::toUpperCase;

这里的隐式 lambda 表达式(即 String::toUpperCase 实例方法引用)有一个 String 参数,这个参数会被 toUpperCase 方法使用。

如果类型的实例方法是泛型的,那么我们就需要在 :: 分隔符前提供类型参数,或者(多数情况下)利用目标类型推导出其类型。

需要注意的是,静态方法引用和类型上的实例方法引用拥有一样的语法。编译器会根据实际情况做出决定。

一般我们不需要指定方法引用中的参数类型,因为编译器往往可以推导出结果,但如果需要我们也可以显式在 :: 分隔符之前提供参数类型信息。

和静态方法引用类似,构造方法也可以通过 new 关键字被直接引用:

     
     
1
     
     
SocketImplFactory factory = MySocketImpl:: new;

如果类型拥有多个构造方法,那么我们就会通过目标类型的方法参数来选择最佳匹配,这里的选择过程和调用构造方法时的选择过程是一样的。

如果待实例化的类型是泛型的,那么我们可以在类型名称之后提供类型参数,否则编译器则会依照”菱形”构造方法调用时的方式进行推导。

数组的构造方法引用的语法则比较特殊,为了便于理解,你可以假想存在一个接收 int 参数的数组构造方法。参考下面的代码:

     
     
1
2
     
     
IntFunction< int[]> arrayMaker = int[]:: new;
int[] array = arrayMaker.apply( 10) // 创建数组 int[10]



3. 综合的例子

package lambda;


class AlreadyExist {
    public static void MM(Integer a) {
        System.out.println("通过类的静态函数引用 数据是 :" + a);
    }

    public void KK(Integer b) {
        System.out.println("通过类的非静态函数引用 数据是 :" + b * b);
    }

}

@FunctionalInterface
interface Cmd {
    void DoSomething(Integer data);
}

interface DefaultCmd {

    default void DefaultMethod(Integer data) {
        System.out.println("接口的默认函数 输出是: " + data);
    }
}

/**
 * Created by szh on 2017/8/7.
 */
public class FuncQuote {

    public static void main(String[] args) {
        Cmd a = (n) -> System.out.println("通过lambda表达式 数据是 :" + n);
        a.DoSomething(5);

        Cmd b = AlreadyExist::MM;
        b.DoSomething(5);

        AlreadyExist s = new AlreadyExist();
        Cmd c = s::KK;
        c.DoSomething(5);

        DefaultCmd defaultCmd = new DefaultCmd() {
        };
        Cmd d = defaultCmd::DefaultMethod;
        d.DoSomething(5);
    }

}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值