Scala的编程基础大全

本文深入探讨Scala编程语言的各类核心概念,包括数据类型处理、函数式编程、面向对象编程、集合操作、异常处理等,旨在为开发者提供全面的Scala编程指南。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.格式化
Double类型数值格式化保留小数并转换为字符串
var d: Double = 2.3423
var f: String = d.formatted("%.2f")
println(f)

2.循环
循环守卫
for (i <- 1 to 10 if i % 2 == 0) println(i)
break使用
import util.control.Breaks._ → break需要的导包

object scalaTest {

def main(args: Array[String]): Unit = {
breakable( → 捕获break()抛出的异常,保证程序的正常运行
for (i <- 1 to 10) {
if (i > 8) {
break() → 抛出异常,达到终止循环的效果
}
println(i)
}
)
}
}
倒序(逆序)循环输出
for (i <- 1 to 10 reverse) println(i)
// 1 to 10 倒序后转为数组
var arr = (1 to 10).reverse
println(arr)
双层循环
for (i <- 1 to 10; k <- 1 to 10 reverse) printf("%d + %d = %d\n", i, k, i + k)
循环返回集合(可对每个元素进行运算)并可以对集合进行数组转换
var y = for(i <- 1 to 20) yield i + 1
var arr = y.toArray
循环生成随机数
val r = new Random
for (i <- 1 to 8) {
number = number + r.nextInt(10).toString → 生成随机数,0至9,不包含10
}
循环遍历数组
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
var arr = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)
for (num <- arr) println(num) → 直接遍历数组元素
for (num <- 0 until arr.length) println(arr(num)) → 通过下标变量数组元素
}
}

3.函数注意事项及细节讨论
1、函数的形参列表可以有多个,如果函数没有形参,调用时可以不带()
2、形参列表返回值类型可以是值类型也可以是引用类型
3、函数可以根据函数体的最后一行进行返回值的类型推断,return省略
4、在省略return的场合中,返回值类型也可以省略
5、如果函数明确的使用了return关键字,那面函数返回就能使用自动类型推断,必须明确表明返回值的类型“:返回值类型 = ”,如果返回值处什么都不写,即便有return关键字,也表示返回()
6、如果函数明确声明无返回值类型(声明类型为Unit或不书写),那么函数体中即使使用return关键字也没有返回值
7、如果不确定返回值类型可以使用返回值类型推断或将返回值类型写为Any
8、Scala语法中任何的语法结构都可以嵌套其他的语法结构(如:函数中再声明函数,类中再声明类,方法中再声明方法) - 通过反编译软件可以看到底层代码中是将嵌套的函数排列为同级的函数,只是改为了private final,如果两个函数名称相同则使用$1、$2区分
9、函数的形参在声明的时候,可以直接初始化赋值(默认值),如果调用时函数时没有传参指定实参,则使用默认值,如果进行传参指定了实参,则使用传参的值

sayOk() → 不传参调用,返回默认值:name = jack
sayOk(“mary”) → 不传参调用,返回默认值:name = mary

def sayOk(name:String = “jack”){println (s"name = ${name}")} → 声明初始化默认值
10、如果函数中存在多个参数,每一个参数都可以设定默认值,那么传递参数的时候遵循从左向右覆盖(注意:类型必须匹配、没有默认值的参数必须进行传参),同时也可以采用带名参数进行传参替换默认值
mySql(“127.0.0.1”, 2222) → 从左向右的覆盖原有默认值,但必须保证参数类型相同
mySql(“123123”, “123123”) → 报错!!!参数类型不匹配
mySql(user = “123123”, passWord = “123123”) → 带名参数:指定参数名称进行传参(覆盖默认值)

def mySql(add: String = “localhost”, port: Int = 3306, user: String = “root”, passWord: String = “root”): Unit = {
println(add)
println(port)
println(user)
println(passWord)
}
11、函数中的形参默认为val的,不能对函数中的形参进行修改
12、递归函数在未执行之前是无法推断出来结果类型,在使用递归时,必须在声明函数的同时明确返回值类型,不能使用类型推断
13、Scala函数支持可变参数,但需要注意以下两点:
⑴可变参数所对应的名称(如例中agrs)是一个集合,可以通过for循环进行遍历或操作
⑵可变参数需要写在形参列表的最后
def agr(n: Int, agrs: Int*): Int = { → 可变参数需要表明参数类型,并在形参列表最后(形参中也可以只有一个可变参数)
var sum = n
for (i <- agrs) sum += i
sum
}
println(“计算总和=” + agr(1, 33, 42, 55, 23, 11, 9))

4.惰性函数
惰性计算(尽可能延迟表达式延迟求值)是许多函数式编程语言的特点。惰性集合在需要时提供其元素,无需预先计算
⑴lazy不能修饰var类型的变量,只能修饰val类型的变量
⑵函数和变量都可以使用lazy进行修饰,修饰后的变量和函数只有在使用时才进行分配

lazy val a = abc → lazy只能修饰val类型的变量
println("--------------------") → 先输出
println(a) → 使用a变量时,才调用abc函数
def abc = println(“hello,word”)

5.异常(快捷键 Ctrl + Alt + T)
1、在Scala中只能有一个catch,通过catch对异常进行处理,保证程序不会中断,如果不处理异常,程序会因为异常而中断执行
2、catch中可以有多个case,每个case匹配一个异常(异常可以无序按从小到大的顺序排列)
3、=>关键符号,表示后面是对异常处理的代码块
4、不管是否捕捉到异常,finally中的代码块始终执行(一般用于关闭资源、流,对对象的清理工作)
5、Scala代码中的异常工作机制和Java一样,但是Scala没有“Checked(编译期)”异常,所有的异常都是在运行时进行捕获处理的
try {
var a = 1 / 0
} catch {
case ex: Exception => println(“存在异常”)
} finally {
println(“hello,word”)
}
6、使用throw关键字,抛出一个异常对象。所有的异常都是Throwable的子类型。throw表达式是有类型的,就是Nothing,因为Nothing是所有类型的子类型,所有throw表达式可以用在所有需要类型的地方
try {
test()
} catch {
case ex: Exception => println(ex.getMessage) → 展示异常信息
}
println(“hello,word”)

def test(): Nothing = throw new Exception(“存在异常”) → throw自定义异常,返回值为Nothing
6.foreach循环遍历
var arr = (1 to 10).reverse.toArray
arr.foreach(println)

输出一个字符串每个字符的char值(对应ASC码值)乘积
var a: Long = 1L
“Hello”.foreach(a *= _.toLong) → _表示遍历出来的元素,因为只出现一次,所有可以使用_替代
println(a)

7.迭代
通过迭代算出一个字符串的每个字符的char值(对应ASC码值)乘积
def s(str: String): Long = {
if (str.length <= 1) str.charAt(0).toLong
else s(str.drop(1)) * str.charAt(0).toLong → drop方法是将第1个字符删除后返回剩余的字符串
}

var a = s(“Hello”)
println(a)

8.scala代码的反编码操作
1)linux反编码操作
创建一个scala文件

写入一个student类并声明不同的属性

编译scala文件为class文件

编译后两个文件(class文件和scala文件)

通过Javap反编译class文件

9.主构造器和辅构造器细节
1、构造器是完成对新对象的初始化,没有返回值
2、主构造器声明直接放置在类名之后
3、主构造器会执行类定义中的所有语句(除去函数之外的语句都会被执行),通过反编译查看代码,可以看出,主构造器是将除去所有函数之外的代码包裹在构造方法中并执行
4、如果主构造器是无参的,可以在声明类的时候省略小括号,同时在声明对象的时候也可以省略小括号
5、辅助构造器名称为this,多个辅助构造器之间通过形参列表中参数的个数及类型进行区分(在底层就是Java方法重载的实现)
6、在调用辅助构造器时,辅助构造器内第一行代码必须能够直接或间接的调用到主构造器
7、如果想让主构造器私有,可以在类名和()中间写上private,这样只能通过调用辅助构造器创建对象(辅助构造器默认为public)
8、辅助构造器声明不能与主构造器声明一致(形参列表),通过反编译底层可以看出主构造器和辅助构造器为同级的构造方法,如果形参列表不相同及形成方法重载,如果一致则会报错

通过重载辅助构造器赋值并调用无参的主构造器

class A() {
var name: String = _
var age: Int = _

def this(name: String) { → 辅助构造器一
this() → 调用主构造器
this.name = name
}

def this(name: String, age: Int) { → 辅助构造器二
this() → 调用主构造器
this.name = name
this.age = age
}

def this(age: Int) { → 辅助构造器三
this(“jack”) → 调用辅助构造器一,同时通过辅助构造器一中的this()调用到主构造器
this.age = age
}
}

object scalaTest {

def main(args: Array[String]): Unit = {
val a = new A(18)
println(“name=” + a.name + “\t” + “age=” + a.age)
}
}

有参主构造器
class A(inName: String, inAge: Int) { → 主构造参数名称不能与类属性名称相同
var name: String = inName → 通过主构造参数给属性赋值
var age: Int = inAge

def this() { → 无参辅助构造器,调用主构造器赋值
this(“jack”,18)
}
}

object scalaTest {

def main(args: Array[String]): Unit = {
val a = new A(“jack”,20)
println(“name=” + a.name + “\t” + “age=” + a.age)
}
}
9、主构造器中的形参如果没有使用任何修饰符修饰,那么这个参数就只是主构造器的局部变量
如果参数使用val 修饰,那么这个参数将被视为类的私有只读参数(反编译底层代码可以看出该参数为类的全局变量,private修饰,同时提供读取方法,没有修改方法)
如果参数使用var修饰,那么这个参数将被视为类的私有可读可写的参数(反编译底层代码可以看出该参数为类的全局变量,private修饰,既提供了读取方法,又提供了修改方法)
class A1(inName: String) { → 无修饰符修饰
var name: String = inName
}

class A2(val inName: String) { → val修饰
var name: String = inName
}

class A3(var inName: String) { → var修饰
var name: String = inName
}

object scalaTest {

def main(args: Array[String]): Unit = {
val a1 = new A1(“jack”)
println(a1.name)

val a2 = new A2("jack")
println(a2.name)
println(a2.inName)

val a3 = new A3("jack")
println(a3.name)
println(a3.inName)
a3.inName = "tom"

}
}
10、通过对类属性添加注解@BeanProperty,可以增加该属性的set和get方法
import scala.beans.BeanProperty → 需要导包

class A {
@BeanProperty var name: String = _ → 对成员变量添加注解,如果属性修饰为val,添加注解后没有set方法
}

object scalaTest {

def main(args: Array[String]): Unit = {
val a = new A
a.setName(“jack”)
println(a.getName)
}
}

10.对象创建流程分析
1、加载类的信息(属性信息、方法信息)
2、在内存(堆)中开辟一个空间
3、使用父类的构造器(主和辅助)进行初始化
4、使用主构造器对属性进行初始化
5、使用辅助构造器对属性进行初始化
6、将开辟的对象地址赋给类对象的引用

11.apply方法使用
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val s1 = new Stu(1) → 通过new创建class类的对象
val s2 = Stu(1) → 不通过new直接创建class类伴生对象的对象(使用apply方法完成,支持方法重载)
val s3 = Stu(1, “s”) → 不通过new直接创建class类伴生对象的对象
}
}

class Stu(var a: Int) { → class类,包含主构造器
var b: String = _
println(“调用class类”)

def this(a: Int, b: String) { → 辅助构造器(开头固定格式:def this)
this(a) → 辅助构造器第一行必须调用通过this调用主构造器
this.b = b → 给主构造器中没有的成员变量赋值
}
}

object Stu { → class类的半生对象(与class类同名)
def apply(a: Int): Stu = { → apply方法(固定格式:def apply),支持方法重载
println(“调用object1类对象”)
new Stu(a) → 通过匿名对象完成class类的对象创建,并返还创建好的对象
}

def apply(a: Int, b: String): Stu = { → 方法重载,完成不同的形参创建对象需求
println(“调用object2类对象”)
new Stu(a, b)
}
}

12.app使用
package Exercise_01

object Excrcise_0218_01 extends App {
println(“hello,word”)
}

object类通过继承App,可以不适用main方法直接执行类中的代码
但存在一个致命的缺点:无法通过系统参数从外部进行传参(既main方法的参数可用)

13.包的特点
Scala自动引用的三个包:java.lang./scala包/Predef包
Java自动引用的一个包:java.lang.

2)基本语法
package 包名
3)包的作用
1、区分相同名字的类
2、当类很多的时候可以很好的管理类
3、控制访问范围
4、可以对类的功能进行扩展
4)源码文件和编译文件的存储
Scala中包名和源码所在的系统文件目录路径可以不同,但编译后的字节码文件路径和包名会保持一致(这个工作由编译器完成)

5)作用域原则
子包访问父包:
可以直接向上访问(即Scala中的子包可以直接访问父包中的内容,大括号体现了作用域的范围),Java中如果需要访问父包内容需要import。如果父包内容和子包类重名,默认采用就近原则,如果希望使用父包或其他指定的包,可以使用包全名指定
父包访问子包:

父包访问子包需要import对应的类

14.包对象
包可以包含类、对象和特制trait,但不能包含函数、方法和属性的定义。这是Java虚拟机的局限性。为了弥补这一点的不足,Scala提供了包对象的概念来解决这个问题
1、每一个包都可以有一个包对象(只能有一个),而且需要在父包中定义
2、包对象的名称必须和包子包名称一样
3、在包对象中可以定义变量、函数、方法
4、包对象中的变量、函数可以在对应的包中使用
5、通过反编译可以看出底层生成两个类:package.class和package$.class

package a.b { → 定义父类包
package c { → 定义子类包
object Test {
def main(args: Array[String]): Unit = {
println(name) → 在子类包中可以直接定义main方法并直接使用包对象内的属性或函数
sayHello
}
}
}

package object c { → 定义子类的包对象,名称与包名相同,添加object修饰,与子类包同级
var name: String = “jack” → 在包对象内定义属性或函数
def sayHello = println(“hello,word”)
}
}

反编译源码 – package.class:
package a.b.c;

import scala.reflect.ScalaSignature;

@ScalaSignature(bytes="\006\0019:Q!\001\002\t\002%\tq\001]1dW\006<WM\003\002\004\t\005\t1M\003\002\006\r\005\t!MC\001\b\003\005\t7\001\001\t\003\025-i\021A\001\004\006\031\tA\t!\004\002\ba\006\0347.Y4f’\tYa\002\005\002\020%5\t\001CC\001\022\003\025\0318-\0317b\023\t\031\002C\001\004B]f\024VM\032\005\006±!\tAF\001\007y%t\027\016\036 \025\003%Aq\001G\006A\002\023\005\021KaTeX parse error: Expected 'EOF', got '\0' at position 1: \̲0̲01\003oC6,W#\00…WMZ\005\003?\001\022aa\025;sS:<'BA\017\021\021\035\0213\0021A\005\002\r\n\001B\1nK~#S-\035\013\003I\035\002"aD\023\n\005\031\002"\001B+oSRDq\001K\021\002\002\003\007!KaTeX parse error: Expected 'EOF', got '\0' at position 2: A\̲0̲02yIEBaAK\006!B….sayHello();
}

public static void name_KaTeX parse error: Expected '}', got 'EOF' at end of input: …package..MODULE.name_$eq(paramString);
}

public static String name()
{
return package…MODULE$.name();
}
}

反编译源码 – package$.class:

package a.b.c;

import scala.Predef.;

public final class package$
{
public static final MODULE$;
private String name;

static
{
new ();
}

public String name()
{
return this.name; }
public void name_$eq(String x$1) { this.name = xKaTeX parse error: Expected 'EOF', got '}' at position 4: 1; }̲ public void….println(“hello,word”); }
private packageKaTeX parse error: Expected '}', got 'EOF' at end of input: () { MODULE = this;

this.name = "jack";

}
}
15.访问修饰符
对比Java访问修饰符权限
访问级别 访问控制修饰符 同类 同包不同类(不含子类) 同包子类 不同包不同类(不含子类) 不同包子类
公开 public √ √ √ √ √
受保护 protected √ √ √ – √(注意)
默认 没有访问控制修饰符 √ √ √ – --
私有 private √ — --- – --

1、当属性访问权限为默认时,从底层看属性是private修饰,但同时生成了XXX_$eq()(类似setter)和XXX()(类似getter方法),因此从使用效果来看是任何地方都能访问,效果与public等同
2、当方法访问权限为默认时,默认为public访问修饰权限
3、private为私有权限,只能在类的内部和伴生对象中可用
4、protected为受保护权限,Scala中受保护权限比Java中更严格,只能子类访问,无法同包访问
5、在Scala中没有public关键字,即不能使用public显式的修饰方法或属性
6、Scala中新增了包访问权限(代码效果 private[包名] var a = “jack”),在修饰符private后添加了[包名],表示在该包名或其子类都可以访问该变量,其他包下仍无法访问(其他访问修饰同理)

16.包的引入

17.封装的注意事项及细节
1、Scala为了简化代码的开发,当声明属性时,本身提供相应的setter/getter的方法。如果属性声明为private,那么生成的setter/getter方法也是private。如果属性省略访问权限修饰符,那么自动生成的setter/getter方法是public
2、如果只是对属性进行简单的get和set,不用专门的写getter/setter方法(属性修饰符为默认public),访问时直接使用对象名.属性名,这样也可以保持访问的一致性
3、从形式上看是可以直接访问属性,但其实是通过底层的类似get的方法访问
4、有了上面的特性,目前许多新的框架,在进行反射时,也支持对属性的直接反射

18.继承及类型判断、类型转换
package Exercise_01

object Exerciser_0127 {
def main(args: Array[String]): Unit = {
val s = new S
val e = new E
val p = new P
test(s)
test(e)
test§
}

def test(p: P) = { → 函数传传参父类对象(因为父类型对象包含子类型对象,所有也可以传参子类型对象)
if (p.isInstanceOf[S]) { → 判断对象是否为第一个子类型对象
val s = p.asInstanceOf[S] → 将父类型对象转换为子类型对象,用s变量接收子类型对象
s.printMessage() → 调用子类重写父类方法
s.showSID() → 调用子类特有方法
} else if (p.isInstanceOf[E]) {
var e = p.asInstanceOf[E]
e.printMessage()
e.showEID()
} else println(“类型转换失败”)
}
}

class P { → 父类
def printMessage() = println(“类型:P”)
def sayHello() = println(“hello”)
}

class S extends P { → 子类一
val SID = 100
override def printMessage() = println(“类型:S”) → 重写父类方法,关键字override
def showSID() = println(“学生ID:” + SID)
}

class E extends P { → 子类二
var EID = 200
override def printMessage() = println(“类型:E”)
def showEID() = println(“人员ID:” + EID)
}

19.超类(父类主构造器分析)
package Exercise_01

object Exerciser_0127 {
def main(args: Array[String]): Unit = {
val e = new E(“jack”,21,“男”) → 创建子类对象、调用辅助构造器,并给辅助构造器赋值
}
}

class P(inName: String) { → 父类
private val name: String = inName
println("父类主构造:P.name = " + name)
}

class E(inName: String, inAge: Int) extends P(inName) { → 通过子类主构造器给父类主构造传参
private var name = inName 注:Scala中不能通过super调用父类主构造器传参
private var age = inAge 也不可以通过子类的辅助构造器给父类的主构造器传参
private var sex: String = _
println("子类主构造:E.name = " + name + ",E.age = " + age)

def this(name: String, age: Int, sex: String) { → 辅助构造器
this(name,age) → 调用主构造器并传参
this.sex = sex
println("子类辅助构造:E.name = " + name + ",E.age = " + age + ",E.sex = " + sex)
}
}

输出结果及其顺序:
父类主构造:P.name = jack
子类主构造:E.name = jack,E.age = 21
子类辅助构造:E.name = jack,E.age = 21,E.sex = 男

20.抽象类
1、抽象类不能被实例化(默认情况下,抽象类是不可以实例化。但如果在实例化的同时,动态的实现了抽象类中的所有抽象方法,也可以进行实例化)
package Exercise_01

object Exerciser_0127 {
def main(args: Array[String]): Unit = {
val a = new A01 { → 实例化抽象类
override def sayHello(): Unit = println(“Hello”) → 实例化抽象类的同时,动态的实现抽象类中的抽象方法
}
a.sayHello() → 调用实例化后对象所对应的方法
}
}

abstract class A01{ → 抽象类
def sayHello() → 抽象方法
}
2、抽象类不一定包含abstract方法(抽象类可以没有abstract方法)
3、一旦包含了抽象方法或抽象属性(抽象属性就是没有初始化的属性),这个类必须用abstract进行修饰
4、抽象方法不能有方法体部分,也不能用abstract修饰
5、如果一个类继承了抽象类,那么这个类中必须重写抽象类中的所有抽象方法和抽象属性,除非这个类使用abstract进行修饰
6、抽象方法和抽象属性不能使用private、final修饰,因为这些修饰符和重写/实现相违背
7、抽象类中可以有实现的方法
8、子类重写父类的抽象方法可以不适用override修饰,使用override修饰也不会报错

21.特质Trait
java使用两种扩展:extends(继承)和implements(实现),scala只使用一种扩展:extends(继承)

1)Trait的特征
建议优先使用Trait
1、类似java中的增强版接口,是基于java中的抽象类来实现的
2、能定义抽象的方法和属性 也能定义具体的方法和属性
3、一个类可以同时继承多个Trait
4、继承类必须实现特质中的所有抽象方法和抽线属性
5、特质内无法定义参数的构造函数
6、实现特质,如果没有继承其它类,那么使用第一个特质使用 extends,后面的使用 with,所以如果有考题说实现特质只能使用 with,这是不对的

扩:
with的用法
class MyXXXX extends A with B with C with D (A : 可以是抽象类,也可以是特质。但是B, C, D 只能是特质)

2)Trait的两种高级用法
A.为实例对象混入Trait
前提:
1、所有的Trait子类必须继承用一个Trait父类
2、混入的Trait子类中的方法名必须相同(重写父类的方法)

package Exercise_01

trait Logger { → 创建父Trait,包含一个实现方法
def log(str: String) {}
}

trait Logger_AA extends Logger { → 子Trait1,重写父Trait的实现方法
override def log(str: String): Unit = {
println(“AAAA:” + str)
}
}

trait Logger_BB extends Logger { → 子Trait2,重写父Trait的实现方法
override def log(str: String): Unit = {
println(“BBBB:” + str)
}
}

trait Logger_CC extends Logger { → 子Trait3,重写父Trait的实现方法
override def log(str: String): Unit = {
println(“CCCC:” + str)
}
}

class Test extends Logger_CC { → 创建一个class类继承Trait3并定义一个方法,在方法中调用Trait3中的log方法
def sayHello(): Unit = {
log(“hello”)
}
}

object TraitTest_0225 {
def main(args: Array[String]): Unit = {
val test = new Test with Logger_BB → 在创建class类对象的时候通过with混入子类Trait2,最后调用sayHello
test.sayHello() 时,在方法中调用的log方法为子类Trait2中的log方法
}
}

输出结果:
BBBB:hello

注:
在创建类对象时混入(val test = new Test with Logger_BB),与class继承子类Trait时混入(class Test extends Logger_CC with Logger_BB)效果一致

B.Trait调用链
前提:
1、所有的Trait子类必须继承用一个Trait父类
2、混入的Trait子类中的方法名必须相同(重写父类的方法)
3、每个Trait子类重写父类Trait方法最后执行的代码必须是调用父类Trait的同名方法

package Exercise_01

trait MyTrait { → 创建父Trait,包含一个实现方法
def test(str: String) {
println(“父类”)
}
}

trait Trait_AA extends MyTrait { → 子Trait1,重写父Trait的实现方法并在重写的方法中最后调用父类的同名方法
override def test(str: String): Unit = {
println(“AAAA:” + str)
super.test(str)
}
}

trait Trait_BB extends MyTrait { → 子Trait2,重写父Trait的实现方法并在重写的方法中最后调用父类的同名方法
override def test(str: String): Unit = {
println(“BBBB:” + str)
super.test(str)
}
}

trait Trait_CC extends MyTrait { → 子Trait3,重写父Trait的实现方法并在重写的方法中最后调用父类的同名方法
override def test(str: String): Unit = {
println(“CCCC:” + str)
super.test(str)
}
}

class Test extends Trait_CC with Trait_BB with Trait_AA { → 创建一个class类继承Trait3并定义一个方法,在方法中
def sayHello(str: String): Unit = { 调用Trait3中的log方法。在继承Trait3的同时,通过
test(str) with混入Trait2、Trait1。最后在调用class对象的时候
} 会依据混入的顺序最先调用Trait1中的test方法,调用
} test方法后调用super.test(str)方法。返回父类Trait时,
由于此前的混入操作,则没有直接调用父类Trait的test
object TraitTest_0225 { 方法,而是逆序调用Trait2中的test方法,如此类推,
def main(args: Array[String]): Unit = { 当最后没有混入的Trait子类时,再调用父类Trait中的
val t = new Test test方法
t.test(“Hello”)
}
}

输出结果:
AAAA:Hello
BBBB:Hello
CCCC:Hello
父类

注:
在创建类对象时混入(class Test extends Trait_CC with Trait_BB with Trait_AA),与class继承子类Trait时混入(val t = new Test with Trait_BB with Trait_AA)效果一致

22.匹配match
1)值或值类型匹配
package Exercise_01

import scala.util.Random

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val arr = Array(1, 2.3, true, “s”, 100) → 创建一个不同类型值的数组(数组为Any类型)
var number = new Random().nextInt(arr.length) → 从0到数组长度值(不包含长度值)之间随机抽取一个数
var value = arr(number) → 获取该数为下标的数组中的元素
value match { → 对该元素进行匹配
case x: Int if x >= 5 => println(value) → 类型匹配加条件守卫(值类型为Int且大于等于5)
case x: Int => println(value) → 类型匹配(值类型为Int)
case 2.3 => println(value) → 值匹配(值为2.3)
case true => println(value) → 值匹配(值为true)
case _ => println(value) → _表示剩余所有的条件,等同于java中的default
}
}
}

注:
匹配顺序为从上至下的顺序,如果符合某一个case条件,则执行该case后的代码并自动停止继续向下的匹配

2)数组、集合的模式(格式)匹配
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val arr = Array(0, 1, 2, 3, 4, 5)
arr match {
case Array(0) => println(“只有元素0”)
case Array(0, _*) => println(“第一个元素为1的数组”) → _*表示可变参数,该项为正确结果
case _ => println(“无符合条件匹配对象”)
}
}
}

package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val list = 0 :: 1 :: Nil
list match {
case 0 :: Nil => println(“只有元素0”)
case x :: y :: Nil => println(“集合中有两个元素”) → 均为符合条件模式
case x :: tail => println(“集合由1个头元素和尾列表组成”) ↗
case _ => println(“没有合适的匹配模式”)
}
}
}

package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val tup = (1, 2, 3, 4)
tup match {
case (1, _, x, y) => println(“元组中共有4个元素,第1个元素为1,第2个元素为任意值”) → 正确结果
case _ => println(“没有合适的匹配模式”)
}
}
}

package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val arr = Array(Array(1, 2, 3), Array(“a”, “b”, “c”))
arr match {
case Array(Array(x, y, z), _) => println(“数组中共两个元素,第1个位数组,第2个为任意值”) → 正确结果
case _ => println(“没有合适的匹配模式”)
}
}
}

3)样例类、样例对象
case class 是多例的,后面要跟构造参数, case object 是单例的,后面不可以跟构造参数
package Exercise_01

import scala.util.Random

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val arr = Array(Teacher, Student(“小明”, 11), Person(“男”))
val number = new Random().nextInt(arr.length)
val value = arr(number)
value match {
case Teacher => println(“样例对象”)
case Student(name, age) => println(“样例类 - Student”)
case Person(sex) => println(“样例类 - Person”)
case _ => println(“匹配失败”)
}
}
}

case class Student(name: String, age: Int)
case class Person(sex: String)
case object Teacher
当一个类被声名为 case class 的时候, scala 会帮助我们做下面几件事情:
1、构造器中的参数如果不被声明为 var 的话,它默认的话是 val 类型的,但一般不推荐将构造器中的参数声明为 var
2、自动创建伴生对象,同时在里面给我们实现子 apply 方法,使得我们在使用的时候可以不直接显示地 new 对象
3、伴生对象中同样会帮我们实现 unapply 方法,从而可以将 case class 应用于模式匹配apply 方法接受参数返回对象, unapply 方法接收对象返回参数
4、实现自己的 toString、 hashCode、 copy、 equals 方法
5、 case class 主构造函数里面没有修饰符,默认的是 val
除此之此, case class 与其它普通的 scala 类没有区别

4)Option类型
在 Scala 中 Option 类型样例类用来表示可能存在或也可能不存在的值(Option 的子类有 Some和 None)。 Some 包装了某个值, None 表示没有值
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val map = Map(“a” -> 1, “b” -> 2, “c” -> 3)
val value = map.get(“aaa”) match { → map.get返回值为Option类型
case Some(x) => {
println(“key值存在”)
x
}
case None => {
println(“key值不存在”)
65535
}
}
println(value)
}
}

上述match匹配代码效果等同于map.getOrElse(“aaa”,65535)

5)偏函数
被包在花括号内没有 match 的一组 case 语句是一个偏函数,它是 PartialFunction[A, B]的一个实例, A 代表输入参数类型, B 代表返回值类型,常用作输入模式匹配
package Exercise_01

object Excrcise_0218_01 {
def ff1: PartialFunction[String, Int] = { → 使用偏函数进行匹配
case “one” => 1
case “two” => 2
case _ => 65535
}

def ff2(str: String): Int = str match { → 使用普通函数进行匹配
case “one” => 1
case “two” => 2
case _ => 65535
}

def main(args: Array[String]): Unit = {
println(ff1(“two”))
println(ff2(“two”))
}
}

23.scala对空的抽象
Option:表示有值(some)或无值(None)
None:空值
Null:空引用
Nil:空列表

24.scala比较运算符
eq、ne:比较引用地址是否相同(eq比较是否相同、ne比较是否不同)
、equals:比较值是否相同(底层是调用equals,但优先比较值是否为空,推荐使用。==和equals比较的前提是类中需要实现toString方法,否则依然是比较类引用地址)

例:
创建一个普通类(未实现toString方法)
scala> class stu(var id:Int,var name:String)
defined class stu
scala> var stu1 = new stu(1,“huangbo”) → 创建对象1
stu1: stu = stu@5cdec700
scala> var stu2 = new stu(1,“huangbo”) → 创建对象2
stu2: stu = stu@6057aebb
scala> stu1 eq stu2
res0: Boolean = false
scala> stu1 ne stu2
res1: Boolean = true
scala> stu1 == stu2
res2: Boolean = false
scala> stu1 equals stu2
res3: Boolean = false

创建一个样例类(自动实现toString方法)
scala> case class Stu(var id:Int,var name:String)
defined class Stu
scala> var sut1 = Stu(1,“huangbo”) → 创建对象1(创建对象不用使用new)
sut1: Stu = Stu(1,huangbo)
scala> var stu2 = Stu(1,“huangbo”) → 创建对象2(创建对象不用使用new)
stu2: Stu = Stu(1,huangbo)
scala> sut1 eq stu2
res5: Boolean = false
scala> sut1 ne stu2
res6: Boolean = true
scala> sut1 == stu2
res7: Boolean = true
scala> sut1 equals stu2
res8: Boolean = true

25.Range

26.方法简写
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
say()
say1()
say2()
say3()
say4
}

def say(): Unit = { → 完整效果
println(“hello,word”)
}
def say1() = { → 返回值为Unit可以省略“:Unit ”
println(“hello,word”)
}
def say2(){ → 返回值为Unit甚至可以直接省略“:Unit = ”
println(“hello,word”)
}
def say3() = println(“hello,word”) → 只有一行代码可以省略“{ }”大括号
def say4 = println(“hello,word”) → 如果方法为无参方法,可以直接省略形参列表的“()”小括号,但调用方法时,
} 只能用过方法名调用该方法,不能带“()”小括号。例:say4

27.函数
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
var arr = Array(8, 9, 1, 2, 3, 4, 5, 6, 7)
var n1 = 16
var n2 = 81
val max1 = (x: Int, y: Int) => if (x > y) x else y → 求最大值函数定义(标准)
val max2: (Int, Int) => Int = (x, y) => if (x > y) x else y → 求最大值函数定义(进阶)
var m1 = max(n1, n2)
var m2 = max1(n1, n2)
var m3 = max2(n1, n2)
var m4 = arr.reduce(max1) → 算子方法中直接使用符合要求的函数
println(m1 + “\t” + m2 + “\t” + m3 + “\t” + m4)
}

def max(a: Int, b: Int): Int = { → 求最大值方法定义
var max = if (a > b) a else b
max
}
}

标准函数:val max1 = (x: Int, y: Int) => if (x > y) x else y
1、val表示定义函数,val后为函数名
2、=是分隔函数名和函数体
3、=>是转换符,转换符前是输入内容,转换符后为输出内容同时包含执行逻辑
4、为了保证函数在使用的时候不因为函数类型的不匹配而出错,所以输入内容中的变量必须指定数据类型,而输出内容可以通过类型自动推断来确定类型,所以不用特地的指定

28.高阶函数
函数是函数式编程语言中的第一公民。函数可以作为参数传入方法或函数中,也可以作为返回值返还给方法或函数
函数可以实现、方法也可以实现,因为函数和方法的本质就是在定义执行的逻辑,所有函数和方法时可以相互转换的
高阶函数的定义:函数可以做为方法或函数的参数或返回值

1)函数做为参数使用
例1:
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
var n = 11
val func: Int => Int = x => x * 100 → 定义一个Int => Int的函数
var fn = f(n, func) → 调用方法并传入定义好逻辑的函数,获得执行结果
println(fn)
}
def f(x: Int, f1: Int => Int) = f1(x) → 定义一个方法,方法形参列表中定义一个需要传入Int => Int的函数
}

例2:
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
var n1 = myfunc((x, y) => x * y) ↘
var n2 = myfunc((x, y) => y - x) → 调用方法,形参列表中传入不同的函数
var n3 = myfunc((x, y) => y / x) ↗
println(n1 + “\t” + n2 + “\t” + n3)
}
def myfunc(func: (Int, Int) => Int) = func(2, 100) → 定义一个方法,方法形参列表中定义一个需要传入(Int,Int =>
} Int的函数,但在输出结果中确定了通过函数执行的数值

例3:模拟map方法效果
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
var arr = Array(1, 2, 3, 4, 5, 6, 7)
mapCopy(arr, x => x * 2)
}

def mapCopy(arr: Array[Int], func: Int => Int) = { → 定义一个方法需要传入一个数组和相关执行逻辑的函数
for (i <- 0 until arr.length) {
println(func(arr(i)))
}
}
}

2)函数做为返回值使用
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
var func1: Int => (Int => Int) = a => (x: Int) => x + a → var func1:函数名称
var func2 = func1(11) Int => (Int => Int):输入体,表示传入一个Int类型的
var num = func2(9) 转换为由Int值转换为Int类型值的函数
println(num) a => (x: Int) => x + a:输出体,表示传入一个变量值
} a,转换为(x: Int) => x + a的函数(a值已经确定)
}

3)匿名函数
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val arr = Array(1, 2, 3, 4, 5, 6)
val sum1 = arr.reduce((x, y) => x + y) → 效果一致
val sum2 = arr.reduce(_ + ) ↗
val arr1 = arr.map(x => x * 2) → 效果一致
val arr2 = arr.map(
* 2) ↗
}
}

当一个参数在函数中出现过一次,并只使用过一次,那么就可以使用_下划线替代该函数参数

4)可变参数叠加
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
var count: Int = 0
def f1(x: Int*): Int = {
for (y <- x.toIterator) { → 通过for循环迭代输出可变参数中的每个元素
count += y
}
count
}
println(f1(1, 2, 3, 4, 5, 10))
}
}

5)综合
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
普通函数定义
def f1(a: Int, b: Int) = a + b
def f2(a: Int, b: Int = 10) = a + b → 定义一个普通函数,参数b设置默认值为10。在调用该函数时,可对b参数进
println(f2(10, 2)) 行重新赋值,如果不赋值,则使用事先设置的默认值
高阶函数定义
def f3(a: Int) = (b: Int) => a + b → 定义一个函数,返回值为另一个函数
val func = f3(3) ↗
println(func(10))
柯里化函数定义一
def f4(a: Int)(b: Int): Int = a + b
def f5(a: Int)(b: Int = 10): Int = a + b → 定义一个柯里化函数,参数b设置默认值为10。在调用该函数时,可对b
println(f5(10)(2)) 参数进行重新赋值,如果不赋值,则使用事先设置的默认值
柯里化函数定义二
def f11(a: Int)(b: Int): Int = a + b → 定义一个参数
def f22 = f11(5) _ → 先传入一个参数的值,将返回值转换为函数
println(f22(10)) → 再传入另一个参数的值,获得结果
隐式参数(implicit用来修饰的形参)
//implicit var x:Int = 10 → 隐式参数的声明方法,同一个作用域内只能存在一个隐式参数
import MyImplicit.z ↗
def f6(a: Int)(implicit b: Int = 100): Int = a + b → implicit导入隐式参数,参数b的默认值变更为隐式参数的值
println(f6(10))
}
}

object MyImplicit {
implicit var x: Int = 100
implicit var y: Int = 200
implicit var z: Int = 1000
}

普通函数:在调用的时候,语义是最明确的,但是在调用的时候,必须一步到位传入所有的需要参数
高阶函数:由于是声明一个函数并返回一个函数,所有可以分布的传入不同的参数
柯里化函数:把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果是一个新函数的技术
隐式参数:
1、一个函数当中的某个形参,必须要声明为implicit才能使用隐式参数值
2、当定义隐式变量的时候,一定要注意,不要在一个作用域中定义多个相同类型的隐式变量
3、当要决定到底使用使用默认的隐式参数值,还是使用隐式变量值作为参数:
首先从全局中去寻找隐式变量值(按照类型去寻找)
然后如果没有找到对应类型的隐式变量值,就使用函数定义中自带的默认隐式参数值

6)闭包
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
def f() = { → 声明一个外部函数
var number = 0 → 声明一个自由变量
val func = (x: Int) => { → 声明一个内部函数,函数的元算逻辑是将传入的参数与自由变量相加,最后返回自由变量
number += x
number
}
func → 将内部函数做为外部函数的返回值
}

val f1 = f  →  调用外部函数,获得做为返回值的内部函数
val f2 = f1(10)  →  调用内部函数并传入参数,已经函数设置的运算逻辑,返回自由变量加上传入参数的和
val f3 = f1(10)
val f4 = f1(10)
val f5 = f1(10)
println(f2, f3, f4, f5)

}
}

输出结果:(10,20,30,40)
闭包注意点:
1、闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。变量的作用域仅限于包含它们的函数,因此无法从其它程序代码部分进行访问。不过,变量的生存期是可以很长,在一次函数调用期间所创建所生成的值在下次函数调用时仍然存在。正因为这一特点,闭包可以用来完成信息隐藏。
2、闭包可以有效的避免误操作或作用域污染等问题

29.函数与方法互相转换
函数与方法的区别:
1、定义时使用的前缀
方法:def
函数:val或var(主要使用val)
2、使用环境
方法:在一个类中,定义一个执行逻辑代表当前类具备的某种行为
函数:定义一个执行逻辑,需要使用做为一个方法或其他函数的参数
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val case2 = case1 _ → 通过方法名加_,将方法转换为函数
println(case2(10, 50))
def case3 = case2 → 通过一个def前缀声明的变量直接将函数转换为方法
println(case3(2, 50))
}

def case1(num1: Int, num2: Int): Int = { → 定义一个方法
num1 * num2
}
}

30.隐式转换
隐式转换使用的三种场景:
1、当调用某个对象不存在的方法的时候
2、当参数类型不匹配的时候
3、当出现视图界定的时候
1)调用对象方法不存在
代码目的:自定义一个方法,通过隐式转换,使File类对象可以使用自定义的方法读取本地文件内的所有内容
package Exercise_01

import java.io.File → 导入相关IO流的包

import scala.io.Source → 导入相关IO流的包

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val file: File = new File(“d:/WordCount.txt”) → 定义一个File对象,输入读取文件地址
import MyImplicit1.fileToRichFile → 导入设定好的隐式转换方法所在对象名称及方法名称
val allWords: String = file.readAllWords() → File类对象在通过隐式转换后,可以使用RichFile类对象的方法
println(allWords)
}
}

object MyImplicit1 { → 声明一个object对象
implicit def fileToRichFile(f: File): RichFile = { → 定义隐式转换方法,将File类对象转换为Rich类对象
new RichFile(f)
}
}

class RichFile(var f: File) { → 声明一个RichFile类
def readAllWords(): String = { → 定义可以读取所有内容的方法
Source.fromFile(f).mkString
}
}
2)参数类型不匹配
A.系统默认类型转换
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
implicit def ff(x: Double): Int = x.toInt → 定义一个隐式函数,默认将Double类型的值转换为Int类型
def f1(x: Int, y: Double): Int = x + y → 定义一个函数,需要传入1个Int类型参数和1个Double类型参数
println(f1(1.1, 2.2)) → 调用f1函数,传入2个Double类型的值,通过隐式转换转为Int类型,最后结果为3
}
}
B.自定义类型转换
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val xuzheng = new Person(“徐铮”, 30) → 定义两个Person类对象
val wangbaoqiang = new Person(“王宝强”, 29) ↗
xuzheng.makeFriends(wangbaoqiang) → 调用Person类对象的方法,方法需传参1个Person类对象
val tangbohu = new Person(“唐伯虎”, 9527) → 定义一个Person类对象
val wangcai = new Dog(“旺财”, 3) → 定义一个Dog类对象

implicit def dogToPerson(dog: Dog): Person = {  →  设置隐式转换的方法将Dog类对象转换为Person类对象
  new Person(dog.name, dog.age)
}

tangbohu.makeFriends(wangcai)  →  调用Person类对象的方法,传参1个Dog类对象,但由于隐式转换已将Dog

} 类对象转换Person类对象,所以运行正常,没有报错
}

class Person(var name: String, var age: Int) {
def makeFriends(per: Person): Unit = {
printf("%s想和%s做朋友", this.name, per.name)
println()
}
}

class Dog(var name: String, var age: Int) {}

3)当出现视图界定的时候
参考泛型-视图界定
31.泛型
概念:用来指示编译器,去校验类型是否准确,确保类型安全
分类:感觉作用的位置范围泛型类和泛型方法

1)泛型类
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val xiaoming = new Student[String, Int](“小明”, 12) → 在创建类对象时,需要明确形参的类型
}
}

class Student[T, S](var name: T, var age: S) {} → 定义类时,在类名后添加形参修饰泛型,并在形参后使用泛型

2)泛型方法
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val stu = new Student
stu.getName(1) ↘
stu.getName(“s”) ↘
stu.getName(2.2) → 由于定义的是泛型方法,所有可以在调用方法的时候传入任何类型的参数
stu.getName(false) ↗
}
}

class Student {
def getName[T](stu: T): Unit = println(stu + “,泛型方法”) → 定义一个泛型方法,在方法名后添加形参的修饰泛型
}

3)类型变量界定
类型变量界定是指在泛型的基础上,对泛型的范围进行进一步的界定,从而缩小泛型的具体范围

没有设置类型变量界定的错误代码:
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val ct = new CompareToTest
ct.compTest(1,2)
}
}

class CompareToTest {
def compTest[T](first: T, second: T) = {
if (first.compareTo(second) > 0) first else second → 编译报错,原因是使用compareTo方法的参数类型必须是实
} 现了Comparable接口。而first和second参数使用类型为
} 泛型,无法判断是否实现了Comparable接口,因此报错
设置类型变量界定的正确代码:
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val ct = new CompareToTest
val str = ct.compTest(“1”, “A”)
println(str)
}
}

class CompareToTest {
def compTest[T <: Comparable[T]](first: T, second: T) = { → 在泛型中定义了T <: Comparable[T],表明了T类型
if (first.compareTo(second) > 0) first else second 上界为Comparable类型既T类型只能为Comparable
} Comparable类型或Comparable类型的子类型
}

4)视图界定
如果希望跨越类继承层次结构时,可以使用视图界定来实现的,其后面的原理是通过隐式转换来实现
隐含参数和方法也可以定义隐式转换,称作视图。视图的绑定从另一个角度看就是 implicit的转换。主要用在两个场合:
1、当一个 T 类型的变量 t 需要转换成 A 类型时
2、当一个类型 T 的变量 t 无法拥有 A 类型的 a 方法或变量时
其实视图的绑定是为了更方便的使用隐式装换
视图界定利用<%符号来实现

没有使用视图界定的错误代码:
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val stu1 = new Student[String, Int](“小明”, 11) → 创建类对象,第二个参数类型指定为Int类型
val stu2 = new Student[String, Int](“小红”, 10) ↗
val number: Int = stu1.age.compareTo(stu2.age) → 对两个Int类型的值使用compareTo方法进行比较。Int类并
println(number) 没有实现Comparable接口,系统默认会将Int类型隐式转换
} RichInt类型,RichInt类实现了Comparable接口。但由于
} 泛型类使用的<:类型变量界定不支持隐式转换,因而运行时报

class Student[T, S <: Comparable[S]](var name: T, var age: S) {} → 定义一个泛型类,设定两个泛型参数,第二个泛型
参数使用类型变量界定,上界为Comparable类
运行报错:

使用视图界定的正确代码:
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val stu1 = new Student[String, Int](“小明”, 11)
val stu2 = new Student[String, Int](“小红”, 10)
val number: Int = stu1.age.compareTo(stu2.age)
println(number)
}
}

class Student[T, S <% Comparable[S]](var name: T, var age: S) {} → 使用<%视图界定后,Int类型可以隐式转换为
RichInt类型,而RichInt类型实现了
Comparable接口,所有代码运行正常

32.协变、逆变
协变的定义:String是Any的子类, MyList444[String] 必定也是 MyList444[Any] 的子类
逆变的定义:String是Any的子类, MyList444[Any] 必定也是 MyList444[String] 的子类
多态的核心:把一个对象,可以赋值给一个父类引用,但不能赋值给一个子类引用

1)不变(默认)
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val list1: MyList[String] = new MyListString → 泛型化对象指定类型都为为String,类型相同,不存在协
val list2: MyList[String] = new MyList[String](“s”,new MyListString) 变、逆变
}
}

class MyList[T](val head: T, val tail: MyList[T]) {} → 定义一个泛型类

2)协变
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val list1: MyList[Any] = new MyList[Any](“s”, new MyList[String](“s”, null)) → 泛型类支持协变,则可以将包含子
val list2: MyList[Any] = new MyList[String](“s”, new MyList[String](“s”, null)) 类的类对象赋值给包含父类的类对
} 象
}

class MyList[+T](val head: T, val tail: MyList[T]) {} → 在泛型定义的时候,使用+T表示协变

3)逆变
原理与协变相反
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val list1: MyList[String] = new MyList[String](“s”, new MyList[Any](“s”, null))
val list2: MyList[String] = new MyList[Any](“s”, new MyList[Any](“s”, null))
}
}

class MyList[-T](val head: T, val tail: MyList[T]) {}

4)添加方法 – 协变
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val list1: MyList[String] = new MyList[String](“s”, null)
val list2 = list1.testMyList(“ss”)
printf("%s", if (list2 == null) “创建失败” else “创建成功”)
}
}

class MyList[+T](val head: T, val tail: MyList[T]) {
def testMyList[U >: T](word: U): MyList[U] = new MyList[U](word, this)
}

33.数组
注:
在scala中的集合中,有两对概念:
1、定长和变长,针对数组(定长、边长主要是针对数组的长度)
2、可变和不可变,针对集合(可变和不可变主要是针对集合的元素)
默认引用的资源(类、jar包等)中的数组都是定长的、集合都是不可变的。如果需要使用变长数组或可变集合,需要手动的引入相应的资源(导包)

变长数组:ArrayBuffer
定长数组:Array
1)创建数组
定长数组创建:
var arr1 = Array(1, 2, 3, 4, 5, 6) → 静态创建(边创建边赋值)
var arr2 = new ArrayInt → 动态创建
变长数组创建:
import scala.collection.mutable.ArrayBuffer → 需要导入的包

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
var arr = new ArrayBufferInt → 动态创建(变长数组关键字为:ArrayBuffer,边长数组不需要指定数组长度)
var arr2 = ArrayBuffer(1, 2, 3, 4) → 静态创建
}
}
2)访问数组中的元素
var arr2 = ArrayBuffer(1, 2, 3, 4)
println(arr2(0))
println(arr2(2))
println(arr2(3))
3)修改数组中的元素(增删改)
定长数组只能修改和访问元素,不能增加和删除元素(修改元素与变长数组同理)
变长数组修改:
package Exercise_01

import scala.collection.mutable.ArrayBuffer

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
var arr = new ArrayBufferInt
arr += 1 → 增加单个元素
arr.append(2) → 增加单个元素
arr += ( 5, 6) → 增加多个元素
arr.insert(2, 3, 4) → 在指定的下标位置添加一个或多个元素(2为数组下标)
arr ++= List( 7, 8) → 增加一个集合中的所有元素
arr -= 8 → 删除指定值的元素
arr.remove(6) → 删除指定下标的元素
arr(5) = 666666 → 修改指定下标元素的值
for (i <- 0 until arr.length) println(arr(i))
}
}
4)遍历数组
var arr2 = ArrayBuffer(1, 2, 3, 4)
for(i <- 0 until arr2.length) println(arr2(i))
5)变长数组与定长数组间的互转
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
var arr1 = Array(1, 2, 3, 4, 5) → 声明一个定长数组
var arr2 = arr1.toBuffer → 将定长数组转换为变长数组
arr2 += 6 → 对变长数组进行元素添加
var arr3 = arr2.toArray → 将变长数组转换为定长数组
for (i <- 0 until arr3.length) println(arr3(i)) → 遍历输出
}
}
6)数组常用的API
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
var arr = Array(7, 2, 1, 8, 3, 9, 5, 4, 6)
println(arr.max) → 输出数组中的最大值:9
println(arr.min) → 输出数组中的最小值:1
println(arr.length) → 输出数组的长度(及数组中的元素个数):9
println(arr.sum) → 输出数组中所有元素的总和:45
println(arr.isEmpty) → 输出数组判断是否为空的结果:false
println(arr.mkString) → 输出数组中的所有元素:721839546
println(arr.mkString("-")) → 输出数组中的所有元素并用指定符号分隔:7,2,1,8,3,9,5,4,6
println(arr.mkString("<", “-”, “>”)) → 输出数组中的所有元素并用指定符号分隔:<7-2-1-8-3-9-5-4-6>
}
}
7)数组中的元素排序
sorted:前提是数组或者集合的泛型实现了Comparable接口的话, 才可以直接调用进行默认规则的排序(默认升序)
sortBy:如果没有实现Comprable接口,那么可以自己制定按照某个属性或者字段去进行默认的字典排序
sortWith:根据自己制定规则进行排序
C.sorted
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val arr = Array(7, 2, 1, 8, 3, 9, 5, 4, 6)
val arr1 = arr.sorted → 使用sorted进行默认排序
val arr2 = arr1.reverse → 使用reverse对排序结果进行反转
for (i <- 0 until arr1.length) println(arr1(i))
for (i <- 0 until arr2.length) println(arr2(i))
}
}
D.sortBy
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val s1 = new Stu(33, “huangbo”)
val s2 = new Stu(22, “xuzheng”)
val s3 = new Stu(11, “wangbaoqiang”)
val arr = Array(s1, s2, s3)
val sarr1 = arr.sortBy(x => x.id) → 通过sortBy将传入的函数指定的字段进行默认字典排序
val sarr2 = arr.sortBy(x => x.name) ↗
for (i <- 0 until sarr1.length) println(sarr1(i).id)
for (i <- 0 until sarr2.length) println(sarr2(i).name)
}
}
class Stu(var id: Int, var name: String) {} → 声明一个包含两个属性(id、name)的类

E.sortWith
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val s1 = new Stu(22, “huangbo”)
val s2 = new Stu(11, “xuzheng”)
val s3 = new Stu(33, “wangbaoqiang”)
val arr = Array(s1, s2, s3)
val arr1 = arr.sortWith((x, y) => x.id > y.id) → 通过sortWith将传入的函数指定的字段进行默认字典排序
val arr2 = arr.sortWith(_.name > _.name) ↗
for (i <- 0 until arr1.length) println(arr1(i).id)
for (i <- 0 until arr2.length) println(arr2(i).name)
}
}
class Stu(var id: Int, var name: String) {} → 声明一个包含两个属性(id、name)的类

8)二维数组转换为一维数组
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val arr = Array(Array(1, 3), Array(2, 4)) → 创建一个二维数组
val arr1 = arr.flatten → 通过flatten将二维数组压平为一维数组
arr1.foreach(println)
}
}

34.集合
对应数据包 举例 备注
scala.collection Map 父类
scala.collection.immutable imutable.HashMap 不可变的子类实现
scala.collection.mutable mutbale.HashMap 可变的子类实现

可变集合:可以在原有的集合上进行元素的增删改操作,操作的是原集合的元素
不可变集合:不能对原有的集合元素进行增删改操作,但进行操作后可以以新的对象(集合)返回结果,原有集合的元素没有任何改变
1)列表List
List创建:
val list1 = 1 :: 2 :: 3 :: Nil → 两种List创建效果一致
val list2 = 1 :: (2 :: (3 :: Nil)) ↗
val list3 = List(1, 2, 3)
val list7 = new ListBufferInt → 创建一个可变List集合
注:
Nil为空列表,主要是为了避免Null空指针的出现
当前所有的List集合对象都是由一个头元素和尾列表组成,所有上面的List(list1、list2)创建可以看成是先由3头元素与Nil空列表组成集合,在有2头元素与3尾列表组成集合,最后由1头元素与2、3尾列表组成集合,这样的右集合符合List是由头元素和尾列表组成的规范

List常用API:
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val list = List(1) → 创建一个集合,集合中有1个元素:1,结果为List(1)
val list1 = 0 :: list → 0为头元素,list集合为尾列表组合成新的集合,结果为List(0,1)
val list2 = list.::(0) → 0为头元素,list集合为尾列表组合成新的集合,结果为List(0,1)
val list3 = 0 +: list → 0为头元素,list集合为尾列表组合成新的集合,结果为List(0,1)
val list4 = list.+:(0) → 0为头元素,list集合为尾列表组合成新的集合,结果为List(0,1)
val list5 = list4.:+(2) → 在list4集合后添加一个元素2,结果为List(0,1,2)
val list6 = List(3, 4, 5) → 静态创建一个集合,包含元素3、4、5,结果为List(3,4,5)
val list7 = list5 ++ list6 → 将两个集合合并为一个集合,结果为List(0,1,2,3,4,5)
val list8 = list5 ::: list6 → 将两个集合合并为一个集合,结果为List(0,1,2,3,4,5)
val list9 = list5 ++: list6 → 将两个集合合并为一个集合,结果为List(0,1,2,3,4,5)
val list10 = list5.:::(list6) → 将两个集合合并为一个集合,后集合在前,结果为List(3,4,5,0,1,2)
val list11 = list10.toBuffer.-(5).toList → 将不可变集合转换为可变集合,删除数值为5的元素,结果为List(3,4,0,1,2)
val num1 = list10.head → 获取集合的头元素,结果为num1:Int = 5
val list12 = list10.tail → 获取集合的尾列表,结果为List(4,5,0,1,2)
val list13 = list10.init → 获取集合的头列表,结果为List(3,4,5,0,1)
val num2 = list10.last → 获取集合的尾元素,结果为num1:Int = 2
val list14 = list10.drop(1) → 抛弃集合下标1之前的元素,结果为List(4,5,0,1,2)
val list15 = list10 drop 1 ↗
val list16 = list10.take(2) → 获取集合下标2之前的元素,结果为List(3,4)
val list17 = list10 take 2 ↗
val tup1 = list10.splitAt(2) → 以第2个元素分隔为两个集合,结果为(List(3,4),List(5,0,1,2)) - 元组类型
val list18 = tup1._1 → 获取元组中的第1个元素,结果为List(3,4)
val list19 = tup1._2 → 获取元组中的第2个元素,结果为List(5,0,1,2)
val str1 = list10.toString() → 将集合转换为字符串,结果为List(3,4,5,0,1,2) - 字符串输出
val str2 = list10.mkString → 将集合转换为字符串,结果为345012 – 元素间无分隔符
val str3 = list10.mkString(",") → 将集合转换为字符串,结果为3,4,5,0,1,2 – 元素间添加分隔符“,”
val str4 = list10.mkString("<", “-”, “>”) → 将集合转换为字符串,结果为<3-4-5-0-1-2>
val arr = list10.toArray → 将集合转换为数组,结果为Array(3,4,5,0,1,2)
val list20 = List(1, 2,) → 静态创建一个集合,包含元素1、2、3、4,结果为List(1,2)
val list21 = List(“a”, “b”) → 静态创建一个集合,包含元素a、b、c、d,结果为List(“a”,“b”)
val list22 = list20 zip list21 → 将两个集合压缩为一个集合,压缩后的集合中元素为元组格式,结果为List((1,a),
(2,b)) - (压缩相同下标的元素为一个元组,对于元素被舍弃)
val map1 = list22.toMap → 将压缩后的元素为元组的集合转换为Map集合,结果为Map(1 –> a,2 ->b)
val tup2 = list22.unzip → 将压缩后的元素为元组的集合解压缩,结果为(List(1,2),List(”a”,”b”))
val tup3 = map1.unzip → 将Map集合解压缩,结果为(List(1,2),List(”a”,”b”))
val list23 = tup2._1 → 解压缩后类型为元组,元素为两个集合,调取第一个元素集合,结果为List(1,2)
val list24 = tup3._2 → 解压缩后类型为元组,元素为两个集合,调取第二个元素集合,结果为List(”a”,”b”)
}
}

List集合中的sorted、sortBy、sortWith使用方法是与Array中的一致,同时其他API:MaxBy、MinBy使用方法与sortBy一致
2)集合Set
在任何场景中set永远只有两个作用:1、去重,2、求交集、并集、差集的

Set常用API:
package Exercise_01

import scala.collection.immutable.HashSet → 导包,创建不可变HashSet
import scala.collection.mutable → 导包,创建可变HashSet

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val set1 = Set(1, 2, 3, 4, 5, 5, 5, 5, 5) → 静态创建一个不可变Set集合并自动去重,结果为Set(5,1,2,3,4)
val set2 = Set(3, 4, 5, 6, 7, 7, 7, 7, 7) → 静态创建一个不可变Set集合并自动去重,结果为Set(5,6,7,3,4)
val set3 = set1 intersect set2 → 求两个Set集合的交集,结果为Set(5,3,4)
val set4 = set1 diff set2 → 求两个Set集合的差集,包含首集合元素差集结果,结果为Set(1,2)
val set5 = set2 diff set1 → 求两个Set集合的差集,包含首集合元素差集结果,结果为Set(6,7)
val set6 = set1 union set2 → 求两个Set集合的并集,结果为Set(5,1,6,2,7,3,4)
val set7 = new HashSetInt → 动态创建一个不可变的HashSet集合,元素类型为Int类型
val set8 = set7 + 1 → 不可变集合添加一个元素1并赋值给另外一个Set集合
val set9 = set8 ++ Set(1, 2, 3, 4) → 不可变集合添加一个Set集合并赋值给另外一个Set集合
val set10 = new mutable.HashSetInt → 动态创建一个可变的HashSet集合,元素类型为Int类型
set10 += 1 → 可变集合添加一个元素1
set10.add(2) → 可变集合添加一个元素2
set10 ++= Set(3, 4, 5) → 可变集合添加一个Set集合
set10 -= 1 → 可变集合删除一个元素1
set10.remove(5) → 可变集合删除一个元素5
}
}

3)映射Map
Map常用API:
package Exercise_01

import scala.collection.mutable → 导包,创建可变HashMap

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val map1 = Map(“a” -> 1, “b” -> 2) → 静态通过 key值->value值 格式创建一个不可变Map集合
val map2 = Map((“a”, 1), (“b”, 2)) → 静态通过 (key值,value值) 格式创建一个不可变Map集合
val map3 = new mutable.HashMapString, Int → 动态创建一个可变HashMap集合
map3(“a”) = 1 → 添加key – value对(”a”,1)
map3 += ((“b”, 2)) → 添加key – value对(”b”,2)
map3 put(“c”, 3) → 添加key – value对(”c”,3)
println(map3(“c”)) → 通过key值“c“,获得对应的value值(如果key值不存在报错)
println(map3.getOrElse(“d”, 1000)) → 通过key值“c“,获得对应的value值(如果key值,返回设定值)
map3 -= “c” → 通过key值删除对应的key – value对
map3.remove(“b”) → 通过key值删除对应的key – value对
map3.clear() → 将Map集合中的所有元素清空
map3 += ((“a”, 1), (“b”, 2), (“c”, 4)) → 向一个可变Map集合中添加以二元组为元素的元组
map3(“c”) = 3 → 对可变Map集合,根据key值修改对应的value值
val bool = map3.contains(“c”) → 判断key值是否存在Map集合中,返回Boolean值
for(i <- map3) println(i) → 增强for遍历Map集合,输出值为元组
for(i <- map3) println(i._1 + “:” + i._2) → 增强for遍历Map集合,输出值为元组中的元素
}
}

4)元组Tuple
元素中可以包含不同数据类型的元素,但元组不存在可变,一定确定则无法修改元素内容(元组下标默认从1开始)
Map常用API:
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
val tup1 = (“a”, 1, 2.2, true) → 创建一个元组,包含4个不同数据类型的元素
val tup2, (a, b, c, d) = (“a”, 1, 2.2, true) → 创建一个元组,包含4个不同数据类型的元素(tup2为定义的元组名,括
中的a,b,c,d分别对应元组中的4个元素。在创建过程中,tup2被调用了2次)
println(tup2._1 + “\t” + tup2._2 + “\t” + tup2._3 + “\t” + tup2._4) → 通过元组名._元素下标调用元素值
println(a + “\t” + b + “\t” + c + “\t” + d) → 创建时已经对应了元素值,直接调用即可
val arr = Array((“a”, 1), (“b”, 2), (“c”, 3)) → 创建一个元素为二元组的数组
val map1 = arr.toMap → 将对偶的元组转换为Map集合
val sarr = tup1.toString().split("\s") → 单元组可以通过转换为字符串,再用空格分隔,转换为字符串数组
}
}

35.算子
1)filter - 过滤
通过逻辑函数将数组、集合中的每个元素标记为true或false,输出标记为true的元素,过滤掉标记为false的元素
例:过滤数组中偶数元素输出
var arr = Array(324, 234, 123, 543, 44, 12, 34, 548, 9345, 7563)
var arr1 = arr.filter(x => if (x % 2 == 0) true else false)
arr1.foreach(println)
运算结果:
324
234
44
12
34
548

2)count – 计数
通过逻辑函数将数组、集合中的每个元素标记为true或false,统计标记为true的元素,过滤掉标记为false的元素
例:统计数组中所有元素可以被3整除的个数
var arr = Array(324, 234, 123, 543, 44, 12, 34, 548, 9345, 7563)
var arr1 = arr.count(x => if (x % 3 == 0) true else false)
println(arr1)
运算结果:
7

36.scala的练习题
1)九九乘法表
package Exercise_01

object Excrcise_0218_01 {
def main(args: Array[String]): Unit = {
for (i <- 1 to 9; k <- 1 to i) printf("%d * %d = %d%s", i, k, i * k, if (i == k) “\n” else “\t”) → 通过if判断决定使用
} 换行符还是tab分隔符(经典操作)
}

2)WordCount
A.简单版(分步骤)
静态创建一个数组
scala> val arr = Array(“hi a b”,“hi hi a”,“hi a b c”)
arr: Array[String] = Array(hi a b, hi hi a, hi a b c)
通过map算子将数组中各个元素通过空格分隔,返回一个以数组为元素的数组
scala> val r1 = arr.map(x => x.split(" "))
r1: Array[Array[String]] = Array(Array(hi, a, b), Array(hi, hi, a), Array(hi, a, b, c))
通过flatten算子将数组中的各个元素(数组)中的元素压平成一个数组中的所有元素
scala> val r2 = r1.flatten
r2: Array[String] = Array(hi, a, b, hi, hi, a, hi, a, b, c)
通过map算子将数组中的每个元素转换为方便计数的元组
scala> val r3 = r2.map(x => (x,1))
r3: Array[(String, Int)] = Array((hi,1), (a,1), (b,1), (hi,1), (hi,1), (a,1), (hi,1), (a,1), (b,1),
(c,1))
通过groupBy算子对数组中的每一个元素进行分组,分组依据为元组的第一个元素
scala> val r4 = r3.groupBy(x => x._1)
r4: scala.collection.immutable.Map[String,Array[(String, Int)]] = Map(b -> Array((b,1), (b,1)), a ->
Array((a,1), (a,1), (a,1)), c -> Array((c,1)), hi -> Array((hi,1), (hi,1), (hi,1), (hi,1)))
通过map算子将分组后的Map集合进行WordCount统计,key值为需要统计的单词,value值的数组长度为统计个数
scala> val r5 = r4.map(x => (x._1,x._2.length))
r5: scala.collection.immutable.Map[String,Int] = Map(b -> 2, a -> 3, c -> 1, hi -> 4)
将统计好结果的Map集合转换为数组,方便排序
scala> val r6 = r5.toArray
r6: Array[(String, Int)] = Array((b,2), (a,3), (c,1), (hi,4))
根据单词出现次数进行倒序排序
scala> r6.sortBy(x => x._2).reverse
res2: Array[(String, Int)] = Array((hi,4), (a,3), (b,2), (c,1))

B.简单版(进阶版)
scala> val arr1 = arr.flatMap(x => x.split(" ")).groupBy(x => x).map(x => (x._1,x._2.length)).toArray.sortBy(x => x._2).reverse
arr1: Array[(String, Int)] = Array((hi,4), (a,3), (b,2), (c,1))

mapper阶段:.flatMap(x => x.split(" "))
shuffle阶段:.groupBy(x => x)
reduce阶段:map(x => (x._1,x._2.length))

3)过滤算子使用
A.过滤掉数组中的所有负数
scala> val arr = Array(6,3,0,-1,4,-3,5,-2,1,2)
arr: Array[Int] = Array(6, 3, 0, -1, 4, -3, 5, -2, 1, 2)

scala> val arr1 = arr.filter(x => if(x >= 0) true else false)
arr1: Array[Int] = Array(6, 3, 0, 4, 5, 1, 2)

B.过滤掉数组中的第一个负数
scala> val arr = Array(6,3,0,-1,4,-3,5,-2,1,2)
arr: Array[Int] = Array(6, 3, 0, -1, 4, -3, 5, -2, 1, 2)

scala> var count = 0
count: Int = 0

scala> val arr1 = arr.filter(x => if(x >= 0) true else if(count != 0) true else {count += 1 ; false})
arr1: Array[Int] = Array(6, 3, 0, 4, -3, 5, -2, 1, 2)

C.过滤掉数组中的除第一个负数以外的所有负数
scala> val arr = Array(6,3,0,-1,4,-3,5,-2,1,2)
arr: Array[Int] = Array(6, 3, 0, -1, 4, -3, 5, -2, 1, 2)

scala> var count = 0
count: Int = 0

scala> val arr1 = arr.filter(x => if(x >= 0) true else {count += 1 ; if(count == 1) true else false})
arr1: Array[Int] = Array(6, 3, 0, -1, 4, 5, 1, 2)

4)求平均数
A.方法一(reduce使用)
scala> val arr = Array(3,4,1,6,5,2)
arr: Array[Int] = Array(3, 4, 1, 6, 5, 2)

scala> val r1 = arr.map(x => (x,1)).reduce((x,y) => (x._1 + y._1 , x._2 + y._2))
r1: (Int, Int) = (21,6)

scala> val avg = r1._1.toDouble / r1._2
avg: Double = 3.5

B.方法二(fold – 折叠使用)
scala> val arr = Array(3,4,1,6,5,2)
arr: Array[Int] = Array(3, 4, 1, 6, 5, 2)

scala> val r1 = arr.foldLeft((0,0))((x,y) => (x._1 + y , x._2 + 1))
r1: (Int, Int) = (21,6)

scala> val avg = r1._1.toDouble / r1._2
avg: Double = 3.5

注:
折叠分类
原生折叠:fold
左折叠:foldLeft
右折叠:foldRight
格式
val r1 = arr.foldLeft((0,0)) ((x,y) => (x._1 + y , x._2 + 1))
第一个括号位置:传入状态的初始值
第二个括号位置:传入元素和状态变量是如何进行合并的一个函数(案例中x表示状态变量(0,0),y为数组中遍历的每个元素)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值