-- 定义一个简单的类
scala的类和java基本差不多,先定义各一个简单类演示:
//定义类,包含field以及方法
class HelloWorld{
private var name="leo"
def sayHello(){println("Hello, "+name)}
def getName= name
}
// 创建类的对象,并调用方法
val helloWorld= new HelloWorld
helloWorld.sayHello()
println(helloWorld.getName) // 也可以不加括号,如果定义方法时不带括号,则调用方法时不能带括号
代码展示:
以上就是定义了一个简单的类 ,在外部怎么使用类,怎么调用方法。
以下将详解类
-- 自定义getter与setter
//如果只是希望拥有简单的getter和setter方法,那么就按照scala提供的语法规则,根据需要为field选择合适的修饰符就好:var、var、private、private[this]
// 但是如果希望能够自己对getter与setter进行控制,则可以自定义getter与setetr方法
// 自定义setter方法的时候一定要注意scala的语法限制,签名、=、参数间不能有空格
class Student{
private var myName="leo"
def name="your name is "+myName
def name_=(newValue:String){ // name_=中间不能有空格
print("you cannot edit your name!!!")
}
}
val leo =new Student
print(leo.name)
leo.name="leo1"
-- 仅暴露field的getter方法
//如果你不希望field有setter方法,则可以定义为val ,但是此时就再也不能改变field的值了
// 但是如果希望能够仅仅暴露出一个getter方法,并且还能通过某些方法更改field的值,那么需要综合使用private以及自定义getter方法
//此时,由于field是private的,所以setter和gette都是private,对外界没有暴露;自己可以实现修改field的值的方法,自己可以覆盖getter方法
class Student{
private var myName="leo"
def updateName(newName:String){
myName=newName //自己实现修改field的值
}
def name="your name is "+myName
}
-- private[this] 的使用
// 如果将field使用private来修饰,那么代表这个field是类私有的,在类的方法中,可以直接访问类的其他对象的private field
//这种情况下,如果不希望field被其他对象访问到,那么可以使用private[this],意味着对私有的field ,只有本类对象内可以访问到。
class Student{
private var myAge=0
def age_=(newAge:Int){
if(newAge >0)myAge=newAge
else println("Illegal age!")
}
def age =myAge
def older(s:Student)={
myAge > s.myAge // 如果 myAge 设置为 private[this] var myAge ,则这里报错
}
}
代码展示如下:
-- java风格的setter和getter方法
//Scala的getter和setter方法的命名与java是不同的,是field和field_=的方式
// 如果想让scala自动生成java风格的getter和setter方法,只要给field添加@BeanProperty注解即可
此时会生成4个方法,name:String 、name_=(newValue:String):Unit、getName():String ,setName(newValue:String):Unit
import scala.beans.BeanProperty //import scala.reflect.BeanProperty
class Student{
@BeanProperty var name:String=_
}
// class Student(@BeanProperty var name:String) //或者使用主构造函数的方式
val s =new Student
s.setName("leo")
s.getName()
代码展示如下:
使用主构造函数的方式:
-- 辅助 构造函数
// Scala中可以给类定义多个辅助constructor,类似于java中的构造函数重载
// 辅助构造函数之间可以相互调用,而且必须第一行调用主constructor
class Student{
private var name
private var age
def this(name:String){
this()
this.name=name
}
def this(name:String,age:Int){
this()
this.age=age
}
}
-- 主constructor
// 在scala中 ,主constructor是与类名放在一起的 ,与java不同
//而且类中,没有定义在任何方法或者是代码块之中的代码,就是主constructor的代码
class Student(val name:String ,val age:Int){
println("your name is " +name+", your age is "+ age)
}
// 主 constructor中还可以通过使用默认参数 ,来给参数默认的值
class Student(val name:String="leo",val age:Int=30){
println("your name is "+ name+", your age is " +age)
}
val s=new Student // 直接使用默认参数
// 如果主 constructor传入的参数什么修饰都没有,比如name:String ,那么如果类内部的方法使用到了,则会声明为private[this] name, ,否则没有该field ,就只能constructor代码使用而已 。
-- 内部类
// 在scala中,同样可以在类中定义内部类,但是与java不同的是,每个外部类的内部类都是不同的类。
import scala.collection.mutable.ArrayBuffer
class A{
class Student(val name:String)
val students=new ArrayBuffer[Student]()
def getStudent(name:String)={
new Student(name)
}
}
val a1 = new A
val s1=a1.getStudent("leo")
a1.students +=s1
val a2=new A
val s2=a2.getStudent("leo")
a1.students +=s2 //报错 error:type mismatch,a1.student和a2的student的实例不是同一个, 所以会报错
-- 面向对象编程之对象 object(scala中所特有的)
//object ,相当于class的单个实例,通常在里面放一些静态的field或者method
// 第一次调用object的方法时,就会执行object的constructor,也就是
//object内部不在method中的代码,但是object不能定义接收参数的constructor
// 注意: object的constructor只会在其第一次被调用时执行一次,以后再次调用就不会再执行constructor了
// object通常用于作为单例模式的实现,或者放class的静态成员,比如工具方法
object Person{
private var eyeNum=2
println("this Person object")
def getEyeNum=eyeNum
}
举例:
-- 伴生对象
//如果有一个class ,还有一个与class同名的object,那么就称这个object是class的伴生对象,class是object的伴生类
// 伴生类和伴生对象必须存放在一个.scala文件中
//伴生类和伴生对象 ,最大的特点就是可以互相访问private field
class Person(val name:String,val age:Int){
def sayHello=println("Hi, "+name+", I guess you are "+age+" years old!"+", and usually you must have "+ Person.eyeNum+"eyes.")
}
object Person{
private val eyeNum =2
def getEyeNum =eyeNum
}
-- 让object继承抽象类
// object的功能其实和class类似,除了不能定义接收参数的constructor之外
// object可以继承抽象类,并覆盖抽象类中的方法
abstract class Hello(var message:String){ //定义抽象类
def sayHello(name:String):Unit
}
object HelloImpl extends Hello("Hello"){ //继承抽象类,实现抽象方法
override def sayHello(name:String)={
println(message+"," +name)
}
}
// 访问
HelloImpl.sayHello("leo")
用法如下:
-- apply方法
//object非常重要的一个特殊方法,就是apply方法
//通常在伴生对象中实现apply方法,并在其中实现构造伴生类的对象的功能
//而创建伴生类的对象时,通常不会使用new Class的方式,而是使用Class()的方式,隐式第调用伴生对象的apply方法,这样会让对象创建更加简洁
//比如,Array类的伴生对象的apply方法就实现了接收可变数量的参数,并创建一个Array对象的功能
val a=Array(1,2,3,4,5)
// 比如,定义自己的伴生类和伴生对象。
class Person(val name:String)
object Person{
def apply(name:String)=new Person(name)
}
val p1=new Person("leo")
val p2=Person("leo") //更简洁
-- main方法
//如同java中,如果要运行一个程序,必须编写一个包含main方法类一样,在scala中如果要运行一个程序,就必须有一个main方法作为入口
//scala中的main方法定义为def main(args:Array[String]),而且
object HelloWorld{
def main(args:Array[String]){
println("Hello World!")
}
}
//除了自己实现main方法外,还可以继承App Trait,然后将需要在main方法中运行的代码,直接作为object的constructor代码,而且用args可以接受传入的参数
object HelloWorld extends App{
if(args.length >0)println("hello," +args(0))
else println("Hello World!")
}
// 如果要运行上述代码,需要将其放入.scala文件,然后先使用scalac编译,再用scala执行
scalac HelloWorld.scala
scala -Dscala.time HelloWorld
// App Trait的工作原理为:App Trait继承自DelayedInit Trait ,scalac命令进行编译时,会把
// 继承 App Trait的object的constructor代码都放到DelayedInit Trait的delayedInit方法中执行
scala -Dscala.time HelloWorld 显示执行时间
-- 用object实现枚举功能
// scala没有直接提供类似于java中的Enum这样的枚举特性,如果要实现枚举,则需要用object继承Enumeration类,并调用Value方法来初始化枚举值
object Season extends Enumeration{
val SPRING,SUMMER,AUTUMN,WINTER=Value
}
// 还可以通过Value传入枚举值得id和name,通过id和toString可以获取,还可以通过id和name来查找枚举值
object Season extends Enumeration{
val SPARING=Value(0,"spring")
val SUMMER=Value(1,"summer")
val AUTUMN=Value(2,"autumn")
val WINTER=Value(3,"winter")
}
Season(0)
Season.withName("spring")
// 使用枚举object.values可以遍历枚举值
for(ele <- Season.values)println(ele)
使用如下所示:
-- -- extends 关键字
// scala中,让子类继承父类,与java一样,也是使用extends关键字
//继承就代表,子类可以继承父类的field和method,然后子类可以在自己的内部放入父类所没有,子类特有的field和method,使用继承可以有效复用代码
//子类可以覆盖父类的field和method,但是如果父类用final修饰,field和method用final修饰,则该类是无法被继承的,field和method是无法被覆盖的
class Person{
private var name="leo"
def getName=name
}
class Student extends Person{
private var score="A"
def getScore=score
}
代码展示:
-- override和super
//scala中,如果子类要覆盖一个父类中的非抽象方法, 则必须使用override关键字
//override关键字可以帮助我们尽早的发现代码中的错误,比如:override修饰的父类方法的方法名写错了,比如要覆盖的父类的参数写错了等,父类中一旦没有就会报错。
//此外,在子类覆盖父类的方法后,如果要用要调用父类的被覆盖的方法, 就可以使用super关键字,显式指定调用父类的方法。
-- -- override field
// scala中,子类可以覆盖父类的的val field,而且子类的val field还可以覆盖
// 父类的val field的getter方法,只要在子类中使用override关键字即可
class Person{
val name:String="Person"
def age:Int=0
}
class Student extends Person{
override val name:String="leo"
override val age:Int=30
}
-- isInstanceOf 和asInstanceOf
//如果我们创建子类的对象,但是又将其赋予父类类型的变量,则在后续的程序中,我们又需要将父类类型的变量转换为
//子类类型的变量,该怎么做呢?
// 首先,需要使用isInstanceOf判断对象是否是指定类的对象,如果是的话,则可以使用asInstanceOf将对象转为指定类型。
//如果对象为null ,则isInstanceOf一定返回false,asInstanceOf一定返回null】
//注意:如果没有用isInstanceOf先判断对象是否为指定类的实例,就直接使用asInstanceOf转换,则可能会抛出异常
class Person
class Student extends Person
val p:Person=new Student
var s:Student=null
if(p.isInstanceOf(Student)) s=p.asInstanceOf(Student)
-- getClass 和classOf
// isInstanceOf只能判断出对象是否是指定类型以及其子类的对象,而不能精确判断出,对象就是指定类的对象
//如果要精确地判断对象就是指定类的对象,那么就只能使用getClass和classOf了
//对象.getClass可以精确获取对象的类,classOf[类]可以精确获取类,然后使用==操作符即可判断
class Person
class Student extends Person
val p:Person=new Student
p.isInstanceOf[Person]
p.getClass == classOf[Person] // 精确判断
p.getClass ==classOf[Student]
-- 使用模式匹配进行类型判断
//实际开发中,比如spark源码中,大量的地方都是使用到模式匹配的方式进行类型的判断
// 这种方式更加简洁明了,而且代码的可维护性和扩展性非常高
// 使用模式匹配,功能性上来说,与isInstanceOf一样,也是判断主要是该类以及该类的子类的对象即可,不是精确判断的
class Person
class Student extends Person
val p:Person =new Studnet
p match{
case per :Person => println("it's Person's object")
case _ => println("unknown type ")
}
-- protected
// 跟java一样,scala中同样可以使用protected关键字来修饰field和method,这样在子类中就不需要super关键字,直接可以访问field和method
// 还可以使用protected[this],则只能在当前子类对象中访问父类的field和method,无法通过其他子类对象访问父类的method和field
class Person{
protected var name:String="leo"
protected[this]var hobby:String="game"
}
class Student extends Person{
def sayHello =println("Hello, "+name)
def makeFriends(s:Student){
println("my hobby is "+ hobby+", your hobby is" + s.hobby)
}
使用如下:
-- 调用父类的constructor
//Scala中,每个类可以有一个主constructor和任意多个辅助constructor,而每个辅助constructor的第一行
//都必须是调用其他辅助constructor或者是主constructor;因此子类的辅助constructor是一定不可能直接调用父类的constructor的
// 只能在子类的主constructor中调用父类的constructor,以下这种语法,就是通过子类的构造函数来调用父类的构造函数
//注意:如果是父类中接收的参数,比如name和age,子类中接收时,就不要用任何val或者var来修饰,否则会认为是子类要覆盖父类的field
class Person(val name:String,val age:Int)
class Student(name:String,age:Int,var score:Double) extends Person(name,age){
def this(name:String){ this(name,0,0)}
def this(age:Int){ this("leo",age,0) }
}
-- 匿名子类
// 在scala中,匿名子类是非常常见的, 而且非常强大.spark源码中也大量使用了这种匿名子类
// 匿名子类也就是说,可以定义一个类的没有名字的子类,并直接创建其对象,然后将对象的引用赋予一个变量,之后甚至可以将该子类的对象传递给其他函数。
class Person(protected val name:String){
def sayHello="Hello ,I'm "+ name
}
val p=new Person("leo"){ //匿名子类
override def sayHello="Hi, I'm "+ name
}
def greeting(p:Person){
println(p.sayHello)
}
用法:
-- 抽象类
// 如果在父类中,有某些方法无法立即实现,而需要依赖不同的子类来覆盖,重写实现自己不同的方法实现。
// 此时可以将父类中的这些方法不给出具体的实现,只有方法签名,这种方法就是抽象方法
//而一个类中如果有一个抽象方法,那么类就必须用abstract声明为抽象类,不可以被实例化
// 在子类中覆盖抽象类的抽象方法时,不需要使用override关键字
abstract class Person(val name:String){ def sayHello:Unit }
class Student(name:String) extends Person(name){
def sayHello:Unit=println("Hello , " + name)
}
-- 抽象field
//如果在父类中定义了 field ,但是没有给出初始化值,则此field为抽象field
//抽象field意味着,scala会根据自己的规则,为val或var类型的field生成对应的getter和setter方法,但是父类中是没有该field的
//子类必须覆盖field,以定义自己的具体的field,并且覆盖抽象field, 不需要使用override关键字
abstract class Person{ var name:String }
class Student extends Person{
val name:String="leo" // 不需要用override关键字
}