专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

Scala之:类与构造器 && 抛出异常

Scala语言来源于Java,因此它天生就是一个面向对象的语言。在本章中会介绍如何在Scala中利用构造器来构造一个类对象,顺便简要介绍Scala中的异常机制。

使用class关键字定义一个类

我们直接给出一个简单的Cat类模板:

class Cat{
  var name : String = "null"
  var age : Int = _
  var color : String = _
  override def toString = s"Cat($name, $age, $color)"
}

在Scala内定义一个类,仍然使用的是class关键字。(我们目前为止都是使用object修饰的,之后会提到classobject的区别)

一般来说,类内部的属性使用var关键字(因为这可以在外部修改这些变量)。在Scala中,声明变量是一定要赋值的。但是可使用_ 表示为这个属性赋默认值,比如AnyVal类型的成员默认为0,AnyRef类型的成员默认为Null。

从Scala的层面上来说,Cat内部定义的成员都是public的,尽管没有加任何修饰符修饰。但是如果我们查看编译后的Cat.class文件的话,这些属性实际上是修饰为private,只不过编译器又额外地生成了公开的name(相当于Java的get方法)和name_$eq方法。(相当于Java的set方法)

这些细节在Scala层面是透明的,因此我们可以不受限制地访问一个Scala类的成员,除非这个成员被显示地标注为了private关键字。

在Scala中,类成员的访问权限被简化了:要么就不带任何修饰符来表示公有,要么就带上private或者protected修饰符表示为私有变量或只允许子类访问,也就是说,Scala的类成员只有三类权限

class Cat {
  //公开
  val age : Int = 1
  //允许子类访问
  protected val toy : String = "toy"
  //私有成员
  private val name : String = "kitty"
}

@BeanProperty注解

为了适配Java的使用习惯,Scala提供@BeanProperty注解,来提供某个属性对应的set/get方法。

@BeanProperty
var name : String = _

等价于:

def setName(inName : String): Unit ={
    this.name = inName
}

def getName : String = this.name

注意,如果要使用此注解,此内部成员需要是可变var的。并且不能使用private关键字来修饰。

.scala文件中的一些细节

一个.scala源文件内部可以存在多个公开的类,这一点与java不同。在编译期间,scalac会将一个.scala文件中不同的类编译成分别的.class类文件。

我们在编译过程中还可以发现一个细节:如果声明的是class,则只会编译出一个.class文件。如果声明的是object,则会同时生成两个.class文件。我们会在后续的伴生对象当中详细地叙述它。

赋值”_”与赋值null

注意,这两种赋值方式都要求变量显示声明数据类型

赋值为”_”表示为当前的变量赋默认值。如果该成员没有显示地声明数据类型,则在程序执行的时候会报错:

unbound placeholder parameter,表示没有绑定参数值类型。

对于不同的数据类型,_所默认代表的值参见下表。

类型 对应的值
Byte Short Int Long 0
Float Double 0.0
StringAnyRef Null
Boolean false

如果赋值了null代表默认为这个属性指向空引用。如果赋值为null,且又不显示的声明数据类型,则该属性变为Null类型(注意!在Scala中,null单独为一个类型,就和Unit一样)。这个属性除了null之外无法再赋其它值,同样会在使用时带来问题。

Scala在声明对象变量时,可以根据new对象的类型自动推断,所以一般情况下: Type可以省略。但是如果我们希望定义一个上转型,这个时候则需要显示地写上数据类型。

object CreateOBJ {
  def main(args: Array[String]): Unit = {
    //定义了一个上转型对象。
    val person : Person = new Emp
  }
  class Person {}
  class Emp extends Person{}
}

类方法

Scala的方法与上一章的函数定义方法是一样的。根据笔者的习惯,在类内部声明的函数(Function)特指为方法(Method)。在class修饰的类内部声明的方法为实例方法。

1、 当Scala开始执行的时候,会在栈区开辟一个main栈,main栈在程序执行完毕后最后销毁。
2、 当Scala程序执行到一个方法时,总会开辟一个新的栈。
3、 每个栈都是独立的空间,AnyVal类型的数据类型是独立的,相互不会影响。
4、 当方法执行完毕之后,该方法开辟的栈会被JVM机回收。

Scala构造方法(构造器)

Scala的构造方法和Java存在着较大的区别。

构造器的作用是对新对象的初始化。

在Java中,一个类可以定义多个不同的构造方法,简称构造方法重载。如果没有显示声明构造器,则Java默认提供无参构造器。如果显示声明了构造器,则Java将不再提供无参构造器。且Java的构造器内第一行省略了super();

Scala的构造器包括了主构造器辅助构造器。编译器通过不同的参数来区分选择何种构造器。而无论是何种构造器,都是不需要返回值的。

主构造器

主构造器直接在class上进行声明,如下面的代码块。类名的后面直接跟进一个参数列表,来为Teacher的内部成员赋值。

class Teacher(inAge: Int = 25, inName: String = "Tom") {
  var age: Int = inAge
  var name: String = inName
}

这等价于Java的这种声明方式:

//上述构造器的Java写法:
public Teacher(int inAge,String inName){
    this.age = inAge;
    this.Name = inName;
}

class内部的代码块实际上就可看作是一个主构造函数的函数体。当主程序使用主构造器创建一个类实例时,会像执行函数一样顺序执行class内部的每一条语句,而函数体内的声明语句则成为了该类的属性成员和内部方法。

//上述构造器的Java写法:
public Teacher(int inAge,String inName){
    this.age = inAge;
    this.Name = inName;
    //我们在Scala的class内的语句相当于都是写入了构造器的函数体内。
    System.out.println("Hire a teacher.")
}

辅助构造器

this关键字为类内部的方法命名,以表示该方法是一个辅助构造器。注意!辅助构造器内的第一行必须显示或隐式地调用主构造器!(这种感觉就像是在Java类当中,super构造器必须写在子构造器的第一行一样。)

这么做的根本原因是:只有主构造器通过extends关键字建立了与父类的联系。而子类的辅助构造器无法直接地调用父类的任何构造器,因此在调用辅助构造器之前,一定要先调用主构造器。这像是一个装饰者模式:先调用主构造器构造其父类和主要属性,然后再使用辅助构造器对其它的拓展属性进行补充。

对于显示地调用主构造器很好理解,那何为间接地调用主构造器呢?那就是某个辅助构造器调用了另一个辅助构造器,而该辅助构造器调用了主构造器。

我们直接给出以下代码:

class B{
  println("1")
  def this(int: Int){
    //B在调用辅助构造器之前,会先调用B的主构造器
    this
    println("2")
  }
}
//A的主构造器调用了B的辅助构造器
class A extends B(5){
  println("3")
  def this(string: String){
    //在调用A的辅助构造器之前,会首先调用A的主构造器
    this
    println("4")
  }
}

其中,类A的示例在初始化前先调用父类B的带参数构造器。

那么这行语句实际上调用了几个构造器呢?

//使用了A的辅助构造器构造对象。
val a : A = new A("a")

从结果来看,一共是调用了4个构造器。因为程序会在控制台中打印:1 2 3 4。程序首先调用父类B的主构造器,然后再调用B的辅助构造器。当B构建完成时,再调用A的主构造器,然后调用A的辅助构造器。

在Scala中定义异常

对Java的异常做个简单回顾:Java的异常分为两类,受检异常(RuntimeException)和非受检异常(CheckedException)。

受检异常,指代JVM在运行期间由于程序的逻辑问题可能引发的意外:如NullPointerException, ArrayIndexOutOfBoundsException等。

非受检异常,指代因外部因素可能引发的程序问题:如ClassNotFoundException(试图反射的类实际上在工程中并不存在)。

然而在Scala的异常机制沿用了Java的try-catch体系,但是写法有些区分,并且从形式上被简化了:在Scala中,只有运行时异常。下面给出一个实例:

try {

  val a: Int = 10 / 0
  println(a)

} catch {
  case ex: ArithmeticException => ex.printStackTrace()
  case ex: Exception => ex.printStackTrace()
}finally {

  println("try-catch执行完毕")
}

在Scala中,只需要写一个catch:它会自动对捕获到的异常进行模式匹配,并执行=>后面的代码块。(注意不要把=>写为->,和Java的lambda语法区分开来)。

Java对catch的顺序有一定的要求:小范围的异常要写在前面。范围大的异常写在后面。而Scala并没有对此做严格限制,但是我们仍然应当遵守这样的编程习惯。

Scala可以在def函数上面加上一个@throws注解的方式来表示可能抛出的异常:

@throws(classOf[NumberFormatException])
def mis(stringInt:String): Int= {
  stringInt.toInt
}

这篇博客记录了有关Java两种异常的梗概。

文章永久链接:https://tech.souyunku.com/41960

未经允许不得转载:搜云库技术团队 » Scala之:类与构造器 && 抛出异常

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们