与Java对比
对比项 | Java | Scala | 说明 |
标识符 | / | / | 反引号括起来的字符也是标识符。 |
语句结束 | 分号(;) | 分号(;)或者换行 | |
包 | package ... | package name package name{ } |
第2种类c#,可以一个文件定义多个package。 package 可嵌套。 |
import | 文件开头 指定类 指定包下所有类(.*) static import(引入静态成员) |
任意位置 指定类 指定包下所有类(._) 包对象 |
// 重命名成员 // 隐藏成员 默认情况下,Scala 总会引入 java.lang._ 、 scala._ 和 Predef._,这里也能解释,为什么以scala开头的包,在使用时都是省去scala.的。 |
类 | class | class object |
一个Scala源文件中可以有多个类。object定义单例对象。 伴生对象实例 |
继承,实现 | extends ,implements | extends,with | class Student extends Person with FileLogger with Cloneable //实现了2个接口。 |
枚举 | enum | extends Enumeration | object WeekDay extends Enumeration{
type WeekDay = Value //声明枚举对外暴露的变量类型 val Mon = Value("1") val Tue = Value("2") ... def checkExists(day:String) = this.values.exists(_.toString==day) def isWorkingDay(day:WeekDay) = ! ( day==Sat || day == Sun) def showAll = this.values.foreach(println) // 打印所有的枚举值 } |
内部类 | 匿名内部类 成员内部类 局部内部类 静态内部类 |
外部类class - 内部类class 外部类class - 内部对象object 外部对象object - 内部类class 外部对象object - 内部对象object |
Java 中,内部类是外部类的成员,而 Scala 正好相反,内部类是绑定到外部对象的。 参考:https://docs.scala-lang.org/zh-cn/tour/inner-classes.html |
接口 | interface | trait | Trait可以定义属性和方法的实现,类似java抽象类。Java8 默认方法。 |
方法 | () {...} | def | Scala方法体在等号后面; def functionName ([参数列表]) : [return type] = {
|
函数 | lambda表达式 | 赋值给变量 | |
闭包 | / | 闭包是一个函数,返回值依赖于声明在函数外部的一个或多个变量。 "闭包",因为它引用到函数外面定义的变量,定义这个函数的过程是将这个自由变量捕获而构成一个封闭的函数。 |
|
泛型 | <T> | [T] | |
数据类型 | / void null Object |
/ Unit Null AnyRef Nothing Any |
数据类型相同。Scala中没有基本类型,全是对象类型。 Unit:Unit只有一个实例值,写成()。 |
符号字面量 | 无 | '<标识符> ,这里 <标识符> 可以是任何字母或数字的标识(注意:不能以数字开头) | package scala 这种字面量被映射成预定义类scala.Symbol的实例。 |
多行字符串 | + 连接 | """ .... """ | val foo = """菜鸟教程 www.runoob.com www.w3cschool.cc www.runnoob.com 以上三个地址都能访问""" |
变量 | 加final声明常量 | var 变量 val 常量 延迟加载 |
延迟加载:变量仅在使用时赋值。 lazy val helloString="Hello Crazy World" |
变量定义 | public String a; | var a String ; 支持多个变量声明。 |
Scala类型在变量后。 val xmax, ymax = 100 // xmax, ymax都声明为100 |
访问修饰符 | private (成员默认) protected public |
private private[x] 或 protected[x] |
Scala protected更严格,仅对子类可见,同包类不可见。 private[x],读作"这个成员除了对[…]中的类或[…]中的包中的类及它们的伴生对像可见外,对其它所有类都是private。 |
算术运算符 | / | / | |
关系运算符 | / | / | |
逻辑运算符 | / | / | |
位运算符 | / | / | |
赋值运算符 | / | / | |
分支语句 | / | / | |
循环语句 | for( Type a : list) { ... } for( ; ; ){
|
for(var a <- list) { ... } for( a <- 1 to 10){
for( a <- numList |
区间 1 until 10 1 to 10 过滤 for 使用 yield 将 for 循环的返回值作为一个变量存储。语法格式如下: var retVal = for{ var x <- List |
数组 | int[] a = new ArrayList<String>(3); | var z:Array[String] = new Array[String](3) | val myMatrix = Array.ofDim[Int](3, 4) 多维数组。 |
case | 常量,字符串 | 所有类型 | person match {
case Person("Alice", 25) => println("Hi Alice!") case Person("Bob", 32) => println("Hi Bob!") case Person(name, age) => println("Age: " + age + " year, name: " + name + "?") } |
异常 | try {
{
|
||
提取器(Extractor) | / | apply,unapply | 提取器是从传递给它的对象中提取出构造该对象的参数。 def apply(user: String, domain: String) = {
// 提取方法(必选) |
类和对象
类定义
//采用关键字class定义
class Person {
//类成员必须初始化,否则会报错
//这里定义的是一个公有成员
var name:String=null
}
默认会为成员生成getter,setter方法
public class cn.scala.xtwy.Person {
private java.lang.String name;
public java.lang.String name();
public void name_$eq(java.lang.String);
public cn.scala.xtwy.Person();
}
可以自定义getter,setter
class Person{
//定义私有成员
private var privateName:String=null;
//getter方法
def name=privateName
//setter方法
def name_=(name:String){
this.privateName=name
}
}
如果也需要程序自动会生成getter方法和setter方法,则需要引入 scala.reflect.BeanProperty然后采用注解的方式修饰变量
class Person {
//类成员必须初始化,否则会报错
//@BeanProperty用于生成getXxx,setXxx方法
@BeanProperty var name:String="john"
}
构造函数
主构造器的定义与类的定义交织在一直,将构造器参数直接放在类名称之后。
主构造器的参数,会变成类的成员。
//具有主构建函数和辅助构建函数的Person类
class Person(var name:String,var age:Int){
//类成员
private var sex:Int=0
//辅助构造器
def this(name:String,age:Int,sex:Int){
this(name,age)
this.sex=sex
}
}
单例对象
在某些应用场景下,我们可能不需要创建对象,而是想直接调用方法,但是Scala语言并不支持静态成员,Scala通过单例对象来解决该问题。
object Student {
private var studentNo:Int=0;
def uniqueStudentNo()={
studentNo+=1
studentNo
}
def main(args: Array[String]): Unit = {
println(Student.uniqueStudentNo())
}
}
伴生对象与伴生类
其实伴生对象与伴生类本质上是不同的两个类,只不过伴生类与伴生对象之间可以相互访问到对主的成员包括私有的成员变量或方法。
class Student(var name:String,age:Int)
object Student {
private var studentNo:Int=0;
def uniqueStudentNo()={
studentNo+=1
studentNo
}
def main(args: Array[String]): Unit = {
println(Student.uniqueStudentNo())
}
}
apply方法
通过利用apply方法可以直接利用类名创建对象
//定义Student类,该类称为伴生类,因为在同一个源文件里面,我们还定义了object Student
class Student(var name:String,var age:Int){
private var sex:Int=0
//直接访问伴生对象的私有成员
def printCompanionObject()=println(Student.studentNo)
}
//伴生对象
object Student {
private var studentNo:Int=0;
def uniqueStudentNo()={
studentNo+=1
studentNo
}
//定义自己的apply方法。此处有new 关键字
def apply(name:String,age:Int)=new Student(name,age)
def main(args: Array[String]): Unit = {
println(Student.uniqueStudentNo())
val s=new Student("john",29)
//直接访问伴生类Student中的私有成员
println(s.sex)
//直接利用类名进行对象的创建,这种方式实际上是调用前面的apply方法进行实现,这种方式的好处是避免了自己手动new去创建对象
//此处没有new,直接用类名当方法名。
val s1=Student("john",29)
println(s1.name)
println(s1.age)
}
}
包
包定义
包即可以定义在顶部,也可以使用 c#方式定义包:
package cn{
package scala{
package xtwy{
class Teacher {
}
}
}
}
包的作用域与引入(import)的使用方法
scala允许在任何地方进行包的引入,_的意思是引入该包下的所有类和对象
package cn{
package scala{
//在包cn.scala下创建了一个Utils单例
object Utils{
def toString(x:String){
println(x)
}
//外层包无法直接访问内层包,下面这一行代码编译通不过
//如果一定要使用的话,可以引入包
import cn.scala.xtwy._
def getTeacher():Teacher=new Teacher("john")
}
//定义了cn.scala.xtwy
package xtwy{
class Teacher(var name:String) {
//演示包的访问规则
//内层包可以访问外层包中定义的类或对象,无需引入
def printName()={Utils.toString(name)}
}
}
}
}
访问控制
在java语言中,主要通过public、private、protected及默认控制来实现包中类成员的访问控制,当定义一个类时,如果类成员不加任何访问控制符时,表示该类成员在定义该类的包中可见。在scala中没有public关键字,仅有private 和 protected访问控制符,当一个类成员不加private和protected时,它的访问权限就是public。
修饰符 | 访问范围 |
---|---|
无任何修饰符 | 任何地方都可以使用 |
private[scala] | 在定义的类中可以访问,在scala包及子包中可以访问 |
private[this] | 只能在定义的类中访问,即使伴生对象也不能访问团 |
private | 在定义的的类及伴生对象中可以访问,其它地方不能访问 |
protected[scala] | 在定义的类及子类中可以访问,在scala包及子包中可以访问, |
protected[this] | 只能在定义的类及子类中访问,即使伴生对象也不能访问 |
protected | 在定义的类及子类中访问,伴生对象可以访问,其它地方不能访问 |
包对象
包对象主要用于将常量、工具函数,使用时直接通过包名引用,类似Java的 static import。
package cn.scala.xtwy
//利用package关键字定义单例对象
package object Math {
val PI=3.141529
val THETA=2.0
val SIGMA=1.9
}
class Coputation{
def computeArea(r:Double)=Math.PI*r*r
}
import高级特性
隐式引入
如果不引入任何包,scala会默认引入
- java.lang._
- scala._
- Predef._
包中或对象中所有的类和方法
重命名
scala中允许对引入的类或方法进行重命名,如果我们需要在程序中同时使用java.util.HashMap及scala.collection.mutable.HashMap时,可以利用重命名的方法消除命名冲突的问题,虽然也可以采用包名前缀的方式使用,但代码不够简洁
//将java.util.HashMap重命名为JavaHashMap
import java.util.{ HashMap => JavaHashMap }
import scala.collection.mutable.HashMap
val javaHashMap = new JavaHashMap[String, String]()
类隐藏
//通过HashMap=> _,这样类便被隐藏起来了
import java.util.{HashMap=> _,_}
类层次结构
层次结构中,处于继承层次最顶层的是Any类,它是scala继承的根类,scala中所有的类都是它的子类。
//==与!=被声明为final,它们不能被子类重写
final def ==(that: Any): Boolean
final def !=(that: Any): Boolean
def equals(that: Any): Boolean
def hashCode: Int
def toString: String
根类Any有两个子类,它们分别是AnyVal和AnyRef。
AnyVal
其中AnyVal是所有scala内置的值类型( Byte, Short, Char, Int, Long, Float, Double, Boolean, Unit.)的父类。
其中 Byte, Short, Char, Int, Long, Float, Double, Boolean与java中的byte,short,char,int,long,float,double,boolean原生类型对应,
而Unit对应java中的void类型。()可以作为Unit类型的实例,它同样可以调用toString等方法
由于( Byte, Short, Char, Int, Long, Float, Double, Boolean, Unit)继承AnyVal,而AnyVal又继承Any,因此它们也可以调用toString等方法。
AnyRef
AnyRef是Any的另外一个子类,它是scala中所有非值类型的父类,对应Java.lang.Object类(可以看作是java.lang.Object类的别名),也即它是所有引用类型的父类(除值类型外)。那为什么不直接Java.lang.Object作为scala非值引用类型的父类呢?这是因为Scala还可以运行在其它平台上如.Net,所以它使用了AnyRef这个类,在JVM上它对应的是java.lang.Object,而对于其它平台有不同的实现。
Scala中原生类型的实现方式
scala采用与java相同原生类型存储方式,由于性能方面及与java进行操作方面的考虑,scala对于原生类型的基本操作如加减乘除操作与java是一样的,当需要遇到其他方法调用时,则使用java的原生类型封装类来表示,如Int类型对应于java.lang.Integer类型,这种转换对于我们使用者来说是透明的。
scala中的==操作它不区分你是原生类型还是引用类型
如果是在java语言中,它返回的是false。在scala中,对于原生类型,这种等于操作同java原生类型,而对于引用类型,它实际上是用equals方法对==方法进行实现,这样避免了程序设计时存在的某些问题。那如果想判断两个引用类型是否相等时怎么办呢? AnyRef中提供了eq、ne两个方法用于判断两个引用是否相等
Nothing、Null类型
Null类型是所有AnyRef类型的子类型,也即它处于AnyRef类的底层,对应java中的null引用。而Nothing是scala类中所有类的子类,它处于scala类的最底层。必须注意的是Null类型处于AnyRef类的底层,它不能够作为值类型的子类
特质(Traits)
Traits几种不同使用方式
Trait类似Java接口,但java接口有其自身的局限性:接口中只能包括抽象方法,不能包含字段、具体方法。Scala语言利用Trait解决了该问题,在scala的trait中,它不但可以包括抽象方法还可以包含字段和具体方法。Trait更像抽象类。
注意:JDK1.8之后接口中可以定义默认方法,静态方法。
1、当做java接口使用
2、带具体实现的trait。
3、带抽象字段的trait。字段未赋值。
4、具体字段的trait。字段赋值。
trait构造顺序
与Java接口,抽象类类似。
class Person
class Student extends Person with FileLogger with Cloneable
上述构造器的执行顺序为:
1 首先调用父类Person的构造器
2 调用父trait Logger的构造器
3 再调用trait FileLogger构造器,再然后调用Cloneable的构造器
4 最后才调用Student的构造器
trait与类的比较
trait有自己的构造器,它是无参构造器,不能定义trait带参数的构造器。除此之外 ,trait与普通的scala类并没有其它区别。
//不能定义trait带参数的构造器
trait FileLogger(msg:String)
trait可以扩展(extends)类
trait Logger{
def log(msg:String):Unit
}
//Exception 是个class。
trait ExceptionLogger extends Exception with Logger{
def log(msg:String):Unit={
println(getMessage())
}
}
self type
即给自己(this)定义个别名。
class A{
//下面 self => 定义了this的别名,它是self type的一种特殊形式。
//这里的self并不是关键字,可以是任何名称
self =>
val x=2
//可以用self.x作为this.x使用
def foo = self.x + this.x
}
class OuterClass {
outer => //定义了一个外部类别名
val v1 = "here"
class InnerClass {
// 用outer表示外部类,相当于OuterClass.this
println(outer.v1)
}
}
trait X{
}
class B{
//self:X => 要求B在实例化时或定义B的子类时
//必须混入指定的X类型,这个X类型也可以指定为当前类型
self:X=>
}
//类C扩展B的时候必须混入trait X
//否则的话会报错
class C extends B with X{
def foo()=println("self type demo")
}
object SelfTypeDemo extends App{
println(new C().foo)
}
函数式编程
函数式编程语言应支持以下特性:
(1)高阶函数(Higher-order functions)
(2)闭包( closures)
(3)模式匹配( Pattern matching)
(4)单一赋值( Single assignment )
(5)延迟计算( Lazy evaluation)
(6)类型推导( Type inference )
(7)尾部调用优化( Tail call optimization)
参考:https://www.cnblogs.com/yinzhengjie/p/9370898.html
函数定义
return 可省略,返回值可省略,能自动推导。
匿名函数
Array(1,2,3,4).map(
(x:Int)=>x+1 //匿名函数
).mkString(",")
//花括方式
Array(1,2,3,4).map{(x:Int)=>x+1}.mkString(",")
//省略.的方式
Array(1,2,3,4) map{(x:Int)=>x+1} mkString(",")
//参数类型推断写法
Array(1,2,3,4) map{(x)=>x+1} mkString(",")
//函数只有一个参数的话,可以省略()
Array(1,2,3,4) map{x=>x+1} mkString(",")
//如果参数右边只出现一次,则可以进一步简化 (—— 代表参数)
Array(1,2,3,4) map{ _ + 1} mkString(",")
//值函数简化方式 (_ 代表参数)
scala> val fun1=1 + ( _ : Double )
val fun2 : (Double)=> Double = 1 + _
函数参数
//函数参数(高阶函数)
//( (Int) => String ) => String
scala> def convertIntToString( f : (Int) => String ) = f(4)
//高阶函数可以产生新的函数
//(Double) => ( (Double) => Double )
scala> def multiplyBy(factor : Double) = ( x : Double ) => factor * x
高阶函数
高阶函数主要有两种:
- 将一个函数当做另外一个函数的参数(即函数参数)
- 返回值是函数的函数
常用高阶函数
def map[B](f: (A) ⇒ B): Array[B]
.flatMap (x=>x.map(y=>y))
List("List","Set","Array") .filter (_.length>3)
Array(1,2,4,3,5).reduce(_+_)
Array(1,2,4,3,5) .foldLeft(0) ((x:Int,y:Int)=>{println(x,y);x+y})
Array(1,2,4,3,5) .foldRight(0) ((x:Int,y:Int)=>{println(x,y);x+y})
Array(1,2,4,3,5) .scanLeft(0) ((x:Int,y:Int)=>{println(x,y);x+y})
SAM(simple abstract method)转换
一个trait(或者抽象类)只有一个方法,在作为参数传递时,即可以通过匿名类传递参数,也可以只传递一个函数。类似于Java的 @FunctionalInterface。
button.addActionListener((event:ActionEvent)=>counter+=1)
button.addActionListener(new ActionListener{
override def actionPerformed(event:ActionEvent){
counter+=1
}
})
new ActionListener{
override def actionPerformed(event:ActionEvent){
}
函数柯里化
//mutiplyBy这个函数的返回值是一个函数
//该函数的输入是Doulbe (参数x),返回值也是Double
def multiplyBy(factor:Double) = (x:Double) => factor*x
//返回的函数作为值函数赋值给变量x
val x=multiplyBy(10)
//变量x现在可以直接当函数使用
x(50)
函数柯里化(curry)是怎么样的呢?其实就是将multiplyBy函数定义成如下形式
// x:是返回函数的参数
def multiplyBy(factor:Double) (x:Double) = x * factor
multiplyBy(10)(50)
//但此时它不能像def multiplyBy(factor:Double)=(x:Double)=>factor*x函数一样,可以输入单个参数进行调用。会出错。
multiplyBy(10)
//错误提示函数multiplyBy缺少参数,如果要这么做的话,需要将其定义为偏函数
//Double => Double = <function1> ,返回一个函数,输入double,输出double。
multiplyBy(10) _
部分应用函数
那什么是部分应用函数呢,所谓部分应用函数就是指,当函数有多个参数,而在我们使用该函数时我们不想提供所有参数(假设函数有3个函数),只提供0~2个参数,此时得到的函数便是部分应用函数。
下划线 _ 并不是占位符的作用,而是作为部分应用函数的定义符
//定义一个求和函数
scala> def sum(x:Int,y:Int,z:Int)=x+y+z
sum: (x: Int, y: Int, z: Int)Int
//不指定任何参数的部分应用函数
scala> val s1=sum _
s1: (Int, Int, Int) => Int = <function3>
scala> s1(1,2,3)
res91: Int = 6
//指定两个参数的部分应用函数
scala> val s2=sum(1,_:Int,3)
s2: Int => Int = <function1>
scala> s2(2)
res92: Int = 6
//指定一个参数的部分应用函数
scala> val s3=sum(1,_:Int,_:Int)
s3: (Int, Int) => Int = <function2>
scala> s3(2,3)
res93: Int = 6