函数式接口 VS 委托

在C中,可以使用函数指针来存储函数的入口,从而使得函数可以像变量一样赋值、传递和存储,使得函数的调用变得十分灵活,是实现函数回调的基础。然而函数指针不存在函数的签名信息,甚至可以指向任何地址,使用上有诸多不安全因素,因此在很多现代语言中不存在函数指针这种类型。

在Java中,包装一个方法的调用,需要创建一个接口类型和相应的实现类型,在实现中调用需要包装的方法,如果需要调用的是实例方法,还需要将实例的引用传递进接口实现的实例中(后面再比较闭包)。这种实现方式的好处是不需要引入更多的语法概念,可以保持语言的精简,学习曲线平缓。缺点是代码量多,不能清晰的区分和表达“这是一个包装了方法的对象”这种概念,同时接口的定义多样,实例的使用者需要了解接口的细节才能很好地使用。

public interface Func<T, TResult> {

TResult invoke(T arg);

}

public static void M(){

Func<String, String> func = new Func<String, String>(){

public String invoke(String arg){

return method(arg);

}

};

String result = func.invoke("world");

}

public static String method(String arg){

return "hello " + arg;

}

在C#(1+)中,有一种特殊的引用类型叫做委托(Delegate),专门用于表达方法的引用。所有的委托类型都继承于System.Delegate,同时,拥有一组特殊的语法,使得委托的定义、实例化、调用十分简单和极具语义。委托的调用可以像调用普通方法一样,使用者不需要了解委托的内在实现。

delegate TResult Func<T,TResult>(T arg); //定义委托

public static void M(string arg){

Func<string, string> func = Method;//指向静态方法

Program p = new Program();

func = p.InstanceMethod;//指向实例方法

string result = func("world");//调用方法

}

public static string Method(string arg){

return "hello " + arg;

}

public string InstanceMethod(string arg){

return "Woo " + arg; ;

}

在.NET中,委托的内部实际上是封装了一个函数指针。

Java8中新加的函数式接口,采用了类似于C#的委托的语法,使得一个方法定义可以直接赋值给一个“函数式接口”,所谓函数式接口就是只包含一个方法定义的接口,可以使用@FunctionalInterface加以约束。

@FunctionalInterface

public interface Func<T, TResult> {

TResult invoke(T arg);

}

public static void M(){

Func<String, String> func = Programe::method;

Programe p = new Programe();

func = p::instanceMethod;

String result = func.invoke("world");

}

public static String method(String arg){

return "hello " + arg;

}

public String instanceMethod(String arg){

return "Woo " + arg;

}

可以看到,无论是指向静态方法还是实例方法,Java8的语法都和C#十分接近,他们的内在实现也是类似的,.NET使用ldftn指令获取方法地址,而Java则使用invokedynamic。在C#,获取方法引用和调用方法的语法完全一致,而Java,静态方法必须使用类名::方法名的写法,和调用时可以只需方法名的写法有区别,同时多了一个::符号,增加了语法的复杂度,不明白这样设计的原因是什么,难道仅仅是为了区别C#。

更多

.NET的Delegate基类提供了委托的链表连接以实现多播,是事件机制实现的基础;提供了运行于线程池的异步调用方法,使用起来也十分方便。

匿名方法和闭包

当一个方法的定义仅仅是为了被引用到一个变量中,往往会使用内联方法定义,也就是匿名方法。

Java:

Func<String, String> func = new Func<String, String>(){

public String invoke(String arg){

return "hello " + arg;

}

};

String result = func.invoke("world");

C#

Func<string, string> func = delegate(string arg){

return "hello " + arg;

};

string result = func("world");

类似的语法,使得一个方法出现在了另一个方法体内,在编译之后,都生成了两个独立的方法。一般说来两个方法之间的临时变量有各自的栈范围,是不可以共享的,也就是说内部方法里不能访问外部方法的变量。然而从代码结构上,似乎又应该允许这样的访问,的确,如果内部方法如果可以访问外部方法的临时变量,将会带来很多便利,代码逻辑也更直观。Java和C#都实现了这种变量穿越的能力,就是闭包。

Java

public static void M(int arg){

final int v = arg;

Func<String, String> func = new Func<String, String>(){

public String invoke(String arg){

return "hello " + arg + v;

}

};

}

C#

public static void M(){

int v=1;

Func<string, string> func = delegate(string arg)    {

return "hello " + arg + v;

};

string result = func("world");

}

然而他们的实现机制则有一些的区别。

Java的实现,在“初始化接口”时将内部需要的变量作为自动生成的匿名类型的构造方法的参数传递进实例内部并使用字段存储,内部方法调用外部变量实质上是调用实例的字段。这样一来,内部访问的变量和外部的变量在XXX实例化的那一刻开始,就独立变化了,表现得不像是同一个变量,因此在Java中闭包变量必须加上final修饰,以解决这一问题。

在C#中,内部方法的访问实现和Java类似,同时添加了对闭包字段访问的属性,在外部方法访问变量时,同样访问的是闭包对象的属性,也就是和内部访问的相同,从而实现了内部方法和外部方法共享一个“变量”。

两者的实现都将方法内的变量提升到了对象的成员,都将延长对象生命周期,需要注意。

泛型与类型推断

泛型是C#2添加的新特性,给C#带来了十分灵动的类型定义方式。例如,不再需要为各种含有不同类型的函数签名的方法定义委托,只需要根据不同参数个数以及有无返回值定义一批泛型委托就可以表示绝大部分函数了。

delegate TResult Func<TResult>();

delegate TResult Func<T, TResult>(T arg);

delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2);

……

delegate void Action();

delegate void Action<T>(T arg);

delegate void Action<T1, T2>(T1 arg1, T2 arg2);

……

再例如,复杂的数据结构可以用嵌套的泛型定义:如Dictionary<Tuple<int, KeyValuePair<string, long>>, List<int>>。这样的数据结构定义比自定义类型更为直观和统一。但是代码则较为冗长,在new这样的实例时,需要重复写两遍。在C#3中,添加了对匿名类型的类型推导能力,自动根据等号右边的表达式推导左边变量的类型。

var data = new Dictionary<Tuple<int, KeyValuePair<string, long>>, List<int>>();

var data = new Func<int>(delegate()    {

return 0;

});

在C#中,各种开泛型和闭泛型在编译后都是区别存在的,Action<>,Action<int>,Action<string>都是不同的类型,从而真正实现了强类型约束以及运行时获取类型参数的能力,同时类型参数可以是值类型,提高了泛型集合的性能。

 

Lambda表达式

Lambda表达式是C#3添加的新特性。用于简化委托的定义。

对于以下匿名方法的定义:

Func<string, string> func = delegate(string arg)    {

return "hello " + arg;

};

去掉delegate关键字,并用符号=>连接参数与方法体:

Func<string, string> func = (string arg) =>   {

return "hello " + arg;

};

通过类型推断,可以省去参数的类型:

Func<string, string> func = (arg) =>   {

return "hello " + arg;

};

对于只有一个参数的方法,可以省去参数的括号:

Func<string, string> func = arg =>   {

return "hello " + arg;

};

对于只有一行表达式的方法,可以省去方法体括号和return关键字:

Func<string, string> func = arg => "hello " + arg;

Java8中的lambda表达式几乎和C#完全一致,只不过连接符号使用->而不是=>。

Func<String, String> func = arg -> "hello " + arg;

显然,lambda表达式大大化简了匿名方法的定义,方法内联到任何需要匿名方法的地方。

如,定义一个可枚举类型,提供一个筛选元素的方法Where:

class Enumerable<T> : IEnumerable<T>

{

public IEnumerable<T> Where(Func<T, bool> predicat)

{

foreach (var item in this)

{

if (predicat(item))

{

yield return item;

}

}

}

……

}

当我需要对已存在的集合进行按条件筛选,则需要提供一个筛选条件的委托类型,这里可以使用lambda表达式来定义,就显得十分方便和直观。

Enumerable<int> collection;

var result = collection.Where(item => item > 100);

高阶函数

Lambda表达式除了简化匿名方法的定义以外,由于其强大的表达能力,赋于了语言更多的函数式表达能力。

将参数或者返回类型为函数的函数称为高阶函数。

如斐波那契数列函数定义:

f(0) = 1;

f(1) = 1;

f(n) = f(n-1) + f(n-2);

用C#可以写成:

Func<int, int> f = null;

f = x => x <= 1 ? 1 : f(x - 1) + f(x - 2);

int result = f(5);

Streams VS linq

在前面的例子中,定了一个Where方法用于对集合元素的筛选,事实上.NET4内置提供了多种类似的集合操作方法(这些方法都是通过扩展方法(C#3新特性)添加的,可以在不修改类定义的情况下为类添加类似于实例方法的效果)。利用这些方法和lambda表达式,可以为集合进行多种操作。

var result = Enumerable.Range(0, 100)//遍历1到100

.Where(n => n % 2 == 0)//筛选能被2整除的数

.Where(n => n % 3 == 0)//筛选能被2整除的数

.Select(n => n.ToString())//转换成字符串

.Reverse();//反转顺序

通过这样的链式编程,可以表达各种数据集合操作,而这些都只是操作定义,真正的数据操作在使用时才进行,达到了延时操作效果。

更进一步,C#实现了linq,一种语言级查询语言,将查询简化到了极致,达到了类似于SQL的效果。

上面的查询可以写成:

var reuslt = from n in Enumerable.Range(0, 100)

where n % 2 == 0 && n % 3 == 0

select n.ToString();

用一行语句表达了纯粹的查询,不存在任何方法、委托的痕迹。

Java8中添加java.util.Stream包,用于实现类似于C#链式查询的效果。

List<String> stringCollection = null;

String[] result = stringCollection.stream()

.filter(s -> s.startsWith("a"))

.map(s -> s.toString())

.toArray();

表达式树

在C#中,lambda表达式可以被编译为一种数据结构,称为表达式树,而这个数据结构可以在运行时被分析、处理或者编译,做到很多灵活、有趣且高效的效果,其中最为瞩目的就是将linq作为ORM的查询语言,将数据库的查询融入到语言中,接受编译时强类型检查。

Annotation VS Attribute

注解(annotation)是Java6添加的新特性,在Java8中,可以对同一个元素添加重复的注解。

注解十分类似于.NET(1+)中的特性(attribute)。不同的是.NET Attribute是类而不是接口,可以带有方法逻辑而不仅仅是数据,从而可以利用各种设计模式,得到更多的设计可能性。

Java8的新特性以及与C#的比较的更多相关文章

  1. 简单了解JAVA8的新特性

    JAVA8新特性会颠覆整个JAVA程序员的编程习惯 甚至如果您坚守JAVA7之前的编程习惯,今后你看比较年轻的程序员写的JAVA代码都会无法理解 所以为了保证不脱钩,我觉得有必要学习JAVA8的新特性 ...

  2. java8的新特性以及用法简介

    1. 介绍 2 接口的默认方法 2 lambda表达式 2.1 函数式接口 2.2 方法与构造函数引用 2.3 访问局部变量 2.4 访问对象字段与静态变量 3. 内建函数式接口 3.1 Predic ...

  3. Java8常用新特性实践

    前言: 时下Oracle开速迭代的Java社区以即将推出Java10,但尴尬的是不少小中企业仍使用JDK7甚至JDK6开发. 从上面列出的JDK8特性中我们可以发现Java8的部分特性很明显的是从Sc ...

  4. Java8 Stream新特性详解及实战

    Java8 Stream新特性详解及实战 背景介绍 在阅读Spring Boot源代码时,发现Java 8的新特性已经被广泛使用,如果再不学习Java8的新特性并灵活应用,你可能真的要out了.为此, ...

  5. java5、java6、java7、java8的新特性

    Java5: 1.泛型 Generics:        引用泛型之后,允许指定集合里元素的类型,免去了强制类型转换,并且能在编译时刻进行类型检查的好处. Parameterized Type作为参数 ...

  6. Java8部分新特性的学习

    Java8中的新特性 一.Lambda表达式 Lambda表达式可以理解为一种可传递的匿名函数:它没有名称,但又参数列表.函数主体.返回类型,可能还有一个可以抛出的异常列表. 匿名:和匿名类类似的,它 ...

  7. java7与java8的新特性

    java7 新特性: 1. switch 里面的 case 条件可以使用字符串了. 2. 运用 List\tempList = new ArrayList<>(); 即泛型实例化类型自动判 ...

  8. Java8接口新特性

    概述 Java 8中,你可以为接口添加静态方法和默认方法.从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念.猜想设计初衷可能使为了兼容8以下的jdk Java8出来了个函数 ...

  9. 深度分析:java8的新特性lambda和stream流,看完你学会了吗?

    1. lambda表达式 1.1 什么是lambda 以java为例,可以对一个java变量赋一个值,比如int a = 1,而对于一个方法,一块代码也是赋予给一个变量的,对于这块代码,或者说被赋给变 ...

随机推荐

  1. hdoj 2022 海选女主角

    Problem Description potato老师虽然很喜欢教书,但是迫于生活压力,不得不想办法在业余时间挣点外快以养家糊口.“做什么比较挣钱呢?筛沙子没力气,看大门又不够帅...”potato ...

  2. IAR FOR ARM的安装及破解

    本博文主要是介绍如何安装以及破解IAR FOR ARM . 1.下载IAR FOR ARM以及注册机 IAR FOR ARM下载:http://pan.baidu.com/s/1i5t1qF7 注册机 ...

  3. 【动态规划】bzoj1642 [Usaco2007 Nov]Milking Time 挤奶时间

    区间按左端点排序,dp. #include<cstdio> #include<algorithm> using namespace std; #define N 1001 st ...

  4. 1.本周的作业请参照此文:http://www.ruanyifeng.com/blog/2015/12/git-workflow.html 制定本组项目的GitHub版本更新流程---答题者:徐潇瑞

    首先,介绍一下gitflow,它是最早诞生.并得到广泛采用的一种工作流程.如果采用git flow开发流程,那么项目存在两个常设分支,一个叫主分支master,另一个叫开发分支develop.mast ...

  5. VS2010 网页错误

    VS2010向导添加消息处理时,弹出以上错误,原因之一为: 类内没有定义IDD的宏

  6. Python 学习---------Day2

    第四章 介绍Python对象类型为什么使用内置类型 内置对象使程序更容易编写 内置对象是拓展的组件 内置对象往往比定制的数据结构更有效率 内置对象是语言标准的一部分Python的核心数据类型 数字 字 ...

  7. #知识#室内设计原理ing

    室内设计原理 第一章 室内设计的含义和基本观点 人的一生,绝大部分时间是在室内度过的,因此,人们设计创造的室内环境,必然会直接关系到室内生活.生产活动的质量,关系到人们的安全.健康.效率.舒适等等.室 ...

  8. c++中的指针之指针在数组

    使用一维指针数组输出一维数组中的数 int array[]={1,2,3,4,5,6};        int *p; p=array;        for(int i=0;i<6;i++){ ...

  9. Spring 3种注入方式

    spring的三种注入方式: 接口注入(不推荐) getter,setter方式注入(比较常用) 构造器注入(死的应用) 关于getter和setter方式的注入: autowire="de ...

  10. td标签内的内容过长导致的问题的解决办法

    问题描述:在开发过程中,td标签中的有一个cell格中的内容过长,导致td标签高度增加,从而导致整个页面内容的不协调: