参考
- 《scala编程指南》scala作者参与编写
- 尚硅谷scala教程
之前接触过python的函数编程,学起来可能更好理解
主要是对照java和python学习,基础语法基本相同,类型后置,自动推断,类型标注有点像python
环境搭建比较简单,scala版本是2.13,直接在idea安装scala插件创建scala项目即可
scala可以两种风格运行,命令式和面向对象
scala提供了repl环境,可以交互式测试
基础语法val和var的区别,可以理解为final和var的区别
scala对照java容易混淆的语法
var array = new Array[String](3) //定长数组
array(0) = "hello" //实际是调用array.apply(0)函数
array(1) = "hello"
array(2) = "hello"
array(1) = "world"
array.foreach(arg => println(arg + '\n'))
Array<String> array = new Array<String>()
array.add("hello")
array.add("hello")
array.add("hello")
array.set(1,"world")
for(String s : array){
System.out.println(s)
}
数据类型
- Scala中一切数据都是对象, 都是Any的子类。
- Scala中数据类型分为两大类:数值类型(AnyVal) 、引用类型(AnyRef) , 不管是值类型还是引用类型都是对象。
- Scala数据类型仍然遵守, 低精度的值类型向高精度值类型, 自动转换(隐式转换)
- Scala中的StringOps是对Java中的String增强
- Unit:对应Java中的void, 用于方法返回值的位置, 表示方法没有返回值。 Unit是一个数据类型, 只有一个对象就是
()
。 Void不是数据类型,只是一个关键字 - Null是一个类型, 只有一个对象就是null。 它是所有引用类型(AnyRef) 的子类。Null 可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal)
- Nothing, 是所有数据类型的子类, 主要用在一个函数没有明确返回值时使用, 因为这样我们可以把抛出的返回值, 返回给任何的变量或者函数
- scala的强制类型转换语法
num.toInt
,还提供了string转换s1.toInt
- Scala 中没有运算符,所有运算符都是方法
主要还是学习函数式编程的思想
函数式编程- 方法是类中的函数
- 函数没有重载和重写的概念;方法可以进行重载和重写
- Scala 语言可以在任何的语法结构中声明任何的语法 ,函数可以嵌套
- 函数讲究大道至简,能省则省
至简原则细节
- return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
- 如果函数体只有一行代码,可以省略花括号
- 返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
- 如果有 return,则不能省略返回值类型,必须指定
- 如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用
- Scala 如果期望是无返回值类型,可以省略等号
- 如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
- 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
- 如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
个人觉得就是用数学公式的思路写代码(只不过数学公式没有类型),不写return,没有参数就是单纯的计算过程,举个例子对照一下
scala函数 | 数学函数 |
---|---|
//return 可以省略,Scala 会使用函数体的最后一行代码作为返回值 //如果函数体只有一行代码,可以省略花括号 //返回值类型如果能够推断出来,那么可以省略 def f2(s: String): String = s + " jinlian" def f3(s: String) = s + " jinlian" 如果有 return,则不能省略返回值类型,必须指定 如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用 | f(x) = 1 + x |
Scala 如果期望是无返回值类型,可以省略等号 def f6() { “dalang6” } //注意“dalang6”不是返回值 | ( 1 + 2 + sin(π) ) |
//如果函数无参,但是声明了参数列表,那么调用时,小括号可加可不加 def f7() = “dalang7” println(f7) //如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略 def f8 = “dalang” println(f8) | x = 1 |
//如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略 def f10(f: String => Unit) = { f(“”) } println(f10((x: String) => { println(“wusong”) })) | f(x) = 1 + x g(x) = f(x) + x |
在 Scala 中,函数是一等公民
-
函数可以作为值进行传递
-
在被调用函数 foo 后面加上 _,相当于把函数 foo 当成一个整体,传递给变量
val f1 = foo _
-
如果明确变量类型,那么不使用下划线也可以将函数作为整体传递给变量
var f2:()=>Int = foo
-
函数可以作为参数进行传递
def f1(f: (Int, Int) => Int): Int
-
函数可以作为函数返回值返回
定义函数
def operation(arr: Array[Int], op: Int => Int) = {
for (elem <- arr) yield op(elem)
}
def op(ele: Int): Int = {
ele + 1
}
val arr = operation(Array(1, 2, 3, 4), op)
println(arr.mkString(","))
def calculator(a: Int, b: Int, op: (Int, Int) => Int): Int = {
op(a, b)
}
//匿名函数写法
val arr2 = operation(Array(1, 2, 3, 4), (ele) => { ele + 1 })
-
参数的类型可以省略,会根据形参进行自动的推导
val arr2 = operation(Array(1, 2, 3, 4), (ele) => { ele + 1 }) calculator(2, 3, (x , y) => x + y)
-
类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过 1 的永远不能省略圆括号。
-
匿名函数如果只有一行,则大括号也可以省略
val arr3 = operation(Array(1, 2, 3, 4), ele => ele + 1 )
-
如果参数只出现一次,则参数省略且后面参数可以用_代替
val arr5 = operation(Array(1, 2, 3, 4), _ + 1) calculator(2, 3, _ + _)
模拟 Map 映射、 Filter 过滤、 Reduce 聚合Demo
object hello {
def main(args: Array[String]): Unit = {
var a = Array(1, 2, 3, 4, 5, 6)
var arr1 = map(a, ele => ele + 1)
println(arr1.mkString(","))
var arr2 = filter(a,ele => ele % 2 == 1)
println(arr2.mkString(","))
var arr3 = reduce(a, _ * _)
//var arr3 = reduce(a, (x, y) => x * y)
println(arr3)
}
def map(arr: Array[Int], op: Int => Int) = {
for (ele <- arr) yield op(ele)
}
def filter(arr: Array[Int], op: Int => Boolean) = {
var arrtmp = ArrayBuffer[Int]();
for (ele <- arr if op(ele)) {
arrtmp.append(ele)
}
arrtmp.toArray
}
def reduce(arr:Array[Int],op:(Int,Int) => Int)= {
var init :Int = arr(0)
for(index <- 1 until arr.length){
init = op(init,arr(index))
}
init
}
}
柯里化&闭包
闭包:如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境,称为闭包
def main(args: Array[String]): Unit = {
def f1()={
var a:Int = 10
def f2(b:Int)={
a + b
}
f2 _
}
val f = f1()
println(f(3))
println(f1()(3))
函数柯里化:把一个参数列表的多个参数,变成多个参数列表 (柯里化必然闭包)
def f3(a:Int)(b:Int)={
a + b
}
println(f3()(3))
值调用和名调用
object TestFunction {
def main(args: Array[String]): Unit = {
var i:Int = 1
myWhile(i <= 10){
println(i)
i +=1
}
}
//柯里化,大括号里的内容作为第二个参数
def myWhile(condition: =>Boolean)(op: =>Unit):Unit={
if (condition){
op
myWhile(condition)(op)
}
}
}
惰性加载
当函数返回值被声明为 lazy 时,函数的执行将被推迟,直到首次对此取值,该函数才会执行。这种函数我们称之为惰性函数
注意:lazy 不能修饰 var 类型的变量
def main(args: Array[String]): Unit = {
lazy val res = sum(10, 30)
println("----------------")
println("res=" + res)
}
def sum(n1: Int, n2: Int): Int = {
println("sum is executed")
return n1 + n2
}
output:
----------------
sum is executed
res=40
面向对象
语法上和java类似,细节上和C++类似
包管理Scala 有两种包的管理风格 ,一种类似java方式,一种类似C#方式
类似C#方式,子包中的类可以直接访问父包中的内容,而无需导包,但父包访问子包需要导包
//类似C#,子包中的类可以直接访问父包中的内容,而无需导包,但父包访问子包需要导包
package com{
package zhaojie{
package scala{
}
}
}
scala程序运行必须提供独立对象并包括main方法作为入口函数,但是scala同样提供了App特质简化这一 *** 作,直接在{}中写main函数的逻辑
object Demo extends App {
var argsc = new Array[String](3)
argsc(0) = "hello"
argsc(1) = "world"
argsc(2) = "ohhhh"
argsc.foreach(arg => println(arg + ":" + ChecksumAccumulator.calculate(arg)))
}
导包
包对象类文件分成两部分class和object
因为scala的类中不允许有静态成员,因此出现了单例对象object,单例对象和类共用一个名字时,object是class的伴生对象
,class是object的伴生类
(似乎是将类的静态部分单独提取到object中)
object和class可以相互访问各自的私有成员,必须在一个文件中定义class和object
没有伴生类的object是孤立对象
,常用作工具类和应用程序入口(main方法)
举例,ChecksumAccumulator例程是通过object的calculate计算校验和
package com.zhaojie.demo
import scala.collection.mutable
class ChecksumAccumulator {
private var sum = 0
def add(b: Byte) = sum += b
def checksum() = ~(sum & 0xFF) + 1
}
object ChecksumAccumulator {
private val cache = mutable.Map.empty[String, Int]
//函数返回值简写
def calculate(s: String) =
if (cache.contains(s)) {
cache(s)
} else {
val acc = new ChecksumAccumulator
for (c <- s) {
acc.add(c.toByte)
}
val cs = acc.checksum()
//不能写成cache(s) += cs 会报空指针
cache += (s -> cs)
cs
}
}
访问权限
- Scala 中属性和方法的默认访问权限为 public,但 Scala 中无 public 关键字。
- private 为私有权限,只在类的内部和伴生对象中可用。
- protected 为受保护权限,Scala 中受保护权限比 Java 中更严格,同类、子类可以访问,同包无法访问。
- private[包名]增加包访问权限,包名下的其他类也可以使用
Scala 中属性和方法都是动态绑定,而 Java 中只有方法为动态绑定
构造器Scala 类的构造器包括:主构造器和辅助构造器
通过伴生对象的 apply 方法,实现不使用 new 方法创建对象
如果想让主构造器变成私有的,可以在()之前加上 private
class 类名(形参列表) { // 主构造器
// 类体
def this(形参列表) { // 辅助构造器
}
def this(形参列表) { //辅助构造器可以有多个...
}
}
写一个单例模式
注意下划线 _
的特殊用法,Scala中必须要显式指定类成员的初值,或者使用下划线让编译器自动设置初始值
class Person private(val name: String, val age: Int) {
}
object Person{
def main(args: Array[String]): Unit = {
var p = Person.getInstance()
}
private val person = new Person("zhangsan",19)
def getInstance() : Person = person
}
object Person {
def main(args: Array[String]): Unit = {
var p = Person.getInstance()
}
//自动设置初始值
private var person: Person = _
def getInstance(): Person = {
if (person == null) {
person = new Person("zhangsan", 10)
}
person
}
}
继承和重写
如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类
重写非抽象方法需要用 override 修饰,重写抽象方法则可以不加 override。
子类中调用父类的方法使用 super 关键字
子类对抽象属性进行实现,父类抽象属性可以用 var 修饰;
子类对非抽象属性重写,父类非抽象属性只支持 val 类型,而不支持 var。
特质特质=抽象类+接口
Scala 语言中,采用特质 trait(特征)来代替接口的概念
Scala 中的 trait 中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质
基本语法
没有父类:class 类名 extends 特质 1 with 特质 2 with 特质 3 …
有父类:class 类名 extends 父类 with 特质 1 with 特质 2 with 特质 3…
所有的 Java 接口都可以当做 Scala 特质使用
动态混入:可灵活的扩展类的功能
- 动态混入:创建对象时混入 trait,而无需使类混入该 trait
- 如果混入的 trait 中有未实现的方法,则需要实现
trait SexTrait {
var sex: String
}
val t2 = new Teacher with SexTrait {
override var sex: String = "male"
}
由于一个类可以混入(mixin)多个 trait,且 trait 中可以有具体的属性和方法,若混入的特质中具有相同的方法(方法名,参数列表,返回值均相同),必然会出现继承冲突问题
- 一个类(Sub)混入的两个 trait(TraitA,TraitB)中具有相同的具体方法,且两个 trait 之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法
- 一个类(Sub)混入的两个 trait(TraitA,TraitB)中具有相同的具体方法,且两个 trait 继承自相同的 trait(TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala采用了特质叠加的策略,就是将混入的多个 trait 中的冲突方法叠加起来
注意:super,不是表示其父特质对象,MyClass 中的 super 指代 Color,Color 中的 super 指代 Category,Category 中的 super指代 Ball
- 调用某个指定的混入特质中的方法,可以增加约束:super[]
super[Category].describe()
这篇blog总结的很全面,Scala 可变集合体系、不可变集合体系 详解
Scala 的集合有三大类:序列 Seq、集 Set、 映射 Map ,所有的集合都扩展自 Iterable特质
不可变集合:scala.collection.immutable
可变集合: scala.collection.mutable
集合高级函数
reduceright和foldright比较特殊
object test {
def main(args: Array[String]): Unit = {
val list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
val nestedList: List[List[Int]] = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9))
val wordList: List[String] = List("hello world", "hello atguigu", "hello scala")
//(1)过滤
println(list.filter(x => x % 2 == 0))
//(2)转化/映射
println(list.map(x => x + 1))
//(3)扁平化
println(nestedList.flatten)
//(4)扁平化+映射 注:flatMap 相当于先进行 map *** 作,在进行 flatten *** 作
println(wordList.flatMap(x => x.split(" ")))
//(5)分组
println(list.groupBy(x => x % 2))
val list = List(1,2,3,4)
val i0 = list.reduceRight((x,y) => x-y) // 1-2-3-4
val i1 = list.reduceRight((x,y) => x-y) // 1-(2-(3-4))
//fold和reduce差不多只是多了初值
val i2 = list.foldLeft(1)((x,y)=>x-y) //1-1-2-3-4
val i3 = list.foldRight(10)((x,y)=>x-y) // 1-(2-(3-(4-10)))
}
}
output:
List(2, 4, 6, 8)
List(2, 3, 4, 5, 6, 7, 8, 9, 10)
List(1, 2, 3, 4, 5, 6, 7, 8, 9)
List(hello, world, hello, atguigu, hello, scala)
Map(1 -> List(1, 3, 5, 7, 9), 0 -> List(2, 4, 6, 8))
wordcount练习
object TestWordCount {
def main(args: Array[String]): Unit = { // 单词计数:将集合中出现的相同的单词,进行计数,取计数排名前三的结果
val stringList = List("Hello Scala Hbase kafka", "Hell Scala Hbase", " Hello Scala", "Hello")
// 1) 将每一个字符串转换成一个一个单词
val wordList: List[String] = stringList.flatMap(str => str.split(" "))
//println(wordList)
// 2) 将相同的单词放置在一起
val wordToWordsMap: Map[String, List[String]] = wordList.groupBy(word => word)
//println(wordToWordsMap)
// 3) 对相同的单词进行计数
// (word, list) => (word, count)
val wordToCountMap: Map[String, Int] = wordToWordsMap.map(tuple => (tuple._1, tuple._2.size))
// 4) 对计数完成后的结果进行排序(降序)
val sortList: List[(String, Int)] = wordToCountMap.toList.sortWith {
(left, right) => {
left._2 > right._2
}
}
// 5) 对排序后的结果取前 3 名
val resultList: List[(String, Int)] = sortList.take(3)
println(resultList)
}
}
模式匹配
样例类
匹配类时
case class Person (name: String, age: Int)
- 样例类仍然是类,和普通类相比,只是其自动生成了伴生对象,并且伴生对象中自动提供了一些常用的方法,如 apply、 unapply、 toString、 equals、 hashCode 和 copy。
- 样例类是为模式匹配而优化的类,因为其默认提供了 unapply 方法,因此,样例类可以直接使用模式匹配,而无需自己实现 unapply 方法。
- 构造器中的每一个参数都成为 val,除非它被显式地声明为 var
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)