1. 实验室名称:
大数据实验教学系统
2. 实验项目名称:
Scala类和对象
3. 实验学时:
4. 实验原理:
1、类
类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用内存,而对象是具体的,占用存储空间。类是用于创建对象的蓝图,它是一个定义包括在特定类型的对象中的方法和变量的软件模板。
我们可以使用 new 关键字来创建类的对象,实例如下:
1. class Point(xc: Int, yc: Int) { 2. var x: Int = xc 3. var y: Int = yc 4. 5. def move(dx: Int, dy: Int) { 6. x = x + dx 7. y = y + dy 8. println ("x 的坐标点: " + x); 9. println ("y 的坐标点: " + y); 10. } 11. }
2、Trait
Trait(特征) 定义的方式与类类似,但它使用的关键字是 trait,如下所示:
1. trait Equal { 2. def isEqual(x: Any): Boolean 3. def isNotEqual(x: Any): Boolean = !isEqual(x) 4. }
以上Trait(特征)由两个方法组成:isEqual 和 isNotEqual。isEqual 方法没有定义方法的实现,isNotEqual定义了方法的实现。子类继承特征可以实现未被实现的方法。所以其实 Scala Trait(特征)更像 Java 的抽象类。
3、单例对象和伴生对象
在 Scala 中,是没有 static 这个东西的,但是它也为我们提供了单例模式的实现方法,那就是使用关键字 object。
Scala 中使用单例模式时,除了定义的类之外,还要定义一个同名的 object 对象,它和类的区别是,object对象不能带参数。
当单例对象与某个类共享同一个名称时,他被称作是这个类的伴生对象:companion object。你必须在同一个源文件里定义类和它的伴生对象。类被称为是这个单例对象的伴生类:companion class。类和它的伴生对象可以互相访问其私有成员。
5. 实验目的:
掌握Scala面向对象基本用法(类、Trait)。
掌握Scala object单例对象,伴生对象。
掌握Scala Case class定义和使用。
掌握Scala中的模式匹配
6. 实验内容:
1、学习Scala面向对象基本用法(类、Trait)。
2、学习Scala object单例对象,伴生对象的用法。
3、学习Scala Case class定义和使用。
4、学习Scala中的模式匹配。
7. 实验器材(设备、虚拟机名称):
硬件:x86_64 ubuntu 16.04服务器
软件:JDK 1.8.162,Scala-2.11.11
8. 实验步骤:
8.1 Scala类和对象
1、类。
Scala 的类定义可以有参数,称为类参数,如上面的 xc, yc,类参数在整个类中都可以访问。
接着我们可以使用 new 来实例化类,并访问类中的方法和变量。
启动shell,在paste模式下,键入以下代码:
1. import java.io._ 2. 3. class Point(xc: Int, yc: Int) { 4. var x: Int = xc 5. var y: Int = yc 6. 7. def move(dx: Int, dy: Int) { 8. x = x + dx 9. y = y + dy 10. println ("x 的坐标点: " + x); 11. println ("y 的坐标点: " + y); 12. } 13. } 14. 15. // 创建一个对象实例 16. val pt = new Point(10, 20); 17. 18. // 调用pt的move方法,移到一个新的位置 19. pt.move(10, 10);
按下Ctrl+D,执行以上代码。输出结果如下:
x 的坐标点: 20 y 的坐标点: 30
Scala构造器包含1个主构造器和若干个(0个或多个)辅助构造器。
辅助构造器的名称为”this”,每个辅助构造器都必须调用一个此前已经定义的辅助构造器或主构造器。
Scala的每个类都有主构造器。但是,Scala的主构造器和Java有着明显的不同,Scala的主构造器是整个类体,需要在类名称后面罗列出构造器所需的所有参数,这些参数被编译成字段,字段的值就是创建对象时传入的参数的值。
在paste模式下,输入以下代码,并执行:
1. class Counter { 2. private var value = 0 //value用来存储计数器的起始值 3. private var name = "" //表示计数器的名称 4. private var mode = 1 //mode用来表示计数器类型(比如,1表示步数计数器,2表示时间计数器) 5. 6. def this(name: String){ //第一个辅助构造器 7. this() //调用主构造器 8. this.name = name 9. } 10. 11. def this (name: String, mode: Int){ //第二个辅助构造器 12. this(name) //调用前一个辅助构造器 13. this.mode = mode 14. } 15. 16. def increment(step: Int): Unit = { value += step} 17. def current(): Int = {value} 18. def info(): Unit = {printf("Name:%s and mode is %d",name,mode)} 19. } 20. 21. val myCounter1 = new Counter //主构造器 22. myCounter1.info //显示计数器信息 23. myCounter1.increment(1) //设置步长 24. printf("Current Value is: %d",myCounter1.current) //显示计数器当前值 25. 26. val myCounter2 = new Counter("Runner") //第一个辅助构造器,计数器的名称设置为Runner,用来计算跑步步数 27. myCounter2.info //显示计数器信息 28. myCounter2.increment(2) //设置步长 29. printf("Current Value is: %d",myCounter2.current) //显示计数器当前值 30. 31. val myCounter3 = new Counter("Timer",2) //第二个辅助构造器,计数器的名称设置为Timer,用来计算秒数 32. myCounter3.info //显示计数器信息 33. myCounter3.increment(3) //设置步长 34. printf("Current Value is: %d",myCounter3.current) //显示计数器当前值
按下Ctrl+D,执行以上代码。输出结果如下:
Name: and mode is 1 Current Value is: 1 Name:Runner and mode is 1 Current Value is: 2 Name:Timer and mode is 2 Current Value is: 3
2、继承。
Scala继承通过扩展一个基类来快速创建一个新的类。在使用Scala继承时,我们需要注意以下几点:
- 1)重写一个非抽象方法必须使用override修饰符。
- 2)只有主构造函数才可以往基类的构造函数里写参数。
- 3)在子类中重写超类的抽象方法时,不需要使用override关键字。
在paste模式下,输入以下代码:
1. import java.io._ 2. 3. class Point(val xc: Int, val yc: Int) { 4. var x: Int = xc 5. var y: Int = yc 6. 7. def move(dx: Int, dy: Int) { 8. x = x + dx 9. y = y + dy 10. println ("x 的坐标点 : " + x); 11. println ("y 的坐标点 : " + y); 12. } 13. } 14. 15. class Location(override val xc: Int, override val yc: Int, val zc :Int) extends Point(xc, yc){ 16. var z: Int = zc 17. 18. def move(dx: Int, dy: Int, dz: Int) { 19. x = x + dx 20. y = y + dy 21. z = z + dz 22. println ("x 的坐标点 : " + x); 23. println ("y 的坐标点 : " + y); 24. println ("z 的坐标点 : " + z); 25. } 26. } 27. 28. val loc = new Location(10, 20, 15); 29. 30. // 移到一个新的位置 31. loc.move(10, 10, 5);
按下Ctrl+D,执行以上代码。输出结果如下:
x 的坐标点 : 20 y 的坐标点 : 30 z 的坐标点 : 20
Scala重写一个非抽象方法,必须用override修饰符:
在paste模式下,键入以下代码:
1. // 定义一个超类 2. class Person { 3. var name = "" 4. override def toString = getClass.getName + "[name=" + name + "]" 5. } 6. 7. // 使用继承,派生出一个子类 8. class Employee extends Person { 9. var salary = 0.0 10. override def toString = super.toString + "[salary=" + salary + "]" 11. } 12. 13. // 创建对象并对字段赋值 14. val fred = new Employee 15. fred.name = "Fred" 16. fred.salary = 50000 17. println(fred)
按下Ctrl+D,执行以上代码。输出结果如下:
Employee[name=Fred][salary=50000.0]
3、Trait。
Scala Trait(特征) 相当于 Java 的接口,实际上它比接口还功能强大。
与接口不同的是,它还可以定义属性和方法的实现。
一般情况下Scala的类只能够继承单一父类,但是如果是 Trait(特征) 的话就可以继承多个,从结果来看就是实现了多重继承。
在paste模式下,输入以下代码:
1. trait Equal { 2. def isEqual(x: Any): Boolean 3. def isNotEqual(x: Any): Boolean = !isEqual(x) 4. } 5. 6. class Point(xc: Int, yc: Int) extends Equal { 7. var x: Int = xc 8. var y: Int = yc 9. def isEqual(obj: Any) = obj.isInstanceOf[Point] && obj.asInstanceOf[Point].x == x 10. } 11. 12. val p1 = new Point(2, 3) 13. val p2 = new Point(2, 4) 14. val p3 = new Point(3, 3) 15. 16. println(p1.isNotEqual(p2)) 17. println(p1.isNotEqual(p3)) 18. println(p1.isNotEqual(2))
按下Ctrl+D,执行以上代码。输出结果如下:
false true true
4、多态。
在scala继承中,scala向父类的构造器传递参数。
在paste模式下,输入以下代码,并执行:
1. // 声明一个抽象类 2. abstract class Element { 3. def demo(): Unit ={ 4. println("Element's invoked") 5. } 6. } 7. 8. class ArrayElement extends Element{ 9. override def demo(): Unit = { 10. println("ArrayElement's invoked") 11. } 12. } 13. 14. class LineElement extends ArrayElement{ 15. override def demo(): Unit = { 16. println("LineElement's invoked") 17. } 18. } 19. 20. class UniforElement extends Element //没有重写父类方法 21. 22. //参数类型为祖宗类,任何子类实例都可以传递(基类) 23. def invokedDemo(e:Element): Unit ={ 24. e.demo() //多态,在运行时调用相应子类方法 25. } 26. 27. invokedDemo(new ArrayElement) //父类引用指向子类对象 28. invokedDemo(new LineElement) //祖父类引用指向孙类对象 29. invokedDemo(new UniforElement) //没有重写父类方法,所以调用的时候输出祖宗类demo
按下Ctrl+D,执行以上代码。输出结果如下:
ArrayElement’s invoked LineElement’s invoked Element’s invoked
8.3 单例对象和伴生对象
1、单例对象。
在面向对象编程中一个常见的设计模式是定义一个只能被实例化一次的类。一个只能被实例化一次的类叫做“单例(singleton)”。
在paste模式下,输入以下代码,并执行:
1. class Marker(val color: String) { 2. println("Creating " + this) 3. override def toString(): String = "marker color " + color 4. } 5. 6. object MarkerFactory { 7. private val markers = Map("red" -> new Marker("red"), 8. "blue" -> new Marker("blue"), 9. "green" -> new Marker("green")) 10. 11. def getMarker(color: String) = if (markers.contains(color)) markers(color) else null 12. } 13. 14. // 单例函数调用,省略了.(点)符号 15. println(MarkerFactory getMarker "blue") 16. println(MarkerFactory getMarker "blue") 17. println(MarkerFactory getMarker "red") 18. println(MarkerFactory getMarker "red") 19. println(MarkerFactory getMarker "yellow")
其中的MarkerFactory就是一个单例。单例一旦定义完毕,它的名字就表示了这个单例的唯一实例。用单例表示单例可以作为参数传递给函数,就像通常传递实例一样。
按下Ctrl+D,执行以上代码。输出结果如下:
marker color blue marker color blue marker color red marker color red null
2、伴生对象。
与类同名的对象(object)称为伴生对象,类称为这个单例对象的伴生类。类和它的伴生对象必须定义在相同的包下和同一个源文件里。类和它的伴生对象可以互相访问其私有成员。
在paste模式下,输入以下代码,并执行:
1. //伴生类 2. class Student(var name:String,var address:String){ 3. private var phone="110" 4. 5. //直接访问伴生对象的私有成员 6. def infoCompObj() = println("伴生类中访问伴生对象:" + Student.sno) 7. } 8. 9. //伴生对象 10. object Student { 11. private var sno:Int = 100 12. 13. def incrementSno()={ 14. sno += 1 //加1 15. sno //返回sno 16. } 17. } 18. 19. println("单例对象:" + Student.incrementSno()) //单例对象 20. //实例化伴生类 21. val obj = new Student("yy","bj") 22. obj.infoCompObj();
按下Ctrl+D,执行以上代码。输出结果如下:
单例对象:101 伴生类中访问伴生对象:101
8.4 模式匹配
1、简单样例。
Scala 提供了强大的模式匹配机制,应用也非常广泛。
一个模式匹配包含了一系列备选项,每个都开始于关键字 case。每个备选项都包含了一个模式及一到多个表达式。箭头符号 => 隔开了模式和表达式。
在paste模式下,输入以下代码,并执行:
1. def matchTest(x: Int): String = x match { 2. case 1 => "one" 3. case 2 => "two" 4. case _ => "many" 5. } 6. 7. println(matchTest(3))
按下Ctrl+D,执行以上代码。输出结果如下:
many
match 对应 Java 里的 switch,但是写在选择器表达式之后。即: 选择器 match {备选项}。
match 表达式通过以代码编写的先后次序尝试每个模式来完成计算,只要发现有一个匹配的case,剩下的case不会继续匹配。
2、不同数据类型的模式匹配。
在paste模式下,输入以下代码,并执行:
1. def matchTest(x: Any): Any = x match { 2. case 1 => "one" 3. case "two" => 2 4. case y: Int => "scala.Int" 5. case _ => "many" 6. } 7. 8. println(matchTest("two")) 9. println(matchTest("test")) 10. println(matchTest(1)) 11. println(matchTest(6))
按下Ctrl+D,执行以上代码。输出结果如下:
2 many one scala.Int
第一个 case 对应整型数值 1,第二个 case 对应字符串值 two,第三个 case 对应类型模式,用于判断传入的值是否为整型,相比使用isInstanceOf来判断类型,使用模式匹配更好。第四个 case 表示默认的全匹配备选项,即没有找到其他匹配时的匹配项,类似 switch 中的 default。
8.5 Case class定义和使用
1、使用样例类。
使用了case关键字的类定义就是就是样例类(case classes),样例类是种特殊的类,经过优化以用于模式匹配。
在paste模式下,输入以下代码,并执行:
1. // 样例类 2. case class Person(name: String, age: Int) 3. 4. val alice = new Person("Alice", 25) 5. val bob = new Person("Bob", 32) 6. val charlie = new Person("Charlie", 32) 7. 8. for (person <- List(alice, bob, charlie)) { 9. person match { 10. case Person("Alice", 25) => println("Hi Alice!") 11. case Person("Bob", 32) => println("Hi Bob!") 12. case Person(name, age) => 13. println("Age: " + age + " year, name: " + name) 14. } 15. }
按下Ctrl+D,执行以上代码。输出结果如下:
Hi Alice! Hi Bob! Age: 32 year, name: Charlie
在声明样例类时,下面的过程自动发生了:
- 构造器的每个参数都成为val,除非显式被声明为var,但是并不推荐这么做;
- 在伴生对象中提供了apply方法,所以可以不使用new关键字就可构建对象;
- 提供unapply方法使模式匹配可以工作;
- 生成toString、equals、hashCode和copy方法,除非显示给出这些方法的定义。
9. 实验结果及分析:
实验结果运行准确,无误
10. 实验结论:
经过本节实验的学习,通过学习Scala类和对象,进一步巩固了我们的scala基础。
11. 总结及心得体会:
经过本节实验的学习,通过使用Shell的方式,学习了Scala面向对象的基本用法(比如类、Trait)
学习了怎么使用Scala object单例对象,伴生对象,学习了Scala Case class定义和使用,学习了Scala中的模式匹配,进一步巩固了我们的scala基础。