首页 > 代码库 > Beginning Scala study note(3) Object Orientation in Scala

Beginning Scala study note(3) Object Orientation in Scala

1. The three principles of OOP are encapsulation(封装性), inheritance(继承性) and polymorphism(多态性).
example:

class Shape{ def area: Double = 0.0 } # supertype
# subtypes
class Rectangle(val width: Double, val height: Double) extends Shape{
override def area: Double = width*height
}
class Circle(val radius: Double){
override def area: Double = math.Pi*radius*radius
}

 A subtype is guaranteed to have all the members of the supertype. Changing the implementation of a method of the supertype is called overriding. In encapsulation, the fields of an object are accessible only through its methods

example:

def draw(s: Shape)
val circle = draw(new Circle(3))
val rectangle = draw(new Rectangle(2,3))

 Inheritance guarantees that any method we could call on an instance of Shape will be defined in the subtypes. This is the heart of polymorphism.

2. Classes and Objects
(1) example:

scala> class Book # defined Book class, corresponding to java declaration: public class Book{ }
defined class Book
scala> :javap -c Book # decompiling the Book class
Compiled from "Book.scala"
public class Book implements scala.ScalaObject {
public Book();
Code:
0: aload_0 
1: invokespecial #10; // Method java/lang/Object."<init>":()V
4: return 
}
scala> new Book # create an object or an instance of Book
res1: Book = Book@9206d91
scala> new Book()
res2: Book = Book@3d189f33

 (2) Constructors

  1) Constructors with Parameters
    a. Parameter Declared as a val
    In this case, Scala generates only a getter method for it, a setter method is not generated.

scala> class Book(val title: String)
scala> :javap -c Book
Compiled from "Book.scala"
public class Book implements scala.ScalaObject {
public java.lang.String title();
//...... 
public Book(java.lang.String);
//......
}

     Scala generates a title method, which you can use to access the field. The value of the field is immutable by definition, because of val type.

scala> val book = new Book("Beginning Scala")
book: Book = Book@39a02a5d
scala> book.title
res2: String = Beginning Scala
scala> book.title = "new title"
<console>:9: error: reassignment to val
book.title = "new title"
^

     In Scala, if the constructor or method takes zero parameters, you can omit the parameter list.

    b. Parameter Declared as a var
    In this case, Scala generates both setter and getter method.

scala> class Book(var title: String)
scala> :javap -c Book
Compiled from "Book.scala"
public class Book implements scala.ScalaObject {
public java.lang.String title();
// ...... 
public void title_$eq(java.lang.String);
// ...... 
public Book(java.lang.String);
// ...... 
}

     If you decompile this code, you can see the generated mutator method with an unusual name, title_$eq

scala> val book = new Book("Beginning Scala")
book: Book = Book@5b375aff
scala> book.title
res0: String = Beginning Scala
scala> book.title = "new title"
book.title: String = new title

     c. Parameter Declared as a private val or var

    In this case, it can prevent getter and setter methods from being generated. The fields could only be accessed from within members of the class.

scala> class Book(private var title: String)
scala> :javap -c Book
Compiled from "Book.scala"
public class Book implements scala.ScalaObject {
public Book(java.lang.String);
// ...... 
}
scala> class Book(private var title: String){
| def printTitle {println(title)}
| }
defined class Book
scala> val book = new Book("Beginning Scala")
book: Book = Book@70f19734
scala> book.title
<console>:10: error: variable title in class Book cannot be accessed in Book
book.title
^
scala> book.printTitle
Beginning Scala

     d. Parameter Declared without val or var

    Scala does not generate getter or setter when neither val nor var are specified on constructor parameters.

scala> class Book(title: String)
defined class Book
scala> val book = new Book("Beginning Scala")
book: Book = Book@1222007
scala> book.title
<console>:10: error: value title is not a member of Book
book.title
^

     The different Parameter with val and Parameter without val/var:

scala> class Book(private val title: String){
| def printTitle(b: Book){ 
| println(b.title)
| }
| }
defined class Book
scala> val book = new Book("Beginning Scala")
book: Book = Book@659a1780
scala> book.printTitle(book)
Beginning Scala
scala> book.printTitle(new Book("Beginning Erlang"))
Beginning Erlang

     You can change the title of the Book because the title is a private field that is accessible to "this " object and the other objects of Book.

class Book(title: String){
  def printTitle(b: Book){
    println(b.title)
  }
}
scala> scala Book.scala
Book.scala:3: error: value title is not a member of this.Book
println(b.title)
^
one error found

     You can provide a default value for a constructor parameter that gives other classes the option of specifying that parameter when calling the constructor.

scala> class Book(val title: String = "Beginning Scala")
defined class Book
scala> val book = new Book # you can call the constructor without specifying a title
book: Book = Book@7a0ad359
scala> book.title
res0: String = Beginning Scala
scala> val book = new Book("new title") # you can specify the title value
book: Book = Book@23a79657
scala> book.title
res2: String = new title
scala> val book = new Book(title="Beginning Scala") # you can also choose to provide named parameters
book: Book = Book@5f6af31
scala> book.title
res3: String = Beginning Scala

   2) Auxiliary Constructor(辅助构造函数)

  Auxiliary constructors are defined by creating methods named "this". In this way, you can define multiple auxiliary constructors, but they must have different signatures. Each auxiliary must begin with a call to a previously defined constructor.

scala> class Book(var title: String, var ISBN: Int){
| def this(title: String){
| this(title,222)
| }
| def this(){
| this("Beginning Scala")
| this.ISBN=111
| }
| override def toString = s"$title ISBN- $ISBN"
| }
defined class Book
scala> val book1 = new Book
book1: Book = Beginning Scala ISBN- 111
scala> val book2 = new Book("Beginning Erlang")
book2: Book = Beginning Erlang ISBN- 222
scala> val book3 = new Book("Beginning Clojure",2222)
book3: Book = Beginning Clojure ISBN- 2222

   (3) Method Declaration

    1) It is recommended to use type inferencing judiciously(明智而谨慎地); if it‘s not immediately obvious what the return type is, declare it explicitly.

def myMethod(): String = "Moof"
def myOtherMethod() = "Moof"
def foo(a: Int):String = a.toString
def f2(a: Int, b: Boolean):String = if(b) a.toString else "false"

     2) You can pass the type of a parameter or the return type as a parameter. The following code takes a parameter p and a type parameter T and returns a List of T.

scala> def list[T](p:T):List[T]=p::Nil
list: [T](p: T)List[T]
scala> list(10)
res9: List[Int] = List(10)
scala> list("Hello")
res10: List[String] = List(Hello)
scala> list(List("1"))
res11: List[List[String]] = List(List(1))

     3) If the last parameter is a variable-length argument(可变长度参数), it is a Seq of the type of the variable-length argument, so in this case the as parameter is a Seq[Int]:

scala> def largest(as: Int*): Int = as.reduceLeft((a,b)=> a max b)
largest: (as: Int*)Int
scala> largest(1)
res13: Int = 1
scala> largest(2,3,99)
res14: Int = 99
scala> largest(33,22,33,22)
res15: Int = 33

     You can mix type parameters with variable-length arguments:

scala> def mkString[T](as: T*):String = as.foldLeft("")(_ + _.toString)
mkString: [T](as: T*)String
scala> mkString(2)
res31: String = 2
scala> mkString(1,2,3)
res32: String = 123
scala> mkString(1,2,3.0)
res33: String = 1.02.03.0

     And you can put bounds on the type parameters. In this case, the types that are passed in must be Number or a subclass of Number:

scala> def sum[T <: Number](as: T*): Double = as.foldLeft(0d)(_ + _.doubleValue)
sum: [T <: Number](as: T*)Double
scala> sum()
res50: Double = 0.0

     4) Methods can be declared within any code scope, except at the top level, where classes, traits, and objects are declared. Methods can reference any variables in their scope.

import java.io.BufferedReader
def readLines(br: BufferedReader) = {
  var ret: List[String] = Nil
  def readAll(): Unit = br.readLine match {
    case null =>
    case s => ret ::= s ; readAll()
  }
  readAll()
  ret.reverse
}

     The readAll method is defined inside the scope of the readLines method, Thus readAll method has access to the variables br and ret. The readAll method calls a method on br, and it updates ret.

    5) Method that override declared methods must include the override modifier, except overriding abstract methods.

scala> abstract class Base{
| def thing: String
| }
defined class Base
scala> class One extends Base{
| def thing = "Moof"
| }
defined class One
scala> val one = new One
one: One = One@1bb62bd
scala> one.thing
res1: String = Moof
scala> class Base{
| def thing: String = ""
| }
defined class Base
scala> class One extends Base{
| def thing: String = "Moof"
| }
<console>:9: error: overriding method thing in class Base of type => String;
method thing needs `override modifier
def thing: String = "Moof"
^

     Methods that take no parameters and variables can be accessed the same way, and a val can override a def in a superclass.

scala> class Two extends One{
| override val thing = (new java.util.Date).toString
| }
defined class Two
scala> val two = new Two
two: Two = Two@38ec38ad
scala> two.thing
res3: String = Tue Jun 28 16:24:51 CST 2016
scala> class Three extends One{
| override lazy val thing = super.thing + (new java.util.Date).toString
| }
defined class Three
scala> val three = new Three
three: Three = Three@779122a0
scala> three.thing
res4: String = MoofTue Jun 28 16:26:14 CST 2016

   (4) Code Blocks

  1) Methods and variables also can be defined in code blocks that are denoted by curly braces:{ }. Code blocks may be nested. The result of a code block is the last line evaluated in the code block.

scala> def meth3(): String = {"Moof"}
meth3: ()String
scala> meth3
res5: String = Moof
scala> def meth4(): String = {
| val d = new java.util.Date()
| d.toString
| }
meth4: ()String
scala> meth4
res6: String = Tue Jun 28 16:42:29 CST 2016

   Variable definitions can be code blocks as well.

scala> val x3:String = {
| val d = new java.util.Date
| d.toString
| }
x3: String = Tue Jun 28 16:45:28 CST 2016

   (5) Call-by-Name

  1) Scala gives an additional mechanism for passing parameters to methods( and functions): call-by-name, which passes a code block to the callee.

scala> def nano() = {
| println("Getting nano")
| System.nanoTime
| }
nano: ()Long
scala> nano
Getting nano
res7: Long = 9437790603685212
scala> def delayed(t: => Long) = { # call by name parameter(按名调用参数)
| println("In delayed method")
| println("Param:"+t)
| t
q | }
delayed: (t: => Long)Long
scala> delayed(nano)
In delayed method
Getting nano
Param:9438187859989956
Getting nano
res10: Long = 9438187860305135

   This indicates that delayed is entered before the call to nano and that nano is called twice.

scala> def notDelayed(t: Long) = {
| println("In notDelayed method")
| println("Param: "+t)
| t
| }
notDelayed: (t: Long)Long
scala> notDelayed(nano)
Getting nano
In notDelayed method
Param: 9438465403584919
res11: Long = 9438465403584919

   nano is called before notDelayed is called because the parameter nano is calculated before notDelayed is called. This is the way Java programmers expect code to work.

  (6) Method Invocation(方法调用)

instance.method() # java dot notation
instance.method # if a method does not take any parameters
instance.method(param) # a single parameter in java
instance.method param # methods that take a single parameter can be invoked without dots or parentheses
instance.method(p1,p2) # invoke multiparameter methods
instance.method[Type Param](p1,p2) # the type param will be inferred by the compiler, also you can explicitly pass

   Because Scala allows method names to contain symbols such as +,-,*,?, Scala‘s dotless method notation creates a syntactically neutral way of invoking methods that are hard-coded operators in Java.

scala> 2.1.*(4.3)
res10: Double = 9.03
scala> 2.1*4.3
res11: Double = 9.03

   (7) Objects

  In Scala, you can use object to refer to an instance of a class or a keyword.
  1) Singleton Objects
  Scala does not have static members. Instead, Scala has singleton objects. A singleton is a class that can have only one instance.

scala> object Car{ # define car object
| def drive {println("drive car")}
| }
defined module Car
scala> Car.drive
drive car
scala> object Main extends App{ # Call car methods
| Car.drive
| }
defined module Main

   Singleton objects cannot take parameter. There are two ways to create a launching point for your application: define an object with a properly defined main method or define an object that extends the App trait.

object Book extends App{
    println("Hello World")    
}

   Note that in both approaches, Scala applications are launched from an object, not a class.

  2) Companion Objects(伴生对象)
  When an object shares a name with a class, it‘s called a companion object, and the class is called a companion class. A companion object is an object that shares the same name and source file with another class or trait. Using this approach lets you create static members on a class. The companion object is useful for implementing helper methods and factory.
  We use a companion class Shape and a companion object Shape, which acts as a factory.

trait Shape {
     def area: Double
}
object Shape{ 
    private class Circle (radius: Double) extends Shape{ 
        override val area = 3.14*radius*radius 
    } 
    private class Rectangle(height: Double, length: Double) extends Shape{ 
        override val area = height * length 
    }  
    def apply(height: Double, length: Double): Shape = new Rectangle(height,length) 
    def apply(radius: Double): Shape = new Circle(radius)
}    

   A singleton object that does not share the same name with a companion class is called a standalone object.

  (8) Packaging and Imports
  1) The following statement imports the contents of the scala.xml package:  

import scala.xml._

   Import statements are made in the scope of prior imports. The following statement imports the scala.xml.transform._ package:

import transform._

   You can import more than one class or object from a single package, for example:

import sclala.collection.immutable.{TreeMap, TreeSet}

   You can import a class or object and rename it.

import scala.util.parsing.json.{JSON=> JsonParser}

  Import can be used inside any code block, and the import will be active only in the scope of that code block.

scala> class Frog{ 
| import scala.xml._ 
| def n:NodeSeq = NodeSeq.Empty 
| }
defined class Frog

  Scala‘s import statement can also import the methods of an object so that those methods can be used without explicit reference to the object that owns them.This is much like Java‘s static import.

scala> object Moose{ 
| def bark = "woof" 
| }
defined object Moose
scala> import Moose._
import Moose._
scala> bark
res2: String = woof

   (9) Inheritance

  1) Scala supports single inheritance.

scala> class Vehicle(speed: Int){ 
| val mph: Int = speed 
| def race() = println("Racing") 
| }
defined class Vehicle

   The different extending class between Scala and Java are: 

  a. method overriding requires the override keyword, except trait
  b. only the primary constructor can pass parameters to the base constructor.

scala> class Car(speed: Int) extends Vehicle(speed){ 
| override val mph = speed
| override def race() = println("Racing Car") 
| }
defined class Car
scala> class Bike(speed: Int) extends Vehicle(speed){ 
| override val mph: Int = speed 
| override def race() = println("Racing Bike") 
| }
defined class Bike
scala> val vehicle1 = new Car(200)
vehicle1: Car = Car@1cfe29c
scala> vehicle1.race
Racing Car
scala> vehicle1.mph
res4: Int = 200
scala> val vehicle2 = new Bike(20)
vehicle2: Bike = Bike@1973547
scala> vehicle2.mph
res5: Int = 20
scala> vehicle2.race
Racing Bike

   (10) Traits

  In Scala, when a class inherits from a trait, it implements the interface of the trait, and inherits all the code contained in the trait.

scala> trait flying{ 
| def fly() = println("flying") 
| }
defined trait flying
scala> trait gliding{ 
| def glide() = println("gliding")
| }
defined trait gliding

   Now you can create the Batmobile class extends Vehicle class along with the flying and gliding traits.

scala> class Batmobile(speed: Int) extends Vehicle(speed) with flying with gliding{ 
| override val mph: Int = speed 
| override def race() = println("Racing Batmobile") 
| override def fly() = println("Flying Batmobile") 
| override def glide() = println("Gliding Batmobile")
| }
defined class Batmobile

   n Scala, traits can inherit classes. The keyword extends is used: when a class inherits a trait, when the class mixes in other traits using the with keyword, when one trait is the child of another trait or class.

  Now create a list of vehicles, then you can use the maxBy method provided by Scala collections library to find the fastest vehicle in the list.

scala> val vehicleList = List(vehicle1, vehicle2, batmobile)
vehicleList: List[Vehicle] = List(Car@1cfe29c, Bike@1973547, Batmobile@8c2321)
scala> val fastestVehicle = vehicleList.maxBy(_.mph)
fastestVehicle: Vehicle = Batmobile@8c2321

   (11) Case Classes

  A case class provides the same facilities as a normal class, but the compiler generates toString, hasCode, and equals methods(which you can override). Case classes can be instantiated without the use of the new statement. By default, all the parameters in the case class‘s constructor become properties on the case class.

scala> case class Stuff(name: String, age: Int) 
defined class Stuff
scala> val s = Stuff("David",45) # create an instance of Stuff without the keywod new(you can use new if you want)
s: Stuff = Stuff(David,45)
scala> s.toString # case classs toString method does the right thing
res0: String = Stuff(David,45)
scala> s == Stuff("David",45) # equal methods does a deep comparison
res1: Boolean = true
scala> s.name # the instance has properies
res2: String = David
scala> s.age
res3: Int = 45
class Stuff(val name: String, val age: Int){ 
  override def toString = "Stuff("+name+","+age+")"
  override def hashCode = name.hashCode + age
  override def equals(other: AnyRef) = other match {
    case s: Stuff => this.name == s.name && this.age == s.age
    case _ => false
  }
}
object Stuff {
  def apply(name String, age: Int) = new Stuff(name,age)
  def unapply(s: Stuff) = Some((s.name,s.age))
}

   (12) Value Classes

  With value classes, Scala allows user-defined value classes that extend AnyVal. Value classes are a new mechanism in Scala to avoid allocating runtime objects. Value classes allow you to add extension methods to a type without the runtime overhead of creating instances.

Class SomeClass(val underlying: Int) extends AnyVal

   SomeClass has a single, public val parameter that is the underlying runtime representation. The type at compile time is SomeClass, but at runtime, the representation is an Int. A value class can defines defs, but no vals, vars, or nested traits classes or objects.

# wrap the Int parameter and encapsulates a twice method.
scala> class SomeClass(val i: Int) extends AnyVal{
 | def twice() = i*2 
| }
defined class SomeClass
scala> val v = new SomeClass(9)
v: SomeClass = SomeClass@9
scala> v.twiceres5: Int = 18
scala> :javap –c SomeClass
…...
SomeClass$.twice$extension # at runtime, the expression
a static object 
…...

   One use case for value classes is to combine them with implicit classes. Using an implicit class provides a more convenient syntax for defining extension methods, while value classes remove the runtime overhead.

  (13) Scala verus Java
  The uniformly of instances in Scala means that the developer does not have to perform special tests or to have different code paths to deal with primitive types.

scala> 1.hashCode
res6: Int = 1 scala> 2.toString
res7: String = 2 scala> def with42(in: Int => Int) = in(42) # define a method that takes a function that transforms an Int to an Int
with42: (in: Int => Int)Int
scala> with42(33+)
res8: Int = 75

 


















Beginning Scala study note(3) Object Orientation in Scala