scala学习笔记(oop)

类与对象

定义类

scala 语法中,类并不声明为 public,所有这些类都具有公有可见性(即默认就是 public)

类的属性

Scala 中声明一个属性,必须显示的初始化,然后根据初始化数据的类型自动推断,属性类型可 以省略(这点和 Java 不同)。

如果赋值为 null,则一定要加类型,因为不加类型, 那么该属性的类型就是Null 类型

如果在定义属性时,暂时不赋值,也可以使用符号_(下划线),让系统分配默认值

创建对象

scala 在声明对象变量时,可以根据创建对象的类型自动推断,所以类型声明可以省略,但当类型和后面 new 对象类型有继承关系即多态时,就必须写

构造器

scala 类的构造器包括:主构造器,辅助构造器

class Person(inName:String,inAge:Int) {//主构造器
  var name:String = inName
  var age:Int = inAge
  age+=10


  override def toString = s"Person($name, $age)"

  def this(name: String)= this(name,10)//辅助构造器必须在第一行显示调用主构造器
  def this()= this("未命名",10)//辅助构造器必须在第一行显示调用主构造器


}

object Person{
  def apply(inName: String, inAge: Int): Person = new Person(inName, inAge)
  def apply(inName:String):Person = new Person(inName)
  def apply():Person = new Person()


  def main(args: Array[String]): Unit = {
    println(apply("张三").toString)
    println(apply().toString)
  }


}

scala构造器没有返回值,作用是完成对象的初始化

主构造器声明时要放在类名之后,会执行类定义中的所有语句

如果主构造器无参数,小括号可省略

若想私有主构造器,可在()前加上private,此时用户只能通过辅助构造器构造对象
class Person private(){}

辅助构造器的声明不能和主构造器的声明一致,会发生错误(即构造器名重复)

构造器参数

  1. 若主构造器形参未用任何修饰符修饰,则该参数为局部变量
  2. 若用val声明,则将该参数作为类的私有只读属性使用
  3. 如果参数使用 var 关键字声明,那么那么 Scala 会将参数作为类的成员属性使用,并会提供属性
    对应的 xxx()[类似 getter]/xxx_$eq()[类似 setter]方法,即这时的成员属性是私有的,但是可读写。
class Worker(inName: String) {
  var name:String = inName
}
class Worker2(val inName:String){
  var name:String = inName
}
class Worker3(var inName:String){
  var name:String = inName
}
object conDemo1{
  def main(args: Array[String]): Unit = {
    val worker = new Worker("hao")
    worker.name//不能访问inName
    val worker2 = new Worker2("hao")
    worker2.inName//可以访问inName
    val worker3 = new Worker3("hao")
    worker3.inName="jie"//可读可写
  }
}

Bean属性

class Worker3(var inName:String){
  @BeanProperty
  var name:String = inName
}
object conDemo1{
 def main(args: Array[String]): Unit = {
 val worker3 = new Worker3("hao")
    worker3.getName
  }
}

在Scala 字段加@BeanProperty 时,这样会自动生成规范 的 setXxx/getXxx 方法。这时可以使用 对象.setXxx() 和 对象.getXxx() 来调用属性。

加载对象的过程

new 以后,加载类的信息,在内存开辟空间,父类主副构造器,主构造器,辅助构造器,附地址值

作用(与Java比较)

java包的作用

1)区分相同名字的类
2) 当类很多时,可以很好的管理类
3) 控制访问范围

scala包的作用

  1. 区分相同名字的类
  2. 当类很多时,可以很好的管理类
  3. 控制访问范围
    4)可以对类的功能进行扩展

Scala 中包名和源码所在的系统文件目录结构要可以不一致,但是编译后的字节码文件路径和包名 会保持一致(这个工作由编译器完成)。

Scala 会自动引入的常用包:
java.lang.* scala Rredef

scala打包形式

package com.atguigu{}

表示我们创建了包 com.atguigu ,在{}中 // 我们可以继续写它的子包 scala //com.atguigu.scala, 还可以写类,特质 trait,还可以写 object

sacla 支持,在一个文件中,可以同时创建多个包,以及给各个包创建类,trait 和 object(支持嵌套,建议嵌套不超过3层)

package cn.kgc.fu{
  package zi{
    class packageDemo{
      val name="hao"
      def play: String => Unit = (message:String)=>println(this.name+" "+message)
         
    }
    object packageDemo{
      def main(args: Array[String]): Unit = {
        println("ok")
      }
    }  
  }
    package zi2{
        
    }
}

包的作用域

作用域原则:scala中可以直接向上访问:scala中子包可以直接访问父包的内容(java中需要使用import),大括号表示包的作用域,当子包与父包类重名时,采用就近原则,需要指定使用某个类,带上包名
父包访问子包中的内容是,需要import

包名可以相对也可以绝对,一般情况下使用相对,当包名产生冲突时,采用绝对路径

包对象

为了弥补“包中不能包含函数/方法或变量的定义”这一局限,scala提高了包对象的概念

如何使用包对象

1. 在包中直接写方法,或者定义变量,就错误==>使用包对象的技术来解决 
2. package object scala 表示创建一个包对象 scala, 他是 com.atguigu.scala这个包对应的包对象 
3. 每一个包都可以有一个包对象 
4. 包对象的名字需要和子包一样 
5. 在包对象中可以定义变量,方法 
6. 在包对象中定义的变量和方法,就可以在对应的包中使用 
7. 在底层这个包对象会生成两个类 package.class 和 package$.class
package object pac {
    var name = "jie"
    def say=()=>println("say me")
}
package cn.kgc.hao.pac

class Pac {
class Person{
  val name = "Nick"
  def play = (message:String)=>println(s"${this.name} $message")

}
class User{
  println("name="+name)
  say()
}
  object Test10{
    def main(args: Array[String]): Unit = {
      say
    }
  }
}

每个包都可以有一个包对象,可以在父包中定义它
包对象名称需要和报名一致,一般用来对包的功能补充

包的可见性

  1. 当属性访问权限为默认时,从底层看属性是 private 的,但是因为提供了 xxx_$eq()[类似setter]/xxx()[类似 getter] 方法,因此从使用效果看是任何地方都可以访问)

  2. 当方法访问权限为默认时,默认为public

  3. private 为私有权限,只在类的内部和伴生对象中可用

  4. protected 为受保护权限,scala 中受保护权限比 Java 中更严格,只能子类访问,同包无法访问

  5. 在 scala 中没有 public 关键字,即不能用 public 显式的修饰属性和方法。

  6. 包访问权限

    private[x] //该成员除了x可见,对其他都为私有
    protected[x]	//类比上一句,差不多
    

包的引入

Scala 会自动引入的包:java.lang.* scala Predef

  1. Scala中,import可以出现在任何地方,不限于文件顶部,作用范围是包含该语句的块末尾

    需要引入时再引入,提高效率

  2. java中导入包下所有类采用*,scala中使用_

  3. import支持选取器
    import scala.collection.mutable.{HashMap, HashSet}

  4. import类支持重命名
    import scala.collection.mutable.{HashMap=>ScalaHashMap}

  5. 导入包时,若存在冲突的类,且该类不会用到,可直接隐藏该类
    import java.util.{HashMap=>_,_} //导入java.util中的若有类,忽略HashMap

三大特性

封装

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

封装的理解和好处

  1. 隐藏实现细节
  2. 提可以对数据进行验证,保证安全合理
  3. 同时可以加入业务逻辑

如何体现封装

  1. 对类中的属性进行封装
  2. 通过成员方法,包实现封装

注意点

  1. scala中,属性声明为var时,本身自带getter/setter方法,属性声明为private,方法也为private,属性访问控制权限为默认,方法也为public

  2. 因此我们如果只是对一个属性进行简单的 set 和 get ,只要声明一下该属性(属性使用默认访问
    修饰符) 不用写专门的 getset,默认会创建,访问时,直接对象.变量。

  3. dog.food 直接访问属性,其实底层仍然是访问的方法,

由于上面的特性,有些框架进行反射时,也支持对属性的直接访问

继承

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

scala类型检查和转换

classOf[String]就如同 Java 的 String.class 。

obj.isInstanceOf[T]就如同 Java 的 obj instanceofT 判断 obj 是不是 T 类型。

obj.asInstanceOf[T]就如同 Java 的(T)obj 将 obj 强转成 T 类型。

scala超类构造器

  1. 只有主构造器可以调用父类的构造器。辅助构造器不能直接调用父类的构造器。在 Scala 的构造
    器中,你不能调用 super(params)

    class Fu(name:String,age:Int) {
    }
    class Zi(name:String)extends Fu(name,1){//正确的调用方式
      //super(name) 	不支持该语法
    
      //def this()=super("abc")	辅助构造器中不能调用父类构造器
    }
    

scala重写

注意事项:
1)def只能重写另一个 def(即:方法只能重写另一个方法)
2) val 只能重写另一个 val 属性 或 重写不带参数的 def
3) var 只能重写另一个抽象的 var 属性

  1. 抽象的字段(属性):就是没有初始化的字段(属性)
  2. 当一个类含有抽象属性时,则该类需要标记为 abstract
  3. 对于抽象的属性,在底层不会生成对应的属性声明,而是生成两个对应的抽象方法(name name_$eq)

在子类中重写父类的抽象属性,本质上是实现抽象方法(overide可写可不写)

scala抽象类

通过 abstract 关键字标记不能被实例化的类。方法不用标记 abstract,只要省掉方法体
即可。抽象类可以拥有抽象字段,抽象字段/属性就是没有初始值的字段

抽象类的使用
  1. 抽象类不能被实例(但实例化时,默认实现所有抽象)
  2. 抽象类中可以没有抽象方法
  3. 有抽象方法和抽象属性的类一定是抽象类
  4. 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法和抽象属性,除非它自己也声
    明为 abstract 类
  5. 抽象方法和抽象类需要被重写或实现,所以是不能用private和final修饰

匿名子类

new抽象类时,直接实现/重写抽象方法、属性(类似于Java)

abstract class  Fu(name:String,age:Int) {
  abstract var birthday:String
  abstract def play():Unit
}
import extendtest.Fu
    val fu = new Fu("zhazha",3) {
      override var birthday: String = "11"

      override def play(): Unit = ""
    }

静态

scala是完全面向对象的语言,在scala中没有静态的概念
为了能够与Java语言交互,产生了一个特殊的对象来模拟类对象:伴生对象,类的所有静态内容都可以放置在它的伴生对象中声明和调用

伴生对象

伴生对象的实现机制,伴生对象在底层是个final修饰的类,类中有一个静态属性MODULE$指向本类对象public static final MODULE$ =new 本类对象

  1. Scala 中伴生对象采用 object 关键字声明,伴生对象中声明的全是 "静态"内容,可以通过伴生
    对象名称直接调用。​
  2. 伴生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。
  3. 伴生对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问
  4. 从语法角度来讲,所谓的伴生对象其实就是类的静态方法和成员的集合
  5. 从技术角度来讲,scala 还是没有生成静态的内容,只不过是将伴生对象生成了一个新的类,实 现属性和方法的调用。
  6. 从底层原理看,伴生对象实现静态特性是依赖于 public static final MODULE$ 实现的。
  7. 伴生对象的声明应该和伴生类的声明在同一个源码文件中(如果不在同一个文件中会运行错
    误!),但是如果没有伴生类,也就没有所谓的伴生对象了,所以放在哪里就无所谓了。
  8. 如果 class A 独立存在,那么A就是一个类, 如果 object A 独立存在,那么A就是一个"静态
    "性质的对象[即类对象], 在 object A中声明的属性和方法可以通过 A.属性 和 A.方法 来实现调用

apply方法

def apply(pName: String): Pig = new Pig(pName)

特质

scala中没有接口(scala是纯面向对象的语言,接口不属于面向对象的范畴),采用trait(特征)来代替接口的概念

trait:多个类具有相同的特征时,就可以将这个特征独立出来,采用关键字 trait声明。

在 scala 中,java 的接口都可以当做 trait 来使用

trait可以理解为抽象类+接口,可以同时拥有抽象方法和具体方法,一个类可以实现/继承多个
特质。

  • trait采用extends关键字,当存在多个特质或父类时,使用with连接

    //没有父类
    class 类名 extends 特质 1 with 特质 2 with 特质 3 ..
    //有父类
    class 类名 extends 父类 with 特质 1 with 特质 2 with 特质 3
    
  • 除了在类声明时继承特质,还可以在构建对象时混入特质,扩展目标类的功能

    • 此种方式也可以应用于对抽象类功能进行扩展
    • 动态混入是 Scala 特有的方式(java 没有动态混入),可在不修改类声明/定义的情况下,扩展类的功能,非常的灵活,耦合性低 。
    • 动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能。
    • 同时要注意动态混入时,如果抽象类有抽象方法,如何混入

我们执行一个动态混入对象的方法,其执行顺序是怎样的?

顺序是,(1)从右到左开始执行 , (2)当执行到 super 时,是指的左边的特质 (3) 如果左边没有特质
了,则 super 就是父特质

如果想要调用具体特质的方法,可以指定:super[特质].xxx(…).其中的泛型必须是该特质的
直接超类类型

  • 父接口:即该特质中既有抽象方法,又有非抽象方法

特质的构造顺序

声明类的同时混入特质

  1. 调用当前类的超类构造器

  2. 第一个特质的父特质构造器

  3. 第一个特质构造器

  4. 第二个特质构造器的父特质构造器, 如果已经执行过, 就不再执行

  5. 第二个特质构造器

  6. …重复 4,5 的步骤(如果有第 3 个,第 4 个特质)

  7. 当前类构造器

声明对象时混入特质

  1. 调用当前类的超类构造器

  2. 当前类构造器

  3. 第一个特质构造器的父特质构造器

  4. 第一个特质构造器.

  5. 第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行

  6. 第二个特质构造器

  7. …重复 5,6 的步骤(如果有第 3 个,第 4 个特质)

  8. 当前类构造器

两种方式对构造顺序的影响

第 1 种方式实际是构建类对象, 在混入特质时,该对象还没有创建。
第 2 种方式实际是构造匿名子类,可以理解成在混入特质时,对象已经创建了

扩展类的特质

  • 特质也是可以继承类的,用来拓展该特质的功能
  • 所有混入这个特质的类,会自动成为这个特质所继承超类的之类
  • 万一混入这个特质的类,已经继承了另一个类A,则要求A必须是特质超类的子类,否则会发生多继承现象,错误

特质的自身类型

类似于特性继承类,但限制了特质混入的类的类型

class Eat{
  def eat():Unit= println("吃东西")
}

trait EatBone  {//特质,吃骨头
  this:Eat=> //明确就是Eat,要求混入该特质的类也是Eat
  override def eat(): Unit = this.eat()

}

class Dog extends Eat with EatBone

嵌套类

内部类访问外部类信息

  1. 通过外部类对象访问
    外部类名.this.属性名 外部类名.this相当于这个外部类的一个实例
  2. 通过外部类别名访问
    外部类名别名.属性名
//方式1: 外部类名.this.属性名
class ScalaOuterClass {
  class ScalaInnerClass{
    def fun(): Unit ={
      var inName=ScalaOuterClass.this.name
    }
  }
  var name= "hao"
  private var sal = 100.23
}

//方式2:外部类名别名.属性名
class ScalaOuterClass {
  outer=>			//这里我们可以这里理解 外部类的别名 看做是外部类的一个实例
  class ScalaInnerClass{
    def fun(): Unit ={
      var inName=ScalaOuterClass.this.name
      var inName2=outer.name
    }
  }
  var name= "hao"
  private var sal = 100.23
}

类型投影

scala和java不用,scala的内部类是外部类对象的成员,所以若新建两个外部类的对象a1,a2,则两个对象创建的内部类a1.B和a2.B是不同的,此时可以使用类型投影(Outer#Inner)表示任何Outer中的Inner

class ScalaInnerClass {
    def fun(): Unit = {
      var inName = ScalaOuterClass.this.name
      var inName2 = outer.name
    }
  }

  def fun2(ic: ScalaOuterClass#ScalaInnerClass): Unit = {
    println(ic)
  }

  var name = "hao"
  private var sal = 100.23
}

object ScalaOuterClass {
  def apply(): ScalaOuterClass = new ScalaOuterClass()

  def main(args: Array[String]): Unit = {
    val a = ScalaOuterClass()
    ScalaOuterClass().fun2(new a.ScalaInnerClass)
  }

}

你可能感兴趣的:(scala)