#

总述 #

类在面向对象编程中是一个最基本的概念。类是对象的模板,用于产生具有相同结构的对象。一个类通常由属性和行为构成。

Java #

定义一个类 #

Java 中使用关键字 class 来定义类

public class Person {
    //  属性
    private int age;
    //  行为
    public void say(String message) {
        System.out.println(message);
    }
}

新建一个类的实例则使用关键字 new

Person person = new Person();

Setter 与 Getter #

一般来说由于类的封装性以及 Java Bean 的规约,在 Java 中通常将属性声明为私有属性,然后提供公有的 Setter 和 Getter 方法来修改和访问属性。

最常见的 Setter

public void setAge(final int age) {
    this.age = age;
}

最常见的 Getter

public int getAge() {
    return age;
}

构造方法 #

构造方法在类中用于构造对象,提供一些在对象创建时就应该被赋予的属性。在 Java 中,默认每个类都有一个无参的构造方法,但是如果你已经在类中定义了构造方法,那么默认的无参构造方法就不会被创建。

在 Java 中,一般都会提供了一个可以为所有公开属性赋值的构造方法,然后重载只有定义属性的构造方法,并且在这些构造方法中调用拥有所有公开属性的构造方法。

public class Man {

    private String name;
    private int age;
    private final String from = "USA";
    private String description = "none";

    public Man() {
    }

    public Man(String name, int age, String description) {
        this.age = age;
        this.description = description;
        this.name = name;
    }

    public Man(String name, int age) {
        this(name, age, null);
    }

    public Man(int age) {
        this(null, age);
    }

    public Man(String name) {
        this(name, 0);
    }
}

基于构造方法构建实例

Man fred = new Man("Fred", 21);
Man peter = new Man("Peter");
Man jack = new Man(21);

可以看到,如果一个类属性很多并且在构造时有多种途径时,那么重载构造方法会是一种非常繁琐无聊的工作。

不可变对象 #

不可变对象在并发编程中占有非常重要的地位。但是Java 中并没有提供用于产生不可变对象的类,你必须手动定义才行。最简单的定义方法就是将类的所有属性声明为 private final 并且在构造方法中构造所有属性,且不提供任何 Setter 方法,Getter 方法只返回基本类型数据或者引用类型数据的不可变形态或副本。

public class ImmutableSong {
    private final String name;
    private final String artist;
    private final Date publishDate;

    public ImmutableSong(final String name,
                         final String artist,
                         final Date publishDate) {
        this.artist = artist;
        this.name = name;
        this.publishDate = publishDate;
    }

    public String getArtist() {
        return artist;
    }

    public String getName() {
        return name;
    }

    public Date getPublishDate() {
        return new Date(publishDate.getTime());
    }
}

Groovy #

定义一个类 #

Groovy 中也使用关键字 class 来定义类

class Person {
    //  属性
    def age
    //  行为
    def say(message) {
        println(message)
    }
}

新建一个类的实例同 Java

def person = new Person()

Setter 与 Getter #

与 Java 不同,默认当你在 Groovy 类中定义了属性的同时 Groovy 提供了对应的 Setter 与 Getter 方法,且当你直接使用属性时其实就是调用这些方法。

def person = new Person()
//  调用属性修改 age
person.age = 10
assert person.age == 10

//  调用方法修改 age
person.setAge(12)
assert person.age == 12

可以看到为 Person 类定义了属性 age 后,无论直接调用属性还是调用方法都改变了 age 的值。

实际上以上的 Person 会被翻译成如下代码

public class Person implements GroovyObject {
    private Object age;
 
    public Person() {
        CallSite[] var1 = $getCallSiteArray();
        MetaClass var2 = this.$getStaticMetaClass();
        this.metaClass = var2;
    }

    public Object getAge() {
        return this.age;
    }

    public void setAge(Object var1) {
        this.age = var1;
    }
}

当然你也可以像 Java 一样在类中自定义 Setter 和 Getter 方法

class Person {

    private def privateAge

    //  自定义 privateAge 的 Setter 和 Getter 方法
    def getPrivateAge() {
        return privateAge
    }

    def setPrivateAge(n) {
        if (n > privateAge) {
            privateAge = n
        }
    }
}

调用自定义的 Setter 和 Getter

person.privateAge = 20
person.privateAge = 14
assert person.age == 20

当我们再次修改 privateAge 的值为 14 时,由于自定义的 Setter 方法不允许年龄被减少,所以这次修改并没成功。这一例子也证明了调用属性其实是调用方法。

Groovy 中当你定义了一个没有访问控制符的属性时,语言会自动提供 Setter 和 Getter 方法,此时这种属性在 Groovy 中被称作为 Properties。而如果属性本身就声明为 public 的话,则不会提供 Setter 和 Getter,这种属性被称作 Fields。本系列中都统一称作为属性,因为 Public Fields 本身就并不建议使用。

构造方法 #

Groovy 中不用像 Java 那样手动创建一个个构造方法,Groovy 会自动为我们生成。

class Man {
    def name
    def age
    private def from = "USA"
    def description = "none"
}

在创建时可以通过 propertyName: propertyValue 指定使用哪个属性来构造实例

def fred = new Man(name: "Fred", age: 21)
def peter = new Man(name: "Peter")
def jack = new Man(age: 21)
def terry = new Man(from: "LA", description: "A man from LA.")

以上四个实例都可以正常使用,如果细心的话,你会发现 from 被声明为 private,虽然无法通过对象进行修改,但是通过构造方法修改它的值却是可能的,很不幸这也是 Groovy 的一个历史遗留 Bug。

不可变对象 #

Groovy 中可以为一个类添加注解 @Immutable 来表明该类的所有属性都不可变。一旦一个类声明为 @Immutable 则此类的所有属性必须明确指定类型而不能使用 def 来定义。

@Immutable
class ImmutableSong {
    String name
    String artist
    Date publishDate
}

Scala #

定义一个类 #

Scala 中也使用关键字 class 来定义类,但是类的属性必须明确指明初始值,而不是像 Java 和 Groovy 一样有默认值。

class Person {
    //  属性
    var age = 0
    //  行为
    def say(message: String) {
      println(message)
    }
}

新建一个类的实例同 Java,但是如果调用无参构造方式时可以省略小括号

val person = new Person()

//  或

val person = new Person

Scala 类文件中可以定义多个类。

Setter 与 Getter #

与 Groovy 一样,Scala 也提供了默认 Setter 和 Getter 方法,只是这些方法的名字比较特别。Scala 中 默认 Setter 方法名为 属性名_=,Getter 方法名为 属性名。同 Groovy 一样,当你直接使用属性时其实就是调用这些方法。

val person = new Person
//  修改 age,实际调用的是 person.age_=(10)
person.age = 10

//  读取 age,实际调用的是 person.age()
println(person.age)

当然你也可以在类中自定义 Setter 和 Getter 方法,且自定义的方式与 Groovy 完全不一样。

Scala 中自定义 Setter 和 Getter 的步骤如下

  • 声明属性为 private
  • 提供修改该属性的 Setter 方法 _=
  • 提供访问该属性的 Getter 方法

class Person {

  private var privateAge = 0

  def trueAge = privateAge
  def trueAge_=(pAge: Int) {
    if (pAge > privateAge) {
      privateAge = pAge
    }
  }
}

调用自定义的 Setter 和 Getter

person.trueAge = 20
person.trueAge = 14
println(person.trueAge) //20

如果一个属性被声明为值的话,则 Scala 只会为其提供默认的 Getter 方法,而没有 Setter 方法。

val birthday = new Date()

对象私有域 #

对象私有域是一个 Scala 比较特别的地方,它可以控制一个属性只能在该对象内部调用。

可能理解起来比较困难,我们先看一个一般的例子

class Person {
  var age = 0
  def isYounger(other: Person) = age < other.age
}

在以上代码中,isYounger() 方法接收类的另一个实例,并且将这个实例的 age 属性与当前实例的 age 属性进行比较。如果使用对象私有域修饰 age 的话则isYounger() 可以接收另一个实例,但无法调用传入的实例的 age 属性,只能调用自己 age 属性。

这种对象私有域在 Scala 中使用 private[this] 来声明

class Person {
  private[this] var age = 0
  //    此时以下调用方式就会报错
  //    def isYounger(other: Person) = age < other.age
}

构造方法 #

Scala 中构造方法分为主构造方法和副构造方法。

主构造方法 #

主构造方法紧跟在类的声明之后

class Man(val name: String, var age: Int){}

主构造方法的参数可以声明为 valvar ,使用方法与其声明为成员变量时相同。

此外,类中的所有可执行语句都属于主构造器,在对象被创建时都会被调用。 所以以上类可以改写为

class Man(val name: String, var age: Int){
    println("Sentences in Main Constructor")
}

每创建一个 Man 的实例,语句 “Sentences in Main Constructor” 都会被打印。

副构造方法 #

副构造方法使用 this() 声明。所有副构造方法必须在第一行调用主构造方法或其它副构造方法。

//  主构造方法
class Man(val name: String, var age: Int) {

  //  副构造方法
  def this(name: String) {
    //  调用主构造方法
    this(name, 0)
  }

  def this(age: Int) {
    //  调用主构造方法
    this("Default Name", age)
  }

  def this() {
    //  调用其它副构造方法 
    this("Default Name")
  }
}

创建实例时可以使用主构造方法也可以使用副构造方法

val fred = new Man("Fred", 21) // Main Constructor
val peter = new Man("Fred") //  Slave Constructor
val jack = new Man(21) //  Slave Constructor

如果声明时不添加 valvar 修饰符则其相当于声明了一个对象私有域。

class Man(val name: String,
          var age: Int,
          description: String) {}

深入构造方法 #

在使用构造方法时我们也可以为每个参数指定默认值

class Man(val name: String = "Default Name",
          var age: Int = 0){}

在声明构造方法的参数时我们也可以像在类中添加成员一样添加限定符

class Man(val name: String,
          var age: Int,
          private var from: String = "USA") {}

也可以不加任何限定符,此时就相当于定义了一个对象私有域

class Man(val name: String,
          var age: Int,
          description: String = "none") {}

私有主构造方法 #

我们可以为主构造方法添加关键字 private 从而声明主构造方法为私有权限,此时类只能通过副构造方法进行创建。

class Woman private(val name: String, val age: Int) {
  def this(name: String) {
    this(name, 0)
  }
}

//  Wrong!! 无法调用私有主构造方法
//  val mary = new Woman("Mary",21)

val jane = new Woman("Jane")

Java 风格的 Bean #

如果希望生成的 Bean 也能被 Java 代码使用,那么Scala 允许你生成 Java 风格的 Setter 与 Getter。只需要在指定的属性上添加注解 BeanProperty ,在Boolean 型属性上添加注解 BooleanBeanProperty

class Model {

  @BeanProperty
  var name = ""

  @BooleanBeanProperty
  var visible = false
}

不可变对象 #

Scala 并没有直接提供这样一种类(尽管样本类之类的看起来有那么点像)。但是由于 Scala 本身提供了大量不可变类作为默认实现,且 Scala 官方建议总是使用 val 声明变量,var 只应该用在不得不的情况下,所以你只要遵守这些规定那么编写不可变对象也不是件难事。

Kotlin #

定义一个类 #

Kotlin 同 Scala 一样使用关键字 class 来定义类,同时类的属性必须明确指明初始值。

class Person {
    //  属性
    var age = 0
    //  行为
    fun say(message: String) {
      println(message)
    }
}

新建一个类的实例无需像 Java 一样使用 new 关键字

val person = Person()

Kotlin 文件中可以定义多个类。

Setter 与 Getter #

与 Scala 一样提供了默认 Setter 和 Getter 方法,且默认的 Setter 方法名为 set(),Getter 方法名为 get()。当你直接使用属性时其实就是调用这些方法。

val person = Person()
//  修改 age
person.age = 10

//  读取 age
println(person.age)

Kotlin 中自定义 Setter 和 Getter 与其它语言都不一样,需要紧跟着属性名声明 set()get()

class Person {

    var trueAge: Int
        get() = age
        set(pAge) {
            if (pAge > age) {
                age = pAge
            }
        }
}

调用自定义的 Setter 和 Getter

person.trueAge = 20
person.trueAge = 14
println(person.trueAge) //20

如果一个属性被声明为值的话,则 Kotlin 只会为其提供默认的 Getter 方法,而没有 Setter 方法。

val birthday = Date()

你也可以仅仅自定义 Setter 和 Getter 的访问权限而不是实现

var setterVisiblity: String = "foo"
    private set

Backing 域 #

由于无法直接访问属性,调用属性实质就是调用方法,所以如果上节改写为如下形式的话会陷入自己调用自己的无限循环中

var age: Int
    get() = age
    set(pAge) {
        if (pAge > age) {
            age = pAge
        }
    }

为此,Kotlin 提供了 Backing 域来直接访问,以前的 Backing 域是以 $ 开头的特殊字符串,但是从 1.0 开始 Backing 域就是关键字 field

var backAge: Int = 0
    set(pAge) {
        if (pAge > field) {
            field = pAge
        }
    }

构造方法 #

Kotlin 中构造方法也分为主构造方法和副构造方法,只不过 Scala 中称为 “Main Constructor” 和 “Slave Constructor”,而 Kotlin 中称为 “Primary Constructor” 和 “Secondary Constructor”。

主构造方法 #

主构造方法紧跟在类的声明之后

class Man(val name: String, var age: Int){}

主构造方法的参数可以声明为 valvar ,使用方法与其声明为成员变量时相同。

此外,类中可以声明 init 语句块,该语句块中的所有可执行语句都属于主构造器,在对象被创建时都会被调用。 所以以上类可以改写为

class Man(val name: String, var age: Int){
    init {
        println("Sentences in primary constructor")
    }
}

每创建一个 Man 的实例,语句 “Sentences in Main Constructor” 都会被打印。

副构造方法 #

副构造方法使用 constructor() 声明。所有副构造方法都必须调用主构造方法或其它副构造方法。

//  主构造方法
class Man(val name: String, var age: Int) {

    //  副构造方法
    //  调用主构造方法
    constructor(name: String) : this(name, 0) {
    }

    //  调用主构造方法
    constructor(age: Int) : this("Default Name", age) {
    }

    //  调用其它副构造方法 
    constructor() : this("Default Name") {
    }
}

创建实例时可以使用主构造方法也可以使用副构造方法

val fred = Man("Fred", 21) // Primary Constructor
val peter = Man("Fred") //  Secondary Constructor
val jack = Man(21) //  Secondary Constructor

深入构造方法 #

在使用构造方法时我们也可以为每个参数指定默认值

class Man(val name: String = "Default Name",
          var age: Int = 0){}

在声明构造方法的参数时我们也可以像在类中添加成员一样添加限定符

class Man(val name: String,
          var age: Int,
          private var from: String = "USA") {}

私有主构造方法 #

我们可以为主构造方法添加关键字 private constructor 从而声明主构造方法为私有权限,此时类只能通过副构造方法进行创建。

class Woman private constructor(val name: String, val age: Int) {
    constructor(name: String) : this(name, 0) {
    }
}

//  Wrong!! 无法调用私有主构造方法
//  val mary = Woman("Mary",21)

val jane = Woman("Jane")

Empty 类 #

Kotlin 中允许声明一个没有类体的类用于仅仅表示类型

class Empty

不可变对象 #

Kotlin 中有一些方法可以实现部分功能,但是语言本身也没有直接提供这样一种类。


项目源码见 JGSK/_15_class

沪ICP备17055033号-2