我们知道,对于一个Java变量,我们可以赋给其一个“值”。
如果你想把“一块代码”赋给一个Java变量,应该怎么做呢?
比如,我想把右边那块代码,赋给一个叫做aBlockOfCode的Java变量:
在Java 8之前,这个是做不到的。但是Java 8问世之后,利用Lambda特性,就可以做到了。
当然,这个并不是一个很简洁的写法。所以,为了使这个赋值操作更加elegant, 我们可以移除一些没用的声明。
这样,我们就成功的非常优雅的把“一块代码”赋给了一个变量。而“这块代码”,或者说“这个被赋给一个变量的函数”,就是一个Lambda表达式。
但是这里仍然有一个问题,就是变量aBlockOfCode的类型应该是什么?
在Java 8里面,所有的Lambda的类型都是一个接口,而Lambda表达式本身,也就是”那段代码“,需要是这个接口的实现。这是我认为理解Lambda的一个关键所在,简而言之就是,Lambda表达式本身就是一个接口的实现。直接这样说可能还是有点让人困扰,我们继续看看例子。我们给上面的aBlockOfCode加上一个类型:
这种只有一个接口函数需要被实现的接口类型,我们叫它”函数式接口“。为了避免后来的人在这个接口中增加接口函数导致其有多个接口函数需要被实现,变成"非函数接口”,我们可以在这个上面加上一个声明@FunctionalInterface, 这样别人就无法在里面添加新的接口函数了:
这样,我们就得到了一个完整的Lambda表达式声明:
最直观的作用就是使得代码变得异常简洁。
我们可以对比一下Lambda表达式和传统的Java对同一个接口的实现:
这两种写法本质上是等价的。但是显然,Java 8中的写法更加优雅简洁。并且,由于Lambda可以直接赋值给一个变量,我们就可以直接把Lambda作为参数传给函数, 而传统的Java必须有明确的接口实现的定义,初始化才行:
有些情况下,这个接口实现只需要用到一次。传统的Java 7必须要求你定义一个“污染环境”的接口实现MyInterfaceImpl,而相较之下Java 8的Lambda, 就显得干净很多。
直接上例子。
假设Person的定义和List
现在需要你打印出guiltyPersons List里面所有LastName以"Z"开头的人的FirstName。
原生态Lambda写法:定义两个函数式接口,定义一个静态函数,调用静态函数并给参数赋值Lambda表达式。
这个代码实际上已经比较简洁了,但是我们还可以更简洁么?
当然可以。在Java 8中有一个函数式接口的包,里面定义了大量可能用到的函数式接口。所以,我们在这里压根都不需要定义NameChecker和Executor这两个函数式接口,直接用Java 8函数式接口包里的Predicate
第一步简化 - 利用函数式接口包:
静态函数里面的for each循环其实是非常碍眼的。这里可以利用Iterable自带的forEach()来替代。forEach()本身可以接受一个Consumer
第二步简化 - 用Iterable.forEach()取代foreach loop:
由于静态函数其实只是对List进行了一通操作,这里我们可以甩掉静态函数,直接使用stream()特性来完成。stream()的几个方法都是接受Predicate
第三步简化 - 利用stream()替代静态函数:
对比最开始的Lambda写法,这里已经非常非常简洁了。但是如果,我们的要求变一下,变成print这个人的全部信息,及p -> System.out.println(p); 那么还可以利用Method reference来继续简化。所谓Method reference, 就是用已经写好的别的Object/Class的method来代替Lambda expression。格式如下:
第四步简化 - 如果是println(p),则可以利用Method reference代替forEach中的Lambda表达式:
这基本上就是能写的最简洁的版本了。
这里假设我们有一个person object,以及一个person object的Optional wrapper:
Optional
只有当Optional
我们现在就来对比一下下面四种常见的null处理中,Java 8的Lambda+Optional
情况一 - 存在则开干
情况二 - 存在则返回,无则返回屁
情况三 - 存在则返回,无则由函数产生
情况四 - 夺命连环null检查
由上述四种情况可以清楚地看到,Optional
关于Java的Lambda, 还有东西需要讨论和学习。比如如何handle lambda exception,如何利用Lambda的特性来进行parallel processing等。总之,我只是一如既往地介绍个大概,让你大概知道,哦!原来是这样子就OK了。网上关于Lambda有很多相关的教程,多看多练。假以时日,必定有所精益。
************************************************************************END*******************************************************************
一、什么时候使用Lambda表达式
总的来说,Lambda 表达式可以用在任何需要使用匿名方法,或是代理的地方。编译器会将Lambda表达式编译为标准的匿名方法(可以使用ildasm.exe or reflector.exe得到确认)。
比如:
List evenNumbers = list.FindAll(i => (i% 2) == 0);
被编译为
List evenNumbers = list.FindAll(delegate (int i)
{
return (i % 2) == 0;
});
二、Lambda表达式的解读
Lambda表达式的写法
ArgumentsToProcess (参数)=> StatementsToProcessThem(报表)
比如
// "i" is our parameter list.
// "(i % 2) == 0" is our statement set to process "i".
List evenNumbers = list.FindAll(i => (i% 2) == 0);
应该这样来理解
// My list of parameters (in this case a singleinteger named i)
// will be processed by the expression (i % 2) == 0.
List evenNumbers = list.FindAll((i) => ((i% 2) == 0));
可以显式指定输入参数的类型
List evenNumbers = list.FindAll((int i) => (i % 2) == 0);
可以使用括号把输入参数和表达式括起来,如果参数或处理表达式只有一个,可以省略括号
List evenNumbers = list.FindAll((i) => ((i% 2) == 0));
有多行处理表达式时,需要使用花括号包起来
List evenNumbers = list.FindAll((i) =>
{
Console.WriteLine("valueof i is currently: {0}", i);
bool isEven = ((i % 2) == 0);
return isEven;
});
当输入参数有多个时
SimpleMath m = new SimpleMath();
m.SetMathHandler((msg, result) =>
{
Console.WriteLine("Message:{0}, Result: {1}", msg, result);
});
或者显式指定输入参数类型
m.SetMathHandler((string msg, int result) =>
{
Console.WriteLine("Message:{0}, Result: {1}", msg, result);
});
当没有输入参数时
VerySimpleDelegate d = new VerySimpleDelegate( () => {return "Enjoy your string!";} );
Lambda表达式可以在使用代理和匿名代理的地方
1、命名函数
public class Common
{
public delegate bool IntFilter(int i);
public static int[] FilterArrayOfInts(int[] ints, IntFilter filter)
{
ArrayList aList = new ArrayList();
foreach (int i in ints)
{
if (filter(i))
{
aList.Add(i);
}
}
return ((int[])aList.ToArray(typeof(int)));
}
}
public class Application
{
public static bool IsOdd(int i)
{
return ((i & 1) == 1);
}
}
using System.Collections;
int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int[] oddNums = Common.FilterArrayOfInts(nums,Application.IsOdd);
foreach (int i in oddNums)
Console.WriteLine(i);
2、匿名函数
int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int[] oddNums =
Common.FilterArrayOfInts(nums, delegate(int i) { return ((i & 1) == 1); });
foreach (int i in oddNums)
Console.WriteLine(i);
3、Lambda表达式
int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int[] oddNums = Common.FilterArrayOfInts(nums,i => ((i & 1) == 1));
foreach (int i in oddNums)
Console.WriteLine(i);
三种方式的比较
int[] oddNums = // using named method
Common.FilterArrayOfInts(nums,Application.IsOdd);
int[] oddNums = // using anonymous method
Common.FilterArrayOfInts(nums,delegate(int i){return((i & 1) == 1);});
int[] oddNums = // using lambda expression
Common.FilterArrayOfInts(nums,i => ((i & 1) == 1));
命名函数虽然简短,但是而外还要定义处理函数,优点是可以重用
匿名方法
匿名方法就是没名儿的委托。虽然没名,但是必须加”delegate“来表示我没名。
// Create a delegateinstance
delegate void Del(int x);
// Instantiate the delegate using an anonymous method
Del d = delegate(int k) { /* ... */ };
Del d = delegate() { System.Console.WriteLine("Copy #:{0}", ++n);};
delegate void Printer(string s);
Printer p = delegate(string j)
{
System.Console.WriteLine(j);
};
p("The delegate using the anonymous method is called.");
delegate string Printer(string s);
private void button1_Click(object sender, EventArgs e)
{
Printer p =delegate(string j)
{
return (j)+"烦死了";
};
Console.WriteLine(p("The delegate using the anonymous method is called."));
}
或者有名。。。。的匿名委托。。。。哈哈,我在抽风还是微软在抽风。让我死吧。
delegate void Printer(string s);
private void button1_Click(object sender, EventArgs e)
{
Printer p = new Printer(Form1.DoWork);
p("http://www.dc9.cn/");
}
static void DoWork(string k)
{
System.Console.WriteLine(k);
}
匿名方法就完了。
Lamdba就是 (int x)=>{x+1}就是这样的。。。例子呢就是上面写了的一段用lambda就这样写
delegate string Printer(string s);
private void button1_Click(object sender, EventArgs e)
{
Printer p = j => j+"烦死了!!!";
Console.WriteLine(p("The delegate using the anonymous method is called."));
}
还能这么用
和上面那个一样,就是简化了。
public Form1()
{
InitializeComponent();
this.Click += (s, e) => { MessageBox.Show(((MouseEventArgs)e).Location.ToString());};
}
超难+===>?其实不难。难的是转换成一般的函数怎么写呢????
Where是Enumerable的一个方法。3.5才有的。里面的参数是Func<(Of <(T, TResult>)>)泛型委托
Func<(Of<(T, TResult>)>) 泛型委托
using System;
delegate string ConvertMethod(string inString);
public class DelegateExample
{
public static void Main()
{
// Instantiate delegate to referenceUppercaseString method
ConvertMethod convertMeth = UppercaseString;
string name = "Dakota";
// Use delegate instance to call UppercaseStringmethod
Console.WriteLine(convertMeth(name));
}
private static string UppercaseString(stringinputString)
{
return inputString.ToUpper();
}
}
写成泛型委托是
using System;
public class GenericFunc
{
public static void Main()
{
// Instantiate delegate to referenceUppercaseString method
Func convertMethod= UppercaseString;
string name = "Dakota";
// Use delegate instance to call UppercaseStringmethod
Console.WriteLine(convertMethod(name));
}
private static string UppercaseString(stringinputString)
{
return inputString.ToUpper();
}
}
于是应用到Linq,再变换到lambda
delegate bool TestFunc(string fruit);
private void button1_Click(object sender, EventArgs e)
{
List fruits =new List { "apple", "http://www.dc9.cn","banana", "mango", "orange", "blueberry", "grape", "strawberry"};
TestFunc f = new TestFunc(DoWork);
Func f2 = DoWork;
IEnumerable query = fruits.Where(f2);
foreach (string fruit in query)
Console.WriteLine(fruit);
}
private static bool DoWork(string k)
{
return k.Length < 6;
}
能用。
==========================================================
delegate bool TestFunc(string fruit);
private void button1_Click(object sender, EventArgs e)
{
List fruits =new List { "apple", "passionfruit","banana", "mango",
"orange", "blueberry", "grape", "http://www.dc9.cn"};
TestFunc f = DoWork;
Func f2 =k=> k.Length < 6;
IEnumerable query = fruits.Where(f2);
foreach (string fruit in query)
Console.WriteLine(fruit);
}
private static bool DoWork(string k)
{
return k.Length < 6;
}
也能用
========================================================
private void button1_Click(object sender, EventArgse)
{
List fruits =new List { "apple", "passionfruit","banana", "mango",
"orange", "blueberry", "grape", "http://www.dc9.cn"};
IEnumerable query = fruits.Where(
delegate(string k){
return k.Length < 6;
}
);
foreach (string fruit in query)
Console.WriteLine(fruit);
}
能用~
private void button1_Click(object sender, EventArgse)
{
List fruits =new List { "apple", "passionfruit","banana", "mango",
"orange", "blueberry", "grape", "http://www.dc9.cn"};
IEnumerable query = fruits.Where(k=>k.Length<6);
foreach (string fruit in query)
Console.WriteLine(fruit);
}
最后,lambda,能用~~~~就酱紫了~~~~
publicdelegate int mydg(int a, int b);
public static class LambdaTest
{
public static int oper(this int a,int b, mydg dg)
{
return dg(a, b);
}
}
Console.WriteLine(1.oper(2, (a, b) => a + b));
Console.WriteLine(2.oper(1, (a, b) => a - b));
**************************************************************END****************************************************************************
lambda运算符:所有的lambda表达式都是用新的lambda运算符 " => ",可以叫他,“转到”或者 “成为”。运算符将表达式分为两部分,左边指定输入参数,右边是lambda的主体。
lambda表达式:
1.一个参数:param=>expr
2.多个参数:(param-list)=>expr
上面这些东西,记着,下面我们开始应用并阐述lambda,让你乐在其中。
阐述这技术,我先上一个例子,然后再慢慢深入分析。例子如下:
namespace 阐述lambda
{
public class Person
{
public string Name { get; set; }
public int Age { get;set; }
}
class Program
{
public static List PersonsList()
{
List persons = new List();
for (int i = 0; i < 7; i++)
{
Person p = new Person() { Name = i + "儿子", Age = 8 - i, };
persons.Add(p);
}
return persons;
}
static void Main(string[] args)
{
List persons = PersonsList();
persons = persons.Where(p => p.Age > 6).ToList(); //所有Age>6的Person的集合
Person per = persons.SingleOrDefault(p => p.Age == 1); //Age=1的单个people类
persons = persons.Where(p => p.Name.Contains("儿子")).ToList(); //所有Name包含儿子的Person的集合
}
}
}
看啦上面的例子,相信你能看出它确实是个甜枣,呵呵,下面我们来看下(p=>p.Age>6)这样的表达式,到底是怎么回事。 首先我们看下委托
//委托 逛超市
delegate int GuangChaoshi(int a);
static void Main(string[] args)
{
GuangChaoshi gwl = JieZhang; Console.WriteLine(gwl(10) + ""); //打印20,委托的应用
Console.ReadKey();
}
//结账
public static int JieZhang(int a)
{
return a + 10;
}
再看表达式
//委托 逛超市
delegate int GuangChaoshi(int a);
static void Main(string[] args)
{
// GuangChaoshi gwl = JieZhang;
GuangChaoshi gwl = p => p + 10;
Console.WriteLine(gwl(10) + ""); //打印20,表达式的应用
Console.ReadKey();
}
委托跟表达式的两段代码,我们可以看出一些东东吧:其实表达式(p => p + 10;)中的 p 就代表委托方法中的参数,而表达式符号右边的 p+10,就是委托方法中的返回结果。 大侠绕道,小虾理解下。
下面再上两个稍微复杂点的理解理解。
1.多参数的
//委托 逛超市
delegate int GuangChaoshi(int a,int b);
static void Main(string[] args)
{
GuangChaoshi gwl = (p,z) => z-(p + 10);
Console.WriteLine(gwl(10,100) + ""); //打印80,z对应参数b,p对应参数a
Console.ReadKey();
}
2. lambda主体运算复杂
///
/// 委托 逛超市
///
/// 花费
/// 付钱
/// 找零
delegate int GuangChaoshi(int a,int b);
static void Main(string[] args)
{
GuangChaoshi gwl = (p, z) =>
{
int zuidixiaofei = 10;
if (p < zuidixiaofei)
{
return 100;
}
else
{
return z - p - 10;
}
};
Console.WriteLine(gwl(10,100) + ""); //打印80,z对应参数b,p对应参数a
Console.ReadKey();
}
上面这些例子,好好理解下,下面我要介绍一个系统指定的 Fun
T 是参数类型,这是一个泛型类型的委托,用起来很方便的。
先上例子
static void Main(string[] args)
{
Func gwl = p => p + 10 + "--返回类型为string";
Console.WriteLine(gwl(10) + ""); //打印‘20--返回类型为string’,z对应参数b,p对应参数a
Console.ReadKey();
}
说明:我们可以看到,这里的p为int 类型参数, 然而lambda主体返回的是string类型的。
再上一个例子
static void Main(string[] args)
{
Func gwl = (p, j) =>
{
if (p + j == 10)
{
return true;
}
return false;
};
Console.WriteLine(gwl(5,5) + ""); //打印‘True’,z对应参数b,p对应参数a
Console.ReadKey();
}
说明:从这个例子,我们能看到,p为int类型,j为int类型,返回值为bool类型。
看完上面两个例子,相信大家应该明白啦Func
static void Main(string[] args)
{
//i*j+w*x
ParameterExpression a = Expression.Parameter(typeof(int),"i"); //创建一个表达式树中的参数,作为一个节点,这里是最下层的节点
ParameterExpression b = Expression.Parameter(typeof(int),"j");
BinaryExpression be = Expression.Multiply(a,b); //这里i*j,生成表达式树中的一个节点,比上面节点高一级
ParameterExpression c = Expression.Parameter(typeof(int), "w");
ParameterExpression d = Expression.Parameter(typeof(int), "x");
BinaryExpression be1 = Expression.Multiply(c, d);
BinaryExpression su = Expression.Add(be,be1); //运算两个中级节点,产生终结点
Expression> lambda = Expression.Lambda>(su,a,b,c,d);
Console.WriteLine(lambda + ""); //打印‘(i,j,w,x)=>((i*j)+(w*x))’,z对应参数b,p对应参数a
Func f= lambda.Compile(); //将表达式树描述的lambda表达式,编译为可执行代码,并生成该lambda表达式的委托;
Console.WriteLine(f(1, 1, 1, 1) + ""); //打印2
Console.ReadKey();
}
这段代码,放上来,仔细理解下,理解透彻啦,lambda表达式基本上也没什么啦。呵呵。。
算啦,我还是画个图算是结尾吧,以便于理解。
上段代码的lambda表达式树,图。
*******************************************************END**********************************************************************
函数式接口和lambda表达式
函数式接口(Functional Interface) :
任何接口,如果只包含唯一 一个抽象方法,那么它就是一个FI。(之前它们被称为 SAM类型,即 单抽象方法类型(Single Abstract Method))。接口中的方法默认就是public abstract的。
接口可能继承了一个 Object 已经提供的方法,比如 toString(),equals( )…这些都不属于函数式接口方法的范畴, 所以函数式接口中所说的方法不包括这些。例如下面FI接口也是一个函数式接口。
@FunctionalInterface
Interface FI{
judge(int a);
equals();
}
API作者们可以通过 @FunctionalInterface 注解来显式指定一个接口是函数式接口。加上这个注解,接口中函数式接口方法少于一个或者多余一个,编译器都会提示错误。
lambda表达式
lambda表达式是匿名方法 ,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 4. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> { System.out.print(s) }
lambda 表达式的语法由参数列表、箭头符号 -> 和函数体组成。函数体既可以是一个表达式,也可以是一个语句块。
假设我们想对一个List按字符串长度进行排序,那么在Java8之前,可以借助匿名内部类来实现:
List words = Arrays.asList("apple", "banana", "pear");
words.sort(new Comparator() {
@Override
public int compare(String w1, String w2) {
return Integer.compare(w1.length(), w2.length());
}
});
上面的匿名内部类简直可以用丑陋来形容,唯一的一行逻辑被五行垃圾代码淹没。根据前面的定义(并查看Java源代码)可知,Comparator是个FI,所以,可以用Lambda表达式来实现:
words.sort((String w1, String w2) -> {
return Integer.compare(w1.length(), w2.length());
});
代码变短了好多!仔细观察就会发现,Lambda表达式,很像一个匿名的方法,只是圆括号内的参数列表和花括号内的代码被->分隔开了。垃圾代码写的越少,我们就有越多的时间去写真正的逻辑代码,不是吗?是的!圆括号里的参数类型是可以省略的:
words.sort((w1, w2) -> {
return Integer.compare(w1.length(), w2.length());
});
如果Lambda表达式的代码块只是return后面跟一个表达式,那么还可以进一步简化:
words.sort(
(w1, w2) -> Integer.compare(w1.length(), w2.length())
);
注意,表达式后面是没有分号的!如果只有一个参数,那么包围参数的圆括号可以省略:
words.forEach(word -> {
System.out.println(word);
});
如果表达式不需要参数呢?好吧,那也必须有圆括号,例如:
Executors.newSingleThreadExecutor().execute(
() -> {/* do something. */} // Runnable
);
怎么用Lambda表达式
既然Lambda表达式这么好用,那么,可以在哪些地方使用呢?如果你真正明白了什么是FI(很容易),应该立刻就能给出答案:任何可以接受一个FI实例的地方,都可以用Lambda表达式。比如,虽然上面给出的例子都是把Lambda表达式当作方法参数传递,可以接受FI作为参数的地方,都可以替换为Lambda表达式。
但实际上你也可以定义变量:
Runnable task = () -> {
// do something
};
Comparator cmp = (s1, s2) -> {
return Integer.compare(s1.length(), s2.length());
};
看两个例子:
使用已有的FI
JDK 1.8 新增加的函数接口:java.util.function 中Predicate 接口是一个函数式接口,它接受一个输入参数 T,返回一个布尔值结果。
eval( )方法的第二个参数是FI, 所以在这里可以用Lambda表达式.
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class Java8Tester {
public static void main(String args[]){
List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
System.out.println("输出所有数据:");
// Predicate predicate = n -> true
// n 是一个参数传递到 Predicate 接口的 test 方法
// n 如果存在则 test 方法返回 true
// 传递参数 n
eval(list, n->true);
// Predicate predicate1 = n -> n%2 == 0
// n 是一个参数传递到 Predicate 接口的 test 方法
// 如果 n%2 为 0 test 方法返回 true
System.out.println("输出所有偶数:");
eval(list, n-> n%2 == 0 );
// Predicate predicate2 = n -> n > 3
// n 是一个参数传递到 Predicate 接口的 test 方法
// 如果 n 大于 3 test 方法返回 true
System.out.println("输出大于 3 的所有数字:");
eval(list, n-> n > 3 );
}
public static void eval(List list, Predicate predicate) {
for(Integer n: list) {
if(predicate.test(n)) {
System.out.print(n + " ");
}
}
}
}
输出
输出所有数据:
1 2 3 4 5 6 7 8 9 输出所有偶数:
2 4 6 8 输出大于 3 的所有数字:
4 5 6 7 8 9
自定义FI
自定义一个FI,里面有一个add( ) 方法。 想不出什么好的例子,随便举个例子很简单能说明问题足矣
package com.java8;
/**
* Created by susq on 2017-6-20.
*/
@FunctionalInterface
public interface Fitest {
int add(int a, int b);
}
package com.java8;
/**
* Created by susq on 2017-6-19.
*/
public class LamJava8 {
public static void main(String[] args) {
int a = 2, b = 3;
int result = addMethod( a, b, (c, d) -> { return c+d; } );
System.out.println( result );
}
public static int addMethod(int a, int b, Fitest fi) {
return fi.add(a, b);
}
}
输出:5
方法addMethod(int a, int b, Fitest fi) 第三个参数是我们自定义的FI, 因此这里可以用(c, d) -> { return c+d; }表达式,c, d是Fitest 中 add( ) 方法的两个参数, { return c+d; } 是add 方法的方法体。这里c, d不能写a, b,会提示变量重复定义编译错误。 因为(c, d) 实质为像(int c, int d) 一样的形参, int被我们省略了。