Scala中面向对象编程怎么用
这篇文章主要讲解了"Scala中面向对象编程怎么用",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"Scala中面向对象编程怎么用"吧!
2 面向对象编程
scala在数据的处理上使用的是函数式编程思想,但在上层的架构组织中仍使用面向对象的模型,对于大型应用程序尤其重要。
类
类的定义
类的声明使用关键字 class:
class Counter{ //这里定义类的字段和方法}
Scala中建议类名首字母大写。
要定义类的字段,使用var/val,和变量的定义一样。
要定义类的方法,使用def 关键字。
下面来定义一个完整的类:
class Woman{ //定义类的字段val sex = "female"//定义类的方法def usedToBeGirl():Unit = { println("A Woman used to be a girl.")}}
方法usedToBeGirl()
的返回值类型为Unit
,代表它没有返回任何结果,是一个空值,表示为()
。
scala的类允许嵌套定义
类的方法
方法的调用
方法若没有参数,在定义时可以省略括号。
方法定义时若省略了括号,那么调用时也不能带括号;若没有省略括号,则调用时带不带括号都可以。
比如:
class MyFather{ //定义类的字段val age = 40val sex = "male"//定义类的方法def getAge():Int = agedef getSex:String = sex}
这里
getAge
方法的调用://先创建一个实例val f = new MyFather//以下这两种调用方法都可以f.getAgef.getAge()//getSex方法声明时没有括号,调用时也不能有括号f.getSex
方法的返回值
方法有声明返回值类型,那么返回值是方法体中最后一条执行语句的值;
方法没有声明返回值类型,那么scala编译器会根据方法体中最后一条执行语句的值来推断返回值类型。
此时如果省略了等号,使用大括号,scala编译器会将返回值推断为Unit。
方法参数
Scala中,方法的参数都是不可变的,隐式使用了 val 进行声明,所以既不可以在声明参数时使用var 或val ,也不可以在方法体中重新给参数赋值。
在scala中调用方法时可以显式地命名参数列表。
scala中允许方法重载
scala中允许方法的嵌套定义
类成员的可见性
scala类中,所有成员的默认可见性都是公有的,且不需要使用 public 关键字进行限定。
除了public之外,scala也具有和java同样的可见性选项:private和protected。
private:对本类和嵌套类可见
protected:对本类和子类可见
scala是不推荐将所有成员设置为公有属性的,而建议将其设置为private,这样外部成员就无法直接访问私有字段。因此,像java类中使用的getter和setter方法一样,scala也提供对private 字段进行访问和修改的方法。
class Counter{ //定义一个private字段private var privateValue = 0//提供访问和修改privateValue的方法def value = privateValuedef value_ = (newValue:Int){ if (newValue > 0) privateValue = newValue} }
其中,value 和value_ 这两个方法是成对的。
访问privateValue字段:
//先实例化一个对象val counter = new Counter//访问privateValuecouner.value//修改privateValuecounter.value_(3)
上面对私有字段进行修改的方法也可以省略下划线_(scala语法规定):
counter.value(3)
这样会显得更加直观。
构造器
在scala中,整个类的定义主体就是类的构造器,称为主构造器,所有位于类方法以外的语句都将在构造过程中被执行。
那么如何向构造器传入参数呢?
scala规定在类名之后使用圆括号列出主构造器的参数列表:
class Counter(name:String){ privateval value = 0}
除了主构造器,scala类还可以拥有零个或多个辅助构造器。
每个辅助构造器的第一条语句必须是对主构造器或者此前已经定义的辅助构造器的调用。
辅助构造器使用this进行定义,调用形式为:this(参数列表)
。
与普通方法的定义不同,构造器的参数可以使用val 或var进行定义。scala将自动为这些参数创建私有字段。
使用val 定义的参数,只会为其生成读方法,因为它是不可变的,只可以读不可以写。
对象
单例对象
scala中的单例对象相当于java中的静态成员。
单例对象的定义使用object 关键字:
object Person{ private var lastId = 0{ lastId += 1 lastId }}
单例对象在第一次被访问的时候初始化。
单例对象包括两种:伴生对象和孤立对象。
当一个单例对象具有同名类,则这个单例对象就称为这个同名类的伴生对象。相应的,这个同名类就称为这个单例对象的伴生类。它们之间可以互相访问对方的私有成员。
反之,没有同名类的单例对象,就称为孤立对象。比如scala程序的入口main方法就定义在一个孤立对象里:
object HelloWorld{ //HelloWorld是一个孤立对象def main(args[]:Array[String]){ println("Hello World!")}}
对于包含了伴生类和伴生对象定义的代码文件,不能直接在REPL中使用":load"命令来执行,需要先对其进行编译,再用scala命令来执行。
apply方法
之前的介绍中曾经使用过apply方法:
val strArr = Array("hadoop","spark","flink")
可以看到这里实例化了一个数组对象strArr,但是并没有使用new关键字进行创建,这里实际上就是隐式调用了apply方法。整个过程就是,scala自动调用了Array类的伴生对象Array中一个apply方法,创建了一个Array对象。
apply方法也可以定义在类中,比如说定义如下类TestApply:
class TestApply{ def apply(param:String):Unit = { println("apply method called " + param)}}
在REPL模式下执行程序:
先创建一个TestApply对象,名为myObject。然后执行myObject("Hello Apply")
,传递了一个参数,执行的结果就是类中的apply方法被自动调用了。
scala> val myObject = new TestApply val myObject: TestApply = TestApply@3caf5c96scala> myObject("Hello Apply")apply method called Hello Apply
然而我的学识尚浅,有一个疑惑,把apply方法名改成别的还会自动调用该方法吗?
经过我的实验可以证明,答案是:不会。
将TestApply类中的apply方法改名为helloApply,然后重新执行程序:
scala> :load /usr/local/scala/mycode/testApply.scalaval args: Array[String] = Array()Loading /usr/local/scala/mycode/testApply.scala...class TestApplyscala> val myObject = new TestApplyval myObject: TestApply = TestApply@53da2aecscala> myObject("Hello Apply") ^ error: TestApply does not take parametersscala> myObject.helloApply("Hello Apply")apply method called Hello Apply只有显式调用helloApply方法才行。
apply方法定义在类的伴生对象中(更通常的用法)
将类的构造方法以apply方法的形式定义在类的伴生对象中,类的伴生对象就成为了一个"工厂",专门生产类的实例,apply方法就称为工厂方法。
实际上,apply方法设计的初衷就是为了保持对象和函数之间使用的一致性,数学中函数的使用形式是函数(参数)
,在scala中函数也是对象,因此scala的方法调用可以省去对方法的调用,像使用函数一样使用方法。
函数(参数)函数.方法(参数)
在scala中,apply的调用规则是:用括号传递给类实例或对象名一个或多个参数时,scala会在相应的类或对象中查找名为apply的方法,且要求参数列表与传入参数一致,然后调用该apply方法。
update 方法
update跟apply方法遵循同样的调用规则,update方法用于对象的重新赋值,比如可变Map类的伴生对象中就有update方法,可以修改某个键的值。
unapply方法
unupply方法用于对对象进行解构操作,它也会被自动调用。
unapply方法可以看做apply方法的反向操作,它从某个类的构造函数中解构出该函数的传入参数。
继承
抽象类
若一个类包含没有实现的成员(字段或方法),那么它就是抽象类,要使用abstract 关键词对该类进行修饰。
abstract class Person{ val name:Stringval age:Intdef getAge()def greeting(){ println("hello")}}
抽象类中的抽象字段必须声明类型。抽象类不能实例化,只能作为父类被其它类继承。
与java不同的是,scala抽象类中的抽象方法不需要加abstract修饰。
类的继承
scala中的继承与java一样,只支持单一继承,即一个子类只能有一个父类。继承父类使用关键字使用extends关键字表示。
定义子类时,需要注意以下几点:
重载父类的抽象成员时,override关键字是可选的;重载父类的非抽象成员时,override关键字是必选的。
建议重载父类的抽象成员也使用关键字override,若在后续的业务中父类的抽象成员被实现了,此时该成员就是非抽象成员。因为重载非抽象成员时override关键字是必选的,如果子类没有使用override关键字,就会出现编译错误,此时用户会及时发现父类的改变。
子类在重载时只能重载val修饰的字段,var本身就是可变的,重载并没有意义。
子类的主构造器必须调用父类的主构造器或辅助构造器。
若父类构造器中有val或var修饰的参数,它就相当于类的字段,因此如果子类的构造器参数中包含与父类构造器中同名的参数,那么子类构造器相当于重载了父类字段,需要使用override对该构造器参数进行修饰。
Scala的类层级结构
类型 | 描述 |
---|---|
Null | 所有引用类型的子类。值为null,表示一个空对象。 |
Nothing | Nothing类型在Scala的类层级的最底端;它是任何其他类型的子类型。 |
Any | Any是所有其他类的超类 |
AnyRef | AnyRef类是Scala里所有引用类(reference class)的基类 |
Nothing主要用于异常处理函数的返回值类型,这样异常抛出后不会返回任何对象,所以异常处理函数可以更为方便地使用。
Option类
scala采用Option类来统一表示对象有值和无值的情况,Option是一个抽象类,有一个具体的类Some和一个对象Node。Some表示有值,None表示无值。
参数化类型
scala中的参数化类型相当于java中的泛型,与之不同的是scala中使用方括号[] 来定义参数化类型。
特质 Trait
scala中的特质Trait 相当于java中的接口。一个特质被子类实现以后,就可以看做这个类的父类,但与继承不同的是,特质允许多重实现,也就是一个子类可以混入多个特质。
特质可以同时用拥有具体方法和抽象方法,这就跟抽象类很相似。
注:
如果特质没有显式地说明继承关系,那么这个特质就默认继承自AnyRef。
如果特质继承自某个父类,那么它无法向该父类构造器传递参数,这就要求特质所继承的父类必须有一个无参构造器。
若要混入多个特质,可以使用with关键字(可以连续使用):
class Man extends Person with Boy{ //字段与方法实现}
特质不能实例化
模式匹配
match 语句
scala中最常见的模式匹配是match语句,类似于其它语言中的switch语句。
val grade = 90grade match { case 'A' => println("85-100")case 'B' => println("70-84")case 'C' => println("60-69")case _ => println("unqualified!")}
最后一个case使用了下划线"_"代表其它情况,相当于java中的default分支。
case 类
定义一个类时,如果在class关键字前加上case修饰,则该类为case类。scala为case类重载了许多实用的方法,包括tiString,equals和hashcode方法。更重要的是,scala为每一个case类自动生成一个伴生对象。
包
为了解决程序中的命名冲突,scala也和java一样使用包来层次化、模块化地组织程序。
将代码放在指定包中的两种方法:
在代码文件的顶端使用package关键字声明
在package子句中加一对大括号,将类和对象放在大括号里
感谢各位的阅读,以上就是"Scala中面向对象编程怎么用"的内容了,经过本文的学习后,相信大家对Scala中面向对象编程怎么用这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!