基础复习第二十六天  java8新特性之Lambda表达式和Stream流的使用

本文深入介绍了Java8的新特性,重点讲解了Lambda表达式和Stream流的使用。首先解释了Junit单元测试的基础知识,包括测试方法的注意事项和常用注解。接着详细阐述了Lambda表达式,从函数式编程思想到Lambda的语法和实际应用,包括自定义函数式接口和各种类型的接口分类。随后,文章讨论了Stream流的优势和使用步骤,涵盖创建Stream的不同方式、中间操作和终结操作,并给出了多个实践示例。最后,提到了Optional类的API和接口的新特性,包括默认方法和静态方法的引入。
摘要由CSDN通过智能技术生成

Junit是什么

Junit是Java语言编写的第三方单元测试框架

单元测试概念

  • 单元:在Java中,一个类就是一个单元

  • 单元测试:程序猿编写的一小段代码,用来对某个类中的某个方法进行功能测试或业务逻辑测试。

Junit单元测试框架的作用

用来对类中的方法功能进行有目的的测试,以保证程序的正确性和稳定性。
能够让方法独立运行起来。

Junit单元测试框架的使用步骤

编写业务类,在业务类中编写业务方法。比如增删改查的方法
编写测试类,在测试类中编写测试方法,在测试方法中编写测试代码来测试。
 	测试类的命名规范:以Test开头,以业务类类名结尾,使用驼峰命名法
        每一个单词首字母大写,称为大驼峰命名法,比如类名,接口名...
        从第二单词开始首字母大写,称为小驼峰命名法,比如方法命名
        比如业务类类名:ProductDao,那么测试类类名就应该叫:TestProductDao
    测试方法的命名规则:以test开头,以业务方法名结尾
        比如业务方法名为:save,那么测试方法名就应该叫:testSave

测试方法注意事项

  • 必须是public修饰的,没有返回值,没有参数

  • 必须使注解@Test修饰

如何运行测试方法

  • 选中方法名 --> 右键 --> Run '测试方法名' 运行选中的测试方法

  • 选中测试类类名 --> 右键 --> Run '测试类类名' 运行测试类中所有测试方法

  • 选中模块名 --> 右键 --> Run 'All Tests' 运行模块中的所有测试类的所有测试方法

如何查看测试结果

  • 绿色:表示测试通过

  • 红色:表示测试失败,有问题

Junit常用注解(Junit4.xxxx版本)

  • @Before:用来修饰方法,该方法会在每一个测试方法执行之前执行一次。

  • @After:用来修饰方法,该方法会在每一个测试方法执行之后执行一次。

  • @BeforeClass:用来静态修饰方法,该方法会在所有测试方法之前执行一次。

  • @AfterClass:用来静态修饰方法,该方法会在所有测试方法之后执行一次。

Junit常用注解(Junit5.xxxx版本)

 @BeforeEach:用来修饰方法,该方法会在每一个测试方法执行之前执行一次。
 @AfterEach:用来修饰方法,该方法会在每一个测试方法执行之后执行一次。
 @BeforeAll:用来静态修饰方法,该方法会在所有测试方法之前执行一次。
 @AfterAll:用来静态修饰方法,该方法会在所有测试方法之后执行一次。

Junit的使用

  • 示例代码

/**
 业务类:实现加减乘除运算
 */
public class Cacluate {
    /*
     业务方法1:求a和b之和
     */
    public int sum(int a,int b){
        return  a + b + 10;
    }
    /*
     业务方法2:求a和b之差
     */
    public int sub(int a,int b){
        return  a - b;
    }
}

public class TestCacluate {

    static Cacluate c = null;

    @BeforeClass // 用来静态修饰方法,该方法会在所有测试方法之前执行一次。
    public static void init(){
        System.out.println("初始化操作");
        // 创建Cacluate对象
        c = new Cacluate();
    }

    @AfterClass // 用来静态修饰方法,该方法会在所有测试方法之后执行一次。
    public static void close(){
        System.out.println("释放资源");
        c = null;
    }

   /* @Before // 用来修饰方法,该方法会在每一个测试方法执行之前执行一次。
    public void init(){
        System.out.println("初始化操作");
        // 创建Cacluate对象
        c = new Cacluate();
    }

    @After // 用来修饰方法,该方法会在每一个测试方法执行之后执行一次。
    public void close(){
        System.out.println("释放资源");
        c = null;
    }*/

    @Test
    public void testSum(){
        int result = c.sum(1,1);
        /*
            断言:预习判断某个条件一定成立,如果条件不成立,则直接奔溃。
            assertEquals方法的参数
            (String message, double expected, double actual)
            message: 消息字符串
            expected: 期望值
            actual: 实际值
         */
        // 如果期望值和实际值一致,则什么也不发生,否则会直接奔溃。
        Assert.assertEquals("期望值和实际值不一致",12,result);
        System.out.println(result);
    }

    @Test
    public void testSub(){
        // 创建Cacluate对象
        // Cacluate c = new Cacluate();

        int result = c.sub(1,1);
        // 如果期望值和实际值一致,则什么也不发生,否则会直接奔溃。
        Assert.assertEquals("期望值和实际值不一致",0,result);
        System.out.println(result);
    }
}

Lambda表达式

函数式编程思想

在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情”。编程中的函数,也有类似的概念,你调用我的时候,给我实参为形参赋值,然后通过运行方法体,给你返回一个结果。对于调用者来做,关注这个方法具备什么样的功能。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做

  • 面向对象的思想:

  • 做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情.

  • 函数式编程思想:

  • 只要能获取到结果,谁去做的不重要,重视的是结果,不重视过程

Java8引入了Lambda表达式之后,Java也开始支持函数式编程。

Lambda表达式不是Java最早使用的,很多语言就支持Lambda表达式,例如:C++,C#,Python,Scala等。如果有Python或者Javascript的语言基础,对理解Lambda表达式有很大帮助,可以这么说lambda表达式其实就是实现SAM接口的语法糖,使得Java也算是支持函数式编程的语言。Lambda写的好可以极大的减少代码冗余,同时可读性也好过冗长的匿名内部类。

备注:“语法糖”是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的for-each语法,其实
底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲,Java中的Lambda可以被当做是匿名内部
类的“语法糖”,但是二者在原理上是不同的。

冗余的匿名内部类

当需要启动一个线程去完成任务时,通常会通过java.lang.Runnable接口来定义任务内容,并使用java.lang.Thread类来启动该线程。代码如下:

public class Demo01Runnable {
	public static void main(String[] args) {
    	// 匿名内部类
		Runnable task = new Runnable() {
			@Override
			public void run() { // 覆盖重写抽象方法
				System.out.println("多线程任务执行!");
			}
		};
		new Thread(task).start(); // 启动线程
	}
}

本着“一切皆对象”的思想,这种做法是无可厚非的:首先创建一个Runnable接口的匿名内部类对象来指定任务内容,再将其交给一个线程来启动。

代码分析:

对于Runnable的匿名内部类用法,可以分析出几点内容:

  • Thread类需要Runnable接口作为参数,其中的抽象run方法是用来指定线程任务内容的核心;

  • 为了指定run的方法体,不得不需要Runnable接口的实现类;

  • 为了省去定义一个RunnableImpl实现类的麻烦,不得不使用匿名内部类;

  • 必须覆盖重写抽象run方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错;

  • 而实际上,似乎只有方法体才是关键所在

编程思想转换

做什么,而不是谁来做,怎么做

我们真的希望创建一个匿名内部类对象吗?不。我们只是为了做这件事情而不得不创建一个对象。我们真正希望做的事情是:将run方法体内的代码传递给Thread类知晓。

传递一段代码——这才是我们真正的目的。而创建对象只是受限于面向对象语法而不得不采取的一种手段方式。那,有没有更加简单的办法?如果我们将关注点从“怎么做”回归到“做什么”的本质上,就会发现只要能够更好地达到目的,过程与形式其实并不重要。

生活举例:

当我们需要从北京到上海时,可以选择高铁、汽车、骑行或是徒步。我们的真正目的是到达上海,而如何才能到达上海的形式并不重要,所以我们一直在探索有没有比高铁更好的方式——搭乘飞机。

而现在这种飞机(甚至是飞船)已经诞生:2014年3月Oracle所发布的Java 8(JDK 1.8)中,加入了Lambda表达式的重量级新特性,为我们打开了新世界的大门。

体验Lambda的更优写法

借助Java 8的全新语法,上述Runnable接口的匿名内部类写法可以通过更简单的Lambda表达式达到等效:

public class Demo02LambdaRunnable {
	public static void main(String[] args) {
		new Thread(() -> System.out.println("多线程任务执行!")).start(); // 启动线程
	}
}

这段代码和刚才的执行效果是完全一样的,可以在1.8或更高的编译级别下通过。从代码的语义中可以看出:我们启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。

不再有“不得不创建接口对象”的束缚,不再有“抽象方法覆盖重写”的负担,就是这么简单!

函数式接口

lambda表达式其实就是实现SAM接口的语法糖,所谓SAM接口就是Single Abstract Method,即该接口中只有一个抽象方法需要实现,当然该接口可以包含其他非抽象方法。

其实只要满足“SAM”特征的接口都可以称为函数式接口,都可以使用Lambda表达式,但是如果要更明确一点,最好在声明接口时,加上@FunctionalInterface。一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。

之前学过的SAM接口中,标记了@FunctionalInterface的函数式接口的有:Runnable,Comparator,FileFilter。

Java8在java.util.function新增了很多函数式接口:主要分为四大类,消费型、供给型、判断型、功能型。基本可以满足我们的开发需求。当然你也可以定义自己的函数式接口。

自定义函数式接口

只要确保接口中有且仅有一个抽象方法即可:

修饰符 interface 接口名称 {
    public abstract 返回值类型 方法名称(可选参数信息);
    // 其他非抽象方法内容
}
接口当中抽象方法的 public abstract 是可以省略的

例如:声明一个计算器Calculator接口,内含抽象方法calc可以对两个int数字进行计算,并返回结果:

public interface Calculator {
    int calc(int a, int b);
}

在测试类中,声明一个如下方法:

public static void invokeCalc(int a, int b, Calculator calculator) {
    int result = calculator.calc(a, b);
    System.out.println("结果是:" + result);
}

下面进行测试:

public static void main(String[] args) {
    invokeCalc(1, 2, (int a,int b)-> {return a+b;});
    invokeCalc(1, 2, (int a,int b)-> {return a-b;});
    invokeCalc(1, 2, (int a,int b)-> {return a*b;});
    invokeCalc(1, 2, (int a,int b)-> {return a/b;});
    invokeCalc(1, 2, (int a,int b)-> {return a%b;});
    invokeCalc(1, 2, (int a,int b)-> {return a>b?a:b;});
}

函数式接口的分类

除了我们可以自定义函数式接口之外,jdk也给我们内置了一些函数式接口,具体分类如下

消费型接口

消费型接口的抽象方法特点:有形参,但是返回值类型是void

接口名

抽象方法

描述

Consumer

void accept(T t)

接收一个对象用于完成功能

BiConsumer<T,U>

void accept(T t, U u)

接收两个对象用于完成功能

DoubleConsumer

void accept(double value)

接收一个double值

IntConsumer

void accept(int value)

接收一个int值

LongConsumer

void accept(long value)

接收一个long值

ObjDoubleConsumer

void accept(T t, double value)

接收一个对象和一个double值

ObjIntConsumer

void accept(T t, int value)

接收一个对象和一个int值

ObjLongConsumer

void accept(T t, long value)

接收一个对象和一个long值

供给型接口

这类接口的抽象方法特点:无参,但是有返回值

接口名

抽象方法

描述

Supplier

T get()

返回一个对象

BooleanSupplier

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值