Scala 编程—第六节:类和对象(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/HG_Harvey/article/details/82499938

前言:
       类和对象的相关知识比较多,笔者分为两篇来介绍,本篇即第一篇主要介绍类定义及对象创建、getter/setter、类主构造器、辅助构造器。

1.类定义及创建对象

1.1 类

定义类

// 采用关键字class定义
class Person {
  // 类成员必须初始化,否则会报错
  // 这里定义的是一个公有成员
  var name:String=null
}

Person类编译后会生成Person.class文件,使用 javap -p Person 命令反编译代码

D:\code\study\scalademo\target\classes\com\harvey\classandobject>javap -p Person
警告: 二进制文件Person包含com.harvey.classandobject.Person
Compiled from "Person.scala"
public class com.harvey.classandobject.Person {
  private java.lang.String name;
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  public com.harvey.classandobject.Person();
}

从字节码文件内容可以看到:虽然我们只在Person类中定义了一个类成员(域)name,类型为String,但Scala会默认帮我们生成name()与name_=()及构造函数Person()。其中name()对应java中的getter方法,name_=()对应java中的setter方法(由于JVM中不允许出现=,所以用$eq代替。值得注意的是定义的是公有成员,但生成的字节码中却是以私有的方式实现的,生成的getter、setter方法是公有的。

Scala中定义的类同Java一样,有一个默认的构造方法。因此,可以直接new操作创建Person对象。

val person = new Person()
person.name_=("tom") // setter 方法
println(person.name) // getter 方法
person.name = "jonh" // 直接修改,其实调用的是person.name_=("jonh")
println(person.name) // getter 方法

1.2 getter setter

Scala 中可以定义自己的getter和setter方法,修改Person类代码如下

class Person {
  private var privateName: String = null // 定义私有成员

  def name = this.privateName // getter 方法

  def name_=(name: String): Unit = { // setter 方法
    this.privateName = name
  }
}

字节码文件

D:\code\study\scalademo\target\classes\com\harvey\classandobject>javap -p Person
警告: 二进制文件Person包含com.harvey.classandobject.Person
Compiled from "Person.scala"
public class com.harvey.classandobject.Person {
  private java.lang.String privateName;
  private java.lang.String privateName();
  private void privateName_$eq(java.lang.String);
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  public com.harvey.classandobject.Person();
}

上面我们定义了一个私有变量(private修饰),Scala会默认帮我们生成getter和setter方法,但是他们也是私有的,能直接使用的是我们自定义的getter和setter方法,如下

val person = new Person()
println(person.name) // null
person.name = "harvey"
println(person.name) // harvey

我们知道Scala中变量的修饰有两种,上面用的都是var,如果使用val修饰变量,scala则只会生成getter方法,如下

class Person {
  val name:String=null
}

字节码文件

D:\code\study\scalademo\target\classes\com\harvey\classandobject>javap -p Person
警告: 二进制文件Person包含com.harvey.classandobject.Person
Compiled from "Person.scala"
public class com.harvey.classandobject.Person {
  private final java.lang.String name;
  public java.lang.String name();
  public com.harvey.classandobject.Person();
}

如果将成员域定义为private[this],那么这个字段是对象私有的,这种情况下,不会生成getter和setter。对象私有字段,只能由当前对象的方法访问,而该类的其他对象的方法是无法访问的

class Person {
  private[this] val name:String=null
}

字节码文件:

D:\code\study\scalademo\target\classes\com\harvey\classandobject>javap -p Person
警告: 二进制文件Person包含com.harvey.classandobject.Person
Compiled from "Person.scala"
public class com.harvey.classandobject.Person {
  public com.harvey.classandobject.Person();
}

Scala中可以使用private[class-name]来指定可以访问该字段的类,class-name必须是当前定义的类,或者是当前定义的类的外部类。这种情况会生成getter和setter方法。

1.3 Bean 属性

Java中我们定义的JavaBean生成的都是getXXX()、setXXX()方法,Scala中生成的getter()和setter()不是这样的(上述示例中可以看出),如果也想和Java中一样,需要引入BeanProperty,然后使用采用注解的方式修饰变量。使用该注解后,将会生成4个方法:Scala的getter/setter和JavaBeans规范的getter/setter(如果是val声明,就没有setter部分了)

// 在Scala 2.10.0之后已被废弃
// import scala.reflect.BeanProperty
import scala.beans.BeanProperty
class Person {
  @BeanProperty var name:String = "john"
}

字节码文件

D:\code\study\scalademo\target\classes\com\harvey\classandobject>javap -p Person
警告: 二进制文件Person包含com.harvey.classandobject.Person
Compiled from "Person.scala"
public class com.harvey.classandobject.Person {
  private java.lang.String name;
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  public void setName(java.lang.String);
  public java.lang.String getName();
  public com.harvey.classandobject.Person();
}

2.类主构造器

2.1 主构造器定义

2.1.1 有参主构造器

主构造器的定义与类的定义交织在一直,将构造器参数直接放在类名称之后,如下代码

// 下列代码定义了类Person,还定义了参数为String、Int类型的主构造器
class Person (val name: String, val age: Int)

字节码文件

D:\code\study\scalademo\target\classes\com\harvey\classandobject>javap -p Person
警告: 二进制文件Person包含com.harvey.classandobject.Person
Compiled from "Person.scala"
public class com.harvey.classandobject.Person {
  private final java.lang.String name;
  private final int age;
  public java.lang.String name();
  public int age();
  public com.harvey.classandobject.Person(java.lang.String, int);
}

上述代码使用Java语言编写如下

public class Person{
  private final String name;
  private final int age;
  public Person(String name,int age){
       this.name=name;
       this.age=age;
  }
  public String getName(){ return name}
  public int getAge() {return age}
}

具体操作

object Test {
  def main(args: Array[String]): Unit = {
    val person = new Person("tom", 18)
    println("name=" + person.name + ",age=" + person.age) // 运行结果:name=tom,age=18
  }
}

2.1.2 无参主构造器

Scala中主构造器的定义可以有参数,也可以无参数,如下代码

// 下列代码定义了类Person,无参主构造器
class Person

字节码文件

D:\code\study\scalademo\target\classes\com\harvey\classandobject>javap -p Person
警告: 二进制文件Person包含com.harvey.classandobject.Person
Compiled from "Person.scala"
public class com.harvey.classandobject.Person {
  public com.harvey.classandobject.Person();
}

具体操作

object Test {
  def main(args: Array[String]): Unit = {
    val person = new Person() // 创建Person的实例
  }
}

2.1.3 使用主构造器进行初始化操作

主构造器会执行类中定义的所有语句,如在创建对象时,需要进行相关初始化操作时,可以将初始化语句放在类体中,同样也可以在类中添加或重写相关方法

class Person(val name: String, val age: Int) {
  println("init opertion...")

  def show(): Unit = {
    println("Hello Scala")
  }

  // 重写toString()方法
  override def toString() = "name = " + name + ",age = " + age
}

具体操作

object Test {
  def main(args: Array[String]): Unit = {
    val person = new Person("tom", 18)
    person.show()
    println(person.toString())
  }
}

运行结果

init opertion...
Hello Scala
name = tom,age = 18

2.2 主构造器参数

上面我们定义了有参主构造器,主构造器参数可以有默认值,如下代码

class Person(val name: String = "", val age: Int = 18) {

}

具体操作

object Test {
  def main(args: Array[String]): Unit = {
    // 使用主构造参数的默认值
    val person1 = new Person
    println("name = " + person1.name + ",age = " + person1.age)

    // 指定参数值
    val person2 = new Person("john", 20)
    println("name = " + person2.name + ",age = " + person2.age)
  }
}

2.3 主构造器参数访问控制

主构造器中的参数是可以加访问控制符的,我们先来看下不加参数情况

class Person(name: String, age: Int) {
  override def toString()= "name = " + name + ",age = " + age
}

等同于如下代码

class Person(private[this] val name: String,private[this] val age: Int) {
  override def toString()= "name = " + name + ",age = " + age
}

字节码文件

D:\code\study\scalademo\target\classes\com\harvey\classandobject>javap -p Person
警告: 二进制文件Person包含com.harvey.classandobject.Person
Compiled from "Person.scala"
public class com.harvey.classandobject.Person {
  private final java.lang.String name;
  private final int age;
  public java.lang.String toString();
  public com.harvey.classandobject.Person(java.lang.String, int);
}

默认参数的主构建器,参数带访问控制符号
从字节码文件中可以看到,当主构造器的参数不用var或val修饰的时候,参数会生成类的私有val成员,并且不会产生getter和setter方法

需要注意的是,将上述Person类中的toString()方法去掉,则类中无任何地方使用了主构造器的参数,此时主构造器参数不会生成类成员,修改代码如下

class Person(name: String, age: Int) {

}

字节码文件

D:\code\study\scalademo\target\classes\com\harvey\classandobject>javap -p Person
警告: 二进制文件Person包含com.harvey.classandobject.Person
Compiled from "Person.scala"
public class com.harvey.classandobject.Person {
  public com.harvey.classandobject.Person(java.lang.String, int);
}

2.4 禁用主构造器

在某些情况下,可能需要禁用主构建器,代码如下

class Person private(var name: String, var age: Int) {

}

字节码文件

D:\code\study\scalademo\target\classes\com\harvey\classandobject>javap -p Person
警告: 二进制文件Person包含com.harvey.classandobject.Person
Compiled from "Person.scala"
public class com.harvey.classandobject.Person {
  private java.lang.String name;
  private int age;
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  public int age();
  public void age_$eq(int);
  private com.harvey.classandobject.Person(java.lang.String, int);
}

3.辅助构造器

前面讲了,如果禁用掉了主构建器,则必须使用辅助构造函数来创建对象。辅助构造函数具有两个特点:
(1). 辅助构建器的名称为this,java中的辅助构造函数与类名相同,这常常会导致修改类名时出现不少问题,scala语言避免了这样的问题
(2). 调用辅助构造函数时,必须先调用主构造函数或其它已经定义好的构造函数。

3.1 类中只有辅助构造函数

如下Person类中只有辅助构造函数

class Person {
  // 类成员,私有
  private var name: String = null;
  private var age: Int = 18;
  private var gender: Int = 0;

  // 辅助构造器
  def this(name: String) {
    this()
    this.name = name
  }

  def this(name: String, age: Int) {
    this(name)
    this.age = age
  }

  def this(name: String, age: Int, gender: Int) {
    this(name, age)
    this.gender = gender
  }
}

字节码文件

D:\code\study\scalademo\target\classes\com\harvey\classandobject>javap -p Person
警告: 二进制文件Person包含com.harvey.classandobject.Person
Compiled from "Person.scala"
public class com.harvey.classandobject.Person {
  private java.lang.String name;
  private int age;
  private int gender;
  private java.lang.String name();
  private void name_$eq(java.lang.String);
  private int age();
  private void age_$eq(int);
  private int gender();
  private void gender_$eq(int);
  public com.harvey.classandobject.Person();
  public com.harvey.classandobject.Person(java.lang.String);
  public com.harvey.classandobject.Person(java.lang.String, int);
  public com.harvey.classandobject.Person(java.lang.String, int, int);
}

在定义辅助构造函数时,需要注意构造函数顺序,如下代码

class Person{
  // 类成员,私有
  private var name:String=null
  private var age:Int=18
  private var sex:Int=0

 // 辅助构造器
 def this(name:String,age:Int,sex:Int){
    this(name,age) // 此处会发生编译错误,这是因为def this(name:String,age:Int)没有被定义
    this.sex=sex
  }

  def this(name:String){
    this()
    this.name=name
  }
  def this(name:String,age:Int){
    this(name)
    this.age=age
  }
}

3.2 类中既有主构造器也有辅助构造函数

如下定义的Person类中,既有主构造器也有辅助构造函数

// 主构造器
class Person(var name: String, var age: Int) {

  // 类成员,私有
  private var gender: Int = 0

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

字节码文件

D:\code\study\scalademo\target\classes\com\harvey\classandobject>javap -p Person
警告: 二进制文件Person包含com.harvey.classandobject.Person
Compiled from "Person.scala"
public class com.harvey.classandobject.Person {
  private java.lang.String name;
  private int age;
  private int gender;
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  public int age();
  public void age_$eq(int);
  private int gender();
  private void gender_$eq(int);
  public com.harvey.classandobject.Person(java.lang.String, int);
  public com.harvey.classandobject.Person(java.lang.String, int, int);
}

3.3 禁用主构造器,使用辅助构造函数创建对象

上述主构造器中我们有提到,主构造器是有可能被禁用的,此时是能通过辅助构造函数来创建对象

// 禁用主构造器
class Person private(var name: String, var age: Int) {

  // 类成员,私有
  private var gender: Int = 0

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

字节码文件

D:\code\study\scalademo\target\classes\com\harvey\classandobject>javap -p Person
警告: 二进制文件Person包含com.harvey.classandobject.Person
Compiled from "Person.scala"
public class com.harvey.classandobject.Person {
  private java.lang.String name;
  private int age;
  private int gender;
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  public int age();
  public void age_$eq(int);
  private int gender();
  private void gender_$eq(int);
  private com.harvey.classandobject.Person(java.lang.String, int);
  public com.harvey.classandobject.Person(java.lang.String, int, int);
}

猜你喜欢

转载自blog.csdn.net/HG_Harvey/article/details/82499938