Scala是什么?
Scala用一种简洁的高级语言将面向对象和函数式编程结合在一起。Scala的静态类型有助于避免复杂应用程序中的错误,它的JVM和JavaScript运行时使您可以轻松访问庞大的库生态系统来构建高性能系统。
它有六大特性:
无缝JAVA互操作
Scala在JVM上运行,因此Java和Scala堆栈可以自由混合以实现完全无缝的集成。
类型推断
根据变量自动推断变量类型。
并发与分配
对集合使用数据并行操作,对并发和分发使用参与者,对异步编程使用期货。
特质
将Java风格接口的灵活性和类的功能结合在一起。提供了多重继承。
模式匹配
类似于Java的swatch...case...,但可以同时匹配类型和类型值。
高阶函数
可以将函数作为参数进行传递。
Scala由谁发起?
Scala是由 Martin Odersky 发起,下面是有关他的一些简介。
Martin是EPFL(瑞士领先的技术大学)编程研究组的教授。他在整个职业生涯中一直不断追求着一个目标:让写程序这样一个基础工作变得高效、简单、且令人愉悦。他可能比世界上任何一个人写过更多的Java和Scala代码。他编写了javac,这是大部分Java程序员所使用的编译器。他也编写了Scala编译器scalac,可谓是Scala社区飞速发展的基石。他著有《Programming in Scala》一书,是最畅销的Scala书籍。他曾经就职于IBM研究院、耶鲁大学、卡尔斯鲁厄大学以及南澳大利亚大学。在此之前,他在瑞士苏黎世联邦理工学院追随Pascal创始人Niklaus Wirth学习,并于1989年获得博士学位。
Scala环境准备
安装
- 确保您具有Java 8 JDK(也称为1.8)
javac -version
在命令行上运行,并确保您看到javac 1.8.___
- 如果您没有版本1.8或更高版本,请安装JDK
- 接下来,下载并安装IntelliJ Community Edition
- 然后,在启动IntelliJ之后,您可以按照有关如何安装IntelliJ插件的说明下载并安装Scala插件 (在插件菜单中搜索“ Scala”)。
创建项目时,我们将安装最新版本的Scala。注意:如果要打开现有的Scala项目,则可以 在启动IntelliJ时单击“ 打开”。
创建项目
- 打开IntelliJ并单击File => New => Project
- 在左侧面板上,选择Scala。在右侧面板上,选择“ IDEA”。
- 将该项目命名为HelloWorld
- 假设这是您第一次使用IntelliJ创建Scala项目,则需要安装Scala SDK。在Scala SDK字段的右侧,单击“ 创建”按钮。
- 选择最高的版本号(例如2.13.1),然后单击“ 下载”。这可能需要几分钟,但是后续项目可以使用相同的SDK。
- 创建SDK后,您将返回“新建项目”窗口,点击完成。
Scala之旅
1.基础
1.主方法
主方法是一个程序的入口点。JVM要求一个名为main
的主方法,接受一个字符串数组的参数。
通过使用对象,你可以如下所示来定义一个主方法。
def main(args: Array[String]): Unit = { }
2.表达式
表达式是可计算的语句。
println(2 + 5) //7
3.代码块(Blocks)
你可以组合几个表达式,并且用{}
包围起来。我们称之为代码块(block)。
代码块中最后一个表达式的结果,也正是整个块的结果。
println({ val y = 2 * 4 y + 1 })//9
4.函数
函数是带有参数的表达式。
你可以定义一个匿名函数(即没有名字),来返回一个给定整数加一的结果
(x: Int) =>x * 2
=>左边代表参数,右边是一个包含参数的表达式。
var double= (x: Int) =>x * 2 //注意位置 println(double(1))//2
函数可带有多个参数。
val add=(x:Int,y:Int)=>x+y println(add(1,2))//3
或者不带参数。
var getTheAnswer = () => 11 println(getTheAnswer())//11
5.方法
方法的行为和函数非常类似,但是它们之间还是有一些关键差别。
方法由def
进行定义,后面跟方法名,参数,返回值类型,等号和方法体。
def addOne(x: Int) = x + 1 println(addOne(11))
方法可以接受多个参数列表。
def addThenMultiple(x: Int, y: Int)(multiplier: Int) = (x + y) * multiplier println(addThenMultiple(3, 4)(4))
还可以不带参数。
def getAnswer()="hello" println(getAnswer())
6.类
可以用class
定义一个类,后面跟它的名字和构造参数。
class Person(addRess: String, name: String) { def say(word: String) = { println(s"${addRess}的${name}说:${word}") } }
调用
val person=new Person("地球","刘星雨") person.say("我是火星派过来拯救你们的~") //地球的刘星雨说:我是火星派过来拯救你们的~
7.对象
对象是它们自己定义的单实例,你可以看作类的单例。
你可以使用object
关键字来定义对象。
object IdFactory { private var counter = 0 def create() = { counter += 1 counter } }
调用
println(IdFactory.create()) //1
8.特质
特质包含某些字段和方法类型。可以组合多个特质。
你可以使用trait
关键字来定义特质。
class CustomizableGreeter(prefix: String, postfix: String) extends Greeter { override def greet(name: String): Unit = { println(prefix + name + postfix) } }
调用
val customGreeter=new CustomizableGreeter("How are you, ", "?") customGreeter.greet("Scala developer")
也可以不带默认实现
class CustomizableGreeter(prefix: String, postfix: String) extends Greeter { override def greet(name: String): Unit }
二. 统一类型
1.Scala类层结构
Any
是所有类型的超类型,也称为顶级类 型。它定义了一些通用的方法如equals
、hashCode
和toString
。Any
有两个直接子类:AnyVal
和AnyRef
。
AnyVal
代表值类型。有9个预定义的非空的值类型分别是:Double
、Float
、Long
、Int
、Short
、Byte
、Char
、Unit
和Boolean
。Unit
是不带任何意义的值类型,它仅有一个实例可以像这样声明:()
。所有的函数必须有返回,所以说有时候Unit
也是有用的返回类型。
AnyRef
代表引用类型。所有非值类型都被定义为引用类型。在Scala中,每个用户自定义的类型都是AnyRef
的子类型。如果Scala被应用在Java的运行环境中,AnyRef
相当于java.lang.Object
。
val list: List[Any] = List( "hello", 123, 12.11f, true, () => "What's happen" )
2.类型转换
类型转换可以按照下面的方向进行转换:
3.Nothing和Null
Nothing
是所有类型的子类型,没有一个值是 Nothing
类型的,它的用途之一是非正常终结信号,如抛异常,程序退出或者无限循环。
Null
是所有引用类型的子类型,它有一个单例由null
所定义。Null
主要使得Scala满足和其它JVM语言的 互操作性的。
三.类
1.类定义
一个最简单的类定义是class
+标识符,类名首字母应大写。
class User
但是通常里面都定义方法。
class User{ def say(name:String)={ println("我叫"+name) } }
2.构造器
构造器可以通过一个默认值来拥有可选参数。
class Point(x: Int = 0, y: Int = 0) { def defaultPoint = { println("x=" + x + ",y=" + y) } }
调用
val point1=new Point(1,2) val point2=new Point point1.defaultPoint //x=1,y=2 point2.defaultPoint //x=0,y=0
3.私有成员Getter和Setter的用法
成员默认是公有的public
。使用private
访问修饰符可以在类外部隐藏它们。
class Number(val y: Int) { private var _x = 0 def x = _x def x_=(newValue: Int) = { _x = newValue } def getNumber = { println(_x) } }
调用
val number = new Number(2) number.x = 10 number.getNumber
四.特质
特质(Traits)用于在类之间共享接口(Interface)和字段(Fields)。它们类似于Java 8的接口。类和对象(Ojbects)可以扩展特质,但是特质不能被实例化,因为特质没有参数。
1.定义一个特质
最简单的特质就是trait
关键字+标识符
trait Color
特质也可以定义泛型
trait Color[C] { def defaultColor(): C def getColor: C = { defaultColor() } }
2.使用特质
class MyColor extends Color[String] { override def defaultColor: String = "红色" override def getColor: String = { defaultColor } }
调用
val color = new MyColor(); println(color.getColor) //红色
3.子类型
凡是需要特质的地方,都可以用子类型来替换。
trait Person { def name: String } class ZhangSan extends Person { override def name: String = "张三" } class LiSi extends Person { override def name: String = "李四" }
调用
val zhangSan = new ZhangSan val liSi = new LiSi var person = ArrayBuffer.empty[Person] person.append(zhangSan) person.append(liSi) person.foreach(p => println(p.name))
五.元组
在Scala中元组是一个可以容纳不同类型元素的类。元组是不可变的。
当我们从函数返回多个值时,元组会派上用场。
元组可以创建如下:
val ingredient=(12,"hello",1.1):Tuple3[Int,String,Double]
这将创建一个包含Int元素,一个String元素和一个Double元素的元组。
Scala包含一系列元素:Tuple1,Tuple10等,直到Tuple22。因此我们创建一个n个 元素(n位于1~22之间)的元组时,Scala基本上从上述一组实例中实例化一个类,使用组成元素进行参数化。
1.访问元素
使用下划线来访问元组中的元素。'tuple._n'取出了第n个元素。
调用上面的实例
println(ingredient._2) //hello
2.解构元素数组
val ingredient=(12,"hello",1.1) val(age,word,num)=ingredient println(age) //12 println(word) //hello
元素数组也可以用于模式匹配。
val personList = List(("张三", 22), ("李四", 24), ("王五", 20))
personList.foreach(p => { p match { case ("张三", distance1) => println("我是张三") //我是张三 case p if (p._1 == "李四") => println("我是李四") //我是李四 case _ => println("我是无名氏") //我是无名氏 } })
或者,在'for'表达式中。
for (x <- personList) { println(x._1 + "," + x._2) }
输出:
张三,22
李四,24
王五,20
六.通过混入(mixin)来组合类
当某个特质被用于组合类时,被称为混入。
abstract class A { val message: String } class B extends A { val message = "hello" } trait C extends A { def upperMessage = message.toUpperCase() } class D extends B with C
调用
val d = new D println(d.message) println(d.upperMessage)
七.高阶函数
高阶函数是指使用其它函数作为参数、或者返回一个函数作为结果的函数。
最常见的一个例子是scala集合类(collections)的高阶函数map
。
val listNum = Seq(1, 2, 3) val addOne = (x: Int) => x + 1 val result=listNum.map(addOne) result.foreach(println) //2 3 4
1.强制转换方法为函数
你同样可以传入一个方法作为高阶函数的参数,这是因为Scala编译器会将方法强制转换成一个函数。
val listNum = Seq(1, 2, 3) def addTwo(x:Int)=x+2 val addTwoResult=listNum.map(addTwo) addTwoResult.foreach(println)
2.接收函数作为参数的函数
有的时候你需要将函数作为参数减少代码冗余。
//定义一个处理方法 private def add(listNum: List[Int], addFunction: Int => Int) = listNum.map(addFunction) //定义加1计算 def addOne(listNum: List[Int]) = { add(listNum, x => x + 1) } //定义加2计算 def addTwo(listNum: List[Int]) = { add(listNum, x => x + 2) }
调用
val listNum = List(1, 2, 3, 4) val addOneResult = addOne(listNum) val addTwoResult = addTwo(listNum) addOneResult.foreach(println) //2 3 4 5 addTwoResult.foreach(println) //3 4 5 6
3.返回函数的参数
有的时候方法需要返回一个函数
//定义一个方法让x和y相加,然后返回一个带z参数的函数。 def add(x: Int, y: Int) = (z: Int) => x + y + z
调用
var addResult = add(1, 2) var result = addResult(3) println(result)//6
八.嵌套方法
在Scala中可以嵌套定义方法,例如下面对象提供了一个factorial
方法计算给定的阶乘:
def factorial(x: Int): Int = { def fact(x: Int, accumulator: Int): Int = { if (x <= 1) accumulator else fact(x - 1, x * accumulator) } fact(x, 1) }
调用
println("Factorial of 3: " + factorial(3))//6
九.多参数列表
方法可以定义多个参数, 当使用较少的参数列表调用多参数列表的方法时,会产生一个新的函数,该函数接收剩余的参数列表作为其参数。这被称为柯里化。
下面是一个例子,在Scala集合 trait TraversableOnce
定义了 foldLeft
def foldLeft[B](z: B)(op: (B, A) => B): B
调用
val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) val res = numbers.foldLeft(1)((m,n)=>m*n) println(res) //3628800
1.单一的函数参数
在某些情况下存在单一的函数参数时,例如上述例子foldLeft
中的op
,多参数列表可以使得传递匿名函数作为参数的语法更为简洁。如果不使用多参数列表,代码可能像这样:
numbers.foldLeft(0,{(m:Int,n:Int)=>m+n})
2.隐式(IMPLICIT)参数
如果要指定参数列表中的某些参数为隐式(implicit),应该使用多参数列表。例如:
def execute(arg: Int)(implicit ec: ExecutionContext) = ???
十.案例类(CASE CLASS)
案例类(Case Class)和普通类差不多,但是案例类非常适合不可变的数据。
1.定义一个案例类
一个最简单的案例类由关键字case class
case class Test()
调用
val test1 = Test//直接调用 val test2 = new Test//使用new调用
可以看到方法是可以直接不通过new进行调用的,这是因为case类默认有个apply
方法来负责创建对象。
2.比较
case class Test1(x: Int, y: Int) //定义一个case类 class Test2(x: Int, y: Int) //定义一个普通类
调用
val test1_1 = Test1(2, 4) val test1_2 = Test1(2, 4) if (test1_1 == test1_2) println("两个类数据相同") else println("两个类是数据不相同")//两个类数据相同 val test2_1 = new Test2(2, 4) val test2_2 = new Test2(2, 4) if (test2_1 == test2_2) println("两个类数据相同") else println("两个类是数据不相同")//两个类是数据不相同
通过演示可以看到,case 类比较的是具体的值,而普通类比较的是引用。
3.拷贝
case class Test(name: String, age: Int)
调用
val test = Test("张三", 18) val test2 = test.copy(name = "李四")//将test值copy给test2,并覆盖name println(test2.name)//李四 println(test2.age)//18
十一.模式匹配
模式匹配是校验某个值(value)是否匹配一个模式的机制,一个成功的匹配同时会将值解构成其组成成分。它是Java中switch
的升级版,同时会代替一系列if/else语句。
1.语法
一个模式匹配包含特定的值,match
关键字,以及至少一个case
。
var x: Int =11 def matchTest = x match { case 1 => "one" case 2 => "two" case 3 => "three" case _=>"no" } println(matchTest)//no,如果不定义_=>则匹配不到11会报异常。
2.案例类(case classses)的匹配
案例类非常适用于模式匹配。
abstract class Person case class ZhangSan(msg: String) extends Person //继承Person case class LiSi(msg: String) extends Person //继承Person
调用
def talk(person: Person) = person match { // case ZhangSan(msg) => "张三说" + msg case LiSi(message) => "李四说" + message } val zhangSan = new ZhangSan("我吃饭了") val lisi = new LiSi("一起去打羽毛球") println(talk(zhangSan)) println(talk(lisi))
3.模式守卫(Pattern gaurds)
为了让匹配更具体,可以加上模式守卫,也就是在case 后面加上if。例如还是上面案例经过简单改造:
def talk(person: Person) = person match { // case ZhangSan(msg,isShow) if isShow => "张三说" + msg //加了个控制是否显示的参数 case LiSi(message,isShow) if isShow => "李四说" + message//加了个控制是否显示的参数 case other=>"我什么也没说" }
val zhangSan = new ZhangSan("我吃饭了",false) val lisi = new LiSi("一起去打羽毛球",true) println(talk(zhangSan))//我什么也没说 println(talk(lisi))//李四说一起去打羽毛球
4.仅匹配类型
上面的匹配的实例,也可以仅仅匹配类型。
case class ZhangSan(msg: String) extends Person //继承Person case class LiSi(msg: String) extends Person //继承Person
def talk(person: Person) = person match { // case p: ZhangSan => "张三说" + p.msg //仅匹配ZhangSan类型 case p: LiSi => "李四说" + p.msg //仅匹配LiSi类型 } val zhangSan = new ZhangSan("我吃饭了") val lisi = new LiSi("一起去打羽毛球") println(talk(zhangSan))//张三说我吃饭了 println(talk(lisi))//李四说一起去打羽毛球
5.密封类
特质(trait)和类(class)都可以用sealed
标记为密封的,这意味着所有子类必须和定义类在相同文件中,从而保证所有子类都是已知的。
sealed abstract class Person
十二.单例对象
单例对象是一种特殊的类,有且只有一个实例。和惰性变量一样,单例对象是延迟创建的,它第一次使用时创建。
单利对象使用关键字object
定义
1.定义一个单利对象
object Test { def googleEmial(userName: String): String = s"${userName}@Google.com" }
object Test2 { def main(args: Array[String]): Unit = { println(googleEmial("zhangsan"))//zhangsan@Google.com } }
2.伴生对象
当一个单利对象和某个类共享一个名称时,这个单利对象称为 伴生对象。同理,这个类被称为这个对象的伴生类。类和伴生对象可以互相访问其私有成员。使用伴生对象来定义那些不依赖实例化而存在的成员变量或者方法。
object Person { private def say(userName: String) = println(userName) } case class Person() { import Person._ //注意这里要引入一下 var userName = "张三 " val result=say(userName)//这里就可以调用私有方法打印了 }
调用
val person=new Person person.result
十三.正则表达式模式
正则表达式是找出数据中指定的字符串。.r
方法可使任意字符串变成一个正则表达式。
val numberPartten = "[0-9]".r def numP(str: String) = numberPartten.findFirstMatchIn(str) match { case Some(_) => println("ok") case None => println("no") } numP("12d") //ok ,因为带有数字中的0~9 numP(str = "d") //no,因为
十四.提取器对象
提取器是一个包含unapply
方法的单利对象,apply
方法就像一个构造器,接收参数并返回方法,反之unapply
方法接收一个实例对象然后返回最初创建它所用的参数。提取器常用在模式匹配和偏函数中。
import scala.util.Random object CustomerID { def apply(name: String) = s"$name--${Random.nextLong}" def unapply(customerID: String): Option[String] = { val stringArray: Array[String] = customerID.split("--") if (stringArray.tail.nonEmpty) Some(stringArray.head) else None } } val customer1ID = CustomerID("Sukyoung") // Sukyoung--23098234908 customer1ID match { case CustomerID(name) => println(name) // prints Sukyoung case _ => println("Could not extract a CustomerID") }
十五.for表达式
Scala是一个轻量级的标记方式用来表示列的推导。推导使用形势为for(enumerator) yield e
的for表达式。此处` enumerators 指一组以分号分割的枚举器。一个enumerator要么是 一个产生新变量的枚举器,要么是一个过略器。for表达式在枚举器每一次绑定中都会计算
e`值,并在循环结束后返回这些组成的序列。
case class Person(name: String, age: Int)
调用
val personList = List(Person("张三", 11), Person("李四", 20), Person("赵六", 66) ) val filterPerson = for (p <- personList if (p.age > 18)) yield p filterPerson.foreach(x=>println("name:"+x.name+",age:"+x.age))// name:李四,age:20 name:赵六,age:66
十六.泛型类
泛型类是可以使用类型参数的类。
1.定义一个泛型类
class Person[T] { def get(t: T) = t }
2.使用
val person1 = new Person[String] val result1 = person1.get("hello") println(result1)//hello val person2=new Person[Int] val result2=person2.get(2) println(result2)//2
十七.型变
型变是复杂类型的子类型关系与其组件类型的子类型关系的相关性。 Scala支持泛型类的类型参数的型变注释,允许它们是协变的,逆变的,或在没有使用注释的情况下是不变的。 在类型系统中使用型变允许我们在复杂类型之间建立直观的连接,而缺乏型变则会限制类抽象的重用性。
1.协变
子类型转换成父类型就叫协变。
使用注释 +A
,可以使一个泛型类的类型参数 A
成为协变。 对于某些类 class List[+A]
,使 A
成为协变意味着对于两种类型 A
和 B
,如果 A
是 B
的子类型,那么 List[A]
就是 List[B]
的子类型。 这允许我们使用泛型来创建非常有用和直观的子类型关系。
考虑以下简单的类结构:
abstract class Animal { def name: String } case class Cat(name: String) extends Animal case class Dog(name: String) extends Animal
object CovarianceTest extends App { def printAnimalNames(animals: List[Animal]): Unit = { animals.foreach { animal => println(animal.name) } } val cats: List[Cat] = List(Cat("Whiskers"), Cat("Tom")) val dogs: List[Dog] = List(Dog("Fido"), Dog("Rex")) printAnimalNames(cats) // Whiskers // Tom printAnimalNames(dogs) // Fido // Rex }
2.逆变
父类型转换成子类型就叫逆变。
通过使用注释 -A
,可以使一个泛型类的类型参数 A
成为逆变。 与协变类似,这会在类及其类型参数之间创建一个子类型关系,但其作用与协变完全相反。 也就是说,对于某个类 class Writer[-A]
,使 A
逆变意味着对于两种类型 A
和 B
,如果 A
是 B
的子类型,那么 Writer[B]
是 Writer[A]
的子类型。
考虑在下例中使用上面定义的类 Cat
,Dog
和 Animal
:
abstract class Printer[-A] { def print(value: A): Unit }
class AnimalPrinter extends Printer[Animal] { def print(animal: Animal): Unit = println("The animal's name is: " + animal.name) } class CatPrinter extends Printer[Cat] { def print(cat: Cat): Unit = println("The cat's name is: " + cat.name) } object ContravarianceTest extends App { val myCat: Cat = Cat("Boots") def printMyCat(printer: Printer[Cat]): Unit = { printer.print(myCat) } val catPrinter: Printer[Cat] = new CatPrinter val animalPrinter: Printer[Animal] = new AnimalPrinter printMyCat(catPrinter) printMyCat(animalPrinter) }
这个程序的输出如下:
The cat's name is: Boots The animal's name is: Boots
3.不变
子类型和父类型不能互相转换叫不变。
class Container[A](value: A) { private var _value: A = value def getValue: A = _value def setValue(value: A): Unit = { _value = value } }
val catContainer: Container[Cat] = new Container(Cat("Felix")) val animalContainer: Container[Animal] = catContainer animalContainer.setValue(Dog("Spot")) val cat: Cat = catContainer.getValue // 糟糕,我们最终会将一只狗作为值分配给一只猫
十八.类型上界
在Scala中,类型参数和抽象类型都可以有个类型约束。这种类型边界在限制取值的同时,还能展露更多信息,比如T<:A,这样声明的类型上界T应该是A的子类。
abstract class Person {//定义父类 def name: String } abstract class On extends Person {} class ZhangSan extends On { override def name: String = "张三" } class LiSi extends On { override def name: String = "李四" } class WangWu extends Person {//这个继承的是Person,注意和以上继承的区别 override def name: String = "王五" } class OnContainer[oc <: On](o: On) {//限制参数范围只能是On的子类 }
调用
val zhangSan = new OnContainer[ZhangSan](new ZhangSan) val liSi = new OnContainer[LiSi](new LiSi) var wangWu = new OnContainer[WangWu](new WangWu) //超出限定,所以会报错
十九.类型下界
类型上界 将类型限制为另一种类型的子类型,而 类型下界 将类型声明为另一种类型的超类型。 术语 B >: A
表示类型参数 B
或抽象类型 B
是类型 A
的超类型。 在大多数情况下,A
将是类的类型参数,而 B
将是方法的类型参数。
二十.内部类
在Scala中一个类可以作为另一个类的内部成员。
class Person() { class LiSi() { def name: String = "我是李四" } class WangWu() { def name = "我是王五" } }
调用
val person = new Person val liSi: person.LiSi = new person.LiSi val wangWu: person.WangWu = new person.WangWu println(liSi.name) println(wangWu.name)
二十一.抽象类型(T)
特质(trait)和抽象类(abstract class)可以包含一个抽象类成员,意味着实际类型可由具体实现来确定。
trait Person { type T val elements: T } class ZhangSan extends Person { override type T = Int override val elements: Int = 11 }
调用
val zhangSan = new ZhangSan println(zhangSan.elements)//11
二十二.复合类型
有时要表名一个类型是其它类型的子类型。这Scala中,这也可以表示成复合类型,及多个类型的交集。
假设我们有两个特质 Cloneable
和 Resetable
:
trait Cloneable extends java.lang.Cloneable { override def clone(): Cloneable = { super.clone().asInstanceOf[Cloneable] } } trait Resetable { def reset: Unit }
现在假设我们要编写一个方法 cloneAndReset
,此方法接受一个对象,克隆它并重置原始对象:
def cloneAndReset(obj: ?): Cloneable = { val cloned = obj.clone() obj.reset cloned }
这里出现一个问题,参数 obj
的类型是什么。 如果类型是 Cloneable
那么参数对象可以被克隆 clone
,但不能重置 reset
; 如果类型是 Resetable
我们可以重置 reset
它,但却没有克隆 clone
操作。 为了避免在这种情况下进行类型转换,我们可以将 obj
的类型同时指定为 Cloneable
和 Resetable
。 这种复合类型在 Scala 中写成:Cloneable with Resetable
。
以下是更新后的方法:
def cloneAndReset(obj: Cloneable with Resetable): Cloneable = { //这里就同时拥有clone和reset了。 //... }
二十三.自类型
自类型用于声明一个特质必须混入其它特质。
trait Person { def userName: String } trait Cls { this: Person => def say = println(userName) } class ZhangSan extends Person with Cls { override def userName: String = "张三"
调用
val zhangSan = new ZhangSan zhangSan.say //张三
二十四.隐式参数
自动匹配隐式类型进行传递。
abstract class Monoid[A] { def add(x: A, y: A): A def unit: A } object ImplicitTest { implicit val stringMonoid: Monoid[String] = new Monoid[String] { def add(x: String, y: String): String = x concat y def unit: String = "" } implicit val intMonoid: Monoid[Int] = new Monoid[Int] { def add(x: Int, y: Int): Int = x + y def unit: Int = 0 } def sum[A](xs: List[A])(implicit m: Monoid[A]): A = if (xs.isEmpty) m.unit else m.add(xs.head, sum(xs.tail)) def main(args: Array[String]): Unit = { println(sum(List(1, 2, 3))) // uses IntMonoid implicitly println(sum(List("a", "b", "c"))) // uses StringMonoid implicitly } }
二十五.隐式转换
一个从类型 S
到类型 T
的隐式转换由一个函数类型 S => T
的隐式值来定义,或者由一个可转换成所需值的隐式方法来定义。
隐式转换在两种情况下会用到:
- 如果一个表达式
e
的类型为S
, 并且类型S
不符合表达式的期望类型T
。 - 在一个类型为
S
的实例对象e
中调用e.m
, 如果被调用的m
并没有在类型S
中声明。
例如,当调用一个接受 java.lang.Integer
作为参数的 Java 方法时,你完全可以传入一个 scala.Int
。那是因为 Predef 包含了以下的隐式转换:
import scala.language.implicitConversions implicit def int2Integer(x: Int) = java.lang.Integer.valueOf(x)
二十六.多态方法
Scala可以按值和类型进行参数化。语法和泛型类似。类型参数括在方括号中,而值参数在圆括号中。
def listOfDuplicates[A](x: A, length: Int): List[A] = { if (length < 1) Nil else x :: listOfDuplicates(x, length - 1) } println(listOfDuplicates[Int](3, 4)) // List(3, 3, 3, 3) println(listOfDuplicates("La", 8)) // List(La, La, La, La, La, La, La, La)
二十七.类型推断
Scala编译器通常可以推断出表达式的类型,因此你不必显示声明它。
1.省略类型
val name1="张三"//省略类型,编译器会根据"张三"自动推断出是String类型 val name2:String="张三"//不省略类型
2.参数
Seq(1,2,3).map(x=>x*2)
3.何时不要依赖类型推断
- 通常认为,公公访问的 API应该具有显示类型声明以具有可读性
- 针对重新复制的 情况
var test = "hello" test = 1//会报错
二十八.运算符
在Scala中运算符即是方法。任何具有单个参数的方法都可以用 中缀运算符(我们平时用的最多的那种。)
1.定义和使用运算符
println(10 + 1)//11
2.优先级
* / % + - : = ! < > & ^ |
二十九.传名参数
传名参数仅在被使用时触发实际参数的求值运算。它们与传值参数正好相反。要将一个参数变为传名参数,只需在它的类型前加上=>
。
def whileLoop(condition: => Boolean)(body: => Unit): Unit = if (condition) { body whileLoop(condition)(body) } var i = 2 whileLoop (i > 0) { println(i) i -= 1 } // prints 2 1
方法 whileLoop
使用多个参数列表来分别获取循环条件和循环体。 如果 condition
为 true,则执行 body
,然后对 whileLoop 进行递归调用。 如果 condition
为 false,则永远不会计算 body,因为我们在 body
的类型前加上了 =>
。
现在当我们传递 i > 0
作为我们的 condition
并且 println(i); i-= 1
作为 body
时,它表现得像许多语言中的标准 while 循环。
如果参数是计算密集型或长时间运行的代码块,如获取 URL,这种延迟计算参数直到它被使用时才计算的能力可以帮助提高性能。
三十.注解
注解将元信息与定义进行关连。例如,方法之前的注解 @deprecated
会导致编译器在该方法被使用时打印警告信息。
object DeprecationDemo extends App { @deprecated("deprecation message", "release # which deprecates method") def hello = "hola" hello }
1.确保编码正确性的注解
如果不满足条件,某些注解实际上会导致编译失败。
import scala.annotation.tailrec def factorial(x: Int): Int = { @tailrec def factorialHelper(x: Int, accumulator: Int): Int = { if (x == 1) accumulator else factorialHelper(x - 1, accumulator * x) } factorialHelper(x, 1) }
方法 factorialHelper
使用注解 @tailrec
确保方法确实是尾递归的。 如果我们将方法 factorialHelper
的实现改为以下内容,它将编译失败:
import scala.annotation.tailrec def factorial(x: Int): Int = { @tailrec def factorialHelper(x: Int): Int = { if (x == 1) 1 else x * factorialHelper(x - 1) } factorialHelper(x) }
我们将得到一个错误信息 “Recursive call not in tail position”.
2.影响代码生成的注解
像 @inline
这样的注解会影响生成的代码 。
三十一.默认参数值
scala可以定义默认参数值,这样调用者就可以忽略这些默认值的参数。
def person(name: String = "", age: Int = 18): Unit = { println(name + "" + age + "岁了") }
调用
person("张三")//张三18岁了 person("李四", 11)//李四11岁了
三十二.命名参数
当调用方法时,实际名称可以通过其对应形式的参数的名称来标记。
def person(name: String = "", age: Int = 18): Unit = { println(name + "" + age + "岁了") }
调用
person(name="王五")//王五18岁了
三十三.包和导入
Scala使用包资源来创建命名空间,从而允许你创建模块化程序。
1.创建包
package users { package administrators { class NormalUser } package normalusers { class NormalUser } }
2.导入
import endTest._ //导入endTest包下所有成员 import endTest.Test //导入类Test
三十四.包对象
Scala 提供包对象作为在整个包中方便的共享使用的容器。
包对象中可以定义任何内容,而不仅仅是变量和方法。 例如,包对象经常用于保存包级作用域的类型别名和隐式转换。 包对象甚至可以继承 Scala 的类和特质。
按照惯例,包对象的代码通常放在名为 package.scala
的源文件中。
每个包都允许有一个包对象。 在包对象中的任何定义都被认为是包自身的成员。
看下例。 假设有一个类 Fruit
和三个 Fruit
对象在包 gardening.fruits
中;
// in file gardening/fruits/Fruit.scala package gardening.fruits case class Fruit(name: String, color: String) object Apple extends Fruit("Apple", "green") object Plum extends Fruit("Plum", "blue") object Banana extends Fruit("Banana", "yellow")
包对象与其他对象类似,这意味着你可以使用继承来构建它们。 例如,一个包对象可能会混入多个特质:
package object fruits extends FruitAliases with FruitHelpers { // helpers and variables follows here }
来源:https://www.cnblogs.com/shun7man/p/12508320.html