Scala篇(3)面向对象编程

Scala 面向对象编程学习

Scala是一个完全面向对象的语言

package(Scala中的包)

Scala中的包声明方式默认和java是一致的。但是有其他的使用方式

  1. 在同一个源码文件,可以多次声明包(但是声明的类在最后的那个包中)
    源码中的类所在的位置不需要和包路径相同
//第一种方式
package o.l.scala.Functions
package package1
class User {
	var name: String= _
	var age:Int=_
	def UserString(): String ={
		"name:"+this.name+"\t"+"age="+this.age
	}
}

//第二种方式
package o.l.scala.Functions
package package1{
	class User {
		var name: String= _
		var age:Int=_
		def UserString(): String ={
			"name:"+this.name+"\t"+"age="+this.age
		}
	}
}
上述这两种方式是一样的效果,
在其他类中使用User类的时候,需要导包:import o.l.scala.Functions.package1.User
package o.l.scala.Functions

package package1{
	class User {
		var name: String= _
		var age:Int=_
		def UserString(): String ={
			"name:"+this.name+"\t"+"age="+this.age
		}
	}
}

class User2{
	var name: String= _
	var age:Int=_
	def UserString(): String ={
		"name:"+this.name+"\t"+"age="+this.age
}

如果是这种情况,User类是在包o.l.scala.Functions.package1中,而User2类是在包o.l.scala.Functions中,使用时请注意
  1. Scala中所有的语法都可以嵌套
    package可以使用{ },{ }内声明的类在这个包中,之外声明的类不在这个包中
  2. scala中可以声明父包和子包,父包中的类,子包可以直接访问,不需要引入,其实就是作用域的概念
package package1 {

  class User {
    var name: String = _
    var age: Int = _

    def UserString(): String = {
      "name:" + this.name + "\t" + "age=" + this.age
    }
  }


  package package1_1 {

    class User2 {
      var name: String = _
      var age: Int = _

      def UserString(): String = {
        "name:" + this.name + "\t" + "age=" + this.age
      }

      private val user: User = new User()
    }


  }

}
  1. scala中package中可以声明类,但是无法声明变量和函数(方法)
  2. scala为了弥补包的不足,使用了包对象的概念(package object),其包对象中就可以声明属性和方法。
package object package2{
  var name:String = null
  def getname(): String ={
    name
  }
}

import 说明

  1. import 用于导类
  2. import 可以在任意的地方使用
  3. import 可以导入一个包中的所有的类,采用_ 下划线代替java中的*(import java.util._)
  4. import 导入相同包中的多个类,采用大括号进行包含处理
    import java.util.{ArrayList,List,Date}
  5. import 可以采用特殊的方式来隐藏指定的类,{类名=>_}
  6. import 可以导包(完善Java中的import只能导类的局限性)
    import java.util
    关于scala中的包的特别说明

先看如下代码:

package o.l.scala.Functions
import java.util.HashMap
package java{
  package util{

    class HashMap{

    }
    
    object Main{
      def main(args: Array[String]): Unit = {
        //TODO 此处的HashMap是本包下的HashMap类
        val map: HashMap = new HashMap()
        println(map)
        //TODO 使用 _root_ 可以从最开始的包中查找类,即可以增加绝对路径
        val hashMap = new _root_.java.util.HashMap()
        println(hashMap)
      }
    }
  }
}

结果:
o.l.scala.Functions.java.util.HashMap@763d9750
{}

Process finished with exit code 0

因此这里需要注意一下,在使用import时_root_可以指定原始包中的类

  1. import 导类的时候,可以给类起别名
    import root.java.util.{HashMap=>javaHashMap}
scala中隐式导入的一些类和对象
  • java.lang._
  • scala._
  • scala.Predef._

类的属性

class User06{

  //声明属性
  //TODO scala中给类声明属性,默认为私有的,但是底层提供了公共的getter和setter的方法
  var username:String = _
  //TODO 如果给属性增加private修饰符,那么属性无法在外部访问,因为底层生成的setter和getter方法都是私有的
  private var age:Int = _
  //TODO 如果声明的属性使用val,那么属性是私有的,并且底层使用final修饰,底层只有getter方法,而没有setter方法
  val email:String = "16@163.com"
}

为了能够和java bean规范统一,scala提供了注解,生成java中对应的set,get方法
@BeanProperty var address :String = _

package o.l.scala.Functions

import scala.beans.BeanProperty

class Student1 {
	@BeanProperty var name:String = _
	@BeanProperty var age:Int=_
}

object Test{
	def main(args: Array[String]): Unit = {
		val student = new Student1()
		student.setName("zhangsan")
		student.setAge(20)
		println(student.getName)
		println(student.getAge)
	}
}
结果:
zhangsan
20

Process finished with exit code 0

访问修饰符

  1. public(默认的访问权限, 没有关键字)
  2. protected(访问权限只能子类访问,同包访问不了)
  3. private(私有访问权限,只能当前类访问)
  4. default(package):包访问权限需要特殊的语法规则:private[包名]
package p1 {
	
	
	package p2 {
		class User(){
			var username="张三"
			private var password = "123456789"
			protected var email = "1564@163.com"
			private[p2] var address = "XXXXXX"
		}
		
		//TODO 包级别访问,同包下直接能够访问
		class Emp2{
			def test(): Unit ={
				val user = new User()
				println(user.username)
				println(user.address)
			}
			
			
		}
		
		
	}
	
	package p3{
		
		import o.l.scala.Functions.p1.p2.User
		// TODO 默认公共属性访问
		class Emp{
			def test(): Unit ={
				val user: User = new User()
				println(user.username)
			}
		}
		//TODO protected权限访问
		class Emp2 extends User{
			def test(): Unit ={
				println(this.email)
			}
		}
		
	}
	
	
}

伴生类和伴生对象

scala 中没有static关键字,如果想要实现static的效果,就可以通过伴生对象来实现

在同一个scala文件中定义一个类,同时定义一个同名的object,那么它们就是伴生类和伴生对象的关系,可以互相直接访问私有的field。

package o.l.scala.Functions
//TODO 伴生类 (相当于成员)
class Student {
	private val sname = "张三"
}
//TODO 伴生对象  (相当于静态)
object Student{
	//TODO 使用伴生对象创建伴生类的对象,以实现通过类名. 可以直接调用类中的成员
	def apply(name: String): Student = new Student()
	
	def test(): String ={
		Student.test()
	}
}

val student = Student("张三")

apply()方法在访问Student时会自动被调用,调用伴生对象时是不需要new 伴生类的

伴生类和伴生对象的一个应用(静态工厂)
import scala.collection.mutable

// TODO 伴生类 伴生对象的应用:实现一个静态工厂


object StaticFactory {
	def main(args: Array[String]): Unit = {
		println(Human.getHuman("黑色"))
		println(Human.getHuman("黑色"))
		println(Human.getHuman("白色"))
		println(Human.getHuman("黄色"))
		println(Human.getHuman("黄色"))
	}
}


// TODO 工厂中有一个仓库, 如果创建一个新的对象时,如果仓库里面有,就直接返回仓库中的对象
// TODO 如果仓库中没有,那么就创建一个新的对象,并将新的对象放入到仓库中

object Human {
	
	private val map: mutable.Map[String, Human] = mutable.Map[String, Human](
		"黑色" -> new Human("黑色"),
		"白色" -> new Human("白色")
	)
	
	def getHuman(color: String): Human = map.getOrElseUpdate(color, new Human(color))
	
}

//TODO 私有化主构造器,防止外部创建对象
class Human private(color: String) {
	println(s"$color.......")
	
	override def toString: String = s"人种: $color"
}
结果:

	黑色.......
	白色.......
	人种: 黑色
	人种: 黑色
	人种: 白色
	黄色.......
	人种: 黄色
	人种: 黄色
	
这里需要注意的是,工厂中存在的资源只有一份,即上述两个  人种:黑色 是同一个对象。

类的方法

所谓的方法,其实就是类中声明的函数,所以声明方式和函数是一样的,调用方式上有一些区别

构造方法(主构造方法 & 辅助构造方法)

Scala的构造方法分为两类;主构造方法 & 辅助构造方法

Scala构建对象可以通过辅助构造方法创建,但是必须调用主构造方法
Scala是完全面向函数的语言,所以类也是函数
类是函数,可以使用小括号作为函数的参数列表
类所代表的的函数其实就是这个类的构造方法
默认情况下,scala也是给类提供无参构造方法,所以小括号可以省略
在类的后面声明的构造方法就是主构造方法
在主构造方法中声明的构造方法就是辅助构造方法

在函数式编程中,调用的函数需要定义在之前,先定义后才能被调用,注意顺序。

//TODO 主构造方法
class Scala09_Construction(s: String) {
	//TODO 这里是类体 & 也是构造方法体
	println("主构造方法")
	println(s)
	
	// TODO 这里是辅助构造方法,辅助构造方法可以定义多个,但是参数列表不能相同
	// TODO 辅助构造方法中必须调用主构造方法
	def this(s:String, ss:String){
		this(s)
		println("辅助构造方法2")
	}
	
	def this(){
		this("辅助构造方法1", "XXXXXXXXX")
	}
	
	
}
object test{
	def main(args: Array[String]): Unit = {
		new Scala09_Construction()
	}
}
结果:
主构造方法
辅助构造方法1
辅助构造方法2

Process finished with exit code 0

继承(extends)

使用extends关键字进行类之间的继承

java中的动态绑定机制

成员方法在执行过程中,JVM会将方法和当前调用对象实际内存进行绑定
属性没有动态绑定机制,属性在哪里声明在哪里使用

class AAA {
    public int i = 10;
    public int getResult(){
        return i+10;
    }
}

class BBB extends AAA{
    public int i = 20;
    public int getResult(){
        return i+20;
    }
}




//TODO 动态绑定机制
public class Java_RTTI {
    public static void main(String[] args) {
        AAA aaa = new BBB();
        System.out.println(aaa.getResult());
    }
}
结果:40
class AAA {
    public int i = 10;
    public int getResult(){
        return i+10;
    }
}

class BBB extends AAA{
    public int i = 20;
}




//TODO 动态绑定机制
public class Java_RTTI {
    public static void main(String[] args) {
        AAA aaa = new BBB();
        System.out.println(aaa.getResult());
    }
}
结果:20
class AAA {
    public int i = 10;
    public int getResult(){
        return getI()+10;
    }
    public int getI(){
        return i;
    }
}

class BBB extends AAA{
    public int i = 20;
    public int getI(){
        return i;
    }
}




//TODO 动态绑定机制
public class Java_RTTI {
    public static void main(String[] args) {
        AAA aaa = new BBB();
        System.out.println(aaa.getResult());
    }
}
结果:30

Scala中的动态绑定机制
scala中的属性和方法都是动态绑定的,而Java中只有方法是动态绑定的

Scala中属性也可以重写,因为属性可以抽象
属性只有声明,没有初始化,那么就是抽象属性
抽象属性在编译为class文件时,不产生属性,但是产生抽象getter方法。

scala 虽然可以重写属性,但是不能重写父类var声明的已经初始化的属性,可以重写val声明的已经初始化的属性。

//抽象类
abstract class Person{
	//TODO 抽象属性
	var age:Int
}


class Emp extends Person{
	
	//TODO 重写抽象属性
	override var age: Int = 20
}




object Scala10_Extends {
	def main(args: Array[String]): Unit = {
		println(new Emp().age)
	}
}

如果父类var声明的已经初始化的属性在子类中重写就会出现错误:
在这里插入图片描述
改成val 之后的运行结果:
在这里插入图片描述

父类构造方法(构造方法执行顺序)

class A1A(s:String){
	println("A1A主构造方法="+s)
	
}

class B1B(s:String, s2:String) extends A1A(s){
	
	println("B1B柱构造方法="+s2)
	
	def this(name:String){
		this(name, "B1B手工参数")
		println("B1B 辅助构造函数")
	}
}



object test{
	def main(args: Array[String]): Unit = {
		new B1B("A1A手工传入参数")
	}
}
结果:
A1A主构造方法=A1A手工传入参数
B1B柱构造方法=B1B手工参数
B1B 辅助构造函数

Process finished with exit code 0

类构造方法的参数的作用域默认为整个类的主体,但是如果想要参数作为属性来使用,可以采用特殊方式来声明。

class user1(var name:String){


特质(trait)(Scala中没有接口)

Scala中没有接口的概念,但是scala中有特质Trait

  • 特质是可以继承的,所以使用extends关键字
  • 如果类继承多个特质, 采用with关键字
  • 如果类同时存在父类和特质,依然采用extends,但是继承的是父类,用with连接(混入)特质
Java中的接口与Scala中的特质对比

Java中的接口(interface)与Scala中的特质(trait)

  • java中的接口无法直接构建对象,必须使用实现类

  • java中的接口是可以声明方法的,早期版本中声明的方法都是抽象,新版本的是可以有默认实现的

  • java中的接口是可以声明属性的,属性值无法修改(静态的)

  • scala中的特质也是无法直接构建对象

  • scala中的特质是可以执行代码的

object Trait {
	def main(args: Array[String]): Unit = {
		new MySql()
	}
	
}

trait Operate{
	println("operate........")
}


class MySql extends Operate{

}
结果:
operate........

Process finished with exit code 0

scala中的特质声明的属性和方法都可以在混入的类中调用

object Trait {
	def main(args: Array[String]): Unit = {
		new MySql().insert()
	}
	
}

trait Operate{
	println("operate........")
	def insert(): Unit ={
		println("插入数据")
	}
}


class MySql extends Operate{

}
结果:
operate........
插入数据

Process finished with exit code 0

scala特质中声明的属性值可以修改

object Trait {
	def main(args: Array[String]): Unit = {
		val sql: MySql = new MySql()
		sql.trait_name = "operate_trait"
		print(sql.trait_name)
	}
	
}

trait Operate{
	
	var trait_name:String = _
	
	println("operate........")
	def insert(): Unit ={
		println("插入数据")
	}
}


class MySql extends Operate{

}
结果:
operate........
operate_trait
Process finished with exit code 0

特质也可以继承其他的特质

object Trait {
	def main(args: Array[String]): Unit = {
		val sql: MySql = new MySql()
		sql.insert()
	}
	
}

trait Operate{
	
	var trait_name:String = _
	
	println("operate........")
	def insert(): Unit ={
		println("插入数据")
	}
}

trait DB extends Operate{
	
	override def insert(): Unit ={
		print("向数据库")
		super.insert()
	}
	
}
结果:
operate........
向数据库插入数据

Process finished with exit code 0

特质和父类没有关系,只和当前混入的类有关系,所以,在调用时,父类先执行,然后当前混入类的特质再执行,然后当前类再执行

如果父类混入了相同的特质,那么特质的代码只会执行一遍

object Trait {
	def main(args: Array[String]): Unit = {
		val sql: MySql = new MySql()
	}
	
}

trait Operate{
	
	var trait_name:String = _
	
	println("operate........")
	def insert(): Unit ={
		println("插入数据")
	}
}

trait DB extends Operate{
	println("DB........")
	override def insert(): Unit ={
		print("向数据库")
		super.insert()
	}
	
}


class MySql extends DB with Operate {
	println("MySql.........")
}
结果:
operate........
DB........
MySql.........

Process finished with exit code 0

多特质混入时,代码执行顺序为从左到右,如果有父特质,会优先执行

object Trait {
	def main(args: Array[String]): Unit = {
		val sql: MySql = new MySql()
	}
	
}

trait Operate{
	
	var trait_name:String = _
	
	println("operate........")
	def insert(): Unit ={
		println("插入数据")
	}
}

trait DB extends Operate{
	println("DB........")
	override def insert(): Unit ={
		print("向数据库")
		super.insert()
	}
	
}

trait File extends Operate{
	println("File........")
	override def insert(): Unit ={
		print("向文件")
		super.insert()
	}
	
}
结果:
operate........
DB........
File........
MySql.........

Process finished with exit code 0

多特质混入时,同名方法的调用顺序为从右往左

object Trait {
	def main(args: Array[String]): Unit = {
		val sql: MySql = new MySql()
		val sql2: MySql2 = new MySql2()
		sql.insert()
		sql2.insert()
	}
	
}

trait Operate{
	
	var trait_name:String = _
	
	println("operate........")
	def insert(): Unit ={
		println("插入数据")
	}
}

trait DB extends Operate{
	println("DB........")
	override def insert(): Unit ={
		print("向数据库")
		super.insert()
	}
	
}

trait File extends Operate{
	println("File........")
	override def insert(): Unit ={
		print("向文件")
		super.insert()
	}
	
}


class MySql extends DB with File {
	println("MySql.........")
}

class MySql2 extends File with DB {
	println("MySql.........")
}

结果:
operate........
DB........
File........
MySql.........
operate........
File........
DB........
MySql.........
向文件向数据库插入数据
向数据库向文件插入数据

Process finished with exit code 0


从结果可以得出,super关键字在特质中调用是上一级特质中的方法/*class MySql extends DB with File*/
如果需要调用直接父类特质的方法,需要特殊的声明方式:super[trait].insert()

特质中super关键字不是指代父特质,而是指代上一级特质
如果希望super关键字指向父类特质,需要增加特殊操作:super[特质]

object Trait {
	def main(args: Array[String]): Unit = {
		val sql: MySql = new MySql()
		sql.insert()
//		val sql2: MySql2 = new MySql2()
//		sql2.insert()
	}
	
}

trait Operate{
	
	var trait_name:String = _
	
	println("operate........")
	def insert(): Unit ={
		println("插入数据")
	}
}

trait DB extends Operate{
	println("DB........")
	override def insert(): Unit ={
		print("向数据库")
		super[Operate].insert()
	}
	
}

trait File extends Operate{
	println("File........")
	override def insert(): Unit ={
		print("向文件")
		super[Operate].insert()
	}
	
}


class MySql extends DB with File {
	println("MySql.........")
}

class MySql2 extends File with DB {
	println("MySql.........")
}

结果:
operate........
DB........
File........
MySql.........
向文件插入数据

Process finished with exit code 0

java的接口可以在scala中当成特质来使用

class MySql2 extends File with DB with Serializable{
	println("MySql.........")
}
特质的动态混入(可灵活的扩展类的功能)

动态混入:创建对象时可以混入trait,而无需使类混入该trait
如果混入的trait中有未实现的方法,则需要实现

object Trait {
	def main(args: Array[String]): Unit = {
//		val sql: MySql = new MySql()
//		sql.insert()
//		val sql2: MySql2 = new MySql2()
//		sql2.insert()
		//TODO trait动态混入
		val teacher: Teacher with SexTrait = new Teacher() with SexTrait {
			override var sex: String = "女"
		}
		teacher.eat()
		teacher.say()
		println(teacher.sex)
		
		
	}
	
}


trait PersonTrait{
	var name:String = _
	
	var age:Int
	
	def eat(): Unit ={
		println("eat...")
	}
	
	def say(): Unit
	
}

trait SexTrait{
	var sex:String
}

class Teacher extends PersonTrait with java.io.Serializable{
	override var age: Int = _
	
	override def say(): Unit = {
		println("say()....")
	}
}

特质可以继承类

特质可以继承一个普通类,继承后,在特质中可以使用该类开放(可以被子类访问的那些)的那些属性和方法

trait Operate15 extends Exception{
	
	this.getMessage()
	
}
特质叠加

由于一个类可以混入(mixin)多个trait,且trait中可以有具体的属性和方法,若混入的特质中具有相同的方法(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。

冲突分为以下两种:

  1. 第一种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法。在这里插入图片描述
  2. 第二种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait继承自相同的trait(TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala采用了特质叠加的策略。在这里插入图片描述

所谓的特质叠加,就是将混入的多个trait中的冲突方法叠加起来

trait Ball {
  def describe(): String = {
    "ball"
  }
}

trait Color extends Ball {
  override def describe(): String = {
    "blue-" + super.describe()
  }
}

trait Category extends Ball {
  override def describe(): String = {
    "foot-" + super.describe()
  }
}

class MyBall extends Category with Color {
  override def describe(): String = {
    "my ball is a " + super.describe()
  }
}

object TestTrait {
  def main(args: Array[String]): Unit = {
    println(new MyBall().describe())
  }
}

运行结果:
在这里插入图片描述

特质叠加执行顺序

思考:上述案例中的super.describe()调用的是父trait中的方法吗?

当一个类混入多个特质的时候,scala会对所有的特质及其父特质按照一定的顺序进行排序,而此案例中的super.describe()调用的实际上是排好序后的下一个特质中的describe()方法。,排序规则如下:
在这里插入图片描述

(1)案例中的super,不是表示其父特质对象,而是表示上述叠加顺序中的下一个特质,即,MyClass中的super指代Color,Color中的super指代Category,Category中的super指代Ball
(2)如果想要调用某个指定的混入特质中的方法,可以增加约束:super[],super[Category].describe()。

trait特质自身类型:指定trait特质使用的范围(可实现依赖注入)

trait的自身类型可以限定trait混入类的范围
例如:

//TODO 指定特质使用的范围
trait Operate19{
	// 这里的this 可以换成任意的合法名称, 然后通过这个名称可以访问Exception中的成员。
	this:Exception =>
	def insert(): Unit ={
		print("插入数据")
		this.getMessage()
	}
}
这个特质的自身类型是:Exception类
如果某个类想要继承这个特质,这个类就必须先继承 Exception这个类
class MySql3 extends Exception with Operate19 {
	println("MySql.........")
}

扩展

类型检查&类型转换&类信息
  • obj.isInstanceOf[T]:判断obj是不是T类型
  • obj.asInstanceOf[T]:将obj强转成T类型
  • classOf[类]:获取类的信息
object Instanceof extends App {
	
	def test(o: Object){
		val bool: Boolean = o.isInstanceOf[AA2]
		if(bool){
			val aa: AA2 = o.asInstanceOf[AA2]
			aa.p()
		}
	}
	test(new AA2())
	private val value: Class[AA2] = classOf[AA2]
	print(value)
	
}
class AA2{
	def p(): Unit ={
		print("..........")
	}
}

结果:
..........class o.l.scala.Functions.AA2
Process finished with exit code 0
枚举类和应用类
object Enum extends App {
	print(Color1.RED)
}

object Color1 extends Enumeration{
	val RED: Value = Value(1, "red")
	val BLUE: Value = Value(2, "blue")
	val GREEN: Value = Value(3, "green")
}

结果:
red
Process finished with exit code 0

注意:枚举类是 伴生对象

对于继承App类的伴生对象与伴生对象中定义main方法,都可以作为程序的入口函数,都可以执行。

type 定义新类型(给类起别名)
type S=String
val name:S = "zhangsan"
sealed关键修饰的类(密封类)

用sealed关键字修饰的类叫做密封类
密封类的子类只能出现在 父类的文件中,在这个文件之外无法定义密封类的子类

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值