Day59_scala(二)

第二讲 Scala面向对象

一、类的基本操作

(一)、类的定义

1、类的定义

​    类或者类型,就是对客观的一类事物的抽象。用一个class关键字来描述,在这个类中可以拥有这一类事物的属性,行为等等。

​    或者说就是用计算机的语言来描述的一类事物,就是类,在java,scala中都是用关键字class来标识。

​    因为类是对一类事物的抽象,所以不具备具体的行为执行能力,要想完成具体的操作,就需要使用该类的实例或者对象。

//创建scala中的一个类
class Dog {
    //用val修饰的变量是只读属性,有getter但没有setter
    //(相当与Java中用final修饰的变量)
    val id = "9527"
    //用var修饰的变量既有getter又有setter
    var age: Int = 18
    // _代表初始值,使用时属性必须指定类型
    var color:String = _
    //类私有字段,只能在伴生对象和类的内部使用
    private var name: String = "唐伯虎"
    //对象私有字段,访问权限更加严格的,当前对象的字段只能被Person类的方法访问到
    //对象私有字段,只能在类的内部使用
    private[this] val pet = "小强"
    //举例
    def keepPet()={
        println(pet)
    }
}
object enter{
    def main(args: Array[String]): Unit = {
        val d = new Dog
        //调用属性
        println(d.id)
        println(d.age)
        //调用方法
        d.keepPet()
    }
}

2、getter/setter操作

//getter
println(dog leg)
//setter
//对象.属性_=(值)
dog.leg_=(10)
println(dog currentLeg)

在Scala中使用@BeanProperty注解来给成员变量提供getter/setter,该注解和private访问权限修饰符不可共存.

   这种方式定义类似java bean的操作,在Scala中几乎不用,Java bean的主要作用:

说白了就是在各个业务线/层之间传递数据,Scala提供了另外的一种结果来模拟Java中的bean--case class(样例类,样本类)

class Student {
	@BeanProperty var name:String = _
	private var age = 13
}

(二)、类的构造

1、Java中的构造器

类的构造函数,在java中一个类的构造函数(器),可以分为有参构造器或者无参的构造器,或者默认构造器的说法。所谓默认构造器指的就是,用户不指定/创建相关构造器,而虚拟机会自动的为我们的类添加一个无/空参的构造器,用于对象的创建。

在java中一个类,可以拥有若干个构造器,既可以拥有无参构造器,也可以拥有有参构造器,如果用户提供了构造器,此时便不会再有默认的构造器了。

2、Scala中的构造器

Scala中有两类构造器——主构造器和辅助构造器。

(1)、构造器验证

object _02ConstructorOps {
	def main(args: Array[String]): Unit = {
		val p = new Person("jack", 23)
		p.show()
	}
}
class Person() {
	private var name:String = _
	private var age:Int = _
	def Person(): Unit = {
		println("--Person()--是默认的构造器吗?")
	}
	//自定义构造器 自定义构造器的第一句话必须要调用其它构造器
	def this(name:String, age:Int) {
		this()
		this.name = name
		this.age = age
		println("---------this(name:String, age:Int)---------")
	}
	println("-------------构造器~----------------")
	def show(): Unit = {
		println(s"name=${name}\tage=${age}")
	}
}

(2)、Scala构造器

例中的这个和类的定义交织在一起的默认的构造器,是scala类中最重要的一个构造器——主构造器(默认的构造器是无参主构造器,定义在类名和类的{之间,省略了())。

其余的自定义构造器,也就是在类中通过def this(xxx)定义的构造器称之为辅助构造器,同时辅助构造器的第一句话,必须要通过this来调用本类主构造器或者其它辅助构造器。

object _02ConstructorOps {
	def main(args: Array[String]): Unit = {
		val _3p = new Person(77)
		_3p.show()
	}
}
class Person(var name:String, var age:Int) {
	def Person(): Unit = {
		println("--Person()--是默认的构造器吗?")
	}
	def this() {
		this("嘿嘿", 33)
		println("---this() ---")
	}
	def this(name:String) {
		this()
		this.name = name
		println("---this(name:String) ---")
	}
	def this(age:Int) {
		this("tom")
		this.age = age
		println("---this(age:Int) ---")
	}
	println("-------------构造器~----------------")

	def show(): Unit = {
		println(s"name=${name}\tage=${age}")
	}
}

(三)、内部类

主要用到这个内部类的原因,就在于如果将两个事物,每一个事物在java中都是类class,将这两个可能具有包含关系,比如人Person,心脏Heart,平行去定义,就不大符合实际的情况。而Heart是人这一类事物内部的一类其它事物,能够直接访问人这一类事物中的很多属性,如果将Heart定义在外部,要想获取人的血液,就需要在Heart并行的创建人Person对象,再进行其它操作,显然不合理。

对于这类型的操作,我们就是用类的嵌套来完成定义,或者这种结构称之为嵌套类或者内部类。

1、Scala版本的内部类

object _03InnerClassOps {
	def main(args: Array[String]): Unit = {
		val outer = new Outer
		val inner = new outer.Inner()
		inner.show()
	}
}
class Outer { ooo =>
	 val x = 5
	class Inner { iii =>
		val x = 6
		def show(): Unit = {
			val x = 7
			System.out.println("x = " + x)
			System.out.println("inner class x = " + iii.x)
			System.out.println("Outer class x = " + ooo.x)
		}
	}
}

2、总结

scala的内部类和java的内部类的主要区别就在内部类对象的创建方式不一样。其次scala为了灵活的在内部类的局部访问内部类成员或者外部类的成员,可以给内部类和外部类的引用提供一个别名,方便操作。

也就是说,上例中这iii.x <=> this.x, ooo.x <=> Outer.this.x

内部类使用蛮多的一个地方:就是匿名内部类。

(四)、object对象

scala中的除了class以外,还有两个与此平级的结构——一个是Object对象,一个是Trait特质。为啥要有这个Object呢?

主要的原因在于,在java中的一个class既可以拥有非静态的成员,也可以拥有静态static的成员。但是在scala中的class结构中,只能拥有非静态。为了给scala中的类也来提供类似于java中的静态成员的功能,于是乎就有了Object对象这个结构。

1、object对象

Java中的main函数是静态,此时在class中定义的main是无法运行,但是把class ObjectClassOps替换成object ObjectClassOps便可以运行,主要的原因在于,被object所修饰的结构中的所有的成员都是static静态。如下图2-2-1所示:

 

所以我们可以归纳出object的主要作用:

  1. 给Scala类提供程序运行的入口,静态的main函数
  2. 给Scala类也来提供静态成员——Scala类的伴生对象来实现。

2、单例

(1)、Scala版本单例

  1. 在Scala中没有静态方法和静态字段,但是可以使用object这个语法结构来达到同样的目的
  2. 存放工具方法和常量
  3. 高效共享单个不可变的实例
  4. 单例模式
/**
  * scala的单例操作
  */
object _06LazhSingletonOps {
	def main(args: Array[String]): Unit = {
		val s1 = Singleton.getInstance()
		println("x: " + s1.x)
		s1.x = 6
		val s2 = Singleton.getInstance()
		println("x: " + s2.x)
		println("-------------------------")
		val s3 = LazySingleton.getInstance()
		println("x: " + s3.x)
		s3.x = 9
		val s4 = LazySingleton.getInstance()
		println("x: " + s4.x)
	}
}
class Singleton private () {//私有化主构造器
	var x = 5
}
object Singleton {
	val instance = new Singleton
	def getInstance(): Singleton = {
		instance
	}
}
class LazySingleton private() {
	var x = 6
}
object LazySingleton {
	var instance: LazySingleton = null
	def getInstance(): LazySingleton = {
		if(instance == null) {
			LazySingleton.synchronized {
				if (instance == null) {
					instance = new LazySingleton
				}
			}
		}
		instance
	}
}

我们发现,在上述这个案例当中,同一个scala源文件中可以包含类名相同的一个class和object,这在java中是不被允许的;同时上述的这种单例的构造结果,可以看到对一个class似乎也提供了静态的功能。把这种结构,和class在同一个源文件(.scala)中同名object结构称之为该类的伴生对象,把该类称之为该object的伴生类。

​    伴生对象的主要作用,就是为其伴生类提供类似java中的静态成员操作;其二还可以提供另外一种对象的创建方式。

package cn.bigdata.scala

class Dog {
  val id = 1
  private var name = "bigdata"

  def printName(): Unit ={
    //在Dog类中可以访问伴生对象Dog的私有属性
    println(Dog.CONSTANT + name )
  }
}

/**
  * 伴生对象
  */
object Dog {

  //伴生对象中的私有属性
  private val CONSTANT = "汪汪汪 : "

  def main(args: Array[String]) {
    val p = new Dog
    //访问私有的字段name
    p.name = "123"
    p.printName()
  }
}

注意:

1.  要想使用伴生对象创建本类的对象,必须要让本伴生对象复写一个apply方法,该apply方法的参数列表对应的是本类构造器的参数列表。

2.  伴生对象,不仅可以访问对应伴生类的非私有成员,同时还可以访问对应伴生类的私有成员

3.  伴生对象的使用,在scala中是非常非常广泛,举个例子,在工作中集合对象的创建几乎都用伴生对象来操作,还有后面spark中各个非常关键的对象的创建也是用伴生对象的形式。

二、类的继承体系

类与类之间的一个很重要关系——继承/扩展(extends)。

(一)、类的扩展

1、继承特点

  1. 子类可以继承父类的所有非私有(private),非静态的成员(变量和成员方法)。
  2. 可以对父类的相关方法进行覆盖/重写
  3. 也可以添加自己独有的成员
  4. 被final修饰的父类成员,子类不可以继承
  5. 被protected修饰的父类成员,子类可以继承
  6. 子类覆盖父类的方法的异常,必须要大于等于父类的异常
  7. 子类的访问权限必须要大于等于父类

2、继承案例

class Person {
	private var name:String = _
	protected var age:Int = 0
	def this(name:String, age:Int) {
		this()
		this.name = name
		this.age = age
	}
	def show(): Unit = {
		println(s"person's name is $name")
	}
}

 

class Student extends Person {
	age = 15
	def this(age:Int) {
		this()
		this.age  = age
	}
	override def show(): Unit = {
		super.show()//子类调用父类的成员通过super关键字
		println(s"Student's age is $age")
	}
}

注意:

​    override在Java中是一个注解,用来表示该方法是继承的,Scala中是一个关键字,必须要添加在重写的方法前面,除非该方法是抽象的。

public class OverrideOps {
    public static void main(String[] args) {
        Fu f = new Zi();
        f.llllllllllll();
    }
}
class Fu {
    public void llllllllllll(){
    }
}
class Zi extends Fu {
    @Override//错误的
    public void lllllll11lll() {
		//原因在于这个中多了两个数字1,看着和L挺接近的,但是是两个不同的方法,所以无法覆盖
        //正是因为这一点,在定义Long的数据时候,尽量使用L来进行标识
    }
    @Override
    public void llllllllllll() {
        super.llllllllllll();
    }
}

(二)、类型检查和转换

2、Scala的类型检查和转换

在Scala中也有类似于java中的类型检查和类型转换,这些操作,通常应用在多态(父类引用指向子类对象),类型的判断的时候。在Scala中使用isInstanceOf来进行类型判断,是asInstanceOf进行类型转换。需要注意的是这两个操作都是对象的方法。

var map01 = Map[String,Any]("name"->"张三","gender"->'男',"bhtday"->1998)

println(map01("name").isInstanceOf[String])
println(map01("gender").isInstanceOf[Char])
println(map01("bhtday").isInstanceOf[Int])
println(map01("name").asInstanceOf[String])
println(map01("gender").asInstanceOf[Char])
println(map01("bhtday").asInstanceOf[Int])

(三)、受保护字段和方法

所谓受保护的字段和方法其实就是被访问权限修饰符protected所修饰的成员。被该关键字所修饰的成员有啥特点:

Java:只能被子类访问,同时必须要在本包下面被访问。

ScalaJava的基础之上,可以做到更灵活、更精准的访问权限控制

object _03ProtectOps {
	def main(args: Array[String]): Unit = {
		val dog = new Dog("黃色")
		dog.show()
		dog.age
	}
}
class Animal {
	private var name:String = _
	protected var age = 3
	def this(name:String, age:Int) {
		this()
		this.name = name
		this.age = age
	}
	def show(): Unit = {
		println(s"Animal: ${name}, ${age}")
	}
}
class Dog extends Animal {
	age = 4
	private var color:String = _
	def this(color:String) {
		this()
		this.color = color
	}
	override def show(): Unit = {
		super.show()
		println("color: " + color)
	}
}

这里有一个错误,age不能像java中一样,同同一个包下面被直接访问,错误如下图3-1-1所示:

但是scala中提供了一个更加强大的功能来精确的控制一个成员的访问权限。就只privateprotected后面加上中括号[],[]里面写上要在哪一个范围内可以被访问,比如这里将其修改为

protected[extendz],其中的这个extendz就是本类所在的包,于是age便可以被访问了。更加精确的是,只能在本包,及其子包下面被访问。

class Animal {

   private var name:String = _

   protected[this] var age = 3

   def this(name:String, age:Int) {

      this()

      this.name = name

      this.age = age

   }

   def show(): Unit = {

      println(s"Animal: ${name}, ${age}")

   }

}

class Dog extends Animal {

   age = 4

   private var color:String = _

   def this(color:String) {

      this()

      this.color = color

   }

   override def show(): Unit = {

      super.show()

      println("color: " + color)

   }

   def makeFriend(dog:Dog): Unit = {

      println(s"${this.age}和另外的一個小狗${dog.age}來交朋友")

   }

}

其中有一个比较特殊的就是private[this]或者protected[this]

编译会出现如下问题,如图3-1-2所示:

 

protected[this]所修饰的变量,只能在本类,及其子类中被调用,但不可以被子类对象调用。

这种通过精确指定成员的访问权限修饰的方式在scala中是非常常见的,大家一定要能读懂。

总结:

​       Scala中的访问权限修饰符就只有这么两个:privateprotected,没有Java中的public,所以我们在创建类的时候,不需要加public关键字,其实不加这个public就相当于Java中的public

(四)、超类的构造

在类的扩展中有个问题,子类没有办法给父类中的name进行直接赋值,所以尝试子类辅助构造器通过类似Java中的super关键字去调用父类的相关构造器,但是出现如图3-1-3下异常:

也是就是,Scala中子类无法通过super调用父类构造,但是在子类show方法中,却可以成功调度用super.show()方法,super在这里便是父类的一个实例引用,说明父类完成的构造,那么父类的构造是如何被调用的呢? 

object _01ExtendsOps {
	def main(args: Array[String]): Unit = {
		val stu = new Student(18)
		stu.show()
	}
}
class Person {
	private var name:String = _
	protected var age:Int = 0
	println("父类Person的主构造器-------")
	def this(name:String, age:Int) {
		this()
		this.name = name
		this.age = age
		println("----父类Person的辅助构造器------")
	}
	def show(): Unit = {
		println(s"person's name is $name")
	}
}
class Student extends Person {
	println("子类Student的主构造器")
	age = 15
	def this(age:Int) {
		super()
		this.age  = age
		println("----子类Student的辅助构造器------")
	}
	override def show(): Unit = {
		super.show()
		println(s"Student's age is $age")
	}
}

结果:

父类Person的主构造器-------

子类Student的主构造器

----子类Student的辅助构造器------

person's name is null Student's age is 18

我们看到的是,在子类的主构造器中调用了父类的构造器,因为子类的辅助构造器必须要在第一句话上面使用this来调用本类的主构造器或者其他辅助构造器,所以就没有机会来调用父类的构造器,也就是说,子类只能通过主构造器来调用父类的构造器。

class Person1(name:String) {
    var age = 12
    //定义辅助构造器
    def this(name:String,age:Int){
        //辅助构造器首行必须调用主构造器或其它辅助构造器
        this(name)
        this.age=age
        println(age)
    }
}
class Student1(name:String,hobby:String) extends Person1(name){
    var color = "red"
    println(name)
    println(hobby)
    //定义辅助构造器
    def this(name:String,hobby:String,color:String){
        //子类当中辅助构造器只能调用本类的主构造器或辅助构造器
        this(name,hobby)
        this.color=color
        println(color)
    }
}
object enter111{
    def main(args: Array[String]): Unit = {
        val s = new Student1("zhang","running")
    }
}

(五)、抽象类

1、抽象类

​很简单,概念和java中的抽象类一样,所谓抽象类,指的就是一个类中的方法有抽象,或者说没有被实现,把这种类称之为抽象类。

​scala中的抽象类也是使用abstract关键字来进行定义;同时该抽象类中既可以有抽象方法,也可以有非抽象方法;scala中的抽象方法可以省略abstract关键字。

abstract class Animal {
	var color:String = _
	/*abstract*/ def sleep()
	def dead(): Unit = {
		println("动物固有一死,或清蒸,或红烧~")
	}
}
class Dog extends Animal {
	def sleep(): Unit = {
		println("小狗睡觉是趴着睡~")
	}
	override def dead(): Unit = {
		super.dead()
		println("但是,狗乃人类之伙伴,最好不要清蒸,或红烧,不道德")
	}
}
class Horse extends Animal {
	override def sleep(): Unit = {
		println("小马驹睡觉是站着睡~")
	}
}

说明一点:

子类覆盖父类的抽象方法,可以省略掉override关键。

2、抽象字段

和Java中不同的地方,就是在scala中除了有抽象方法以外,还可以有抽象的字段,啥叫做抽象字段,只有字段的定义,没有进行初始化的。

abstract class AbstractFu {
	/*abstract*/ val name:String
	var age:Int
}
class AbstractZi extends AbstractFu {
	/*override*/ val name = "zhangsan"
	 var age = 16
}

定义的这个val的抽象字段,子类只能进行一次初始化,后期则不可以进行修改;而var的变量可以进行任意的操作。

(六)、Trait特质

​    Scala中的这个继承和java的继承有一个缺陷,只能进行单继承,但是可以进行多层继承,但是多层继承又要要求,类与类之间必须具有继承关系,这显然不一定满足,还是有局限的。所以再java中推出了接口interface这个概念来满足多重继承,只不过这里不叫继承,而称之为多实现,使用关键字implements来连接,多个接口interface之间使用","进行分割。

​    Scala呢?

​    Scala对于同样的需求,设计出了另外一个结构,什么呢——trait,特质。这个trait的功能要比java中的接口强大的多,不仅仅拥有抽象方法,还可以拥有非抽象方法,同时可以多重扩展trait,扩展特质的时候使用关键extends,多个特质之间使用with进行连接。

​    所以如果说,一个trait特质中的所有方法都是抽象方法,那么该trait就可以当做Java中的接口去对待。

1、特质的定义

trait的功能要比Java中的接口强大的多,不仅仅拥有抽象方法,还可以拥有非常抽象方法,同时可以多重扩展trait,扩展特质的时候使用关键extends,多个特质之间使用with进行连接。

object _02TraitOps {
	def main(args: Array[String]): Unit = {
		val cLog = new ConsoleLog
		cLog.log("冬天来了,春天也就不远了~")
	}
}
trait Log {
	def log(msg:String)
	def show(): Unit = {
		println("trait中的非抽象方法")
	}
}
class ConsoleLog extends Log {
	override def log(msg: String): Unit = {
		println("console--->" + msg)
	}
}
class FileLog extends Log {
	override def log(msg: String): Unit = {
		println("file--->" + msg)
	}
}

2、特质的多扩展

trait的功能要比Java中的接口强大的多,不仅仅拥有抽象方法,还可以拥有非常抽象方法,同时可以多重扩展trait,扩展特质的时候使用关键extends,多个特质之间使用with进行连接。

//将日志信息发送到网络中区
class SocketLog extends Log with Serializable {
    override def log(msg: String): Unit = {
    	println("socket--->" + msg)
    }
}

3、特质的混入

当一个类的实例只需要进行一次符合某种特性的操作,而这种特性被定义在另外一个特质中,就可以通过Scala特质特有的一个概念来处理——混入(mix in),混入并不会对整个类造成侵入性,只会对当前的对象产生一次性的影响。

object _02TraitOps {
	def main(args: Array[String]): Unit = {
		println("-----------------混入--------------")
		val sLog = new SocketLog with Sercurit
		sLog.log("我要飞得更高~")
		sLog.validate()
	}
}
trait Log {
	def log(msg:String)
	def show(): Unit = {
		println("trait中的非抽象方法")
	}
}
trait Sercurit {
	def validate(): Unit = {
		println("安全你我他,快乐给大家~")
	}
	/*def log(msg: String): Unit = {
		println("Sercurit--->" + msg)
	}*/
}
//将日志信息发送到网络中区
class SocketLog extends Log  {
	override def log(msg: String): Unit = {
		println("socket--->" + msg)
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值