Scala 面向对象编程
Class & object
由于Scala没有静态方法和静态类,通过object去定义静态方法或者静态对象。当object和Class放在一个文件中时候称该object为当前Class的伴生对象。
单例类
单例类使用object修饰,所有声明在object中的方法都是静态方法,类似于Java中声明的工具类的作用。
object HelloUtil {
def sayHello(name:String):Unit={
println("hello~ "+name)
}
}
类
class User {
var id:Int =_
var name:String = _
var age:Int = _
//扩展构造
def this(id:Int,name:String,age:Int){
this()
this.id=id
this.name=name
this.age=age
}
}
必须要求在扩展构造方法的第一行显式调用this()-默认构造,其中
_
表示参数赋值为默认值
def this(参数)定义扩展构造器
class User {
var id:Int =_
var name:String = _
var age:Int = _
def this(age:Int){
this()
this.age=age
}
def this(id:Int,name:String,age:Int){
this(age)
this.name=name
this.id=id
}
}
使用该种方式声明构造类似于Java编程,但是要求在构造的第一行必须显示调用this方法。
这本质上是因为如上声明的类,等价声明了
class User(){}
类似使用this()调用构造器。
类上声明(默认构造器)
一旦类上声明了参数,在使用this声明其他的构造器的时候,必须在第一行调用类上的构造器。
class User(var id:Int,var name:String,var age:Int) {
}
伴生对象
如果类和object在一个scala源文件中,则称为object User 是class User的伴生对象,使用伴生对象可以方便的创建类的对象,只需要覆盖对应的apply方法,如下:
class User(id:Int,name:String,age:Int) {
override def toString: String = {
this.id+" "+this.name+" "+this.age
}
}
object User{
def apply(id: Int, name: String, age: Int): User = new User(id, name, age)
}
var user=User(1,"zhangsan",18)//等价 new User(1,"zhangsan",18)
这里可以理解apply是一个工厂方法,该方法的作用是生产User实例对象。
使用unapply方法能够将对象中的一些属性反解出来
object User{
def apply(id: Int, name: String, age: Int): User = new User(id, name, age)
def unapply(user: User): Option[(Int, String, Int)] = {
Some(user.id,user.name,user.age)
}
}
var user=User(1,"zhangsan",18)
var User(id,name,age)=user
println(id+" "+name+" "+age)
注意:一个伴生对象中只能有一个unapply方法,这不同于apply方法,因为apply方法可以有多个。
抽象类
abstract class Animal(name:String) {
def eat():Unit={
println("animal can eat...")
}
def sleep():String
}
Trait(接口)
trait Speakable {
def speek():Unit
}
trait Flyable{
def fly():Unit
}
继承&实现
//写一个Dog类继承Animal并且实现Speakable特质
class Dog(name:String) extends Animal(name:String) with Speakable {
override def sleep(): String = {
"i'm a dog I sleep 8 hours"
}
override def speek(): Unit = {
println("wang wang ~~")
}
override def eat(): Unit = {
println("啃⻣头")
}
}
object Dog{
def apply(name: String): Dog = new Dog(name)
}
Trait动态植入
假如说现在有一个Bird继承自Animal,在使用的时候会发现该类需要具备Flyable的功能,可以写法如下:
class Bird(name:String) extends Animal(name :String) {
override def sleep(): String = {
"bird is sleeping"
}
}
var b=new Bird("麻雀") with Flyable{
override def fly(): Unit = {
println("小鸟会飞!")
}
}
b.fly()
①在覆盖有实现的方法必须添加
overwrite
;②一个类只能继承一个类with多个trait例如:Class A extends B with C with D{ }
self
等价于this关键字,在this出现混淆的时候使用self给this关键字起别名。
class User {
self =>
var id:Int = _
var name:String = _
var age:Int = _
def this( id:Int, name:String, age:Int){
this()
self.id=id
self.name=name
self.age=age
}
}
-
Trait强制混合
以下案例就是要求所有Flyable的子类必须实现Bird接口。
trait Flyable{
this:Bird => //可以将 该this看做成Bird类
def fly():Unit={
println(this.name+" 可以⻜!")
}
}
trait Bird{
def name:String
}
class FlyFish(n:String) extends Flyable with Bird {
override def name= this.n
}
Case class
case class就像常规类,case class适用于对不可变数据进行建模。
case class UserCase(id:Int,name:String) {
def sayHi(): Unit ={
println(this)
}
}
var uc1=new UserCase(1,"zhangsan")
var uc2=UserCase(1,"zhangsan")
与普通的class不同的是,Case Class创建的对象
==
比较的是对象的内容,其次CaseClass的所有属性都是只读,不允许修改。通常用于只读数据的建模。可以简单的使用copy来实现两个对象间的值得传递
var uc1=new UserCase(1,"zhangsan")
var uc2=uc1.copy(id=uc1.id,name=uc1.name)
uc2.sayHi()
注:case class之间不存在继承关系。
可见性
private - 私有的
- 修饰属性、方法
class Student02 {
private var id:Int=_
var name:String=_
def this(id:Int,name:String){
this()
this.id=id
this.name=name
}
private def sayHello(): String ={
s"hello $name"
}
override def toString: String = {
s"$id\t$name"
}
}
object Student02{
def main(args: Array[String]): Unit = {
val stu = new Student02(3,"zhaoliu")
stu.id=4
println(stu.sayHello())
println(stu)
}
}
该类内部和伴生对象可以访问,其他类均不可以访问
- 修饰扩展构造
class Student03 {
var id:Int=_
var name:String=_
private def this(id:Int,name:String){
this()
this.id=id
this.name=name
}
}
表示该扩展构造方法只能对伴生对象可见
- 修饰默认构造
private class Student03 {
var id:Int=_
var name:String=_
private def this(id:Int,name:String){
this()
this.id=id
this.name=name
}
}
仅仅只能在本类的内部,以及伴生对象内部使用,其他类不可见
protected
- 修饰属性、方法
class Student04 {
protected var id:Int=_
var name:String=_
def this(id:Int,name:String){
this()
this.id=id
this.name=name
}
protected def sayHello(): String ={
s"hello $name"
}
override def toString: String = {
s"$id\t$name"
}
}
针对 本类、以及伴生对象可见、子类可见
- 修饰扩展构造
class Student05 {
protected var id:Int=_
var name:String=_
protected def this(id:Int,name:String){
this()
this.id=id
this.name=name
}
}
对本类以及该类的伴生对象可见、子类可见
- 修饰默认构造
protected class Student05 {
protected var id:Int=_
var name:String=_
protected def this(id:Int,name:String){
this()
this.id=id
this.name=name
}
}
该类在其他类中可以使用,但是构造仅仅只对本类以及伴生对象可见。
this限定
可以和private
或者protected
联合使用,可以讲可见性缩小为本类的内部,去除伴生对象。
class Student06 {
private[this] var id:Int=_
var name:String=_
def this(id:Int,name:String){
this()
this.id=id
this.name=name
}
private[this] def sayHello(): String ={
s"hello $name"
}
override def toString: String = {
s"$id\t$name"
}
}
包限定
class Student05 {
private[demo12] var id:Int=10
}
class SmallStudent05 extends Student05 {
def sayHello():Unit={
println(s"${id}")
}
}
当使用包限定的时候,就打破了private|protected的语义,全部以包为准
final限定
-
修饰类:最终类,不可以被继承
-
修饰方法:不能被覆盖
-
修饰属性:只能修饰val属性,不可以修饰局部变量(scala已经提供了val表示常量),当final修饰成员变量,属性不可以被遮盖
scala中只用val的成员变量才可以被覆盖
final关键字跟Java中final关键字不同,这里只是为了让属性不可以被遮盖使用
class Animal {
final val name:String="动物"//必须是常量,才可以被子类覆盖,添加上final以后,允许遮盖
val age:Int=18
def eat():Unit={
}
}
class Parrot extends Animal {
override val age: Int = 20
override def eat(): Unit = super.eat()
}
object Parrot{
def main(args: Array[String]): Unit = {
val parrot = new Parrot
println(parrot.name)
println(parrot.age)
}
}
final和var连用没有任何意义。因为var不允许被遮盖;
sealed-密封
Trait
和class
可以标记为sealed
,这意味着必须在同一个Scala源文件中声明所有子类型这样就确保了所有的子类型都是已知的。
sealed trait Speekable {//必须保证此接口的实现都在同一个源文件中
def speek():Unit
}
class Xxx1 extends Speekable{
override def speek(): Unit = {
println("--------")
}
}
class Xxx2 extends Speekable{
override def speek(): Unit = {
println("--------")
}
}
lazy加载
Scala中使用关键字lazy
来定义惰性变量,实现延迟加载(懒加载)。 惰性变量只能是不可变变量,并且只有在调用惰性变量时,才会去实例化这个变量。
def getName():String={
println("----getName-----")
"zhangsan"
}
lazy val name:String=getName()
println(name)
函数对象(lambda表达式)
函数式接口
事实上在Java1.8中也引入了函数式接口,类似Scala中的函数式对象。例如在Java中定义一个函数式接口:
@FunctionalInterface
public interface GreetingService {
String sayHello(String name);
}
要求接口中只能有一个方法声明。只有函数式接口才可以使用lambda表达式做匿名类实现。
GreetingService gs=(String name) -> "hello "+ name;
String results = gs.sayHello("zhangsan");
System.out.println(results);
类似的这种写法在Java 8的集合stream编程中非常常见。例如:
List<String> lines= Arrays.asList("this is a demo","good good study","day dayup","come on baby");
lines.stream()
.flatMap(line->Arrays.asList(line.split(" ")).stream())
.forEach(word->System.out.print(word+" | "));
更多有关Java8 lambda信息请参考:https://howtodoinjava.com/java-8-tutorial
Scala这门语言在lambda编程的灵活性上是java无法媲美的。因为Scala在声明函数式对象
(等价java函数式接口)是非常轻松的。
例如以上案例的GreetingService如果使用Scala声明可以简写为如下:
val sayHello:(String)=>String = (name) => "hello ~"+name
println(sayHello("zhangsan"))
部分应用函数
在Scala中同样对所有的函数都可以理解为是一个函数对象
。例如在Scala中可以将任意一个函数转变成对象。
例如如下定义一个sum函数:
def sum(x:Int,y:Int):Int={
x+y
}
在Scala中可以尝试将一个函数转变成一个对象类型,如下:
scala> def sum(x:Int,y:Int):Int={
| x+y
| }
sum: (x: Int, y: Int)Int
scala> var sumFun = sum _
sumFun: (Int, Int) => Int = <function2>
scala> sumFun(1,2)
res0: Int = 3
通常将sumFun称为sum函数的部分应用函数
,不难看出sumFun事实上是一个变量。该变量的类型是(Int, Int) => Int
通常将该类型称为函数式对象
。事实上以上的(Int, Int) => Int
是Function2
的变体形式。因为Scala最多支持 Funtion0~22 种形式变体。
例如:
class SumFunction extends ((Int,Int)=>Int){
override def apply(v1: Int, v2: Int): Int = {
v1+v2
}
}
// 等价写法:
class SumFunction extends Function2[Int,Int,Int]{
override def apply(v1: Int, v2: Int): Int = {
v1+v2
}
}
scala> sumFun.isInstanceOf[(Int,Int)=>Int]
res2: Boolean = true
scala> sumFun.isInstanceOf[Function2[Int,Int,Int]]
res3: Boolean = true
isInstanceOf该方法等价于java中的instanceof关键字。用于判断类型。
PartitalFunction(偏函数)
偏函数主要适用于处理指定类型的参数数据,通常用于集合处理中。定义一个函数,而让它只接受和处理其参数定义域范围内的子集,对于这个参数范围外的参数则抛出异常,这样的函数就是偏函数(顾名思异就是这个函数只处理传入来的部分参数)。偏函数是个特质其的类型为PartialFunction[A,B],其中接收一个类型为A的参数,返回⼀个类型为B的结果。
通俗讲 就是只处理我想要的数据,其他的数据不要,返回一个新的数组集合,原数组值不变
scala> val pf1=new PartialFunction[Any,Int] {
| override def isDefinedAt(x: Any): Boolean = {
| x.isInstanceOf[Int]
| }
| override def apply(v1: Any): Int = {
| v1.asInstanceOf[Int]
| }
| }
pf1: PartialFunction[Any,Int] = <function1>
scala> val pf2:PartialFunction[Any,Int] = {case x:Int => x*2}
pf2: PartialFunction[Any,Int] = <function1>
scala> val a = Array(1,"a",2,"b",true,3)
a: Array[Any] = Array(1, a, 2, b, true, 3)
//for(i<-a;if(i.isInstanceOf[Int])) yield i
scala> a.collect(pf1)//偏函数变体写法
res0: Array[Int] = Array(1, 2, 3)
scala> a.collect(pf2)//偏函数变体写法
res1: Array[Int] = Array(2, 4, 6)
scala> a
res2: Array[Any] = Array(1, a, 2, b, true, 3)
隐式值注入/转换
隐式值
object CustomImplicits {
//声明隐式值
implicit val a:Int= 1
}
object TestImplicits {
def main(args: Array[String]): Unit = {
//引⼊隐式值
import CustomImplicits._
//获取上下⽂中的⼀个Int 隐式值,要求类型唯⼀
var b:Int= implicitly[Int]
print(s"b值:${b}")
}
}
implicit
声明隐式值,implicitly
获取隐式值
隐式注入
object CustomImplicits {
//声明隐式值
implicit val a:Int= 1
implicit val msg:String="哈喽"
}
object TestImplicits {
def main(args: Array[String]): Unit = {
//引入隐式值
import CustomImplicits._
sayHello("张三")
sayHello("lisi")("Hello")
}
//柯里化
def sayHello(name:String)(implicit msg:String):Unit={
println(s"${msg} ~ ${name}")
}
}
要求上下文中,必须有一个String类型隐式值,系统会自动注入
隐式转换(把不兼容改为兼容)
object CustomImplicits {
//声明隐式转换
implicit def s2d(s:String):Date={
println("-------转换了------")
val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
sdf.parse(s)
}
}
object TestImplicits {
def main(args: Array[String]): Unit = {
//引⼊隐式值
import CustomImplicits._
showMeTime(new Date())
// String --> Date
showMeTime("2018-12-13 10:30:00") //等价 showMeTime(s2d("2018-12-13 10:30:00"))
}
def showMeTime(date:Date):Unit={
println("北京时间:"+date.toLocaleString)
}
}
隐式增强(把不可能变成可能)
object CustomImplicits {
//声明隐式增强
implicit class PigImplicits(pig:Pig){
def fly():Unit={
println(s"${pig.name} 会⻜了 !")
}
def speek():Unit={
println(s"${pig.name} mu mu~")
}
}
object TestImplicits {
def main(args: Array[String]): Unit = {
//引⼊隐式值
import CustomImplicits._
val pig1 = new Pig("猪坚强") with Flyable{
override def fly(): Unit = {
println(s"${name} 会飞 ~")
}
}
pig1.eat()
pig1.sleep()
pig1.fly() //自己飞方法
pig1.speek()//隐式增强
val pig = new Pig("佩奇")
pig.eat()
pig.sleep()
pig.fly()//隐式增强
pig.speek()//隐式增强
}
异常处理
- Java:区分已检查(必须处理)和未检查(可选)异常、捕获有顺序限制,由小 => 大
public class JavaExceptions {
public static void main(String[] args) {
try {
throw new IOException("我自己抛出的~");
} catch (IOException e) { //由精准--> 模糊
e.printStackTrace();
}catch (ArithmeticException e){
e.printStackTrace();
}catch (Throwable e){
e.printStackTrace();
}finally {
System.out.println("最终执行");
}
}
}
- Scala:不区分已检查和未检查异常,捕获按照写case 进行匹配
object ScalaExceptions {
def main(args: Array[String]): Unit = {
try {
throw new IOException("我自己抛出的~")
}catch {
case e:ArithmeticException =>{
println("ArithmeticException")
e.printStackTrace()
}
case e: Throwable =>{
println("Throwable")
e.printStackTrace()
}
case e: IOException =>{
println("IOException")
e.printStackTrace()
}
} finally {
System.out.println("最终执行")
}
}
}
泛型
<:上边界限定
//只能饲养Dog或者Dog的⼦类 上边界限定
def keepDog[T <: Dog](t:T):Unit={
println(t)
}
def main(args: Array[String]): Unit = {
val animal = new Animal("原始动物")
val dog = new Dog("⼤⻩狗")
val smallDog = new SmallDog("⼩狗狗")
keepDog(dog)
keepDog(smallDog)
keepDog(animal)//错误
}
###>:下边界限定
//只能饲养Dog或者Dog的父类 下边界限定 bug
def keepAnimal[T >: Dog](t:T):Unit={
println(t)
}
def main(args: Array[String]): Unit = {
val animal = new Animal("原始动物")
val dog = new Dog("大黄狗")
val smallDog = new SmallDog("小狗狗")
keepAnimal(dog)
keepAnimal(animal)
keepAnimal(smallDog)//本不应该成功!这⾥我们理解为是个Bug
}
//只允许是Dog或者Dog的父类
trait Keeper[T >: Dog] {
def keep(t:T):Unit={
println(t)
}
}
def main(args: Array[String]): Unit = {
val animal = new Animal("原始动物")
val dog = new Dog("⼤⻩狗")
val smallDog = new SmallDog("⼩狗狗")
val k1= new Keeper[Dog] {
override def keep(t: Dog): Unit = {
println(t)
}
}
val k2= new Keeper[Animal] {
override def keep(t: Animal): Unit = {
println(t)
}
}
val k3= new Keeper[SmallDog] { //错误
override def keep(t: SmallDog): Unit = {
println(t)
}
}
}
这个泛型修饰在方法上的时候就会产生一个Bug,所以一般这个泛型会应用于接口中;
###<%视图限定
例如T <% U
,要求上下文必须有一个隐式转换
能够将T 转换为 U类型 有点像指鹿为马
class SmallDog(name:String) extends Dog (name:String){
override def speek(): Unit = {
println(s"$name ⼩狗叫~")
}
}
//可以将T看做是⼩狗
def keeperSmallDog[T <% SmallDog](t:T):Unit={
t.speek()
}
object MyImlipcits {
//定义⼀个String -> SmallDog转换
implicit def s2sd(name:String):SmallDog={
new SmallDog(name)
}
}
def main(args: Array[String]): Unit = {
keeperSmallDog(new SmallDog("⼩花花"))
import MyImlipcits._
keeperSmallDog("佩奇")
}
T:A
上下文绑定
表示上下文中环境中必须存在这种隐式值A[T]
隐式值,否则程序编译出错.这样可以在上下文中还没有隐式值得时候确保方法能编译成功。
class Student[T] {
def showMessage(msg:T):Unit={
msg match {
case name:String => println("name:"+name)
case age:Int => println("age:"+age)
case _ => println("不知道")
}
}
}
//上下⽂中 必须得有 Student[T] 类型隐式值
def sayInformation[T:Student](t:T):Unit={
val stu = implicitly[Student[T]]
stu.showMessage(t)
}
def main(args: Array[String]): Unit = {
import MyImlipcits._
sayInformation("zhangsan")
sayInformation(18)
sayInformation(true)
}
object MyImlipcits {
implicit val stu1=new Student[String]
implicit val stu2=new Student[Int]
}
+A协变
//管理T或者T的⼦类
trait Manager[+T] {}
//将子类的泛型引用赋值给父类
var m1=new Manager[Animal] {}
var m2=new Manager[Dog] {}
var m3=new Manager[SmallDog] {}
m1=m2
m2=m3
m2=m1//错误
-A逆变
//管理T或者T的⽗类
trait Manager[-T] {}
//将父类的泛型引用赋值给子类
var m1=new Manager[Animal] {}
var m2=new Manager[Dog] {}
var m3=new Manager[SmallDog] {}
m1=m2//错误
m2=m3//错误
m2=m1
m3=m1
A不变
//管理T或者T的⽗类
trait Manager[T] {}
var m1=new Manager[Animal] {}
var m2=new Manager[Dog] {}
var m3=new Manager[SmallDog] {}
m1=m2//错误
m2=m3//错误
m2=m1//错误
m3=m1//错误