Scala学习笔记(基础语法)

it2026-04-05  5

Scala介绍

Scala 是一门多范式的编程语言,Scala支持面向对象和函数式编程Scala源代码(.scala)会被编译成Java字节码(.class),然后运行于JVM之上,并可以调用现有的Java类库,实现两种语言的无缝对接。Spark就是使用Scala编写的,因此大部分学习scala的原因都是为了更好的学习spark这一大数据计算框架

语法特点

变量声明

var 声明可变变量 eg: var age: Int = 18 val 声明不可变变量 eg: val sex = "male" (声明变量时,类型可以省略, val 修饰的对象属性在编译后,等同于加上final)

数据类型:

Scala 与 Java有着相同的数据类型,在Scala中数据类型都是对象,scala没有java中的原生(基本)类型Scala数据类型分为两大类 AnyVal(值类型) 和 AnyRef(引用类型)

for 循环

1. 方式一 // i 将会从 1-3 循环, 前后闭合 (包括1 和 3) for(i <- 1 to 3){ print(i + " ") } 2. 方式二 // 在于 i 是从1 到 (3-1),前闭后开 for(i <- 1 until 3) { print(i + " ") } 3. 方式三 // 循环守卫,为true则进入循环体,false则跳过 for(i <- 1 to 3 if i != 2) { print(i + " ") } 4. 方式四 // 双重for循环 for(i <- 1 to 3; j <- 1 to 3) { println(" i =" + i + " j = " + j) } //等价为: for (i <- 1 to 3) { for (j <-1 to 3) { println("ok") } } 5. 方式五 // 循环返回值 val res = for(i <- 1 to 10) yield i * 2 println(res)

函数

定义

def 函数名 ([参数名: 参数类型], ...)[[: 返回值类型] =] { 语句... //完成某个功能 return 返回值 }
函数的返回值:

返回值形式1: // def 函数名(参数列表) : 数据类型 = {函数体} // 返回值确定,清晰 返回值形式2: // def 函数名(参数列表) = {函数体} // 有返回值, 类型是推断出来的 返回值形式3: // def 函数名(参数列表) {函数体} // 无返回值 Unit

注意事项

a. 如果没有return,默认执行到最后一行的结果作为返回值 b. 如果函数明确使用return关键字,那么函数返回就不能使用自行推断了,这时要明确写成 : 返回类型 = ,当然如果你什么都不写,即使有return 返回值为() c. 如果函数明确声明无返回值(声明Unit),那么函数体中即使使用return关键字也不会有返回值 d. 函数的形参列表可以是多个, 如果函数没有形参,调用时 可以不带() e. 如果明确函数没有返回值,那么等号可以省略 f. 如果变量只在=>右边只出现一次,可以用_来代替

高阶函数

//第一种. 函数的第一个参数类型是另一个函数 def apply(f: Int => String, v: Int) = f(v) //eg: def fmtInt(n: Int) : String = "[整数值{" + n + "}]" def main(args: Array[String]): Unit = { println(apply(fmtInt, 1200)) } //第二种. 函数的返回值是一个函数 def addBy(n: Int) = { (d : Double) => n + d } //eg: def main(args: Array[String]): Unit = { println(addBy(50)(80.223)) }

函数的柯里化

柯里化指的是将接受原来的第二个参数原来接受多个参数的函数变成新的接受一个参数的函数的过程, 新函数的参数为唯一参数, 如果有n个参数, 就是把这个函数分解成n个新函数的过程 函数编程中,接受多个参数的函数都可以转化为接受单个参数的函数,这个转化过程就叫柯里化

案例

// 原始函数, 有3个参数的函数 def addMulti(a: Int, b: Int, c: Int) = (a + b) * c // 函数A的返回值是一个函数B, 函数B的返回值是函数C def addMulti(a: Int) = { (b: Int) => (c: Int) => (a + b) * c } def main(args: Array[String]): Unit = { println(addMulti(50)(80)(20)) }

异常处理

try { val r = 10 / 0 } catch { case ex: ArithmeticException=> println(“捕获了除数为零的算术异常") case ex: Exception => println("捕获了异常") } finally { // 最终要执行的代码 println("scala finally...") }

catch子句是按次序捕捉的。因此,在catch子句中,越具体的异常越要靠前,越普遍的异常越靠后

类的本质也是函数,构造函数

class Person { var age : Int = 10 var sal = 8090.9 var Name = null var address : String = null }

Scala中声明一个属性,必须显示的初始化,然后根据初始化数据的类型自动推断,属性类型可以省略(这点和Java不同),如果赋值为null,则一定要加类型,因为不加类型, 那么该属性的类型就是Null类型。暂时不给属性赋值可用_让系统分配默认值

构造器

class Person (a: Int, n: String) { // 主构造器 def this(age:Int, sal:Double){ // 辅助构造器无论是直接或间接,最终都一定要调用主构造器,执行主构造器的逻辑 this(age, "张三") // 需要在第一行调用主构造器 this.sal = sal } var age : Int = a var sal = 8090.9 var Name : String = n }

注意:只有主构造器可以调用父类的构造器,辅助构造器不能直接调用父类的构造器

构造器参数

a. Scala类的主构造器的形参未用任何修饰符修饰,那么这个参数是局部变量 class Persion(a:Int) b. 如果参数使用val关键字声明,那么Scala会将参数作为类的私有的只读属性使用 class Persion(val a:Int) c. 如果参数使用var关键字声明,那么Scala会将参数作为类的成员属性使用,提供getset方法 class Persion(var a:Int)

对象创建流程分析

class Person { var age: Short = 90 var name: String = _ def this(n: String, a: Int) { this() this.name = n this.age = a } } var p : Person = new Person("小倩",20) Scala对象创建对象流程分析: 加载类的信息(属性信息和方法信息), 如果父类也没有加载, 则由父到子加载父类 在内存中()给对象开辟空间 使用父类的构造器(主构造器/辅助构造器)完成父类的初始化 (多个父类) 使用本类的主构造器完成初始化 使用本类的辅助构造器继续初始化 将对象在内存中的地址赋给 p 这个引用

scala中包也可以像嵌套类那样嵌套使用(包中有包),在同一个文件中,可以将类(class / object)、trait 创建在不同的包中

package com.example{ class User{} // 此类在com.example包下 package scala{ class User{} // 这个类就在com.example.scala包下 } }

作用域:scala中子包直接访问父包中的内容,大括号体现作用域,重名时,默认采用就近原则

访问权限

private,protected和默认的 public 当方法访问权限为默认时,默认为public访问权限 a. 处处可以访问public b. 子类和伴生对象能访问protected c. 本类和伴生对象访问 private

类型转换

classOf[String]就如同Java的 String.class obj.isInstanceOf[T]就如同Java的obj instanceof T 判断obj是不是T类型。 obj.asInstanceOf[T]就如同Java的(T)obj 将obj强转成T类型。

覆写字段

a. def只能重写另一个def(即:方法只能重写另一个方法) b. val只能重写父类的一个val属性 或父类的不带参数的def方法 c. var只能重写另一个抽象的var属性,即声明未初始化的属性

伴生对象 定义 同一个文件中,类名和伴生对象名相同,则它们互为彼此的伴生类和伴生对象

object Cat { } class Cat { }

scala中的静态概念,scala由于是纯面向对象,没有static关键字,为了能和Java交互,产生了一种特殊的对象来模拟类对象,即伴生对象,一个类的所有静态内容都可以放置在它的伴生对象中声明和调用

单利模式举例:

object TestSingleTon extends App{ val singleTon = SingleTon.getInstance val singleTon2 = SingleTon.getInstance println(singleTon.hashCode() + " " + singleTon2.hashCode()) } //将SingleTon的构造方法私有化 class SingleTon private() {} object SingleTon { private var s:SingleTon = null def getInstance = { if(s == null) { s = new SingleTon } s } }

特质(trait) scala中没有接口也没有implement关键字,采用trait(特质、特征)来代替接口的概念,多个类具有相同的特征(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明。

声明: trait 特质名 { trait体 }

使用 没有父类 class 类名 extends 特质1 with 特质2 with 特质3 … 有父类 class 类名 extends 父类 with 特质1 with 特质2 with 特质3 注意事项和细节 a. scala在叠加特质的时候,会首先从后面的特质开始执行 b. scala中特质中如果调用super,并不是表示调用父特质的方法,而是向前面(左边)继续查找特质,如果找不到,才会去父特质查找 c. 如果想要调用具体特质的方法,可以指定:super[特质].xxx(…). d. 当我们给某个方法增加了abstract override 后,就是明确的告诉编译器,该方法确实是重写了父特质的抽象方法,但是重写后,该方法仍然是一个抽象方法,如果有super则继续执行左侧的特质,没有就结束了

特质构造器顺序

第一种特质的构造顺序(声明类的同时混入特质): 调用当前类的超类构造器 第一个特质的父特质构造器 第一个特质构造器 第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行 第二个特质构造器 …重复4,5的步骤(如果有第3个,第4个特质) 当前类构造器 第二种特质的构造顺序(在构建对象时,动态混入特质): 调用当前类的超类构造器 当前类构造器 第一个特质构造器的父特质构造器 第一个特质构造器. 第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行 第二个特质构造器 …重复5,6的步骤(如果有第3个,第4个特质) 当前类构造器

动态混入 动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能 使用案例:

trait Operate3 { def insert(id: Int): Unit = { println("插入数据 = " + id) } } class OracleDB { } abstract class MySQL3 { } var oracle = new OracleDB with Operate3 oracle.insert(999) val mysql = new MySQL3 with Operate3 mysql.insert(4)

scala中创建对象的几种方式: new对象 apply方法 动态混入 匿名子类创建对象 反射 反序列化 工具类(Unsafe.allocate)

隐式转换

作用     a. 可通过隐式转换函数给类动态添加功能     b. 指定某些数据类型的相互转化 基本介绍

隐式转换函数是以implicit关键字声明的带有单个参数的函数。这种函数将会自动应用,将值从一种类型转换为另一种类型隐式转换函数的函数名可以是任意的,隐式转换与函数名称无关,只与函数签名(函数参数类型和返回值类型)有关隐式函数可以有多个(即:隐式函数列表),但是需要保证在当前环境下,只有一个隐式函数能被识别

eg:

object ImplicitTest { def main(args: Array[String]): Unit = { //将MySQL转成DB。 implicit def addDelete(mysql: MySQL): DB = { new DB } // MySQL ==> DB, 同时还保留MySQL类本身的功能 val mysql : MySQL = new MySQL //这样写也√ // val mysql = new MySQL mysql.delete() mysql.insert() } } class MySQL { def insert(): Unit = { println("插入数据~") } } class DB { def delete(): Unit = { println("删除数据") } }

隐式值 将某个形参变量标记为implicit,所以编译器会在方法省略隐式参数的情况下去搜索作用域内的隐式值作为缺省参数

implicit val str1: String = "jack" def hello(implicit name: String): Unit = { println(name + " hello") } hello // 和缺省参数很像, 但是功能不同

模式匹配 Scala中的模式匹配类似于Java中的switch语法,从第一个case分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有case都不匹配,那么会执行case _ 分支,类似于Java中default语句,每个case中,不用break语句,自动中断case。

for (ch <- "+-3!") { var sign = 0 var digit = 0 ch match { case '+' => sign = 1 case '-' => sign = -1 // 这里可以增加一个if 的判断,这样就可以对某个范围数据进行匹配了. // 匹配到一个 _ 就不会再匹配的,这个原则和普通的case 是一样的。 case _ if ch.toString.equals("3") => digit = 3 //模式匹配守卫功能 case _ => sign = 2 } println(ch + " " + sign + " " + digit) } println("res=" + res)

模式匹配-类型匹配

val result = obj match { case a : Int => a case b : Map[String, Int] => "对象是一个字符串-数字的Map集合" case c : Map[Int, String] => "对象是一个数字-字符串的Map集合" case d : Array[String] => "对象是一个字符串数组" case e : Array[Int] => "对象是一个数字数组" case f : BigInt => Int.MaxValue case _ => "啥也不是" } println(result)

for循环也可以进行模式匹配

val map = Map("A"->1, "B"->0, "C"->3) for ( (k, v) <- map ) { println(k + " -> " + v) }
最新回复(0)