Cris 的 Scala 笔记整理(七):面向对象

7. 面向对象(重点)

7.1 Scala 面向对象基础

[修饰符] class 类名 {java

类体程序员

}编程

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

  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 文件函数

属性

属性是类的一个组成部分,通常是值数据类型,也但是引用类型工具

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 = _
}
复制代码

练习

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

    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")
        }
    }
    复制代码

  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()方法,没有冲突,两者能够共存

对象建立流程分析

请针对如下代码简述对象建立流程

class Bike {
  var brand = ""
  var color = ""

  def this(brand: String, color: String) {
    this
    this.brand = brand
    this.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.Pig
            System.out.println("pig1.getClass() = " + pig1.getClass());
    // pig2.getClass() = class cris.package2.Pig
            System.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.Bird1
  var b2 = new cris.package2.Bird2
  // class cris.package1.Bird1
  println(b1.getClass)
  // class cris.package2.Bird2
  println(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 Apple
            val apple2 = new com.cris.Apple
            // class com.cris.scala.Apple
            println(apple.getClass)
            // class com.cris.Apple
            println(apple2.getClass)
          }
        }
      }
    }
    复制代码
  • 父包要访问子包的内容时,须要import对应的类

    package com.cris {
    	
      import com.cris.scala.Apple
    
      object Apple{
        def main(args: Array[String]): Unit = {
            // 推荐只在使用的时候再引用,控制做用域
    	  import com.cris.scala.Apple
          val apple = new Apple()
    // class com.cris.scala.Apple
          println(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) true
        else if (this.hour > other.hour) false
        else if (this.hour == other.hour) {
          if (this.minute < other.minute) true
          else if (this.minute > other.minute) false
          else false
        }
      }
    }
    复制代码
  2. 建立一个Student类,加入可读写的JavaBeans属性name(类型为String)和id(类型为Long)。有哪些方法被生产?(用javap查看。)你能够在Scala中调用JavaBeans的getter和setter方法吗?

    object Practice {
      def main(args: Array[String]): Unit = {
        var s = new Student
        println(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 += money
      println(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 -= money
      println(s"卡号:${this.no},取出:${money},余额为:${this.balance}")
    }
  }
}
复制代码

相关文章
相关标签/搜索