Scala类和对象(二)

1. 类和属性

1.1 如何控制构造函数字段的可见性

在Scala中:

  1. 如果一个字段被声明为var, Scala会为该字段生成getter和setter方法。
  2. 如果字段是val, Scala只生成getter方法。
  3. 如果一个字段没有var或者val的修饰符, Scala比较保守,不会生成getter和setter方法。
  4. 另外,var和val字段可以被private关键字修饰,这样可以防止生成getter和setter方法。

我们看一下测试代码:

object Demo{
  def main(args: Array[String]): Unit = {
      val p = new Person("haah")
      println(p.name)
  }

  class Person(var name:String){

  }
}
结果:
haah

我们再把name设置为val:

这里写图片描述

还没有到运行阶段,编译器自动捕获了异常。改为var就可以了。

如果设置为非val和非var的字段:

这里写图片描述

直接找不到name这个对象,当构造函数参数既没有声明为val或var时,字段的可见性很受限制,井且Scala不会为此生成访问修改方法。

给val或者var加上private,这个关键字会阻止getter和setter方法的生成,所以这种宇段只能被类的成员变量使用。

2. 构造函数

如同在java中可以定义多个构造函数,Scala中也可以,不同的是除了主构造函数以外其余的构造函数都被称为辅助构造函数。所有的辅助构造函数必须以this为名,另外每个构造函数必须调用之前已经定义好了的构造函数:

//主构造函数
class MakePizza(var size:Int,var pizzaType:String){
  //辅构造函数
  def this(pizzaAuthor:String){
    this(size,"3")
  }
   //辅构造函数
  def this(){
    this("libai")
  }

}

定义私有主构造函数:

class Order private{

}

私有构造函数是无法实例化的。我们知道在java中实现单例模式就是把构造函数私有化。提供一个getInstance方法来初始化类。

class MakePizza private (var size:Int,var pizzaType:String){

  object getInstance extends MakePizza(size,pizzaType){
    val makePizza = new MakePizza(size,pizzaType)
  }

}

设置构造函数参数默认值

class Socket(var timeout:Int = 10000)

那么在调用构造函数的时候就可以不指定默认值:

val s = new Socket

用case类生成模板代码:

定义case类可以生成模板类,包括如下方法:apply,unapply,toString,equals,hashCode。

case class  Person(name:String,relation:String)

将类定义为case类会生成许多模板代码,好处在于:

  1. 会生成一个apply方怯,这样就可以不用new关键字创建新的实例。
  2. 由于case类的构造函数参数默认是val ,那么构造函数参数会自动生成访问方站。如果是var也会有修改方怯。
  3. 会生成一个默认的toString方怯。
  4. 会生成一个unapply方法,在模式匹配时很好用。
  5. 会生成equals和hashCode方法。
  6. 还有一个copy方站。

定义case类,就不用再new来创建一个实例:

val wmily = Person("xiaoming","niece")

case类的构造参数默认是val,所以会自动生成get方法,不会生成set。当把case类的构造方法参数设置为var的时候就会有get和set。

case类主要是为了创建“不可变的记录”,这样容易在模式匹配中使用。正因为如此,casel的构造函数参数默认值是val,如果你改为var那就违背了case的本意。

3. 方法

在java中声明一个方法如下:

public String doSomething(int x){

}

在scala中则是这样:

sef doSomeThing(x:Int):String = {

}

控制方法的作用域:

Scala中的方法缺省是public。

在java中protected修饰的方法对同一包中的类都可见。但是在Scala中仅对该类的子类可见。

下表中给出了Scala中各种级别的访问控制:

修饰符 描述
private[this] 对当前实例可见
private 对当前类的所有实例可见
protected 对当前类以及其子类的实例可见
private[model] 对model包下的所有类可见
private[coolapp] 对cooapp包下的所有类可见
private[acme] 对acme包下的所有类可见
无修饰符 公开方法

调用父类中的方法:

Scala中调用父类,用super代表父类,然后是方法名:

class App extends Fruit{
    override def onCreate(color:String){
        super.onCreate(color)
    }
}

可以继承多个类:

与java中不同的是Scala的类对象可以同时继承多个类。如果继承的多个类中有相同的方法可以用如下方式选择要使用哪个类中的方法:

super[类名].方法名

举个例子:

object Demo{
  def main(args: Array[String]): Unit = {
    var a = new ShowColor
    a.printApple("red")
    a.printBanana("yellow")

  }
}

trait Fruit{
  def getColor(color: String): Unit ={
     printf("the color is %s \n",color)
  }
}

trait Apple extends Fruit{
  override def getColor(color: String): Unit = super.getColor(color)
}

trait Banana extends Fruit{
  override def getColor(color: String): Unit = super.getColor(color)
}

class ShowColor extends Fruit with Apple with Banana{
  def printSuper(color: String) = super.getColor(color: String)
  def printApple(color: String) = super[Apple].getColor(color: String)
  def printBanana(color: String) = super[Banana].getColor(color: String)
}

结果:
the color is red 
the color is yellow 

Process finished with exit code 0

方法参数默认值:

如同python一样,Scala可以给方法的参数一个默认值:

def makeCnnection(timeout:Int = 3000,[protocol:String="http"){
    println("timeout = %d,protocol=%s".format(timeout,protocol))
}

定义一个返回多个值(tuples)的方法

如果我们希望一个方法能够返回多个值,又不想把这些值字段用一个对象包装,那么可以使用tuples从方法中返回多个值。

tuples是Scala中的数据类型,表示元组的意思。元组是使用()表示的数据结构。后面我们介绍集合列表的时候会详细说。

加入有如下方法返回了包含3个字段的元组:

def getStackInfo = {
    return ("RPC","sufface",34)
}

可以使用一个元组来接收:

val result = getsStackInfo
println(b._1,b._2,b._3)

tuple中的值可以通过其位置来访问。

可变参数的方法

在java中可以传入String 类型的可变参数:String … str,在Scala中也提供可变参数的方法:在参数类型后面加一个 “*”,这个参数就变成了可变参数。

def printAll(str:String*){

}

注意:同java一样,当一个方法包含可变参数的时候,那么这个可变参数必须是在所有参数中最后一个位置,否则会报错。

使用_*来匹配一个序列

对于一个序列:Array,List,Seq,Vector,都可以使用_*来匹配里面的每一个对象,从而可以使他可以当做变参传递给一个方法:

def main(args: Array[String]): Unit = {
    val fruit = List("apple","banana","pair")
    printAll(fruit: _*)
}

def printAll(str:String*): Unit ={
    str.foreach(println)
}

解析fruit的每一个对象然后作为参数传递给printAll()方法。

方法的异常声明

使用@throws注解声明可能抛出的异常。这个和java中的使用方式有区别。

@throws(classOf[Exception])
def paly: Unit ={
  //code....
}

4. 对象

对象的强制转换:

使用asInstanceOf将一个实例转换为期望的类型。

val recongnizer = cm.lookup("recongnizer").asInstanceOf[Recognizer]

上面的Scala代码等于下面的java代码:

Recognizer recognizer = (Recognizer)cm.lookup("Recognizer");

java中的对象.class的Scala等价类

在java中当你有一个方法里面要求将不同的对象作为参数传进来然后使用不同对象的不同方法,这个时候你可以传入一个参数:class clz。然后利用反射获取不同对象的方法。

在Scala中使用classOf方法来代替java中的.class。

val info = new DataLine.Info(classOf[TargetDataLine],null)

等价于java中的:

info = new DataLine.info(TargetDataLine.class,null)

用object 启动一个应用:

在Scala中启动一个应用有两种方法:

  1. 定义继承App特质的object;
  2. 定义一个object,并实现main方法

    object Demo{
    def main(args: Array[String]): Unit = {
    println(“hello”)
    }

    def printAll(str:String*): Unit ={
    str.foreach(println)
    }
    }

或者:

object Hello extends App{
  println("hello")
}

不用new关键字创建对象实例:

有两种办法:

  1. 为类对象创建伴生类,并在伴生类内定义一个apply方法;
  2. 将类定义为case类。

用apply方法创建一个伴生类:

class Person{
  var name:String = ""
}

object Person{
  def apply(name:String): Person = {
    var p = new Person
    p.name = name
    p
  }
}

调用方式:

val carry = Person("carry")

在同一个文件中定义Person类和Person对象,在对象中定义apply方法接受期望的参数,这个方法本质上是类的构造函数。

将类声明为case类:

case class Person(var name:String)
val p = Person("carry")

case类起的作用在于他在伴生类中生成了一个apply方法,上文我们已经说过将一个类定义为case类会自动生成很多方法。

5.包管理

Scala的包管理跟Java类似,但更灵活。除了在类文件的开头用package语句外,还可以用花括号,与C++和C#的命名空间很像。

Scala会隐式的导入两个包:

java.lang._
scala._

Scala里的”_”字符类似于java里的”*”。

5.1 花括号风格的包记号法:
package com.rickiyang.zoo{
  class Foo{
    //todo
  }

  class food{
    //todo
  }
}

这种方式允许在一个文件中放多个包。也可以用“花括号”方式定义嵌套的包。

5.2 在导入时重命名类名:
import java.util.{ArrayList => JavaList}

然后再代码中就可以使用别名了。

当你为你的类取了别名后那么原来的名字就不可以使用了。否则会出错。

6. 特质—>java中的接口

上文中我们使用过trait修饰类,trait就是Scala中的特质,相当于java中的接口。正如Java类能够实现多个接口一样, Scala类可以继承多个特质。所以trait的功能要比java接口的功能要强大的多。

做为普通的接口来使用:

方法如果不需要任何参数,在def后面指定方法名即可:

trait BasePlay{
  def playBasketBall
  def playFootBall
}

需要参数只需要将其罗列出来:

trait BasePlay{
  def playBasketBall(count:Int)
  def playFootBall(count:Int)
}

当一个类需要继承特质时,要使用extends和with关键字。只继承一个特质时,使用extends:

class BasketBall extends BasketBall{
  //todo 
}
  • 继承一个类和一个或多个特质时,对类使用extends ,对特质使用with。
  • 当一个类继承多个特质时,使用extends继承第一个特质,其余的使用with 。
  • 除非实现特质的类是一个抽象类,否则它必须实现特质所有的抽象方怯。
  • 如果一个类继承了一个特质但是没有实现它的抽象方法,这个类必须被声明为抽象类。
  • 特质也可以继承另一个特质。

像抽象类一样使用特质:

定义为trait的类中既可以有抽象方法也可以有已经实现了的方法。

trait BasePlay{
  def playBasketBall {println("haha")}
  def playFootBall
}

class Football extends BasePlay {
  override def playFootBall: Unit = {
    println("hahahh football")
  }
}

对于trait中已经实现了的方法,他的继承类可以重写也可以不重写。但是抽象方法一定要重写,否则继承类必须定义为抽象类(abstract)。

posted @ 2018-05-19 15:02  rickiyang  阅读(394)  评论(0编辑  收藏  举报