快学Scala读书笔记

scala不刻意区分原生类型和引用类型

scala单参数方法可以用运算符形式调用,例如:

// 这两种写法等效
a.add(b)
a add b

scala没有提供"++" 和 "--" 运算符,可以使用:

a += 1
a -= 1

import scala开头的包可以省略scala前缀

// 以下写法等效
import scala.collection.mutable.Map
import collection.mutable.Map

apply方法可以直接在类名或者实例变量名后直接用小括号来调用

class Demo {
  def apply(n: int): Unit = {}
}

可以这么调用

val demo = new Demo
demo(6) //相当于demo.apply(6)
object Demo {
  def apply(n: int): Unit = {}
}

可以这么调用

Demo(6)

这种写法多用于构建对象,例如:

object Demo {
  def apply(n: Int) = {
    val demo = new Demo
    demo.setXXX
    // ...
    demo
  }
}
class Demo {
  // ...
}

scala没有checked exception

scala中if 和 else是一个表达式,因此可以这么使用:

val a = if (1 > 0) 1 else 0

如果这样写:

val a = if (1 < 0) 1

则相当于:

val a = if (1 < 0) 1 else ()

()是一个Unit类,代表无值

{}代码块包含一系列表达式,代码块的值就是最后一个表达式的值

val s = {
  val a, b = 1
  a + b
}
// s == 2

代码块如果最后一条语句为赋值语句,则代码块的值为Unit,例如:

{
  var n = 0
  n += 1
}

readLine函数从控制台读取输入:

val str = readLine("Please input a string")

for循环:

for (i <- 0 to 10) {}
// i 从0到10,包含0和10

for(i <- 0 until 10) {}
// i 从0到10,包含0不包含10

// 取字符串中每一个char可以使用循环:
for(s <- "Hello") {}

scala for循环没有break和continue,如果想break可以使用:

import scala.util.control.Breaks._
breakable {
  for (s <- 1 to 10) {
    if (...) break
  }
}

高级for循环:

for (i <- 1 to 3; j <- 1 to 3) println(i *10 + j)

推导式中可以带守卫:

for (i <- 1 to 3; j <- 1 to 3 if i != j) println(i *10 + j)

可以在for循环中定义变量:

for (i <- 1 to 3; from = 4 - i; j <- from to 3) println(i *10 + j)

for循环的循环体以yield开始可以构造出一个新的集合:

val collection = for (i <- 1 to 3; from = 4 - i; j <- from to 3) yield i *10 + j

函数无需指明返回类型,但是递归函数必须指明

函数参数可以指定默认值:

def add (x: int, y: int = 1) = {x + y}

add(1) // 返回2
add(1,3) // 返回4

变长参数:

def add (x: int, y: int*) {
  // y被认为是数组
  for(i <- y) {}
}

集合转换成变长序列:

val a = 1 to 5
add(1, a: _*)

懒值:懒值介于val和def之间

val result = ... // 被定义的时候执行
lazy val result = ... // 第一次使用的时候执行
def result = ... // 每一次使用的时候都执行

throw表达式的类型为Nothing。if/else中如果一个分支的类型为Nothing则表达式的类型为另一个分支的类型:

if (x >=0) {sqrt(x)} else throw new IllegalArgumentException("...") //类型为Double

异常捕获采用的是模式匹配:

try {
...
} catch {
  case _: ExceptionA => print("...")
  case e: ExceptionB => e.printStackTrace()
}

定长数组时Array,变长数组为ArrayBuffer

定义数组:

val arr = new Array[Int](10) // 定义数组arr,长度为10,类型为Int,所有值初始化为0
val stringArr = Array("Hello", "World")
val s = stringArr(0) // 值为"Hello"

ArrayBuffer运算:

val a = new ArrayBuffer[int]()
a += 1// a为 (1)
a += (2, 3, 4, 5) // a为 (1, 2, 3, 4, 5)
a ++= Array(6, 7, 8, 9, 10) // a为(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
a.trimEnd(5) // a为 (1, 2, 3, 4, 5)
b.insert(index, 被插入元素序列)
b.remove(起始index, 移除元素个数)

ArrayBuffer 转换为 Array

arr.toArray

遍历数组

for(i <- 0 until a.length) {} // 使用index
for(e <- a) {} // 不使用index

0 until (a.length, 2) // step为2
(0 until a.length).reverse // 逆序

数组常用方法:

  • sum
  • max
  • sorted
  • scala.util.Sorting.quickSort
  • mkString

多维数组:

val a = Array.ofDim[Double](3, 4) // 3行4列
a(4)(2)

构造Map:

val map = Map("key1" -> 1, "key2" -> 2) // 不可变map
val map = scala.collection.mutable.Map("key1" -> 1, "key2" -> 2) // 可变map

映射是对偶的集合,"key1" -> 1 构造了一个对偶,相当于("key1", 1),因此map可以这样构造:

val map = Map(("key1", 1), ("key2", 2))

获取map的值:

val value = map("key1")

map是否存在key:

map.contains("key")

map.getOrElse("key", 0) // key存在则返回key对应的值,否则返回0
map.get("key") // 返回Option对象,要么是Some(值),要么是None

更新可变映射:

map("key") = 1
map += ("key3" -> 3, "key4" -> 4)
map -= "key4" // 移除key为"key4"的entry

遍历映射:

for((key, value) <- map) {}
for((key, value) <- map) yield (value, key) // 反转映射

map.keySet // 返回键的集合
map.values // 返回值的集合

元组

val tuple = (1, "Paul")
val name = tuple._1 // 返回1,下标从1开始而不是0
val (id, name) = tuple // id为1,name为Paul
val (id, _) = tuple // id 为1

patition分区操作:

"ABCdef".patition(_.isUpper) // 返回("ABC", "def")

zip操作:

val a = Array("a", "b", "c")
val b = Array(1, 2, 3)
val c = a.zip(b) // c为Array(("a", 1), ("b", 2), ("c", 3))

对偶的集合可以使用toMap转换为map

方法的调用:

class A {
  def func() = {}
}

可以使用a.func()或者a.func方式调用,但是定义时候如果没有给出小括号,例如:

class A {
  def func = {}
}

则只能使用a.func方式调用

如果成员变量的名字为a,则在scalar中a的getter方法名为a,setter方法名为a_=

var变量会自动生成setter和getter
如果变量是private的则setter和getter都是private的
val声明的变量只有getter方法
private[this]变量没有setter和getter方法

privateprivate[this]的区别

class Counter {
  private var count = 0
  def isLess(other: Counter) = count < other.count
  // 虽然count是private类型变量,但是other是Counter类型,因此在    Counter内部能够正常访问
  // 此处counter若为private[this],Counter类只能访问当前对象的count变量
}

自动生成java bean的setter和getter方法

@BeanProperty var a = 0

主构造器:

class Student(val name: String, val age: Int)

主构造器会执行类定义中的所有语句

如果声明构造器参数没有指明val或者var,则生成的字段为private[this]。若该字段未被使用则不会生成该字段。

私有化主构造器:

class Person private (val name: String)

类型投影:

ArrayBuffer[Network#Member] // 任意Network实例中的Member对象

单例对象:

object Person {
}
```scala

main函数:
```scala
object Demo {
  def main(args: Array[String]) {}
}

也可以扩展App特质:

object Demo extends App {
  // main函数执行的内容
}

枚举类型

object Direction extend Enumeration {
  type Direction = Value
  val EAST, WEST, SOUTH, NORTH = Value
}

包可以写成嵌套格式:

package com {
  package paul {
    class A
  }
}

scala中的包名是相对的,如果要使用绝对包名,需要以root开始

包对象:
每个包都有一个包对象,需要在父包中定义它,名字和子包一样。

package object people {}

引入包的全部成员:

import com.paul._

scala中import语句可以出现在任何地方,不局限于文件顶部

包选取器,用于引入包中的几个成员:

import java.awt.{Color, Font}
import java.util.{HashMap => JavaHashMap} //使用别名

重写方法必须使用override修饰符

isInstanceOf() // 检查类型
asInstanceOf() // 转换类型

如果只想测试p是不是某一个特定的类而不是其子类的话,可以:

if (p.getClass == classOf[A]) {}

scala中protected字段对于同一个包的其他类而言是不可见的,只有它的子类可见。这点和Java是不同的

超类构造器:

class Person(val name: String)
class User(val name: String, val password: String) extends Person(name)

def只能重写def
val只能重写另一个val或不带参数的def
var只能重写另一个抽象的var

提前定义:

class Ant extends {
  override val range = 2
} with Creature

重写变量range会在Creature构造函数之前执行

scala检查对象是否相等可以用==运算符

读取文件行:

import scala.io.Source
val source = Source.fromFile("myFile.txt", "UTF-8")
val lineIterator = source.getLines
for (line <- lineIterator) {}
source.close()
for (c <- source) {} // 处理单个字符

val iter = source.buffered
iter.head // peek下一个字符,但不取出
从URL或者是其他源读取数据:
val source1 = Source.fromURL()
val source1 = Source.fromString("Hello World")
val source3 = Source.fromStdin()

读取二进制文件需要使用Java的类库

Scala序列化:

@SerialVersionUID(42L) class Person extends Serialization

如果使用默认的SerialVersionUID可以省略该注解

scala执行系统命令:

import system.process._
"ls -al .." ! // 执行结果被打印到标准输出
!返回结果是被执行程序的返回值,如果正常返回0,否则显示非0值

val result = "ls -al .." !! // 输出结果会以字符串的形式返回

"ls -al .." #| "grep sec" ! // #| 为管道操作符

"ls -al .." #> new File("file.txt") ! // 输出重定向到文件
"ls -al .." #>> new File("file.txt") ! // 输出追加到文件末尾
"grep sec" #< new File("output.txt") ! // 文件内容作为输入
"grep sec" #< new File("output.txt") ! // URL内容作为输入

p #&& q // 如果p成功,执行q
p #|| q // 如果p不成功,执行q

正则表达式:

val regexp = """[abc]{1,3}""".r // String的r方法生成正则表达式。为了避免转义,可以使用原始字符串语法:"""..."""

迭代匹配值:

for (matching <- regexp.findAllIn("...")) {}

匹配值转换为数组:

val matches = regexp.findAllIn("...").toArray

其他正则表达式方法:
findfirstIn, findPrefixOf, replaceFirstIn, replaceAllIn

正则表达式组:

val numItemPattern = """(\d+) ([A-Za-z]+)""".r

匹配组:

val numItemPattern (num, item) = "99 bottles"

匹配多个组

for (numItemPattern (num, item) <- numItemPatter.findAllIn("99 bottles, 100 kettles"))

实现多个特质:

class A extend B with C with D

如果特质中方法已经实现,子类中重写该方法必须要添加override关键字。如果特质中方法未实现,则写不写override均可

class A extends B with C {
  def f = {
    super.func()
    // 如果特质B和C中都有方法func,则按照从左到右的优先循序,执行B中的func方法

    super[C].func()
    // 明确指明调用特质C中的func方法
  }
}

特质中出现的具体字段会被添加到子类中

trait Parent {
  val count = 0
}

class Child extends Parent {
  // 相当于也加入了val count = 0
}

特质不能有构造器参数,每个特质都有一个无参数构造器。

特质可以扩展类,该类会自动成为混入该特质的类的超类

自身类型:
特质可以以如下代码开始:

this: 类型A =>
...

如此定义的特质只能被混入类型A的子类当中

自身类型也可以处理结构类型:

trait A {
this: {def getMessage(): Unit} =>
...
// 只有具有getMessage方法的类才能混入该特质
}

yield事scala的保留字,如果调用java的方法中包含该单词,需要反引号。例如:Thread.yield

定义前置操作符:

class A {
  def unary_-(): Unit = {}
}

调用该前置操作符时可使用:-a,相当于a.unary_-

用于构造列表的::操作符是右结合的:
1 :: 2 :: Nil的意思是1 :: (2 :: Nil)
2 :: Nil 相当于Nil.::(2)

函数调用语法可以扩展到函数之外的值,例如:
f(x, y, z) 相当于调用f.apply(x, y, z)
f(x, y, z) = value 相当于调用f.update(x, y, z, value)

提取器:

class Fraction(val num: Int, val den: Int)

object Fraction {
  def unapply(input: Fraction): Option[(Int, Int)] = {
    if (input.den == 0) None else Some((input.num, input.den))
  }
}
val Fraction(a, b) = new Fraction(3, 4) // a 为 3, b 为 4
// unapply方法用在赋值语句和模式匹配中

unapplySeq方法:匹配任意长度序列

将函数本身赋予一个变量:

import math._
val fun = ceil _

匿名函数:

(x: Double) => 3 * x

函数参数可以放在大括号中:

Array(1, 2, 3).map((x: Int) => 3 * x)
Array(1, 2, 3).map{(x: Int) => 3 * x}

函数接收函数为参数:

def f(g: (Int) => Int) = g(10)
f((x: Int) => 3 * x)

x的类型可推断为Int,因此类型可以省略为:

f((x) => 3 * x)

参数列表中只有一个参数,小括号可以省略:

f(x => 3 * x)

参数在=> 右侧仅出现了一次,可以省略为:

f(3 * _) // 简写方式仅能在参数类型已知的情况下有效

函数柯里化:

def mulOneAtATime(x: Int)(y: Int) = x * y
mulOneAtATime(3)(4)

柯里化的用途之一:提供更多的类型推断信息

val a = Array("Hello", "World")
val b = Array("hello", "world")
a.corresponds(b)(_.equalsIgnoreCase(_))

corresponds的定义为:

def corresponds[B](that: Seq[B])(p: (A: B) => Boolean):Boolean

可以根据第一个参数传进去的类型B,以用于第二个参数p

def runInThread(block: () => Unit) = {
  new Thread {
    override def run() {block()}
  }.start()
}
runInThread{() => println("")}

可以将参数列表省略掉:

def runInThread(block: => Unit) = {
  new Thread {
    override def run() {block} // 调用时不需要加括号
  }.start()
}
runInThread{println("")} // 传递匿名函数时不需要参数列表

不可变序列:
Indexed: Vector, Range
non-indexed: List, Stream, Stack, Queue
可变序列:
indexed: ArrayBuffer
non-indexed: Stack, Queue, PriorityQueue, LinkedList, DoubleLinkedList

List包含head和tail
List(9, 4, 2) 相当于 9 :: 4 :: 2 :: Nil

集合应用的view方法返回的是懒视图,只对被求值的时候进行计算
可以使用force方法对懒集合进行强制计算

使用线程安全的集合:混入Synchronized开头的几个集合特质,例如:

val synchronizedMap = new scala.collection.mutable.HashMap[String, Int] with scala.collection.mutable.SynchronizedMap[String, Int]

集合并行计算:调用par函数

for(i <- (0 until 100).par) print(i + " ")
for(i <- (0 until 100).par) yield i + " "

并行集合转换为串行集合:调用ser方法

模式匹配:

p match {
  case '+' => ...
  case _: Int => ...
  case e: Exception => e.printStackTrace()
  case _: Int if p % 2 == 0 => ...
  case _ => ... // 相当于default
}

match语句也可以是表达式

for中也可以使用模式:

for((key, "") <- System.getProperties()) {// key为所有值为空白的键}

样例类case class:
样例类构造器每一个参数自动称为val,除非被声明为var
使用伴生对象的apply方法创建新对象
提供unapply方法,可使用模式匹配
生成toString, hasCode, equals和copy方法

copy方法的使用:

case class Person(name: String, age: Int)
val p1 = Person("Paul", 20)
val p2 = p1.copy() // p2为 name="Paul" age=20
val p3 = p1.copy(age=30) // p3 为 name="Paul" age=30

嵌套值绑定到变量:使用@符号

密封类:
使用样例类做模式匹配是,编译器确保已经列出了所有可能的选择
密封类所有的子类都必须在密封类所在的文件中定义

sealed abstract class Direction
case object EAST extends Direction
case object WEST extends Direction
case object NORTH extends Direction
case object SOUTH extends Direction


d match {
  case EAST => ...
  case WEST => ...
  case NORTH => ...
  case SOUTH => ...
}

偏函数:被花括号包围的一组case语句为偏函数。

val f: PartialFunction[Char, Int] = {case '+' => 1, case '-' => -1}
f('-') // 调用f.apply('-') 返回 -1
f.isDefinedAt('0') // false 至少匹配其中一个模式时返回true
f('0') // 抛出MatchError

Scala XML

scala内建支持XML格式

节点序列的类型为NodeSeq

var xml = <html><head></head><body></body></html>
for (elem <- xml.child) { // 返回xml的子节点
  println(elem)
}

可以使用NodeBuffer的形式在程序中构建NodeSeq:

var nodeItems = new NodeBuffer
nodeItems += <ul></ul>
var nodes: NodeSeq = nodeItems // NodeSeq为不可变类型,NodeBuffer可以被隐式转换为NodeSeq

通过attributes方法获取节点中的属性:

var node = <img src="image.jpg"></img>
var imgSrc = node.attributes("src") // 结果为image.jpg,类型为节点序列,不是字符串
imgSrc.text // 转换类型为字符串
for (attr <- elem.attributes) {} // 遍历attr.key和attr.value.text

elem.attributes.asAttrMap // 将属性转换为Map类型

XML内嵌表达式:

var items = Array("Hello", "World")
val elem = <ul><li>{items(0)}</li><li>{items(1)}</li></ul>
println(elem) // 结果为<ul><li>Hello</li><li>World</li></ul>

XPath匹配XML节点:

val x = elem \ "li" // 返回elem直接所有一级子节点为li的节点
val x = elem \\ "li" // 返回elem所有类型为li的子节点
val x = elem \ "_" \ "li" // 返回elem所有二级子节点,且类型为li
val x = elem \ "@src" // 返回elem节点的src属性
val x = elem \\ "@src" // 返回elem节点所有子类节点的src属性

从文件中加载XML

import scala.xml.XML
val xmlFile = XML.loadFile("xmlFile.xml")

泛型类
scala使用中括号表示泛型类:

class Pair[T, S] (val first: T, val second: S)

泛型函数:

class getMiddle[T](arr: Array[T]) = arr(arr.length / 2)
val fun = getMiddle[String] _ //返回一个函数,但是类型被限制为String

泛型上界:
[T <: Type] T必须为Type的子类

泛型下界:
[T >: Type] T必须为Type的超类

视图界定:
[T <% Type] T可以被隐式转换成Type

基本类型数组泛型指定:需要将上界指定为Manifest

协变:对于Pair[+T],如果A是B的子类,那么Pair[A]是Pair[B]的子类。
逆变:对于Pair[+T],如果A是B的子类,那么Pair[A]是Pair[B]的父类。

函数在参数上是逆变的,在返回值上是协变的。对于某个对象的消费适合逆变,而对于他的产出则适用于协变。

单例类型:

class A {
  def funA() = {
    ...
    this
  }
}

class B extends A {
  def funB() = {
    ...
    this
  }
}

val b = new B
b.funA.funB // 会出错,funA返回的this类型为A,A没有funB方法

将类定义修改为:

class A {
  def funA() = {
    ...
    this.type
  }
}

class B extends A {
  def funB() = {
    ...
    this.type
  }
}
val b = new B
b.funA.funB // 无问题
object A
class B {
  def funA(obj: A.type) = {} // 指代的是A这个单例对象而不是A这个类型
}

类型别名:

type MyType = HashMap[Int, String]

结构类型:

def fun(obj: {def f(s: String): Any}) {}
// 具有f(s: String): Any方法的类的实例才能作为该方法的参数。(scala使用反射来调用该方法)

复合类型:T1 with T2 with T3 ...

自身类型:

trait MyExceptionTrait {
this: Exception =>
...
// this被当做Exception对象来对待
}

MyExceptionTrait 特质只能被混入Exception及其子类中

抽象类型:

trait A{
  type Content
  def make(): Content
}

class B extends A {
  type Content = String // 必须指定类型
  def make():String = {}
}

隐式转换:

package com.paul
class Fraction(val x:Int, val y: Int) {
  def *(another: Fraction) = {...}
}
object Fraction {
  def apply(val x:Int, val y: Int) = new Fraction(x, y)
}

package com.paul
object FractionConversion {
  implicit def int2Fraction(n: Int) = Fraction(n, 1)
}

class Demo extends App {
  import com.paul.FractionConversion._
  val result = 3 * Fraction(3, 2) // 隐式调用int2Fraction
}

隐式参数:

case class Delimeter(val left: String, val: right: String)

class Tools {
  implicit val delimeter = Delimeter("<<", ">>")
  def makeString(s: String)(implicit delim: Delimeter) = {
    // demimeter会被隐式的赋予delim
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容

  • Scala与Java的关系 Scala与Java的关系是非常紧密的!! 因为Scala是基于Java虚拟机,也就是...
    灯火gg阅读 3,444评论 1 24
  • Scala的集合类可以从三个维度进行切分: 可变与不可变集合(Immutable and mutable coll...
    时待吾阅读 5,815评论 0 4
  • 第一章 基础 声明和变量 在Scala中,我们鼓励你使用val--除非你真的需要修改它的内容。注意:你不需要给出值...
    jackLee阅读 309评论 0 0
  • 数组是一种可变的、可索引的数据集合。在Scala中用Array[T]的形式来表示Java中的数组形式 T[]。 v...
    时待吾阅读 953评论 0 0
  • 从去年开始认真尝试画水彩画,一直很喜欢水彩晕染的感觉,水彩技法还是挺多的,要画出更好的效果就得勤练习。画了一幅...
    赵公子ruby阅读 231评论 0 1