欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

scala之面向对象

程序员文章站 2022-07-12 13:09:41
...

1. 要点

类:

  • 类中的字段自动带有getter方法和setter方法.

  • @BeanProperty注解来生成JavaBean的getXxx/setXxx方法

  • 主构造器, 主构造器的参数直接构成类的字段

  • 辅构造器, 需要提前调用主构造器this()

  • 权限修饰符

    • protected: 修饰类的成员, 只能在子父类中访问
    • private[package]: 在package包和它的子包中可以访问.
  • 抽象类: 相比java多了抽象字段的概念

  • 类型的判断和转换

继承:

  • 关键字extends

  • 覆写方法时必须使用override关键字, 并满足两同两小一大

  • 属性的覆写,可以覆写属性, 多态中属性的引用也是使用子类对象的

    • var只能覆写抽象的var, 有定义的var不能覆写
    • val可以覆写val和没有参数def
  • 只有主构造器才可以调用父类构造器

多态:

  • Scala中, 方法有多态, 属性也有多态!

特质:

  • 特质叠加冲突2种解决方式
    1. 在子类中覆写该方法
    2. 新增父类, 使用菱形继承关系

对象:

  • 用对象存作为单例或存放工具方法

  • 类可以拥有一个同名的伴生对象

    • 可以互相访问私有成员
    • 编译后, 伴生对象中的都为静态成员, 伴生类中的成员都为非静态成员.
  • apply: scala中任何对象都可以像函数一样去调用

  • 对象可以扩展或特质

    • 除显式定义main方法外, 可以用扩展App特质的对象
    • 扩展Enumeration对象来实现枚举

2. 面向对象概念

面向对象就是通过封装, 继承, 多态(面向对象的三大特征)把程序的耦合性降低, 提高程序的可维护性, 可扩展性, 可复用性.

3. 类

定义类

class Demo

属性的默认初始化值

  • 数字 0
  • 布尔值 false
  • 引用型 null

主构造器

类和java一样, 默认有空构造器, 但是一旦有定义构造器, 则不会再默认提供空构造器.

主构造器位置紧跟类名

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

可以增加关键字private将构造器私有化

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

给类定义的所有的属性都是私有. 并且会给这些私有属性自动添加公共的gettersetter, 字节码文件反编译如下:

public class com.demo.scala.User {
  private java.lang.String name; // 属性私有
  private final int age; // 属性私有
  public java.lang.String name();   // 自动添加name属性的getter
  public void name_$eq(java.lang.String);  // 自动添加name属性的setter
  public int age(); // getter
}

@BeanProperty注解可以生成JavaBean的getXxx/setXxx方法

class User2(@BeanProperty var name: String, @BeanProperty val age: Int)

主构造器的3种参数:

  1. class demo(var name: String): name自动成为类的私有属性, 并自动生成setter()getter()方法
  2. class demo(val name: String): 生成getter()方法, 但不生成setter()方法
  3. class demo(name: String):不生成getter()方法, 也不生成setter()方法

构造器重载: 辅助构造器

辅助构造器与主构造器构成重载关系

辅助构造器的首行必须调用主构造器

辅助构造函数的参数, 仅仅是一个普通的参数, 不会成为类的属性

注意: 辅助构造器只能调用主构造, 父类的构造器由主构造器调用

class Demo(name: String){
    // 定义一个无参辅助构造器
	def this() = {
    	// 首行必须调用主构造器
        this("lisi")
	}
    
    // 定义一个无参辅助构造器
    def this(age: Int) = {
        this("lisi")
        this.age = age
	}	
}

scala 的权限修饰符

  1. 用在外部类上:

    • 默认(public, 没有public关键字)

      class A

    • private 只能在当前表使用, 其他地方无法使用

  2. 用在类的内部成员(属性, 方法, 内部类)上:

    • 默认(public, 没有public关键字)

    • protected

      这个限制更加严格. 只能在子父类中访问, 即使在同包中也不能访问

      super.foo();
      
    • private

      只能在当前类中访问

      scala中做了一些改造, 精细化的控制, 可以指定访问的包和它的子包

      private[mode] def speak() = println("speak...")

      mode包和它的子包中可以访问.

抽象类

含有抽象方法或者抽象字段的类为抽象类

abstract class A{
    def f():Unit = {
        println("f.....")
    }
    
    //抽象方法
    def eat(): Unit
    
    //抽象字段
    var age: Int
    val sex: String
}

注意: 相比java多了抽象字段的概念

类型的判断和转换

有时候需要在使用多态后, 需要进行类型转换, 需要先判断类型, 然后再向下转型.

a.isInstanceOf[B]: 判断a是否为B的对象

a.asInstanceOf[B]: a转换B的类型

object Extra1 {
    def main(args: Array[String]): Unit = {
        val a:A = new B
        // java中判断类型:   a instanceof B
        if (a.isInstanceOf[B]) {  // 判断a是否为B的对象
            val b = a.asInstanceOf[B] // a转换B的类型
            b.foo()
        }
    }
}
 
 
class A
class B extends A{
    def foo() = println("foo...")
}

4. 继承

继承和java一样, 是扩展类的方式

面向对象的3大特征: 封装, 继承(单继承), 多态

继承的基本语法

class A
class B extends A

继承的时候构造的处理

  1. 在子类的辅构造器中, 必须先调用自己的主构造, 不能主动去调用父类的构造器.
  2. 只有主构造器才有权力去调用父类的构造器!!!

方法的覆写

java@Overreide注解可以省略. scala中方法覆写需要关键字override, 且不可以省略.

覆写的原则

两同, 两小, 一大

  • 两同: 方法名相同, 参数列表相同

  • 两小: 返回值, 异常类型小(相同或为子类)

  • 一大: 权限相同或更大

    java中的权限: public, protected(同包或子类), friendly(同包中访问), private

    scala中的权限: 默认(public, 但没该关键字), protected(子类), private

属性的覆写

scala中, 属性也可以覆写, 也具有多态!!!

属性覆写的规则:

  1. var只能覆写抽象的var, 有定义的var不能覆写
  2. val可以覆写val和没有参数def

5. 多态

scala中属性也有多态, 多态中属性的引用也是使用子类对象的属性.

6. 特质trait

trait的本质: 字节码之后, 还是接口,

trait是支持多混入

class A extends t1 with t2 with t3...

注意: trait的特性要比接口多, 但最常用的方式还是作为接口来使用.

叠加冲突

多个trait有相同实现好的方法的时,会产生冲突

  1. 解决方案1: 在子类中把冲突的方法覆写

scala之面向对象

  1. 增一个父trait, 成菱形关系, 最终使用最后叠加的那个!!!
    scala之面向对象

    1. 初始化的是, 一个trait最多初始化一次
    2. 初始化的时候是从父开始, 然后从左到右
    3. super.foo()不是真正的找父类, 而是按照叠加的顺序向前找
    4. super[T12].foo() 明确指定这个super应该是哪个类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v4M0s78770///E:\Images\markdown\1587905679513.png)]

.png)]

  1. 新增一个父trait, 成菱形关系, 最终使用最后叠加的那个!!!
    1. 初始化的是, 一个trait最多初始化一次
    2. 初始化的时候是从父开始, 然后从左到右
    3. super.foo()不是真正的找父类, 而是按照叠加的顺序向前找
    4. super[T12].foo() 明确指定这个super应该是哪个类

特质继承类

  1. 方法1

    class A{
        def foo() = {
            println("A... foo")
        }
    }
    
    
    trait B extends A{
        def eat() = {
            println("B ... eat")
            foo()
        }
    }
    
    // 但继承: extends 要么是A要么A的子类
    class C extends A with B
    
  2. 使用自身类型(selftype)

    trait B{
        // s就是A类型的对象
        s: A =>
    
        def eat()= {
            println("B ... eat")
            s.foo()
        }
    }
    

动态叠加

java中所有的继承关系都应该在定义类是确定好.

scala支持特质的动态叠加. 在创建对象的时候, 可以临时只针对整个对象来叠加特质

object Trait5 {
    def main(args: Array[String]): Unit = {
        val h = new H with F1
        h.foo()
    }
}

class H

trait F1 {
    def foo() = println("f1 foo...")
}

特质的字节码文件

Scala将特质编译成JVM的类和接口. 如果特质有具体方法, Scala会创建出一个伴生类, 该伴生类用静态方法存放特质的方法.

例如:

trait ConsoleLogger extends Logger {
	def log(msg: String) { println(msg)}
}

编译后

public interface ConsoleLogger extends Logger { // 生成java接口
    void log(String msg);
}

public class ConsoleLogger$class { // 生成java伴生类
    public static void log(ConsoleLogger self, String msg)
        println(msg);
    
}

7. 对象

单例对象

object 对象名{
    
    def main(args: Array[String]){
        
    }
}

java中的单例有两种, 饿汉式, 懒汉式, 涉及到多线程还需要双重判断

伴生类和伴生对象

一个类可以有一个和其同名的对象, 该对象就称为该类的伴生对象

  1. class的名字和object的名字相同
  2. 可以互相访问对方的私有成员
  3. 伴生类和伴生对象必须在同一个.scala文件中
  4. 将来编译成字节码之后, 站在java的角度, 伴生对象中的都是为成为静态成员, 伴生类中的成员都会成为非静态成员.

apply

函数的特点是可以直接调用, 在scala中, 任何对象也可以像函数一样去调用

// 函数调用:
函数名(参数)

// 对象调用
对象名(参数)

注意:

  1. 其实函数也可以通过apply进行调用. (方法不行), 如果是方法, 先把方法转成函数在使用.

    在Scala语言中, 函数也是对象, 每一个对象都是scala.FunctionN(1-22)的实例

  2. 伴生对象apply, 通常情况是返回***伴生类的对象***, 然后在外面创建对象的时候, 可以省略new

  3. 普通类中的apply, 一般根据具体的业务逻辑来实现.

  4. apply 也可以重载

8. 枚举

继承Enumeration

示例:

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")
}

9. 内部类

类型投影: Outer#Inner

def foo(obj: Outer#Inner)

示例:

两种方式在内部类中调用外部类成员:

  1. 通过Outer.this
  2. 通过自身类型that =>
object InnerDemo1 {
    def main(args: Array[String]): Unit = {
        val outer1 = new Outer("outer1")
        val inner1 = new outer1.Inner
        
        val outer2 = new Outer("outer2")
        val inner2 = new outer2.Inner
        
        inner1.foo(inner1)
        
        inner1.foo(inner2)
    }
}
class Outer(val outerName: String) {
    // 自身类型
    that =>
    val a = 20
    class Inner {
        val a = 10
        
        def foo(obj: Outer#Inner) = {
            println("Inner ... foo")
            println("inner = " + a)
            println("inner = " + this.a)
            // 外部类的对象
            println("outer = " + Outer.this.a)  //通过Outer.this, 调用外部类对象成员方法一
            println("outerName = " + that.outerName) //通过自身类型that =>, 调用外部类对象成员方法方法二
        }
    }
    
}

输出

Inner ... foo
inner = 10
inner = 10
outer = 20
outerName = outer1
Inner ... foo
inner = 10
inner = 10
outer = 20
outerName = outer1
相关标签: scala