内容简介:[修饰符] class 类名 {类体}
[修饰符] class 类名 {
类体
}
-
scala语法中,类并不声明为public,所有这些类都具有公有可见性(即默认就是public)
-
一个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 = _ } 复制代码
练习
-
针对 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 } } } 复制代码
-
使用过程重写上面的 Scala 代码
def func(x: Int) { for (i <- 0 to x reverse) { print(i + "\t") } } 复制代码
-
编写一个for循环,计算字符串中所有字母的Unicode代码(toLong方法)的乘积。举例来说,"Hello"中所有字符串的乘积为9415087488L
def cal(str:String): Unit ={ var result = 1L for(x <- str){ result*=x.toLong } print(result) } 复制代码
-
使用 StringOps 的 foreach 方法重写上面的代码
var r2 = 1L // _ 可以理解为字符串的每一个字符 "Hello".foreach(r2 *= _.toLong) print(r2) 复制代码
-
使用递归解决上面求字符串每个字符 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)) } 复制代码
-
编写函数计算 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 类型()
-
如果我们不希望改变对象的引用(即:内存地址), 应该声明为val 性质的,否则声明为var, scala设计者推荐使用val ,因为一般来说,在程序中,我们只是改变对象属性的值,而不是改变对象的引用
-
scala在声明对象变量时,可以根据创建对象的类型自动推断,所以类型声明可以省略, 但当类型和后面new 对象类型有继承关系即多态时,就必须写 了
方法
Scala中的方法其实就是函数,只不过一般将对象中的函数称之为方法
def 方法名(参数列表) [:返回值类型] = {
方法体
}
练习
-
嵌套循环打印图形
def func1(): Unit ={ for (i <- 1 to 4; j <- 1 to 3) { if (j == 3) println("*") else print("*\t") } } 复制代码
-
计算矩形的面积
class Test { def area(): Double = { (this.width * this.length).formatted("%.2f").toDouble } var width: Double = _ var length: Double = _ 复制代码
构造器
java 的构造器回顾
[修饰符] 方法名(参数列表){
构造方法体
}
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) } 复制代码
细节
-
Scala构造器作用是完成对新对象的初始化,构造器没有返回值。
-
主构造器的声明直接放置于类名之后 [反编译]
-
主构造器会执行类定义中的所有语句,这里可以体会到Scala的函数式编程和面向对象编程融合在一起,即:构造器也是方法(函数),传递参数和使用方法和前面的函数部分内容没有区别
-
如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略
-
辅助构造器名称为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(){} 复制代码
- 辅助构造器的声明不能和主构造器的声明一致,会发生错误
属性高级
-
Scala类的主构造器函数的形参未用任何修饰符修饰,那么这个参数是局部变量
-
如果 参数使用val关键字声明 ,那么Scala会将参数作为类的 私有的只读属性使用
-
如果参数使用 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", "黄色") } 复制代码
-
加载类信息(属性信息,方法信息)
-
在堆中,给对象开辟空间
-
调用主构造器对属性进行初始化
-
使用辅助构造器对属性进行初始化
-
把对象空间的地址,返回给 bike 引用
7.2 面向对象进阶
包(难点)
回顾 Java 的包知识
-
作用
-
区分相同名字的类
-
当类很多时,可以很好的 管理 类
-
控制访问范围
-
-
打包基本语法
package com.cris;
-
打包的本质分析
实际上就是 创建不同的文件夹 来 保存类文件
-
示例代码
先在不同的包下建立同名的类
如果想要在一个类中同时使用上面的两个 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 要求源码所在路径和字节码文件所在路径必须保持一致,如果我们此时去修改源码的打包路径
-
基本语法
import java.awt.* or import java.util.List
-
注意事项: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中的包的功能更加强大,使用也相对复杂些
-
基本语法 package 包名
-
Scala包的三大作用(和Java一样)
- 区分相同名字的类
- 当类很多时,可以很好的管理类
- 控制访问范围
-
Scala中 包名和源码所在的系统文件目录结构要可以不一致 ,但是 编译后的字节码文件路径 和 包名会保持一致 (这个工作由 编译器 完成)
-
图示
-
命名规范
只能包含数字、字母、下划线、小圆点.,但不能用数字开头, 也不要使用关键字
一般是小写字母+小圆点一般是
com.公司名.项目名.业务模块名
-
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 包下生成 package.class 和 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=》等价于使用了 package$.class 中的 MODULE$.eat() println(salary) // 1000.0=> 等价于使用了 package$.class 中的 MODULE$.salary() } } } } 复制代码
使用反编译 工具 打开瞧瞧
具体的执行流程第二章节已经解释过,这里不再赘述
注意事项:
- 每个包都可以有一个包对象,但是需要在父包中定义它
- 包对象名称需要和包名一致,一般用来对包(里面的类)的功能做补充
包的可见性
在Java中,访问权限分为: public,private,protected和默认。在Scala中,你可以通过类似的修饰符达到同样的效果。但是使用上有区别
-
当属性访问权限为默认时,从底层看属性是private的,但是因为提供了xxx_$eq()[类似setter]/xxx()[类似getter] 方法,因此从使用效果看是任何地方都可以访问)
-
当方法访问权限为默认时,默认为public访问权限
-
private为私有权限,只在类的内部和伴生对象中可用
示例:
-
protected为受保护权限,scala中 受保护权限 比Java中更严格, 只能子类访问 ,同包无法访问
-
在scala中没有public关键字,即不能用public显式的修饰属性和方法。
包访问权限( 表示属性有了限制。同时增加了包的访问权限 ),这点和Java不一样,体现出Scala包使用的灵活性
包的引入
细节说明
-
在Scala中,import语句可以出现在任何地方,并不仅限于文件顶部,import语句的作用一直延伸到包含该语句的块末尾。这种语法的好处是:在需要时在引入包,缩小import 包的作用范围,提高效率
示例如下:
-
Java中如果想要导入包中所有的类,可以通过通配符*,Scala中采用下 _
-
如果不想要某个包中全部的类,而是其中的几个类,可以采用选取器(大括号)
-
如果引入的多个包中含有相同的类,那么可以将不需要的类进行重命名进行区分,这个就是重命名
-
或者使用 import java.util.{HashMap => _ } 对冲突的包进行隐藏
练习
-
编写一个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 } } } 复制代码
-
创建一个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 } 复制代码
-
编写一段程序,将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}") } } } 复制代码
以上所述就是小编给大家介绍的《Cris 的 Scala 笔记整理(七):面向对象》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 面向Python,面向对象(基础)
- 面向Python,面向对象(基础3)
- <<深入PHP面向对象、模式与实践>>读书笔记:面向对象设计和过程式编程
- 《JavaScript面向对象精要》之六:对象模式
- 《JavaScript面向对象精要》之三:理解对象
- 面向对象的程序设计之理解对象
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。