2022.1.15 scala从入门到上手写代码.md

158 阅读9分钟

Preface

本人的学习笔记,价值有限,仅供参考

本文受众:Java选手

目标:上手scala写代码,即,并不深入这门语言的以下方面:

  1. 如何与各种编程模式融合(参照设计模式和编码大全);
  2. 某一种集合数据结构(比如list)底层是如何实现;
  3. 多线程模型(结合os的进程线程模型)和内存模型(都知道scala用的是JVM,基于此可以比较方便理解);
  4. 语法糖(看到啥学啥,随缘用);
  5. 网络编程的底层实现;
  6. etc...

而是从以下几个方面入手,停留在用的层面:

  1. 基本语法:了解有哪些保留字和操作符(扫一眼并收藏为书签)、怎么声明变量、变量的作用域、基础数据类型以及类型转换; 循环、if-else怎么写、函数怎么定义和调用、代码结构如何抽象(java的类、go的package); 字符串相关的,因为Java里的字符串坑比较多。
  2. 集合数据结构:数组、列表/链表、map/字典、set等的常用编码模式和需要避开的坑;
  3. 并发编程:简单了解多线程模型&如何使用、再看一些常用的模式
  4. 网络编程:常用的模式
  5. 编写单元测试的方式
  6. 编码规范
  7. 语言特性
  8. 处理异常

依此展开下文。

基本语法

HelloWorld

以一个HelloWorld程序+注释来说明scala的基本语法。see more:docs.scala-lang.org/cheatsheets…

package com.example	// package的定义,和Java一样可以import package或者import Class,不同点在于Java用*,scala用_,还可以用{Class1, Class2}引入一个包下面的多个class

// 定义一个object,object的概念和Java的object相同,但这种定义方式有点像Java的匿名内部类
object ScalaHelloWorldWithMain {

  // 定义main函数,和Java一样是程序入口
  def main(args: Array[String]): Unit = {
    println("Hello World")
    println("local: " + square(2))
  }

  // 定义函数
  def square(x: Int) = x * x

  // 可以直接调用函数,这里和Java不一样,Java必须加一个static,声明为static代码块
  println("global: ", square(2))
    
}

也可以继承App类,这样就不用显式地声明main函数(参照App类的注释):

object ScalaHelloWorldWithApp extends App {
  println("Hello World")
}

scala中的代码结构&如何在idea中创建scala项目(在已经安装scala SDK和scala plugin的前提下,如果没有装scala plugin,调用scala内置函数idea会标红报错)参照:docs.scala-lang.org/getting-sta…

基础数据类型

数字

  • Char (16-bit unsigned Unicode character)
  • Byte (8-bit signed value)
  • Short (16-bit signed value)
  • Int (32-bit signed value)
  • Long (64-bit signed value)
  • Float (32-bit IEEE 754 single-precision floating-point value)
  • Double (64-bit IEEE 754 single-precision floating-point value)

and:

  • Boolean (can have true or false values)
  • Unit (carries no meaningful information)

**变量声明:**通过val关键字声明常量或者var关键字声明变量,也可以不声明关键字,让scala通过value做类型推断。 语法:val or val VariableName : DataType = [Initial Value] muliple assignments:val (myVar1: Int, myVar2: String) = Pair(40, "Foo")或者val (myVar1, myVar2) = Pair(40, "Foo")

// 定义number类型
// byte
val b1: Btye = 100
// float & double
val f1 = 12.05f
val d1 = 12.3496067 // 隐式声明为double
var d2 = 12.3496067D // 显式声明为double

// 通过int的字面量推断类型
val i5 = 1234
val i6 = 0xAFBF // 16进制
val l4 = 1234L
val l3 = 0xCAFEBABEL // 16进制也要在末尾加L

字符

Char,声明语法和Java一样

处理字符串

语法和Java一样

在println中使用变量:docs.scala-lang.org/overviews/s…

类型转换

调用toType func,比如:

  • Int -> Byte,调用toByte

    val aByteSizedInteger = 127
    val byteFromInt = aByteSizedInteger.toByte	//注意数据越界
    
  • String -> Int,调用toInt

    val c4: Char = '@'
    val symbolToInt = c4.toInt // 可以转,按照编码
    val stringToInt = "ABC".toInt // 不能转,会报错java.lang.NumberFormatException: For input string: "ABC"
    

todo:extend: Any和AnyVal

类和对象

概念和语法

类和对象的概念和Java中的一样,参考:www.runoob.com/scala/scala… 主要不同点:

  1. 定义class可以带参数(感觉像是把构造函数放在这儿了的意思),一个带参数的scala class等同于定义了私有变量且带getter和setter以及constructor的Java类;可以用val定义constructor参数,会变成不能修改的变量; 依然可以定义多个构造函数,scala称之为辅助构造函数(auxiliary constructor),语法和Java稍微不一样:

    def this(firstName: String) = {
        this(firstName, "defaultLastName")
    }
    
  2. 一个scala文件可以定义多个class;

  3. scala的class没有public、private、protected这类声明;

  4. 接口和继承:(见下面的Scala traits)

  5. Java的static vs. Scala的object

示例代码:

// 这个例子定义了可以在类中做的所有事情
// todo:之后可以关注类的初始化过程
class ScalaClassSample(var firstName: String, var lastName: String) {

  println("the constructor begins")

  // 默认的access域是public
  var age = 0

  // 定义类私有变量
  private val HOME = System.getProperty("JAVA_HOME")

  def this(firstName: String) = {
    this(firstName, "defaultLastName")
  }
  // 定义类方法
  // 和Java一样所有类都是Object的子类,都有toString()方法?
  override def toString(): String = s"$firstName $lastName is $age years old"

  // Unit,有点像void?
  def printHome(): Unit = println(s"HOME = $HOME")
  // this关键字和Java的this一样?
  def printFullName(): Unit = println(this)

  printHome()
  printFullName()
  println("you've reached the end of the constructor")
}

// 伴生对象
object ScalaClassSample {
  def main(args: Array[String]): Unit = {
    val sample = new ScalaClassSample("jack", "hook")

    println(sample.toString())
    println("age is:" + sample.age)
    // age是public变量,设置它的值
    sample.age = 36
    println("age is:" + sample.age)

  }
}

todo:伴生对象

docs.scala-lang.org/overviews/s…

定义枚举

Scala traits

Scala traits可以当作Java的接口和抽象类使用。scala也有抽象类,之后再说明什么时候应该用抽象类而不是Scala traits。

实现接口方法或者重写父类的抽象方法可以不用override关键字,重写父类的非抽象方法则需要。

可以动态混合特征(Mixing traits in on the fly,谷歌翻译),我理解为在new对象的时候再声明它继承了哪些类。

定义一个接口:

trait TailWagger {
    def startTail(): Unit
    def stopTail(): Unit
}

定义一个抽象类:

trait Pet {
    def speak() = println("Yo")
    def comeToMaster(): Unit
}

实现多个接口: extends第一个trait; with 剩下的traits。

class Dog extends TailWagger with Speaker with Runner {
    // 实现接口方法或者重写父类的抽象方法可以不用override关键字

    def startTail(): Unit = println("tail is wagging")
    override def stopTail(): Unit = println("tail is stopped")

    // 甚至不需要return
    override def speak(): String = "Woof!"

    override def startRunning(): Unit = println("running")

}

动态混合特征(Mixing traits on the fly):

class Dog(name: String)

object ScalaTraits extends App {
  val d = new Dog("Fido") with TailWagger with Runner
  println(d.startRunning())
  println(d.stopRunning())
}

抽象类

Scala 还有一个抽象类的概念,类似于 Java 的抽象类。但是因为特征是如此强大,你很少需要使用抽象类。实际上,您只需要在以下情况下使用抽象类:

  • 您想创建一个需要构造函数参数的基类
  • 您的 Scala 代码将从 Java 代码中调用

(抄自:docs.scala-lang.org/overviews/s…

变量

**声明:**在 Scala 中,使用关键词 "var" 声明变量,使用关键词 "val" 声明常量

参考:www.runoob.com/scala/scala…

**作用域:**Scala中的访问修饰符和Java一样,不同之处在于Scala只给函数和变量声明,没有给class;

函数

声明语法:def funcName([params1: Type]): [return type] = {} 如果不写函数体,只定义函数签名,会隐式地声明一个抽象方法。

**函数/方法调用:**函数直接调用;instance.funcName调用方法

Scala&函数式编程

Scala为函数式编程提供的features:

  1. 纯函数(pure functions)
  2. 传递函数(passing functions around)
纯函数

In Functional Programming, Simplified, Alvin Alexander defines a pure function like this:

  • The function’s output depends only on its input variables
  • It doesn’t mutate any hidden state
  • It doesn’t have any “back doors”: It doesn’t read data from the outside world (including the console, web services, databases, files, etc.), or write data to the outside world

As a result of this definition, any time you call a pure function with the same input value(s), you’ll always get the same result. 理解为不包含任何隐藏的状态和后门(不读写任何外部存储、不改变外部变量值),函数被调用后的输出只受其输入变量影响。典型的纯函数是math库相关函数。 定义:

纯函数是仅依赖于其声明的输入及其内部算法来产生其输出的函数。它不会从“外部世界”(函数范围之外的世界)读取任何其他值,也不会修改外部世界中的任何值。

以上描述是为了说明Scala不仅提供了许多纯函数供调用,且提供方便地编写纯函数的能力。

传递函数

函数可以作为参数传递,且传的时候可以是匿名函数。

scala中的匿名函数 除了'_'比较让人疑惑之外,其他的都比较像Java的lambda表达式

  1. 匿名函数作为传递函数的写法:

    val ints = List(1,2,3)
    val doubledInts = ints.map(_ * 2)
    

    doubleInts将会是一个List(2,4,6);'_'在scala中类似一个通配符的概念,它在不同的场景中可以被解释为不同的意思,在这里的意思是表示列表ints中的所有元素。

    上面的代码也可以写成:

    val doubledInts = ints.map((i: Int) => i * 2)
    // 这个比较像Java的lambda表达式
    val doubledInts = ints.map(i => i * 2)
    
  2. 定义匿名函数赋值给变量:

    var myfc1 = (str1:String, str2:String) => str1 + str2
    
  3. 使用匿名函数遍历集合&filter:

    val multiplier = 3
    val ints = List(1, 2, 3, 4)
    val doubled = ints.map(_ * multiplier)
    assert(doubled == List(3, 6, 9, 12))
    
    val ints2 = List(1,2,3,4,5)
    val actual = ints2.filter(_ > 2)
    assert(actual == List(3,4,5))
    
处理空值

函数式编程不使用空值,空值的替代品是Option、Some和None三个类,Some和None是Option的子类,因此代码可以这样编写:

def toInt(s: String): Option[Int] = {
    try {
        Some(Integer.parseInt(s.trim))
    } catch {
        case e: Exception => None
    }
}

验证结果: 通过contains/isEmpty方法或match表达式;contains/isEmpty用于比较Option、Some、None,等同于match表达式(注释是这么说的)

var handle = new ScalaFuncExceptionValHandle()

val a = handle.toInt("1")
a match {
    case Some(1) => println("toInt(1) return 1")
}
// contains/isEmpty方法专用于比较Option、Some、None,等同于match表达式
assert(a.contains(1))

val b = handle.toInt("foo")
b match {
    case None => println("toInt(\"foo\") return None")
}
assert(b.isEmpty)

可以认为Option是一个容器,None是一个空容器(然而这对我写代码有什么用呢?所以并不详细看了)

流程控制语句

nothing special except match phrase & for with yield

所有流程控制语句的文档index:docs.scala-lang.org/overviews/s…

有try-catch finally,和Java一样

集合数据结构

todo

并发编程

todo

网络编程

todo