Scala(六)之 面向对象

6. 面向对象

Scala的面向对象思想和Java的面向对象思想和概念是一致的。

Scala中语法和Java不同,补充了更多的功能。

6.1 类和对象

6.1.1 方法

1)基本语法

def 方法名(参数列表) [:返回值类型] = { 
	方法体
}

2)案例实操

class Person {

    def sum(n1:Int, n2:Int) : Int = {
        n1 + n2
    }
}

object Person {

    def main(args: Array[String]): Unit = {

        val person = new Person()

        println(person.sum(10, 20))
    }
}

6.1.2 创建对象

1)基本语法

val | var 对象名 [:类型]  = new 类型()

2)案例实操

(1)val修饰对象,不能改变对象的引用(即:内存地址),可以改变对象属性的值。

(2)var修饰对象,可以修改对象的引用和修改对象的属性值

(3)自动推导变量类型不能多态,所以多态需要显示声明

class Person {
    var name: String = "canglaoshi"
}

object Person {

    def main(args: Array[String]): Unit = {
        //val修饰对象,不能改变对象的引用(即:内存地址),可以改变对象属性的值。
        val person = new Person()
        person.name = "bobo"

        // person = new Person()// 错误的

        println(person.name)
    }
}

6.1.3 构造器

和Java一样,Scala构造对象也需要调用构造方法,并且可以有任意多个构造方法。
Scala类的构造器包括:主构造器和辅助构造器

1)基本语法

class 类名(形参列表) {  // 主构造器
   // 类体
   def  this(形参列表) {  // 辅助构造器
   }
   def  this(形参列表) {  //辅助构造器可以有多个...
   }
} 

说明:

(1)辅助构造器,函数的名称this,可以有多个,编译器通过参数的个数及类型来区分。

(2)辅助构造方法不能直接构建对象,必须直接或者间接调用主构造方法。

(3)构造器调用其他另外的构造器,要求被调用构造器必须提前声明。

2)案例实操

(1)如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略。

//(1)如果主构造器无参数,小括号可省略
//class Person (){
class Person {

    var name: String = _

    var age: Int = _

    def this(age: Int) {
        this()
        this.age = age
        println("辅助构造器")
    }

    def this(age: Int, name: String) {
        this(age)
        this.name = name
    }

    println("主构造器")
}

object Person {

    def main(args: Array[String]): Unit = {

        val person2 = new Person(18)
    }
}

6.1.4 构造器参数

1)说明

Scala类的主构造器函数的形参包括三种类型:未用任何修饰、var修饰、val修饰

(1)未用任何修饰符修饰,这个参数就是一个局部变量

(2)var修饰参数,作为类的成员属性使用,可以修改

(3)val修饰参数,作为类只读属性使用,不能修改

2)案例实操

class Person(name: String, var age: Int, val sex: String) {

}

object Test {

    def main(args: Array[String]): Unit = {

        var person = new Person("bobo", 18, "男")

        // (1)未用任何修饰符修饰,这个参数就是一个局部变量
        // printf(person.name)

        // (2)var修饰参数,作为类的成员属性使用,可以修改
        person.age = 19
        println(person.age)

        // (3)val修饰参数,作为类的只读属性使用,不能修改
        // person.sex = "女"
        println(person.sex)
    }
}

6.2封装

封装就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作。Java封装操作如下,

(1)将属性进行私有化

(2)提供一个公共的set方法,用于对属性赋值

(3)提供一个公共的get方法,用于获取属性的值

Scala中的public属性,底层实际为private,并通过get方法(obj.field())和set方法(obj.field_=(value))对其进行操作。所以Scala并不推荐将属性设为private,再为其设置public的get和set方法的做法。但由于很多Java框架都利用反射调用getXXX和setXXX方法,有时候为了和这些框架兼容,也会为Scala的属性设置getXXX和setXXX方法(通过@BeanProperty注解实现)。

6.3 继承

1)基本语法

class 子类名 extends 父类名  { 类体 }

(1)子类继承父类的属性和方法

(2)scala是单继承

2)案例实操

(1)子类继承父类的属性和方法

(2)继承的调用顺序:父类构造器->子类构造器

class Person(nameParam: String) {

    var name = nameParam
    var age: Int = _

    def this(nameParam: String, ageParam: Int) {
        this(nameParam)
        this.age = ageParam
        println("父类辅助构造器")
    }

    println("父类主构造器")
}


class Emp(nameParam: String, ageParam: Int) extends Person(nameParam, ageParam) {

    var empNo: Int = _

    def this(nameParam: String, ageParam: Int, empNoParam: Int) {
        this(nameParam, ageParam)
        this.empNo = empNoParam
        println("子类的辅助构造器")
    }

    println("子类主构造器")
}

object Test {
    def main(args: Array[String]): Unit = {
        new Emp("z3", 11,1001)
    }
}

6.4 抽象属性和抽象方法

6.4.1 抽象属性和抽象方法

1)基本语法

(1)定义抽象类:abstract class Person{} //通过abstract关键字标记抽象类

(2)定义抽象属性:val|var name:String //一个属性没有初始化,就是抽象属性

(3)定义抽象方法:def hello():String //只声明而没有实现的方法,就是抽象方法

案例实操

abstract class Person {

    val name: String

    def hello(): Unit
}

class Teacher extends Person {

    val name: String = "teacher"

    def hello(): Unit = {
        println("hello teacher")
    }
}

2)继承&重写

(1)如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类

(2)重写非抽象方法需要用override修饰,重写抽象方法则可以不加override。

(3)子类中调用父类的方法使用super关键字

(4)子类对抽象属性进行实现,父类抽象属性可以用var修饰;

子类对非抽象属性重写,父类非抽象属性只支持val类型,而不支持var。

​ 因为var修饰的为可变变量,子类继承之后就可以直接使用,没有必要重写

(5)Scala中属性和方法都是动态绑定,而Java中只有方法为动态绑定。

案例实操(对比Java与Scala的重写)

Scala

class Person {
    val name: String = "person"

    def hello(): Unit = {
        println("hello person")
    }
}

class Teacher extends Person {

    override val name: String = "teacher"

    override def hello(): Unit = {
        println("hello teacher")
    }
}

object Test {
    def main(args: Array[String]): Unit = {
        val teacher: Teacher = new Teacher()
        println(teacher.name)
        teacher.hello()

        val teacher1:Person = new Teacher
        println(teacher1.name)
        teacher1.hello()
    }
}

Java

class Person {

    public String name = "person";
    public void hello() {
        System.out.println("hello person");
    }

}
class Teacher extends Person {

public String name = "teacher";

    @Override
    public void hello() {
        System.out.println("hello teacher");
    }

}
public class TestDynamic {
public static void main(String[] args) {

        Teacher teacher = new Teacher();
        Person teacher1 = new Teacher();

        System.out.println(teacher.name);
        teacher.hello();

        System.out.println(teacher1.name);
        teacher1.hello();
    }
}

image-20201025204657563

6.4.2 匿名子类

1)说明

Java一样,可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类。

2)案例实操

abstract class Person {

    val name: String

    def hello(): Unit
}

object Test {

    def main(args: Array[String]): Unit = {

        val person = new Person {

            override val name: String = "teacher"

            override def hello(): Unit = println("hello teacher")
        }
    }
}

6.5 单例对象(伴生对象)

Scala语言是完全面向对象的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。但是为了能够和Java语言交互(因为Java中有静态概念),就产生了一种特殊的对象来模拟类对象,该对象为单例对象。若单例对象名与类名一致,则称该单例对象这个类的伴生对象,这个类的所有“静态”内容都可以放置在它的伴生对象中声明

6.5.1 单例对象语法

1)基本语法

object Person{
	val country:String="China"
}

2)说明

(1)单例对象采用object关键字声明

(2)单例对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。

(3)单例对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。

3)案例实操

//(1)伴生对象采用object关键字声明
object Person {
    var country: String = "China"
}

//(2)伴生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。
class Person {
    var name: String = "bobo"
}

object Test {
    def main(args: Array[String]): Unit = {
        //(3)伴生对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。
        println(Person.country)
    }
}

6.5.2 apply方法

1)说明

(1)通过伴生对象的apply方法,实现不使用new方法创建对象。

(2)如果想让主构造器变成私有的,可以在()之前加上private。

(3)apply方法可以重载。

(4)Scala中obj(arg)的语句实际是在调用该对象的apply方法,即obj.apply(arg)。用以统一面向对象编程和函数式编程的风格。

(5)当使用new关键字构建对象时,调用的其实是类的构造方法,当直接使用类名构建对象时,调用的其实时伴生对象的apply方法。

2)案例实操

object Test {

    def main(args: Array[String]): Unit = {

        //(1)通过伴生对象的apply方法,实现不使用new关键字创建对象。
        val p1 = Person()
        println("p1.name=" + p1.name)

        val p2 = Person("bobo")
        println("p2.name=" + p2.name)
    }
}

//(2)如果想让主构造器变成私有的,可以在()之前加上private
class Person private(cName: String) {
    var name: String = cName
}

object Person {

    def apply(): Person = {
        println("apply空参被调用")
        new Person("xx")
    }

    def apply(name: String): Person = {
        println("apply有参被调用")
        new Person(name)
}
//注意:也可以创建其它类型对象,并不一定是伴生类对象
}

6.6 特质(Trait)

Scala语言中,采用特质trait(特征)来代替接口的概念,也就是说,多个类具有相同的特质(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明。

Scala中的trait中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质。这种感觉类似于Java中的抽象类。

Scala引入trait特征,第一可以替代Java的接口,第二个也是对单继承机制的一种补充。

6.6.1 特质声明

1)基本语法

trait 特质名 {
	trait主体
}

2)案例实操

trait PersonTrait {

    // 声明属性
    var name:String = _

    // 声明方法
    def eat():Unit={

    }

    // 抽象属性
    var age:Int
    
    // 抽象方法
    def say():Unit
}
通过查看字节码,可以看到特质=抽象类+接口

6.6.2 特质基本语法

一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了extends关键字,如果有多个特质或存在父类,那么需要采用with关键字连接。
1)基本语法:
没有父类:class 类名 extends 特质1 with 特质2 with 特质3 …
有父类:class 类名 extends 父类 with 特质1 with 特质2 with 特质3…

2)说明

(1)类和特质的关系:使用继承的关系。

(2)当一个类去继承特质时,第一个连接词是extends,后面是with。

(3)如果一个类在同时继承特质和父类时,应当把父类写在extends后。

3)案例实操

(1)特质可以同时拥有抽象方法和具体方法

(2)一个类可以混入(mixin)多个特质

(3)所有的Java接口都可以当做Scala特质使用

(4)动态混入:可灵活的扩展类的功能

(4.1)动态混入:创建对象时混入trait,而无需使类混入该trait

(4.2)如果混入的trait中有未实现的方法,则需要实现

trait PersonTrait {

    //(1)特质可以同时拥有抽象方法和具体方法
    // 声明属性
    var name: String = _

    // 抽象属性
    var age: Int

    // 声明方法
    def eat(): Unit = {
        println("eat")
    }

    // 抽象方法
    def say(): Unit
}

trait SexTrait {
    var sex: String
}

//(2)一个类可以实现/继承多个特质
//(3)所有的Java接口都可以当做Scala特质使用
class Teacher extends PersonTrait with java.io.Serializable {

    override def say(): Unit = {
        println("say")
    }

    override var age: Int = _
}


object TestTrait {

    def main(args: Array[String]): Unit = {

        val teacher = new Teacher

        teacher.say()
        teacher.eat()

        //(4)动态混入:可灵活的扩展类的功能
        val t2 = new Teacher with SexTrait {
            override var sex: String = "男"
        }

        //调用混入trait的属性
        println(t2.sex)
    }
}

6.6.3 特质叠加

由于一个类可以混入(mixin)多个trait,且trait中可以有具体的属性和方法,若混入的特质中具有相同的方法(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。冲突分为以下两种:

第一种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法。

img

第二种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait继承自相同的trait(TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala采用了****特质叠加****的策略。

img

所谓的****特质叠加****,就是将混入的多个trait中的冲突方法叠加起来,案例如下,

trait Ball {
   def describe(): String = {
      "ball"
   }
}

trait Color extends Ball {
   override def describe(): String = {
      "blue-" + super.describe()
   }
}

trait Category extends Ball {
   override def describe(): String = {
      "foot-" + super.describe()
   }
}

class MyBall extends Category with Color {
   override def describe(): String = {
      "my ball is a " + super.describe()
   }
}

object TestTrait {
   def main(args: Array[String]): Unit = {
      println(new MyBall().describe())
   }
}

结果如下:

image-20201025205705619

6.6.4 特质叠加执行顺序

思考:上述案例中的super.describe()调用的是父trait中的方法吗?

当一个类混入多个特质的时候,scala会对所有的特质及其父特质按照一定的顺序进行排序,而此案例中的super.describe()调用的实际上是排好序后的下一个特质中的describe()方法。,排序规则如下:

image-20201025205741812

结论:

(1)案例中的super,不是表示其父特质对象,而是表示上述叠加顺序中的下一个特质,即,MyClass中的super指代Color,Color中的super指代Category,Category中的super指代Ball。

(2)如果想要调用某个指定的混入特质中的方法,可以增加约束:super[],例如super[Category].describe()。

6.6.5 特质自身类型

1)说明

自身类型可实现依赖注入的功能。

2)案例实操

class User(val name: String, val age: Int)

trait Dao {
   def insert(user: User) = {
      println("insert into database :" + user.name)
   }
}

trait APP {
   _: Dao =>
 
   def login(user: User): Unit = {
      println("login :" + user.name)
      insert(user)
   }
}

object MyApp extends APP with Dao {
   def main(args: Array[String]): Unit = {
      login(new User("bobo", 11))
   }
}

6.6.6特质和抽象类的区别

1.优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类。
2.如果你需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数,而特质不行(有无参构造)。

6.7 扩展

6.7.1 类型检查和转换

1)说明

(1)obj.isInstanceOf[T]:判断obj是不是T类型。

(2)obj.asInstanceOf[T]:将obj强转成T类型。

(3)classOf获取对象的类名。

2)案例实操

class Person{

}

object Person {
    def main(args: Array[String]): Unit = {

        val person = new Person

        //(1)判断对象是否为某个类型的实例
        val bool: Boolean = person.isInstanceOf[Person]

        if ( bool ) {
            //(2)将对象转换为某个类型的实例
            val p1: Person = person.asInstanceOf[Person]
            println(p1)
        }

        //(3)获取类的信息
        val pClass: Class[Person] = classOf[Person]
        println(pClass)
    }
}

6.7.2 枚举类和应用类

1)说明

枚举类:需要继承Enumeration

应用类:需要继承App

2)案例实操

object Test {
    def main(args: Array[String]): Unit = {

        println(Color.RED)
    }
}

// 枚举类
object Color extends Enumeration {
    val RED = Value(1, "red")
    val YELLOW = Value(2, "yellow")
    val BLUE = Value(3, "blue")
}

// 应用类
object Test20 extends App {
    println("xxxxxxxxxxx");
}

6.7.3 Type定义新类型

1)说明

使用type关键字可以定义新的数据数据类型名称,本质上就是类型的一个别名

2)案例实操

object Test {

    def main(args: Array[String]): Unit = {
        
        type S=String
        var v:S="abc"
        def test():S="xyz"
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值