Cris 玩转 Scala

栏目: Scala · 发布时间: 5年前

内容简介:Scala是一种纯面向对象的语言,每个值都是对象。对象的数据类型以及行为由类和特质描述Scala也是一种函数式语言,其函数也能当成值来使用。Scala提供了轻量级的语法用以定义匿名函数,支持高阶函数,允许嵌套多层函数,并支持柯里化Scala具备类型系统,通过编译时检查,保证代码的安全性和一致性

Scala是一种纯面向对象的语言,每个值都是对象。对象的数据类型以及行为由类和特质描述

② 函数式编程

Scala也是一种函数式语言,其函数也能当成值来使用。Scala提供了轻量级的语法用以定义匿名函数,支持高阶函数,允许嵌套多层函数,并支持柯里化

③ 静态类型

Scala具备类型系统,通过编译时检查,保证代码的安全性和一致性

④ 扩展性

Scala的设计秉承一项事实,即在实践中,某个领域特定的应用程序开发往往需要特定于该领域的语言扩展。Scala提供了许多独特的语言机制,可以以库的形式轻易无缝添加新的语言结构

⑤ 并发性

Scala使用Actor作为其并发模型,Actor是类似线程的实体,通过邮箱发收消息。Actor可以复用线程,因此可以在程序中可以使用数百万个Actor,而线程只能创建数千个。在2.10之后的版本中,使用Akka作为其默认Actor实现

1.2 学习 Scala 的原因

  1. Spark 框架就是使用 Scala 写的,为了更好的学习 Spark ,Scala 是必须要熟悉并且精通的!
  2. Scala 是一门 Scalable Language,支持面向对象和函数式编程,十分有趣
  3. Scala 和 Java 十分 “亲近”,Scala 的创造者就是 Java 5.0 和 Java 8.0 的编译器的作者,熟悉 Scala 对于 Java 学习也会有一个更加深刻的理解
  4. 现在会 Scala 的人相对较少,而大数据领域 Spark 的地位又非常高,如果可以精通 Scala,那么对于个人发展也是帮助大大的:happy:

1.3 Java 和 scala 以及 JVM 的关系图

Cris 玩转 Scala

Scala 比较难学的原因主要是:

  1. 语法兼顾面向对象和函数式编程,比较容易混淆
  2. 部分支持 Java 的类库,又有自己的类库,还对 Java 的部分类库做了封装,查看 Scala 源码要求较高
  3. 由于是对 Java 的 “封装”,学习 Scala 的前提就是要会 Java:cowboy_hat_face:

1.4 Scala 学习建议

  1. 主要就是学习 Scala 的特殊语法
  2. 区别 Scala 和 Java
  3. 规范的使用 Scala 编写代码

只要按照以上三点学习,那么熟悉乃至精通 Scala 也就不在话下了~

1.5 安装 Scala

因为 Cris 使用的 Linux 桌面系统,所以安装方法和 Windows 不太一致,悉知~

这里给出 Scala 的官网

下载 tar.gz 包,然后解压到指定文件夹,注意:提前安装 jdk!

然后配置 /etc/profile

Cris 玩转 Scala

输入 scala -version

Cris 玩转 Scala

1.6 第一个 Scala 程序

首先使用 Vim 编写一个最简单的 Scala 程序

object testScala{
    def main(args:Array[String]):Unit = {
        System.out.println("hello,Scala")
    }
}
复制代码

然后使用 scalac 编译

Cris 玩转 Scala

使用 java 和 scala 同样可以执行

Cris 玩转 Scala

进一步证实了 Scala 语言编写的代码是基于 Java 的并且执行环境就是 JVM 虚拟机

1.7 IDEA 安装 Scala 插件

直接在 IDEA 的插件地址下载即可,注意:IDEA 版本和 Scala 插件的版本要一致!

安装完毕重启 IDEA 即可

让我们新建一个 Scala 项目

Cris 玩转 Scala
Cris 玩转 Scala

如果新建文件没有出现 scala 的选项,请参考

Cris 玩转 Scala

当然实际开发中都是建一个 Maven 项目

Cris 玩转 Scala
Cris 玩转 Scala

2. Scala 基础语法

2.1 反编译 Scala 代码了解运行原理

首先新建一个 Scala object

object Hello {
  def main(args: Array[String]): Unit = {
    println("hello,scala!")
  }
}
复制代码

直接执行可以得到 hello,scala! 的结果

我们再看看编译后的字节码文件

Cris 玩转 Scala

使用反编译软件打开这两个字节码文件

Cris 玩转 Scala
Cris 玩转 Scala

//说明一下scala程序的执行流程

//1. object 在底层会生成两个类 Hello , Hello$

//2. Hello 中有个main函数,调用 Hello

public final class Hello

{

public static void main(String[] paramArrayOfString)

{

​ Hello .main(paramArrayOfString);

}

}

//3. Hello 对象是静态的,通过该对象调用Hello$的main函数

public void main(String[] args)

{

​ Predef..MODULE$.println("hello,scala");

}

//4. 可以理解我们在main中写的代码在放在Hello$ 的main, 在底层执行scala编译器做了一个包装

如果使用 Java 代码模拟实现 Scala 程序的执行流程

/**
 * 模拟 Scala 的运行流程
 *
 * @author cris
 * @version 1.0
 **/
public class Hello2 {
    public static void main(String[] args) {
        Hello2$.HELLO_$.hello();
    }
}

final class Hello2$ {
    public static Hello2$ HELLO_$;

    static {
        new Hello2$();
    }

    public void hello() {
        System.out.println("hello,scala!");
    }

    private Hello2$() {
        HELLO_$ = this;
    }
}
复制代码

至此,对于 Scala 的运行机制,我们应该有了一个简单的了解,并且为什么会编译出两个 Scala 的字节码文件也有了个认识 :smile:

2.2 Scala 执行流程图及开发规范

Cris 玩转 Scala
  1. Scala源文件以 “.scala" 为扩展名

  2. Scala程序的执行入口是main()函数

  3. Scala语言严格区分大小写

  4. Scala方法由一条条语句构成,每个语句后不需要分号(Scala语言会在每行后自动加分号),这也体现出Scala的简洁性

  5. 如果在同一行有多条语句,除了最后一条语句不需要分号,其它语句需要分号

2.3 Scala 的特殊字符

object TestChar {
  def main(args: Array[String]): Unit = {
    println("name\tage") // name	age
    println("cirs\"18") // cirs"18
    println("simida\\james") // simida\james
    println("rose\ncurry")
    println("abc\rk") // k,如果是 java,也是 k
  }
}
复制代码

2.4 关联源码包及文档

在使用scala过程中,为了搞清楚scala底层的机制,需要查看源码,下面看看如何关联和查看Scala的源码包

  • 先要解压下载的 Scala 的源码包

    Cris 玩转 Scala
  • 将解压后的源码包移动到之前解压的 Scala 安装包路径下,然后打开 IDEA 关联即可

    $ sudo mv scala-2.12.4/ /usr/local/scala/lib/
    复制代码
    Cris 玩转 Scala

    之后就可查看 Scala 的源码了

    Cris 玩转 Scala

如果想要查看 Scala 的文档可以去官网查看,也可以下载下来

Cris 玩转 Scala
Cris 玩转 Scala

2.5 注释

用于注解说明解释程序的文字就是注释,注释提高了代码的阅读性;

注释是一个 程序员 必须要具有的良好编程习惯。将自己的思想通过注释先整理出来,再用代码去体现

Scala 中的注释分为三种:

  1. 单行注释:

    // 打印一行文字
        println("hello,scala!")
    复制代码
  2. 多行注释:

    /*
        println("hello,scala!")
        println("hello,scala!")
        */
    复制代码
  3. 文档注释

    /**
        * 打印名字的函数
        *
        * @param name 名字
        */
      def say(name: String): Unit = {
        println(name)
      }
    复制代码

拓展:使用命令生成 Scala 的文档注释

Cris 玩转 Scala

然后查看文档

Cris 玩转 Scala
Cris 玩转 Scala

2.6 Scala语言输出的三种方式

  1. 字符串通过+号连接(类似java)
  2. printf用法 (类似C语言)字符串通过 % 传值
  3. 字符串通过$引用(类似PHP)
var name = "cris"
    var salary = 28000.00
    println("my name is " + name) // my name is cris
    printf("my name is %s,and salary is %f\n", name, salary) // my name is cris,and salary is 28000.000000
    print(s"my name is $name,and salary is $salary") // my name is cris,and salary is 28000.0
复制代码

3.变量

3.1 基本概念

变量相当于内存中一个数据存储空间的表示,你可以把变量看做是一个房间的门牌号,通过门牌号我们可以找到房间,而通过变量名可以访问到变量(值)

变量的初始化(自动类型推断)scala要求变量声明时初始化

var age = 12
var name = "cris"
var flg = true
var key = 'a'

// 如果不使用 Scala 的自动类型推断
val b: Double = 11.11
var a: String = "simida"
复制代码

变量使用详解

var | val 变量名 [: 变量类型] = 变量值,说明:在scala中声明一个变量时,可以不指定类型,编译器根据值确定

声明变量时,类型可以省略(编译器自动推导,即类型推导)

类型确定后,就不能修改,说明Scala 是强数据类型语言

在声明/定义一个变量时,可以使用var 或者 val 来修饰, var 修饰的变量可改变(值和引用),val 修饰的变量不可改(引用)

val修饰的变量在编译后,等同于加上final通过反编译看底层代码就知道了

var 修饰的对象引用可以改变,val 修饰的则不可改变,但对象的状态(值)却是可以改变的。(比如: 自定义对象、数组、集合等等)

变量声明时,需要初始值

object Main {
  def main(args: Array[String]): Unit = {
    var stu = new Stu
    val stu2 = new Stu

    // var 修改的变量指向的对象的值和引用均可改变
    stu.name = "james"
    stu = null

    // val 修饰的变量指向的对象的值可以改变,但是引用无法改变
    stu2.name = "simida"
    //    stu2 = null   // 报错
  }

}

class Stu {
  // 属性默认就是 public 权限
  var name = "cris"
}
复制代码

能使用 val 就别使用 var !

因为改变变量引用指向的的工作量大大高于改变对象值的工作量,使用 val 关键字,底层编译器也会对其进行优化,效率更高!

3.2 数据类型

Scala 与 Java有着相同的数据类型,在Scala中数据类型都是对象,也就是说scala没有java中的原生类型

Scala数据类型分为两大类 AnyVal(值类型) 和 AnyRef(引用类型), 注意:不管是AnyVal还是AnyRef 都是对象

相对于java的类型系统,scala要复杂些!也正是这复杂多变的类型系统才让面向对象编程和函数式编程完美的融合在了一起

val a = 12
println(a.toString) // 12

val result = 'a'.equals(a)
println(result) // false
复制代码

3.3 数据类型体系图

Cris 玩转 Scala
  1. scala中一切数据都是对象,都是Any的子类

  2. scala中数据类型分为两大类 值类型(AnyVal) , 引用类型(AnyRef),不管是值类型还是引用类型都是对象

  3. scala数据类型仍然遵守低精度的值类型向高精度的值类型,自动转换(隐式转换)

  4. scala中有两个特殊的类型 Null , 它只有一个实例就是null, 在scala中,也叫bottom class

  5. scala中有个特殊的类型 Nothing, 它是用于在一个函数没有正常返回值使用 , 因为这样我们可以把抛出的返回值,返回给任何的变量或者函数

3.4 整数类型

Scala的整数类型就是用于存放整数值的,比如 12 , 30, 3456等等

整型的类型

数据类型 描述
Byte [1] 8位有符号补码整数。数值区间为 -128 到 127
Short [2] 16位有符号补码整数。数值区间为 -32768 到 32767
Int [4] 32位有符号补码整数。数值区间为 -2147483648 到 2147483647
Long [8] 64位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807 = 2的(64-1)次方-1

建议:开发时,根据具体的业务选择合适的数据类型,做大数据最重要的就是细节,节省每一块内存,节省每一毫秒,这就是别人和你的差距!!!

  • Scala各整数类型有固定的表示范围和字段长度,不受具体OS的影响,以保证Scala程序的可移植性。

  • Scala的整型 常量/字面量 默认为 Int 型,声明Long型 常量/字面量 须后加‘l’’或‘L’

  • Scala程序中变量常声明为Int型,除非不足以表示大数,才使用Long

3.5 浮点数据类型

Scala的浮点类型可以表示一个小数,比如 123.4f,7.8 ,0.12等等

浮点型分类

Float [4] 32 位, IEEE 754标准的单精度浮点数
Double [8] 64 位 IEEE 754标准的双精度浮点数

浮点数据类型使用细节

Cris 玩转 Scala
val num = 23.2e2 // 等价于 23.2 × 10^2
    println(num) // 2320.0
    val num2 = 23.2e-2 // 等价于 23.2 × 10^-2
    println(num2) // 0.232

    // 实际开发中,如果对精度要求很高,请一定使用 Duoble 数据类型,Float 数据类型最多精确到小数点后 7 位
    val num3 = 23.243454544334f
    val num4 = 23.243454544334
    println(num3) // 23.243454
    println(num4) // 23.243454544334
复制代码

3.6 字符类型(Char)

字符类型可以表示单个字符,字符类型是Char, 16位无符号Unicode字符(2个字节), 区间值为 U+0000 到 U+FFFF

字符类型使用细节

  1. 字符常量是用单引号(‘ ’)括起来的单个字符。例如:var c1 = 'a‘ var c2 = '中‘ var c3 = '9'

  2. Scala 也允许使用转义字符‘\’来将其后的字符转变为特殊字符型常量。例如:var c3 = ‘\n’ // '\n'表示换行符

  3. 可以直接给Char赋一个整数,然后输出时,默认会按照对应的unicode 字符输出

  4. Char类型是可以进行运算的,相当于一个整数,因为它都对应有Unicode码

var a = 'A'
    println(a) // A
    println(a.toInt) // 65
    println(a + 3) // 68
    println((a + 3).toChar) // D

    var b: Char = 65
    println(b) // A,Char 类型的数字输出默认根据 assii 表进行转换成对应的字符 
复制代码

小思考

var e: Char = 99 // 编译器只判断数值范围是否越界
    
    //var f : Char = 'a'+1  // 参与运算,自动转换为 Int 数据类型,将 Int =》 Char 有精度损失问题
    var n = 'a' + 1
    var n2: Char = ('a' + 1).toChar
复制代码

字符型 存储到 计算机中,需要将字符对应的码值(整数)找出来

存储:字符——>码值——>二进制——>存储

读取:二进制——>码值——> 字符——>读取

3.7 布尔类型

布尔类型也叫Boolean类型, Booolean类型数据只允许取值true和false

boolean类型占1个字节。

boolean 类型适于逻辑运算,一般用于程序流程控制

3.8 Unit类型、Null类型和Nothing类型

Unit 表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。
Null null , Null 类型只有一个实例值 null
Nothing Nothing类型在Scala的类层级的最低端;它是任何其他类型的子类型。当一个函数,我们确定没有正常的返回值,可以用Nothing 来指定返回类型,这样有一个好处,就是我们可以把返回的值(异常)赋给其它的函数或者变量(兼容性)
...
	var res = eat()
    println(res)  // ()

 	var result = exe()  // 任意数据类型都可以接收 Nothing 数据类型的数据
  }

  def eat(): Unit ={
    println("eat meat!")
  }

  def exe(): Nothing ={
    throw new Exception("这是异常")
  }
复制代码

细节

  • Null类只有一个实例对象,null,类似于Java中的null引用。null可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal: 比如 Int, Float, Char, Boolean, Long, Double, Byte, Short)

  • Unit类型用来标识过程,也就是没有明确返回值的函数。由此可见,Unit类似于Java里的void。Unit只有一个实例,(),这个实例也没有实质的意义

  • Nothing,可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方法不会正常返回,而且由于Nothing是其他任意类型的子类,他还能跟要求返回值的方法兼容

3.9 值类型转换

当Scala程序在进行赋值或者运算时,精度小的类型自动转换为精度大的数据类型,这个就是自动类型转换(隐式转换)

数据类型按精度(容量)大小 排序

Cris 玩转 Scala

自动类型转换细节说明

  1. 有多种类型的数据混合运算时,系统首先自动将所有数据转换成容量最大的那种数据类型,然后再进行计算。

  2. 当我们把精度(容量)大 的数据类型赋值给精度(容量)小 的数据类型时,就会报错,反之就会进行自动类型转换。

  3. (byte, short) 和 char之间不会相互自动转换。

  4. byte,short,char 他们三者可以计算,在计算时首先转换为int类型。

  5. 自动提升原则: 表达式结果的类型自动提升为 操作数中最大的类型

自动类型转换的逆过程,将容量大的数据类型转换为容量小的数据类型。使用时要加上强制转函数,但可能造成精度降低或溢出,格外要注意

强制类型转换细节说明

  1. 当进行数据的 从 大——>小,就需要使用到强制转换

  2. 强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级

  3. Char类型可以保存 Int的常量值,但不能保存Int的变量值,需要强转

  4. Byte和Short类型在进行运算时,当做Int类型处理。

var a = 12.3.toInt
println(a)  //12

var result = 10 * 3.4.toInt + 5 * 1.6.toInt
println(result) // 35
var result2 = (10 * 34 + 5 * 1.6).toInt
println(result2) // 348

var b : Char = result.toChar
var d : Char = 100
复制代码

3.10 值类型和字符串的转换

在将String 类型转成 基本数据类型时,要确保String类型能够转成有效的数据,比如 我们可以把 "123" , 转成一个整数,但是不能把 "hello" 转成一个整数

把 "12.5" 转成 Int ? // 错误,在转换中,不会自动使用字符串的截取

3.11 标识符的命名规范

Scala 对各种变量、方法、函数等命名时使用的字符序列称为标识符 凡是自己可以起名字的地方都叫标识符

  1. Scala中的标识符声明,基本和Java是一致的,但是细节上会有所变化。

  2. 首字符为字母,后续字符任意字母和数字,美元符号,可后接下划线_

  3. 数字不可以开头。

  4. 首字符为操作符(比如+ - * / ),后续字符也需跟操作符 ,至少一个(反编译)

  5. 操作符(比如+-*/)不能在标识符中间和最后.

  6. 用反引号 .... 包括的任意字符串,即使是关键字(39个)也可以 [true]

var ++ = 12
    println(++) // 12

    var `true` = false
    println(`true`)   // false
复制代码

注意事项:

  1. 包名:尽量采取有意义的包名,简短,有意义

  2. 变量名、函数名 、方法名 采用驼峰法

标识符小测试

hello // ok

hello12 // ok

1hello // error

h-b // error

x h // error

h_4 // ok

_ab // ok

Int // ok , 因为在scala Int 是预定义的字符

Float // ok

_ // error ,因为在scala _ 有很多其他的作用。

Abc // ok

+*- // ok

+a // error

Scala 中的关键字

package, import, class, object, trait, extends, with, type, forSome
private, protected, abstract, sealed, final, implicit, lazy, override
try, catch, finally, throw
if, else, match, case, do, while, for, return, yield
def, val, var
this, super
new
true, false, null

4. 运算符

4.1 运算符种类

  1. 算术运算符

  2. 赋值运算符

  3. 比较运算符(关系运算符)

  4. 逻辑运算符

  5. 位运算符

4.2 算数运算符

Cris 玩转 Scala
var cal = 10 / 3
println(cal) // 3
var cal2: Double = 10 / 3
println(cal2) // 3.0
var cal3 = 10.0 / 3
println(cal3) // 3.3333333333333335

println(cal3.formatted("%.2f")) // 3.33 保留小数点后两位,并且使用四舍五入

var cal4 = 10 % 3
println(cal4) //  1
复制代码

细节:

对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。 例如:var x : Int = 10/3 ,结果是 3

当对一个数取模时,可以等价 a%b=a-a/b*b , 这样我们可以看到 取模的一个本质运算(和java 的取模规则一样)

println(-10 % 3) // -1
println(10 % (-3)) // 1
println(-10 % (-3)) // -1
复制代码

注意:Scala中没有++、--操作符,需要通过+=、-=来实现同样的效果

小练习

/*计算周数和天数*/
var days = 96
var weeks = days / 7
var day = days % 7
println(s"还剩下${weeks}周及 $day 天") // 还剩下13周及 5 天

/*计算摄氏度*/
var huashi = 32.4
var sheshi = 5.0 / 9 * (huashi - 100)
println(sheshi.formatted("%.2f")) // -37.56
复制代码

4.3 关系运算符

关系运算符一览图

Cris 玩转 Scala

小细节

var a = 1.3
var b = 1.3
println(a == b) //true
a = 1.2
b = 1.2f
println(a == b) //false
a = 1
b = 1.0
println(a == b) //true
复制代码

细节说明

  1. 关系运算符的结果都是Boolean型,也就是要么是true,要么是false。

  2. 关系运算符组成的表达式,我们称为关系表达式。

  3. 比较运算符“==”不能误写成“=”

  4. 使用陷阱: 如果两个浮点数进行比较,应当保证数据类型一致

4.4 逻辑运算符

一览图

Cris 玩转 Scala

Scala 的逻辑运算符使用和 Java 一致

4.5 赋值运算符

一览图

Cris 玩转 Scala
Cris 玩转 Scala

关于按位与 &:两个数的二进制位同为1,则为1;否则为0

关于按位或 |:两个数的二进制位有1,则为1,;否则为0

关于按位异或 ^ :两个数的二进制位不同,则为1;相同为0

4.6 位运算符

小练习:快速交换两个数的值

var d = 2
var e = 1
d = d + e
e = d - e
d = d - e
复制代码

或者使用按位异或

var d = 2
var e = 1
d = d ^ e
e = d ^ e
d = d ^ e
复制代码
Cris 玩转 Scala

注意:Scala 没有三元运算符,而是用 if else 替换

// 求出两个数的最大值

val a = 10
val b = 20

val max = if (a > b) {
  a
} else {
  b
}
println(max)
复制代码

4.7 运算符优先级

Cris 玩转 Scala

4.8 键盘输入

先看看 Java 怎么处理键盘输入的

public class Demo {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入名字");
        String name = scanner.next();
        System.out.println("请输入年龄");
        int age = scanner.nextInt();
        System.out.println(String.format("名字是:%s,年龄是:%s", name, age));
    }
}
复制代码

再看看 Scala 怎么处理的

def main(args: Array[String]): Unit = {
  println("请输入名字:")
  val name = StdIn.readLine()
  println("请输入年龄:")
  val age = StdIn.readShort()
  println(s"名字是:${name},年龄是:${age}")
}
复制代码

5. 流程控制

5.1 分支控制

单分支

if (条件表达式) {

​ 执行代码块

}

说明:当条件表达式为ture 时,就会执行 { } 的代码

双分支

if (条件表达式) {

​ 执行代码块1

​ } else {

​ 执行代码块2

}

多分支

if (条件表达式1) {

​ 执行代码块1

​ }

​ else if (条件表达式2) {

​ 执行代码块2

​ }

​ ……

​ else {

​ 执行代码块n

​ }

分支控制if-else 注意事项

  1. 如果大括号{}内的逻辑代码只有一行,大括号可以省略, 这点和java 的规定一样。

  2. Scala中任意表达式都是有返回值的,也就意味着if else表达式其实是有返回结果的,具体返回结果的值取决于满足条件的代码体的最后一行内容

  3. Scala中是没有三元运算符,但是可以这样写 var result = if(a>0)"a" else "b"

  4. 在scala中没有switch,而是使用模式匹配来处理

小练习:

求二元一次方程组的解(ax^2+bx+c=0)

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

  println("请输入a:")
  val a = StdIn.readInt()
  println("请输入b:")
  val b = StdIn.readInt()
  println("请输入c:")
  val c = StdIn.readInt()

  val d = math.pow(b, 2) - 4 * a * c

  if (d > 0) {
    val x1 = (-b + math.sqrt(b * b - 4 * a * c)) / (2 * a)
    var x2 = (-b - math.sqrt(b * b - 4 * a * c)) / (2 * a)
    println(x1.formatted("%.2f") + "," + x2.formatted("%.2f"))
  } else if (d == 0) {
    val x = (-b + math.sqrt(b * b - 4 * a * c)) / (2 * a)
    println(x)
  } else {
    println("无解")
  }

}
复制代码

5.2 for循环控制

Scala 也为for 循环这一常见的控制结构提供了非常多的特性,这些for 循环的特性被称为for 推导式(for comprehension)或for 表达式(for expression)

范围数据循环方式1

for(i <- 1 to 3){

print(i + " ")

}

  1. i 表示循环的变量, <- 规定 to 规定

  2. i 将会从 1-3 循环, 前后闭合

范围数据循环方式2

for(i <- 1 until 3) {

print(i + " ")

}

  1. 这种方式和前面的区别在于 i 是从1 到 3-1

  2. 前闭合后开的范围

循环守卫

for(i <- 1 to 3 if i != 2) {

print(i + " ")

}

循环守卫,即循环保护式(也称条件判断式,守卫)。保护式为true则进入循环体内部,为false则跳过,类似于continue

上面的代码等价

for (i <- 1 to 3){
	if (i != 2) {
		print(i + "")
	}
复制代码

引入变量

for(i <- 1 to 3; j = 4 - i) {

print(j + " ")

}

  1. 没有关键字,所以范围后一定要加;来隔断逻辑

  2. 上面的代码等价

for (i <- 1 to 4) {
  val j = i + 1
  println(j)
}
复制代码

还有另外一种写法

for {
  i <- 1.to(2)
  j = i + 2} {
  println(i + "****" + j)
}
复制代码

嵌套循环

for (i <- 1.to(3); j <- 1 to 2) {
  println(s"i=${i},j=${j}")
}
复制代码
  1. 没有关键字,所以范围后一定要加;来隔断逻辑

  2. 上面的代码等价

for (i <- 1.to(3)) {
  for (j <- 1 to 2) {
    println(i + "--" + j)
  }
}
复制代码

练习:打印九九乘法表

/*打印九九乘法表*/
for (i <- 1.to(9); j <- 1 to i){
  print(i+"*"+j+"="+(i*j)+"\t")
  if (i == j){
    println()
  }
}
复制代码

循环返回值

val ints = for (i <- 1.to(3)) yield i * 2
println(ints) // Vector(2, 4, 6)
复制代码

将遍历过程中处理的结果返回到一个新Vector集合中,使用yield关键字

细节注意

  1. scala 的for循环形式和java是较大差异,这点请同学们注意,但是基本的原理还是一样的。

  2. scala 的for循环是表达式,是有返回值的。java的for循环不是表达式,没有返回值.

  3. scala 的for循环的步长如何控制! [for(i <- Range(1,3,2)]

/*右闭*/
    for (i <- 1.to(5, 2)) {
      println(i)
    }

    /*右开*/
    for (i <- Range.apply(1, 5, 2)) {
      println("===" + i)
    }
复制代码

5.3 while 循环

while 循环

循环变量初始化

while (循环条件) {

​ 循环体(语句)

​ 循环变量迭代

}

  1. 循环条件是返回一个布尔值的表达式

  2. while循环是先判断再执行语句

  3. 与If语句不同, While语句本身没有值 ,即整个While语句的结果是Unit类型的()

  4. 因为while中没有返回值,所以当要用该语句来计算并返回结果时,就不可避免的使用变量 ,而变量需要声明在while循环的外部,那么就等同于循环的内部对外部的变量造成了影响, 也就违背了函数式编程的重要思想( 纯函数思想 ),所以不推荐使用,而是 推荐使用for循环

do...while 循环

  1. 循环条件是返回一个布尔值的表达式

  2. do..while循环是先执行,再判断

  3. 和while 一样,因为do…while中没有返回值,所以当要用该语句来计算并返回结果时,就不可避免的使用变量 ,而变量需要声明在do...while循环的外部,那么就等同于循环的内部对外部的变量造成了影响, 也就违背了函数式编程的重要思想(纯函数思想),所以不推荐使用,而是推荐使用for循环

/*while 循环*/
var i = 1
while (i < 2) {
  println(i)
  i += 1
}

/*do while 循环*/
do {
  println("*" + i)
  i += 1
} while (i < 4)
复制代码

5.4 多重循环

介绍

  1. 将一个循环放在另一个循环体内,就形成了嵌套循环。其中,for ,while ,do…while均可以作为外层循环和内层循环。【 建议一般使用两层,最多不要超过3层

  2. 实质上,嵌套循环就是把内层循环当成外层循环的循环体。当只有内层循环的循环条件为false时,才会完全跳出内层循环,才可结束外层的当次循环,开始下一次的循环。

  3. 设外层循环次数为m次,内层为n次, 则内层循环体实际上需要执行 m*n=m n次。

5.5 循环中断(重点)

Scala内置控制结构特地 去掉了break和continue ,是为了更好的适应 函数式编程 ,推荐使用函数式的风格解决break和contine的功能,而不是一个关键字

细节

  1. 在break() 函数 代码是
def break(): Nothing = { throw breakException }
复制代码
  1. breakable(op: =>Unit) 函数, 是一个高阶函数,可以接受另外一个函数或者一段并执行,并处理如果捕获到breakException, 就 throw ex
def breakable(op: => Unit) {
  try {
    op
  } catch {
    case ex: BreakControl =>
      if (ex ne breakException) throw ex
  }
}
复制代码
  1. scala中有这个风格,如果我们传入的是参数【代码】,是多行的,则我们的可以使用{} 替代()

  2. 在scala如果要使用continue的逻辑,则使用if-else 来处理

  3. 在for循环中,一样可以使用breakable( break() )

5.6 练习

  1. 100以内的数求和,求出当和第一次大于20的当前数

    // 如果不使用 Scala 的异常中断机制
    var sum = 0
    var flag = true
    for (i <- 1.to(100);if flag) {
        sum += i
        if (sum >= 20) {
            println(i)
            flag = false
        }
    }
    
    // 如果使用 Scala 的异常中断机制
    breakable {
        var sum = 0
        for (i <- 1 to 100) {
            sum += i
            if (sum >= 20) {
                println(i)
                break()
            }
        }
    }
    复制代码
  2. 实现登录验证,有三次机会,如果用户名为”cris” ,密码”123”提示登录成功,否则提示还有几次机会,请使用for 循环完成

    // 如果不使用 Scala 的异常中断机制
    var flg = true
    for (i <- 1 to 3; if flg) {
    
        println("请输入名字:")
        val name = StdIn.readLine()
        println("请输入密码:")
        val password = StdIn.readLine()
        if ("cris".equals(name) && "123".equals(password)) {
            println("登录成功!")
            flg = false
        } else {
            if (i != 3) {
                println("登录失败!你还剩下" + (3 - i) + "次机会!")
            } else {
                println("登录失败!你已经没有机会了!")
            }
        }
    }
    }
    
    // 如果使用 Scala 的异常中断机制
    breakable {
        for (i <- 1 to 3) {
            println("请输入名字:")
            val name = StdIn.readLine()
            println("请输入密码:")
            val password = StdIn.readLine()
            if ("cris".equals(name) && "123".equals(password)) {
                println("登录成功!")
                break()
            } else {
                if (i != 3) {
                    println("登录失败!你还剩下" + (3 - i) + "次机会!")
                } else {
                    println("登录失败!你已经没有机会了!")
                }
            }
        }
    }
    复制代码

    换句话说,Scala 通过异常中断机制来完成循环的终止,这个机制得以实现的前提就是函数式编程

  3. 根据规则统计路口通过总次数:金额大于50000,每次通过减少5%,;否则每次通过金额减去1000

    var money = 100000.0
    var count = 0
    while (money >= 1000) {
        count += 1
        if (money > 50000) {
            money *= 0.95
        } else {
            money -= 1000
        }
    }
    println("可以经过" + count + "次路口")
    复制代码
  4. 移除缓冲数组中除第一个负数以外的所有负数(剩下所有元素的顺序需保持一致)

    val array = ArrayBuffer(1, 2, -4, 0, -2, -1, 9)
    var flag = true
    /*这里需要得到满足要求的所有索引,精髓!*/
    val indexs = for (i <- array.indices; if array(i) >= 0 || flag) yield {
      if (array(i) < 0) flag = false
      i
    }
    println(indexs)
    // 这里还必须使用 索引 来对可变数组中的元素进行替换!
    for (i <- 0 until indexs.length) array(i) = array(indexs(i))
    // 从尾巴开始,去掉指定范围元素
    array.trimEnd(array.length - indexs.length)
    println(array) // ArrayBuffer(1, 2, -4, 0, 9)
    复制代码

6. 函数式编程基础(重点)

Cris 玩转 Scala

函数式编程和 oop 的关系

Cris 玩转 Scala

概念说明

  1. 在scala中,方法和函数几乎可以等同(比如他们的定义、使用、运行机制都一样的),只是函数的使用方式更加的灵活多样。

2)) 函数式编程是从编程方式(范式)的角度来谈的,可以这样理解函数式编程把函数当做 一等公民 ,充分利用函数支持的函数的多种使用方式

​ 在Scala当中,函数是一等公民,像变量一样,既可以作为函数的参数使用,也可以将函数赋值给一个变量. ,函数的创建不用依赖于类或者对象,而在Java当中,函数的创建则要依赖于类、抽象类或者接口

  1. 面向对象编程是以对象为基础的编程方式。

  2. 在 Scala 中函数式编程和面向对象编程融合在一起了

Cris 玩转 Scala

函数式编程介绍

Ø "函数式编程"是一种"编程范式"(programming paradigm)。

Ø 它属于"结构化编程"的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。

Ø 函数式编程中,将函数也当做数据类型,因此可以接受函数当作输入(参数)和输出(返回值)。

Ø 函数式编程中,最重要的就是函数。

函数式编程基本语法

Cris 玩转 Scala

小练习

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

    val res = getResult(1, 2, '=')
    if (res != null) println("result=" + res) else println("返回结果不正确!请输入规定的操作符")
  }

  /**
    * 要求:定义一个方法,只对 + 和 - 做出对应的操作
    */
  def getResult(a: Int, b: Int, x: Char) = {
    if (x == '+') {
      a + b
    } else if (x == '-') {
      a - b
    } else {
      null
    }
  }

}
复制代码

递归(重点)

  1. 执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)

  2. 函数的局部变量是独立的,不会相互影响

  3. 递归必须向退出递归的条件逼近,否则就是无限递归,死龟了:)

  4. 当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当函数执行完毕或者返回时,该函数栈本身也会被系统销毁

递归练习

  1. 以下两个递归函数的结果

    def test(n: Int): Unit = {
        if (n > 2) {
          test(n - 1)
        }
        println("n=" + n)
      }
    
      def test2(n: Int): Unit = {
        if (n > 2) {
          test(n - 2)
        } else {
          println("n=" + n)
        }
      }
    复制代码
  2. 请使用递归的方式,求出斐波那契数1,1,2,3,5,8,13... 给你一个位置n,求出它的斐波那契数是多少?

    def feibo(n: Int): Int = {
      if (n <= 0) {
        0
      } else {
        if (n <= 2) 1 else feibo(n - 2) + feibo(n - 1)
      }
    }
    复制代码
  3. 已知 f(1)=3; f(n) = 2*f(n-1)+1; 请使用递归的思想编程,求出 f(n)的值?、

    //已知 f(1)=3; f(n) = 2*f(n-1)+1; 请使用递归的思想编程,求出 f(n)的值?
    def func1(n: Int): Int = {
      if (n < 1) {
        0
      } else {
        if (n == 1) 1 else 2 * func1(n - 1) + 1
      }
    }
    复制代码
  4. 有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一半,然后再多吃一个。当到第十天时,想再吃时(还没吃),发现只有1个桃子了。问题:最初共多少个桃子

    def func2(n: Int): Int = {
      if (n == 10) {
        1
      } else {
        2 * (func2(n + 1) + 1)
      }
    }
    复制代码

函数细节(重要!)

  1. 函数的形参列表可以是多个,函数没有形参,调用时 可以不带()

  2. 形参列表和返回值列表的数据类型可以是值类型和引用类型

  3. Scala中的函数可以根据函数体最后一行代码自行推断函数返回值类型。那么在这种情况下,return关键字可以省略

  4. 因为Scala可以自行推断,所以在省略return关键字的场合,返回值类型也可以省略

  5. 如果函数明确使用return关键字,那么函数返回就不能使用自行推断了,这时要明确写成 : 返回类型 = ,当然如果你什么都不写,即使有return 关键字返回值也为()

/*返回值为 ()*/
def test2(): Unit = {

}

/*确定返回值类型*/
def test3(): Int = {
  1
}

/*自动对返回值进行类型推断*/
def test4(n: Boolean) = {
  if (n) "cris" else 4
}
复制代码
  1. 如果函数明确声明无返回值(声明Unit),那么函数体中即使使用return关键字也不会有返回值

  2. 如果明确函数无返回值或不确定返回值类型,那么返回值类型可以省略(或声明为Any)

  3. Scala语法中任何的语法结构都可以嵌套其他语法结构,即:函数中可以再声明/定义函数,类中可以再声明类 ,方法中可以再声明/定义方法

  4. Scala函数的形参,在声明参数时,直接赋初始值(默认值),这时调用函数时,如果没有指定实参,则会使用默认值。如果指定了实参,则实参会覆盖默认值

def testAge(age: Int = 12): Unit = {
  println(age)
}
复制代码
  1. 如果函数存在多个参数,每一个参数都可以设定默认值,那么这个时候,传递的参数到底是覆盖默认值,还是赋值给没有默认值的参数,就不确定了(默认按照声明顺序[从左到右])。在这种情况下,可以采用带名参数

  2. scala 函数的形参默认是val的,因此不能在函数中进行修改

  3. 递归函数未执行之前是无法推断出来结果类型,在使用时必须有明确的返回值类型

  4. Scala函数支持可变参数

// 调用
tests(1,2,3)
tests(1 to 4:_*)	// 将 1 to 4 当做参数序列,不能直接传入序列
// 定义
def tests(args: Int*): Unit = {
  println(args) // WrappedArray(1, 2, 3)
  for (x <- args) {
    println(x)
  }
}
复制代码
  1. 函数的简写
/*func 就是 func0 的简写*/
def func = "cris"

def func0() = {
  "cris"
}

// 无参数的函数被调用可以省略小括号
println(func) // cris

var flag = true

def fuc = if (flag) "cris" else 12

println(fuc) // cris
复制代码

过程

将函数的返回类型为Unit的函数称之为过程(procedure)

注意事项

  1. 注意区分:如果函数声明时没有返回值类型,但是有 = 号,表示可以通过类型推断最后一行代码。这个时候,函数其实是有返回值的,如果该返回值不是 Unit 则该函数并不是过程

  2. IDEA 会对没有返回值的函数自动不全,即自动加上 Unit 关键字,但是考虑到 Scala 语言的简洁性,建议不加

    // 这样的函数称之为过程,即通过 Unit 关键字表明了返回值为 ()
    def testfunc(): Unit ={
      
    }
    // 这样的函数也称之为过程,函数体前面没有 = ,表示返回值为()
    def func(){
        
    }
    
    // 实际开发中,或者 Scala 源码中经常省略 Unit ,并使用 = 来进行类型推断该函数
    def testfunc() = {
    
    }
    复制代码
  3. 总结:定义函数的时候,如果确定返回值类型,那么使用 :返回值类型 = ,否则使用 = 即可(确定没有返回值的话还是建议使用 :Unit ,毕竟可读性更好)

惰性函数

应用场景

​ 惰性计算(尽可能延迟表达式求值)是许多函数式编程语言的特性。惰性集合在需要时提供其元素,无需预先计算它们,这带来了一些好处。首先,您可以将耗时的计算推迟到绝对需要的时候。其次,您可以创造无限个集合,只要它们继续收到请求,就会继续提供元素。函数的惰性使用让您能够得到更高效的代码。Java 并没有为惰性提供原生支持,但是 Scala提供了

简介

​ 当 函数返回值被声明为lazy时 ,函数的 执行将被推迟 ,直到我们 首次对此取值,该函数才会执行 。这种函数我们称之为惰性函数,在Java的某些框架代码中称之为懒加载(延迟加载)

示例

object LazyTest {
  def main(args: Array[String]): Unit = {
    
    val result = func(1, 2)
    println("-----------------")
//    println(result)
  }

  def func(a: Int, b: Int): Int = {
    println("func 函数被执行了....")
    a + b
  }

}
复制代码
Cris 玩转 Scala

如果我们不加上 lazy 关键字,那么即使没有使用到 result ,函数也会在我们调用的时候去执行,如果加上 lazy 关键字呢?

lazy val result = func(1, 2)
    println("-----------------")
//    println(result)
复制代码
Cris 玩转 Scala

注意细节

  1. lazy 不能修饰 var 类型的变量

  2. 不但是在调用函数时,加了 lazy ,会导致函 数的执行被推迟,我们在声明一个变量时,如果给声明了 lazy ,那么变量值得分配也会推迟。 比如 lazy val i = 10

异常

异常简介

  1. Scala提供try和catch块来处理异常。try块用于包含可能出错的代码。catch块用于处理try块中发生的异常。可以根据需要在程序中有任意数量的try...catch块。

  2. Scala 语法处理上和 Java类似 ,但是又不尽相同

Java异常处理的注意点

  1. java语言按照try—catch—finally的方式来处理异常

  2. 不管有没有异常捕获,都会执行finally, 因此通常可以在finally代码块中释放资源

3)) 可以有多个catch,分别捕获对应的异常,这时需要把范围小的异常类写在前面,把范围大的异常类写在后面,否则编译错误。会提示 "Exception 'java.lang.xxxxxx' has already been caught"

Scala 的异常处理

object ExceptionTest {
  def main(args: Array[String]): Unit = {
    try {
      val a = 1 / 0
    } catch {
      case ex: ArithmeticException =>
        println("发生了算数异常!")
        println("要怎么处理呢?")
      case ex: Exception => println("不用处理了~")
    } finally {
      println("finally!")
    }
  }
}
复制代码
Cris 玩转 Scala
  1. 我们将可疑代码封装在try块中。 在try块之后使用了一个catch处理程序来捕获异常。如果发生任何异常,catch处理程序将处理它,程序将不会异常终止。

  2. Scala的异常的工作机制和Java一样,但是 Scala没有“checked(编译期)”异常 ,即Scala没有编译异常这个概念,异常都是在运行的时候捕获处理。

  3. 用throw关键字,抛出一个异常对象。所有 异常都是Throwable的子类型throw表达式是有类型的,就是Nothing ,因为Nothing是所有类型的子类型,所以throw表达式可以用在需要类型的地方

  4. 在Scala里,借用了模式匹配的思想来做异常的匹配,因此,在catch的代码里,是一系列case子句来匹配异常,当匹配上后 => 有多条语句可以换行写,类似 java 的 switch case x: 代码块..

  5. 异常捕捉的机制与其他语言中一样,如果有异常发生,catch子句是按次序捕捉的。因此,在catch子句中,越具体的异常越要靠前,越普遍的异常越靠后,如果把越普遍的异常写在前,把具体的异常写在后,在scala中也不会报错,但这样是非常不好的编程风格

  6. finally子句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作,这点和Java一样

  7. Scala提供了throws关键字来声明异常。可以使用方法定义声明异常。 它向调用者函数提供了此方法可能引发此异常的信息。 它有助于调用函数处理并将该代码包含在try-catch块中,以避免程序异常终止。在scala中,可以使用throws注释来声明异常

@throws(classOf[ArithmeticException])
def func() = {
  val a = 1 / 0
}
复制代码

函数练习

  1. 打印金字塔

    def main(args: Array[String]): Unit = {
      func1()
    }
    
    def func1(): Unit = {
      println("请输入打印的行数:")
      val i = StdIn.readInt()
      for (x <- 0.until(i)) {
        for (a <- x.until(i - 1)) {
          print(" ")
        }
        for (b <- 0.until(2 * x + 1)) {
          print("*")
        }
        println()
      }
    }
    复制代码
    Cris 玩转 Scala
  2. 根据输入的函数打印九九乘法表

    def main(args: Array[String]): Unit = {
      func2()
    }
    
    def func2(): Unit = {
      println("请输入打印的乘法表行数:")
      val x = StdIn.readInt()
      for (i <- 1.to(x); y <- 1.to(i)) {
        printf("%d*%d=%d\t", y, i, y * i)
        if (i == y) {
          println()
        }
      }
    }
    复制代码
Cris 玩转 Scala

7. 面向对象(重点)

7.1 Scala 面向对象基础

[修饰符] class 类名 {

类体

}

  1. scala语法中,类并不声明为public,所有这些类都具有公有可见性(即默认就是public)

  2. 一个Scala源文件可以包含多个类

定义一个最简单的类

object Demo {
  def main(args: Array[String]): Unit = {
    var man = new Man
    man.name = "cris"
    man.age = 12
    println(man.name + "----" + man.age) // cris----12
  }

}

class Man {
  var name = ""
  var age = 0
}
复制代码

反编译对应的 class 文件

Cris 玩转 Scala

属性

属性是类的一个组成部分,一般是值数据类型,也可是引用类型

Cris 玩转 Scala
def main(args: Array[String]): Unit = {
    val man = new Man()
    val pc = new PC
    man.pc = pc
    man.pc.brand = "惠普"
    // man.pc().brand()
    println(man.pc.brand) // 惠普
  }

class Man {
  var name = "" // 手动设置初始值,此时可以省略成员属性的数据类型声明
  var age = 0
  var pc: PC = _ // _ 表示让 Scala 自动赋默认值,此时声明带上成员属性的数据类型,否则编译器无法确定默认值
}

class PC {
  var brand: String = _
}
复制代码
Cris 玩转 Scala

练习

  1. 针对 for(int i = 10;i>0;i--){System.out.println(i)} 翻译成 Scala 代码

    object Practice {
      def main(args: Array[String]): Unit = {
        for (i <- 0.to(10).reverse) {
          print(i + "\t") // 10  9  8  7  6  5  4  3  2  1  0  
        }
      }
    }
    复制代码
  2. 使用过程重写上面的 Scala 代码

    def func(x: Int) {
      for (i <- 0 to x reverse) {
        print(i + "\t")
      }
    }
    复制代码
  3. 编写一个for循环,计算字符串中所有字母的Unicode代码(toLong方法)的乘积。举例来说,"Hello"中所有字符串的乘积为9415087488L

    def cal(str:String): Unit ={
      var result = 1L
      for(x <- str){
        result*=x.toLong
      }
      print(result)
    }
    复制代码
  4. 使用 StringOps 的 foreach 方法重写上面的代码

    var r2 = 1L
    // _ 可以理解为字符串的每一个字符
    "Hello".foreach(r2 *= _.toLong)
    print(r2)
    复制代码
  5. 使用递归解决上面求字符串每个字符 Unicode 编码乘积的问题

    def recursive(str: String): Long = {
      if (str.length == 1) str.charAt(0).toLong
      /*drop(n)从索引为 1 开始切片到结尾*/
      else str.take(1).charAt(0).toLong * recursive(str.drop(1))
    }
    复制代码
  6. 编写函数计算 x^n,其中 n 是整数(负数,0,正数),请使用递归解决

    def pow(x: Int, n: Int): Double = {
      if (n == 0) 1
      else if (n < 0) {
        1.0 / x * pow(x, n + 1)
      } else {
        x * pow(x, n - 1)
      }
    }
    复制代码

对象

val | var 对象名 [:类型] = new 类型()

  1. 如果我们不希望改变对象的引用(即:内存地址), 应该声明为val 性质的,否则声明为var, scala设计者推荐使用val ,因为一般来说,在程序中,我们只是改变对象属性的值,而不是改变对象的引用

  2. scala在声明对象变量时,可以根据创建对象的类型自动推断,所以类型声明可以省略, 但当类型和后面new 对象类型有继承关系即多态时,就必须写

方法

Scala中的方法其实就是函数,只不过一般将对象中的函数称之为方法

def 方法名(参数列表) [:返回值类型] = {

​ 方法体

}

练习

  1. 嵌套循环打印图形

    def func1(): Unit ={
        for (i <- 1 to 4; j <- 1 to 3) {
            if (j == 3) println("*")
            else print("*\t")
        }
    }
    复制代码
    Cris 玩转 Scala
  2. 计算矩形的面积

    class Test {
      def area(): Double = {
        (this.width * this.length).formatted("%.2f").toDouble
      }
    
    
      var width: Double = _
      var length: Double = _
    复制代码

构造器

java 的构造器回顾

[修饰符] 方法名(参数列表){

​ 构造方法体

}

  1. 在Java中一个类可以定义多个不同的构造方法,构造方法重载

  2. 如果程序员没有定义构造方法,系统会自动给类生成一个默认无参构造方法 (也叫默认构造器)

3) 一旦定义了自己的构造方法 ,默认的构造方法就覆盖了,就不能再使用默认的无参构造方法,除非显示的定义一下,即: Person(){}

Scala 构造器

和Java一样,Scala构造对象也需要调用构造方法,并且可以有任意多个构造方法。

Scala类的构造器包括: 主构造器 和 辅助构造器

基础语法

class 类名(形参列表) { // 主构造器

// 类体

def this(形参列表) { // 辅助构造器

}

def this(形参列表) { //辅助构造器可以有多个...

}

}

简单示例

abstract class Dog {
  var name = ""
  var age = 0
  val color: String

  def this(name: String, age: Int) {
    this()
    this.name = name
    this.age = age
  }

  def eat(): Unit = {
    println("吃狗粮")
  }

  def run()
}
复制代码
class Cat(var name: String, val color: String) {
  println("constructor is processing")

  def describe: String = name + "--" + color
}
  def main(args: Array[String]): Unit = {
	var cat = new Cat("tom", "gray")
    println(cat.describe)
    var cat2 = new Cat("jack", "red")
    println(cat2.describe)
  }
复制代码

细节

  1. Scala构造器作用是完成对新对象的初始化,构造器没有返回值。

  2. 主构造器的声明直接放置于类名之后 [反编译]

  3. 主构造器会执行类定义中的所有语句,这里可以体会到Scala的函数式编程和面向对象编程融合在一起,即:构造器也是方法(函数),传递参数和使用方法和前面的函数部分内容没有区别

  4. 如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略

  5. 辅助构造器名称为this(这个和Java是不一样的),多个辅助构造器通过不同参数列表进行区分, 在底层就是java的构造器重载,辅助构造器第一行函数体必须为 this.主构造器

abstract class Dog {
    var name = ""
    var age = 0
    val color: String

    def this(name: String, age: Int) {
        this()
        this.name = name
        this.age = age
    }

    def eat(): Unit = {
        println("吃狗粮")
    }

    def run()
}
复制代码

6)) 如果想让主构造器变成私有的,可以在()之前加上private,这样用户只能通过辅助构造器来构造对象了,说明:因为Person3的主构造器是私有,因此就需要使用辅助构造器来创建对象

class Car private(){}
复制代码
  1. 辅助构造器的声明不能和主构造器的声明一致,会发生错误

属性高级

  1. Scala类的主构造器函数的形参未用任何修饰符修饰,那么这个参数是局部变量

  2. 如果 参数使用val关键字声明 ,那么Scala会将参数作为类的 私有的只读属性使用

  3. 如果参数使用 var关键字声明 ,那么那么Scala会将参数作为类的成员属性使用,并会提供属性对应的xxx()[类似getter]/xxx_$eq()[类似setter]方法,即这时的 成员属性是私有的,但是可读写

class Counter {

  /*1. 有公开的 getter 和 setter 方法*/
  var count = 0
  /*2. 私有化 getter 和 setter,可以手动提供 setter 和 getter*/
  private var number = 1
  /*3. 只能被访问getter,无法修改setter,final 修饰的 age 属性*/
  val age = 12
  /*4. 对象级别的私有*/
  private[this] var length = 12

  def compare(other: Counter): Boolean = other.number > number

  //  def compareLength(other: Counter): Boolean = length > other.length

  def increase(): Unit = {
    number += 1
  }

  /*无参方法可以省略(),{}也可以省略*/
  def current: Int = number
}

def main(args: Array[String]): Unit = {
    var c = new Counter()
    c.count = 3
    println(c.count) // 3

    c.increase()
    println(c.current) // 2

    println(c.age) // 12
}
复制代码

如果在主构造器中为属性设置了默认值,那么就不必在函数体内再去声明属性以及赋值了,大大简化代码的书写

def main(args: Array[String]): Unit = {
	val dog = new Dog()
    println(dog.name) // cris
    println(dog.age)  // 10
  }
}

class Dog(var name :String= "cris",var age:Int = 10){
    
}
复制代码

JavaBean 注解

JavaBeans规范定义了Java的属性是像getXxx()和setXxx()的方法。许多Java工具(框架)都依赖这个命名习惯。为了Java的互操作性。将Scala字段加@BeanProperty时,这样会自动生成规范的 setXxx/getXxx 方法。这时可以使用 对象.setXxx() 和 对象.getXxx() 来调用属性

给某个属性加入@BeanPropetry注解后, 会生成getXXX和setXXX的方法

并且对原来底层自动生成类似xxx(),xxx_$eq()方法,没有冲突,二者可以共存


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

爆品方法论

爆品方法论

陈轩 / 2019-1-6 / 49

作者利用自己在品牌定位和爆品营销领域十三年的实践历练,结合移动社交媒体、爆品营销策略和社会心理学,精心筛选出上百个经典的营销案例,既分享了爆品内容的炮制方法和营销原理,也分享了爆品内容的推广技巧,告诉读者如何用移动社交媒体来颠覆传统营销模式,如何用互联网思维来玩转营销,实现低成本、高销量、大传播,成功打造市场爆品。一起来看看 《爆品方法论》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

MD5 加密
MD5 加密

MD5 加密工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具