大数据技术之_16_Scala学习_04
第五章 函数式编程-基础
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
46题的示例代码原理同上13题,不在赘述!
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 中
集合.函数(函数)
如下图所示:
我的GitHub地址:https://github.com/heizemingjun
我的博客园地址:https://www.cnblogs.com/chenmingjun
我的蚂蚁笔记博客地址:https://blog.leanote.com/chenmingjun
Copyright ©2018~2019 黑泽君
【转载文章务必保留出处和署名,谢谢!】