Java8函数式编程和lambda表达式

本文介绍了Java8的函数式编程思想,对比了函数式编程与命令式编程的区别,并通过实例展示了Lambda表达式的使用。讨论了JDK8中函数接口的新特性,包括默认方法和函数接口的实现。同时,详细解释了方法引用的概念及其四种形式,通过实例演示了如何简化代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

函数式编程

函数式编程更多时候是一种编程的思维方式,是一种方法论。函数式与命令式编程区别主要在于:函数式编程是告诉代码你要做什么,而命令式编程则是告诉代码要怎么做。简单说,函数式编程是基于某种语法或调用API去进行编程。

例如,从整型数组中找出最小的那个数字,采用命令式编程实现如下:

public static void main(String[] args){
    int[] array={1,2,3,4,5,6,7,8,9,10};
    
    int minx=Integer.MAX_VALUE;
    for(int a:array){
        if(a<minx)
           	minx=a;
    }
    System.out.println(minx);
}

而采用函数式编程来实现,则简化为如下代码:

	int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

	int minx = IntStream.of(array).min().getAsInt();
	System.out.println(minx);

我们可以看出,命令式编程需要自己实现具体逻辑,而函数式编程则是调用API完成需要,将命令式的代码写成一系列函数调用,在函数式编程下代码更简洁、易懂,这也就是为什么要使用函数式编程的原因之一。所以才说函数式编程是告诉代码你要做什么,而命令式编程则是告诉代码要怎么做,这是一种思维的转变。

lambda表达式作为函数式编程的基础,也就显得十分重要:

Java不支持lambda表达式的时候,我们会去这样创建一个线程:

	new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("run()方法");
			}	
		}).start();

而采用lambda表达式:

	new Thread(() -> System.out.println("run()")).start();

lambda表达式一句话即完成线程的创建,它强调了函数的输入输出,而隐藏了内部实现细节,并且可以接收函数作为输入(参数)和输出(返回值).

->的左边是输入,右边是输出.上述代码参数为空。

lambda表达式的作用就是返回了Runnable接口的实现对象,这调用某个方法获取实例对象类似,只不过是将实现代码直接写进了lambda表达式中.

JDK8接口新特性

  1. 函数接口,接口只能有一个需要实现的方法,可以使用FunctionalInterface注解进行声明

    @FunctionalInterface
    public interface InterfaceOne {
    	int doubleNum(int i);
    }
    

    使用lambda表达式获取该接口的实现实例的几种写法:

    	InterfaceOne num1 = (i) -> i * 2;
    
    	System.out.println(num1.doubleNum(10));
    	InterfaceOne num2 = i -> i * 2;
    
    	// 指定参数类型
    	InterfaceOne num3 = (int i) -> i * 2;
    
    	InterfaceOne num4 = (int i) -> {
    		System.out.println(i);
    		return i * 2;
    	};
    
  2. 接口的默认方法,用于提供默认实现。默认方法和普通实现类的方法一样,可以使用this关键字。

    @FunctionalInterface
    public interface InterfaceOne {
    	int doubleNum(int i);
    }
    
    default int add(int x,int y){
        return x+y;
    }
    

    之所以说默认方法这个特性比较重要,是因为我们借助这个特性可以在以前所编写的一些接口上提供默认实现,并且不会影响任何的实现类以及既有的代码。例如我们最熟悉的List接口,在JDK1.2以来List接口就没有改动过任何代码,到了1.8之后才使用这个新特性增加了一些默认实现。这是因为如果没有默认方法的特性的话,修改接口代码带来的影响是巨大的,而有了默认方法后,增加默认实现可以不影响任何的代码。

  3. 当接口多重继承时,可能会发生默认方法覆盖的问题,这时要指定使用哪一个接口的默认方法实现。

    @FunctionalInterface
    public interface InterfaceOne {
    	int doubleNum(int i);
    
    	default int add(int x, int y) {
    		return x + y;
    	}
    }
    
    @FunctionalInterface
    interface InterfaceTwo {
    	int doubleNum(int i);
    
    	default int add(int x, int y) {
    		return x + y;
    	}
    }
    
    @FunctionalInterface
    interface InterfaceThree extends InterfaceOne, InterfaceTwo {
    	// 指定使用哪一个接口的默认方法实现
    	@Override
    	default int add(int x, int y) {
    		return InterfaceOne.super.add(x, y);
    	}
    }
    

    函数接口

    接口输入参数返回类型说明
    PredicateTboolean断言
    ConsumerT/消费一个数据
    Function<T,R>TR输入T,输出R
    Supplier/T提供一个数据
    UnaryOperatorTT一元函数(输入输出类型相同)
    BiFunction<T,U,R>(T,U)R两个输入参数的函数
    BinaryOperator(T,T)T二元函数(输入输出类型相同)

    其中最常用的是Function接口,该接口为我们省去定义一些不必要的函数接口。使用如下例子说明Function接口:

    public class TestFunction {
    
    	public static void main(String[] args) {
    		Account account = new Account("FEEL", 9999999);
    
    		Function<Long, String> func = i -> new DecimalFormat("#,###").format(i);
    		account.show(func);
    	}
    }
    
    class Account {
    	private String name;
    	private long balance;
    
    	public Account(String name, long balance) {
    		this.name = name;
    		this.balance = balance;
    	}
    
    	public void show(Function<Long, String> func) {
    		System.out.println(name + "的账户余额: " + func.apply(this.balance));
    	}
    }
    

    输出为:

    FEEL的账户余额: 9,999,999

如果不使用Function接口,则需要自己定义一个接口,并且不支持链式操作。

@FunctionalInterface
interface Format {
	String format(long i);
}

class Account {
	private String name;
	private long balance;

	public Account(String name, long balance) {
		this.name = name;
		this.balance = balance;
	}

	public void show(Format fm) {
		System.out.println(name + "的账户余额: " + fm.format(balance));
	}
}

public class Test_No_Function {

	public static void main(String[] args) {
		Account account = new Account("FEEL", 999999999);
		Format fm = i -> new DecimalFormat("#,###").format(i);

		account.show(fm);
	}
}

下面是PredicateConsumer接口的使用:

public static void main(String[] args) {
		// 断言函数接口
		Predicate<Integer> p = i -> i > 0;

		System.out.println("1>0? " + p.test(1));

		// 消费函数接口
		Consumer<String> consumer = System.out::println;
		System.out.println("Hello Consumer Interface!");

	}

输出结果:

1>0? true
Hello Consumer Interface!

这些接口一般对基本类型做了封装,所以使用基本类型时无需指定泛型:

public static void main(String[] args){
	//断言函数接口
    IntPredicate IP=i->i>0;
    System.out.println(IP.test(1));
    
    //消费函数接口
    IntConsumer ic=(v)->System.out.println("输入整数:"+v);
    ic.accept(1999);
}

输出结果:

true
输入整数:1999

下面是其他一些接口的使用方式:

public static void main(String[] args) {
		// 提供数据接口
		Supplier<Integer> supplier = () -> 10;
		System.out.println("提供的数据是: " + supplier.get());

		// 一元函数接口
		UnaryOperator<Integer> unaryOperator = i -> i >> 1;
		System.out.println("计算结果: " + unaryOperator.apply(10));

		//二元函数接口
		BinaryOperator<Integer> binaryOperator = (a, b) -> a * b;
		System.out.println("计算结果: " + binaryOperator.apply(10, 10));
	}

输出结果为:

提供的数据是: 10
计算结果: 5
计算结果: 100

方法引用

我们通常会使用lambda表达式来创建匿名方法但有时仅仅需要调用一个已存在的方法.如下:

Arrays.sort(array,(s1,s2)->s1.compareToIgnoreCase(s2));

而在Jdk8中,可以通过一个新特性来简化上述lambda表达式:

Arrays.sort(array,String::compareToIgnoreCase);

上述这行称为Method Reference(方法引用),其标准形式:

类名::方法名::(只需写方法名,不需要写括号)

方法引用的四种形式:

类型示例代码示例对应lambda表达式
引用静态方法ContainingClass::staticMethodNameString::valueOf(s)->String.valueOf(s)
引用某个对象的实例方法containingObject::instanceMethodNamex::toString()()->this.toString()
引用某个类型的任意对象的实例方法ContainingType::methodNameString::toString(s)->s.toString
引用构造方法ClassName::newString::new()->new String()

定义实体类如下:

package com.demo.after;

/**
 * @author bushizhe
 * @Time 2019年5月21日 下午4:38:56
 * @Description
 */
public class FEEL {
	private String name = "FEEL";
	private int age = 21;

	public FEEL() {

	}

	public FEEL(String name) {
		this.name = name;
	}

	public FEEL(String name, int age) {
		this.name = name;
		this.age = age;
	}

	public static void show(FEEL feel) {
		System.err.println(feel.name + "今年" + feel.age + "岁了!!");
	}

	public int getAge(int age) {
		System.out.println(name + "年龄:" + age);
		return age + 10;
	}

	@Override
	public String toString() {
		return "FEEL [name=" + name + ", age=" + age + "]";
	}

}

通过方法引用调用实体类中方法:

package com.demo.after;

import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.IntUnaryOperator;
import java.util.function.Supplier;

/**
 * @author bushizhe
 * @Time 2019年5月21日 下午4:44:20
 * @Description
 */
public class TestMethod {
	public static void main(String[] args) {
		// 方法引用,调用打印方法
		Consumer<String> consumer = System.out::println;
		consumer.accept("接收的数据:");

		// 静态方法引用
		Consumer<FEEL> consumer2 = FEEL::show;
		consumer2.accept(new FEEL("FEEL", 20));

		// 实例方法引用,通过对象实例进行引用
		FEEL feel = new FEEL();
		IntUnaryOperator func = feel::getAge;
		System.out.println("十年之后:" + func.applyAsInt(20) + "岁");

		// 无参构造方法的引用,
		Supplier<FEEL> supplier = FEEL::new;
		System.out.println("创建了新对象" + supplier.get());

		// 有参构造函数的方法引用
		BiFunction<String, Integer, FEEL> func2 = FEEL::new;
		System.out.println("创建了新对象" + func2.apply("FLING", 20));
	}
}

输出结果:

接收的数据:
FEEL今年20岁了!!
FEEL年龄:20
十年之后:30岁
创建了新对象FEEL [name=FEEL, age=21]
创建了新对象FEEL [name=FLING, age=20]

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一倾而尽

你的鼓励将是我最大的动力,谢谢

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值