(三)Scala面向对象编程
Scala面向对象编程
面向对象编程的三个特性:
- 封装
1) 属性 / 字段 (数据)
2)方法(对数据的操作)- 继承
涉及到父类 & 子类的关系;
对父类中的属性、方法进行重写;- 多态
父类引用指向子类对象【抽象类其实就是多态的一种体现】
1.1. Scala中常用的类定义
- class
- object
- case class 【 Scala中apply的应用】
- case object 【 Scala中apply的应用】
- trait
- Scala中类的定义,可以用class也可以用object
- 如果在scala中需要有主入口main方法,只能定义为object【特殊情况,如果extends APP,就可以不用main方法】
- class 类1{} 为 object 类1{}的伴生类,object类1{} 为 class 类1{}的伴生对象
具体文章参考:【 Scala中apply的应用】
class 类名{
//定义属性
//定义方法
}
object 类名{
//定义属性
//定义方法
}
- 特殊情况:如果extends App,就可以不用main方法
object AppTest extends App {
println("不用main也可以输出")
util.Properties.setProp("scala.time","true") // 打印scala运行数据 ,输出结果:[total 225ms]
}
使用命令:scalac xxx.scala编译源码,生成.class文件;
使用命令:javap -p xxx,反编译class文件
import scala.beans.BeanProperty
class User{
@BeanProperty val name = "Scala"
val age = 18
var address = "成都"
private val money = 10000L
def eat() = {
println(s"$name : $age")
}
def printMoney() = {
println(s"money = ${money}")
}
}
--------------反编译class文件后,格式如下------------------
public class com.maggie.scalaDemo.User {
private final java.lang.String name;
private final int age;
private java.lang.String address;
private final long money;
public java.lang.String name(); // 相当于getName()方法
public int age();
public java.lang.String address();
public void address_$eq(java.lang.String); // var修饰的变量,还多了address_$eq()方法,相当于setAddress()方法
private long money();
public void eat();
public void printMoney(); // printMoney()方法
public java.lang.String getName(); // @BeanProperty修饰的,就能看得setter和getter方法
public com.maggie.scalaDemoUser(); // User 的无参构造器
}
--------------调用User对象---------------------
object UserTest{
def main(args: Array[String]): Unit = {
val user = new User() // 实例化了一个User对象
// user.name = "Maggie" // 会报错,val定义的变量,不允许修改
user.address = "chengdu"
println("user.address = " + user.address) // 输出结果:user.address = chengdu
user.address_$eq("成都市")
println("user.address = " + user.address) // 输出结果:user.address = 成都市
//如果想获取到money,只能通过printMoney()方法
user.printMoney() // 输出结果:money = 10000
}
}
1.2. scala主从构造器
- 主构造器(一般只有一个)
1)如果定义的Person类不带属性,则默认生成无参构造器
2)无参构造器不一定是主构造器;
3)主构造器中的参数个数需要和主构造器属性个数&类型一致 - 附属构造器
1)可以定义多个附属构造器
2)辅助构造器的名称必须为 this;
3)每个辅助构造器必须以主构造器或其他的辅助构造器的调用开始。
4)如果是class Person(class : 自定义类) 则可以添加无参构造器;
如果是class Person(val name:String)这种,则无法添加无参构造器
class Person(val name:String, val age:Int){
println("功能类似于Java中的静态代码块 static{}, class Person enter")
var gender:String = _
// 1)辅助构造器的名称必须为 this;
def this(name:String, age:Int, gender:String) ={
// 该类有个默认主构造器:Person(name:String,age:int)
// 2)每个辅助构造器必须以主构造器或其他的辅助构造器的调用开始。
this(name, age)
this.gender = gender
}
println("class Person over")
}
// 调用Person类
object PersonTest{
def main(args: Array[String]): Unit = {
val person = new Person("张三" , 21)
println(person.name + ":" + person.age) // 调用的时候才输出
}
}
/**
* ======输出结果(顺序--从上到下执行,调用类,最后执行构造器/方法中的输出)====:
* 功能类似于Java中的静态代码块 static{}, class Person enter
* class Person over
* 张三:21
*/
----------Person.class 反编译后:javap -p .\Person.class----------
public class com.maggie.scalaDemo.Person {
private final java.lang.String name;
private final int age;
private java.lang.String gender;
public static void main(java.lang.String[]);
public java.lang.String name();
public void gender_$eq(java.lang.String);
public com.maggie.scalaDemo.Person(java.lang.String, int); // 主构造器,如果定义的Person类不带属性,则默认生成无参构造器
public com.maggie.scalaDemo.Person(java.lang.String, int, java.lang.String); // 附属构造器
}
// 参看Scala中的SparkContext类
1.3. Scala中方法重写override
- 重写父类中的val属性或者方法,使用override关键字
// 父类Person
class Person(val name:String, val age:Int) {
println("class Person enter......")
var gender: String = "male"
val address : String = "成都"
def this(name: String, age: Int, gender: String) = {
this(name, age)
this.gender = gender
println("我是Person类中3个参数的构造方法")
}
println("class Person ......over")
}
// 子类Student
class Student(name:String, age:Int, val major:String) extends Person(name,age){
println("class Student enter......")
override def toString: String = name + "---" + age + "---" + major // [override重写了Person中的toString方法]
// override var gender = "female" 或者 override val gender = "female" 【var修饰的变量不能被重写,均报错】
override val address = "成都市" // 子类重写父类属性
println("class Student ......over")
}
// 调用Student类
object StudentTest{
def main(args: Array[String]): Unit = {
val stu = new Student("张三" , 21, "Scala")
println(stu.toString) // 【(1)Student中重写toString方法前后输出结果不一致】
println(stu.gender) // 【Student中var修饰的变量不能被重写,均报错】
println(stu.address) // 【Student中重写address属性前后输出结果不一致】
}
}
/**
* ======输出结果(--从上到下执行,调用父类,再调用子类,最后执行构造器/方法中的输出)====:
* class Person enter......
* class Person ......over
* class Student enter......
* class Student ......over
* com.maggie.scalaDemo.Student@71c7db30 【(1)Student中重写toString方法前输出结果】
* 张三---21---Scala 【(1)Student中重写toString方法后输出结果】
* male
* 成都 【重写前输出结果】 成都市 【重写后输出结果】
*/
1.4. Scala抽象类abstract & extends &override
- 抽象类中有一个或者多个方法没有完整的定义
- 抽象类中,属性也可以申明为抽象字段
- 抽象类是不能直接被实例化,可以通过子类去实例化(extends)
1)抽象属性可以写override也可以不写override,都对抽象属性进行重写
2)抽象类中的属性如果已经赋初始值中,子类重写属性的时候,必须要写override关键字, 否则报错
// 定义的抽象类
abstract class AbstractClass{
def speak()
val name:String
val address:String
val test:String = "test"
}
// 实现抽象类,使用关键字extends,重写方法/属性,使用关键字override
class SubClass extends AbstractClass{
override def speak(): Unit = println("我是实现的子类重写的方法。。")
override val name: String = "我是SubClass"
val address:String = "成都市" // 抽象属性可以写override也可以不写override,都对抽象属性进行重写
override val test:String = "newTest1" // 必须要写override关键字, 否则报错
}
// 测试抽象类子类
object AbstractClassTest {
def main(args: Array[String]): Unit = {
println("---------方法一:子类继承抽象类---------")
val subclass = new SubClass
subclass.speak()
println(subclass.name)
println(subclass.address)
println(subclass.test)
println("---------方法二:匿名子类---------")
val abStr = new AbstractClass {
override def speak(): Unit = println("我是匿名子类重写的方法。。")
override val name: String = "我是abStr"
val address:String = "四川省" // 抽象属性可以写override也可以不写override,都对抽象属性进行重写
override val test:String = "newTest2" // 必须要写override关键字, 否则报错
}
abStr.speak()
println(abStr.name)
println(abStr.address)
println(abStr.test)
}
}
1.5. Scala中packge object
-
Scala的import还支持导包时候取别名
- import com.gargantua.{HomeWork => HW}
-
当Scala要调用Java API,而Scala的语法无能识别Java的类型,使用隐式转换需要导入 【scala.collection.JavaConverters._ 】再使用 asScala 转为scala的集合
import scala.collection.JavaConverters._
List list = new ArrayList()
for (e <- list.asScala) {
// ...
}
- 在当前包下可以唯一创建一个类名和包名相同的 packge object
- 对于使用 packge object 中的方法,可以不用导包
package com
package object Person{
// 使用 packge object 中的方法,可以不用导包
}
1.6. Scala中type关键字
- Scala 使用 classOf() 返回运行时类型
- 可以使用 type 关键字定义新的数据类型名称 (换了个名字)
type S = String // 重写定义了一个S类型,为String重命名为S
val str:S = ""
1.7. Scala多态
- 父类引用指向子类对象
- val student = new Person()
1.8. Scala接口Trial
- Scala没有interface接口,通过定义trait实现接口(javap 命令解析trait发现就是interface)
- 继承/实现 ,都是用extends,多个接口放在with 后面
class Dog extends Animal with MyLogging with MyException {
// 使用需要注意 Animal\MyLogging\MyException的顺序
// super.method 只会匹配到最后一个with父类中的方法
// super[MyLogging].method 可以指定特定父类
}
1.9. Scala泛型
泛型在Scala和Java中类似,用于类或者方法用于指定任意类型 或者一种类型的参数,参数在使用的时候才会被确定,泛型可以有效的增强程序的灵活性,使用泛型的目的是可以使得类或者方法具有更高的通用性,或者作为一种规范。
调用的时候指定类型即可。 其中的T* 表示的可变参数
- 泛型的规则:
- 1)泛型的上边界和下边界
- 上边界(上限): 表示泛型的类型必须是 某种类型的子类,表示为 <:
- 下边界(下限):表示泛型的类型必须是 某种类型的父类,表示为 >:
【下限其实没有做实际的控制,与没有限制一样】
- 2)协变和逆变
- 协变: 在类型参数的前面加上一个 +
- 逆变: 在类型参数前面加上一个 -
- 1)泛型的上边界和下边界
1.10. Ordering & Ordered(排序)
- Scala中用于比较的两个类:Ordered、Ordering
- Ordered 实现了java.lang.Comparable接口,对Comparable进行了扩展,更灵活,用气量更方便
- Ordering 实现了java的 Comparator接口,同时对Comparator进行了扩展
Ordering : trait Ordering[T] extends Comparator[T]
Ordered : trait Ordered[T] extends java.lang.Comparable[T]Comparator : public int compare(Obj o1, Obj o2)
Comparable : public int compareTo(Obj obj)
- <% 视图界定