Java8中的lambda表达式入门教程

1.基本介绍

lambda表达式,即带有参数的表达式,为了更清晰地理解lambda表达式,先上代码:

1.1 两种方式的对比

1.1.1 方式1-匿名内部类

class Student{
 private String name;
 private Double score;
 public Student(String name, Double score) {
  this.name = name;
  this.score = score;
 }
 public String getName() {
  return name;
 }
 public Double getScore() {
  return score;
 }
 public void setName(String name) {
  this.name = name;
 }
 public void setScore(Double score) {
  this.score = score;
 }
 @Override
 public String toString() {
  return "{"
    + "\"name\":\"" + name + "\""
    + ", \"score\":\"" + score + "\""
    + "}";
 }
}:
@Test
public void test1(){
 List<Student> studentList = new ArrayList<Student>(){
  {
   add(new Student("stu1",100.0));
   add(new Student("stu2",97.0));
   add(new Student("stu3",96.0));
   add(new Student("stu4",95.0));
  }
 };
 Collections.sort(studentList, new Comparator<Student>() {
  @Override
  public int compare(Student o1, Student o2) {
   return Double.compare(o1.getScore(),o2.getScore());
  }
 });
 System.out.println(studentList);
}

代码调用Collections.sort方法对集合进行排序,其中第二个参数是一个匿名内部类,sort方法调用内部类中的compare方法对list进行位置交换,因为java中的参数类型只能是类或者基本数据类型,所以虽然传入的是一个Comparator类,但是实际上可以理解成为了传递compare方法而不得不传递一个Comparator类 ,这种方式显得比较笨拙,而且大量使用的话代码严重冗余,这种情况在java8中通过使用lambda表达式来解决。

lambda表达式专门针对只有一个方法的接口(即函数式接口),Comparator就是一个函数式接口

@FunctionalInterface
public interface Comparator<T> {
 int compare(T o1, T o2);
}

@FunctionalInterface的作用就是标识一个接口为函数式接口,此时Comparator里只能有一个抽象方法,由编译器进行判定。

使用lambda表达式之后方式1 中的代码改造如下

1.1.2 方式2-lambda表达式

public void test1_(){
  List<Student> studentList = new ArrayList<Student>(){
   {
    add(new Student("stu1",100.0));
    add(new Student("stu2",97.0));
    add(new Student("stu3",96.0));
    add(new Student("stu4",95.0));
   }
  };
  Collections.sort(studentList,(s1,s2)-> Double.compare(s1.getScore(),s2.getScore()));
  System.out.println(studentList);
 }

1.2 lambda语法

1.2.1 多参数

(1). lambda表达式的基本格式为(x1,x2)->{表达式...};

(2). 在上式中,lambda表达式带有两个参数,此时参数类型可以省略,但两边的括号不能省略

(3). 如果表达式只有一行,那么表达式两边的花括号可以省略

1.2.2 无参数

一个常见的例子是新建一个线程,不使用lambda表达式的写法为

public void testThread(){
  new Thread(new Runnable() {
   @Override
   public void run() {
    System.out.println("hello, i am thread!");
   }
  }).start();
 }

其中Runnable接口也是一个函数式接口,源码如下

@FunctionalInterface
public interface Runnable {
 /**
  * When an object implementing interface <code>Runnable</code> is used
  * to create a thread, starting the thread causes the object's
  * <code>run</code> method to be called in that separately executing
  * thread.
  * <p>
  * The general contract of the method <code>run</code> is that it may
  * take any action whatsoever.
  *
  * @see  java.lang.Thread#run()
  */
 public abstract void run();
}

将其转换为lambda表达式的写法为

public void testThread_(){
 new Thread(()-> System.out.println("hello, i am thread!")).start();
}

对于没有参数的情况 :

(1).参数的括号不能省略,

(2).其他语法同多参数

1.2.3 一个参数

我们构造一个只有一个参数的函数式接口

@FunctionalInterface
public interface MyFunctionalInterface {
 public void single(String msg);
}

/**
 * 需要单个参数
 */
public static void testOnePar(MyFunctionalInterface myFunctionalInterface){
 myFunctionalInterface.single("msg");
}
/**
  * 一个参数,可以省略参数的括号
  */
 @Test
 public void testOneParameter(){
  testOnePar(x-> System.out.println(x));
 }

对于一个参数的情况:

(1).可以省略参数的括号和类型

(2).其他语法同多参数

1.3 jdk提供的常用函数式接口

在这里我们为了演示只有一个参数的情况自己创建了一个函数式接口,其实java8中已经为我们提供了很多常见的函数式接口,截图如下:

常见的有

Function:提供任意一种类型的参数,返回另外一个任意类型返回值。 R apply(T t);

Consumer:提供任意一种类型的参数,返回空值。 void accept(T t);

Supplier:参数为空,得到任意一种类型的返回值。T get();

Predicate:提供任意一种类型的参数,返回boolean返回值。boolean test(T t);

因此针对上面的情况,我们可以直接使用Consumer类,

/**
  * 需要单个参数
  */
 public static void testOnePar1(Consumer unaryOperator){
  unaryOperator.accept("msg");
 }

2.方法引用

lambda表达式用于替换函数式接口,方法引用也是如此,方法引用可以使代码更加简单和便捷

2.1 小试牛刀

上代码,根据List中字符串长度排序:

public static void test1_() {
 List<String> strLst = new ArrayList<String>() {
  {
   add("adfkjsdkfjdskjfkds");
   add("asdfasdfafgfgf");
   add("public static void main");
  }
 };
 Collections.sort(strLst, String::compareToIgnoreCase);
 System.out.println(strLst);
}

只要方法的参数和返回值类型与函数式接口中抽象方法的参数和返回值类型一致,就可以使用方法引用。

2.2 使用方式

方法引用主要有如下三种使用情况

(1). 类::实例方法

(2). 类::静态方法

(3). 对象::实例方法

其中后两种情况等同于提供方法参数的lambda表达式,

如System.out::println 等同于(x)->System.out.println(x),

Math::pow 等同于(x,y)->Math.pow(x,y).

第一种中,第一个参数会成为执行方法的对象,String::compareToIgnoreCase)等同于(x,y)->x.compareToIgnoreCase(y)

此外,方法引用还可以使用this::methodName及super::methodName表示该对象或者其父类对象中的方法

class Father {
 public void greet() {
  System.out.println("Hello, i am function in father!");
 }
}

class Child extends Father {
 @Override
 public void greet() {
  Runnable runnable = super::greet;
  new Thread(runnable).start();
 }
}
public static void main(String[] args){
  new Child().greet();
 }

最后打印的结果为:Hello, i am function in father!

3.构造器引用

构造器引用同方法引用类似,同样作用于函数式接口

构造器引用的语法为 ClassName::new

啥也不说,线上代码

List<String> labels = Arrays.asList("aaa","bbb","ccc","ddd");
Stream<Button> buttonStream = labels.stream().map(Button::new);

如上代码所示,map方法内需要一个Function对象

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

调用Button的构造器,接收一个String类型的参数,返回一个Button类型的对象

public class Button extends ButtonBase {
  /**
   * Creates a button with the specified text as its label.
   *
   * @param text A text string for its label.
   */
  public Button(String text) {
   super(text);
   initialize();
  }
 }

另外一个例子如下

Button[] buttons1 = buttonStream.toArray(Button[]::new);

toArray方法的申明如下

<A> A[] toArray(IntFunction<A[]> generator);

接收一个IntFunction类型的接口R apply(int value);该接口接收一个int型参数,返回指定类型

调用数组的初始化方法刚好适合。

有一个简单的构造器引用的例子如下:

public class LambdaTest3 {

 @Test
 public void test1_(){
  List<Integer> list = this.asList(ArrayList::new ,1,2,3,4,5);
  list.forEach(System.out::println);
 }
 public <T> List<T> asList(MyCrator<List<T>> creator,T... a){
  List<T> list = creator.create();
  for (T t : a)
   list.add(t);
  return list;
 }
}
interface MyCrator<T extends List<?>>{
 T create();
}

我们在项目中经常使用asList来创建一个ArrayList,但是也只能是ArrayList,

public static <T> List<T> asList(T... a) {
 return new ArrayList<>(a);
}

我们如何在asList中指定创建哪种类型的List的实例呢,使用构造器引用使得asList方法可以指定生成的List类型。

4.自由变量的作用范围

啥都不说,上代码先:

public class LambdaTest4 {
 public void doWork1(){
  Runnable runnable = ()->{
   System.out.println(this.toString());
   System.out.println("lambda express run...");
  };
  new Thread(runnable).start();
 }

 public void doWork2(){
  Runnable runnable = new Runnable() {
   @Override
   public void run() {
    System.out.println(this.toString());
    System.out.println("anony function run...");
   }
  };
  new Thread(runnable).start();
 }
 public static void main(String[] args) {
  new LambdaTest4().doWork1();
  new LambdaTest4().doWork2();
 }
}

代码中doWork1和doWork2分别使用lambda表达式和匿名内部类的方式实现了Runnable接口,最后打印的结果如下

com.java8.lambda.LambdaTest4@74f84cf
lambda express run...
com.java8.lambda.LambdaTest4$1@4295c176
anony function run...

可见使用lambda表达式的方式,表达式中的this指的是包含lambda表达式的类,而使用匿名内部类的方式,this指的是匿名内部类本身。

4.1 自由变量和闭包

lambda达式中的变量有几类,1.参数内的变量,2.lambda表达式中的内部变量,3.自由变量,自由变量指的是在lambda表达式之外定义的变量。

包含自由变量的代码则称为闭包,如果理解了lambda表达式会在编译阶段被转换为匿名内部类,那么可以很容易理解自由变量在lambda表达式中的作用范围,在lambda表达式中会捕获所有的自由变量,并且将变量定义为final类型,所以不能改变lambda表达式中自由变量的值,如果改变,那么首先就无法编译通过。

对于引用类型(如ArrayList),final指的是引用指向的类始终不变,进行add操作是允许的,但是应该保证变量的线程安全。

代码如下所示:

public class Outer {
 public AnnoInner getAnnoInner(int x) {
  int y = 100;
  return new AnnoInner() {
   int z = 100;
   @Override
   public int add() {
    return x + y + z;
   }
  };
 }
 public AnnoInner AnnoInnergetAnnoInner1(List<Integer> list1) {
  List<Integer> list2 = new ArrayList<>(Arrays.asList(1, 2, 3));
  return ()->{
   list2.add(123);
   int count = 0;
   Iterator<Integer> it = list1.iterator();
   while (it.hasNext()){
    count+=it.next();
   }
   Iterator<Integer> it1 = list2.iterator();
   while (it1.hasNext()){
    count+=it1.next();
   }
   return count;
  };
 }
 @Test
 public void test(){
  AnnoInner res = new Outer().AnnoInnergetAnnoInner1(new ArrayList<>(Arrays.asList(1,2,3)));
  System.out.println(res.add());
 }
}
interface AnnoInner {
 int add();
}

最后返回135

5.接口的静态方法和默认方法

java8对于接口做出了种种改进,使得我们可以在接口中实现默认方法和静态方法,见Comparator接口完整定义

@FunctionalInterface
public interface Comparator<T> {
 int compare(T o1, T o2);
 boolean equals(Object obj);
 default Comparator<T> reversed() {
  return Collections.reverseOrder(this);
 }
 default Comparator<T> thenComparing(Comparator<? super T> other) {
  Objects.requireNonNull(other);
  return (Comparator<T> & Serializable) (c1, c2) -> {
   int res = compare(c1, c2);
   return (res != 0) ? res : other.compare(c1, c2);
  };
 }
 default <U> Comparator<T> thenComparing(
   Function<? super T, ? extends U> keyExtractor,
   Comparator<? super U> keyComparator)
 {
  return thenComparing(comparing(keyExtractor, keyComparator));
 }
 default <U extends Comparable<? super U>> Comparator<T> thenComparing(
   Function<? super T, ? extends U> keyExtractor)
 {
  return thenComparing(comparing(keyExtractor));
 }
 default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {
  return thenComparing(comparingInt(keyExtractor));
 }
 default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) {
  return thenComparing(comparingLong(keyExtractor));
 }
 default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor) {
  return thenComparing(comparingDouble(keyExtractor));
 }
 public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
  return Collections.reverseOrder();
 }
 @SuppressWarnings("unchecked")
 public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
  return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
 }
 public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {
  return new Comparators.NullComparator<>(true, comparator);
 }
 public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {
  return new Comparators.NullComparator<>(false, comparator);
 }
 public static <T, U> Comparator<T> comparing(
   Function<? super T, ? extends U> keyExtractor,
   Comparator<? super U> keyComparator)
 {
  Objects.requireNonNull(keyExtractor);
  Objects.requireNonNull(keyComparator);
  return (Comparator<T> & Serializable)
   (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
            keyExtractor.apply(c2));
 }
 public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
   Function<? super T, ? extends U> keyExtractor)
 {
  Objects.requireNonNull(keyExtractor);
  return (Comparator<T> & Serializable)
   (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
 }
 public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
  Objects.requireNonNull(keyExtractor);
  return (Comparator<T> & Serializable)
   (c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
 }
 public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor) {
  Objects.requireNonNull(keyExtractor);
  return (Comparator<T> & Serializable)
   (c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2));
 }
 public static<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) {
  Objects.requireNonNull(keyExtractor);
  return (Comparator<T> & Serializable)
   (c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2));
 }
}

在比较器接口中定义了若干用于比较和键提取的静态方法和默认方法,默认方法的使用使得方法引用更加方便,例如使用java.util.Objects类中的静态方法isNull和nonNull可以在Stream中很方便的进行null的判定(之后会有对于stream的介绍)。但是在接口中引入默认方法设计到一个问题,即

(1).接口中的默认方法和父类中方法的冲突问题

(2).接口之间引用的冲突问题

对于第一个冲突,java8规定类中的方法优先级要高于接口中的默认方法,所以接口中默认方法复写Object类中的方法是没有意义的,因为所有的接口都默认继承自Object类使得默认方法一定会被覆盖。

对于第二个冲突,java8强制要求子类必须复写接口中冲突的方法。如下所示:

public class LambdaTest5 implements myInterface1, myInterface2 {
 @Override
 public void getName() {
  myInterface1.super.getName();
 }
 public static void main(String[] args) {
  new LambdaTest5().getName();
 }
}
interface myInterface1 {
 default void getName() {
  System.out.println("myInterface1 getName");
 }
 ;
}
interface myInterface2 {
 default void getName() {
  System.out.println("myInterface2 getName");
 }
}

强制使用myInterface1中的getName方法

总结

以上所述是小编给大家介绍的Java8中的lambda表达式入门教程,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • Java8新特性lambda表达式有什么用(用法实例)

    我们期待了很久lambda为java带来闭包的概念,但是如果我们不在集合中使用它的话,就损失了很大价值.现有接口迁移成为lambda风格的问题已经通过default methods解决了,在这篇文章将深入解析Java集合里面的批量数据操作(bulk operation),解开lambda最强作用的神秘面纱. 1.关于JSR335 JSR是Java Specification Requests的缩写,意思是Java 规范请求,Java 8 版本的主要改进是 Lambda 项目(JSR 335),其

  • Java8中lambda表达式的应用及一些泛型相关知识

    语法部分就不写了,我们直接抛出一个实际问题,看看java8的这些新特性究竟能给我们带来哪些便利 顺带用到一些泛型编程,一切都是为了简化代码 场景: 一个数据类,用于记录职工信息 public class Employee { public String name; public int age; public char sex; public String time; public int salary; } 我们有一列此类数据 List<Employee> data = Arrays.asL

  • Java8 新特性Lambda表达式实例详解

    Java8 新特性Lambda表达式实例详解 在介绍Lambda表达式之前,我们先来看只有单个方法的Interface(通常我们称之为回调接口): public interface OnClickListener { void onClick(View v); } 我们是这样使用它的: button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { v.setText("

  • Java8中的 Lambda表达式教程

     1. 什么是λ表达式 λ表达式本质上是一个匿名方法.让我们来看下面这个例子: public int add(int x, int y) { return x + y; } 转成λ表达式后是这个样子: (int x, int y) -> x + y; 参数类型也可以省略,Java编译器会根据上下文推断出来: (x, y) -> x + y; //返回两数之和 或者 (x, y) -> { return x + y; } //显式指明返回值 可见λ表达式有三部分组成:参数列表,箭头(-&g

  • Java8深入学习系列(一)lambda表达式介绍

    前言 最近在学习java8,所以接下来会给大家介绍一系列的Java8学习内容,那么让我们先从lambda表达式开始. 众所周知从java8出现以来lambda是最重要的特性之一,它可以让我们用简洁流畅的代码完成一个功能. 很长一段时间java被吐槽是冗余和缺乏函数式编程能力的语言,随着函数式编程的流行java8种也引入了 这种编程风格.在此之前我们都在写匿名内部类干这些事,但有时候这不是好的做法,本文中将介绍和使用lambda, 带你体验函数式编程的魔力. 什么是lambda? lambda表达

  • Java8新特性之Lambda表达式浅析

    说到java 8,首先会想到lambda(闭包)以及虚拟扩展方法(default method),这个特性早已经被各大技术网站炒得沸沸扬扬了,也是我们java 8系列开篇要讲的第一特性(JEP126 http://openjdk.java.net/jeps/126),jdk8的一些库已经应用了lambda表达式重新设计了,理解他对学习java 8新特性有着重要的意义. 一.函数式接口 函数式接口(functional interface 也叫功能性接口,其实是同一个东西).简单来说,函数式接口是

  • 浅析Java8新特性Lambda表达式和函数式接口

    什么是Lambda表达式,java8为什么使用Lambda表达式? "Lambda 表达式"(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数.我们可以把 Lambda表达式理解为是 一段可以传递的代码.最直观的是使用Lambda表达式之后不用再写大量的匿名内部类,简化代码,提高了代码的可读性. // 启动一个线程,不使用Lambda

  • Java8中的lambda表达式入门教程

    1.基本介绍 lambda表达式,即带有参数的表达式,为了更清晰地理解lambda表达式,先上代码: 1.1 两种方式的对比 1.1.1 方式1-匿名内部类 class Student{ private String name; private Double score; public Student(String name, Double score) { this.name = name; this.score = score; } public String getName() { ret

  • 详解Java8中的Lambda表达式

    Lambda是什么 Lambda表达式,也可称为闭包,是java8的新特性,作用是取代大部分内部类,优化java代码结构,让代码变得更加简洁紧凑. Lambda的基本语法 (expression)->expression 或 (expression)->{statements;} Lambda最重要特点 用()->{}代码块替代匿名内部类 //(param)->expression;//(param)->statment;//(param)->{statments};/

  • 详解Java8中的lambda表达式、::符号和Optional类

    Java8中的lambda表达式.::符号和Optional类 0. 函数式编程 函数式编程(Functional Programming)属于编程范式(Programming Paradigm)中的用语,此外还有命令式编程(Imperative Programing)等,有兴趣的同学可以自行了解,我们这里大概解释一下函数式编程,在函数式编程中,输入一旦确定了,输出都确定了,函数调用的结果只依赖于传入的输入变量和内部逻辑,不依赖于外部,这样的写出的函数没有副作用.举个例子: public cla

  • java8中的lambda表达式,看这篇绝对够

    目录 Lambda表达式 特性 一.lambda表达式介绍 1.1 lambda表达式结构 1.2 常见的Lambda表达式 1.3 基本语法 1.4 类型检查 1.5 类型推断 1.6 变量作用域 1.7 方法引用 1.8 构造器引用 二.在何处使用lambda表达式 2.1 函数式接口介绍 2.2 常见的函数式接口 2.3 常见的Lambda和已有的实现 2.4 针对装箱拆箱的优化 2.5 复合Lambda函数 Lambda表达式 Lambda是简洁的标识可传递匿名函数的一种方式.“互动”事

  • java8中的lambda表达式简介

    目录 Lambda表达式的语法 Lambda表达式作用域 方法引用 指向静态方法的方法引用 指向任意类型实例方法的方法引用 指向现有对象的实例方法的方法引用 构造方法引用 lambda与匿名内部类 匿名内部类 总结 Lambda表达式类似匿名函数,简单地说,它是没有声明的方法,也即没有访问修饰符.返回值声明和方法名. Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中). Lambda表达式的语法 (parameters) -> expression 或 (parameters)

  • JAVA8 lambda表达式权威教程

    Java 8新特性----Stream流 jdk8是Java 语言开发的一个主要版本,它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的Stream API 等等.今天就重点介绍一个非常重要得特性之一 lambda表达式 一:什么是 Stream? Stream(流)是一个来自数据源的元素队列并支持聚合操作 Java中的Stream并不会存储元素,而是按需计算. 数据源 流的来源. 可以是集合,数组,I/O channel, 产生器generator 等. 聚合操作 类似

  • Java8与Scala中的Lambda表达式深入讲解

    前言 最近几年Lambda表达式风靡于编程界.很多现代编程语言都把它作为函数式编程的基本组成部分.基于JVM的编程语言如Scala.Groovy及Clojure把它作为关键部分集成在语言中.而如今,(最终)Java 8也加入了这个有趣的行列. Java8 终于要支持Lambda表达式!自2009年以来Lambda表达式已经在Lambda项目中被支持.在那时候,Lambda表达式仍被称为Java闭包.在我们进入一些代码示例以前,先来解释下为什么Lambda表达式在Java程序员中广受欢迎. 1.为

  • python中的lambda表达式用法详解

    本文实例讲述了python中的lambda表达式用法.分享给大家供大家参考,具体如下: 这里来为大家介绍一下lambda函数. lambda 函数是一种快速定义单行的最小函数,是从 Lisp 借用来的,可以用在任何需要函数的地方 .下面的例子比较了传统的函数定义def与lambda定义方式: >>> def f ( x ,y): ... return x * y ... >>> f ( 2,3 ) 6 >>> g = lambda x ,y: x *

随机推荐