Scala语言具有面向对象的、函数式编程的、静态类型的、可扩展的、可以交互操作的显著特性。
Scala是一门以java虚拟机(JVM)为运行环境将面向对象和函数式编程的最佳特性结合在一起的静态类型编程语言(scala是一门类java的多范式语言)
(1)scala运行于java虚拟机之上,并且兼容现有的java程序
(2)scala是一门纯粹的面向对象的语言
(3)scala也是一门函数式语言,每个函数都是一个对象
通常编写好的java代码存放在 ,java 文件中,需要经过编译变成 .class 文件,才可以在JVM上运行。而scala,通常是创建一个 .scala 文件来编写代码,同样会被编译成 .class 文件,然后在JVM上运行。
scala的类型层次结构:
Any类位于层级结构的最顶层,包括了两个子类AnyVal和AnyRef,其中AnyVal是所有值类型的父类,包括Byte,Short,Int等。而AnyRef包括scala的引用类型和其他java的引用类型。Null和Nothing位于scala类层级结构的最低层。
在idea中下载scala插件,然后创建一个普通的maven项目,导入相关的依赖,即可开始在idea中编写scala代码
<dependencies>
<dependency>
<groupId>org.scala-langgroupId>
<artifactId>scala-libraryartifactId>
<version>2.11.12version>
dependency>
<dependency>
<groupId>org.scala-langgroupId>
<artifactId>scala-compilerartifactId>
<version>2.11.12version>
dependency>
<dependency>
<groupId>org.scala-langgroupId>
<artifactId>scala-reflectartifactId>
<version>2.11.12version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.49version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.1version>
<configuration>
<source>1.8source>
<target>1.8target>
configuration>
plugin>
<plugin>
<groupId>org.scala-toolsgroupId>
<artifactId>maven-scala-pluginartifactId>
<version>2.15.2version>
<executions>
<execution>
<goals>
<goal>compilegoal>
<goal>testCompilegoal>
goals>
execution>
executions>
plugin>
plugins>
build>
首先创建一个 .scala 文件,然后开始编写main方法。scala与java不同,scala的主方法是这样的
object Demo1 {
def main(args: Array[String]): Unit = {
}
}
这里需要思考的是,为什么scala中的主方法所在的类要用object修饰,而不是class
如果在java中运行main方法,会发现这里调用主方法是通过类名 . main()的方式,因为main方法是静态static,所以可以通过类名直接调用
但是scala里面没有static,不能通过这种方式来定义主方法,因此在运行的时候,找不到main方法去执行。解决方法是将class换成object
scala跟java一样需要编译生成.class文件,才能在JVM里面运行,也就是类被加载到JVM里面的方法区。此时会生成一个Class对象,也即类对象。类对象在JVM中有且只有一个(单例模式,一个类只有一个对象)
在scala中,类名前面有object和class两种情况
object :“类对象”
class:“普通的类”
由object修饰的类中所有的方法以及变量都是静态的,可以直接通过“类对象”进行调用
由class修饰的类中所有的方法以及变量都是普通的,可以通过“类的对象”来调用(类的对象就是new出来的对象)
而main方法作为程序的入口,所在的类必须是object修饰
解释完为什么是object修饰的类名后,再回过头来看一下主方法的构成
def —— 定义一个函数的关键字
main —— 方法名
args: Array[String] —— args是参数名,Array[String]是参数类型
Unit —— 返回值类型,相当于java中的void
{} —— 里面编写方法体
object Demo1 {
//参数列表为空的函数
def test2():Unit= {
println("Demo1的方法")
}
//参数列表不为空的函数
def test3(str:String):Unit={
println(str)
}
//定义变量,val定义的变量不可重新赋值,var定义的变量可以重新赋值
val a1 = 2
var b1 = 3
//main方法,程序的入口
def main(args: Array[String]): Unit = {
println("test")
//执行上面定义的两个方法
test2()
test3("传入参数的方法")
//打印两个变量
println(a1)
b1 = 10
println(b1)
//对于class定义的类,需要创建类的对象(new)来调用方法
val t1 = new test1()
t1.test()
}
}
//class修饰的类,方法不是静态的
class test1{
def test():Unit = {
println("test")
}
}
补充:val修饰的变量是不可以重新赋值,但是修改里面的值还是可以的,比如
val i = 10,那么像 i + 10 或者 i * 2 都是可以的,不可以的是 i = 20 这样重新的赋值
虽然var 定义的变量可以重新赋值,但是能用 val 定义的变量不要使用 var
7种数值类型Byte、Char、Short、Int、Long、Float、Double类型和1个Boolean类型。
注意这里是 Int 而不是 int,首字母都是要大写的
val a1:Int = 1
val a2:Char = 'a'
像这样在变量名后面 :跟上类型即可规定变量的类型
数据类型转换的时候,只需要加上一个 To 即可
val a1:Int = 1
println(a1.toDouble) //结果为 1.0
字符串的拼接与打印
val str3 = "abc"
val str4 = "efg"
val i3 = 10
// 可以通过 $变量名 在字符串中取变量的值 底层也是StringBuilder
println(s"${str3},${str4},${i3}") //结果为 abc,efg,10
if - else,while,do - while 循环结构和 java 基本类似,但是for循环不一样。scala的for循环结构跟接近于java 的 foreach
val s = "a,b,c,d,e,f,g"
val list:List[String] = s.split(",").toList
for (elem <- list) {
println(elem)
}
文件读取
//文件读取
val source:BufferedSource = Source.fromFile("Scala/data/test.txt")
val lines: Iterator[String] = source.getLines()
for (elem <- lines) {
println(elem)
}
source.close
val writer: FileWriter = new FileWriter("Scala/data/textWriter.txt")
for(i <- 0 to 10){
writer.write("这是第"+i+"行数据\n")
}
writer.close()
val writer: FileWriter = new FileWriter(“Scala/data/textWriter.txt”,true)
后面默认会有一个append属性值为false,写入时是覆盖式写入,现在给定一个true值后就会变成追加式写入
//类里面可以定义属性,方法
//在scala中,整个定义的类的{}包裹的内容都是默认的构造方法
class demo3student(id:String,name:String,age:Int) {
//定义类的属性,需要在属性名前面加上_,定义即赋值
val _id:String = id
val _name:String = name
val _age:Int = age
//重写了父类的方法
override def toString():String = {
return s"demo3student(_id=${_id}, _name=${_name}, _age=${_age})"
}
}
object demo3 {
def main(args: Array[String]): Unit = {
//创建对象
val stu1: demo3student = new demo3student("001", "zhang", 12)
println(stu1.toString())
//打印变量
println(stu1._id)
println(stu1._name)
println(stu1._age)
}
}
现在增加新的需求,在java中可以通过重载的方式定义不同的构造方法,那么在scala中,如何定义不同参数的构造方法
class demo3student(id:String,name:String,age:Int) {
//定义类的属性,需要在属性名前面加上_,定义即赋值
val _id:String = id
val _name:String = name
val _age:Int = age
//如果没有调用下面重载后的构造方法,而是使用默认的构造方法,则gender的值应该是null或者0,这里用下划线_表示
//使用_表示稍后进行赋值,但是定义变量时必须声明类型
var _gender:String = _
println("调用了默认的构造方法")
//重载构造方法,创建对象时,传入gender属性值则会调用这个构造方法,给gender赋值
def this(id:String,name:String,age:Int,gender:String){
//第一行代码必须调用上面的默认的构造方法
this(id,name,age)
_gender = gender
println("调用了新的构造方法")
}
//重写了父类的方法
override def toString():String = {
return s"demo3student(_id=${_id}, _name=${_name}, _age=${_age},_gender=${_gender})"
}
}
object demo3 {
def main(args: Array[String]): Unit = {
//创建对象
val stu1: demo3student = new demo3student("001", "zhang", 12)
val stu2: demo3student = new demo3student("002","chen",18,"男")
println(stu1.toString())
println(stu2.toString())
}
}
在scala中通过this可以定义一个新的构造方法,但是新的构造必须在第一行通过this调用默认的构造方法
class A(id:String,name:String){
val _id:String = id
val _name:String = name
override def toString = s"A(_id=${_id},_name=${_name})"
def printAB():Unit = {
println("这是A的方法")
}
}
//B继承A
//在scala中,继承的时候就需要给定A的参数,参数值是B的参数列表里面的参数值
class B(id:String,name:String,age:Int) extends A(id,name){
val _age:Int = age
override def toString = s"A(_id=${_id},_name=${_name},_age=${_age})"
override def printAB():Unit = {
println("这是B的方法")
}
}
object demo4{
def main(args: Array[String]): Unit = {
val stu1:A = new A("001", "zhang")
val stu2:B = new B("002", "chen", 12)
println(stu1)
println(stu2)
stu1.printAB()
stu2.printAB()
}
}
class A(id:String,name:String){
val _id:String = id
val _name:String = name
override def toString = s"A(_id=${_id},_name=${_name})"
}
//B继承A
class B(id:String,name:String,age:Int) extends A(id,name){
val _age:Int = age
override def toString = s"A(_id=${_id},_name=${_name},_age=${_age})"
}
object demo4{
//方法里面定义的是参数是父类的对象,调用时传入父类对象或其子类对象都可以
//这是多态的一种体现,父类引用指向了子类对象
def printTest(a:A):Unit = {
println("父类对象或者其子类对象")
a.printAB()
}
def main(args: Array[String]): Unit = {
val stu1:A = new A("001", "zhang")
val stu2:B = new B("002", "chen", 12)
printTest(stu1)
printTest(stu2)
}
}
虽然上面说了一大堆,但是实际使用的时候,还是使用样例类比较多
新建一个 case Class 文件,如果idea没有这个选项,直接创建一个 .scala 文件也行,下面就是 .scala文件的代码
//样例类,通过这种方式定义一个类,然后根据这个类来创建对象
//一般都使用样例类来创建对象,不会使用继承,重写之类的
case class demo5case(id:String
,name:String
,age:Int
,gender:String
,clazz:String)
然后在main方法 new 出来这个类的对象
import java.io.{BufferedReader, FileWriter}
import scala.io.{BufferedSource, Source}
object demo6 {
def main(args: Array[String]): Unit = {
val stu3: demo5case = new demo5case("003", "wang", 15, "女", "文科一班")
println(stu3)
}
}
通过反编译器可以将 .class 反编译,这时就可以发现上面创建的样例类,里面的 id,name等变量都是被 private final 修饰的。而且下面会被隐式的加上许多方法,比如 get 方法等,但因为这里都是final修饰的变量,所以没有能够修改变量的方法
因此,如果希望样例类里面定义的变量可以被修改,就需要加上var。此时再创建一次对象,然后再次反编译,之后就会发现 id 变量前面只被 private 修饰了
case class demo5case(
var id:String
,name:String)
val stu3: demo5case = new demo5case("003", "wang", 15, "女", "文科一班")
println(stu3)
stu3.id = "004"
println(stu3)
回归正题,为什么创建对象时,可以去掉 new 关键字
反编译后,会发现这个方法也被自动添加到样例类的 .class 文件中
然后还要再拓展一个知识点,伴生对象
Scala不能定义静态成员,而是代之定义单例对象。以object关键字定义。对象定义了某个类的单个实例,包含了你想要的特性。
当一个单例对象和它的同名类一起出现时,这时的单例对象就被称为这个同名类的伴生对象。没有同名类的单例对象,被称为孤立对象。
object Demo7 {
def main(args: Array[String]): Unit = {
val t1: teacher = teacher.apply("002", "lisi")
println(t1._id)
// apply可以省略,直接通过 ”类对象"名直接进行调用
// 因此可以通过apply方法省略new关键字
val t2: teacher = teacher("003", "xiaoming")
println(t2._id)
}
}
class teacher(id: String, name: String) {
val _id: String = id
val _name: String = name
}
// teacher类的伴生对象
object teacher {
def apply(id: String, name: String): teacher = {
new teacher(id, name)
}
}
因为有伴生对象里面的apple方法来创建teacher类的对象,因此不需要 new 出来一个对象,又因为 apply 方法可以被省略,所以达成的效果就是创建样例类的对象时,不需要写 new 关键字