Scala(三) 类和特质

一:基础语法

一个.scala文件可以定义多个类

一个.scala文件可以定义多个类(class、object、trait), class和object定义的类名可以一直,如果object的类名和class的类名一样,那么就将object这个类称之为class这个类的"伴生类"。

class Test { }
class Test2 { }
// 当类中没有任何代码中可以省略大括号
class Test3

object Test { }
object Test2 { }

trait ITest { }

trait ITest2 { }

class与object的区别

  • class创建对象需要使用new关键字,类名后面不需要跟小括号,例如 val person = new Preson
  • object创建对象不需要使用new关键字, 类后面可以不跟小括号,也可以跟小括号,小括号中也可以传值,类后面有小括号时需要自己在类中重写apply方法, 例如
var arr = Array
var arr2 = Array()
var arr3 = Array(1, 2, 3) 

object类的特点

  • 单例
  • 创建对象不需要关键字new, 直接通过类名来创建对象
  • 主构造器的参数列表为空的,因此就省略掉了,但是主构造器代码块是存在的
  • 常用与封装一些工具类、枚举类、常量、隐式转换函数等

属性修饰符

在类中的属性中可以使用private和private[this]来修饰

  • private: 可以在本类和伴生类中访问
  • private[this]: 只能在本类中访问

伴生类和伴生对象

  • 伴生类使用class定义,伴生对象使用object定义,伴生类和伴生对象必须定义在一个.scala文件中,并且名字相同
  • 伴生类和伴生对象可以互相访问被private修饰的关键字
  • 伴生类只能通过关键字new创建对象,伴生对象可以不使用new关键字来创建对象
// 伴生类
class Test {
  private var foo: String = "foo"
  println(Test.bar)
}

// 伴生对象
object Test {
  private var bar: String = null

  println(new Test().foo)
}

属性初始值

类的属性必须有值,不能不设置值,可以设置默认值如null、0、false等,如果没有给初始值会报错:
Class Xxx must either be declared abstract or implement abstract member

Getter & Setter 方法

在Scala中是不需要显式写Getter和Setter方法,Scala会自动帮你生成, 自已也可以显式的定义Getter&Setter方法

构造器

构造器即构造函数,Scala中有两种构造器:

  • 主构造器:与类名定义在一起(如 class Person(var 属性名: 数据类型, var 属性名: 数据类型)),一个类只能定义一个主构造器, 主构造器中的参数会成为类的属性, 不包含在任何方法中的代码都属于主构造器代码, 在主构造器的参数中默认是使用private[this] 修饰
  • 辅助构造器:可以定义多个,但是辅助构造器的第一行代码必须调用主构造器
/**
  * 属性修饰符、伴生对象的演示示例
  */
class Person {
  // 属性必须有初始值
  var name: String = null
  val country: String = "China"

  // 被private修饰的字段只能在本类和伴生对象中访问
  private var age: Int = 0
  // private[this]修饰符只能在本类中访问
  private[this] var birthday: String = null


  // 显式定义 Getter & Setter方法
  def getAge() = {
    this.age
  }

  def setAge(age: Int) = {
    if (age > 0 && age < 150) {
      this.age = age
    } else {
      println("年龄不合法")
    }
  }
}

/**
  * 伴生对象:object的名字和class名字一致的类
  */
object Person {
  val xiaoming = new Person
  xiaoming.age = 10
  xiaoming.name = "mengday"
}
/**
  * 主构造器与辅助构造器
  * 
  * User(var name: String, private[this] var age: Int) 为主构造器
  * 在创建对象时会执行主构造器方法体中除了定义方法之外的所有代码
  */
class User(var name: String, private[this] var age: Int) {
  println("主构造器 开始代码")

  var gender: String = null
  var address: String = null

  // 辅助构造器
  def this(name: String, age: Int, gender: String) {
    this(name, age)
    this.gender = gender
    println("gender=" + gender)
  }

  // 辅助构造器
  def this(name: String, age: Int, gender: String, address: String) {
    this(name, age, gender)
    this.address = address
    println(s"address=$address")
  }

  println(s"主构造器 结束代码 User($gender, $address, $name, $age)")
}

object User {
  def main(args: Array[String]): Unit = {
    /**
      * 主构造器 开始代码
      * 主构造器 结束代码 User(null, null, mengday, 28)
      * gender=male
      * address=上海市
      */
    var user = new User("mengday", 28, "male", address = "上海市")
  }
}

/**
  * 无参主构造器:没有参数列表,有构造器体
  */
object Student {
  println("没有参数的主构造器体 开始")

  def apply(): Unit = {
    println("apply")
    Student
  }

  println("没有参数的主构造器体 结束")
}


object Mian {
  def main(args: Array[String]): Unit = {
    // 首次创建对象会执行主构造器
    val student = Student
    // 单例,第二次就不会执行主构造器代码块
    val student2 = Student
    // 单例是同一个对象,所以hashCode一致
    println(student.hashCode() == student2.hashCode())

	 // 没有参数的主构造器体 开始 
    // 没有参数的主构造器体 结束
    // true
  }
}


object Mian2 {
  def main(args: Array[String]): Unit = {
    // 类后面直接跟小括号: 先创建对象再调用无参apply方法, 注意使用Student不带小括号是不会调用该方法
    val student = Student()
    
    // 没有参数的主构造器体 开始
	 // 没有参数的主构造器体 结束
	 // apply
  }
}

子类调用父类的构造器

子类调用父类的构造器时需要在在继承的时候不但需要指定继承的类,也要指定要调用哪个构造器

class Person(var name: String, var age: Int) {
  println("Person主构造器代码体:" + name + "," + age)
}

// 当子类主构造器参数名字和父构造器参数名字一致时不能使用var关键字来修饰(name和age不能使用var,子类特有的字段可以使用var来修饰)
class Chinese(name: String, age: Int, var gender: String) extends Person (name, age){
  println("Chinese主构造器代码体:" + name + "," + age + "," + gender)
}

object Main {
  def main(args: Array[String]): Unit = {
    // Person主构造器代码体:mengday,28
    // Chinese主构造器代码体:mengday,28,male
    new Chinese("mengday", 28, "male")
  }
}

object 枚举类

使用object来定义类并继承Enumeration类, Value是Enumeration内的一个抽象类。

object YesOrNoEnum extends Enumeration {
  type YesOrNoEnum = Value
  
  val YES = Value(1, "是")
  val NO = Value(0, "否")

  def checkExists(code: String) = this.values.exists(_.toString == code)
}

object EnumMain {
  def main(args: Array[String]): Unit = {
    // 是
    println(YesOrNoEnum(1))
    // 是
    println(YesOrNoEnum.YES)
    println(YesOrNoEnum.withName("否"))

    val value = YesOrNoEnum.YES
    value match {
      case YesOrNoEnum.YES => println("yes")
      case YesOrNoEnum.NO => println("no")
    }
  }
}

样例类 case class

  1. case class是一种特殊的class
  2. case class的主构造器必须有参数列表, 参数大部分为可变参数
  3. 创建对象时普通的类必须需要使用new关键字,而case class可要可不要
  4. case class 默认实现了equals、hashCode和toString方法
  5. case class的主构造器参数是使用public修饰符修饰的
  6. 默认实现了Serializable可以序列化操作
  7. 自动继承了scala.Product类
  8. 支持模式匹配
import java.io.{ByteArrayOutputStream, ObjectOutputStream}

case class Foobar(foo: String*) {

}


object Main {
  def main(args: Array[String]): Unit = {
    // case class类可以使用new关键字来创建对象,也可以不使用new关键字
    var userNewKeywordToCreateObject = new Foobar("mengday", "mengday2")
    var noUserNewKeywordToCreateObject = Foobar("mengday", "mengday2")

    // case class 默认实现了equals、hashCode和toString方法
    println(userNewKeywordToCreateObject.hashCode() + " " + noUserNewKeywordToCreateObject.hashCode())
    println(userNewKeywordToCreateObject == noUserNewKeywordToCreateObject)
    println(noUserNewKeywordToCreateObject)

    // case class的主构造器参数是使用public修饰符修饰的, 因此可以直接访问
    noUserNewKeywordToCreateObject.foo.foreach(println)

    // 一般主构造器参数为可变参数,可变参数就是一个数组,可以通过下标来访问元素
    // 自动继承了scala.Product类,因此可以访问该类中的方法
    val value = noUserNewKeywordToCreateObject.productElement(0)
    println(value)

    // 默认实现了Serializable可以序列化操作
    var bos = new ByteArrayOutputStream
    var oos = new ObjectOutputStream(bos)
    oos.writeObject(noUserNewKeywordToCreateObject)

    noUserNewKeywordToCreateObject match {
      case Foobar("mengday") => println("case 1")
      case Foobar("mengday", "mengday2") => println("case 2")
      case _ => println("else")
    }
  }
}

抽象类 abstract

抽象类使用abstract来定义,抽象类中可以定义抽象属性(没有赋值的属性)和抽象方法(没有方法体只有方法签名),也可以包括普通属性和普通方法。子类使用关键字extends来继承抽象类型,使用override关键字来重写抽象类中的抽象字段和抽象方法,通过super关键字来引用父类。

abstract class AbsTest {
  // 抽象字段
  var name: String
  var age: Int = 18

  // 抽象方法
  def say(str: String)

  // 非抽象方法
  def sayHello: Unit = {
    println("say hello")
  }
}

class MyTest extends AbsTest {
  override var name: String = "MyTest name"
  age = 20

  override def say(str: String): Unit = {
    // 调用父类的方法
    super.sayHello
    println(name + "-" + age + ": say " + str)
  }
}

object TestMain {
  def main(args: Array[String]): Unit = {
    val obj = new MyTest
    obj.say("hi")

    // 子类对象赋值给父类类型
    val absTest: AbsTest = new MyTest
    // isInstanceOf: 判断某个实例对象是否是某个类的实例
    println(absTest.isInstanceOf[MyTest])

    // asInstanceOf: 将某个对象转换为某个类型的实例对象
    val myTest: MyTest = absTest.asInstanceOf[MyTest]
    myTest.say("bye bye")

    // getClass: 获取某个类的实际类型,返回Class类型
    println(absTest.getClass)
    // classOf[T] 获取类的Class对象
    println(classOf[MyTest])
    // true
    println(absTest.getClass == classOf[MyTest])
  }
}

匿名类

即没有名字的类,可以用于实例化抽象类和普通类,使用代码块来重写需要重写的元素。

abstract class Person {
  def sayHello(): Unit = {
    println("hello")
  }
}

class Chinese {
  def sayHello(): Unit = {
    println("你好")
  }
}

object Main {
  def main(args: Array[String]): Unit = {
    // 抽象类不可以直接实例化,需要通过代码块来实现抽象类
    val japanese = new Person {
      override def sayHello(): Unit = {
        println("おはよう")
      }
    }
    japanese.sayHello()
    // class Main$$anon$1
    println(japanese.getClass)


    // 普通类的匿名类对象
    var beiJingPeople = new Chinese() {
      override def sayHello(): Unit = {
        println("吃了吗你内?")
      }
    }
    beiJingPeople.sayHello()
    println(beiJingPeople.getClass)
  }
}

trait 特质

“特征"或者"特质”,可以定义属性和方法,方法可以是抽象的或者普通的,trait可以被继承extends, Scala中没有implement关键字而是使用with关键字,with后面可以使用特质类

  • trait似于Java中的接口, 可以定义属性,可以作为接口使用
  • trait具有多重继承的功能,通过extends关键字来继承
trait American {

  // 抽象字段
  var skinColor: String
  // 非抽象字段
  val language: String = "English"

  // 普通方法
  def eat(): Unit = {
    println("Hamburger")
  }

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

trait Chinese {
  def drink(): Unit = {
    println("果粒橙")
  }

  def kungfu(): Unit
}

trait Russia {
  def sayRussian(): Unit = {
    println("привет")
  }
}

trait Japanese {
  val flower: String = "樱花"
  def bushido(): Unit = {
    println("武士道")
  }
}

// 继承Chinese实现American, 可以使用多个with来实现多继承
class GreenLargeMan extends Chinese with American with Russia {
  override var skinColor: String = "绿色"

  override def kungfu(): Unit = {
    println("咏春:叶问")
  }

  override def sayHello(): Unit = {
    println("I'm super man")
  }
}


object Main {
  def main(args: Array[String]): Unit = {
    val people = new GreenLargeMan
    people.eat()
    people.kungfu()
    people.sayRussian()

    // 混入:在类的后面使用with关键字来继承某个类的所有非私有成员
    val peole2 = new GreenLargeMan with Japanese
    peole2.bushido()
    println(peole2.flower)
  }
}
发布了308 篇原创文章 · 获赞 936 · 访问量 133万+

猜你喜欢

转载自blog.csdn.net/vbirdbest/article/details/88846086