2024年java8基础和应用

java8基础和应用目录 一 基础知识 1 为什么要学习 Java8 2 行为参数化 3 初识 Lambda 二 函数式数据处理 1 流的使用 2 流和集合 3 流的操作 4 流的构建 5 收集器的使用 6 分组的使用 三 学会使用 Optional 1 防御式检查

大家好,我是讯享网,很高兴认识大家。



目录

一、基础知识

1)为什么要学习 Java8

2)行为参数化

3)初识 Lambda

二、函数式数据处理

1)流的使用

2)流和集合

3)流的操作

4)流的构建

5)收集器的使用

6)分组的使用

三、学会使用Optional

1)防御式检查

2)学会使用Option

四、新的日期和时间


一、基础知识

1)为什么要学习 Java8

Java 8 所做的改变,在许多方面比Java 历史上任何一次改变都更加深远,这些改变会让你的编程更加容易

传统写法

 
讯享网 

Java 8写法

讯享网

熟悉 Linux 操作的同学对这个指令应该不默认

这种操作便是基于流式操作, 会把文件转换创建成一个流,然后会转换流中字符,会对流中的行进行排序,则会输出流的最后三行。这种就像是流水线操作,经过每个中转站,将处理完的结果转入下一个处理中心,最后得到最终结果。

Java 8 的第一个编程思想就是流处理,流式一系列数据项,一次只生成一项,程序可以从输入流中一个一个读取数据项,然后以同样的方式将数据项写入输出流。一个程序的输出流很可能就是另一个程序的输入流。

函数传递

已知一个集合中有以下几种花:

 

这个时候如果我想要红花,那么传统写法是这样子的:

讯享网

那么如果我想要8块钱以下的花,那么写法就是这样的:

 

其实代码写法大部分都是一样的,只是判断的条件不一样,那么我们进行第一版优化

我们将判断方法抽取出来:

 

借助函数式接口,将我们自定义的方法传递进去:

 

使用:

 

我们也可以借助 流来传递函数,就可以不用事先写好判断函数了:

 

默认方法

在 Java 8 之前我们可以实现一个接口然后被强制重写这个接口的方法,那么隐含着很多问题:如果动物类新增一个方法,那么其实这个类是不需要这个方法的,但是如果不重写就会编译报错。因此在 Java 8 之后也设计了默认方法这一种方式巧妙的解决了这种问题。

 

Java 8 之后可以这样写:

 

以上便是 Java 8 的部分特性,那么接下来就让我们来了解 Java 8的使用

2)行为参数化

开发中,我们需要应对不断的需求,怎么样才能做到自适应可扩展就是我们要关注的地方。

需求1:筛选出红色的花

 

需求2:筛选出绿色的话

聪明的你肯定想到了我们可以通过传递一个颜色参数来过滤花朵,而不用每一次都修改主要代码。

 

需求3:筛选出价格小于8块钱的花

这样子我们只能再写一个方法来实现这个需求,为了防止后续价格的变化,聪明的我们提前将价格设置成可变参数。

 

为了保持代码的整洁,我们被迫重写了一个方法来实现上述的需求:

 

通过来控制要筛选价格类型的花还是颜色类型的花,但是这种写法实在是不美观。

那么,我们既然都能把花的属性作为参数进行传递,那么我们能不能我们能不能把过滤花的这种行为也作为一个参数进行传递,想着想着,你就动起了手:

首先定义一个过滤行为的接口:

 

然后自定义两个行为过滤类继承这个接口:

 

然后重写我们的过滤方法,通过将行为作为参数传递:

 

这样子我们的代码已经很明了,但是我们再观察一下上面的方法,这个方法只能传递对象作为参数,而对象的核心方法也只有,如果我们有新的行为就需要新建一个类继承接口实现方法。那么我们有没有办法直接将这一个行为作为参数传递,答案是有的:Lombda.

 

我们甚至可以将多种行为作为作为一个参数传递:

 

可以看到,行为参数化是一个很有用的模式,它能够轻松地使用不断变化的需求,这种模式可以把一个行为封装起来,并通过传递和使用创建的行为将方法的行为参数化。

它可以替代匿名类

如果我们将一个鲜花的集合按照价格进行排序,我们会这样做:

 

那么通过行为参数化我们可以这样写:

 

也可以这样写:

 

甚至可以这样写:

 

对比一下传统写法,你是不是已经开始爱上这种方式的写法了

3)初识 Lambda

可以理解为是一种简洁的匿名函数的表示方式:它没有名称,但它有,,,还可以有一个可以。

表达式鼓励采用行为参数化的风格。利用表达式我们可以自定义一个对象

Lambda 例子

  • 从一个对象中抽取值,具有一个 String 类型的参数,返回一个 int 类型的值,Lambda 表达式没有 return 语句,已经隐含了 return
  • 布尔表达式,具有一个 Flower 类型的参数,返回一个 boolean 类型的值
  • 消费一个对象,具有一个 String 类型的参数,没有返回值(void)
  • 创建一个对象,没有传入参数,返回一个 int 类型的值(1)

函数式接口

函数式接口就是只定义一个抽象方法的接口,并使用标记。

例如

  •  
  •  
  •  
  •  

Lambda 表达式可以允许直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的示例(Lambda表达式就是函数式接口一个具体实现的示例)

 

使用函数式接口

这个接口中定义了一个的抽象方法,它接受泛型 T 对象,并返回一个 boolean。你如果需要 表示一个涉及类型 T 的布尔表达式时,就可以使用这个接口。

 

这个接口定义了一个的抽象方法,它接受泛型 T 对象,没有返回(void)。你如果需要访问类型 T 的对象,并对其执行某些操作,就可以使用这个接口。

 

这个接口定义了一个的抽象方法,它接受泛型 T 对象,并返回一个泛型 R 的对象。你如果需要定义一个Lambda,将输入对象的信息映射输出,就可以使用这个接口。

 

这个接口定义了一个的抽象方法,它没有传入参数,会返回一个泛型 T 的对象,如果你需要定义一个 Lambda,输出自定义的对象,就可以使用这个接口。

 

类型检查

以这个为例子:

  • 首先找出 filter 方法的声明
  • 要求第二个参数是 Predicate 类型的对象
  • Predicate 是一个函数式接口,定义了一个 的抽象方法,并返回一个boolean 类型的值

类型推断

我们可以继续将这个代码简化为:

使用局部变量

Lambda 表达式不仅能够使用主体里面的参数,也能够使用自由变量(在外层作用域中定义的变量)。

 

注意点:Lambda 表达式对于全局变量和静态变量可以没有限制的使用,但是对于局部变量必须显示声明为 final

因为实例变量是存储在中,而局部变量是存储在中,属于线程私有的。而 Lambda 是在一个线程中使用的,访问局部变量只是在访问这个变量的副本,而不是访问原始值。

方法引用

方法引用就是让你根据已有的方法实现来创建 Lambda表达式。可以看做是单一方法的 Lambda 的语法糖。

例子

 
  • ==>
  • ===>
 
  • ===>

如何构建方法引用

  • 指向静态方法的方法引用(Integer的sum方法 == )
  • 指向任意类型示例方法的方法引用(String的length方法 == )
  • 指向现有对象的示例方法的方法引用(flower实例的getPrice方法 == )

复合 Lambda 表达式

我们有一组鲜花集合如下:

 

按鲜花的价格进行排序:

 

这样子默认是使用升序进行排列的,那么我们如果想进项降序:

 

这里的粉花和白花的价格一样,那我们在价格排序完后再按照颜色排序那应该怎么做:

 

用于接口

 
 
 

用于接口

 
 

两者的区别就是操作的顺序不一样

二、函数式数据处理

1)流的使用

集合是 Java 中使用最多的API。流是 Java API 的新成员,它允许以声明式方式处理数据集合,可以看作是遍历数据集的高级迭代器。而且,刘海可以透明地并行处理,这样就可以无需多写任何多线程代码了。

现在有一组花的集合如下:

 

需求:获取10块钱以下并且按照价格排序的花的颜色

传统写法

 

为了完成这个需求不仅代码量大,还多定义了 这个临时变量,真的是糟糕透了! Java 8 之后,代码才应该有它该有的样子:

 

通过筛选出10元以下的花,然后通过按照花的价格进行排序,再通过映射出花的颜色,最后通过将流归约成一个集合。filter 处理的结果传给了 sorted 方法,再传给 map 方法,最后传给 collect 方法。

甚至我们还可以利用多核架构并行执行这段代码,只需要把换成

 

java8基础和应用

因为 、 、 和 等操作是与具体线程模型无关的高层次构件,所以它们的内部实现可以是单线程的,也可能透明地充分利用你的多核架构!在实践中,这意味着你用不着为了让某些数据处理任务并行而去操心线程和锁。

2)流和集合

集合与流之间的差异就在于什么时候进行计算。集合是一个内存中的数据结构,它包含数据结构中目前所有的值——集合中的每个元素都得出来才能添加到集合中。流则是在概念上固定的数据结构(你不能添加或删除元素),其元素则是的。从另一个角度来说,流就像是一个:只有在消费者要求的时候才会计算值

只能遍历一次:和迭代器类似,流只能遍历一次。遍历完之后,这个流已经被消费掉了。你可以从原始数据源那里再获得一个新的流来重新遍历一遍。

 

3)流的操作

流可以拆成三大操作

-> ->

 

中间操作:

操作返回类型操作参数filterStreamPredicatemapStreamFuncation<T, R>limitStreamsortedStreamComparatordistinctStreamskipStreamlonglimitStreamlongflatMapStreamFuncation<T, Steam>

终端操作

操作返回类型操作参数forEachvoidConsumercountlongcollectRCollector<T, A, R>anyMatchbooleanPredicatenoneMatchbooleanPredicateallMatchbooleanPredicatefindAnyOptionalfindFirstOptionalreduceOptionalBinaryOperator

(1)使用流

 

3A4U4.jpg

 

 

3AmHV.jpg

 

(2)映射

流支持 方法,它会接受一个函数作为参数,这个行数会被应用到每个元素上,并将其映射成一个新的元素。

 

它是创建一个新的集合,而不是修改原有的集合

(3)流的扁平化

将一个单词的集合,拆分成各个字母的集合:

===>

首先我们尝试使用看能不能解决问题:

 

可以看到,这样处理后的结果是一个数组的集合,并不是我们想要的结果,这是因为map返回的流实际上是类型的。但是我们想要的是来表示一个字符流。

既然需要的字符流,那我们使用来处理试一下:

 

这是返回了一个的集合,貌似只要将这个集合处理合并一下就可以解决问题了。所以出现了。

 

果然,已经成功解决了问题,方法就是让你把一个流中的每个值都转成另一个流,然后把所有的流连接起来成为一个流。

(4)匹配

  • 流中是否有一个元素能够匹配所给定谓词,只有有一个匹配上就返回 true

 
  • 流中的元素是否都能匹配给定的谓词,所有匹配上才能返回 true

 
  • 流中没有任何元素与给定的谓词相匹配,有一个匹配就会返回 false

 

(5)查找

  • 返回当前流中的任意元素

 
  • 返回当前流中的第一个元素

 

(6)归约

接收两个参数,一个是初始值,一个是将集合中所有元素结合的操作

也支持一个参数,将集合中所有元素结合的操作,不过返回的是一个 Option ,Option 下面会讲到

传统写法:

 

改进后:

 

传统写法:

 

改进后:

 

(7)小练习(出于网上)

(1) 找出2011年发生的所有交易,并按交易额排序(从低到高)。 (2) 交易员都在哪些不同的城市工作过? (3) 查找所有来自于剑桥的交易员,并按姓名排序。 (4) 返回所有交易员的姓名字符串,按字母顺序排序。 (5) 有没有交易员是在米兰工作的? (6) 打印生活在剑桥的交易员的所有交易额。 (7) 所有交易中,最高的交易额是多少? (8) 找到交易额最小的交易

Ur1At1.png

答案:

4)流的构建

  • 由值创建流:
 
  • 由数组创建流:
 
  • 由文件生成流:
 

5)收集器的使用

如今有一组花的集合如下:

 

这个时候我想按照花的颜色进行分类,获取一个

传统写法:

 

相信以上代码是比较常见的,那么当我们学习了 Java 8之后有没有什么比较好的写法呢:

 

一行代码解决,Java 8 真的是秀啊!

函数式变成的一个主要优势就是,我们只要告诉它 “做什么”,而不用关心“怎么做”。就像是上一个例子中,我们需要的是按颜色分组,所以我们只要跟收集器说 按照颜色分组就行。我们上面也比较经常用到的是,它的作用就是将我们需要的结果收集成一个集合。

用来计算总数

 

用来查找最大值和最小值

 

用来求和

 

用来求平均数

 

用来连接字符串

 

6)分组的使用

如今有一组花的集合如下:

 

按照颜色分组

 

统计每种颜色的数量

 

也可以支持多级分组

先按颜色分组,再按价格分组

 

先按颜色分组,再找每个颜色中最贵的花

 

这个工厂方法接受两个参数——要转换的收集器以及转换函数,并返回另一个收集器。这个收集器相当于旧收集器的一个包装, collect 操作的最后一步就是将返回值用转换函数做一个映射。在这里,被包起来的收集器就是用 maxBy 建立的那个,而转换函数 Optional::get 则把返回的 Optional 中的值提取出来。

Collectors 的常用方法

方法返回类型用途toListList把流中所有项目都收集到一个ListtoSetSet把流中所有项目都收集到一个Set,删除重复项toCollectionCollection把流中所有项目收集到给定的供应源创建的集合countingLong计算流中元素的个数summingIntInteger对流中项目的一个整数属性求和averagingIntDouble计算流中项目Integer属性的平均值joiningString连接对流中每个项目调用toString方法所生成的字符串maxByOptional一个包裹了流中按照给定比较器选出最大元素的Optional,如果为空则为Optional.empty()minByOptional一个包裹了流中按照给定比较器选出最小元素的Optional,如果为空则为Optional.empty()reducing归约操作产生的类型从一个作为累加器的初始值开始,利用 BinaryOperator 与流中的元素组个结合,从而将流归约成单个值collectingAndThen转换函数返回的类型包裹另一个收集器,对其结果应用转换函数groupingByMap<K, List>根据项目的一个属性的值对流中的项目作为组,并将属性值作为结果Map的键

三、学会使用Optional

开发中最经常遇到的异常某过于了吧。因为这就是我们为了方便甚至不可避免的像 null 引用这样的构造所付出的代价。Java 8之后仿佛出现了转机,那就是用来代替。

上面这段代码乍看之下应该没啥问题,平时开发的时候也很有可能会情不自禁的写出类似这种的代码。但是问题也就来了,真的是每个人都有手机吗,如果获取不到手机,那么调用是不是就会出现熟悉的异常了。

1)防御式检查

为了避免空指针异常,Java 8出现的为我们很好的避免了。

经典预防方式

 

每次引用都做一次判空操作,效果想必也不赖,也可以避免空指针异常。当时每一次判空都得添加一个 判断,真实让人头大。

Optional 预防

从图中可以看出 相当于是一个容器,里面可以装 T 类型的对象。当变量不存在的时候,缺失的值就会被建模成一个“空”的Optional对象,由方法返回。这就是和的区别,如果引用一个 null,那结果肯定是会触发异常,但是引用则没事。

上述代码可修改为:

 

一行代码搞定,干净利落。

2)学会使用Option

创建Optional对象

创建一个空的Optional

创建一个非空的Optional

不接受空值。如果 person 是一个空值则会抛出 NullPointException 异常,而不是等你试图访问 person 的属性才抛出异常。

创建一个可接受空值的Optional

如果 person 是 null ,那么得到的 Optional 对象就是个空对象。

使用map

Optional 中的 方法和流中的相似,都是从Optional对象中提取和转换值。

 

获取到的是一个Optional对象是为了防止获取到一个 null,我们可以通过来获取值。

默认行为

我们可以使用方法来获取 Optional 的值,也可以使用来定义一个默认值,遭遇到空的Optional值的时候,默认值会作为该方法的调用返回值。以下是Optional的常用方法:

最简单但又是最不安全的方法,如果变量存在,则直接返回封装的变量值,反之则抛出异常。

允许自己定义一个默认值在Optional为空的时候返回。

是方法的延迟调用版,在Optional对象不含值的时候执行调用。

和方法类似,在Optional对象为空的时候会抛出一个异常,但是这个异常我们可以自定义。

在Optional对象存在的执行的方法,反之不操作。也接受一个空参数的,如果

方法描述empty返回一个空的Optional实例filter如果值存在并且满足提供的谓词,就会返回包含该值的Optional对象;否则返回一个空的Optional对象get如果值存在,将该值用Optional封装返回,否则抛出一个NullPointException异常ifPresent如果值存在,就执行使用该值的方法调用,否则什么也不做ifPresent如果值存在就返回true,否则返回falsemap如果值存在,就对该值执行提供的 mapping 函数调用of将指定值用Optional封装后返回,如果该值为 null,则抛出一个 NullPointException异常ofNullable将指定值用 Optional 封装之后返回,如果该值为null,则返回一个空的 Optional 对象orElse如果有值则将其返回,否则返回一个默认值orElseGet如果有值则将其返回,否则返回一个由指定的 Supplier 接口生成的值orElseThrow如果有值则将其放回,否则抛出一个由指定的 Supplier 接口生成的异常

四、新的日期和时间

在 Java 8之前,我们对日期和时间的支持智能依赖 类,这个类无法表示日期,只能以毫秒的精度表示时间。而且它的表现方式也不是那么直观,在Java1.0的这个类中,年份的起始是 1900 年,月份的起始是 0 开始,如果我们这个时候想要构造一个 2020年7月18号的日期,我们就得这样做:

 

这种的构造方式简直是糟糕透了不是吗,对于不了解Date 的来说太不友好了。在java1.1 后出现了这个类,而中大部分方法都被废弃了,但是这个类中也有类似的问题和设计缺陷,而且两个日期类的出现,我们有时候也难以选择使用哪一个。

LocalDate

创建一个 LocalDate 对象

 

也可以使用 TemporalField 读取 LocalDate 的值

 

LocalTime

创建一个 LocalTime 对象

 

同样也可以使用 TemporalField 读取 LocalTime 的值

 

LocalDateTime

LocalDateTime 相当于合并了日期和时间,以下是创建的几种方式:

 

时间点的日期 时间类的通用方法:

方法名是否静态方法描述from是依据传入的 Temporal 对象创建对象实例now是依据系统时钟创建 Temporal 对象of是由 Temporal 对象的某个部分创建该对象的实例parse否由字符串创建 Temporal 对象的实例atOffset否将 Temporal 对象和某个时区偏移相结合atZone否将 Temporal 对象和某个时区相结合format否使用某个指定的格式器将 Temporal 对象转换为字符串( Instant 类不提供该方法)get否读取 Temporal 对象的某一部分的值minus否创建 Temporal 对象的一个副本,通过将当前 Temporal 对象的值减去一定的时长创建该副本plus否创建 Temporal 对象的一个副本,通过将当前 Temporal 对象的值加上一定的时长创建该副本with否以该 Temporal 对象为模板,对某些状态进行修改创建该对象的副本

Duration和Period

这两个类是用来表示两个时间内的间隔的

 

日期 - 时间类中表示时间间隔的通用方法:

方法名是否静态方法方法描述between是创建两个时间点之间的 intervalfrom是由一个临时时间点创建 intervalof是由它的组成部分创建 interval 的实例parse是由字符串创建 interval 的实例addTo否创建该 interval 的副本,并将其叠加到某个指定的 temporal 对象get否读取该 interval 的状态isNegative否检查该 interval 是否为负值,不包含零isZero否检查该 interval 的时长是否为零minus否通过减去一定的时间穿件该 interval 的副本multipliedBy否将 interval 的值乘以某个标量创建该 interval 的副本negated否以忽略某个时长的方式创建该 interval 的副本plus否以增加某个指定的时长的方式创建该 interval 的副本subtractFrom否从指定的temporal对象中减去该 interval

看完不赞,都是坏蛋

小讯
上一篇 2024-12-26 09:14
下一篇 2024-12-28 09:33

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/7942.html