Scala语言入门
Scala(发音为/ˈskɑːlə, ˈskeɪlə/)是一门多范式的编程语言,设计初衷是要集成面向对象编程和函数式编程的各种特性。洛桑联邦理工学院的Martin Odersky于2001年基于Funnel的工作开始设计Scala。Java平台的Scala于2003年底/2004年初发布。
特点:
- 同样运行在JVM上,可以与现存程序同时运行。
- 可直接使用Java类库。
- 同Java一样静态类型。
- 语法和Java类似,比Java更加简洁(简洁而并不是简单),表达性更强。
- 同时支持面向对象、函数式编程。
- 比Java更面向对象。
关注点:
- 类型推断、不变量、函数式编程、高级程序构造。
- 并发:actor模型。
- 和现有Java代码交互、相比Java异同和优缺。
和Java关系:
javac java .java --------> .class ----------> run on JVM .scala -------> .class ----------> run on JVM scalac scala
测试代码:Scala分支
阅读:
环境配置
Scala需要依赖Java,访问这里查看特定Scala版本依赖的Java版本。这里选择,最新的JDK17配合Scala2.13.6。
Windows中下载安装配置环境变量:
- 类似于java配置
SCALA_HOME
为安装目录。 - 添加
%SCALA_HOME%\bin
到path环境变量。
Linux中类似,可以使用包管理器,但如果依赖版本不严格一致的话,需要官网下载对应版本安装即可。
也有交互式执行环境:
scala
交互式执行环境中的传统艺能:
println("hello,world!")
暂时不管项目配置,还是单文件编译执行为主,项目开发肯定要以包的形式组织可以使用IntelliJ IDEA开发,使用maven或者sbt进行项目配置。
使用VSCode编辑器,安装插件Scala Syntax (official)和Scala (Mentals)。
新建文件HelloScala.scala
。
object HelloScala { // HelloScala is a object, not a class, will create a
def main(args : Array[String]) : Unit = {
println("hello,world!");
}
}
可以使用插件CodeRunner直接快捷键运行。也可以在命令行编译为字节码后再运行:
scalac HelloScala.scala scala helloScala
或者直接运行scala源文件:
scala HelloScala.scala
和java如出一辙。
如果编译的话会生成2个.class
字节码文件,HelloScala.class
和HelloScala$.class
。都是字节码但是不能通过java
直接运行。但对于HelloWorld这个例子来说,java源代码编译而成的字节码是可以通过scala
命令运行的。
原因是没有引入Scala的库,添加classpath
就可以通过java执行scala编译成的字节码了:
java -cp %SCALA_HOME%/lib/scala-library.jar; HelloScala
使用Java Decompiler反编译字节码到java源文件可以看到引入Scala库的逻辑。并且:
- scala源文件中的
HelloScala
对象编译后成为了一个类,但对象本身编译后就是生成的另一个类HelloScala$
类的单例对象HelloScala$.MODULE$
,称之为伴生对象。 HelloScala$
有一个main
实例方法,HelloScala
类的静态方法通过这个单例对象转调这个实例方法。完成打印。- Scala比Java更面向对象。
IDEA环境配置
使用IntelliJ IDEA:
- 创建Maven项目,JDK版本17。
- 安装插件:Scala。一般默认都已经装了。
- Maven项目默认用Java写,在
main/
目录下新建目录scala/
,然后将目录标记为Source Root。 - 这样甚至可以在同一个项目中混用Scala和Java源文件,并互相调用。
- 需要能够添加scala源文件,右键项目,添加框架支持,配置Scala SDK,选择,然后就可以右键添加Scala源文件了。
- 添加包,添加Scala类,选择对象,编辑源码。
package chapter01 //object:关键字,声明一个单例对象(伴生对象) object HelloWord { /*main方法:从外部可以直接调用执行方法 * def 方法名(参数名称:参数类型):方法返回值类型={方法体} * */ def main(args: Array[String]): Unit = { System.out.println("hhaha") println("Hello scala") } }
- Ctrl + Shift + F10运行。
- 可以看到执行的命令是
java
在classpath
中引入了Scala的jar
包形式的库。 - 调用java的类库:
package VeryStarted object HelloWorld { def main(args: Array[String]): Unit= { println("Hello,world!") System.out.println("Hello,world! from java") } }
语法含义:
object SingletonObject { body }
def MethodName(ArgName: ArgType): RetType = { body }
object
关键字创建的伴生对象,可以理解为替代Java的static
关键字的方式,将静态方法用单例对象的实例方法做了替代,做到了更纯粹的面向对象。
仅仅测试理解语法既可以单文件编写VSCode编译执行也可以用IDEA管理项目,影响不大。
再用一个等价的类定义来认识和区别一下Scala和Java:
java:
public class Student { private String name; private Integer age; private static String school = "XDU"; public Student(String name, Integer age) { this.name = name; this.age = age; } public void printInfo() { System.out.println(this.name + " " + this.age + " " + Student.school); } // psvm public static void main(String[] args) { Student tch = new Student("tch", 20); tch.printInfo(); } }
scala:
package VeryStarted class Student(name: String, age: Int) { def printInfo(): Unit = { println(name + " " + age + " " + Student.school) } } // 引入伴生对象,名称一致,同一个文件 object Student { val school: String = "XDU" def main(args: Array[String]): Unit = { val tch = new Student("tch", 20) tch.printInfo() } }
Scala库源码与API文档:
- 官网上下载下载Scala源码,解压到Scala安装目录或任意位置。
- IntelliJ IDEA进入反编译的源码后选择右上角附加源码,选择源码的
src/
目录。 - 安装包里面已经有了文档,没有的话可以单独下载。
- Scala依赖Java,某些类型就是Java的包装,库中有一部分java源码。
变量与数据类型
注释:
- 和java一样
//
单行/* */
多行/** */
文档,方法或者类前面,便于scaladoc
生成文档。
变量和常量:
var | val 变量名 :变量类型 = 变量值
var name [:VariableType] = value // variable
val name [:ConstantType] = value // constant
因为Scala的函数式编程要素,所以一个指导意见就是能用常量就不要用变量。
- 声明变量时,类型可以省略,编译器会自动推导。
- 静态类型,类型经过给定或推导确定后就不能修改。
- 变量和常量声明时,必须有初始值。
- 变量可变,常量不可变。
- 引用类型常量,不能改变常量指向的对象,可以改变对象的字段。
- 不以
;
作为语句结尾,scala编译器自动识别语句结尾。
标识符命名规范:
- 字母下划线开头,后跟字母数字下划线,和C/C++/Java一样。
- 操作符开头,且只包含(+-*/#!等),也是有效的标识符。这样用会用什么奇怪的好处吗?答案是灵活到天顶星的运算符重载。
- 用反引号包括的任意字符串,即使是同39个Scala关键字同名也可以。有点奇怪的用法,尚不知道为什么。
var _abc:String = "hello"
val -+/%# = 10
val `if` = 10
println(_abc)
println(-+/%#)
println(`if`)
关键字:
package import class obejct trait extends with type for
private protected abstract sealed final implicit lazy override
try catch finlly throw
if else match case do while for return yield
def var val
this super
new
true false null
- 其中Java没有的关键字:
object trait with implicit match yield def val var
字符串:
- 类型:
String
+
号连接*
字符串乘法,复制一个字符串多次printf
格式化输出- 字符串插值:
s"xxx${varname}"
前缀s
模板字符串,前缀f
格式化模板字符串,通过$
获取变量值,%
后跟格式化字符串。 - 原始字符串:
raw"rawstringcontents${var}"
,不会考虑后跟的格式化字符串。 - 多行字符串:
""" """
。 - 输出:
print printf println ...
package String //传值字符串 object ScalaSt { def main(args: Array[String]): Unit = { //字符串,通过+号连接 val name:String="alice" val age:Int=18 println(age+"岁的"+name+"在尚硅谷学习") //*用于将一个字符串重复拼接 println(name*3) //传值字符串,通过%传值 print("%d岁的%s在尚硅谷学习",age,name) println() //插值字符串 :通过$获取变量值 println(s"${age}岁的${name}在尚硅谷学习") //格式化模板代码 val num:Double=2.3453 println(f"The num is ${num}%2.2f") println(f" : power ${num}%.2f.") //三引号表示字符串,保持多行字符串的原始输出 s""" |select* |from |student |where |name=${name} |and |age>${age} |""".stripMargin } }
输入:
StdIn.readLine()
StdIn.readShort() StdIn.readDouble
import scala.io.StdIn
package String import scala.io.StdIn object StdInr { def main(args: Array[String]): Unit = { //输入信息 // val str = StdIn.readLine("请输入您的大名: ") // val str1 = StdIn.readLine("请输入您的年龄: ") // val name = StdIn.readLine() // val age=StdIn.readInt() // println(s"欢迎${age}岁的${name}来到尚硅谷学习") println("input name:") val name: String = StdIn.readLine() println("input age:") val age: Int = StdIn.readInt() println(s"欢迎${age}岁的${name}来到尚硅谷学习")
读写文件:
package String import java.io.{File, PrintWriter} import scala.io.Source object FileIO { def main(args: Array[String]): Unit = { //1.从文件中读取数据(绝对路径) Source.fromFile("F:\\scala代码\\scala_tut\\src\\main\\resources\\test.txt").foreach(print) //2.将数据写入文件 val writer = new PrintWriter(new File("F:\\scala代码\\scala_tut\\src\\main\\resources\\test.txt")) writer.write("hello scala from java writer") writer.close() } }
数据类型:
- java基本类型
char byte short int long float double boolean
。 - java引用类型:Object,数组,字符串,包装类,集合,POJO对象等(对象类型)
- java基本类型对应包装类型:
Charater Byte Short Integer Long Float Double Boolean
。 - java中不是纯粹的面向对象。
- Scala吸取了这一点,所有数据都是对象,都是
Any
的子类。 Any
有两个子类:AnyVal
值类型AnyRef
引用类型。- 数值类型都是
AnyVal
子类,和Java数值包装类型都一样,只有整数在scala中是Int
、字符是Char
有点区别。 StringOps
是java中String
类增强,AnyVal
子类。Unit
对应java中的void
,AnyVal
子类。用于方法返回值的位置,表示方法无返回值,Unit
是一个类型,只有一个单例的对象,转成字符串打印出来为()
。Void
不是数据类型,只是一个关键字。Null
是一个类型,只有一个单例对象null
就是空引用,所有引用类型AnyRef
的子类,这个类型主要用途是与其他JVM语言互操作,几乎不在Scala代码中使用。Nothing
所有类型的子类型,也称为底部类型。它常见的用途是发出终止信号,例如抛出异常、程序退出或无限循环。
Scala数据类型
Scala是完全面向对象的语言,所以不存在基本数据类型的概念,有的只是任意值对象类型(AnyVal)和任意引用对象类型(AnyRef)
空集是所有集合的子集
整数类型:都是有符号整数,标准补码表示。
Byte
1字节Short
2字节Int
4字节Long
8字节- 整数赋初值超出表示范围报错。
- 自动类型推断,整数字面值默认类型
Int
,长整型字面值必须加L
后缀表示。 - 直接向下转换会失败,需要使用强制类型转换,
(a + 10).toByte
。
浮点类型:
Float
IEEE 754 32位浮点数Double
IEEE 754 64位浮点数- 字面值默认
Double
字符类型:
- 同java的
Character
,2字节,UTF-16编码的字符。 - 字符常量:
''
- 类型
Char
- 转义:
\t \n \r \\ \" \'
etc
布尔类型:true false
空类型:
Unit
无值,只有一个实例,用于函数返回值。Null
只有一个实例null
,空引用。Nothing
确定没有正常的返回值,可以用Nothing来指定返回值类型。好像意思是抛异常时返回Nothing,不是特别懂。
object NullType { def main(arg : Array[String]) : Unit = { // Unit def f1(): Unit = { println("just nothing!") } val a = f1() println(a) // () // null only used for AnyRef 空引用NuLL // val n:Int = null // invalid //所有类对应的应用类型 //var student:Student=new Student("alice",20) student=null println(student) //Nothing def m2(n:Int):Int={ if(n==0) throw new NullPointException else return n } val b=m2(0) println(b) } }
数据类型转换:
- 自动类型提升:多种数据类型混合运算,自动提升到精度最大的数据类型。
- 高精度赋值到低精度,直接报错。
- 除了图中的隐式类型转换,都需要强制类型转换。
Byte Short Char
计算时会直接提升为Int
。Boolean
不能参与整数浮点运算,不能隐式转换为整数。
强制类型转换:
toByte toInt toChar toXXXX
'a'.toInt
2.7.toInt
- 数值与String的转换:
"" + n
"100".toInt
"12.3".toFloat
12.3".toDouble.toInt
- 整数强转是二进制截取,整数高精度转低精度可能会溢出,比如
128.toByte
。
Scala标准库:
Int
Double
这些数据类型对应于Java中的原始数据类型,在底层的运行时不是一个对象,但Scala提供了从这些类型到scala.runtime.RichInt/RichDouble/...
的(低优先级)隐式类型转换(在Perdef
中定义),从而提供了非原始类型具有的对象操作。- 基本类型都是默认导入的,不需要显式导入,位于包
scala
中。还有scala.Predef
对象也是自动导入。 - 其他需要导入的包:
scala.collection
集合。scala.collection.immutable
不可变数据结构,比如数组、列表、范围、哈希表、哈希集合。scala.collection.mutable
可变数据结构,数组缓冲、字符串构建器、哈希表、哈希集合。scala.collection.concurrent
可变并发数据结构,比如字典树。
scala.concurrent
原始的并发编程。scala.io
输入输出。scala.math
基本数学操作。scala.sys
操作系统交互。scala.util.matching
正则。- 标准库中的其他部分被放在独立的分开的库中。可能需要单独安装,包括:
scala.reflect
反射API。scala.xml
xml解析、操作、序列化。scala.collection.parallel
并行集合。scala.util.parsing
parser的组合子,什么东西?scala.swing
java的GUI框架Swing的封装。- 定义了一些别名给常用的类,比如
List
是scala.collection.immutable.List
的别名,也可以理解为默认导入? - 其他别名可能是底层平台JVM提供的,比如
String
是java.lang.String
的别名。
强转溢出面试题
package String //128:int类型 ,占据4个字节,32字节 //1000 0000 //原码: 0000 0000 0000 0000 0000 0000 1000 0000 //补码 : 0000 0000 0000 0000 0000 0000 1000 0000 //截取最后一个字节,byte /*得到补码 1000 0000 表示最大负数 -128 * */ //130:int类型 ,占据4个字节,32字节 //1000 0000 //原码: 0000 0000 0000 0000 0000 0000 1000 0010 //补码 : 0000 0000 0000 0000 0000 0000 1000 0010 //截取最后一个字节,byte /*得到补码 1000 0010 原码 1111 1110 表示最大负数 -126 * */ object TestType { def main(args: Array[String]): Unit = { var n:Int = 128 var b:Byte=n.toByte println(b) } }
运算符
运算符:
- 和Java基本相同。
- 算术运算:
+ - * / %
,+
可以用于一元正号,二元加号,还可以用作字符串加法,取模也可用于浮点数。没有自增和自减语法++ --
。 - 关系运算:
== != < > <= >=
- 逻辑运算:
&& || !
,&& ||
所有语言都支持短路求值,scala也不例外。 - 赋值运算:
= += -= *= /= %=
- 按位运算:
& | ^ ~
- 移位运算:
<< >> >>>
,其中<< >>
是有符号左移和右移,>>>
无符号右移。 - scala中所有运算符本质都是对象的方法调用,拥有比C++更灵活的运算符重载。
自定义运算符:
- Scala中运算符即是方法,任何具有单个参数的方法都可以用作中缀运算符,写作中缀表达式的写法。
10.+(1)
即是10 + 1
。 - 定义时将合法的运算符(只有特殊符号构成的标识符)作为函数名称即可定义。
运算符优先级:
- 当一个表达式使用多个运算符时,将根据运算符的第一个字符来评估优先级。内置的运算符和自定义运算符都是函数,遵守同样的规则。
(characters not shown below)
* / %
+ -
:
= !
< >
&
^
|
package 运算符 object Operator { def main(args: Array[String]): Unit = { //1.算术运算符 val result:Int=10/3 println(result) val result1: Double = 10 / 3 println(result1) val result2: Double = 10.0 / 3 强转过程的精度缺失二进制 println(result2) val result3: Double = 10.0 / 3 println(result3.formatted("%5.2f")) } }
java比较运输运算符==
package 运算符; public class test { public static void main(String[] args) { String s1="hello"; String s2=new String("hello"); boolean b = s1 == s2; //比较的是引用地址 System.out.println(b); false System.out.println(s1.equals(s2)); //比较的是内容 true } }
object Operator { def main(args: Array[String]): Unit = { //1.算术运算符 val result:Int=10/3 println(result) val result1: Double = 10 / 3 println(result1) val result2: Double = 10.0 / 3 println(result2) val result3: Double = 10.0 / 3 println(result3.formatted("%5.2f")) //2.比较运算符 val s1:String="hello" val s2:String=new String("hello") println(s1==s2) //true println(s1.equals(s2)) //true //引用地址比较 println(s1.eq(s2)) //false } }
//3.逻辑运算符 def m(n:Int):Int={ println("m被调用了") return n } val n=1 //短路与 println((4>5)&&m(n)>1) //判断一个字符串是否为空 def isNotEmpty(str:String):Boolean={ return str!=null && !("".equals(str.trim)) } println(isNotEmpty(null)) //false
//赋值运算符 //自增自减 int x=15; int y=x++; System.out.println(x+","+y); //16,15 x=15; y=++x; System.out.println(x+","+y); //16,16 x=23; y=x++; System.out.println(x); //temp=x++ x=tmp 23
var b:Byte=10 var i:Int=12 i+=1 println(i) //没有i++的写法scala中
在Scala中其实是没有运算符的,所有运算符都是方法。
scala是完全面向对象的语言,所以数字其实也是对象
当调用对象的方法时,点.可以省略
如果函数参数只有一个,或者没有参数,()可以省略