Scala编程详解

1.7 条件控制与循环

scala没有for循环,只能使用while循环替代for循环,或者使用简易版for循环

var n=10;for (i <- 1 to 10) println(i)

或者使用until,左闭右开 for (i <- 1 until 10) println(i)

增强for循环

for (c <- "hello") print(i + " ")

跳出循环语句

scala没有break语句

import scala.utils.control.Breaks._

多重for循环

for (i <- 1 to 9; j <- 1 to 9){
    if (j == 9){
        println(i*j)
    }
    else{
        print(i*j + " ")
    }
}

if 守卫

for (i <- 1 to 20 if i%2 ==0) println(i)

for 推导式

for (i <- 10) yield i

输入和输出

val name = readLine("Welcome to Game House, Please tell me your name:")
print("Thanks. Then please tell me your age: ")
val age = readInt()
if (age > 18){
    printf("Hi, %s, you are %d years old, so you are legal to come here!", name, age)
}else{
    printf("Sorry, boy, %s, you are only %d years old. you are illegal to come here!", name, age)
}


1.9 函数入门

def sayHello(name:String) = printf("hello %s!", name)

def fab(n: Int): Int = {
  if (n <=0 ) 1
  else fab(n-1) + fab(n-2)
}

默认参数

def sayHello(firstName: String, lastName: String = "xixi") = printf(firstName+" "+lastName)

带名参数

sayHello(lastName = "hehe", firstName = "oh")可以不考虑参数顺序

变长参数

def sum(nums: Int*) = {
  var result = 0
  for (num <- nums){
    result += num
  }
  result
}

sum(1, 2, 3, 4, 5)

<u>使用序列调用变长参数</u>**

// 递归
def sum(nums: Int*): Int = {          // Int*是变长参数
  if (nums.length == 0) 0
  else nums.head + sum2(nums.tail:_*) //nums.tail: WrappedArray(2,3,4,5)
}

val s = sum(1 to 5)     //错误!!! 1 to 5类型是range
val s = sum(1 to 5:_*)  // res0: Int = 15

**过程 **

过程: 定义函数时函数体没有用=连接,返回值类型为Unit

def sayHello(name: String) {print(xxx)}
def sayHello(name: String) = "Hello, " +name
def sayHello(name: String): Unit = "Hello, " +name

Lazy值

Lazy值: 只有在第一次使用该变量时,变量对应的表达式才会发生计算(RDD)

lazy val lines = fromFile(filePath).mkString

异常

try{
  throw new IllegalArgumentException("x should not be negative")
}catch{
  case _: IllegalArgumentException => print("sorry, error!")
}finally{
  print("release io resource")
}

1.13 数组操作

Array

scala中Array代表的含义与Java类似,是长度不可变的数组

scala数组的底层实际上是Java数组

val a = new Array[Int](10)     //初始化为0
val a = new Array[String](10)  //初始化为null
val a = Array("aa", 30)        // 类型为Any
a(0) = 1

ArrayBuffer

import scala.collection.mutable.ArrayBuffer
val b = ArrayBuffer[Int]()
b += 1
b += (2,3,4,5)      // 重要!!! 加多个元素 b:ArrayBuffer(1,2,3,4,5)
b ++= Array(6,7,8)  // ++=操作符 可以添加其他集合中的所有元素 b:ArrayBuffer(1,2,3,4,5,6,7,8)
b.trimEnd(5)        // 从尾部开始数5个截断 (1,2,3)
b.insert(1,6)       // 在位置1插入6 (从0开始数) 也可以同时插入多个数b.insert(1,6,7,8)
b.remove(1,3)       // 从位置1开始移除掉3个元素

Array与ArrayBuffer相互转换

val a = b.toArray
val b = a.toBuffer

遍历Array和ArrayBuffer

for (i <- 0 until b.length) println(b(i))  // 注意这里是until不能用to 否则下标越界
// 跳跃遍历
for (i <- 0 until (b.length, 2) println(b(i))  // 隔2个遍历 0,2,4,6,8
// 从尾部遍历
for (i <- (0 until b.length).reverse) println(b(i)) 
// 增强for循环
for (e <- b) println(e)

数组其他操作

val a = Array(1,2,3,4,5)
val sum = a.sum
val max = a.max
scala.util.Sorting.quickSort(a)
a.mkString()    // String = 12345
a.mkString(",") // String = 1,2,3,4,5
a.mkString("<",",",">") // String = <1,2,3,4,5>

数组转换

// 1. 使用yield转换 类型不变 Array转换后还是Array 
val a = Array(1,2,3,4,5)
val a2 = for(e<-a) yield e*e // Array(1,4,9,16,25)
val b = ArrayBuffer[Int]()
b += (1,2,3,4,5)
val b2 = for (e <- b if e%2 == 0) yield e*e // b2: ArrayBuffer
// 2. 使用函数式编程转换数组
 a.filter(_%2==0).map(2*_)
 a.filter{_%2==0}.map{2*_}

算法案例

// 移除第一个负数之后的所有负数
val a = ArrayBuffer[Int]()
a += (1,2,3,4,5,-1,-3,-7,-11)
var foundFirstNegative = false
var arrayLen = a.length
var index = 0
while(index<arrayLen){
  if (a(index)>=0){
    index +=1
  }
  else{
    if (!foundFirstNegative){foundFirstNegative = true;index += 1} // 第一个负数不移除
    else {a.remove(index);arrayLen -= 1}
  }
}

这个算法的问题在于,发现了一个负数就要remove一次,则整个数组往前移一位,性能较差$o(n^2)$

// 优化:记录索引,一次性移除
val a = ArrayBuffer[Int]()
a += (1,2,3,4,5,-1,-3,-7,-11)
var foundFirstNegative = false
val keepIndexes = for (i<-0 until a.length if !foundFirstNegative || a(i)>=0) yield{
  if (a(i) < 0) foundFirstNegative = true
  i
}
for (i <- 0 until keepIndexes.length)   a(i) = a(keepIndexes(i)) // 把要保留的元素全部移到了a的前面
a.trimEnd(a.length - keepIndexes.length) // 把后面的负数截断

1.16 Map与Tuple

创建Map

val ages = Map("Leo" -> 30, "Jen" -> 21)
ages("Leo") = 21  // 默认为immutable 不可修改
val ages = scala.collenction.mutable.Map("Leo" -> 30, "Jen" -> 21)
// 另一种方式定义Map元素
val ages = Map(("Leo", 30),("Jen", 21))
// 创建一个空的HashMap,但不能创建一个空的Map
val ages = new scala.collection.mutable.HashMap[String, Int]

访问Map元素

val leoAge = ages("leo")
val leoAge = if(ages.contains("Leo")) ages("Leo") else 0
val leoAge = ages.getOrElse("Leo", 0)

修改Map元素

ages("Leo") = 31
ages += ("Mike" -> 35, "Tom" -> 30)
ages -= "Mike"
// 更新immutable的map
val ages2 = ages + ("Mike" -> 35, "Tom" -> 30)  // ages2是可变的
val ages3 = ages - "Tom"
val ages2 = ages + ("Leo" -> 31)  //【注意】这里会自动对原有元素Leo的值进行修改,而不是增加新的元素

遍历Map元素

for ((key, value) <- ages) println(key, value)
for (key <- ages.keySet)   println(key)
for (value <- ages.values) println(value)
for ((key, value) <- ages) yield(value, key) // 反转key和value

SortedMap和LinkedHashMap

// SortedMap可以自动对Map的key排序
val ages = scala.collection.immutable.SortedMap("leo"->30, "alice"->15, "jen"->25)
ages.keySet // Set(alice, jen, leo)
// LinkedHashMap可以记住插入的顺序
val ages = new scala.collection.mutable.LinkedHashMap[String, Int]()
ages("leo") = 30
ages("alice") = 21

Tuple

val t = ("leo", 30)
t._1 // 下标从1开始
val names = Array("leo", "jack", "mike")
val ages = Array(30, 24, 26)
val nameAges = names.zip(ages) // Array((leo, 30), (jack, 24), (mike, 26)) 元素为Tuple
for ((name, age) <- nameAges) println(name +":"+ age)

1.17 面向对象编程

定义一个简单的类

class HelloWorld{
  private var name = "leo"
  def sayHello() = {println("hello! "+name)}
  def getName = name
}
val hw = new HelloWorld
hw.name      // 不行,因为是private,只能在类里使用
hw.sayHello()
hw.getName   // 不能用getName(),因为定义时没有()

getter与setter

// 定义不带private的var field,此时scala生成的面向JVM的类时,会定义为private的name字段,并提供public的getter和setter方法
// 如果使用private修饰field, 则生成的getter和setter也是private的
// 如果定义val field,则只会生成getter方法
// 如果不希望生成setter和getter方法,则将field声明为private[this]
class Student{
  var name = "leo" // private 自动生成getter和setter
}
//调用getter和setter方法,分别叫做name和name_=
val leo = new Student
print(leo.name)
leo.name = "leo1" // 调用name_=方法重新set name

自定义getter与setter

class Student{
  private var myName = "leo"
  def name = "your name is "+myName
  def name_=(newValue: String){      // name_= 等号不能有空格
    print("\nyou cannot edit your name")
  }
}

仅暴露field的getter方法

class Student{
  private var myName = "leo"
  def updateName(newName: String){
    if(newName == "leo1") myName = newName
    else print("not accept this new name!")
  }
  def name = "your name is "+myName
}

private[this]的使用

class Student{
  private var myAge = 0
  def age_=(newValue: Int){
    if(newValue > 0) myAge = newValue
    else print("illegal age!")
  }
  def age = myAge
  def older(s: Student) = {  
    myAge > s.myAge
  }
}

val s1 = new Student
s1.age = 20
val s2 = new Student
s2.age = 25
s1.older(s2)

Java风格的getter和setter 加上注解 @BeanProperty var name: String = _

protected

// 用protected修饰的field和method,在子类中不需要用super关键字,可以直接访问
// 还可以使用protected[this],则只能在当前子类对象中访问父类的field和method,无法通过其他子类对象访问父类的field和method
class Person{
  protected var name: String = "leo"
  protected[this] var hobby: String = "game"
}
class Student extends Person{
  def sayHello = println("hello"+name)
  def makeFriends(s: Student){
     println("my hobby is "+hobby+", your hobby is "+ s.hobby) // error: value hobby is not a member of Student
  }
}

辅助constructor

class Student{
  private var name = ""
  private var age = 0
  def this(name: String){
    this()
    this.name = name
  }
  def this(name: String, age: Int){
    this(name)
    this.age = age
  }
}

主constructor

// Scala中,主constructor是与类名放在一起的,与java不同
// 而且类中,没有定义在任何方法或者代码块之中的代码,就是主constructor的代码
class Student(val name: String, val age: Int){
  println("your name is "+name+", your age is "+age)
}
// 主构造参数也可以有默认参数
class Student(val name: String = "leo", val age: Int = 18){
  println("your name is "+name+", your age is "+age)
}
val s = new Student
  
// 如果主constructor传入的参数没有修饰,如name: String,如果类内部的方法使用到了,那么会声明为private[this] name; 否则没有该field,就只能被constructor代码使用

调用父类的constructor

// scala中,每个类可以有一个主constructor和多个辅助constructor,而每个辅助的第一行都必须是调用其他辅助constructor或者主constructor,因此子类的辅助constructor是一定不可能直接调用父类的constructor的
// 只能在子类的主constructor中调用父类的constructor => Person(name, age)
// 【注意】如果是父类中接收的参数,比如name和age,子类中接收时,就不要用任何val或者var来修饰,否则会任务是子类要覆盖父类的field
class Person(val name: String, val age: Int){
}
class Student(name: String, age: Int, var score: Double) extends Person(name, age){
  def this(name: String){
    this(name, 0, 0)
  }
  def this(age: Int){
    this("leo", age, 0)
  }
}

内部类

import scala.collection.mutable.ArrayBuffer
class Class{
  class Student(name: String){}
  val students = new ArrayBuffer[Student]
  def getStudent(name: String) = {
    new Student(name)
  }
}
val c1 = new Class
val s1 = c1.getStudent("leo")
c1.students += s1
/*************/
val c2 = new Class
val s2 = c2.getStudent("leo")
c1.students += s2  // [error]type mismatch s2是c2外部类的Student实例,c1无法获得

object

// object相当于class的单个实例,通常放一些静态的field或者method,第一次调用object的方法时,就会执行object的constructor,也就是object内部不在method中的代码,但是object不能定义接受参数的constructor
// 注意,object的constructor只会在第一次被调用时执行,以后不会再次执行
// object通常用于作为单例模式的实现,或者放class的静态成员,比如工具方法
object Person{
    private eyeNum = 2
    println("this Person object")
    def getEyeNum = eyeNum
}
/*******让object继承抽象类*******/
abstract class Hello(var msg:String){
  def sayHello(name: String): Unit
}
object HelloImpl extends Hello("hello"){
  override def sayHello(name: String) = {
    println(msg+", "+name)
  }
}
HelloImpl.sayHello("leo")
/******用object实现枚举功能******/
object Season extends Enumeration{
  val SPRING, SUMMER, AUTUMN, WINTER = Value // 调用Value方法来初始化枚举值
}
Season.WINTER // Seaon.Value = WINTER
// 还可以用过Value传入枚举值的id和name,
object Season extends Enumeration{
  val SPRING = Value(0, "Spring")
  val SUMMER = Value(1, "SUMMER")
  val AUTUMN = Value(2, "Autumn")
  val WINTER = Value(3, "winter")
}
Season.SPRING.id
Season.SPRING.toString
// 可以反过来通过id和name找到枚举值
Season(0)
Season.withName("Spring")
// 增强for循环之间打印所有枚举值
for (ele <- Season.values) prinln(ele)

apply方法

// object中非常重要的一个特殊方法就是apply方法,通常在object中实现apply方法,并在其中实现构造object对象的功能
// 而创建伴生类的对象时,通常不会使用new class的方式,而是使用Class()的方式,隐式地调用伴生对象的apply方法,这样会让对象创建更加简洁,如 val a = Array(1,2,3,4,5)
class Person(val name:String)
object Person{
  def apply(name:String) = new Person(name)
}
val p = Person("leo")

main方法

// scala的main方法必须在object中实现
// App Trait的工作原理为:App Trait继承自DelayedInit Trait,scalac命令进行编译时,会把继承App Trait的object的constructor代码都放到DelayedInit Trait的delayedInit方法中执行
object HelloWorld extends App{
    if (args.length > 0) println("Hello, "+args(0))
    else println("Hello World!")
}
// shell
scalac HelloWorld.scala
scala HelloWorld leo
scala -Dscala.time HelloWorld  // 同时显示程序运行时间

继承

class Person{
  private var name = "leo"
  def getName = name
}
class Student extends Person{
  private var score = "A"
  def getScore = score
}
/**override和super**/
class Person{
  private var name = "leo"
  def getName = name
}
class Student extends Person{
  private var score = "A"
  def getScore = score
  override def getName = "hi i'm " + super.getName  // 注意用super
}
/**override field**/
class Person{
  val name: String = "Person"
  def age: Int = 0
}
class Student extends Person{
  override val name: String = "leo"
  override val age: Int = 30  // 覆盖的是def方法
}

isInstanceOf和asInstanceOf

// 如果我们创建了子类的对象,又将其赋予了父类类型的变量。则在后续程序中,我们又需要将父类类型的变量转换为子类类型的变量,应该如何做?
// 首先,需要使用isInstanceOf判断对象是否是指定类的对象,若是,则可以用asInstanceOf将对象转换为指定类型
// 注意,如果对象是null,则isInstanceOf一定返回false,asInstanceOf一定返回null
// 注意,如果没有用isInstanceOf先判断对象是否为指定类的实例,就直接用asInstanceOf转换,则可能会抛出异常
class Person
class Student extends Person
val p: Person = new Student
val s: Student = null
if (p.isInstanceOf[Student])  s = p.asInstanceOf[Student]

getClass和classOf

// 对象.getClass可以精确获取对象的类,classOf[类]可以精确获取类,然后使用==操作符即可判断
class Person
class Student extends Person
val p: Person = new Student
p.isInstanceOf[Person]   //true
p.isInstanceOf[Student]  //true
p.getClass == classOf[Person]  // false
p.getClass == classOf[Student] // true

使用模式匹配进行类型判断

// 类似isInstanceOf,不是精确判断
p match{
  case per: Person => println("person")
  case _ => println("unknown type")
}

匿名子类

// 匿名内部类,就是说可以定义一个类的没有名称的子类,并直接创建其对象,然后将对象的引用赋予一个变量。之后甚至可以将该匿名子类的对象传递给其他函数。
class Person(protected val name: String){
  def sayHello = "hello i'm " + name
}
val p = new Person("leo"){
  override def sayHello = "hi i'm " + name
}
def greeting(p: Person{def sayHello: String}){
  println(p.sayHello)
}
greeting(p) // hi i'm leo

抽象类

abstract class Person(val name: String){
  def sayHello: Unit
}
class Student(name: String) extends Person{
  def sayHello: Unit = println("hello "+name)  // 不用override
}
// 抽象field,在父类中定义了field,但没有给出初始值,则此field为抽象field
abstract class Person{
  val name: String
}
class Student extends Person{
  val name: String = "leo"
}

Trait

将Trait作为接口使用

// 类继承trait后,必须实现其中的抽象方法,实现时不需要使用override
// scala不支持对类进行多重继承,但可以用with继承多个trait
trait SayHello{
  def sayHello(name: String)
}
trait MakeFriends{
  def makeFriends(p: Person)
}
class Person(val name: String) extends SayHello with MakeFriends{
  def sayHello(name: String) = println("hello "+ name)
  def makeFriends(p: Person) = println("hi " + p.name)
}
val p1 = new Person("leo")
val p2 = new Person("anna")
p1.makeFriends(p2)

在Trait中定义具体方法/字段

// Trait中可以包含一些很多类都通用的方法,比如打印日志等,spark中就使用了Logger trait
trait Logger{
  def log(msg: String) = println(msg)
}
class Person(val name: String) extends Logger{
  def makeFriends(p: Person){
    println("hi i'm "+ name +", nice to meet you "+ p.name)
    log("makeFriends method is invoked with parameter Person[name="+p.name+"]")
  }
}
// 定义字段
trait Person{
  val eyeNum: Int = 2
}
class Student(val name: String) extends Person{
  def sayHello = println("hi i'm "+ name + ", i have " + eyeNum + " eyes.")
}

在Trait中定义抽象字段

trait SayHello{
  val msg: String
  def sayHello(name: String) = println(msg+", "+name)
}
class Person(val name: String) extends SayHello{
  val msg: String = "hello"
  def makeFriends(p: Person){
    sayHello(p.name)
    println("I'm "+name+"!")
  }
}

为实例混入trait

// 有时我们可以在创建类的对象时,指定该对象混入某个trait,这样,就只有这个对象混入该trait的方法,而类的其他对象则没有
trait Logged{
  def log(msg: String){}
}
trait MyLogger extends Logged{
  override def log(msg: String) {
    println("log: " + msg)
  }
}
class Person(val name: String) extends Logged{
  def sayHello {
    println("hi, i'm " + name)
    log("sayHello is invoked!)
  }
val p1 = new Person("leo")
p1.sayHello  // hi, i'm leo
val p2 = new Person("jack") with MyLogger  // 动态地混入trait

trait调用链

// scala中支持让类继承多个trait后,依次调用多个trait中的同一个方法,只要让多个trait的同一个方法中,在最后都执行super即可
// 类中调用多个trait中都有的这个方法时,首先会从最右边的trait方法开始执行,然后依次往左执行,形成一个调用链条
// 这种特性非常强大,其实就相当于设计模式中的责任链模式的一种具体实现依赖
trait Handler{
  def handle(data: String){
  }
}
trait DataValidHandler extends Handler{
  override def handle(data: String){  // 覆盖Handler的handle方法
    println("check data: "+data)  // 自己的逻辑
    super.handle(data)  // 调用下一个trait
  }
}
trait SignatureValidHandler extends Handler{
  override def handle(data: String){
    println("check signature: "+data)
    super.handle(data)
  }
}
class Person(val name: String) extends SignatureValidHandler with DataValidHandler{
  def sayHello = { println("hello, "+name); handle(name)}
}

在trait中覆盖抽象方法

// 覆盖时,如果使用了super.方法的代码,则无法通过编译,因为super.方法就会去掉父trait的抽象方法,此时子trait的该方法还是会被认为是抽象的
// 如果此时要通过编译,就得给子trait的方法加上abstract override修饰
trait Logger{
  def log(msg: String)
}
trait MyLogger extends Logger{
  abstract override def log(msg: String) {super.log(msg)}
}

混合使用trait的具体方法和抽象方法

// 在trait中可以混合使用trait的具体方法和抽象方法
// 可以让具体方法依赖于抽象方法,而抽象方法则放到继承trait的类中去实现
// 这种trait其实就是设计模式中的模板设计模式的体现
trait Valid{
  def getName: String  // 抽象方法
  def valid: Boolean = {
    getName == "leo"
  }
}
class Person(val name: String) extends Valid{
  println(valid)
  def getName = name
}

trait的构造机制

// 不包含在任何方法中的代码就是trait的构造代码
// 而继承了trait的类的构造机制如下: 1、父类的构造函数先执行;2、trait的构造代码执行,多个trait从左到右依次执行;3、构造trait时先构造父trait,如果多个trait继承同一个父trait,则父trait只会构造一次;4、所有trait构造完毕之后,子类的构造函数执行
class Person{ println("Person's constructor!") }
trait Logger{ println("Logger's constructor!") }
trait MyLogger extends Logger{ println("MyLogger's constructor!") }
trait TimeLogger extends Logger{ println("TimeLogger's constructor!") }
class Student extends Person with MyLogger with TimeLogger{
  println("Student's constructor!")
}
val s = new Student()
/**
Person's constructor!
Logger's constructor!
MyLogger's constructor!
TimeLogger's constructor!
Student's constructor!
*/

trait field的初始化

// scala中,trait是没有接收参数的构造函数的,这是trait与class的唯一区别,但是如果需求是要trait能够对field进行初始化,就只能用提前定义
trait SayHello{
  val msg: String
  println(msg.toString)
}
class Person extends SayHello{
  val msg: String = "init"
}
val p = new Person // error: java.lang.NullPointException 
// 过程: p先构造父类(没有父类),再构造trait,此时由于msg没初始化msg.toString肯定是空指针
  
/***提前定义***/
class Person
val p = new{
  val msg: String = "null"
} with Person with SayHello    // 动态混入

class Person extends {
  val msg: String = "init"    // 因为要先构造父类
} with SayHello()
 
/***lazy value***/  
trait SayHello{
  lazy val msg: String = null   
  println(msg.toString)
}
class Person extends SayHello{
  override lazy val msg: String = "init"
}

trait继承class

class MyUtil{
  def printMsg(msg: String) = println(msg)
}
trait Logger extends MyUtil {
  def log(msg: String) = printMsg("log: "+msg)
}
class Person(val name: String) extends Logger{
  def sayHello{
    log("hi i'm" + name)
    printMsg("hi i'm" + name)
  }
}

1.24 函数式编程

将函数赋值给变量

// scala中可以将函数作为值赋值为变量
// scala语法规定,将函数作为值赋值为变量时,必须在函数后加上空格和下划线

def sayHello(name: String) = println("hello! "+name)
val sayHelloFunc = sayHello _
sayHelloFunc("leo")

匿名函数

// 匿名函数:函数不需要命名
// 可以直接定义函数之后,将函数赋值给某个变量;也可以将直接定义的匿名函数传入其他函数之中
// (参数名: 参数类型) => 函数体
val sayHelloFunc = (name: String) => println("hello, "+name)
sayHelloFunc("leo")

高阶函数

// scala中可以直接将某个函数传入其他函数,作为参数。是Java不具备的。
// 接收其他函数作为参数的函数,称作高阶函数
val sayHelloFunc = (name: String) => println("hello, "+name)
def greeting(func: (String)=>Unit, name: String){func(name)}
greeting(sayHelloFunc, "leo")

Array(1,2,3,4,5).map((num: Int)=>num*num)
// 函数作为返回值 
def getGreetingFunc(msg: String) = (name: String) => println(msg+", "+name)
val greetingFunc = getGreetingFunc("hello")
greetingFunc("leo")  // hello, leo

高阶函数的类型推断

// 高阶函数可以自动推断出参数类型,而不需要写明类型;而且对于只有一个参数的函数,还可以省去其小括号;如果仅有一个参数在右侧的函数体内只使用一次,还可以将接收参数省略,并且将参数用_来替代
def greeting(func: (String) => Unit, name: String) {func(name)}
greeting((name) => println("hello "+name), "leo") // name的String类型可以不用写了
greeting(name => println("hello "+name), "leo") // name的()可以不用写了
greeting(_ => println("hello "+_), "leo") 

def triple(func: (Int)=>Int) = {func(3)}
triple(5+_)  // 8

scala的常用高阶函数

// map
Array(1, 2, 3).map(2 * _)
// foreach: 对传入的每个元素都进行处理,但是没有返回值
(1 to 9).map("*" * _).foreach(println _)
// filter: 对传入的每个元素都进行条件判断
(1 to 20).filter(_ % 2 == 0)
// reduceLeft: 从左侧开始,进行reduce操作
(1 to 9).reduceLeft(_ * _)
//sortWith: 对元素进行两两相比,进行排序
Array(3,2,5,4,10,1).sortWith(_ < _) // Array(1,2,3,4,5,10)

闭包

// 函数在变量不处于其有效作用域时,还能够对变量进行访问,即为闭包
def getGreetingFunc(msg: String) = (name: String) => println(msg+", "+name)
var greetingFuncHello = getGreetingFunc("hello")
val greetingFuncHi = getGreetingFunc("hi")
// msg只是一个局部变量,确诊getGreetingFunc执行完之后,还可以继续存在创建的函数之中,greetingFuncHello("leo")调用时,值为"hello"的msg仍保留在了返回的函数体的内部,可以反复的使用
// 这种变量超出了其作用域,还可以使用的情况,即为闭包

// scala通过给每个函数创建对象来实现闭包,实际上对于getGreetingFunc函数创建的函数,msg是作为函数对象的变量存在的,因此每个函数才可以拥有不同的msg

SAM转换

// SAM-single abstract method
// 在Java中,不支持直接将函数传入一个方法作为参数,通常来说,唯一的办法就是定义一个实现了某个接口的类的实例对象,该对象只有一个方法;而这些接口都只有单个的抽象方法,也就是SAM

// 在调用Java方法时,使用的功能,SAM转换,即将SAM转换为Scala函数
// 要使用SAM转换,需要使用Scala提供的特性,隐式转换
import javax.swing._
import java.awt.event._
val button = new JButton("Click")
button.addActionListener(new ActionListener{
  override def actionPerformed(event: ActionEvent){
    println("Click Me!!")
  }
}) 
// 隐式转换
// 接收参数为ActionEvent,返回值为Unit的匿名函数
implicit def convertActionListener(actionProcessFunc:(ActionEvent)=>Unit) = new ActionListener{
  override def actionPerformed(event:ActionEvent){
    actionProcessFunc(event)
  }
}
button.addActionListener((event: ActionEvent) => println("Click Me!!"))

Currying 柯里函数

// 把原来接收两个参数的一个函数,转换为两个函数,第一个函数接收原先的第一个参数,然后返回接收原先第二个参数的第二个函数。
def sum(a: Int, b: Int)  = a+b
sum(1,1)
def sum2(a: Int) = (b: Int) => a+b  // 返回值为匿名函数
sum2(1)(1)
def sum3(a: Int)(b: Int) = a+b

return

// Scala中,不需要使用return来返回函数的值,使用return的匿名函数,是必须给出返回类型的,否则无法通过编译
def greeting(name: String) = {
  def sayHello(name:String): String = {
    return "Hello, " + name
  }
  sayHello(name)
}

1.26 集合

List

immutable

val list = List(1,2,3,4)
list.head // Int = 1
list.tail // List(2,3,4)
// List的 :: 操作方法,可以用于将head和tail合并成一个List, 0::List = List(0,1,2,3,4)

// 案例:用递归函数来给List中的每个元素都加上指定前缀并打印
def decorator(list: List[Int], prefix: String){
  if (list != Nil){
    println(prefix + list.head)
    decorator(list.tail, prefix)  // 当list = (4)时,list.tail = Nil
  }
}

LinkedList

val ll = scala.collection.mutable.LinkedList(1,2,3,4,5)
ll.elem // 头元素,类似head
ll.next // 类似tail
// 案例:使用while循环将LinkedList的每个元素*2
var currentList = ll  // 【注意】对currentList的元素操作,ll的元素也随之改变
while (currentList != Nil){
  currentList.elem = currentList.elem*2
  currentList = currenList.next
}

// 使用while循环将LinkedList每隔一个元素*2
var ll = scala.collection.mutable.LinkedList(1,2,3,4,5,6,7,8,9,10)
var currentList = ll 
var fisrt = true
while(currentList != Nil && currentList.next != Nil){ //注意两重判断
  if(f) {currentList.elem *= 2;first = false}
  currentList = currentList.next.next
  if (currentList != Nil) currentList.elem *= 2
}
ll

Set

val s = Set(1,2,3)
s + 1 // Set(1,2,3)
s + 4 // Set(1,2,3,4)
// Set不保证插入顺序
s = new scala.collection.mutable.HashSet[Int](); s+=1;s+=2;s+=3
// LinkedHashSet可以保证插入顺序
val s = scala.collection.mutable.LinkedHashSet[Int]();s+=1;s+=2;s+=3

集合的函数式编程

// Java里面是没有函数式编程的,所以也没有高阶函数,也无法直接将函数传入一个方法,或者让一个方法返回一个函数
List("a","b","c").map("xixi"+_)
List("hello world", "you me").flatMap(_.split(" ")) // List(hello, world, you, me)
List("I","have","an","apple").foreach(println(_))
List("leo", "jen", "jack").zip(List(100,70,80)) // List((leo,100), (jen,70), (jack,80))
  
 /****单词计数****/
val lines01 = scala.io.Source.fromFile("C://Users//Administrator//Desktop//test01.txt").mkString
val lines02 = scala.io.Source.fromFile("C://Users//Administrator//Desktop//test02.txt").mkString
val lines = List(lines01, lines02)
lines.flatMap(_.split(" ")).map((_, 1)).map(_._2).reduceLeft(_+_)

1.28 模式匹配

// Scala的模式匹配除了值还可以对类型进行匹配,对Array和List的元素情况进行匹配,对case class进行匹配
// match case里,只要一个分支满足了就不会进行处理了,所以不需要break
def judgeGrade(name: String, grade: String){
  grade match{
    case "A" => println("A")
    case "B" => println("B")
    case _ if name == "leo" => println(name + "you are a good boy")  // if守卫
    case _ => println("o")
  }
}

//在模式匹配中进行变量赋值(对于_的情况)
def judgeGrade(grade: String){
  grade match{
    case "A" => println("A")
    case "B" => println("B")
    case badGrade => println(badGrade)
  }
}

对类型进行模式匹配

import java.io._
def processException(e: Exception){
  e match{
    case e1: IllegalArgumentException => println(e1)
    case e2: FileNotFoundException => println(e2)
    case e3: IOException => println(e3)
    case _: Exception => println("idk your exception")
  }
}
processException(new IOException("This is an IOException!"))

对Array和List进行模式匹配

// 对Array进行模式匹配,分别可以匹配带有指定元素的数组,带有指定个数元素的数组和以某元素开头的数组
// 对List进行模式匹配,与Array类似,但是需要使用List特有的::操作符

//案例:对朋友打招呼
def greeting(arr: Array[String]){
  arr match{
    case Array("leo") => println("hi leo")
    case Array(girl1, girl2, girl3) => println("hi, girls")  // Array("leo",g1,g2)会被匹配
    case Array("leo",_*) => println("hi leo, please introduce your friends")
    case _ => println("hey who are you")
  }
}

def greeting(list: List[String]){
  list match{
    case "leo"::Nil => println("hi leo")
    case girl1::girl2::girl3::Nil => println("hi girls")
    case "leo"::tail => println("hi leo who are they")
    case _ => println("who are you")
  }
}

case class

// case class样例类,类似JavaBean,只定义field,且由scala编译器自动提供getter和setter方法,但是没有method,case class的主构造函数接收的参数通常不需要使用var或val修饰,scala自动就会使用val修饰
// scala自动为case class定义了伴生对象,也就是object,并且定义了apply()方法,该方法接收主构造函数中相同的参数,并返回case class对象
class Person
case class Teacher(name: String, subject: String) extends Person
case class Student(name: String, classroom: String) extends Person
def judgeIdentify(p: Person){
  p match{
    case Teacher(name, subject) => println("teacher "+name+" "+subject)
    case Student(name, classroom) => println("student "+name+" "+classroom)
    case _ => println("illegal")
  }
}
val leo: Person = Student("leo", "class1")
val tom: Person = Teacher("leo", "Math")
case class Worker(name: String) extends Person
val jack: Person = Worker("jack")

Option

// Option有两种值,一种是Some表示有值, 一种是None,表示没有值
// Option通常会用于模式匹配中,用于判断某个变量是有值还是没有值,这比null来得简单明了

// 案例: 成绩查询
val grades = Map("leo"->"A", "jack"->"B", "jen"->"C")
def getGrade(name: String){
  val grade = grades.get(name)
  grade match{
    case Some(grade) => println("your grade is "+grade)
    case None => println("sorry no your grade")
  }
}

1.30 类型参数

class Student[T](val localID: T){
  def getSchoolID(hukouID: T) = "S-"+hukouID+"-"+localID
}
val leo = new Student[Int](111)
leo.getSchoolID("2") // 不行 required:Int

泛型函数

// 泛型函数,与泛型类类似,可以给某个函数在声明时指定泛型类型,然后在函数体内,多个变量或者返回值直接,就可以使用泛型类型进行声明,从而对某个特殊的变量,或者多个变量,进行强制性的类型限制。
// 与泛型类一样,你可以通过给使用了泛型类型的变量传递值来让Scala自动推断泛型的实际类型,也可以在调用函数时,手动指定泛型类型。

/**案例: 卡片售卖机,可以指定卡片的内容,内容可以是String/Int**/
def getCard[T](content: T) = {
  if (content.isInstanceOf[Int]) "card:001,"+content
  else if (content.isInstanceOf[String]) "card: this is your card, "+content
  else "card: "+content
}
getCard[String]("hello world")

上边界Bounds

// 在指定泛型类型的时候,有时,我们需要对泛型类型的范围进行界定,而不是可以是任意的类型,比如,我们可能要求某个泛型类型,它就必须是某个类的子类,这样在程序中就可以放心地调用泛型类型继承的父类的方法,程序才能正常的使用和运行。此时就可以使用上下边界Bounds的特性。
// Scala的上下边界特性允许泛型类型必须是某个类的子类,或者必须是某个类的父类

/***案例: 在派对上交朋友***/
class Person(val name: String){
  def sayHello = println("hello i'm"+name)
  def makeFriends(p: Person){
    sayHello
    p.sayHello
  }
}
class Student(name: String) extends Person(name)
class Party[T <: Person](p1: T, p2: T){
  def play = p1.makeFriends(p2)
}
class Worker(val name: String)
val leo = new Student("leo")
val tom = new Worker("tom")
val party = new Party(leo, tom) // error
val jack = new Student("jack")
val party = new Party(leo, jack)
party.play()

下边界Bounds

// 下边界:即指定泛型类型必须是某个类的父类
class Father(val name: String)
class Child(name: String) extends Father(name)
def getIDCard[R >: Child](person: R){
  if(person.getClass == classOf[Child]) println("child")
  else if (person.getClass == classOf[Father]) println("father")
  else println("your are not allowed to getIDCard")
}

View Bounds

// 上下边界Bounds,虽然可以让一种泛型类型,支持有父子关系的多种类型。但是,在某个类与上下边界Bounds指定的父子类型范围内的类都没有任何关系时,则默认是不能接收的。
// 然而,View Bounds作为一种上下边界Bounds的加强版,支持可以对类型进行隐式转换,将指定的类型进行隐式转换后,再判断是否在边界指定的类型范围内

/***跟小狗交朋友***/
class Person(val name: String){
  def sayHello = println("hello i'm"+name)
  def makeFriends(p: Person){
    sayHello
    p.sayHello
  }
}
class Student(name: String) extends Person(name)
class Dog(val name: String)}{
  def sayHello = println("won won i'm "+name)
}
implicit def dog2person(obj: Object): Person = if(obj.isInstanceOf[Dog]){
  val dog = obj.asInstanceOf[Dog]
  new Person(dog.name)
}else Nil
class Party[T <% Person](p1: T, p2: T){}  // <%
val leo = new Student("leo")
val doggy = new Dog("doggy")
val party = new Party(leo, doggy)

Context Bounds

// Context Bounds是一种特殊的Bounds,它会根据泛型类型的声明,比如“T: 类型”要求必须存在一个类型为“类型[T]”的隐式值。

/***案例:使用Scala内置的比较器比较大小***/
class Calculator[T: Ordering](val num1: T, val num2: T){
  def max(implicit order: Ordering[T]) = if(order.compare(num1, num2) > 0) num1 else num2
}
val cal = new Calculator(1,5) // cal: Calculator[Int] = Calculator@2a76840c
cal.max  // 5

Manifest Context Bounds

// 在Scala中,如果要实例化一个泛型数组,就必须使用Manifest Context Bounds。也就是说,如果数组元素类型为T的话,需要为类或者函数定义[T:Manifest]泛型类型,这样才能实例化Array[T]这种泛型数组。

/***案例:打包饭菜(一种食品打成一包)***/
class Meat(val name: String)
class Vegetable(val name: String)
def packageFood(foods: T*) = {     // foods: T* 变长参数
  val foodPackage = new Array[T](foods.length)
  for(i <- 0 until foods.length) foodPackage(i) = foods(i)
  foodPackage
}
val yuxiangrousi = new Meat("yuxiangrousi")
val yangpai = new Meat("yangpai")
val bocai = new Vegetable("bocai")
packageFood(yuxiangrousi, yangpai) // Array[Object] = Array(Meat@2ca65ce4, Meat@7957dc72, Vegetable@6058e535)
packageFood(yuxiangrousi, yangpai, bocai) // Array[Meat] = Array(Meat@2ca65ce4, Meat@7957dc72)

协变和逆变

// SCala的协变和逆变是非常有特色的,完全解决了Java中的泛型的一大缺憾
// 举例来说,Java中,如果有Professional是Master的子类,那么Card[Professional]不是Card[Master]的子类。而Scala中,只要灵活使用协变和逆变,就可以解决Java泛型的问题。

/***案例:进入会场***/
class Master
class Professional extends Master
  // 大师以及大师级别以下的名片都可以进入会场
class Card[+T](val name: String) // +T 协变
def enterMeet(card: Card[Master]){
  println("welcomt to this meeting "+ card.name)
}
val leo = new Card[Master]("leo")
val jack = new Card[Professional]("jack")
jack.isInstanceOf[Card[Master]]  // true Card[Professional]是Card[Master]的子类
enterMeet(jack)  // 可以进入
  // 专家及专家以上级别的名片都可以进入会场
class Card[-T](val name: String)  // -T 逆变 含义是把Master强制转换为Professional的子类
val leo = new Card[Professional]("leo")
val jack = new Card[Master]("jack")
jack.isInstanceOf[Card[Professional]]  // true
def enterMeet(card: Card[Professional]){
  println("welcomt to this meeting "+ card.name)
}
enterMeet(jack)

Existential Type

// 在Scala里,有一种特殊的类型参数,就是Existential Type,存在性类型
Array[T] forSome {type T}
Array[_]

1.32 隐式转换和隐式参数

Scala的隐式转换,最核心的就是定义隐式转换参数,即implicit conversion function。定义的隐式转换函数,只要在编写的程序内引入,就会被Scala自动使用。Scala会根据隐式转换函数的签名,在程序中使用到隐式转换接收的参数类型定义的对象时,会自动将其传入隐式转换函数,转换为另外一种类型的对象并返回。这就是“隐式转换”。

隐式转换

// 要实现隐式转换,只要程序可见的范围内定义隐式转换函数即可,Scala会自动使用隐式转换函数。隐式转换函数与普通函数唯一的语法区别就是,要以implicit开头,而且最好要定义函数返回类型。
/***案例:特殊售票窗口(只接受特殊人群,比如学生、老人等)***/
class SpecialPerson(val name: String)
class Student(val name: String)
class Oldman(val name: String)
implicit object2SpecialPerson(obj: Object): SpecialPerson = {
  if(obj.getClass == classOf[Student]) {val stu = obj.asInstanceOf[Student]; new SpecialPerson(stu.name)}
  else if(obj.getClass == classOf[Oldman]) {val old = obj.asInstanceOf[Oldman]; new SpecialPerson(old.name)}
  else Nil
}
var ticketNum = 0
def buySpecialTicket(p: SpecialPerson) = {
  ticketNum += 1
  "T-"+ticketNum
}
val s = new Student("stu")
buySpecialTicket(s)

使用隐式转换加强现有类型

// 隐式转换非常强大的一个功能,就是可以加强现有类型的功能。也就是说,可以为某个类定义一个加强版的类,并定义互相之间的隐式转换,从而让源类在使用加强版的方法时,有Scala自动进行隐式转换为加强类,然后再调用该方法。
/***案例:超人变身***/
class Man(val name: String)
class Superman(val name: String){
  def emitLaser = println("emit laser")
}
implicit def man2superman(man: Man): Superman = new Superman(man.name)
val leo = new Man("leo")
leo.emitLaser

导入隐式转换

Scala默认会使用两种隐式转换,一种是源类型,或者目标类型的伴生对象内的隐式转换函数;一种是当前程序作用域内的可以用唯一标识符表示的隐式转换函数。

如果隐式转换函数不在上述两种情况下的话,那么就必须手动使用import语法引入某个包下的隐式转换函数,比如import test._ 通常建议,仅仅在需要进行隐式转换的地方,比如某个函数或者方法内,用import导入隐式转换函数,避免不需要的隐式转换。

隐式转换的发生时机

  1. 调用某个函数,但是给函数传入的参数的类型,与函数定义的接收参数类型不匹配
  2. 使用某个类型的对象,调用某个方法,而这个方法并不存在于该类型时
  3. 使用某个类型的对象调用某个方法,虽然该类型有这个方法,但是给方法传入的参数类型,与方法定义的接收参数的类型不匹配
/***3. 案例:特殊售票窗口加强版***/
class TicketHouse{
  var ticketNum = 0
  def buySpecialTicket(p: SpecialPerson) = {
    ticketNum += 1
    "T-"+ticketNum
  }
}
val ticketHouse = new TicketHouse
val leo = new Student("leo")
ticketHouse.buySpecialTicket(leo)

隐式参数

// 所谓的隐式参数,指的是在函数或者方法中,定义一个用implicit修饰的参数,此时Scala会尝试找到一个指定类型的,用implicit修饰的对象,即隐式值,并注入参数。
// Scala会在两个范围内查找:一种是当前作用域内可见的val或var定义的隐式变量;一种是隐式参数类型的伴生对象内的隐式值
/***案例:考试签到***/
class SignPen{
  def write(cotent: String) = println(content)
}
implicit val signPen = new SignPen   // 签到只需要一只笔!
def signForExam(name: String)(implicit signPen: SignPen){
  signPen.write(name+" come to exam in time.")
}

1.33 Actor入门

Actor类似Java的多线程编程,但是又有所不同。Scala的Actor尽可能地避免锁和共享状态,从而避免多线程并发时出现资源争用的情况,进而提升多线程编程的性能。此外,Scala Actor的这种模型还可以避免死锁等一系列传统多线程编程的问题。

Spark中使用的分布式多线程框架,是Akka。Akka也实现了类似Scala Actor的模型,其核心概念同样也是Actor。

Actor的创建、启动和消息收发

// Scala提供了Actor trait来让我们更方便地进行actor多线程编程,就Actor trait就类似于Java中的Thread和Runnable一样,是基础的多线程基类和接口。我们只要重写Actor trait的act方法,即可实现自己的线程执行体,与Java中重写run方法类似。此外,使用start方法启动Actor,使用!向Actor发送消息,Actor使用receive和模式匹配接收消息。

/***案例:Actor Hello World***/
class HelloActor extends Actor{
  def act(){
    while(true){
      receive{case name: String => println("hello "+name)}
    }
  }
}
val helloActor = new HelloActor
helloActor.start()
helloActor!"leo"   // hello leo

收发case class类型的消息

/***案例:用户注册登录后台接口***/
case class Login(username: String, password: String)
case class Register(username: String, password: String)
class UserManageActor extends Actor{
  def act(){
    while(true){
      receive{
        case Login(username, password) => println("login: "+name+", "+password)
          case Register(username, password) => println("register: "+name+", "+password)
      }
    }
  }
}
val userManageActor = new UserManageActor
userManageActor.start()
userManageActor ! Register("leo", "123")

Actor之间互相收发消息

// 如果两个Actor之间要互相收发消息,那么Scala建议一个Actor向另一个Actor发送消息时,同时带上自己的引用;其他Actor收到自己的消息时,直接通过发送消息的actor的引用,即可以给它回复消息。
/***案例:打电话***/
case class Message(content: String, sender: Actor)
class LeoActor extends Actor{
  def act(){
    while(true){
      receive{
        case Message(content, sender) => {
          println("leo: "+content)
          sender!"please call me later."  // 注意!不能有空格
        }
      }
    }
  }
}

class JackActor(val leoActor: Actor) extends Actor{
  def act(){
    leoActor!Message("hi i'm jack", this) // 传jackActor自己的引用
    receive{
      case response: String => println("jack telephone: "+response)
    }
  }
}
val leoActor = new LeoActor
leoActor.start()
val jackActor = new JackActor(leoActor)
jackActor.start()

同步消息和Future

// 默认情况下,消息都是异步的;但是如果希望发送的消息是同步,即对方接收后,一定要给自己返回结果,那么可以使用!?的方式发送消息。
val reply = actor!?message
// 如果要异步发送一个消息,但是在后续要获得消息的返回值,那么可以使用Future。即!!语法。
val future = actor!!message
val reply = future()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容