【scala 笔记(6)】 类(class)和对象(object)

本文详细探讨了Scala中的类与对象,包括简单类和方法定义、带setter和getter的属性、Bean属性、构造器、内部类、单例对象、伴生对象以及apply方法。介绍了Scala中如何定义和使用类的各个特性,如构造器参数生成的字段和方法,以及对象作为单例和工具函数的作用。此外,还特别讨论了伴生对象的apply方法和应用程序对象的main方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

熟悉JAVA/C++的人会发现Scala里的某些概念虽然相似,但却不完全相同。

类 (class)

简单类和方法定义

简单类类形式和JAVA/C++很像:

class Counter{
  private var value: Int = 0
  def increment(): Unit = value += 1
  def count() :Int = value
}
在scala中并不需要申明public, scala源文件中可以包含多个类,这些类都是公有的。

定义一个类对象,并进行调用:

scala> val flag = new Counter()
flag: Counter = Counter@3b05a99b

// scala 中可以不带 () 来进行调用该方法, 这在C++中就是不可以的会返回一个函数指针。
scala> flag.count  
res0: Int = 0

scala> flag.increment

scala> flag.count()
res2: Int = 1

带 setter 和 getter 的属性

在JAVA中定义一个带 setter 和 getter 的属性:

public class Person{
    private int age;
    public void setAge(int age) { this.age = age;}
    public int getAge() { return this.age ; }
}

之所以不建议使用 public 属性的成员变量而采用 getter和setter的方法,是因为它们让你可以从简单的get和set机制出发, 并在需要的时候进行改进。 但也不意味着它们总是好的。通常情况下如果使用者都可以对一个状态进行获取和设置这是比较不好的事情。

下面我们来看下Scala :
- 先定义一个Person类

class Person{
  val country = "china"
  var name = "borey"
  private var phone = "123456789"
  // scala 允许更加严格的访问限制, 通过 private[this] 这个修饰符来实现
  private [this] var money = 999999999
}
  • 用scalac编译下, 再用javap查看下字节码
prod@AWS-TEST-DT:~/borey_zhu/scala$ scalac Person.scala 
prod@AWS-TEST-DT:~/borey_zhu/scala$ ls
Person.class  Person.scala
prod@AWS-TEST-DT:~/borey_zhu/scala$ javap Person
Compiled from "Person.scala"
  • 生成的Java代码:
public class Person {
  private final java.lang.String country;
  private java.lang.String name;
  private java.lang.String phone;
  private int money;
  public java.lang.String country();
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  private java.lang.String phone();
  private void phone_$eq(java.lang.String);
  public Person();
}

通过上面scala的定义编译和反译,很快就能得出scala类中定义的结构:

定义public getterpublic setterprivate getterpivate setter备注
varyesyesyesyes
valyesnoyesno仅带 getter 属性
private varnonoyesyes
private [this] varnononono对象私有字段

说明:
在Scala中, getter和setter方法并非定义为getXxxsetXxx, 而是定义为xxx和xxx_方法( = 号被翻译为$eq, 是因为JVM中不允许方法名中包含等号),不过它们的用意是相同的。

Bean属性

在上一节中,scala对于你定义的字段提供了 setter 和 getter方法。不过,这些方法名称并不是Java工具所预期的。 JavaBeans规范 把JAVA属性定义为一对getXxx和setXxx方法。 许多Java工具都依赖这样的命名习惯。

在scala中当你将字段标注为 @BeanProperty 时, 这样的方法会被自动生成。对于类型为 Boolean, 如果你需要 getter 被命名为 isStatus 使用 BooleanBeanProperty

import scala.beans.{BeanProperty, BooleanBeanProperty}

class Counter{
  @BeanProperty
  var value: Int = 0

  @BooleanBeanProperty
  var flag:Boolean = false
}

javap -p Counter.class 后得到

public class Counter {
  private int value;
  private boolean flag;
  public int value();
  public void value_$eq(int);
  public boolean flag();
  public void flag_$eq(boolean);
  public int getValue();
  public void setValue(int);
  public boolean isFlag();
  public void setFlag(boolean);
  public Counter();
}

针对字段生成的方法

Scala字段生成的方法何时使用
val/var name公有的name
name_ =(仅限于var)
实现一个可以被公开访问并且背后是以字段形式保存的属性
@BeanProperty val/var name公有的name
getName()
name_ =(仅限于var)
setName(…)(仅限于var)
与JavaBeans互操作
private val/var name私有的name
name_ =(仅限于var)
用于将字段访问现在在本类的方法,就和Java一样。 尽量使用 private
private[this] val/var name用于将字段访问限制在同一个对象上调用的方法, 不经常用到
private[class] val/var name依赖于具体实现将访问权赋予外部类。 并不经常用到

构造器

和Java/C++一样, Scala 也有任意多的构造器。例如:

// Counter.scala
class Counter{
  var value:Int = 0
  private var flag :Boolean = false

  def this(v:Int, f:Boolean)  { // 辅助构造器
    this() // 主构造器
    this.value = v
    this.flag = f
  }
}

javap 反译后

prod@AWS-TEST-DT:~/borey_zhu/scala$ scalac Counter.scala 
prod@AWS-TEST-DT:~/borey_zhu/scala$ javap -p Counter.class 
Compiled from "Counter.scala"
public class Counter {
  private int value;
  private boolean flag;
  public int value();
  public void value_$eq(int);
  private boolean flag();
  private void flag_$eq(boolean);
  public Counter();
  public Counter(int, boolean);
}

在 Scala 中,可以将构造器参数放在类名后。 例如:

// Counter.scala
class Counter(var value:Int, private var flag:Boolean){
}

通过javap看下, scala会帮助我们自动生成如下代码, 和上面定义的几乎一样

prod@AWS-TEST-DT:~/borey_zhu/scala$ scalac Counter.scala 
prod@AWS-TEST-DT:~/borey_zhu/scala$ javap -p Counter.class 
Compiled from "Counter.scala"
public class Counter {
  private int value;
  private boolean flag;
  public int value();
  public void value_$eq(int);
  private boolean flag();
  private void flag_$eq(boolean);
  public Counter(int, boolean);
}

在某种情况下通过该方法可以便利开发者,无需定义辅助构造器。

针对构造器参数生成的字段和方法

构造器参数生成的字段和方法
name: String对象私有字段。如果没有方法使用name, 则没有该字段
private val/var name:String私有字段, 私有的getter/setter 方法
val/var name:String私有字段, 公有的getter/setter 方法
@BeanProperty val/var name:String私有字段, 公有的Scala版和JavaBeans版的 getter/setter方法

内部类

在scala中, 内部类定义几乎和Java一致:

class A(val a: Int){
  class B(val b: Int){}

  def addB(_b: B): Unit = {
    println(this.a + _b.b)
  }
}

如何使用? 例如:

scala> val a = new A(10)
a: A = A@1d0a61c8

scala> val b = new a.B(30)// 和java 不一样,java 中 a.new B(30)
b: a.B = A$B@28501a4b

scala> a.addB(b)
40

scala> val a2 = new A(10)
a2: A = A@4743a322

scala> val b2 = new a2.B(30)
b2: a2.B = A$B@6a4ccef7

scala> a.addB(b2)  // why ???
<console>:14: error: type mismatch;
 found   : a2.B
 required: a.B
       a.addB(b2)

为何上述表达式会显示 error: type mismatch 错误呢? 因为在每个A的实例中都有它自己的B类, 也就是说a.B 和 a2.B 是 不同的两个类 。 这和JAVA不同, Java中内部类从属于外部类。 Scala 采用的方式可能跟符合常规: 从构造对象B来看 ,你只需要 new a.B(n) , 而在Java中采用 a.new B(n)

那么如果就是想让 a.addB(b2) 操作呢? 有两种解决方案:

  • 将 B类 定义在 A的伴生对象中:
// B类的一个不错的位置吧
object A{
  class B(val b: Int){}
}
  • 使用 类型投影
// A#B  其含义是任意A的B
def addB(_b: A#B): Unit = {
    println(this.a + _b.b)
  }

对象(object)

在scala中,如果你需要某个类的单个实例时,或者想为其他值或函数找一个可以放置的地方时,这个时候object是个很好的去处:

单例对象

scala 没有静态方法和静态字段的, 你可以用object这个语法结构来达到同样的目的。 对象定义了某个类的单个实例, 包含了你想要的特性。

// 例如:在程序中想获取一个唯一编号
object A{
  private var uuid = 0
  def newUuid() = { uuid += 1; uuid}
}

scala> A.newUuid
res4: Int = 1

scala> A.newUuid
res5: Int = 2

1、对象的构造器在该对象第一次被使用时调用。如果一个对象从未被使用,那么其构造器也不会被执行。

2、对象本质上可以拥有类的所有特性, 除了提供构造参数之外。

3、对于任何你在Java 或 C++ 中会使用单例对象的地方, 在Scala 中都可以用对象来实现:

3.1 作为存放工具函数或常量的地方;

3.2 高效的共享单个不可变实例;

3.3 需要用单个实例来协调某个服务;

伴生对象

在Java或C++中, 你通常会用到既有实例方法又有静态方法的类。 在Scala中, 你可以通过类和与类同名的“伴生”对象来达到同样的目的。 例如:

// 生成的 对象C 都会拥有一个唯一id

object C{
  private var uuid = 0
  def newUuid() = { uuid += 1; uuid}
}

class C{
  val uuid:Int = C.newUuid()
  def showUuid(): Unit ={
    println("uuid = %d".format(uuid))
  }
}

使用

scala> val c1 = new C
c1: C = C@5d66e944

scala> val c2 = new C
c2: C = C@2ad99cf3

scala> c1.showUuid
uuid = 1

scala> c2.showUuid
uuid = 2

apply 方法

我们通常会定义和使用对象的apply方法。 当遇到如下形式的表达式时, apply方法就会被调用:

Object(Param1, ... , ParamN)

通常, 这样一个apply方法返回的是伴生类的对象 ,如: 数组 val arr = Array(1,2) 省去了new 关键字

注意: Array(100) 和 new Array(100) 很容易就混淆了当你知道有apply的存在时, Array(100)调用的是apply(100), 输出一个单元素100的数组, 而 new Array(100), 会调用Array构造器 this(100), 结果是包含100个null的 Array[Nothing]数组。

class D(val x:Int){
}
object D{
  def apply(x:Int): D = new D(x)
}

// val d = D(100) 不必 new D(100)

应用程序对象

每个应用程序都会存在入口函数Scala也不例外,每个Scala程序都必须从一个对象的main方法开始, 这个方法的类型为 Array[String] => Unit:

object Test {
  def main(args: Array[String]): Unit = {
    println("hello scala!")
  }
}

也可以通过extends App 方法:

object Test extends App{
  println("hello scala!")
}

特质 App 代码 , 感兴趣的同学可以看下, 从该文中的 noted 可以看出 该模式下, 主函数main已经执行了, 但用户代码还未被初始化; 用户代码会被编译器特殊处理, 初始化被挪到了 delayedInit方法中, 还可以根据要求打印出代码运行的时间。

/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2010-2013, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |    http://scala-lang.org/               **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
\*                                                                      */

package scala

import scala.compat.Platform.currentTime
import scala.collection.mutable.ListBuffer

/** The `App` trait can be used to quickly turn objects
 *  into executable programs. Here is an example:
 *  {{{
 *  object Main extends App {
 *    Console.println("Hello World: " + (args mkString ", "))
 *  }
 *  }}}
 *  Here, object `Main` inherits the `main` method of `App`.
 *
 *  `args` returns the current command line arguments as an array.
 *
 *  ==Caveats==
 *
 *  '''''It should be noted that this trait is implemented using the [[DelayedInit]]
 *  functionality, which means that fields of the object will not have been initialized
 *  before the main method has been executed.'''''
 *
 *  It should also be noted that the `main` method should not be overridden:
 *  the whole class body becomes the “main method”.
 *
 *  Future versions of this trait will no longer extend `DelayedInit`.
 *
 *  @author  Martin Odersky
 *  @version 2.1, 15/02/2011
 */
trait App extends DelayedInit {

  /** The time when the execution of this program started, in milliseconds since 1
    * January 1970 UTC. */
  @deprecatedOverriding("executionStart should not be overridden", "2.11.0")
  val executionStart: Long = currentTime

  /** The command line arguments passed to the application's `main` method.
   */
  @deprecatedOverriding("args should not be overridden", "2.11.0")
  protected def args: Array[String] = _args

  private var _args: Array[String] = _

  private val initCode = new ListBuffer[() => Unit]

  /** The init hook. This saves all initialization code for execution within `main`.
   *  This method is normally never called directly from user code.
   *  Instead it is called as compiler-generated code for those classes and objects
   *  (but not traits) that inherit from the `DelayedInit` trait and that do not
   *  themselves define a `delayedInit` method.
   *  @param body the initialization code to be stored for later execution
   */
  @deprecated("the delayedInit mechanism will disappear", "2.11.0")
  override def delayedInit(body: => Unit) {
    initCode += (() => body)
  }

  /** The main method.
   *  This stores all arguments so that they can be retrieved with `args`
   *  and then executes all initialization code segments in the order in which
   *  they were passed to `delayedInit`.
   *  @param args the arguments passed to the main method
   */
  @deprecatedOverriding("main should not be overridden", "2.11.0")
  def main(args: Array[String]) = {
    this._args = args
    // 执行你定义在 {} 中的代码块
    for (proc <- initCode) proc()  
    // 如果 运行命如: scala -Dscala.time Test 将打印程序运行时间
    if (util.Properties.propIsSet("scala.time")) { 
      val total = currentTime - executionStart
      Console.println("[total " + total + "ms]")
    }
  }
}

特质 DelayedInit : 从源码中可以看出只要 extends DealyedInit , 编译器对该特质的类都会将用户代码挪到 delayedInit 下面 , code becomes delayedInit (code)

/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2010-2013, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |    http://scala-lang.org/               **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
\*                                                                      */

package scala

/** Classes and objects (but note, not traits) inheriting the `DelayedInit`
 *  marker trait will have their initialization code rewritten as follows:
 *  `code` becomes `delayedInit (code)`.
 *
 *  Initialization code comprises all statements and all value definitions
 *  that are executed during initialization.
 *
 *  Example:
 *  {{{
 *    trait Helper extends DelayedInit {
 *      def delayedInit(body: => Unit) = {
 *        println("dummy text, printed before initialization of C")
 *        body // evaluates the initialization code of C
 *      }
 *    }
 *
 *    class C extends Helper {
 *      println("this is the initialization code of C")
 *    }
 *
 *    object Test extends App {
 *      val c = new C
 *    }
 *  }}}
 *
 *  Should result in the following being printed:
 *  {{{
 *    dummy text, printed before initialization of C
 *    this is the initialization code of C
 *  }}}
 *
 *  @see "Delayed Initialization" subsection of the Scala Language Specification (section 5.1)
 *
 *  @author  Martin Odersky
 */
@deprecated("DelayedInit semantics can be surprising. Support for `App` will continue. See the release notes for more details: https://github.com/scala/scala/releases/tag/v2.11.0-RC1", "2.11.0")
trait DelayedInit {
  def delayedInit(x: => Unit): Unit
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值