大數據技術之_16_Scala學習_04_函數式編程-基礎+面向對象編程-基礎


第五章 函數式編程-基礎5.1 函數式編程內容說明5.1.1 函數式編程內容5.1.2 函數式編程授課順序5.2 函數式編程介紹5.2.1 幾個概念的說明5.2.2 方法、函數、函數式編程和面向對象編程關系分析圖5.2.3 函數式編程小結5.3 為什么需要函數5.4 函數的定義5.4.1 函數的定義5.4.2 快速入門案例5.5 函數的調用機制5.5.1 函數的調用過程5.5.2 函數的遞歸調用5.5.3 遞歸練習題5.6 函數注意事項和細節討論5.7 函數練習題5.8 過程5.8.1 基本概念5.8.2 注意事項和細節說明5.9 惰性函數5.9.1 看一個應用場景5.9.2 畫圖說明(大數據推薦系統)5.9.3 Java 實現懶加載的代碼5.9.4 惰性函數介紹5.9.5 案例演示5.9.6 注意事項和細節5.10 異常5.10.1 介紹5.10.2 Java 異常處理回顧5.10.3 Java 異常處理的注意點5.10.4 Scala 異常處理舉例5.10.5 Scala 異常處理小結5.11 函數的練習題第六章 面向對象編程-基礎6.1 類與對象6.1.1 Scala 語言是面向對象的6.1.2 快速入門-面向對象的方式解決養貓問題6.1.3 類和對象的區別和聯系6.1.4 如何定義類6.1.5 屬性6.1.6 屬性/成員變量6.1.7 屬性的高級部分6.1.8 如何創建對象6.1.9 類和對象的內存分配機制(重要)6.2 方法6.2.1 基本說明和基本語法6.2.2 方法的調用機制原理6.2.3 方法練習題6.3 類與對象應用實例6.4 構造器6.4.1 看一個需求6.4.2 回顧-Java 構造器的介紹+基本語法+特點+案例6.4.3 Scala 構造器的介紹+基本語法+快速入門6.4.4 Scala 構造器注意事項和細節6.5 屬性高級6.5.1 構造器參數6.5.2 Bean 屬性6.6 Scala 對象創建的流程分析6.7 作業03


第五章 函數式編程-基礎

5.1 函數式編程內容說明

5.1.1 函數式編程內容

函數式編程-基礎
  1、函數定義/聲明
  2、函數運行機制
  3、遞歸【難點:最短路徑,郵差問題,背包問題,迷宮問題,回溯】
  4、過程
  5、惰性函數和異常

函數式編程-高級
  6、值函數(函數字面量)
  7、高階函數
  8、閉包
  9、應用函數
  10、柯里化函數,抽象控制…

5.1.2 函數式編程授課順序

  1、在 scala 中,函數式編程和面向對象編程融合在一起,學習函數式編程式需要 oop 的知識,同樣學習 oop 需要函數式編程的基礎。[矛盾]
  2、二者關系如下圖:
  


  3、授課順序:函數式編程基礎 -> 面向對象編程 -> 函數式編程高級

5.2 函數式編程介紹

5.2.1 幾個概念的說明

  在學習 Scala 中將方法、函數、函數式編程和面向對象編程明確一下:
  1、在 scala 中,方法函數幾乎可以等同(比如他們的定義、使用、運行機制都一樣的),只是函數的使用方式更加的靈活多樣。
  2、函數式編程是從編程方式(范式)的角度來談的,可以這樣理解:函數式編程把函數當做一等公民,充分利用函數、支持的函數的多種使用方式
  比如:在 Scala 當中,函數是一等公民,像變量一樣,既可以作為函數的參數使用,也可以將函數賦值給一個變量,函數的創建不用依賴於類或者對象,而在 Java 當中,函數的創建則要依賴於類、抽象類或者接口。
  3、面向對象編程是以對象為基礎的編程方式。
  4、在 scala 中函數式編程和面向對象編程融合在一起了。
示例代碼如下:

package com.atguigu.chapter05

object Method2Function {
  def main(args: Array[String]): Unit = {
    // 傳統的方式使用方法
    // 先創建一個對象
    val dog = new Dog
    println(dog.sum(10, 20))

    // 方法轉成函數后使用函數
    val f1 = dog.sum _
    println("f1=" + f1) // f1=<function2>
    println("f1=" + f1(50, 60))

    // 直接寫一個函數並使用函數
    // 格式:val f2 = (Int, Int) => {}
    val f2 = (n1: Int, n2: Int) => {
      n1 + n2 // 函數體
    }
    println("f2=" + f2) // f2=<function2>
    println("f2=" + f2(80, 90))
  }
}

class Dog {
  // 方法
  def sum(n1: Int, n2: Int): Int = {
    n1 + n2 // 方法體
  }
}

輸出結果如下:

30
f1=<function2>
f1=110
f2=<function2>
f2=170

5.2.2 方法、函數、函數式編程和面向對象編程關系分析圖

在學習 Scala 中將方法、函數、函數式編程和面向對象編程關系分析圖如下:

5.2.3 函數式編程小結

  1、“函數式編程”是一種“編程范式”(programming paradigm)。
  2、它屬於“結構化編程”的一種,主要思想是把運算過程盡量寫成一系列嵌套的函數調用。
  3、函數式編程中,將函數也當做數據類型,因此可以接受函數當作輸入(參數)和輸出(返回值)。
  4、函數式編程中,最重要的就是函數。

5.3 為什么需要函數

學習一個技術或者知識點的流程:

5.4 函數的定義

5.4.1 函數的定義

5.4.2 快速入門案例

使用函數完全前面的案例。
示例代碼如下:

package com.atguigu.chapter05

object FunDemo01 {
  def main(args: Array[String]): Unit = {
    println("" + getRes(10, 20, '+'))
  }

  // 定義一個函數/方法
  def getRes(n1: Int, n2: Int, oper: Char) = { // 返回值形式2: = 表示返回值類型不確定,使用類型推導完成。
    if (oper == '+') {
      // return n1 + n2 // return 關鍵字可以寫可以不寫
      n1 + n2
    } else if (oper == '-') {
      n1 - n2
    } else {
      // 返回 null
      null
    }
  }
}

5.5 函數的調用機制

5.5.1 函數的調用過程

  為了讓大家更好的理解函數調用機制,看1個案例,並畫出示意圖,這個很重要,比如 getSum 計算兩個數的和,並返回結果。

5.5.2 函數的遞歸調用

注意:Struts2 中的攔截器的底層實現機制就是把一個對象放到堆中,然后不停的開棧去指向該對象的內存地址,每一個棧都有機會去修改該對象的值。

函數遞歸需要遵守的重要原則(總結)
  1、程序執行一個函數時,就創建一個新的受保護的獨立空間(新函數棧)。
  2、函數的局部變量是獨立的,不會相互影響。
  3、遞歸必須向退出遞歸的條件逼近,否則就是無限遞歸,死龜了:)
  4、當一個函數執行完畢,或者遇到 return,就會返回,遵守誰調用,就將結果返回給誰。

5.5.3 遞歸練習題

題1:斐波那契數,請使用遞歸的方式,求出斐波那契數1,1,2,3,5,8,13… 給你一個整數n,求出它的斐波那契數是多少?
示例代碼如下:

package com.atguigu.chapter05.recursive

import scala.io.StdIn

/**
  * 題1:斐波那契數,請使用遞歸的方式,求出斐波那契數1,1,2,3,5,8,13... 給你一個整數n,求出它的斐波那契數是多少?
  * 思路:f(1)=1, f(2)=1, f(3)=f(2)+f(1)=1+1=2, f(4)=f(2)+f(3)=1+2=3, ..., f(n)=f(n-1)+f(n-2)
  */
object Exercise01 {
  def main(args: Array[String]): Unit = {

    println("請輸入一個正整數:")
    val n = StdIn.readInt()
    printf("%d的斐波那契數是:%d", n, fbn(n))
  }

  def fbn(n: Int): Int = {
    if (n == 1 || n == 2) {
      1
    } else {
      fbn(n - 1) + fbn(n - 2)
    }
  }
}

題2:求函數值,已知 f(1)=3; f(n) = 2*f(n-1)+1; 請使用遞歸的思想編程,求出 f(n) 的值?
示例代碼如下:

package com.atguigu.chapter05.recursive

import scala.io.StdIn

/**
  * 題2:求函數值,已知 f(1)=3; f(n) = 2*f(n-1)+1; 請使用遞歸的思想編程,求出 f(n) 的值?
  * n=1, f(1)=3
  * n=2, f(2)=2*f(1)+1=7
  * n=3, f(3)=2*f(2)+1=15
  *
  */
object Exercise02 {
  def main(args: Array[String]): Unit = {
    println("請輸入一個正整數:")
    val n = StdIn.readInt()
    printf("f(%d) 的值是:%d", n, f(n))
  }

  def f(n: Int): Int = {
    if (n == 1) {
      3
    } else {
      2 * f(n - 1) + 1
    }
  }
}

題3:猴子吃桃子問題:有一堆桃子,猴子第一天吃了其中的一半,並再多吃了一個!以后每天猴子都吃其中的一半,然后再多吃一個。當到第十天時,想再吃時(還沒吃),發現只有1個桃子了。問題:最初共多少個桃子?
示例代碼如下:

package com.atguigu.chapter05.recursive

import com.atguigu.chapter05.recursive.Exercise02.f

import scala.io.StdIn

/**
  * 題3:猴子吃桃子問題:有一堆桃子,猴子第一天吃了其中的一半,並再多吃了一個!
  * 以后每天猴子都吃其中的一半,然后再多吃一個。當到第十天時,想再吃時(還沒吃),發現只有1個桃子了。問題:最初共多少個桃子?
  *
  * day = 10 桃子有 1
  * day =  9 桃子有 (day10的桃子 + 1) *2
  * day =  8 桃子有 (day9 的桃子 + 1) *2
  *
  */
object Exercise03 {
  def main(args: Array[String]): Unit = {
    println("最初共有:" + f(1) + "個桃子")
  }

  def f(n: Int): Int = {
    if (n == 10) {
      1
    } else {
      (f(n + 1) + 1) * 2
    }
  }
}

5.6 函數注意事項和細節討論

  1、函數的形參列表可以是多個,如果函數沒有形參,調用時可以不帶()。

  2、函數的形參列表和返回值列表的數據類型可以是值類型和引用類型。【案例演示】
示例代碼如下:

package com.atguigu.chapter05.fundetails

object Details01 {
  def main(args: Array[String]): Unit = {
    val tiger = new Tiger
    val tiger2 = test01(10, tiger)
    println(tiger2.name) // tom
    println(tiger.name)  // tom
    println(tiger.hashCode() + " " + tiger2.hashCode()) // 2101440631 2101440631
  }

  // 2、函數的形參列表和返回值列表的數據類型可以是值類型和引用類型。
  def test01(n: Int, tiger: Tiger): Tiger = {
    println("n=" + n)
    tiger.name = "tom"
    tiger
    // return tiger // 3、Scala 中的函數可以根據函數體最后一行代碼自行推斷函數返回值類型。那么在這種情況下,return 關鍵字可以省略。
  }
}

class Tiger {
  var name = ""
}

  3、Scala 中的函數可以根據函數體最后一行代碼自行推斷函數返回值類型。那么在這種情況下,return 關鍵字可以省略。 【案例同上】
  4、因為 Scala 可以自行推斷,所以在省略 return 關鍵字的場合,返回值類型也可以省略。
  


  5、如果函數明確使用 return 關鍵字,那么函數返回就不能使用自行推斷了,這時要明確寫成 : 返回值類型 = ,當然如果你什么都不寫,即使有 return,那么返回值為(),即這時 return 無效。
  6、如果函數明確聲明無返回值(聲明 Unit),那么函數體中即使使用 return 關鍵字也不會有返回值。
示例代碼如下:
package com.atguigu.chapter05.fundetails

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

    println(getSum2(10, 30))  // ()

    println(getSum3(9, 9))    // ()

  }

  // 如果寫了 return,那么返回值類型就不能省略。
  def getSum(n1: Int, n2: Int): Int = {
    return n1 + n2
  }

  // 如果返回值這里什么什么都沒有寫,即表示該函數沒有返回值。
  // 這時 return 無效
  def getSum2(n1: Int, n2: Int) {
    return n1 + n2
  }

  // 如果函數明確聲明無返回值(聲明Unit),那么函數體中即使使用 return 關鍵字也不會有返回值。
  def getSum3(n1: Int, n2: Int): Unit = {
    return n1 + n2
  }
}

  7、如果明確函數無返回值或不確定返回值類型,那么返回值類型可以省略(或聲明為 Any)。
示例代碼如下:

package com.atguigu.chapter05.fundetails

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

  }

  // 7、如果明確函數無返回值或不確定返回值類型,那么返回值類型可以省略(或聲明為 Any)。
  def f3(s: String) = {
    if (s.length >= 3)
      s + "123"
    else
      3
  }

  def f4(s: String): Any = {
    if (s.length >= 3)
      s + "123"
    else
      3
  }
}

  8、Scala 語法中任何的語法結構都可以嵌套其他語法結構(很靈活),即:函數中可以再聲明/定義函數類中可以再聲明類方法中可以再聲明/定義方法
示例代碼如下:

package com.atguigu.chapter05.fundetails

object Details04 {
  def main(args: Array[String]): Unit = { // public void main(String[] args)

    def f1():Unit = { // private final void f1$1
      println("f1")
    }

    def sayok(): Unit = { // private final void sayok$1
      println("sayok~")
      def sayok(): Unit = { // private final void sayok$2
        println("sayok~~")
      }
    }

    println("ok")
  }

  def sayok(): Unit = { // public void sayok()
    println("sayok")
  }
}

  9、Scala 函數的形參,在聲明參數時,直接賦初始值(默認值),這時調用函數時,如果沒有指定實參,則會使用默認值。如果指定了實參,則實參會覆蓋默認值
示例代碼如下:

package com.atguigu.chapter05.fundetails

object Details05 {
  def main(args: Array[String]): Unit = {
    println(sayOk())      // jack ok!
    println(sayOk("tom")) // tom ok!
  }

  def sayOk(name: String = "jack"): String = {
    return name + " ok! "
  }
}

  10、如果函數存在多個參數,每一個參數都可以設定默認值,那么這個時候,傳遞的參數到底是覆蓋默認值,還是賦值給沒有默認值的參數,就不確定了(默認按照聲明順序[從左到右])。在這種情況下,可以采用帶名參數
示例代碼如下:

package com.atguigu.chapter05.fundetails

object Details06 {
  def main(args: Array[String]): Unit = {
    mysqlCon()
    mysqlCon("127.0.0.1", 7777) // 從左到右覆蓋

    // 如果我們希望指定覆蓋某一個默認值,則使用帶名參數即可,比如只想修改用戶名和密碼,其他的不改
    mysqlCon(user = "tom", pwd = "1234")

    // 練習
    // f6("v2") // 報錯,p2的值沒有指定
    f6(p2 = "v2") // v1v2
  }

  def mysqlCon(add: String = "localhost", port: Int = 3306,
               user: String = "root", pwd: String = "root"): Unit = {
    println("add=" + add)
    println("port=" + port)
    println("user=" + user)
    println("pwd=" + pwd)
  }

  def f6(p1: String = "v1", p2: String) {
    println(p1 + p2);
  }
}

  11、scala 函數的形參默認是 val 的,因此不能在函數中進行修改。

  12、遞歸函數未執行之前是無法推斷出來結果類型,在使用時必須有明確的返回值類型。
示例代碼如下:

  def f(n: Int) = { // 錯誤,遞歸不能使用類型推斷,必須指定返回的數據類型。
    if (n <= 0)
      1
    else
      n * f(n - 1)
  }

  13、Scala 函數支持可變參數
示例代碼如下:

  // 支持0到多個參數
  def sum(args: Int*): Int = {
  }

  // 支持1到多個參數
  def sum(n1: Int, args: Int*): Int = {
  }

  說明:
  1、args 是集合, 通過 for 循環 可以訪問到各個值。【args 是參數名,可以任意起】
  2、案例演示: 編寫一個函數 sum,可以求出 1 到多個 int 的和。
  3、可變參數需要寫在形參列表的最后。
示例代碼如下:

package com.atguigu.chapter05.fundetails

object VarParameters {
  def main(args: Array[String]): Unit = {
    println(sum(10, 20, 30)) // 這里可變參數為2個
  }

  // 支持1到多個參數
  def sum(n1: Int, args: Int*): Int = {
    println("args.length=" + args.length)
    var sum = n1
    for (item <- args) {
      sum += item
    }
    sum
  }
}

5.7 函數練習題

判斷下面的代碼是否正確:
示例代碼如下:

  object Hello01 {
    def main(args: Array[String]): Unit = {
      def f1 = "venassa"
      println(f1) // 輸出 venassa
    }
  }

  // 上面代碼 def f1 = "venassa" 等價於
  def f1() = {
    "venassa"
  }
  // 說明:
  // 1、函數的形參列表可以是多個,如果函數沒有形參,函數可以不帶()。
  // 2、函數的函數體只有一行代碼時,可以省略{}。

5.8 過程

5.8.1 基本概念

基本介紹:
  將函數的返回類型為 Unit 的函數稱之為過程(procedure),如果明確函數沒有返回值,那么等號可以省略。

案例說明:

  // f10 沒有返回值,可以使用 Unit 來說明
  // 這時,這個函數我們也叫過程(procedure)
  def f10(name: String): Unit = { // 如果明確函數沒有返回值,那么等號可以省略。
    println(name+ "hello")
  }

5.8.2 注意事項和細節說明

  1、注意區分: 如果函數聲明時沒有返回值類型,但是有 = 號,可以進行類型推斷最后一行代碼。這時這個函數實際是有返回值的,該函數並不是過程。(這點在講解函數細節的時候講過的)
  2、開發工具的自動代碼補全功能,雖然會自動加上 Unit,但是考慮到 Scala 語言的簡單,靈活,最好不加。

5.9 惰性函數

5.9.1 看一個應用場景

  惰性計算(盡可能延遲表達式求值)是許多函數式編程語言的特性。惰性集合在需要時提供其元素,無需預先計算它們,這帶來了一些好處。首先,您可以將耗時的計算推遲到絕對需要的時候。其次,您可以創造無限個集合,只要它們繼續收到請求,就會繼續提供元素。函數的惰性使用讓您能夠得到更高效的代碼。Java 並沒有為惰性提供原生支持,Scala 提供了。

5.9.2 畫圖說明(大數據推薦系統)

5.9.3 Java 實現懶加載的代碼

示例代碼如下:

package com.atguigu.chapter05;

public class LazyDemo {

    private String property; // 屬性也可能是一個數據庫連接,文件等資源

    public String getProperty() {
        if (property == null) { // 如果沒有初始化過,那么就進行初始化
            property = initProperty();
        }
        return property;
    }

    private String initProperty() {
        return "property";
    }
}
// 比如常用的【單例模式懶漢式】實現時就使用了上面類似的思路實現

5.9.4 惰性函數介紹

  當函數返回值被聲明為 lazy 時,函數的執行將被推遲,直到我們首次對此取值,該函數才會執行,這種函數我們稱之為惰性函數。在 Java 的某些框架代碼中稱之為懶加載(延遲加載)

5.9.5 案例演示

示例代碼如下:

package com.atguigu.chapter05.mylazy

object LazyDemo01 {
  def main(args: Array[String]): Unit = {
    lazy val res = sum(10, 20)
    println("----------")
    println("res=" + res) // 在要使用 res 前,才執行
  }

  def sum(n1: Int, n2: Int): Int = {
    println("sum() 執行了..")
    return n1 + n2
  }
}

輸出結果如下:

----------
sum() 執行了..
res=30

5.9.6 注意事項和細節

  1、lazy 不能修飾 var 類型的變量。
  2、不但是在調用函數時,加了 lazy,會導致函數的執行被推遲,我們在聲明一個變量時,如果聲明了 lazy,那么變量值的分配也會推遲。 比如 lazy val i = 10。

5.10 異常

5.10.1 介紹

  Scala 提供 try 塊和 catch 塊來處理異常。try 塊用於包含可能出錯的代碼。catch 塊用於處理 try 塊中發生的異常。可以根據需要在程序中有任意數量的 try…catch 塊。
  語法處理上和 Java 類似,但是又不盡相同。

5.10.2 Java 異常處理回顧

示例代碼如下:

package com.atguigu.chapter05.exception;

public class JavaExceptionDemo {
    public static void main(String[] args) {
        try {
            // 可疑代碼
            int i = 0;
            int b = 10;
            int c = b / i; // 執行代碼時,會拋出 ArithmeticException 異常
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 最終要執行的代碼
            System.out.println("java finally");
        }

        System.out.println("繼續執行");
    }
}

輸出結果如下:

java finally
繼續執行
java.lang.ArithmeticException: / by zero
    at com.atguigu.chapter05.exception.JavaExceptionDemo.main(JavaExceptionDemo.java:9)

5.10.3 Java 異常處理的注意點

  1、java 語言按照 try-catch-catch…-finally 的方式來處理異常。
  2、不管有沒有異常捕獲,都會執行 finally,因此通常可以在 finally 代碼塊中釋放資源。
  3、可以有多個 catch,分別捕獲對應的異常,這時需要把范圍小的異常類寫在前面,把范圍大的異常類寫在后面,否則編譯錯誤。會提示 "Exception 'java.lang.xxxxxx' has already been caught"。

5.10.4 Scala 異常處理舉例

示例代碼如下:

package com.atguigu.chapter05.exception

object ScalaExceptionDemo {
  def main(args: Array[String]): Unit = {
    try {
      val r = 10 / 0
    } catch {
      // 說明
      // 1. 在 scala 中只有一個 catch
      // 2. 在 catch 中有多個 case, 每個 case 可以匹配一種異常
      // 3. => 關鍵符號,表示后面是對該異常的處理代碼塊
      // 4. finally 最終要執行的代碼
      case ex: ArithmeticException => { println("捕獲了除數為零的算數異常") } // 當對該異常的處理代碼塊為一行時,{}可以省略
      case ex: Exception => println("捕獲了異常")
    } finally {
      // 最終要執行的代碼
      println("scala finally")
    }

    System.out.println("繼續執行")
  }
}

輸出結果如下:

捕獲了除數為零的算數異常
scala finally
繼續執行

5.10.5 Scala 異常處理小結

  1、我們將可疑代碼封裝在 try 塊中。在 try 塊之后使用了一個 catch 處理程序來捕獲異常。如果發生任何異常,catch 處理程序將處理它,異常處理了程序將不會異常終止
  2、Scala 的異常的工作機制和 Java 一樣,但是 Scala 沒有“checked(編譯期)” 異常,即 Scala 沒有編譯異常這個概念,異常都是在運行的時候捕獲處理。
  3、Scala 用 throw 關鍵字,拋出一個異常對象。所有異常都是 Throwable 的子類型。throw 表達式是有類型的,就是 Nothing,因為 Nothing 是所有類型的子類型,所以 throw 表達式可以用在需要類型的地方。
示例代碼如下:

package com.atguigu.chapter05.exception

object ThrowDemo {
  def main(args: Array[String]): Unit = {
    // val res = test()
    // println(res.toString)
    // println("繼續執行002") // 異常拋出了,但是沒有被處理,后續程序不能執行

    // 如果我們希望在 test() 拋出異常后,后續代碼可以繼續執行,則我們需要如下處理
    try {
      test()
    } catch {
      case ex: Exception => {
        println("捕獲到異常是:" + ex.getMessage)
        println("繼續執行001")
      }
      case ex: ArithmeticException => println("得到一個算術異常(小范圍異常)")
    } finally {
      // 寫上對 try{} 中的資源的分配
    }

    println("繼續執行002")
  }

  def test(): Nothing = {
    // Exception("異常出現")
    throw new ArithmeticException("算術異常")
  }
}

輸出結果如下:

捕獲到異常是:算術異常
繼續執行001
繼續執行002

  4、在 Scala 里,借用了模式匹配的思想來做異常的匹配,因此,在 catch 的代碼里,是一系列 case 子句來匹配異常。【前面案例可以看出這個特點,模式匹配我們后面詳解】,當匹配上后 => 有多條語句可以換行寫,類似 java 的 switch case x: 代碼塊…
  5、異常捕捉的機制與其他語言中一樣,如果有異常發生,catch 子句是按次序捕捉的。因此,在 catch 子句中,越具體的異常越要靠前,越普遍的異常越靠后,如果把越普遍的異常寫在前,把具體的異常寫在后,在 scala 中也不會報錯,但這樣是非常不好的編程風格。
  6、finally 子句用於執行不管是正常處理還是有異常發生時都需要執行的步驟,一般用於對象的清理工作,這點和 Java 一樣。
  7、Scala 提供了 throws 關鍵字來聲明異常。可以使用方法定義聲明異常。它向調用者函數提供了此方法可能引發此異常的信息。它有助於調用函數處理並將該代碼包含在 try-catch 塊中,以避免程序異常終止。在 scala 中,可以使用 throws 注釋來聲明異常。
示例代碼如下:

package com.atguigu.chapter05.exception

object ThrowsComment {
  def main(args: Array[String]): Unit = {
    f()
  }

  @throws(classOf[NumberFormatException]) // 等同於 Java 中 NumberFormatException.class
  def f() = {
    "abc".toInt
  }
}

輸出結果如下:

Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:580)
    at java.lang.Integer.parseInt(Integer.java:615)
    at scala.collection.immutable.StringLike$class.toInt(StringLike.scala:272)
    at scala.collection.immutable.StringOps.toInt(StringOps.scala:29)
    at com.atguigu.chapter05.exception.ThrowsComment$.f(ThrowsComment.scala:10)
    at com.atguigu.chapter05.exception.ThrowsComment$.main(ThrowsComment.scala:5)
    at com.atguigu.chapter05.exception.ThrowsComment.main(ThrowsComment.scala)

5.11 函數的練習題

1、函數可以沒有返回值案例,編寫一個函數,從終端輸入一個整數打印出對應的金子塔。
示例代碼如下:

package com.atguigu.chapter05.exercises

import scala.io.StdIn

/**
  * 1、函數可以沒有返回值案例,編寫一個函數,從終端輸入一個整數打印出對應的金子塔。
  * 思路:本質是打印出所有的 n行m列 數據。 分別循環即可!
  */
object Exercise01 {
  def main(args: Array[String]): Unit = {
    println("請輸入一個整數n(n>=1):")
    val n = StdIn.readInt()
    printJin(n)
  }

  def printJin(n: Int): Unit = {
    for (i <- 1 to n) { // 行數
      for (j <- 1 to (n - i)) {
        printf("-")
      }
      for (j <- 1 to (2 * i - 1)) { // 列數
        printf("*")
      }
      for (j <- 1 to (n - i)) {
        printf("-")
      }
      println()
    }
  }
}

輸出結果如下:

請輸入一個整數n(n>=1):
5
----*----
---***---
--*****--
-*******-
*********

2、編寫一個函數,從終端輸入一個整數(1—9),打印出對應的乘法表。
示例代碼如下:

package com.atguigu.chapter05.exercises

import scala.io.StdIn

/**
  * 2、編寫一個函數,從終端輸入一個整數(1—9),打印出對應的乘法表。
  */
object Exercise01 {
  def main(args: Array[String]): Unit = {
    println("請輸入數字(1-9)之間:")
    val n = StdIn.readInt()
    print99(n)
  }

  def print99(n: Int): Unit = {
    for (i <- 1 to n) {
      for (j <- 1 to i) {
        printf("%d * %d = %d\t", j, i, j * i)
      }
      println()
    }
  }
}

3、編寫函數,對給定的一個二維數組 (3×3) 轉置,這個題講數組的時候再完成。

    1 2 3       1 4 7            
    4 5 6       2 5 8               
    7 8 9       3 6 9

第六章 面向對象編程-基礎

6.1 類與對象

看一個養貓貓問題:
  張老太養了只貓貓:一只名字叫小白,今年3歲,白色。還有一只叫小花,今年10歲,花色。請編寫一個程序,當用戶輸入小貓的名字時,就顯示該貓的名字,年齡,顏色。如果用戶輸入的小貓名錯誤,則顯示張老太沒有這只貓貓。

問題:
  1、貓有三個屬性,類型不一樣。
  2、如果使用普通的變量就不好管理。
  3、使用一種新的數據類型:
    (1) 可以管理多個不同類型的數據 [屬性]。
    (2) 可以對屬性進行操作 => 方法。

類與對象的關系示意圖

6.1.1 Scala 語言是面向對象的

  1、Java 是面向對象的編程語言,由於歷史原因,Java 中還存在着非面向對象的內容:基本類型,null,靜態方法等。
  2、Scala 語言來自於 Java,所以天生就是面向對象的語言,而且 Scala 是純粹的面向對象的語言,即在 Scala 中,一切皆為對象。
  3、在面向對象的學習過程中可以對比着 Java 語言學習。

6.1.2 快速入門-面向對象的方式解決養貓問題

示例代碼如下:

package com.atguigu.chapter06.oop

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

    // 創建一只貓
    val cat = new Cat

    // 給貓的屬性賦值
    // 說明
    // 1. cat.name = "小白" 其實不是直接訪問屬性,而是等價於 cat.name_$eq("小白")
    // 2. cat.name 等價於 cat.name()
    cat.name = "小白"
    cat.age = 3
    cat.color = "白色"

    printf("\n小貓的信息如下:%s %d %s", cat.name, cat.age, cat.color)
  }
}

/* 反編譯查看源碼
public void main (String[] args) {
  Cat cat = new Cat ();

  cat.name_$eq ("小白");
  cat.age_$eq (3);
  cat.color_$eq ("白色");
}
*/

// 定義一個 Cat 類
// 一個 class Cat 對應的字節碼文件只有一個 Cat.class ,默認是public
class Cat {
  // 定義/聲明三個屬性
  // 說明
  // 1. 當我們聲明了 var name: String 時,同時在底層對應生成 private name
  // 2. 同時在底層會生成 兩個 public 方法 public String name() 類似 => getter 和 public void name_$eq(String x$1) => setter
  var name: String = "" // Scala 中定義變量必須給初始值
  var age: Int = _ // 下划線表示給 age 一個默認值,如果是 Int 類型,默認就是 0
  var color: String = _ // 如果是 String 類型,默認值就是 null
}

/* 反編譯查看源碼
public class Cat {
  private String name = "";
  private int age;
  private String color;

  public String name() {
    return this.name;
  }
  public void name_$eq(String x$1) {
    this.name = x$1;
  }
  public int age() {
    return this.age;
  }
  public void age_$eq(int x$1) {
    this.age = x$1;
  }
  public String color() {
    return this.color;
  }
  public void color_$eq(String x$1) {
    this.color = x$1;
  }
}
*/

輸出結果如下:

小貓的信息如下:小白 3 白色

6.1.3 類和對象的區別和聯系

通過上面的案例和講解我們可以看出:
  1、類是抽象的,概念的,代表一類事物,比如人類,貓類…
  2、對象是具體的,實際的,代表一個具體事物。
  3、類是對象的模板,對象是類的一個個體,對應一個實例。
  4、Scala 中類和對象的區別和聯系 和 Java 是一樣的。

6.1.4 如何定義類


我們可以通過 反編譯來看 scala 的類默認為 public 的特性。

6.1.5 屬性


示例代碼如下:

 

class Dog {
  var name = "jack"
  var lover = new Fish
}

class Fish {

}

6.1.6 屬性/成員變量


示例代碼如下:

 

package com.atguigu.chapter06.oop

object PropertyDemo {
  def main(args: Array[String]): Unit = {
    // val p1 = new Person
    // println(p1.Name)     // Null
    // println(p1.address)  // String 類型

    val a = new A
    println(a.var1) // null
    println(a.var2) // 0
    println(a.var3) // 0.0
    println(a.var4) // false

    // 不同對象的屬性是獨立,互不影響,一個對象對屬性的更改,不影響另外一個
    // 創建兩個對象
    var worker1 = new Worker
    worker1.name = "jack"
    var worker2 = new Worker
    worker2.name = "tom"
  }
}

class Person3 {
  var age: Int = 10   // 給屬性賦初值,省略類型,會自動推導
  var sal = 8090.9
  var Name = null     // Name 是什么類型
  var address: String = null // ok
}

class A {
  var var1: String = _  // null   String 和 引用類型默認值是 null
  var var2: Byte = _    // 0
  var var3: Double = _  // 0.0
  var var4: Boolean = _ // false
}

class Worker {
  var name = ""
}

輸出結果如下:

null
0
0.0
false

6.1.7 屬性的高級部分

  說明:屬性的高級部分和構造器(構造方法/函數) 相關,我們把屬性高級部分放到構造器那里講解。

6.1.8 如何創建對象


示例代碼如下:
package com.atguigu.chapter06.oop

object CreateObjDemo {
  def main(args: Array[String]): Unit = {
    val emp = new Emp // 此時的 emp 類型就是 Emp

    // 如果我們希望將子類對象,交給父類引用,這時就需要寫上類型,不能省略!
    val emp1: Person = new Emp
  }
}

class Person {

}

class Emp extends Person {

}

6.1.9 類和對象的內存分配機制(重要)

內存布局圖:


示例代碼如下:
package com.atguigu.chapter06.oop

object MemState {
  def main(args: Array[String]): Unit = {
    val p2 = new Person2
    p2.name = "jack"
    p2.age= 10

    val p1 = p2
    println(p1 == p2) // true
    println("p2.age=" + p2.age) // 10
    println("p1.age=" + p1.age) // 10
  }
}

class Person2 {
  var name = ""
  var age: Int = _ // 如果是用下划線的方式給默認值,則屬性必須指定類型,因為這有這樣,scala 底層才能夠進行類型推斷
}

6.2 方法

6.2.1 基本說明和基本語法


示例代碼如下:
package com.atguigu.chapter06.method

object MethodDemo01 {
  def main(args: Array[String]): Unit = {
    // 使用一把
    val dog = new Dog
    println(dog.cal(10, 20))
  }
}

class Dog {
  private var sal: Double = _
  var food: String = _

  def cal(n1: Int, n2: Int): Int = {
    return n1 + n2
  }
}

6.2.2 方法的調用機制原理

6.2.3 方法練習題


1~3題的示例代碼如下:
package com.atguigu.chapter06.method

/**
  * 1、編寫類(MethodExec),編寫一個方法,方法不需要參數,在方法中打印一個10*8的矩形,在main方法中調用該方法。
  *
  * 2、修改上一個程序,編寫一個方法中,方法不需要參數,計算該矩形的面積,並將其作為方法返回值。在main方法中調用該方法,接收返回的面積值並打印(結果保留小數點2位)。
  *
  * 3、修改上一個程序,編寫一個方法,提供m和n兩個參數,方法中打印一個m*n的矩形,再編寫一個方法計算該矩形的面積(可以接收長len和寬width), 將其作為方法返回值。在main方法中調用該方法,接收返回的面積值並打印。
  */
object MethodDemo02 {
  def main(args: Array[String]): Unit = {
    val m = new MethodExec
    m.printRect1()

    m.len = 1.2
    m.width = 3.4
    println("面積=" + m.area1())

    m.printRect2(5, 4)
    println("面積=" + m.area2(1.2, 3.4))
  }
}

class MethodExec {

  var len = 0.0
  var width = 0.0

  def printRect1(): Unit = {
    for (i <- 0 until 10) {
      for (j <- 0 until 8) {
        print("*")
      }
      println()
    }
  }

  def area1(): Double = {
    this.len * this.width.formatted("%.2f").toDouble
  }

  def printRect2(m: Int, n: Int): Unit = {
    for (i <- 0 until m) {
      for (j <- 0 until n) {
        printf("*")
      }
      println()
    }
  }

  def area2(len: Double, width: Double): Double = {
    len * width.formatted("%.2f").toDouble
  }
}

輸出結果如下:

********
********
********
********
********
********
********
********
********
********
面積=4.08
****
****
****
****
****
面積=4.08

4~6題的示例代碼原理同上1~3題,不在贅述!

6.3 類與對象應用實例


景區門票案例

小狗案列的示例代碼如下:
package com.atguigu.chapter06.dogcase

/**
  * 小狗案例
  *
  * 編寫一個Dog類,包含name(String)、age(Int)、weight(Double)屬性。
  * 類中聲明一個say方法,返回String類型,方法返回信息中包含所有屬性值。
  * 在另一個DogCaseTest類中的main方法中,創建Dog對象,並訪問say方法和所有屬性,將調用結果打印輸出。
  */
object DogCaseTest {
  def main(args: Array[String]): Unit = {
    val dog = new Dog
    dog.name = "泰斯特"
    dog.age = 2
    dog.weight = 50
    println(dog.say())
  }
}

class Dog {
  var name = ""
  var age = 0
  var weight = 0.0

  def say(): String = {
    "小狗的信息是:name=" + this.name + "\tage=" + this.age + "\tweight=" + this.weight
  }
}

輸出結果如下:

小狗的信息是:name=泰斯特    age=2   weight=50.0

盒子案列、景區門票案例的示例代碼原理同上小狗案例,不在贅述!

6.4 構造器

6.4.1 看一個需求

  前面我們在創建 Person 的對象時,是先把一個對象創建好后,再給他的年齡和姓名屬性賦值,如果現在我要求,在創建人類的對象時,就直接指定這個對象的年齡和姓名,該怎么做? 這時就可以使用構造方法/構造器。

6.4.2 回顧-Java 構造器的介紹+基本語法+特點+案例

Java 構造器的介紹
  構造器(constructor)又叫構造方法,是類的一種特殊的方法,它的主要作用是完成對新對象的初始化

Java 構造器的基本語法

Java 構造器的特點

Java 構造器的案例

6.4.3 Scala 構造器的介紹+基本語法+快速入門

Scala 構造器的介紹
  和 Java 一樣,Scala 構造對象也需要調用構造方法,並且可以有任意多個構造方法(即 scala 中構造器也支持重載)。
  Scala 類的構造器包括: 主構造器輔助構造器

Scala 構造器的基本語法

Scala 構造器的快速入門
示例代碼如下:

package com.atguigu.chapter06.constructor

/**
  * Scala構造器的快速入門:創建Person對象的同時初始化對象的age屬性值和name屬性值
  */
object ConstructorDemo01 {
  def main(args: Array[String]): Unit = {
    val p1 = new Person("bruce", 20)
    println(p1)
  }
}

class Person(inName: String, inAge: Int) {
  var name: String = inName
  var age: Int = inAge

  // 重寫toString方法
  override def toString: String = {
    "name=" + this.name + "\tage=" + this.age
  }
}

輸出結果如下:

name=bruce    age=20

6.4.4 Scala 構造器注意事項和細節

  1、Scala 構造器作用是完成對新對象的初始化,構造器沒有返回值。
  2、主構造器的聲明直接放置於類名之后。【可以反編譯查看】
  3、主構造器會執行類定義中的所有語句(除掉函數部分),這里可以體會到 Scala 的函數式編程和面向對象編程融合在一起,即:構造器也是方法(函數),傳遞參數和使用方法和前面的函數部分內容沒有區別。【案例演示+反編譯查看-語法糖】
  4、如果主構造器無參數,小括號可省略,構建對象時調用的構造方法的小括號也可以省略。
  5、輔助構造器名稱為 this(這個和 Java 是不一樣的),多個輔助構造器通過不同參數列表進行區分, 在底層就是f構造器重載。【案例演示+反編譯查看】
示例代碼如下:

package com.atguigu.chapter06.constructor

object ConstructorDemo02 {
  def main(args: Array[String]): Unit = {
    // val a = new A
    val aa = new A("jack")
    // 執行順序:
    // 1、bbb  父類構造器
    // 2、A    子類主構造器
    // 3、aaa  子類輔助構造器
  }
}

class B {
  println("bbb")
}

class A extends B {
  println("A")
  def this(name: String) {
    this // 調用A的主構造器,其根本原因就是實現子類與父類之間的繼承關系,不然繼承關系就斷了!!!
    println("aaa")
  }
}

輸出結果如下:

bbb
A
aaa

示例代碼如下:

package com.atguigu.chapter06.constructor

object ConstructorDemo03 {
  def main(args: Array[String]): Unit = {
    val p1 = new Person("scott")
    p1.showInfo()
  }
}

class Person() {
  var name: String = _
  var age: Int = _

  def this(name: String) {
    // 輔助構造器無論是直接或間接,最終都一定要調用主構造器,執行主構造器的邏輯
    // 而且需要放在輔助構造器的第一行[這點和 java 一樣,java 中一個構造器要調用同類的其它構造器,也需要放在第一行]
    this() // 直接調用主構造器
    this.name = name
  }

  def this(name: String, age: Int) {
    this() // 直接調用主構造器
    this.name = name
    this.age = age
  }

  def this(age: Int) {
    this("匿名") // 間接調用主構造器,因為 def this(name: String) 中直接調用了主構造器
    this.age = age
  }

  def showInfo(): Unit = {
    println("person信息如下:")
    println("name=" + this.name)
    println("age=" + this.age)
  }
}

輸出結果如下:

person信息如下:
name=scott
age=0

  6、如果想讓主構造器變成私有的,可以在()之前加上 private,這樣用戶只能通過輔助構造器來構造對象了。【反編譯查看】
  7、輔助構造器的聲明不能和主構造器的聲明(即形參列表)一致,會發生錯誤(即構造器名重復)。

6.5 屬性高級

  前面我們講過屬性了,這里我們再對屬性的內容做一個加強。

6.5.1 構造器參數


示例代碼如下:
package com.atguigu.chapter06.constructor

object ConstructorDemo04 {
  def main(args: Array[String]): Unit = {
    val worker1 = new Worker1("smith1")
    worker1.name    // 不能訪問 inName

    val worker2 = new Worker2("smith2")
    worker2.inName  // 可以訪問 inName
    println("hello!")

    val worker3 = new Worker3("jack")
    worker3.inName = "mary"
    println(worker3.inName)
  }
}

// 1. 如果 主構造器是 Worker1(inName: String),那么 inName 就是一個局部變量。
class Worker1(inName: String) {
  var name = inName
}
// 2. 如果 主構造器是 Worker2(val inName: String),那么 inName 就是 Worker2 的一個 private 的只讀屬性。
class Worker2(val inName: String) {
  var name = inName
}

// 3. 如果 主構造器是 Worker3(var inName: String),那么 inName 就是 Worker3 的一個 private 的可以讀寫屬性。
class Worker3(var inName: String) {
  var name = inName
}

6.5.2 Bean 屬性


示例代碼如下:
package com.atguigu.chapter06.constructor

import scala.beans.BeanProperty

object BeanPropertyDemo {
  def main(args: Array[String]): Unit = {
    val car = new Car
    car.name = "寶馬"
    println(car.name)

    // 使用 @BeanProperty 自動生成 getXxx() 和 setXxx()
    car.setName("奔馳")
    println(car.getName())
  }
}

class Car {
  @BeanProperty var name: String = null
}

6.6 Scala 對象創建的流程分析

6.7 作業03

1、一個數字如果為正數,則它的 signum 為1.0,如果是負數,則 signum 為-1.0,如果為0,則 signum 為0.0。編寫一個函數來計算這個值。
示例代碼如下:

package com.atguigu.chapter06.exercises

import scala.io.StdIn

/**
  * 1、一個數字如果為正數,則它的 signum 為1,如果是負數,則 signum 為-1,如果為0,則 signum 為0。編寫一個函數來計算這個值。
  */
object Exercise01 {
  def main(args: Array[String]): Unit = {
    println("請輸入一個數字:")
    val n = StdIn.readDouble()
    println("該數的 signum 為:" + signum(n))
  }

  def signum(n: Double): Double = {
    if (n > 0) {
      1
    } else if (n < 0) {
      -1
    } else {
      0
    }
  }
}

輸出結果如下:

請輸入一個數字:
0
該數的 signum 為:0.0

請輸入一個數字:
5
該數的 signum 為:1.0

請輸入一個數字:
-3
該數的 signum 為:-1.0

2、一個空的塊表達式{}的值是什么?類型是什么?
示例代碼如下:

package com.atguigu.chapter06.exercises

/**
  * 2、一個空的塊表達式 {} 的值是什么?類型是什么?
  */
object Exercise02 {
  def main(args: Array[String]): Unit = {
    val t = {}
    println("t=" + t) // t=()
    println(t.isInstanceOf[Unit]) // true
  }
}

3、針對下列 Java 循環編寫一個 Scala 版本:

    for (int i=10; i>=0; i–-) {
        System.out.println(i);
    }

示例代碼如下:

package com.atguigu.chapter06.exercises

/**
  * 3、針對下列 Java 循環編寫一個 Scala 版本:
  * for (int i=10; i>=0; i–-) {
  *   System.out.println(i);
  * }
  */
object Exercise03 {
  def main(args: Array[String]): Unit = {
    // 方式一:
    for (i <- 0 to 10) {
      println("i=" + (10 - i))
    }

    println("----------")

    // 方式二:
    for (i <- 0 to 10 reverse) { // 逆序
      println("i=" + i)
    }

    // 定義一個 List 集合
    val list = List(1, 2, 3)
    println(list) // List(1, 2, 3)
    println(list.reverse) // List(3, 2, 1)
  }
}

4、編寫一個過程 countdown(n: Int),打印從 n 到 0 的數字。
示例代碼如下:

package com.atguigu.chapter06.exercises

import scala.io.StdIn

/**
  * 4、編寫一個過程 countdown(n:Int),打印從 n 到 0 的數字。
  */
object Exercise04 {
  def main(args: Array[String]): Unit = {

    val m=3
    val res1 = (0 to m).reverse
    println(res1) // Range(3, 2, 1, 0)

    // foreach
    // foreach 函數可以接收 (f: Int => U),即接收一個輸入參數為 Int,輸出參數為 Unit 的函數
    // 下面這句代碼的含義是:
    // 1、將 res1 的每個元素依次遍歷出來,傳遞給 println(x)
    // 調用系統的 println 函數
    res1.foreach(println)

    // 調用自定義的 println 函數
    res1.foreach(myPrintln)


    println("----------")
    println("請輸入一個數字:")
    val n = StdIn.readInt()
    println("----------")
    countdown(n)
    println("----------")
    countdown2(n)
    println("----------")
    countdown3(n)
  }

  // 自定義一個 println 函數
  def myPrintln(n: Int): Unit = {
    println(n)
  }

  // 方式一:
  def countdown(n: Int): Unit = {
    for (i <- 0 to n) {
      println(n-i)
    }
  }

  // 方式二:
  def countdown2(n: Int): Unit = {
    for (i <- 0 to n reverse) {
      println(i)
    }
  }

  // 方式三:
  def countdown3(n: Int): Unit = {
    // 說明
    // 這里使用到高階函數的特性
    (0 to n).reverse.foreach(println)
  }
}

5、編寫一個 for 循環,計算字符串中所有字母的 Unicode 代碼(toLong 方法)的乘積。舉例來說,"Hello" 中所有字符串的乘積為 9415087488L。
示例代碼如下:

package com.atguigu.chapter06.exercises

import scala.io.StdIn

/**
  * 5、編寫一個 for 循環,計算字符串中所有字母的 Unicode 代碼(toLong 方法)的乘積。舉例來說,"Hello" 中所有字符串的乘積為 9415087488L。
  */
object Exercise05 {
  def main(args: Array[String]): Unit = {
    println("請輸入一行字符串:")
    val str = StdIn.readLine()
    println("該字符串中所有字母的 Unicode 代碼的乘積為:" + unicode(str))

    unicode2()
  }

  // 方式一:
  def unicode(str: String): Long = {
    var res:Long = 1
    for (i <- 0 to str.length - 1) { // 索引從0開始
      var s = str.charAt(i).toLong
      res *= s
    }
    res
  }

  // 方式二:
  def unicode2() = {
    var res:Long = 1
    for (i <- "Hello") {
      res *= i.toLong
    }
    println("res=" + res)
  }
}

輸出結果如下:

請輸入一行字符串:
Hello
該字符串中所有字母的 Unicode 代碼的乘積為:9415087488
res=9415087488

6、同樣是解決前一個練習的問題,請用 StringOps 的 foreach 方式解決。
示例代碼如下:

package com.atguigu.chapter06.exercises

import scala.io.StdIn

/**
  * 6、同樣是解決前一個練習的問題,請用 StringOps 的 foreach 方式解決。
  */
object Exercise06 {
  def main(args: Array[String]): Unit = {
    var res1: Long = 1
    // 說明
    // 方式一:
    // "Hello".foreach((_) => {res *= _.toLong})
    "Hello".foreach(res1 *= _.toLong)
    println("res1=" + res1)

    // 方式二:
    var res2 = 1L
    "Hello".foreach(myCount)
    println("res1=" + res2)
    def myCount(char: Char): Unit = {
      res2 *= char.toLong
    }
  }
}

輸出結果如下:

res1=9415087488
res1=9415087488

7、編寫一個函數 product(str: String),計算字符串中所有字母的 Unicode 代碼(toLong 方法)的乘積。
示例代碼如下:

package com.atguigu.chapter06.exercises

import scala.io.StdIn

/**
  * 7、編寫一個函數 product(str: String),計算字符串中所有字母的 Unicode 代碼(toLong 方法)的乘積。
  */
object Exercise07 {
  def main(args: Array[String]): Unit = {
    println("請輸入一行字符串:")
    val str = StdIn.readLine()
    println("該字符串中所有字母的 Unicode 代碼的乘積為:" + product1(str))
    println("該字符串中所有字母的 Unicode 代碼的乘積為:" + product2(str))
  }

  // 方式一:
  def product1(str: String): Long = {
    var multi = 1L
    for (i <- 0 to str.length - 1) { // 索引從0開始
      var s = str.charAt(i).toLong
      multi *= s
    }
    multi
  }

  // 方式二:
  def product2(str: String): Long = {
    var multi = 1L
    for (i <- str) {
      multi *= i.toLong
    }
    multi
  }
}

輸出結果如下:

請輸入一行字符串:
Hello
該字符串中所有字母的 Unicode 代碼的乘積為:9415087488
該字符串中所有字母的 Unicode 代碼的乘積為:9415087488

8、把7練習中的函數改成遞歸函數。
示例代碼如下:

package com.atguigu.chapter06.exercises

/**
  * 8、把7練習中的函數改成遞歸函數。
  */
object Exercise08 {
  def main(args: Array[String]): Unit = {
    println("res=" + product("Hello")) // res=9415087488

    println("Hello".take(1)) // H     獲取的是該字符串的第一個字符串
    println("Hello".drop(1)) // ello  獲取的是該字符串的除第一個字符串之外的剩余字符串
  }

  def product(str: String): Long = {
    if (str.length == 1) return str.charAt(0).toLong
    else str.take(1).charAt(0).toLong * product(str.drop(1))
  }
}

輸出結果如下:

res=9415087488
H
ello

9、編寫函數計算,其中 n 是整數,使用如下的遞歸定義:


示例代碼如下:
package com.atguigu.chapter06.exercises

/**
  * 9、編寫函數計算,其中 n 是整數,使用如下的遞歸定義:
  */
object Exercise09 {
  def main(args: Array[String]): Unit = {
    println(mi(2.5, 3))
  }

  // 遞歸的妙用:求 x 的 n 次方,厲害啊!!!
  def mi(x: Double, n: Int): Double = {
    if (n == 0) 1   // x 的 0 次方等於 1
    else if (n > 0) x * mi(x, n - 1)
    else 1 / mi(x, -n)
  }
}

注意:本題可以用於好好理解“遞歸”的妙用!!!

Java 與 Scala 在函數層面上的不同體現:
// 在 Java 中
函數(接收參數)

// 在 Scala 中
集合.函數(函數)

如下圖所示:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM