溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java8和Scala的面向?qū)ο蠛秃瘮?shù)式編程有什么不同

發(fā)布時(shí)間:2021-11-30 14:29:45 來源:億速云 閱讀:130 作者:iii 欄目:大數(shù)據(jù)

這篇文章主要講解了“Java8和Scala的面向?qū)ο蠛秃瘮?shù)式編程有什么不同”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Java8和Scala的面向?qū)ο蠛秃瘮?shù)式編程有什么不同”吧!

面向?qū)ο蠛秃瘮?shù)式編程的混合:Java 8和Scala的比較

Scala是一種混合了面向?qū)ο蠛秃瘮?shù)式編程的語(yǔ)言。它常常被看作Java的一種替代語(yǔ)言,程序員們希望在運(yùn)行于JVM上的靜態(tài)類型語(yǔ)言中使用函數(shù)式特性,同時(shí)又期望保持Java體驗(yàn)的一致性。和Java比較起來,Scala提供了更多的特性,包括更復(fù)雜的類型系統(tǒng)、類型推斷、模式匹配、定義域語(yǔ)言的結(jié)構(gòu)等。除此之外,你可以在Scala代碼中直接使用任何一個(gè)Java類庫(kù)。

Scala簡(jiǎn)介

HelloWorld

命令式Scala
object Beer {
	def main(args: Array[String]){
		var n : Int = 2
		while( 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ù)式的方式實(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來實(shí)現(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)建集合是非常簡(jiǎn)單的

val authorsToAge = Map("Raoul" -> 23, "Mario" -> 40, "Alan" -> 53)

Java中那樣手工添加每一個(gè)元素:

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的集合有一個(gè)重要的特質(zhì)我們應(yīng)該牢記在心,那就是我們之前創(chuàng)建的集合在默認(rèn)情況下是只讀的。這意味著它們從創(chuàng)建開始就不能修改。

更新一個(gè)Scala集合會(huì)生成一個(gè)新的集合

val numbers = Set(2, 5, 3);
val newNumbers = numbers + 8 //這里的操作符+會(huì)將8添加到Set中,創(chuàng)建并返回一個(gè)新的Set對(duì)象
println(newNumbers)
println(numbers)

Java中提供了多種方法創(chuàng)建不可修改的(unmodifiable)集合。下面的代碼中,變量newNumbers是集合Set對(duì)象numbers的一個(gè)只讀視圖:

Set<Integer> numbers = new HashSet<>();
Set<Integer> newNumbers = Collections.unmodifiableSet(numbers);

這意味著你無法通過操作變量newNumbers向其中加入新的元素。不過,不可修改集合僅僅是對(duì)可變集合進(jìn)行了一層封裝。通過直接訪問numbers變量,你還是能向其中加入元素。

與此相反,不可變(immutable)集合確保了該集合在任何時(shí)候都不會(huì)發(fā)生變化,無論有多少個(gè)變量同時(shí)指向它。

使用集合
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開始的一個(gè)序列)訪問元組中的元素,比如:

println(book._1)
println(numbers._4)
Stream

Scala也提供了對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu),它采用延遲方式計(jì)算數(shù)據(jù)結(jié)構(gòu),名稱也叫Stream!不過Scala中的Stream提供了更加豐富的功能,讓Java中的Stream有些黯然失色。Scala中的Stream可以記錄它曾經(jīng)計(jì)算出的值,所以之前的元素可以隨時(shí)進(jìn)行訪問。

除此之外,Stream還進(jìn)行了索引,所以Stream中的元素可以像List那樣通過索引訪問。注意,這種抉擇也附帶著開銷,由于需要存儲(chǔ)這些額外的屬性,和Java 8中的Stream比起來,Scala版本的Stream內(nèi)存的使用效率變低了,因?yàn)镾cala中的Stream需要能夠回溯之前的元素,這意味著之前訪問過的元素都需要在內(nèi)存“記錄下來”(即進(jìn)行緩存)。

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語(yǔ)言中,你可以使用Option使用Optional類似的方法實(shí)現(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() < 20

Scala語(yǔ)言中,你可以直接傳遞這兩個(gè)方法給內(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() > 60

val 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")

閉包

閉包是一個(gè)函數(shù)實(shí)例,它可以不受限制地訪問該函數(shù)的非本地變量。不過Java 8中的Lambda表達(dá)式自身帶有一定的限制:它們不能修改定義Lambda表達(dá)式的函數(shù)中的本地變量值。這些變量必須隱式地聲明為final。

Scala中的匿名函數(shù)可以取得自身的變量,但并非變量當(dāng)前指向的變量值。

def main(args: Array[String]) {
	var count = 0
	val inc = () => count+=1
	inc()
	println(count)
	inc()
	println(count)
}

不過在Java中,下面的這段代碼會(huì)遭遇編譯錯(cuò)誤,因?yàn)閏ount隱式地被強(qiáng)制定義為final:

public static void main(String[] args) {
	int count = 0;
	Runnable inc = () -> count+=1;//錯(cuò)誤:count必須為final或者在效果上為final
	inc.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提供了一種特殊的語(yǔ)法可以自動(dòng)完成這部分工作。

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

更加簡(jiǎn)潔的Scala類

由于Scala也是一門完全的面向?qū)ο笳Z(yǔ)言,你可以創(chuàng)建類,并將其實(shí)例化生成對(duì)象。

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

getter方法和setter方法

單純只定義字段列表的Java類,你還需要聲明一長(zhǎng)串的getter方法、setter方法,以及恰當(dāng)?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語(yǔ)言中構(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的接口對(duì)比

Scala還提供了另一個(gè)非常有助于抽象對(duì)象的特性,名稱叫trait。它是Scala為實(shí)現(xiàn)Java中的接口而設(shè)計(jì)的替代品。trait中既可以定義抽象方法,也可以定義帶有默認(rèn)實(shí)現(xiàn)的方法。trait同時(shí)還支持Java中接口那樣的多繼承,所以你可以將它們看成與Java 8中接口類似的特性,它們都支持默認(rèn)方法。trait中還可以包含像抽象類這樣的字段,而Java 8的接口不支持這樣的特性。

trait Sized{
	var size : Int = 0
	def isEmpty() = size == 0
}

class Empty extends Sized//一個(gè)繼承自trait Sized的類
println(new Empty().isEmpty())//打印輸出true

你可以創(chuàng)建一個(gè)Box類,動(dòng)態(tài)地決定到底選擇哪一個(gè)實(shí)例支持由trait Sized定義的操作

class Box
val b1 = new Box() with Sized //在對(duì)象實(shí)例化時(shí)構(gòu)建trait
println(b1.isEmpty()) //打印輸出true
val b2 = new Box()
b2.isEmpty() //編譯錯(cuò)誤:因?yàn)锽ox類的聲明并未繼承Sized

感謝各位的閱讀,以上就是“Java8和Scala的面向?qū)ο蠛秃瘮?shù)式編程有什么不同”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)Java8和Scala的面向?qū)ο蠛秃瘮?shù)式編程有什么不同這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)
AI