7. 面向对象(重点)

7.1 Scala 面向对象基础

[修饰符] class 类名 {

类体

}

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

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

定义一个最简单的类

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

反编译对应的 class 文件

属性

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

  def main(args: Array[String]): Unit = {val man = new Man()val pc = new PCman.pc = pcman.pc.brand = "惠普"// man.pc().brand()println(man.pc.brand) // 惠普}class Man {var name = "" // 手动设置初始值,此时可以省略成员属性的数据类型声明var age = 0var pc: PC = _ // _ 表示让 Scala 自动赋默认值,此时声明带上成员属性的数据类型,否则编译器无法确定默认值
}class PC {var brand: String = _
}
复制代码

练习

  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 = 1Lfor(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) 1else 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")}
    }
    复制代码
  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 = 0val color: Stringdef this(name: String, age: Int) {this()this.name = namethis.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 = 0val color: Stringdef this(name: String, age: Int) {this()this.name = namethis.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 = 12def compare(other: Counter): Boolean = other.number > number//  def compareLength(other: Counter): Boolean = length > other.lengthdef increase(): Unit = {number += 1}/*无参方法可以省略(),{}也可以省略*/def current: Int = number
}def main(args: Array[String]): Unit = {var c = new Counter()c.count = 3println(c.count) // 3c.increase()println(c.current) // 2println(c.age) // 12
}
复制代码

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

def main(args: Array[String]): Unit = {val dog = new Dog()println(dog.name) // crisprintln(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()方法,没有冲突,二者可以共存

对象创建流程分析

请针对以下代码简述对象创建流程

class Bike {var brand = ""var color = ""def this(brand: String, color: String) {thisthis.brand = brandthis.color = color}
}def main(args: Array[String]): Unit = {var bike = new Bike("ofo", "黄色")
}
复制代码
  1. 加载类信息(属性信息,方法信息)

  2. 在堆中,给对象开辟空间

  3. 调用主构造器对属性进行初始化

  4. 使用辅助构造器对属性进行初始化

  5. 把对象空间的地址,返回给 bike 引用

7.2 面向对象进阶

包(难点)

回顾 Java 的包知识

  1. 作用

    1. 区分相同名字的类

    2. 当类很多时,可以很好的管理

    3. 控制访问范围

  2. 打包基本语法

    package com.cris;

  3. 打包的本质分析

    实际上就是创建不同的文件夹保存类文件

  4. 示例代码

    先在不同的包下建立同名的类

    如果想要在一个类中同时使用上面的两个 Pig,Java 的解决方式如下:

        public static void main(String[] args) {Pig pig1 = new Pig();cris.package2.Pig pig2 = new cris.package2.Pig();
    //        pig1.getClass() = class cris.package1.PigSystem.out.println("pig1.getClass() = " + pig1.getClass());
    //        pig2.getClass() = class cris.package2.PigSystem.out.println("pig2.getClass() = " + pig2.getClass());}
    复制代码

    再来看看我们的源码所在路径和字节码文件所在路径,都是一一对应的

    Java 要求源码所在路径和字节码文件所在路径必须保持一致,如果我们此时去修改源码的打包路径

  5. 基本语法

    import java.awt.* or import java.util.List

  6. 注意事项:java中包名和源码所在的系统文件目录结构要一致,并且编译后的字节码文件路径也和包名保持一致

接着看看 Scala 是如何处理的

我们使用 Scala 重写上面的 Java 包案例

def main(args: Array[String]): Unit = {var b1 = new cris.package1.Bird1var b2 = new cris.package2.Bird2//    class cris.package1.Bird1println(b1.getClass)//    class cris.package2.Bird2println(b2.getClass)
}
复制代码

此时我们如果修改了 Bird1 的打包路径

再看看源代码和字节码文件所在的路径

Scala 的包

和Java一样,Scala中管理项目可以使用包,但Scala中的包的功能更加强大,使用也相对复杂些

  1. 基本语法 package 包名

  2. Scala包的三大作用(和Java一样)

    1. 区分相同名字的类
    2. 当类很多时,可以很好的管理类
    3. 控制访问范围
  3. Scala中包名和源码所在的系统文件目录结构要可以不一致,但是编译后的字节码文件路径包名会保持一致(这个工作由编译器完成)

  4. 图示

  5. 命名规范

    只能包含数字、字母、下划线、小圆点.,但不能用数字开头, 也不要使用关键字

    一般是小写字母+小圆点一般是 com.公司名.项目名.业务模块名

  6. Scala 自动 import 的包有:java.lang.*,scala,Predef 包

Scala 打包细节(难点)

  • 常用的两种打包形式

    • 源代码的路径和字节码文件路径保持一致

    • 源代码的路径和字节码文件路径不一致

    • 上面的演示中已经很清楚的展示了 Scala 包的这一特点,我们继续用下面代码演示 Scala 包的嵌套

      我们在 Detail 类文件中写入以上非常奇怪的代码,编译运行后再查看源代码和字节码文件的位置

      进一步印证了 Scala 中源文件和字节码文件路径可以不一致

  • 包也可以像嵌套类那样嵌套使用(包中有包), 见上面图示。好处是:程序员可以在同一个文件中,将类(class / object)、trait 创建在不同的包中,非常灵活

  • 作用域原则:可以直接向上访问。即: Scala中子包中直接访问父包中的内容, 大括号体现作用域。(提示:Java中子包使用父包的类,需要import)。在子包和父包 类重名时,默认采用就近原则,如果希望指定使用某个类,则带上包名即可

    示例代码

    package com.cris {class Apple {}package scala {class Apple {}object Boy {def main(args: Array[String]): Unit = {/*1. Scala 中子包可以直接访问父包的内容;2. 子包和父包的类重名,默认采取就近原则;3. 可以带上类的路径名指定使用该类*/val apple = new Appleval apple2 = new com.cris.Apple//        class com.cris.scala.Appleprintln(apple.getClass)//        class com.cris.Appleprintln(apple2.getClass)}}}
    }
    复制代码
  • 父包要访问子包的内容时,需要import对应的类

    package com.cris {import com.cris.scala.Appleobject Apple{def main(args: Array[String]): Unit = {// 推荐只在使用的时候再引用,控制作用域import com.cris.scala.Appleval apple = new Apple()
    //      class com.cris.scala.Appleprintln(apple.getClass)}}package scala {class Apple {}}
    }-
    复制代码
  • 可以在同一个.scala文件中,声明多个并列的package(建议嵌套的pakage不要超过3层)

包对象

基本介绍:包可以包含类、对象和特质trait,但不能包含函数或变量的定义。这是Java虚拟机的局限。为了弥补这一点不足,scala提供了包对象的概念来解决这个问

参见如下代码

package com.cris {// 不能直接在 package 中定义函数和变量//  var name = "cris"/*** 包对象的名字需要和包名一致* package object emp 会在 com.cris.emp 包下生成&emsp;package.class 和&emsp;package$.class*/package object emp {def eat(): Unit = {println("eat")}val salary = 1000.0}package emp {object test {def main(args: Array[String]): Unit = {eat() // eat=》等价于使用了&emsp;package$.class 中的&emsp;MODULE$.eat()println(salary) // 1000.0=>&emsp;等价于使用了&emsp;package$.class 中的 MODULE$.salary()}}}
}
复制代码

使用反编译工具打开瞧瞧

具体的执行流程第二章节已经解释过,这里不再赘述

注意事项:

  1. 每个包都可以有一个包对象,但是需要在父包中定义它
  2. 包对象名称需要和包名一致,一般用来对包(里面的类)的功能做补充

包的可见性

在Java中,访问权限分为: public,private,protected和默认。在Scala中,你可以通过类似的修饰符达到同样的效果。但是使用上有区别

  1. 当属性访问权限为默认时,从底层看属性是private的,但是因为提供了xxx_$eq()[类似setter]/xxx()[类似getter] 方法,因此从使用效果看是任何地方都可以访问)

  2. 当方法访问权限为默认时,默认为public访问权限

  3. private为私有权限,只在类的内部和伴生对象中可用

示例:

  1. protected为受保护权限,scala中受保护权限比Java中更严格,只能子类访问,同包无法访问

  2. 在scala中没有public关键字,即不能用public显式的修饰属性和方法。

包访问权限(表示属性有了限制。同时增加了包的访问权限),这点和Java不一样,体现出Scala包使用的灵活性

包的引入

细节说明

  1. 在Scala中,import语句可以出现在任何地方,并不仅限于文件顶部,import语句的作用一直延伸到包含该语句的块末尾。这种语法的好处是:在需要时在引入包,缩小import 包的作用范围,提高效率

    示例如下:

  2. Java中如果想要导入包中所有的类,可以通过通配符*,Scala中采用下 _

  3. 如果不想要某个包中全部的类,而是其中的几个类,可以采用选取器(大括号)

  4. 如果引入的多个包中含有相同的类,那么可以将不需要的类进行重命名进行区分,这个就是重命名

  5. 或者使用 import java.util.{HashMap => _ } 对冲突的包进行隐藏

练习

  1. 编写一个Time类,加入只读属性hours和minutes,和一个检查某一时刻是否早于另一时刻的方法before(other:Time):Boolean。Time对象应该以new Time(hrs,min)方式构建

    object Practice {def main(args: Array[String]): Unit = {val time1 = new Time(4, 12)val result = time1.before(new Time(4, 14))println(result)}
    }class Time(val hour: Int, val minute: Int) {def before(other: Time) = {if (this.hour < other.hour) trueelse if (this.hour > other.hour) falseelse if (this.hour == other.hour) {if (this.minute < other.minute) trueelse if (this.minute > other.minute) falseelse false}}
    }
    复制代码
  2. 创建一个Student类,加入可读写的JavaBeans属性name(类型为String)和id(类型为Long)。有哪些方法被生产?(用javap查看。)你可以在Scala中调用JavaBeans的getter和setter方法吗?

    object Practice {def main(args: Array[String]): Unit = {var s = new Studentprintln(s.getName)println(s.age)}
    }class Student {@BeanProperty var name = "好学生"@BeanProperty var age = 0}
    复制代码
  3. 编写一段程序,将Java哈希映射中的所有元素拷贝到Scala哈希映射。用引入语句重命名这两个类

    object Ex extends App {import java.util.{HashMap => JavaHashMap}import scala.collection.mutable.{HashMap => ScalaHashMap}var map1 = new JavaHashMap[Int, String]()map1.put(1, "cris")map1.put(2, "james")map1.put(3, "simida")var map2 = new ScalaHashMap[Int, String]()for (key <- map1.keySet().toArray()) { // key 的数据类型是 AnyRef// asInstanceOf 强制数据类型转换map2 += (key.asInstanceOf[Int] -> map1.get(key))}println(map2.mkString("||")) // 2 -> james||1 -> cris||3 -> simida}
    复制代码

抽象

我们在前面去定义一个类时候,实际上就是把一类事物的共有的属性和行为提取出来,形成一个物理模型(模板)。这种研究问题的方法称为抽象

示例代码

object Demo extends App {var account = new Account("招行:888888", 200, "123456")account.query("123456")account.save("123456", 100)account.query("123456")account.withdraw("123456", 250)account.query("123456")}class Account(val no: String, var balance: Double, var pwd: String) {def query(pwd: String): Unit = {if (pwd != this.pwd) {println("密码错误!")} else {println(s"卡号:${this.no},余额还有:${this.balance}")}}def save(pwd: String, money: Double): Unit = {if (pwd != this.pwd) {println("密码错误")} else {this.balance += moneyprintln(s"卡号:${this.no},存入:${money},余额为:${this.balance}")}}def withdraw(pwd: String, money: Double): Unit = {if (pwd != this.pwd) {println("密码错误")} else if (money > this.balance) {println("余额不足")} else {this.balance -= moneyprintln(s"卡号:${this.no},取出:${money},余额为:${this.balance}")}}
}
复制代码

Cris 的 Scala 笔记整理(七):面向对象相关推荐

  1. Cris 的 Scala 笔记整理(九):面向对象高级

    9. 面向对象高级 9.1 静态属性和静态方法 ① 回顾 Java 的静态概念 public static 返回值类型 方法名(参数列表) {方法体} Java 中静态方法并不是通过对象调用的,而是通 ...

  2. Cris 的 Scala 笔记整理(八):面向对象中级-封装

    封装 ​ 从数据的角度:封装 (encapsulation) 就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作 ​ 从模 ...

  3. Cris 的 Scala 笔记(三):变量

    文章目录 3.变量 3.1 基本概念 3.2 数据类型 3.3 数据类型体系图 3.4 整数类型 3.5 浮点数据类型 3.6 字符类型(Char) 3.7 布尔类型 3.8 Unit类型.Null类 ...

  4. Cris 的 Scala 笔记(五):流程控制

    文章目录 5. 流程控制 5.1 分支控制 单分支 双分支 多分支 分支控制if-else 注意事项 5.2 for循环控制 范围数据循环方式1 范围数据循环方式2 循环守卫 引入变量 嵌套循环 循环 ...

  5. 【Beetl笔记整理七】格式化

    版权声明:本文为 小异常 原创文章,非商用自由转载-保持署名-注明出处,谢谢! 本文网址:https://sunkuan.blog.csdn.net/article/details/114743052 ...

  6. Java笔记整理七(网络编程,TCP通信程序,函数式接口,方法引用)

    网络编程入门 1.软件结构 C/S结构 :全称为Client/Server结构,是指客户端和服务器结构.常见程序有QQ.迅雷等软件. B/S结构 :全称为Browser/Server结构,是指浏览器和 ...

  7. JavaScript之面向对象与原型笔记整理--------创建对象之原型(2)

    4.原型 每个函数都有一个prototype属性,这个属性是一个对象,用途是包含可以由特定类型的所有实例共享的属性和方法. 逻辑上可以这么理解:prototype通过调用构造函数而创建的那个对象的原型 ...

  8. Deep Learning(深度学习)学习笔记整理系列之(七)

    Deep Learning(深度学习)学习笔记整理系列 zouxy09@qq.com http://blog.csdn.net/zouxy09 作者:Zouxy version 1.0 2013-04 ...

  9. 慕课Java第三季学习及笔记整理

    学习网址及截图和部分图片来源:https://www.imooc.com/learn/110 慕课Java第三季学习及笔记整理 一.异常与异常处理 1-1 java异常简介 概念 异常体系结构 1-2 ...

最新文章

  1. 第四章:Spring项目文件上传两种方式(全解析)
  2. excel pandas 空格_Pandas写入excel:1)索引中的空白行;2)每天保存索引
  3. python爬虫案例-Python爬虫案例集合
  4. vbs复制自己到tmp目录
  5. 【NLP】Transformer大家庭简介!
  6. 从操作系统层面分析Java IO演进之路
  7. 米莱狄机器人怎么那么多_米莱狄究竟应该怎么玩?
  8. 公安人像(证照)比对接口简介
  9. 同学使用计算机存在的问题,计算机应用基础教学问题及应对策略
  10. 学生如何提高专业英文阅读能力(转自施一公博客)
  11. 苹果查看电池实际容量
  12. 2022ICPC预选赛 A Yet Another Remainder(数学)(构造)
  13. 动漫线稿怎么画才流畅
  14. 函数对称性常见公式_函数的各种对称性
  15. 聚点 内部 内点 导集
  16. 前端/后端(FE / BE)
  17. 使用background 的url 引入背景图片失效的问题
  18. 电子数据取证之网站分析和重构基础
  19. 关于AI视觉-百度大脑EdgBoard边缘计算盒
  20. STM32移植BME680传感器输出IAQ(室内空气质量)

热门文章

  1. 插入(insert)
  2. 我的 2020,总结与告别
  3. Android实现文字垂直滚动
  4. 测试项目经理推荐的Java 并发测试神器
  5. Java内存分析工具MAT(Memory Analyzer Tool)的介绍与使用
  6. dnf7月7日服务器维护,dnf7月7日早7点/8点半停机更新公告
  7. [计算机]防止电脑进入锁屏
  8. linux su 不能输密码错误,su - root正确输入密码但是登录不了系统,报错su: Permission denied...
  9. 什么是Pyc?Pyc的作用是什么?
  10. NONMEN软件概览及数据文件