1. 程式人生 > >Scala入門到精通——第八節 包和引入

Scala入門到精通——第八節 包和引入

本節主要內容

  1. 包的作用與定義
  2. 包的作用域與引入(import)的使用方法
  3. 訪問控制
  4. 包物件
  5. import高階特性
  6. 內部類

包的作用與定義

同java中的包、C++中的名稱空間一樣,Scala中的包主要用於大型工程程式碼的組織同時也解決命名衝突的問題。Scala中的包與java有著諸多的相似之處,但Scala語言中的包更加靈活。

//將程式碼組織到cn.scala.xtwy包中
package cn.scala.xtwy

abstract class Animal {
  //抽象欄位(域)
  var height:Int
  //抽象方法
  def eat:Unit
}

class
Person(var height:Int) extends Animal{
override def eat()={ println("eat by mouth") } } object Person extends App{ new Person(10).eat() }

上述包也可通過下面的包定義方式進行:

//下面的程式碼定義了一個cn.scala.xtwy包
//在程式的任何地方都可以通過cn.scala.xtwy.Teacher來使用Teacher這個類
package cn{
  package scala{
    package xtwy{
      class
Teacher {
} } } }

將上面的程式碼命名為Teacher.scala,程式碼組織如下:
這裡寫圖片描述

可以看到我們將程式碼放在預設包下面,但編碼後的位元組碼檔案被自動組織到cn.scala.xtwy資料夾當中,如下圖
這裡寫圖片描述
可以看出,我們可以在任何地方進行包中類的定義,scala幫助我們進行自動檔案組織

我們將Teacher.scala內容修改如下:


package cn{
  package scala{
    package xtwy{
      class Teacher {

      }
    }
  }
}
//添加了cn.scala.spark包,包中定義了一個SparkDemo類
package cn{ package scala{ package spark{ class SparkDemo{ } } } }

下圖給出的是Teacher.scala編譯後產生的包檔案:
這裡寫圖片描述

通過前面的介紹,我們知道了如何定義包,包是怎麼組織程式碼的。在實際開發過程當中,儘量使用java包的定義方式並將程式碼集中進行管理,這樣別人讀程式碼的時候更方便,程式碼更簡潔。

包的作用域與引入(import)的使用方法

下面的程式碼給出了包的作用域和引入的使用方法

package cn{
  package scala{
    //在包cn.scala下建立了一個Utils單例
    object Utils{
      def toString(x:String){
        println(x)
      }
      //外層包無法直接訪問內層包,下面這一行程式碼編譯通不過
     //def getTeacher():Teacher=new Teacher("john")
     //如果一定要使用的話,可以引入包
     import cn.scala.xtwy._
     def getTeacher():Teacher=new Teacher("john")
    }
    //定義了cn.scala.xtwy
    package xtwy{
      class Teacher(var name:String) {
           //演示包的訪問規則
           //內層包可以訪問外層包中定義的類或物件,無需引入
           def printName()={Utils.toString(name)}
      }

    }
  }
}
object appDemo{
        //scala允許在任何地方進行包的引入,_的意思是引入該包下的所有類和物件
        import cn.scala._
        import cn.scala.xtwy._
        def main(args: Array[String]): Unit = {
            Utils.toString(new Teacher("john").name)
            new Teacher("john").printName() 
        }

}

訪問控制

在java語言中,主要通過public、private、protected及預設控制來實現包中類成員的訪問控制,當定義一個類時,如果類成員不加任何訪問控制符時,表示該類成員在定義該類的包中可見。在scala中沒有public關鍵字,僅有private 和 protected訪問控制符,當一個類成員不加private和protected時,它的訪問許可權就是public。下面逐個進行講解:

1 private 成員
private成員同java是一樣的,所有帶該關鍵字修飾的成員僅能在定義它的類或物件中使用,在外部是不可見的

class Student(var name:String,var age:Int){
  private var sex:Int=0
  //內部類
  class Course(val cName:String,val gpa:Float){
    //可以直接訪問其外部類的私有成員
    def getStudentSex(student:Student)= student.sex
  }
}

//班級類
class Class{
  //下面這條語句統計通不過,因為sex是私有的
  // def getStudentSex(student:Student)=student.sex
}
object Student {
  private var studentNo:Int=0;
  def uniqueStudentNo()={
    studentNo+=1
    studentNo
  }
  def apply(name:String,age:Int)=new Student(name,age)

  def main(args: Array[String]): Unit = {
    println(Student.uniqueStudentNo())
    val s=new Student("john",29)
    //直接訪問伴生類Student中的私有成員
    println(s.sex)

    val s1=Student("john",29)
    println(s1.name)
    println(s1.age)

    //使用內部類
    val c1=new s1.Course("Scala",3.0f)

  }
}

2 protected 成員
在java語言中,protected成員不但可以被該類及其子類訪問,也可以被同一個包中的其它類使用,但在scala中,protected成員只能被該類及其子類訪問

class SuperClass {
  protected def f()=println(".....")
}

class SubClass extends SuperClass{
  f()
}

class OtherClass{
  //下面這個語句會報錯
  //f()
}

3 無修飾符成員
無修飾符的成員同java 的public,可以在任何位置進行訪問

4 範圍保護
在scala中提供了更為靈活的訪問控制方法,private、protected除了可以直接修飾成員外,還可以以private[X]、protected[X]的方式進行更為靈活的訪問控制,這種訪問控制的意思是可以將private、protected限定到X,X可以是包、類,還可以是單例物件

package cn{
  class UtilsTest{
     //編譯通不過,因為Utils利用private[scala]修飾,只能在scala及其子包中使用
    //Utils.toString()
  }
  package scala{
    //private[scala]限定Utils只能在scala及子包中使用
    private[scala] object Utils{
      def toString(x:String){
        println(x)
      }
      import cn.scala.xtwy._
      def getTeacher():Teacher=new Teacher("john")

    }
    package xtwy{
      class Teacher(var name:String) {
           def printName()={Utils.toString(name)}
      }

    }
  }
}
object appDemo{
        import cn.scala._
        import cn.scala.xtwy._
        def main(args: Array[String]): Unit = {
            //編譯通不過,同UtilsTest
            //Utils.toString(new Teacher("john").name)
            new Teacher("john").printName() 
        }

}

private[this],限定只有該類的物件才能訪問,稱這種成員為物件私有成員

package cn.scala.xtwy;

class Teacher(var name: String) {
  private[this] def printName(tName:String="") :Unit= { println(tName) }
  //呼叫private[this] printName方法
  def print(n:String)=this.printName(n)
}

object Teacher{
   //private[this]限定的成員,即使伴生物件Teacher也不能使用
  //def printName=new Teacher("john").printName()
}

object appDemo {
  def main(args: Array[String]): Unit = {
          //編譯不能通過
         //new Teacher("john").printName()
  }

}

private,定義的類及伴生物件可以訪問

package cn.scala.xtwy;

class Teacher(var name: String) {
  private def printName(tName:String="") :Unit= { println(tName) }
  //可以訪問
  def print(n:String)=this.printName(n)
}

object Teacher{
  //伴生物件可以訪問
  def printName=new Teacher("john").printName()
}

object appDemo {
  def main(args: Array[String]): Unit = {
          //不能訪問
         //new Teacher("john").printName()
  }

}

下面給出的是訪問規則表

修飾符 訪問範圍
無任何修飾符 任何地方都可以使用
private[scala] 在定義的類中可以訪問,在scala包及子包中可以訪問
private[this] 只能在定義的類中訪問,即使伴生物件也不能訪問團
private 在定義的的類及伴生物件中可以訪問,其它地方不能訪問
protected[scala] 在定義的類及子類中可以訪問,在scala包及子包中可以訪問,
protected[this] 只能在定義的類及子類中訪問,即使伴生物件也不能訪問
protected 在定義的類及子類中訪問,伴生物件可以訪問,其它地方不能訪問

包物件

包物件主要用於將常量、工具函式,使用時直接通過包名引用

//下面的程式碼給出了包物件的定義
package cn.scala.xtwy

//利用package關鍵字定義單例物件
package object Math {
  val PI=3.141529
  val THETA=2.0
  val SIGMA=1.9
}

class Coputation{
  def computeArea(r:Double)=Math.PI*r*r
}

上述程式碼編譯後會生成下列檔案:
這裡寫圖片描述

對應位元組碼檔案如下:

D:\ScalaWorkspace\ScalaChapter08\bin\cn\scala\xtwy\Math>javap -private package.c
lass
Compiled from "Math.scala"
public final class cn.scala.xtwy.Math.package {
  public static double SIGMA();
  public static double THETA();
  public static double PI();
}

D:\ScalaWorkspace\ScalaChapter08\bin\cn\scala\xtwy\Math>javap -private package$.
class
Compiled from "Math.scala"
public final class cn.scala.xtwy.Math.package$ {
  public static final cn.scala.xtwy.Math.package$ MODULE$;
  private final double PI;
  private final double THETA;
  private final double SIGMA;
  public static {};
  public double PI();
  public double THETA();
  public double SIGMA();
  private cn.scala.xtwy.Math.package$();
}

不能看出,它為我們的包物件Math建立了一個資料夾,然後建立了兩個類,通過單例的方式實現方法呼叫。

import高階特性

1 隱式引入
在集合那一講,我們提到,如果不引入任何包,scala會預設引入java.lang._
scala._
Predef._
包中或物件中所有的類和方法,稱這種引入會隱式引入

2 重新命名

scala中允許對引入的類或方法進行重新命名,如果我們需要在程式中同時使用java.util.HashMap及scala.collection.mutable.HashMap時,可以利用重新命名的方法消除命名衝突的問題,雖然也可以採用包名字首的方式使用,但程式碼不夠簡潔

//將java.util.HashMap重新命名為JavaHashMap
import java.util.{ HashMap => JavaHashMap }
import scala.collection.mutable.HashMap
object RenameUsage {
  def main(args: Array[String]): Unit = {
    val javaHashMap = new JavaHashMap[String, String]()
    javaHashMap.put("Spark", "excellent")
    javaHashMap.put("MapReduce", "good")
    for(key <- javaHashMap.keySet().toArray){
      println(key+":"+javaHashMap.get(key))
    }

    val scalaHashMap=new HashMap[String,String]
    scalaHashMap.put("Spark", "excellent")
    scalaHashMap.put("MapReduce", "good")
    scalaHashMap.foreach(e=>{
      val (k,v)=e
      println(k+":"+v)
    })
  }

}

3 類隱藏

//通過HashMap=> _,這樣類便被隱藏起來了
import java.util.{HashMap=> _,_}
import scala.collection.mutable.HashMap


object RenameUsage {
  def main(args: Array[String]): Unit = {

    //這樣的話,HashMap便無歧義地指向scala.collection.mutable.HashMap
    val scalaHashMap=new HashMap[String,String]
    scalaHashMap.put("Spark", "excellent")
    scalaHashMap.put("MapReduce", "good")
    scalaHashMap.foreach(e=>{
      val (k,v)=e
      println(k+":"+v)
    })
  }

內部類

前面我們提到過內部類,我們看到內部類可以像類的其它成員一樣訪問外部類的私有成員,本小節將對內部類進行更加詳細的介紹。

要點1:外部類不能訪問內部類的成員域,但內部類可以直接訪問外部類成員域,哪怕這個成員域是private私有的

class OuterClass {
  //即使定義為 private[this] var x:Int=0,也是可行的
  private var x:Int=0

  //def getInnerY=y,外部類不能直接訪問內部類的成員域
  class InnerClass{
    private var y:Int=0
    //內部的類可以直接訪問外部類的成員變數和成員方法,注意外部類的成員變數是private
    def getOuterX= x
  }
}

object AppDemo{
  def main(args: Array[String]): Unit = {
    val o=new OuterClass
    //建立內部類物件,通過o.InnerClass方式,InnerClass就像是OuterClass的成員變數一樣
    val i=new o.InnerClass
    println(i.getOuterX)
  }
}

從上述程式碼可以看出,內部類除了是一個類之外,與外部類的成員沒有任何區別,它可以與外部類的成員域一樣被使用

新增公眾微訊號,可以瞭解更多最新Spark、Scala相關技術資訊
這裡寫圖片描述