# scala **Repository Path**: nicelusanjin/scala ## Basic Information - **Project Name**: scala - **Description**: Scala笔记 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-04-27 - **Last Updated**: 2025-05-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: Notes, Languages ## README ### 变量 ```scala val/var 变量名:变量类型 = 初始值 ``` * val定义的是不可重新赋值的变量,也就是自定义常量 * var定义的是可重新赋值的变量 > 注意:scala中定义变量时,类型写在变量名后面 ### 字符串 * 使用双引号(可以用+) * 使用差值表达式 ```scala val/var 变量名 = s"${变量/表达式}"字符串 ``` * 使用三引号(大段文本保存) * 惰性赋值 当有一些变量保存的数据较大时,而这些数据又不需要马上加载到JVM内存中,就可以使用惰性赋值来提高效率(用到时才会加载到内存)。 ```scala lazy val 变量名 = 表达式 ``` > lazy不能修饰var类型的变量 ### 标识符 * 变量或方法:从第二个单词开始,每个单词的首字母都大写,其他字母全部小写(小驼峰命名法) ``` zhangSanAge, student_Country, getSum ``` * 类或特质(Trait):每个单词的首字母都大写,其他所有字母全部小写(大驼峰命名法) ``` Person, StudentDemo, OrderItems ``` * 包:全部小写,一般是公司的域名反写,多级包之间用.隔开 ``` com.itheima.add cn.itcast.update ``` ### 数据类型 | 基础类型 | 类型说明 | | -------- | ------------------------ | | Byte | 8位带符号整数 | | Short | 16位带符号整数 | | Int | 32位带符号整数 | | Long | 64位带符号整数 | | Char | 16位无符号Unicode字符 | | String | Char类型的序列(字符串) | | Float | 32位单精度浮点数 | | Double | 64位双精度浮点数 | | Boolean | true或false | >1. scala中所有的类型都使用大写字母开头 >2. 整型使用Int而不是Integer >3. scala中定义变量可以不写类型,让scala编译器自动推断 >4. Scala中默认整型是Int,默认的浮点型是Double ![](./images/Types.png) | 类型 | 说明 | | ------- | ------------------------------------------------------------ | | Any | **所用类型**的父类,它有两个子类AnyRef与AnyVal | | AnyVal | **所有数值类型**的父类 | | AnyRef | **所有对象类型(引用类型)**的父类 | | Unit | 表示空,Unit是AnyVal的子类,它只有一个实例,它类似于Java中的void,但scala要比java更加面向对象 | | Null | Null是AnyRef的子类,也就是说他是所有引用类型的子类,可以将null赋值给任何对象类型。 | | Nothing | 所有类型的子类,不能直接创建该类型的实例,某个方法抛出异常时,返回的就是Nothing类型,因为Nothing是所有类的子类,那么它可以赋值为任何类型 | ### 类型转换 #### 1. 值类型的类型转换 ##### 1.1 自动类型转换 范围小的数据类型的值会自动转换为范围大的数据类型的值。 `Byte, Short, Char -> Int -> Long -> Float -> Double` ##### 1.2 强制类型转换 范围大的数据类型通过一定格式(强制类型转换函数)可以将其转换成范围小的数据类型值。 ```scala val/var 变量名:数据类型 = 具体的值.toXxx //Xxx表示你要转换的数据类型 ``` #### 2. 值类型和String类型之间的相互转换 ##### 2.1 值类型的数据转换为String类型 ```scala val/var 变量名:String = 值类型数据 + "" val/var 变量名:String = 值类型数据.toString ``` ##### 2.2 String类型的数据转换成其对应的值类型 ```scala val/var 变量名:值类型 = 字符串值.toXxx ``` > String类型的数据转为Char类型的数据,方式有点特殊,并不是调用toChar,而是toCharArray ### 键盘录入 1. 导包 > 格式:import scala.io.StdIn 2. 通过`StdIn.readXxx()`来接收用户键盘录入的数据 > 接收字符串数据:StdIn.readLine() > > 接收整数数据:StdIn.readInt() ### 算术运算符 | 运算符 | 功能解释 | | ------ | -------------------------------------------- | | + | 加号. 1)表示正数 2)普通加法操作 3)字符串拼接 | | - | 减号. 1)表示负数 2)普通减法操作 | | * | 乘号 | | / | 除号 | | % | 取余 | > 1. scala中没有++和--,但是有+=,-=,*=,/=,%= > 2. 两个整数相除的结果,还是整数。如果想要获取到小数,则必须有浮点数参与 ### 关系运算符 | 需求描述 | Scala代码 | | -------------------- | --------- | | 比较数据值 | ==或者!= | | 比较引用值(地址值) | eq方法 | ### 分支结构 1. 如果大括号{}内的逻辑代码只有一行,则大括号可以省略 2. 条件表达式也有返回值 ```scala val result = if(sex == "male") 1 else 0 ``` 3. 没有三元表达式 #### 块表达式 * 使用{}表示一个块表达式 * 和if表达式一样,块表达式也是有值的 * 值就是最后一个表达式的值 ```scala val a ={ println("1+1") 1+1 } println("a:" + a) ``` ### 循环结构 #### 1. for循环 ```scala for(i <- 表达式/数组/集合){ //执行逻辑 } ``` 示例: ```scala val nums = 1 to 10 "nums: scala.collection.immutable.Range.Inclusive = Range 1 to 10" for(i <- nums){ ... } for(i <- 1 to 10){ ... } ``` ##### 1.1 守卫 ```scala for(i <- 表达式/数组/集合 if 表达式){ //... } ``` 示例 ```scala //if中的()可以不写 for(i <- 1 to 10 if i % 3 == 0) println(i) ``` ##### 1.2 推导式 Scala中的for循环也是有返回值的,在for循环中,可以使用yield表达式构建出一个集合,我们把使用yield的for表达式称之为**推导式**。 ```scala //生成10、20、30、...100集合 val v = for(i <- 1 to 10) yield i *10 println(v) ``` ### 方法 #### 1.1 语法格式 ```scala def 方法名(参数名:参数类型, 参数名:参数类型) : [return type] = { //方法体 } ``` > * 参数列表的参数类型不能省略 > * 返回值类型可以省略,由Scala编译器自动推断 > * 返回值可以不写return,默认就是{}块表达式的值 > * 若方法体只有一行代码,则{}也可以省略 > * 若定义递归方法,则不能省略返回值类型 #### 1.2 惰性方法 当记录方法返回值的变量被声明为lazy时,方法的执行将被推迟,直到我们首次使用该值时,方法才会执行。 #### 1.3 方法参数 * 默认参数 ```scala def getSum(x:Int = 10, y:Int = 20) = x + y val sum = getSum() //30 val sum2 = getSum(1, 2) //3 val sum3 = getSum(1) //21 ``` * 带名参数 ```scala val sum4 = getSum(a = 1) //21 val sum5 = getSum(b = 1) //11 ``` * 变长参数 如果方法的参数是不固定的,可以将该方法的参数定义成变长参数 ```scala def 方法名(参数名:参数类型*):返回值类型 = { //方法块 } ``` > 1. 在参数类型后面加一个*号,表示参数可以是0个或多个 > 2. 一个方法有且只能有一个变长参数,并且变长参数要放到参数列表的最后边 示例: ```scala def getSum(a:Int*) = a.sum ``` #### 1.4 方法调用方式 * 后缀调用法 ```scala //对象名.方法名(参数) Math.abs(-1) ``` * 中缀调用法 ```scala //对象名 方法名 参数 Math abs -1 1 to 10 1 + 1 ``` > 1. 如果有多个参数,使用括号括起来 > 2. 所有的操作符都是方法 > 3. 操作符是一个方法名字是符号的方法 * 花括号调用法 ```scala Math.abs{ //表达式1 //表达式2 } ``` > 方法只有一个参数,才能使用花括号调用法 * 无括号调用法 如果方法没有参数,可以在定义/调用时省略方法名后面的括号。 ```scala class Person(var name: String) { def getName:String = name } object Main extends App{ val p = new Person("John") println(p.getName) } ``` ### 函数 ```scala val 函数变量名 = (参数名:参数类型, 参数名:参数类型, ...) => 函数体 ``` > * 在Scala中,函数是一个对象(变量) > * 类似于方法,函数也有参数列表和返回值 > * 函数定义不需要使用`def`定义 > * 无需指定返回值类型 示例: ```scala val getSum = (x:Int, y:Int) => x + y val sum = getSum(11, 22) ``` ### 方法和函数的区别 * 方法是隶属于类或者对象的,在运行时,它是加载到JVM的方法区中 * 可以将函数对象赋值给一个变量,在运行时,它是加载到JVM的堆内存中 * 函数是一个对象,继承自FunctionN,函数对象有apply,curried,toString,tupled这些方法。方法则没有 > 结论:在Scala中,函数是对象,而方法是属于对象的,所以可以理解为:方法归属于函数 示例: ```scala //1. 定义方法 def add(x:Int, y:Int) = x + y //2. 尝试将方法赋值给变量 val a = add //3. 上述代码会报错 :12: error: missing argument list for method add Unapplied methods are only converted to functions when a function type is expected. You can make this conversion explicit by writing `add _` or `add(_,_)` instead of `add`. val a = add ``` 方法转换为函数: 有时候需要将方法转换为函数,例如作为变量传递,就需要将方法转换为函数 ```scala val 变量名 = 方法名 _ //格式为:方法名 + 空格 + 下划线 ``` > 注意:使用`_`即可将方法转换为函数 ### 类和对象 Scala中创建类和对象可以通过class和new关键字来实现,**用class来创建类**,**用new来创建对象** 1. 创建一个scala项目,并创建一个object类 > 注意object修饰的类是单例对象 2. 在object类中添加main方法 3. 创建Person类,并在main方法中创建Person类的对象,然后输出结果 ```scala object Main { def main(args: Array[String]): Unit = { val p = new Person() println(p) } } class Person{ } ``` ``` scala test.scala ``` #### 简写形式 * 如果类是空的,没有任何成员,可以省略`{}` * 如果构造器的参数为空,可以省略`()` ```scala object Main { def main(args: Array[String]): Unit = { val p = new Person println(p) } } class Person ``` #### 成员变量 * 在类中使用`var/val`来定义成员变量 ```scala //test.scala object Main { def main(args: Array[String]): Unit = { val p = new Person println(p.name) } } class Person{ val name = "LuXin" val age = 23 } ``` * 在定义`var`类型的成员变量时,可以使用`_`来初始化成员变量(已弃用) * String -> null * Int -> 0 * Boolean -> false * Double -> 0.0 * ... * `val`类型的成员变量,必须要自己手动初始化 ```scala //test.scala import scala.compiletime.uninitialized object Main { def main(args: Array[String]): Unit = { val p = new Person p.name = "Luxin" p.age = 23 println(p.name) } } class Person{ var name:String = uninitialized var age:Int = uninitialized } ``` #### 访问权限修饰符 和Java一样,scala也可以通过访问修饰符,来控制成员变量和成员方法是否可以被外界访问。 1. 在scala中,**没有public关键字**,任何没有被标记为private或protected的成员都是公共的 2. scala中的权限修饰符只有四种:private,private[this],protected,默认 #### 类的构造器 1. 主构造器 ```scala class 类名(var/val 参数名:类型 = 默认值, var/val 参数名:类型 = 默认值){ //构造代码块 } ``` > * 主构造器的参数列表直接定义在类的后面,添加val/var表示直接通过主构造器定义成员变量 > * 构造器参数列表可以指定默认值 > * 创建实例,调用函数构造器可以指定字段进行初始化 > * 整个class中除了字段定义和方法定义的代码都是构造代码 ```scala //test.scala object Main { def main(args: Array[String]): Unit = { val p1 = new Person println(p1.name) val p2 = new Person("LiSi", 24) println(p2.name) val p3 = new Person(age = 22) println(p3.age) } } class Person(val name:String = "LuXin", val age:Int = 23) { println("Main constructor!") } ``` > 若主构造器内的变量未用val/var修饰,意味着它不是成员变量,而是局部变量,只在类中可以访问。 2. 辅助构造器 在scala中,除了定义主构造器外,还可以根据需要来定义辅助构造器。例如:允许通过多种方式,来创建对象,这时候就可以定义其他更多的构造器。我们把除了主构造器之外的构造器称为**辅助构造器**。 * 定义辅助构造器与定义方法一样,也使用`def`关键字来定义 * 辅助构造器的默认名字都是`this`,且不能修改 ```scala def this(参数名:类型, 参数名:类型) { //第一行需要调用主构造器或者其他构造器 //构造器代码 } ``` > 注意:辅助构造器的第一行代码,必须要调用主构造器或者其他辅助构造器 ```scala object Main { def main(args: Array[String]): Unit = { val p1 = new Person println(p1.name) val p2 = new Person(Array("LiSi", "24")) println(p2.name) } } class Person(val name:String = "LuXin", val age:String = "23") { def this(arr:Array[String]) = { this(arr(0), arr(1)) } } ``` #### 单例对象 scala中是没有static关键字的,要想定义类似Java中的static变量、static方法,就要使用到scala中的单例对象了,也就是object。 单例对象表示全局仅有一个对象,也叫`孤立对象`,定义单例对象和定义类很像,就是把class换成object。 ```scala object 单例对象名{} //定义一个单例对象 ``` > 1. 在object类中定义的成员变量类似于Java中的静态变量,在内存中都只有一个对象 > 2. 在单例对象中,可以直接使用`单例对象名.`的形式调用成员 ```scala object Main { def main(args: Array[String]): Unit = { println(Dog.leg_num) } } object Dog { val leg_num = 4 } ``` 在单例对象中定义的成员方法类似于Java中的静态方法 ```scala object Main { def main(args: Array[String]): Unit = { PrintUtil.printSpliter() } } object PrintUtil { def printSpliter() = println("-" * 15) } ``` #### 定义程序入口 scala程序中,如果要运行一个程序,必须有一个main方法。在java中main方法是静态的,而在scala中没有静态方法,所以在scala中,这个main方法必须放在一个单例对象中。 定义main方法 ```scala def main(args:Array[String]):Unit = { //方法体 } ``` 示例: ```scala object Main { def main(args: Array[String]): Unit = { println("Hello Scala!") } } ``` #### 继承APP特质 创建一个object,继承自App特质(Trait),然后将需要编写在main方法中的代码,直接写在object的构造方法体内。 ```scala object 单例对象名 extends App { //方法体 } ``` 示例: ```scala object Main extends App{ println("Hello Scala2!") } ``` #### 伴生对象 Java中,将静态方法和成员方法写在一起,这是不规范的。应该将他们分离。在scala中,要实现类似的效果,可以使用伴生对象来实现。 一个class和object具有相同的名字,这个object称为伴生对象,这个class称为伴生类。 * 伴生对象必须要和伴生类一样的名字 * 伴生对象和伴生类在同一个scala源文件中 * 伴生对象和伴生类可以互相访问private属性 ```scala object Main extends App{ val p = new Person } object Person { private val address = "zjg" } class Person(val name:String = "LuXin", val age:String = "23") { println(Person.address) } ``` 或Person伴生对象访问Person类私有成员: ```scala object Main extends App{ val p = new Person Person.getName(p) } object Person { def getName(p:Person) = println(p.name) } class Person(private val name:String = "LuXin", val age:String = "23") { } ``` #### private[this]访问权限 如果某个成员的权限设置为private[this],表示只能在当前类中访问,伴生对象也不可以访问。 #### apply方法 在scala中,支持创建对象的时候,**免new**的动作,这种写法非常简便,优雅。要想实现**免new**,我们就要通过伴生对象的apply方法来实现。 ```scala object 伴生对象名 { def apply(参数名:参数类型, 参数名:参数类型...) = new 类(...) } val 对象名 = 伴生对象名(参数1, 参数2, ...) ``` #### 继承 * scala中使用extends关键字来实现继承 * 可以在子类中定义父类中没有的字段和方法,或者重写父类的方法 * 类和单例对象都可以有父类 ```scala class/object A类 extends B类{ //... } ``` #### 方法重写 * 子类要重写父类中的某一个方法,该方法必须要用override关键字来修饰 * 可以使用override来重写一个val字段 > 注意:父类用var修饰的变量,子类不能重写 * 使用super关键字来访问父类的方法 ```scala object Main extends App{ val p = new Student println(p.name) // Ren p.sayHello() } class Person { val name = "LuXin" val age = 23 def sayHello() = println("Hello, Person!") } class Student extends Person{ override val name = "Ren" override def sayHello() = { super.sayHello() println("Hello, Student!") } } ``` #### 类型推断 有时候,我们设计的程序,要根据变量/子类的类型来执行对应的逻辑。如多个子类继承同一个父类,如何进行类型推断呢? * isInstanceOf * getClass/classOf ##### isInstanceOf, asInstanceOf * isInstanceOf: 判断对象是否为指定类的对象 * asInstanceOf:将对象转换为指定的类型 ```scala val tureOrFalse:Boolean = 对象.isInstanceOf[类型] val 变量 = 对象.asInstanceOf[类型] ``` 示例: ```scala val trueOrFalse:Boolean = p.isInstanceOf[Student] val s = p.asInstanceOf[Student] ``` ##### 多态 * 同一事物在不同时刻表现出来的不同形态,状态 * 父类引用不能直接使用子类的特有成员 ```scala val p:Person = new Student //多态 if(p.isInstanceOf[Student]){ val s = p.asInstanceOf[Student] } s.sayHello() ``` ##### getClass和classOf InstanceOf只能判断对象是否为指定类以及其子类的对象,而不能精确的判断出:对象就是指定类的对象。如果要求精确地判断出对象的类型就是指定的数据类型,那么就只能使用getClass和classOf来实现。 * p.getClass可以精确获取对象的类型 * classOf[类名]可以精确获取数据类型 * 使用==操作符可以直接比较类型 ```scala val p:Person = new Student p.getClass == classOf[Student] ``` #### 抽象类 scala通过abstract关键字实现抽象类。 如果类中有抽象字段或者抽象方法,那么该类就应该是一个**抽象类**。 > * 抽象字段:没有初始化的变量就是抽象字段 > * 抽象方法:没有方法体的方法就是一个抽象方法 ```scala abstract class 抽象类名 { val/var 抽象字段名:类型 def 方法名(参数:参数类型, 参数:参数类型,...):返回类型 } ``` > 抽象方法的var可以被子类中的override修饰 #### 匿名内部类 匿名内部类是继承了类的匿名的**子类对象**,它可以直接用来创建实例对象。 * 当对象方法(成员方法)仅调用一次的时候 * 可以作为方法的参数进行传递 ```scala new 父类名() { //重写类中所有的抽象内容 } ``` > 上述格式中,如果类的主构造器参数列表为空,则小括号可以省略不写 ### 特质(Trait) 有些时候,我们会遇到一些特定的需求,即:在不影响当前继承体系的情况下,对某些类(或者某些对象)的功能进行加强。例如:有猴子类和大象类,他们都有姓名,年龄以及吃的功能。但是部分猴子经过马戏团的训练后,学会了骑独轮车,那骑独轮车这个功能就不能定义到父类(动物类)或者猴子类中,而是应该定义到`特质`中,而Scala中的特质,要用关键字`trait`修饰。 * 特质可以提高代码的复用性 * 特质可以提高代码的扩展性和可维护性 * 类与特质之间是继承关系,只不过类与类之间只支持`单继承`,但是类与特质之间,`既可以单继承,也可以多继承` * Scala的特质中可以有普通字段,抽象字段,普通方法,抽象方法 > 1. 如果特质中只有抽象内容,这样的特质叫:接口 > 2. 如果特质中既有抽象内容,又有具体内容,这样的特质叫:富接口 定义特质: ```scala trait 特质名称 { //普通字段 //抽象字段 //普通方法 //抽象方法 } ``` 继承特质: ```scala class 类 extends 特质1 with 特质2 { // 重写抽象字段 // 重写抽象方法 } ``` 注意: * scala中不管是类还是特质,继承关系用的都是extends关键字 * 如果要继承多个特质(trait),则特质名之间使用`with`关键字隔开 ```scala object Main extends App{ val w = new MessageWorker w.send("Hello") w.receive() } trait MessageSender { def send(msg:String): Unit } trait MessageReceiver { def receive(): Unit } class MessageWorker extends MessageSender with MessageReceiver { override def send(msg:String): Unit = println("Send message:" + msg) override def receive(): Unit = println("Message received!") } ``` Object单例对象也可以继承trait ```scala object Main extends App{ MessageWorker.send("Hello") MessageWorker.receive() } trait MessageSender { def send(msg:String): Unit } trait MessageReceiver { def receive(): Unit } object MessageWorker extends MessageSender with MessageReceiver { override def send(msg:String): Unit = println("Send message:" + msg) override def receive(): Unit = println("Message received!") } ``` #### 对象混入trait 有些时候,我们希望在不改变类继承体系的情况下,对对象的功能进行临时增强或者扩展,这个时候就可以考虑使用`对象混入技术`了。所谓的`对象混入`指的就是:在scala中,类和特质之间无任何的继承关系,但是通过特定的关键字,却可以让该类对象具有指定特质中的成员。 ```scala val/var 对象名 = new 类 with 特质 ``` ```scala object Main extends App{ val p = new Person with MessageSender p.send("Hello") } trait MessageSender { def send(msg:String): Unit = println("Send message:" + msg) } class Person ``` #### 使用trait实现适配器模式 设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它并不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。设计模式一共有23中,分为如下的3类: 1. 创建型 指的是:需要创建对象的,常用的模式有:单例模式,工厂方法模式 2. 结构型 指的是:类、特质之间的关系结构,常用的模式有:适配器模式,装饰模式 3. 行为型 指的是:类(或者特质)能够做什么,常用的模式有:模板方法模式,职责链模式 ##### 适配器模式 当特质中有多个抽象方法,而我们只需要用到其中的某一个或者某几个方法时,不得不将该特质中的所有抽象方法给重写了,这样做很麻烦。针对这种情况,我们可以定义一个抽象类去继承该特质,重写特质中的所有的抽象方法,方法体为空。这时候,我们需要使用哪个方法,只需要定义类继承抽象类,重写指定方法即可。这个抽象类就叫:适配器类。这种设计模式(设计思想)就叫:适配器模式。 ```scala trait 特质A{ //抽象方法1 //抽象方法2 //抽象方法3 //... } abstract class 类B extends A{ //适配器类 //重写抽象方法1,方法体为空 //重写抽象方法2,方法体为空 //重写抽象方法3,方法体为空 //... } class 自定义类C extends 类B{ //需要使用哪个方法,重写哪个方法即可 } ``` #### 使用trait实现模板方法模式 在现实生活中,我们会遇到论文模板,简历模板等,而在面向对象设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。 例如,取银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。这就要用到模板方法设计模式了。 在scala中,我们可以先定义一个操作中的算法框架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重新定义该算法的某些特定步骤,这就是:模板方法设计模式 ```scala abstract class A { //父类,封装的是公共部分 def 方法名(参数列表) = { //具体方法,在这里也叫:模板方法 //步骤1,已知 //步骤2,未知,调用抽象方法 //步骤3,已知 //步骤n... } //抽象方法 } class B extends A{ //重写抽象方法 } ``` > 抽象方法的个数要根据具体的需求来定,不一定只有一个,也可以是多个 #### 使用trait实现职责链模式 多个trait中出现了同一个方法,且该方法最后都调用了super.该方法名(),当类继承了这多个trait后,就可以依次调用多个trait中的此同一个方法了,这就形成了一个调用链。 1. 按照从右往左的顺序依次执行 > 即首先会从最后边的trait方法开始执行,然后依次往左执行对应trait中的方法 2. 当所有特质的该方法执行完毕后,最后会执行父特质中的此方法 > 在scala中,一个类继承多个特质的情况叫`叠加特质` ```scala trait A { //父特质 def show() } trait B extends A { //子特质,根据需求可以定义多个 override def show() = { //具体的代码逻辑 super.show() } } trait C extends A { override def show() = { //具体的代码逻辑 super.show() } } class D extends B with C { //具体的类,用来演示:叠加特质 def 方法名() = { //这里可以是该类自己的方法,不一定非的是show()方法 //具体的代码逻辑 super.show() //这里就构成了:调用链 } } /* 执行顺序为: 1.先执行类D中的自己的方法 2.再执行特质C中的show()方法 3.再执行特质B中的show()方法 4.最后执行特质A中的show()方法 */ ``` > 特质A的show()方法只调用一次 #### trait的构造机制 如果遇到了一个类继承了某个父类且继承了多个父特质的情况,那该类(子类),该类的父类,以及该类的父特质之间是如何构造的呢? * 每个特质只有一个`无参数`的构造器 也就是说:trait也有构造代码,但和类不一样,特质不能有构造器参数 * 遇到一个类继承另一个类、以及多个trait的情况,当创建该类的实例时,它的构造器执行顺序如下: 1. 执行父类的构造器 2. 按照`从左到右`的顺序,依次执行trait的构造器 3. 如果trait有父trait,则父trait的构造器只初始化一次 4. 执行子类构造器 #### trait继承class 在Scala中,trait也可以继承class。trait会将class中的成员都继承下来。 ```scala class 类A { //类A //成员变量 //成员方法 } trait B extends A { //特质B } ``` ### 密封(sealed) sealed作用是限制继承关系,确保所有的子类都在**同一个文件**中定义,从而让编译器能够进行**模式匹配的完备性检查** #### 密封特质(sealed trait) ```scala sealed trait Animal case class Dog(name: String) extends Animal case class Cat(name: String) extends Animal def animalSound(animal: Animal): String = animal match { case Dog(_) => "Woof" case Cat(_) => "Meow" // 不需要默认case,编译器知道所有可能性 } ``` ### 样例类 在Scala中,样例类是一种特殊类,一般是用于**保存数据**的。 ```scala case class 样例类名([var/val] 成员变量名1:类型1, 成员变量名2:类型2, 成员变量名3:类型3){} ``` * 如果不写,则变量的默认修饰符是val,即:val是可以省略不写的 * 如果要实现某个成员变量值可以被修改,则需要手动添加var来修饰此变量 #### 样例类中的默认方法 当我们定义一个样例类后,编译器会自动帮助我们生成/重写一些方法,常用的如下: * apply()方法 * 可以让我们快速地使用类名来创建对象,省去了new这个关键字 * 例如:`val p = Person()` * toString()方法 * 可以让我们通过输出语句打印对象时,直接打印该对象的各个属性值 * 例如:`println(p)`打印的是对象p的各个属性值,而不是它的地址值 * equals()方法 * 可以让我们直接使用`==`来比较两个样例类对象的所有成员变量值是否相等 * 例如:`p1 == p2`比较的是两个对象p的各个属性值,而不是它的地址值 * `obj1.eq(obj2)`是用来比较地址值的 * hashCode()方法 * 用来获取对象的哈希值的。即:同一对象哈希值肯定相同,不同对象哈希值一般不同 * 哈希值是通过对象的各个属性做哈希运算得出的,不代表地址值 * 例如: ```scala val p1 = new Person("张三", 23) val p2 = new Person("张三", 23) println(p1.hashCode() == p2.hashCode()) //结果为true ``` * copy()方法 * 可以用来快速创建一个属性值相同的实例对象,还可以使用带名参数的形式给指定的成员变量赋值 * 例如: ```scala val p1 = new Person("张三", 23) val p2 = p1.copy(age=24) println(p1) println(p2) ``` * unapply()方法 * 一般用作`提取器` ### 样例对象 在scala中,**用case修饰的单例对象就叫:样例对象,而且它没有主构造器**。 1. 当做枚举值使用 > 枚举:就是一些固定值,用来统一项目规范的 2. 作为没有任何参数的消息传递 ```scala case object 样例对象名 ``` ```scala trait Sex case object Male extends Sex case object Female extends Sex case class Person(var name:String, var sex:Sex) {} object Main extends App{ val p = Person("张三", Male) println(p) } ``` ### 数组(地址连续) 数组就是用来存储多个同类型元素的容器,每个元素都有编号,且编号都是从0开始数的。Scala中,有两种数组,一种是**定长数组**,另一种是**变长数组**。 #### 定长数组 1. 数组的长度不允许改变 2. 数组的内容是可变的 格式一:通过指定长度定义数组 ```scala val/var 变量名 = new Array[元素类型](数组长度) ``` 格式二:通过指定元素定义数组 ```scala val/var 变量名 = Array(元素1, 元素2, 元素3, ...) ``` > 1. 在scala中,数组的泛型使用`[]`来指定 > 2. 使用`数组名(索引)`来获取数组中的元素(可读可写) > 3. 数组元素是有默认值的,Int:0,Double:0.0,String:null > 4. 通过`数组名.length`或者`数组名.size`来获取数组长度 #### 变长数组 数组的长度和内容都是可变的,可以往数组中添加、删除元素 * 创建变长数组,需要先导入ArrayBuffer类 ```scala import scala.collection.mutable.ArrayBuffer ``` * 格式一:创建空的ArrayBuffer变长数组 ```scala val/var 变量名 = ArrayBuffer[元素类型]() ``` * 格式二:创建带有初始元素的ArrayBuffer变长数组 ```scala val/var 变量名 = ArrayBuffer(元素1, 元素2, 元素3......) ``` ##### 增删改元素 * 使用`+=`添加单个元素 * 使用`-=`删除单个元素 * 使用`++=`追加一个数组到变长数组中 * 使用`--=`移除变长数组中的指定多个元素 ```scala import scala.collection.mutable.ArrayBuffer object Main extends App{ val arr1 = ArrayBuffer("hadoop", "spark", "flink") arr1 += "flume" arr1 -= "hadoop" arr1 ++= Array("hive", "sqoop") arr1 --= Array("sqoop", "spark") println(arr1) } ``` #### 遍历数组 > to:包左包右 > > until:包左不包右 ```scala object Main extends App{ val arr1 = Array(1, 2, 3, 4, 5) for(i <- 0 to arr1.length - 1) println(arr1(i)) println("-" * 15) for(i <- 0 until arr1.length) println(arr1(i)) println("-" * 15) for(i <- arr1) println(i) } ``` #### 数组常用方法 * sum()方法:求和 * max()方法:求最大值 * min()方法:求最小值 * sorted()方法:排序,返回一个新的数组 * reversed()方法:反转,返回一个新的数组 ```scala object Main extends App{ val arr1 = Array(2, 1, 3, 5, 4) println(s"sum: ${arr1.sum}") println(s"max: ${arr1.max}") println(s"min: ${arr1.min}") val arr2 = arr1.sorted val arr3 = arr1.reverse for(i <- arr1) println(i) println("-"*15) for(i <- arr2) println(i) println("-"*15) for(i <- arr3) println(i) } ``` ### 元组 元组一般用来存储多个不同类型的值。例如同时存储姓名,年龄,性别,出生年月这些数据,就要用到元组来存储了,并且**元组的长度和元素都是不可变的**。 格式一:通过小括号实现 ```scala val/var 元组 = (元素1, 元素2, 元素3, ......) ``` 格式二:通过箭头实现 ```scala val/var 元组 = 元素1->元素2 ``` > 注意:上述这种方式,只适用于元组中只有两个元素的情况 在scala中,可以通过`元组名._编号`的形式来访问元组中的元素,_1表示访问第一个元素,依次类推 也可以通过`元组名.productIterator`的方式,来获取该元组的迭代器,从而实现遍历元组 格式一:访问元组中的单个元素 ```scala println(元组名._1) //打印元组的第一个元素 println(元组名._2) //打印元组的第二个元素 ... ``` 格式二:遍历元组 ```scala val tuple1 = (值1, 值2, ...) val it = tuple1.productIterator for(i <- it) println(i) ``` ### 列表(链表存储) 每个元素可以是**不同类型**的值,特点:**有序,可重复**, 分为**不可变列表**和**可变列表** > 有序的意思是指元素的存入顺序和取出顺序是一致的 > > 可重复的意思是列表中可以添加重复元素 #### 不可变列表(默认) 列表的元素、长度都是不可变的 1. 通过小括号直接初始化 ```scala val/var 变量名 = List(元素1, 元素2, ...) ``` 2. 通过`Nil`创建一个空列表 ```scala val/var 变量名 = Nil ``` 3. 使用`::`方法实现 ```scala val/var 变量名 = 元素1 :: 元素2 :: Nil ``` > 使用::拼接方式来创建列表,必须在最后添加一个Nil #### 可变列表 列表的元素、长度都是可变的 先导入包: ```scala import scala.collection.mutable.ListBuffer ``` > 可变集合都在`mutable`包中,不可变集合都在`immutable`包中(默认导入) 1. 创建空的可变列表 ```scala val/var 变量名 = ListBuffer[数据类型]() ``` 2. 通过小括号直接初始化 ```scala val/var 变量名 = ListBuffer(元素1, 元素2, 元素3,...) ``` #### 可变列表的常用操作 | 格式 | 功能 | | ------------ | -------------------------------------------------------- | | 列表名(索引) | 根据索引(索引从0开始),获取列表中的指定元素 | | += | 往列表中添加单个元素 | | ++= | 往列表中追加一个列表 | | -= | 删除列表中的某个指定元素 | | --= | 以列表的形式,删除列表中的多个元素 | | toList | 将可变列表(ListBuffer)转换为不可变列表(List) | | toArray | 将可变列表(ListBuffer)转换为数组 | | isEmpty | 判断列表是否为空 | | ++ | 拼接两个列表,返回一个新的列表 | | head | 获取列表的首个元素 | | tail | 获取列表中除首个元素之外,其他所有元素 | | reverse | 对列表进行反转,返回一个新的列表 | | take | 获取列表中的前缀元素(具体个数可以自定义) | | drop | 获取列表中的后缀元素(具体个数可以自定义) | | flatten | 对列表进行扁平化操作,返回一个新的列表 | | zip | 对列表进行拉链操作,即:将两个列表合并成一个列表 | | unzip | 对列表进行拉开操作,即:将一个列表拆解成两个列表 | | toString | 将列表转换成其对应的默认字符串形式 | | mkString | 将列表转换成其对应的指定分隔符拼接起来字符串形式 | | union | 获取两个列表的并集元素(元素不去重),并返回一个新的列表 | | intersect | 获取两个列表的交集元素,并返回一个新的列表 | | diff | 获取两个列表的差集元素,并返回一个新的列表 | * list1.take(2):获取列表前2个元素 * list1.drop(3):前3个元素是前缀元素,获取除它们以外剩下的后缀元素 * list1.flatten:将嵌套列表中的所有具体元素单独的放到一个新列表中 * zip(拉链):将两个列表,组合成一个元素为元组的列表 > 解释:将列表List(“张三”,”李四”),List(23,24)组合列表List((“张三”, 23), (“李四”, 24)) > > 若列表长度不一致,以较小的为准,舍弃较长列表的后续元素 * unzip(拉开):将一个包含元组的列表,拆解成包含两个列表的元组 > 解释:将列表List((“张三”, 23), (“李四”, 24))拆解为元组(List(“张三”,”李四”),List(23,24)) * zipWithIndex:将列表的每个元素和对应的索引做zip > 解释:将列表List("a", "b", "c", "d").zipWithIndex得到List(("a", 0), ("b", 1), ("c", 2), ("d", 3)) ### 集合 Set(集合)代表没有重复元素的集合。特点是:**唯一、无序** Scala中的集合分为**不可变集合**和**可变集合** #### 不可变集合 不可变集合指的是**元素,集合长度不可变** 1. 创建一个空的不可变集合 ```scala val/var 变量名 = Set[类型]() ``` 2. 给定元素来创建一个不可变集合 ```scala val/var 变量名 = Set(元素1, 元素2, 元素3,...) ``` * 获取集合的大小(size) * 遍历集合(和遍历数组一致) * 添加一个元素,生成一个新的Set(+) * 拼接两个集合,生成一个新的Set(++) * 拼接集合和列表,生成一个新的Set(++) > 1. `-`表示删除一个元素,生成一个新的Set > 2. `--`表示批量删除某个集中的元素,从而生成一个新的Set #### 可变集合 可变集合指的是**元素,集合长度都可变**,他的创建方式和不可变集合创建方式一致,不过需要导入: ```scala import scala.collection.mutable.Set ``` ### 映射(Map) 映射指的就是Map。它是由键值对(key, value)组成的集合。特点是:键具有唯一性,但是值可以重复。在Scala中,Map也分为不可变Map和可变Map。 > 如果添加重复元素(即:两组元素的键相同),则会用`新值覆盖旧值` #### 不可变Map 不可变Map指的是**元素、长度都不可变** 1. 通过箭头的方式实现 ```scala val/var map = Map(键->值, 键->值, 键->值...) //推荐,可读性更好 ``` 2. 通过小括号的方式实现 ```scala val/var map = Map((键,值), (键,值), (键,值)...) ``` #### 可变Map 可变Map指的是**元素、长度都可变**,定义语法与不可变Map一致,只不过需要先手动导包 ```scala import scala.collection.mutable.Map ``` 可变Map初始化 ```scala val memory = Map.empty[BigInt, BigInt] ``` #### Map基本操作 1. `map(key)`:根据键获取其对应的值,键不存在返回None 2. `map.keys`:获取所有的键 3. `map.values`:获取所有的值 4. 遍历map集合:可以通过普通for实现 ```scala for((k,v) <- map1) println(k,v) ``` 5. getOrElse:根据键获取其对应的值,如果键不存在,则返回指定的默认值 6. `+`:增加键值对,并生成一个新的Map 7. `-`:根据键删除其对应的键值对元素,并生成一个新的Map ### 迭代器(iterator) scala针对每一类集合都提供了一个迭代器(iterator),用来迭代访问集合 1. 使用`iterator`方法可以从集合获取一个迭代器 > 迭代器中有两个方法: > > * hasNext方法:查询容器中是否有下一个元素 > * next方法:返回迭代器的下一个元素,如果没有,抛出NoSuchElementException 2. 每一个迭代器都是有状态的 > 即:迭代完保留在最后一个元素的位置,再次使用则抛出NoSuchElementException 3. 可以使用while或者for来逐个获取元素 ```scala object Main extends App{ val list1 = List(1, 2, 3, 4, 5) val it = list1.iterator while(it.hasNext) { println(it.next()) } } ``` ### 函数式编程 * 所谓的函数式编程指的就是`方法的参数列表`可以`接收函数对象` * 例如:add(10, 20)就不是函数式编程,而`add(函数对象)`这种格式就叫函数式编程 | 函数名 | 功能 | | -------- | ------------------------------ | | foreach | 用来遍历集合的 | | map | 用来对集合进行转换的 | | flatmap | 用来对结合进行映射扁平化操作 | | filter | 用来过滤出指定的元素 | | sorted | 用来对集合元素进行默认排序 | | sortBy | 用来对集合按照指定字段排序 | | sortWith | 用来对集合进行自定义排序 | | groupBy | 用来对集合元素按照指定条件分组 | | reduce | 用来对集合元素进行聚合计算 | | fold | 用来对集合元素进行折叠计算 | #### foreach原型 ```scala def foreach(f:(A) => Unit):Unit //简写模式 def foreach(函数) //函数格式 (函数的参数列表) => {函数体} ``` | foreach | API | 说明 | | ------- | ------------- | ---------------------------------------------------- | | 参数 | f:(A) => Unit | 接收一个函数对象,函数的参数为集合的元素,返回值为空 | | 返回值 | Unit | 表示foreach函数的返回值为:空 | 简化函数定义: 1. 通过**类型推断**简化函数定义 > 因为使用foreach来迭代列表,而列表中的每个元爱类型是确定的, 所以我们可以通过类型推断让scala程序来自动推断出集合中的每个元素参数的类型,即:在我们创建函数时,可以省略其参数列表的类型 2. 通过**下划线**简化函数定义 > 当函数参数,只在函数体中出现一次,而且函数体没有嵌套调用时,可以使用下划线来简化函数定义 ```scala object Main extends App{ val list1 = List(1, 2, 3, 4, 5) list1.foreach((x: Int) => {println(x)}) list1.foreach(x => println(x)) list1.foreach(println(_)) } ``` #### map原型 集合的映射操作是指`将一种数据类型转换为另一种数据类型`的过程。 > 例如:把List[Int]转换成List[String] ```scala def map[B](f:(A) => B): TraversableOnce[B] //简写形式 def map(函数对象) ``` | map方法 | API | 说明 | | ------- | ------------------ | ------------------------------------------------------------ | | 泛型 | [B] | 指定map方法最终返回的集合泛型,可省略不写 | | 参数 | f:(A) => B | 函数对象,参数列表为类型A(要转换的列表元素),返回值为类型B | | 返回值 | TraversableOnce[B] | B类型的集合,可省略不写 | ```scala object Main extends App{ val list1 = List(1, 2, 3, 4, 5) val list2 = list1.map((x: Int) => {"*" * x}) val list3 = list1.map(x => "*" * x) val list4 = list1.map("*" * _) } ``` #### flatMap原型 先map,然后在flatten > 1. map是将列表中的**元素转换为一个List** > 2. flatten再将整个列表进行扁平化 ```scala def flatMap[B](f:(A) => GenTraversableOnce[B]): TraversableOnce[B] //简写形式 def flatMap(f:(A) => 要将元素A转换成的集合B的列表) ``` | flatMap方法 | API | 说明 | | ----------- | ------------------------------ | ------------------------------------------------------------ | | 泛型 | [B] | 最终要返回的集合元素类型,可省略不写 | | 参数 | f:(A) => GenTraversableOnce[B] | 传入一个函数对象
函数的参数是集合的元素
函数的返回值是一个集合 | | 返回值 | TraversableOnce[B] | B类型的集合 | ```scala object Main extends App{ val list1 = List("hadoop hive spark flink flume", "kudu hbase sqoop storm") val list2 = list1.map((x:String) => {x.split(" ")}) val list3 = list2.flatten val list4 = list1.flatMap((x:String) => {x.split(" ")}) val list5 = list1.flatMap(x => x.split(" ")) val list6 = list1.flatMap(_.split(" ")) } ``` #### filter原型 过滤指的是过滤出(筛选出)符合一定条件的元素 ```scala def filter(f:(A) => Boolean):TraversableOnce[A] //简写形式 def filter(f:(A) => 筛选条件) ``` | filter方法 | API | 说明 | | ---------- | ------------------ | ------------------------------------------------------------ | | 参数 | f:(A) => Boolean | 传入一个函数对象
接收一个集合类型的参数
返回布尔类型,满足条件返回true,不满足返回false | | 返回值 | TraversableOnce[A] | 符合条件的元素列表 | ```scala object Main extends App{ val list1 = (1 to 10).toList val list2 = list1.filter(x => x % 2 == 0) val list3 = list1.filter(_ % 2 == 0) } ``` #### reduce原型 > * reduce和reduceLeft效果一致,表示从左到右计算 > * reduceRight表示从右到左计算 ```scala object Main extends App{ val list1 = (1 to 10).toList val list2 = list1.reduce((x, y) => x + y) val list3 = list1.reduce(_ + _) } ``` #### fold原型 fold和reduce很像,只不过多了一个指定初始值的参数 ### 模式匹配 * 判断固定值 * 类型查询 * 快速获取数据 #### 简单模式匹配 ```scala val/var result = 变量 match { case "常量1" => 表达式1 case "常量2" => 表达式2 case "常量3" => 表达式3 case _ => 表达式4 } ``` > 每个表达式后面自动有break #### 匹配类型 除了匹配数据之外,match表达式还可以进行类型匹配。如果我们要根据不同的数据类型,来执行不同的逻辑,也可以使用match表达式来实现。 ```scala 对象名 match { case 变量名1:类型 => 表达式1 case 变量名2:类型 => 表达式2 case 变量名3:类型 => 表达式3 ... case _ => 表达式4 } ``` > 如果case表达式中无需使用到匹配到的变量,可以使用下划线代替 ```scala object Main extends App{ val a:Any = "hadoop" val result = a match { case s:String => s.length case i:Int => i + 1 case _ => 0 } } ``` #### 守卫 在case语句中添加if条件判断 ```scala 变量 match { case 变量名 if条件1 => 表达式1 case 变量名 if条件2 => 表达式2 case 变量名 if条件3 => 表达式3 ... case _ => 表达式4 } ``` ```scala object Main extends App{ val a = 1 a match { case x if x >= 0 && x <= 3 => println("[0-3]") case x if x > 3 && x <= 6 => println("[4-6]") case _ => println("other") } } ``` #### 匹配样例类 ```scala 对象名 match { case 样例类型1(字段1, 字段2, 字段n) => 表达式1 case 样例类型2(字段1, 字段2, 字段n) => 表达式2 ... case _ => 表达式3 } ``` > 1. 样例类型后的小括号中,编写的字段个数要和该样例类的字段个数保持一致 > 2. 通过match进行模式匹配的时候,要匹配的对象必须声明为:Any类型 ```scala case class Person(name: String, age: Int) case class Order(id:Int) object Main extends App{ val c:Any = Person("John", 25) c match { case Person(name, age) => println(s"Person: $name, Age: $age") case Order(id) => println(s"Order: $id") case _ => println("Unknown type") } } ``` #### 匹配数组、列表、元组 ```scala //在match块中: //数组 Array(1,x,y) //以1开头,后续的两个元素不固定 Array(0) //只匹配一个0元素的数组 Array(0, _*) //可以任意数值,但是以0开头 //列表 List(1,x,y) //以1开头,后续的两个元素不固定 List(0) //只匹配一个0元素的数组 List(0, _*) //可以任意数值,但是以0开头 //元组 (1,x,y) //以1开头的,一共三个元素的元组 (x,y,5) //一共有三个元素,最后一个元素为5的元组 ``` #### 匹配变量 ```scala val arr1 = (0 to 10).toArray val Array(_,x,y,z,_*) = arr1 //x,y,z=1,2,3 ``` #### 匹配for表达式 ```scala //匹配年龄为23的人 object Main extends App{ val map1 = Map("John" -> 23, "Tom" -> 23, "Bob" -> 25) for((k, v) <- map1 if v == 23) { println(s"$k is $v years old") } for((k, 23) <- map1) { println(s"$k is 23 years old") } } ``` ### Option类型 实际开发中,在返回一些数据时,难免会遇到空指针异常(NullPointerException),遇到一次就处理一次相对来说还是很繁琐的。在Scala中,我们返回某些数据时,可以返回一个Option类型的对象来封装具体的数据,从而实现有效的避免空指针异常。 Option类型表示可选值,有两种形式: * Some(x):x表示实际的值 * None:表示没有值 > 可以使用getOrElse方法,当值为None时可以指定一个默认值 ```scala object Main extends App{ def divide(x: Int, y: Int): Option[Int] = { if (y == 0) None else Some(x / y) } val result = divide(10, 0) match { case Some(value) => println(s"Result: $value") case None => println("Division by zero!") } //或 println(result.getOrElse("Division by zero!")) } ``` ### 偏函数 偏函数是指**被包在花括号内的一组case语句**。偏函数是PartialFunction[A, B]类型的一个实例对象,其中A代表输入参数类型,B代表返回结果类型。 ```scala val 对象名 = { case 值1 => 表达式1 case 值2 => 表达式2 ... case _ => 表达式n } ``` ```scala object Main extends App{ val pf: PartialFunction[Int, String] = { case 1 => "one" case 2 => "two" case _ => "other" } println(pf(1)) // prints "one" println(pf(2)) // prints "two" } ``` #### 偏函数结合map使用 ```scala object Main extends App{ val list1 = (1 to 10).toList val list2 = list1.map { case x if x >= 1 && x <= 3 => "[1-3]" case x if x >= 4 && x <= 8 => "[4-8]" case _ => "(8-*)" } println(list2) } //List([1-3], [1-3], [1-3], [4-8], [4-8], [4-8], [4-8], [4-8], (8-*), (8-*)) ``` ### 异常处理 #### 捕获异常 ```scala try { //可能会出现的问题的代码 } catch { case ex:异常类型1 => //代码 case ex:异常类型2 => //代码 } finally { //代码 } ``` > 1. try中的代码是我们编写的业务处理代码 > 2. 在catch中表示出现某个异常时,需要执行的代码 > 3. 在finally中,写的是不管是否出现异常都会执行的代码 #### 抛出异常 ```scala throw new Exception("这里写异常的描述信息") ``` ### 提取器 一个类要想支持模式匹配(类似实例类),则必须实现一个提取器,其实就是unapply方法。提取器就是将一个对象的各个属性提取出来。 要实现一个提取器,只需要在该类的伴生对象中实现一个unapply方法即可。 ```scala class Student(val name:String, val age:Int) object Student{ def apply(name:String, age:Int) = new Student(name, age) def unapply(s:Student) = { if(s == null) None else Some(s.name, s.age) } } object Main extends App{ val c:Any = Student("Luxin", 23) c match { case Student(name, age) => println(s"Student:${name}, ${age}") case _ => println("None") } } ``` ### 高阶函数 如果一个函数的参数列表可以接受函数对象,那么这个函数就被称之为高阶函数。 #### 作为值的函数 ```scala object Main extends App{ val list1 = (1 to 10).toList val func = (x:Int) => "*" * x val list2 = list1.map(func) println(list2) } //List(*, **, ***, ****, *****, ******, *******, ********, *********, **********) ``` #### 匿名函数 ```scala object Main extends App{ val list1 = (1 to 10).toList val list2 = list1.map("*" * _) println(list2) } //List(*, **, ***, ****, *****, ******, *******, ********, *********, **********) ``` ### 柯里化方法 柯里化是指**将原先接受多个参数的方法转换为只有一个参数的参数列表的过程** ```scala //定义 def func(x:Int)(y:Int) = x + y //调用 func(1)(2) ``` ### 闭包 闭包指的是**可以访问不在当前作用域范围数据的一个函数** ```scala val y = 10 val add = (x:Int) => { x + y } println(add(5)) //结果15 ``` > 柯里化就是一个闭包 ### 控制抽象 假设函数A的参数列表需要接受一个函数B,且函数B没有输入值也没有返回值,那么函数A就被称之为`控制抽象`函数 ```scala val 函数A = (函数B:() => Unit) => { //代码1 //代码2 //... 函数B() } ``` ```scala object Main extends App{ //定义 val myShop = (f1: () => Unit) => { println("Welcome in!") f1() println("Thanks For Coming!") } //调用 myShop( () => { println("I want to buy a zenbook!") } ) } ``` ### 隐式转换 **以implicit关键字声明的带有单个参数的方法**。该方法是**被自动调用的**,用来实现自动将某种类型的数据转换为另一中类型的数据。 #### 手动导入隐式转换 1. 在`object单例对象`中定义隐式转换方法 > 隐式转换方法解释:就是用implicit关键字修饰的方法 2. 在需要用到隐式转换的地方,引入隐式转换 > 类似与`导包`,通过`import关键字实现` 3. 当需要用到`隐式转换方法`时,程序会自动调用 > 自动调用隐式转换方法的时机: > > 1. 当对象调用类中不存在的方法或者成员时,编译器会自动对该对象进行隐式转换 > 2. 当方法中的参数类型与目标类型不一致时,编译器也会自动调用隐式转换 示例: > File类没有read方法,需要升级成RichFile ```scala import java.io.File import scala.io.Source class RichFile(file:File) { def read() = Source.fromFile(file).mkString } object ImplicitDemo { implicit def file2RichFile(file:File) = new RichFile(file) } object Main extends App{ import ImplicitDemo.file2RichFile val file = new File("./1.txt") println(file.read()) } ``` > 执行流程: > > 1. 先找File类有没有read()方法,有就用 > 2. 没有就去查看有没有该类型的隐式转换,将该对象转成其他类型的对象 > 3. 如果没有隐式转换,直接报错 > 4. 如果可以将该类型的对象升级为其他类型的对象,则查看升级后的对象中有没有指定的方法,有,不报错,没有,就报错 #### 自动导入隐式转换 如果在当前作用域中有隐式转换方法,会自动导入隐式转换。 ```scala import java.io.File import scala.io.Source class RichFile(file:File) { def read() = Source.fromFile(file).mkString } object Main extends App{ implicit def file2RichFile(file:File) = new RichFile(file) val file = new File("./1.txt") println(file.read()) } ``` ### 隐式参数 在scala的方法中,可以带有一个**标记为implicit的参数列表**。调用该方法时,此参数列表可以不用给初始化值,因为**编译器会自动查找缺省值,提供给该方法**。 1. 在方法后面添加一个参数列表,参数使用implicit修饰 2. 在object中定义implicit修饰的隐式值 3. 调用方法,可以不传入implicit修饰的参数列表,编译器会自动查找缺省值 > 1. 和隐式转换一样,可以使用import手动导入隐式参数 > 2. 如果在当前作用域定义了隐式值,会自动导入 手动导入: ```scala object ImplicitParam { implicit val delimit_default = ("<<<", ">>>") } object Main extends App{ def show(name:String)(implicit delimit:(String, String)) = delimit._1 + name + delimit._2 import ImplicitParam.delimit_default println(show("LuXin")) println(show("John")("(((", ")))")) } ``` 自动导入: ```scala object Main extends App{ def show(name:String)(implicit delimit:(String, String)) = delimit._1 + name + delimit._2 implicit val delimit_default = ("<<<", ">>>") println(show("LuXin")) println(show("John")("(((", ")))")) } ``` ### 泛型 泛型的意思是**泛指某种具体的数据类型**,在scala中,泛型用**[数据类型]**表示。 #### 泛型方法 ```scala def 方法名[泛型名称](...) = { //... } ``` ```scala object Main extends App{ def getMiddleElement[T](arr:Array[T]) = arr(arr.length / 2) println(getMiddleElement(Array(1,2,3,4,5))) println(getMiddleElement(Array("a", "b", "c","d", "e"))) } ``` #### 泛型类 ```scala class 类[T](val 变量名:T) ``` ```scala class Pair[T](var a:T, var b:T) object Main extends App{ val p1 = new Pair[Int](10, 20) println(p1.a, p1.b) } ``` #### 泛型特质 ```scala trait 特质A[T]{ //特质中的成员 } class 类B extends 特质A[指定具体的数据类型] { //类中的成员 } ``` ```scala trait Logger[T] { val a:T def show(b:T):Unit } object ConsoleLogger extends Logger[String] { override val a: String = "Luxin" override def show(b: String): Unit = println(b) } object Main extends App{ ConsoleLogger.show("hhhh") } ``` #### 泛型上界 ```scala [T <: 类型] ``` > 例如:[T <: Person]的意思是,泛型T的数据类型必须是Person类型或者Person的子类型 #### 泛型下界 ```scala [T >: 类型] ``` > 例如:[T >: Person]的意思是,泛型T的数据类型必须是Person类型或者Person的父类 #### 泛型上下界 ```scala [T >: 类型1 <: 类型2] ``` > 下界写在前面,上界写在后面 ### 迭代器(Iterator) Iterable继承了Traversable特质,同时也是其他集合的父特质。最重要的是,它定义了获取迭代器(Iterator)的方法:`def iterator: Iterator[A]`,这是一个抽象方法,它的具体实现类需要实现这个方法,从而实现迭代放回集合中的元素。 Traversable两种遍历数据的方式: * 通过iterator()方法实现,迭代访问元素的功能 > 这种方式属于`主动迭代`,我们可以通过hasNext()检查是否还有元素,并且可以主动的调用next()方法获取元素,即:我们可以自主的控制迭代过程 * 通过foreach()方法实现,遍历元素的功能 > 这种方式属于`被动迭代`,我们只提供一个函数,并不能控制遍历的过程,即:迭代过程是由集合本身控制的 ```scala object Main extends App{ val list = (1 to 10).toList val it: Iterator[Int] = list.iterator while(it.hasNext){ val num = it.next() println(num) } list.foreach(println(_)) } ``` ### Seq特质 Seq特质代表`按照一定顺序排列的元素序列`,序列是一种特别的可迭代集合,它的元素特点是`有序(元素存取顺序一致),可重复,有索引` IndexedSeq和LinearSeq是Seq的子特质,代表着序列的两大子门派。 1. `IndexedSeq`特质代表索引序列,相对于Seq来说,它并没有增加额外的方法,对于随机访问元素的方法来讲,它更加有效,常用的子集合有:NumericRange, Range, Vector, String等 * Range集合 > Range代表一个有序的整数队列,每个元素之间的间隔相同。 * NumericRange集合 > NumericRange集合是一个更通用的等差队列 * Vector集合 > Vector是一个通用的不可变的数据结构,相对来讲,它获取数据的时间会稍长一些,但是随机更新数据于要比数组和链表快很 2. `LinearSeq`特质代表线性序列,它通过链表的方式实现,因此它的head, tail, isEmpty执行起来相对更加高效,常用的子集合有:List,Stack,Queue等 * Stack:表示`栈`数据结构,元素特点是`先进后出` * Queue:表示`队列`数据结构,元素特点是`先进先出`