《Java8实战》笔记(15):面向对象和函数式编程的混合-Java 8和Scala的比较
面向?qū)ο蠛秃瘮?shù)式編程的混合:Java 8和Scala的比較
Scala是一種混合了面向?qū)ο蠛秃瘮?shù)式編程的語言。它常常被看作Java的一種替代語言,程序員們希望在運行于JVM上的靜態(tài)類型語言中使用函數(shù)式特性,同時又期望保持Java體驗的一致性。和Java比較起來,Scala提供了更多的特性,包括更復(fù)雜的類型系統(tǒng)、類型推斷、模式匹配、定義域語言的結(jié)構(gòu)等。除此之外,你可以在Scala代碼中直接使用任何一個Java類庫。
Scala簡介
HelloWorld
命令式Scala
object Beer {def main(args: Array[String]){var n : Int = 2while( n <= 6 ){println(s"Hello ${n} bottles of beer")n += 1}} }輸出
Hello 2 bottles of beer Hello 3 bottles of beer Hello 4 bottles of beer Hello 5 bottles of beer Hello 6 bottles of beer函數(shù)式Scala
Java 8以更加函數(shù)式的方式實現(xiàn)
public class Foo {public static void main(String[] args) {IntStream.rangeClosed(2, 6).forEach(n -> System.out.println("Hello " + n +" bottles of beer"));} }Scala來實現(xiàn)
object Beer {def main(args: Array[String]){2 to 6 foreach { n => println(s"Hello ${n} bottles of beer") }} }基礎(chǔ)數(shù)據(jù)結(jié)構(gòu):List、Set、Map、Tuple、Stream以及Option
創(chuàng)建集合
在Scala中創(chuàng)建集合是非常簡單的
val authorsToAge = Map("Raoul" -> 23, "Mario" -> 40, "Alan" -> 53)Java中那樣手工添加每一個元素:
Map<String, Integer> authorsToAge = new HashMap<>(); authorsToAge.put("Raoul", 23); authorsToAge.put("Mario", 40); authorsToAge.put("Alan", 53);Scala輕松地創(chuàng)建List(一種單向鏈表)或者Set(不帶冗余數(shù)據(jù)的集合)
val authors = List("Raoul", "Mario", "Alan") val numbers = Set(1, 1, 2, 3, 5, 8)Scala中,關(guān)鍵字val表明變量是只讀的,并由此不能被賦值(就像Java中聲明為final的變量一樣)。而關(guān)鍵字var表明變量是可以讀寫的。
不可變與可變的比較
Scala的集合有一個重要的特質(zhì)我們應(yīng)該牢記在心,那就是我們之前創(chuàng)建的集合在默認情況下是只讀的。這意味著它們從創(chuàng)建開始就不能修改。
更新一個Scala集合會生成一個新的集合
val numbers = Set(2, 5, 3); val newNumbers = numbers + 8 //這里的操作符+會將8添加到Set中,創(chuàng)建并返回一個新的Set對象 println(newNumbers) println(numbers)Java中提供了多種方法創(chuàng)建不可修改的(unmodifiable)集合。下面的代碼中,變量newNumbers是集合Set對象numbers的一個只讀視圖:
Set<Integer> numbers = new HashSet<>(); Set<Integer> newNumbers = Collections.unmodifiableSet(numbers);這意味著你無法通過操作變量newNumbers向其中加入新的元素。不過,不可修改集合僅僅是對可變集合進行了一層封裝。通過直接訪問numbers變量,你還是能向其中加入元素。
與此相反,不可變(immutable)集合確保了該集合在任何時候都不會發(fā)生變化,無論有多少個變量同時指向它。
使用集合
val fileLines = Source.fromFile("data.txt").getLines.toList() val linesLongUpper = fileLines.filter(l => l.length() > 10).map(l => l.toUpperCase())元組
Java目前還不支持元組
Scala提供了名為元組字面量
val raoul = ("Raoul", "+ 44 887007007") val alan = ("Alan", "+44 883133700")Scala支持任意大小的元組
val book = (2014, "Java 8 in Action", "Manning") val numbers = (42, 1337, 0, 3, 14)你可以依據(jù)它們的位置,通過存取器(accessor) _1、_2(從1開始的一個序列)訪問元組中的元素,比如:
println(book._1) println(numbers._4)Stream
Scala也提供了對應(yīng)的數(shù)據(jù)結(jié)構(gòu),它采用延遲方式計算數(shù)據(jù)結(jié)構(gòu),名稱也叫Stream!不過Scala中的Stream提供了更加豐富的功能,讓Java中的Stream有些黯然失色。Scala中的Stream可以記錄它曾經(jīng)計算出的值,所以之前的元素可以隨時進行訪問。
除此之外,Stream還進行了索引,所以Stream中的元素可以像List那樣通過索引訪問。注意,這種抉擇也附帶著開銷,由于需要存儲這些額外的屬性,和Java 8中的Stream比起來,Scala版本的Stream內(nèi)存的使用效率變低了,因為Scala中的Stream需要能夠回溯之前的元素,這意味著之前訪問過的元素都需要在內(nèi)存“記錄下來”(即進行緩存)。
Option
Java8的Optional
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語言中,你可以使用Option使用Optional類似的方法實現(xiàn)該函數(shù):
def getCarInsuranceName(person: Option[Person], minAge: Int) = person.filter(_.getAge() >= minAge).flatMap(_.getCar).flatMap(_.getInsurance).map(_.getName).getOrElse("Unknown")函數(shù)
Scala中的一等函數(shù)
def isJavaMentioned(tweet: String) : Boolean = tweet.contains("Java") def isShortTweet(tweet: String) : Boolean = tweet.length() < 20Scala語言中,你可以直接傳遞這兩個方法給內(nèi)嵌的filter,如下所示
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) tweets.filter(isShortTweet).foreach(println)現(xiàn)在,讓我們一起審視下內(nèi)嵌方法filter的函數(shù)簽名:
def filter[T](p: (T) => Boolean): List[T]匿名函數(shù)和閉包
匿名函數(shù)
val isLongTweet : String => Boolean= (tweet : String) => tweet.length() > 60val isLongTweet : String => Boolean= new Function1[String, Boolean] {def apply(tweet: String): Boolean = tweet.length() > 60 }isLongTweet.apply("A very short tweet")如果用Java,你可以采用下面的方式:
Function<String, Boolean> isLongTweet = (String s) -> s.length() > 60; boolean long = isLongTweet.apply("A very short tweet");isLongTweet("A very short tweet")閉包
閉包是一個函數(shù)實例,它可以不受限制地訪問該函數(shù)的非本地變量。不過Java 8中的Lambda表達式自身帶有一定的限制:它們不能修改定義Lambda表達式的函數(shù)中的本地變量值。這些變量必須隱式地聲明為final。
Scala中的匿名函數(shù)可以取得自身的變量,但并非變量當前指向的變量值。
def main(args: Array[String]) {var count = 0val inc = () => count+=1inc()println(count)inc()println(count) }不過在Java中,下面的這段代碼會遭遇編譯錯誤,因為count隱式地被強制定義為final:
public static void main(String[] args) {int count = 0;Runnable inc = () -> count+=1;//錯誤:count必須為final或者在效果上為finalinc.run();System.out.println(count);inc.run(); }科里化
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; }Stream.of(1, 3, 5, 7).map(multiplyCurry(2)).forEach(System.out::println);Scala提供了一種特殊的語法可以自動完成這部分工作。
def multiply(x : Int, y: Int) = x * y val r = multiply(2, 10);該函數(shù)的科里化版本如下:
def multiplyCurry(x :Int)(y : Int) = x * y val r = multiplyCurry(2)(10)val multiplyByTwo : Int => Int = multiplyCurry(2) val r = multiplyByTwo(10)類和trait
更加簡潔的Scala類
由于Scala也是一門完全的面向?qū)ο笳Z言,你可以創(chuàng)建類,并將其實例化生成對象。
class Hello {def sayThankYou(){println("Thanks for reading our book")} } val h = new Hello() h.sayThankYou()getter方法和setter方法
單純只定義字段列表的Java類,你還需要聲明一長串的getter方法、setter方法,以及恰當?shù)臉?gòu)造器。多麻煩啊!
public class Student {private String name;private int id;public Student(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getId() {return id;}public void setId(int id) {this.id = id;} }Scala語言中構(gòu)造器、getter方法以及setter方法都能隱式地生成,從而大大降低你代碼中的冗余:
class Student(var name: String, var id: Int) val s = new Student("Raoul", 1) println(s.name) s.id = 1337 println(s.id)Scala的trait與Java8的接口對比
Scala還提供了另一個非常有助于抽象對象的特性,名稱叫trait。它是Scala為實現(xiàn)Java中的接口而設(shè)計的替代品。trait中既可以定義抽象方法,也可以定義帶有默認實現(xiàn)的方法。trait同時還支持Java中接口那樣的多繼承,所以你可以將它們看成與Java 8中接口類似的特性,它們都支持默認方法。trait中還可以包含像抽象類這樣的字段,而Java 8的接口不支持這樣的特性。
trait Sized{var size : Int = 0def isEmpty() = size == 0 }class Empty extends Sized//一個繼承自trait Sized的類 println(new Empty().isEmpty())//打印輸出true你可以創(chuàng)建一個Box類,動態(tài)地決定到底選擇哪一個實例支持由trait Sized定義的操作
class Box val b1 = new Box() with Sized //在對象實例化時構(gòu)建trait println(b1.isEmpty()) //打印輸出true val b2 = new Box() b2.isEmpty() //編譯錯誤:因為Box類的聲明并未繼承Sized小結(jié)
- Java 8和Scala都是整合了面向?qū)ο缶幊毯秃瘮?shù)式編程特性的編程語言,它們都運行于JVM之上,在很多時候可以相互操作。
- Scala支持對集合的抽象,支持處理的對象包括List、Set、Map、Stream、Option,這些和Java 8非常類似。不過,除此之外Scala還支持元組。
- Scala為函數(shù)提供了更加豐富的特性,這方面比Java 8做得好,Scala支持:函數(shù)類型、可以不受限制地訪問本地變量的閉包,以及內(nèi)置的科里化表單。
- Scala中的類可以提供隱式的構(gòu)造器、getter方法以及setter方法。
- Scala還支持trait,它是一種同時包含了字段和默認方法的接口。
總結(jié)
以上是生活随笔為你收集整理的《Java8实战》笔记(15):面向对象和函数式编程的混合-Java 8和Scala的比较的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据结构和算法(01)--- 算法复杂度
- 下一篇: Java中int[]与Integer[]