《Java8实战》读书笔记13:Java8 与 Scala

Posted 笑虾

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Java8实战》读书笔记13:Java8 与 Scala相关的知识,希望对你有一定的参考价值。

《Java8实战》读书笔记13:Java8 与 Scala

第 15 章 面向对象和函数式编程的混合:Java 8和Scala的比较

本章内容
 什么是Scala语言
 Java 8与Scala是如何相生相承的
 Scala中的函数与Java 8中的函数有哪些区别
 类和trait

15.1.1 你好,啤酒

见识一下 scalajava8 做个对比 。
这是官方提供的在线环境用于新手了解Scala :https://scastie.scala-lang.org/

  1. 书上代码是 scala2 的,上面环境默认 scala3 到 BuildSettings 中设置一下即可。
  2. 注意这里用的是 => 而不是 java8 的 -> (我亲爱的 js 有伴了)
  3. 字符串前的 s 表示 字符串插值。(这里也有点像 js 的模板字符串)
  4. 喜欢折腾的可以配下 Idea 环境:GETTING STARTED WITH SCALA IN INTELLIJ
object Beer  
	def main(args: Array[String]) 
		2 to 6 foreach  n => println(s"你好 $n 杯啤酒")  
	 

scala3 如下:(莫非强制加返回类型?)

object HelloWorld 
	def main(args: Array[String]): Unit = 
		  2 to 6 foreach  n => println(s"你好 $n 杯啤酒") 
	

直接看个截图吧:

15.1.2 基础数据结构:List、Set、Map、Tuple、Stream 以及 Option

1. 创建集合

// 创建 Map
val authorsToAge = Map("Raoul" -> 23, "Mario" -> 40, "Alan" -> 53)
// 创建 List
val authors = List("Raoul", "Mario", "Alan")
// 创建 Set
val numbers = Set(1, 1, 2, 3, 5, 8)
  1. -> 用于完成映射
  2. var 声明变量,可读写。
  3. val 声明常量,是只读的。
  4. val authorsToAge : Map[String, Int] 可显示声明变量类型。不过Scala可以替你推断变量的类型。

2. 不可变与可变的比较

  1. Scala的集合默认都是只读`的。(Scala并未强制你必须使用不可变集合,只是各种暗示你接受这种风格)
  2. 可见其推荐的 修改 就是将变化部分创建副本,并引用无变化的部分。得到一个新的集合。
val numbers = Set(2, 5, 3); // 这里的操作符+会将8添加到Set 中,创建并返回一个新的Set对象
val newNumbers = numbers + 8 
println(newNumbers)         // (2, 5, 3, 8)
println(numbers)            // (2, 5, 3)

从例子中也能看出,拼接操作返回的是一个副本。

不可修改与不可变的比较
Java中提供了多种方法创建不可修改的(unmodifiable)集合。下面的代码中,变量newNumbers是集合Set对象numbers的一个只读视图:
Set numbers = new HashSet<>();
Set newNumbers = Collections.unmodifiableSet(numbers);
这意味着你无法通过操作变量newNumbers向其中加入新的元素。不过,不可修改集合仅仅是对可变集合进行了一层封装。通过直接访问numbers变量,你还是能向其中加入元素。
与此相反,不可变(immutable)集合确保了该集合在任何时候都不会发生变化,无论有多少个变量同时指向它。
我们在第14章介绍过如何创建一个持久化的数据结构:你需要创建一个不可变数据结构,该数据结构会保存它自身修改之前的版本。任何的修改都会创建一个更新的数据结构。

3. 使用集合

  • 因为用的在线环境,没法读本地文件,只好改下代码:(分号不是必需的)
val fileLines = List("Java-123456789", "Scala-123456789", "JavaScrip-t123456789");
val linesLongUpper = fileLines.filter(l => l.length() > 10) 
			.map(l => l.toUpperCase())
			.foreach(str => println(s"输出:$str"));

  • 中缀表达式下划线 写法:_.length()解读为l =>l.length()
val fileLines = List("Java-123456789", "Scala-123456789", "JavaScrip-t123456789");
fileLines filter (_.length() > 10) map (_.toUpperCase()) foreach (str => println(s"输出:$str"));
  • 对应 Java 8Stream调用 parallel 方法。scala 可以使用 par 方法。
val linesLongUpper = fileLines.par filter (_.length() > 10) map(_.toUpperCase())

4. 元组

声明元组

val book = (2014, "Java 8 in Action", "Manning") // 元组类型为(Int, String, String)
val numbers = (42, 1337, 0, 3, 14) // 元组类型为(Int, Int, Int, Int, Int)

读取元组(_1、_2 这东西叫 存取器accessor,元素从 1 开始算 )

println(book._1) // 2014
println(numbers._4) // 3
  1. Scala支持任意大小的元组。
  2. 但是元组中元素的最大上限为23。(Scala3规则已经变了,不止23,具体多少没试。)

5. Stream

  1. Scala 也提供了 Stream 实现 延迟计算,而且功能更加丰富。
  2. Scala 中的 Stream 可以记录它曾经计算出的值,所以之前的元素可以随时进行访问。(看来都做了缓存,是它的风格)
  3. Stream 还进行了索引,所以 Stream 中的元素可以像 List 那样通过索引访问。
  4. 可能唯一的缺陷就是,你得多买点内存。

6. Option

我们建议你在设计API时尽可能地使用Optional,这种方式下,接口用户只需要阅读方法签名就能了解他们是否应该传递一个optional值。我们应该尽量地用它替代null,避免发生空指针异常。

  • Java 版:
public String getCarInsuranceName(Optional<Person> person, int minAge)  
	return person.filter(p -> p.getAge() >= minAge) 
	 .flatMap(Person::getCar) 
	 .flatMap(Car::getInsurance) 
	 .map(Insurance::getName) 
	 .orElse("Unknown"); 

  • scala 版:
def getCarInsuranceName(person: Option[Person], minAge: Int) = 
 person.filter(_.getAge() >= minAge) 
 .flatMap(_.getCar) 
 .flatMap(_.getInsurance) 
 .map(_.getName)
 .getOrElse("Unknown")

注意 在前面的代码中,你使用的是_.getCar(并未使用圆括号),而不是_.getCar() (带圆括号)。Scala语言中,执行方法调用时,如果不需要传参,那么函数的圆括号可省的。

15.2 函数

Scala提供了下面这些特性

  1. 函数类型,它是一种语法糖,体现了Java语言中函数描述符的思想,即,它是一种符号,表示了在函数接口中声明的抽象方法的签名。这些内容我们在第3章中都介绍过。
  2. 能够读写非本地变量的匿名函数,而Java中的Lambda表达式无法对非本地变量进行写操作。
  3. 对科里化的支持,这意味着你可以将一个接受多个参数的函数拆分成一系列接受部分参数的函数。

15.2.1 Scala 中的一等函数

函数Scala语言中是一等值。这意味着它们可以像 Integer 或者 String 那样,作为参数传递,可以作为结果值返回。正如我们在前面章节所介绍的那样,Java 8中的方法引用和Lambda表达式也可以看成一等函数。

  • 使用谓词(返回一个布尔型结果的函数)定义这两个筛选条件:
def isJavaMentioned(tweet: String) : Boolean = tweet.contains("Java") 
def isShortTweet(tweet: String) : Boolean = tweet.length() < 20
val tweets = List( 
 "I love the new features in Java 8", 
 "How's it going?", 
 "An SQL query walks into a bar, sees two tables and says 'Can I join you?'" 
) 
tweets.filter(isJavaMentioned).foreach(println) // I love the new features in Java 8
tweets.filter(isShortTweet).foreach(println) // How's it going?
  1. filter的函数签名:def filter[T](p: (T) => Boolean): List[T] 表示参数p是一个函数,它接受参数类型T 返回结果类型 Boolean
  2. 函数类型 可以简写为:() => Boolean(T) => Boolean
  3. 对应Java8Predicate<T> 或 者 Function<T, Boolean>

15.2.2 匿名函数和闭包

1. 匿名函数

  • 定义一个匿名函数,它的功能是检查给定的消息长度,判断它是否超长。

对应 Java8 中函数式接口的声明方式。先看个 Java8 原版的

Function<String, Boolean> isLongTweet = (String s) -> s.length() > 60;
boolean b = isLongTweet.apply("A very short tweet");

再看 Scala 版本

val isLongTweet : String => Boolean  = new Function1[String, Boolean]  
	def apply(tweet: String): Boolean = tweet.length() > 60 

最后看 Scala 语法糖版本

val isLongTweet : String => Boolean = (tweet : String) => tweet.length() > 60
  1. 这是一个函数类型的变量,它接受一个 String 参数,返回一个布尔类型的值。
  2. 匿名函数很眼熟,没什么好说的。
  3. 这是Scala中声明匿名类的语法糖。
    Scala 中从 Function0(一个函数不接受任何参数,并返回一个结果)到 Function22(一个函数接受22个参数),它们都定义了apply方法。

Scala 还提供了另一个非常酷炫的特性,你可以使用语法糖调用apply方法,效果就像一次函数调用:

var a = isLongTweet.apply("A very short tweet");
var b = isLongTweet("A very short tweet");
print(a); // false
print(b); // false

2. 闭包

Java8中Lambda表达式自身带有一定的限制:它们不能修改定义Lambda表达式的函数中的
本地变量值。这些变量必须隐式地声明为final。

public static void main(String[] args)  
	int count = 0; 
	Runnable inc = () -> count+=1; // 错误:count 必须为 final 或者在效果上为 final
	inc.run(); 
	System.out.println(count);
	inc.run(); 

与此相反,Scala中的匿名函数可以取得自身的变量,但并非变量当前指向的变量值。比如,下面这段代码在Scala中是可能的:

object HelloWorld 
  def main(args: Array[String]): Unit = 
    var count = 0 
    val inc = () => count+=1 // 这是一个闭包,它捕获并递增count
    inc() 
    println(count) // 打印输出1
    inc() 
    println(count) // 打印输出2
  

书上说:虽然可以是可以,但是你这么玩了,你的并发运行,还想不想要了?

15.2.3 科里化

Scala 特别提供了一个构造器,帮助我们更加轻松地科里化一个现存的方法

  • 先回顾一个Java的示例
// 原函数
static int multiply(int x, int y)  
	return x * y; 
 
int r = multiply(2, 10);
// 科里化版本:
static Function<Integer, Integer> multiplyCurry(int x)  
	return (Integer y) -> x * y; 

// 应用:利用 map 将 1、3、5、7 分别 *2
Stream.of(1, 3, 5, 7) 
	.map(multiplyCurry(2)) 
	.forEach(System.out::println); // 2, 6, 10, 14
  • Scala 版本
// 原函数
def multiply(x : Int, y: Int) = x * y 
val r = multiply(2, 10);
println(r);
// 直接声明:科里化版本
def multiplyCurry(x :Int)(y : Int) = x * y 
val r = multiplyCurry(2)(10)
println(r); // 20

把前一半拆分出来用:

def multiplyCurry(x :Int)(y : Int) = x * y 
val multiplyByTwo : Int => Int = multiplyCurry(2) 
val r = multiplyByTwo(10)
println(r); // 20

15.3 类和 trait

15.3.1 更加简洁的 Scala 类

1. 声明类

class Hello  
	def sayThankYou() : Unit =  
		println("Thanks for reading our book") 
	 
 
val h = new Hello() 
h.sayThankYou()

2. getter方法和setter方法

Scala 语言中构造器、getter、setter方法都能隐式地生成:

class Student(var name: String, var id: Int);
val s = new Student("笨笨", 1); // 初始化 Student 对象
println(s.name); // 取得名称,打印输出 笨笨
s.id = 9527; // 设置id
println(s.id); // 打印输出9527

15.3.2 Scala 的 trait 与 Java 8 的接口对比

  1. trait 对应Java中的接口
  2. trait 支持默认方法。
  3. trait 支持多继承。

定义一个名为Sizedtrait,它包含一个名为size的可变字段,以及一个带有默认实现的isEmpty方法:

trait Sized 
	var size : Int = 0; // 名为size的字段
	def isEmpty() = size == 0; // 带默认实现的 isEmpty 方法

声明 Empty 继承 Sized

class Empty extends Sized; // Empty 类继承自 Sized
println(new Empty().isEmpty()); // 打印输出true

有一件事非常有趣,traitJava的接口类似,也是在对象实例化时被创建(不过这依旧是一个编译时的操作)。比如,你可以创建一个Box类,动态地决定到底选择哪一个实例支持由trait Sized定义的操作:

  • 在对象实例化时构建trait
class Box
val b1 = new Box() with Sized
println(b1.isEmpty()) // 打印输出true
  • 没有构建trait
class Box
val b2 = new Box()
b2.isEmpty() // 编译错误:因为Box类的声明并未继承Sized

15.4 小结

下面是这一章中介绍的关键概念和你应该掌握的要点。

  1. Java 8Scala都是整合了面向对象编程和函数式编程特性的编程语言,它们都运行于JVM之上,在很多时候可以相互操作。
  2. Scala支持对集合的抽象,支持处理的对象包括List、Set、Map、Stream、Option,这些和Java 8非常类似。不过,除此之外Scala还支持元组
  3. Scala为函数提供了更加丰富的特性,这方面比Java 8做得好,Scala支持:函数类型、可以不受限制地访问本地变量的闭包,以及内置的科里化表单。
  4. Scala中的类可以提供隐式构造器、getter、setter方法。
  5. Scala还支持trait,它是一种同时包含字段默认方法接口

读书总结

Scala 有个了简单的了解。

以上是关于《Java8实战》读书笔记13:Java8 与 Scala的主要内容,如果未能解决你的问题,请参考以下文章

《Java8实战》读书笔记12:函数式编程

《Java8实战》读书笔记12:函数式编程

《Java8实战》 - 读书笔记 - Parallel Stream并行流知识

《Java8实战》 - 读书笔记 - Lambda 表达式的组合用法

《Java8实战》读书笔记11:Java8中新的日期时间API

《Java8实战》读书笔记11:Java8中新的日期时间API