# Scala-demo **Repository Path**: CandyPop/scala-demo ## Basic Information - **Project Name**: Scala-demo - **Description**: scala学习笔记记录,源码 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-04-23 - **Last Updated**: 2022-08-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: Scala ## README #### Scala scala同样是基于jvm开发的语言,也可以说是java的扩展,弥补了java在做大数据方面的不足,所以scala是java++? * Scala基于JVM,和Java完全兼容,同样具有跨平台,可移植性好,方便的垃圾回收等特性 * Scala比java更加面向对象 * Scala是一门函数式编程语言 Scala更适合大数据的处理 * Scala对集合类型数据处理有非常好的支持 * Spark的底层使用Scala编写 ##### Scala 环境搭建 * 确保安装了jdk1.8 * 下载对应的scala-2.12.11 * 配置环境变量`SCALA_HOME`是你的安装路径 ```scala object HelloScala{ // 一个简单的scala文件,scala不需要;号最为结尾 // 他总是参数写在前面,类型写在后面,unit表示方法返回为空,相当void def main(args: Array[String]) : Unit = { println("hello scala") } } ``` 执行 `scalac HelloScala.scala` 会生成两个.class文件 ![1650684016775](./img/1650684016775.png) ![1650684070947](./img/1650684070947.png) 反编译后的内容,但是这个是通过scala编译成.class再通过java反编译的的内容,所以有些地方看起来会有些奇怪。 ![1650684621081](./img/1650684621081.png) ![1650684631112](./img/1650684631112.png) 也就意味着,实际上也是代表HelloScala$实际是才是负责编译我们的scala文件类,通过HelloScala.class作为入口来执行。 在HelloScala$的反编译结果中,将自己在静态代码块中初始化,并赋值给了`static final MOUDLE`对象 ```java static{ new ();//实际上是 new HelloScala$() } //然后再构造函数赋值 private HelloScala$(){ MOUDLE = this; } ``` 这样是一种单例模式的实现,你可以认为scala定义的object在jvm中最终解释为一个静态对象,和class对象一样,都是有且只有一份的,所以scala中,是不需要定义static对象,也就是在scala中,main函数没有定义成static的原因,因为在全局任何都可以拿到已经初始化好的静态HelloScala ##### IDEA 中 使用Scala 创建一个空的maven项目,然后安装scala插件 ![1650685980512](./img/1650685980512.png) 添加框架依赖 ![1650686168770](./img/1650686168770.png) ![1650686188006](./img/1650686188006.png) 执行程序 ![1650686300998](./img/1650686300998.png) 这里涉及很多类型,但我们目前只需要一个全局唯一的可执行的对象,所以这里选择Object ![1650686429292](./img/1650686429292.png) ```scala /** * * @author Pop * @date 2022/4/23 12:17 * * scala 直接将 类和构造函数组合在了一起 * * Class Student 是 Object Student的伴生类 * Object Student 是 Class Student 伴生对象 * 他们彼此相伴相生 同样,他们也可以彼此访问 构造的时候,也可以传入是变量还是常量 */ class Student(var name:String,val age:Int) { def printInfo():Unit = { println(String.format("%s,%s,%s", name,Int,Student.school)) } } // 引入伴生对象 // object 诞生解决了静态属性访问,和静态方法的访问,由于scala没有static关键字,所以object作为了替代 object Student{ val school: String = "pop" def main(args: Array[String]): Unit = { new Student("pop",20).printInfo(); } } ``` 要彼此成为伴生需要有两个条件 * 名字必须一致 * class与object定义必须在同一个文件里 最后反编译出来的效果,class Student的结果,是存储在Student.class文件里的,同样他也是入口执行文件。jvm需要他来执行程序,Object student的内容存储在Student$.class文件。 ##### Scala的注释 和java完全一样 ##### 变量和常量 java中的变量和常量 ``` 变量类型 变量名称 = 初始值 int a = 10 final 常量类型 常量名称 = 初始值 final int b = 20 ``` scala中的变量和常量 ``` var 变量名[:变量类型] = 初始值 var i:Int = 10; val 常量名[:常量类型] = 初始值 val j:Int = 20; 能用常量的地方不用变量 ``` ```scala /** * * @author Pop * @date 2022/4/23 14:25 */ object Scala_Variable { def main(args: Array[String]): Unit = { var a:Int = 10 // 声明变量时,类型可以省略,编译器自动推断,即类型推倒 var a1 = 10 // 类型确定后,就不能修改,说明Scala是强数据类型语言 var a2 = 15 // a2 = "123" 不允许 // 变量声明后,必须要有初始值 // 声明一个,定义一个变量时,可以用var或者val来修饰,前者修饰变量,后者修饰常量 } } ``` ![1650696208298](./img/1650696208298.png) ```scala var `class`:Int = 20 var 123 = 123 var -=====@ = 23 ``` ##### 字符串 ```scala /** * * @author Pop * @date 2022/4/23 14:51 */ object Scala_String { def main(args: Array[String]): Unit = { // 字符串,通过 + 号拼接 val name:String = "pop" val age:Int = 18; println(age+" 岁的 "+name+" 吃烧烤") // * 将一个字符串复制多次并拼接 println(name*3) // 格式化输出 printf("%d岁的%s吃烧烤",age,name) // 字符串模版,插值字符串,通过$获取变量值 //s"" 表示是一个字符串模版 println(s"${age}岁的${name}在吃烧烤") var num:Double = 1.236 //f"" 格式化模版字符串 println(f"num 的值是 ${num}%2.2f")// 总共保持两位,小数点保持两位 //raw"" 不做任何处理 所以%2.2f也会输出 println(raw"num 的值是 ${num}%2.2f") // 三引号表示字符串,保持多行字符串的原格式输出 s""" |select * |from | student | where | name = ${name} """.stripMargin //stripMargin 去除中间的特殊字符,例如 | } } ``` ##### 控制台输入 ##### 读写文件 ##### 数据类型 ![1650698532140](./img/1650698532140.png) 7.Nothing,是所有数据类型的子类,主要用在一个函数没有明确返回值时使用,因为这样我们可以把抛出的返回值,返回给任何的变量或者函数。(例如抛出异常的时候,是没有值也没有引用的) ##### 一个转换类型面试题 ```scala object test{ def main(args:Array[String]):Unit={ val n:Int = 130 val b:Byte = n.toByte println(b)//-126 } } ``` 由于byte的只有一个字节,所以他的范围是-128到127 当你赋值了一个大于byte范围的数字给他的时候,他的内部会做强制截取。 ``` 130 在 int 类型中的表示 原码 0000 0000 0000 0000 0000 0000 1000 0010 补码 0000 0000 0000 0000 0000 0000 1000 0010 由于是无符号,所以原反补都是一样的 由于byte中只有8位 所以他会强制截取补码 补码 1000 0010 而当你反推会原码的时候,先取反 再+1 1000 0001 原码 1111 1110 -126 ``` ![1654527686297](./img/1654527686297.png) 类型转换 ![1654696052240](./img/1654696052240.png) ![1654697537393](./img/1654697537393.png) ![1654697553289](./img/1654697553289.png) ![1654697584582](./img/1654697584582.png) ![1654697681958](./img/1654697681958.png) ![1654697698613](./img/1654697698613.png) ![1654700504750](./img/1654700504750.png) ##### 分支语句的返回值 ```scala val result:Unit = if(age<=6){ println("童年") }else if(age<=8){ println("童年1") } val result1:String = if(age<=6){ "童年" }else if(age<=8){ "童年1" } // 默认分支的最后一行,是这个分支的返回值 // 需要特别说明的一点是scala的分支判断的返回值可以是Unit,而你在分支中写的最后一行,也可以是String类型,意义是,你可以返回String类型,但是我不接受 val result1:Unit = if(age<=6){ "童年" }else if(age<=8){ "童年1" } // 这也是不报错的,且可以执行的 val result1:Any = if(age<=6){ "童年" }else if(age<=8){ age } // 简写 如果你只需要一行的话,可以这样简写 var res2 = if(age>=18) "成年" else "未成年" ``` ##### 循环 请看源码 ##### 函数基础 ![1655126589185](./img/1655126589185.png) 函数关注的是映射关系,即便调用多次,只要传入的值是固定的,结果永远幂等。 具体看源码 ##### 函数柯里化&闭包 * 闭包:如果一个函数,访问到了他的外部(局部)变量的值,那么这个函数和他所处的环境,相当于打包在了一起,成为闭包 ```scala def func(i:Int):String=>Char=>Boolean = { def f1(s:String):Char=>Boolean = { def f2(c:Char):Boolean={ if(i==0&&s==""&&c=='0') false else true } f2 } f1 } /*对于任何一个支持函数编程的语言中,闭包都是很重要的知识点 闭包的出现,可以让内部的函数访问外部的局部变量 那么如果根据我们以前的理解, */ func(0)("")('0') /* 在func(0)执行后,其实他的任务已经完成了,虽然他返回了另一个f1函数 但是作为执行流程,他的栈帧被压入到虚拟机栈,被执行完后,他会被弹栈,他所代表 的参数,局部变量也会被释放,那么为什么f1,f2却可以访问到已经被释放的值呢 在scala中,任何的东西都是对象,所以对于函数而言,他也是对象,那么如果是创建对象 他的内存空间的申请会发生在堆中,同时,他的所有的局部变量也会保存在其中,即便他的方法执行完成,弹出,也不会释放,直到gc的回收。 所以闭包内的函数可以拿到 */ ``` * 函数柯里化:把一个参数列表的多个参数,变成多个参数列表 ##### 递归 自己调用自己 ##### 导包 ![1655308678940](./img/1655308678940.png) ##### Scala中的类和对象 scala中没有public,一个scala中可以写多个类。 ``` [修饰符] class 类名 { 类体 } ``` * scala 语法中,类并不声明为public,这些类都具有公有可见性(即默认就是public) * 一个scala 源文件可以包含多个类 ##### 封装 ```scala // 定义一个类 ,,默认是public class Student{ // 定义属性 默认是public private var name:String = "pop" // 创建set get 方法 @BeanProperty val age:Int = 18 // 默认给一个空值 var sex:String = _ } // 其实类和的对象的属性,都是封装了private的,当我们obj.property其实也是调用scala底层封装的public getXX 熟悉 ``` ##### 访问权限 在java中,访问权限分为:public,private,protected和默认,在scala中,你可以通过类似的修饰符达到同样的效果,但是使用上有区别 * Scala 中熟悉和方法的默认访问权限为public,但 Scala中**无public关键字** * private 为私有权限,只为类的内部和伴生对象中可用 * protected 为类保护权限,Scala中受保护权限比Java更严格,同类,子类可以访问,同包无法访问 * private[包名]增加包访问权限,包名下的其他类也可以使用 ##### 构造器 Scala类的构造器包括:主构造器和辅助构造器 ```scala class 类名(形参列表){ // 主构造器 // 类体 def this(形参列表){ // 辅助构造器 } def this(形参列表){ // 辅助构造器可以有多个 } } ``` * 辅助构造器,函数的名称 this,可以有多个,编译器通过参数的个数来区别 * 辅助构造器不能直接构建对象,必须直接或者间接调用主构造器 * 构造器调用其他另外的构造器,要求被调用的构造器必须提前申明 ##### 构造器参数 * Scala类的柱构造器函数的形参包含三种类型,未用过任何修饰,var修饰,val修饰 * 未用任何修饰符修饰,这个参数就是一个局部变量 * var 修饰参数,作为**类的成员属性**可以使用,可以修改 * val 修饰参数,作为**类的只读属性**使用,不能修改 ##### 匿名子类 不在乎名字,只是想实现 ##### 单例对象(伴生对象) Scala语言是**完全面向对象**语言,所以并没有静态的操作,因为Scala中没有 静态的概念。但是为了能够和Java语言交互,因为Java中有静态概念,就产生了一种特殊的对象来**模拟类对象**,该对象为**单例对象**,若单例对象名和类名一样,则 称该单例对象这个类的伴生对象,这个类的所有静态 * 单例对象采用object关键字名称 * 单例对象对应的类称之为伴生类,伴生对象的名称因为和伴生类名一致 * 单例对象中的属性和方法都可以通过伴生对象名直接调用访问 ##### 特质(Trait) Scala语言中,采用特殊trait(特征)来代替接口的概念,也就是说,多个类具有相同的特质(特征)时,就可以将这个特质,特征独立出来,采用关键字trait 声明。 Scala中的trait中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质,这种感觉类似于Java中的抽象类 Scala引入了trait特征,第一个**可以替代Java的接口**,第二个也是**对单继承机制**的一种补充 ```scala trait 特质名 { trait 主体 } ``` ##### 特质基本语法 单继承,多实现 一个类具有某种特质(特征),就意味着这个类满足了这个特征(特征)的所有要素,所以在使用时,在也采用extends关键字,如果有多个特质或存在父类,那么需要采用with关键字连接 * 基本语法: * 没有父类:class 类名 extends 特质1 with 特质2 with 特质3 ... * 有父类:class 类名 extends 父类 with 特质1 with 特质2 with 特质3 ##### 特质和抽象类的区别 * 优先使用**特质**。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类。 * 如果你需要构造函数参数,使用抽象类。因为抽象类可以定义**带参数**的构造函数,而特质不行,有无参构造 ##### 自身类型 自身类型你可以理解成代理模式的静态代理 他通过在特质里声明指定的类型,可以在自己的特质内容使用该声明的所有属性和方法 ##### 类型的检查和转换 * obj.isInstanceOf[T] 判断obj 是不是 T类型 * obj.asInstanceOf[T] 将obj强转T类型 * 查看类本身的类型classOf[T] 看看 T是什么类型 ##### 枚举类和应用类 * 枚举类:需要继承Enumeration * 应用类:需要继承App ##### 集合类型 * Scala的集合有三大类:序列Seq,集Set,映射Map,所有集合都扩展自Iterable特质 * 对于几乎所有的集合类,Scala都同时提供了可变和不可变的版本,分别位于以下两个包 * 不可变集合:scala.collection.immutable * 可变集合:scala.collection.mutable * Scala不可变集合,就是指该集合对象不可修改,每次修改都会返回一个新对象,而不会对原对象进行修改,类似于java中的String对象对象 * 可变集合,就是这个集合可以直接对原对象进行修改,而不会返回新的对象,类似于Java中的StringBuilder对象 ![1656338660975](./img/1656338660975.png) 绿色表示的是**特质特征**,蓝色表示的是**集合类型** 需要注意的是,indexedSeq下方的**Array**和**String**是虚线,并不是直接继承关系,这里涉及了隐身转换 `Predef.scala` ##### 数组