scala之面向对象
1. 要点
类:
-
类中的字段自动带有getter方法和setter方法.
-
用
@BeanProperty
注解来生成JavaBean的getXxx
/setXxx
方法 -
主构造器, 主构造器的参数直接构成类的字段
-
辅构造器, 需要提前调用主构造器
this()
-
权限修饰符
-
protected
: 修饰类的成员, 只能在子父类中访问 -
private[package]
: 在package
包和它的子包中可以访问.
-
-
抽象类: 相比
java
多了抽象字段的概念 -
类型的判断和转换
继承:
-
关键字
extends
-
覆写方法时必须使用
override
关键字, 并满足两同两小一大 -
属性的覆写,可以覆写属性, 多态中属性的引用也是使用子类对象的
-
var
只能覆写抽象的var, 有定义的var不能覆写 -
val
可以覆写val
和没有参数def
-
-
只有主构造器才可以调用父类构造器
多态:
- 在
Scala
中, 方法有多态, 属性也有多态!
特质:
- 特质叠加冲突2种解决方式
- 在子类中覆写该方法
- 新增父类, 使用菱形继承关系
对象:
-
用对象存作为单例或存放工具方法
-
类可以拥有一个同名的伴生对象
- 可以互相访问私有成员
- 编译后, 伴生对象中的都为静态成员, 伴生类中的成员都为非静态成员.
-
apply
: scala中任何对象都可以像函数一样去调用 -
对象可以扩展或特质
- 除显式定义main方法外, 可以用扩展App特质的对象
- 扩展
Enumeration
对象来实现枚举
2. 面向对象概念
面向对象就是通过封装, 继承, 多态(面向对象的三大特征)把程序的耦合性降低, 提高程序的可维护性, 可扩展性, 可复用性.
3. 类
定义类
class Demo
属性的默认初始化值
- 数字 0
- 布尔值 false
- 引用型 null
主构造器
类和java一样, 默认有空构造器, 但是一旦有定义构造器, 则不会再默认提供空构造器.
主构造器位置紧跟类名
class User(var name: String, val age: Int)
可以增加关键字private
将构造器私有化
class private User(var name: String, val age: Int)
给类定义的所有的属性都是私有. 并且会给这些私有属性自动添加公共的getter
或setter
, 字节码文件反编译如下:
public class com.demo.scala.User {
private java.lang.String name; // 属性私有
private final int age; // 属性私有
public java.lang.String name(); // 自动添加name属性的getter
public void name_$eq(java.lang.String); // 自动添加name属性的setter
public int age(); // getter
}
用@BeanProperty
注解可以生成JavaBean的getXxx
/setXxx
方法
class User2(@BeanProperty var name: String, @BeanProperty val age: Int)
主构造器的3种参数:
-
class demo(var name: String)
:name
自动成为类的私有属性, 并自动生成setter()
与getter()
方法 -
class demo(val name: String)
: 生成getter()
方法, 但不生成setter()
方法 -
class demo(name: String)
:不生成getter()
方法, 也不生成setter()
方法
构造器重载: 辅助构造器
辅助构造器与主构造器构成重载关系
辅助构造器的首行必须调用主构造器
辅助构造函数的参数, 仅仅是一个普通的参数, 不会成为类的属性
注意: 辅助构造器只能调用主构造, 父类的构造器由主构造器调用
class Demo(name: String){
// 定义一个无参辅助构造器
def this() = {
// 首行必须调用主构造器
this("lisi")
}
// 定义一个无参辅助构造器
def this(age: Int) = {
this("lisi")
this.age = age
}
}
scala
的权限修饰符
-
用在外部类上:
-
默认(
public
, 没有public
关键字)class A
-
private
只能在当前表使用, 其他地方无法使用
-
-
用在类的内部成员(属性, 方法, 内部类)上:
-
默认(
public
, 没有public
关键字) -
protected
这个限制更加严格. 只能在子父类中访问, 即使在同包中也不能访问
super.foo();
-
private
只能在当前类中访问
scala
中做了一些改造, 精细化的控制, 可以指定访问的包和它的子包private[mode] def speak() = println("speak...")
在
mode
包和它的子包中可以访问.
-
抽象类
含有抽象方法或者抽象字段的类为抽象类
abstract class A{
def f():Unit = {
println("f.....")
}
//抽象方法
def eat(): Unit
//抽象字段
var age: Int
val sex: String
}
注意: 相比java
多了抽象字段的概念
类型的判断和转换
有时候需要在使用多态后, 需要进行类型转换, 需要先判断类型, 然后再向下转型.
a.isInstanceOf[B]
: 判断a是否为B的对象
a.asInstanceOf[B]
: a转换B的类型
object Extra1 {
def main(args: Array[String]): Unit = {
val a:A = new B
// java中判断类型: a instanceof B
if (a.isInstanceOf[B]) { // 判断a是否为B的对象
val b = a.asInstanceOf[B] // a转换B的类型
b.foo()
}
}
}
class A
class B extends A{
def foo() = println("foo...")
}
4. 继承
继承和java一样, 是扩展类的方式
面向对象的3大特征: 封装, 继承(单继承), 多态
继承的基本语法
class A
class B extends A
继承的时候构造的处理
- 在子类的辅构造器中, 必须先调用自己的主构造, 不能主动去调用父类的构造器.
- 只有主构造器才有权力去调用父类的构造器!!!
方法的覆写
java
中@Overreide
注解可以省略. scala
中方法覆写需要关键字override
, 且不可以省略.
覆写的原则
两同, 两小, 一大
-
两同: 方法名相同, 参数列表相同
-
两小: 返回值, 异常类型小(相同或为子类)
-
一大: 权限相同或更大
java中的权限: public, protected(同包或子类), friendly(同包中访问), private
scala中的权限: 默认(public, 但没该关键字), protected(子类), private
属性的覆写
在scala
中, 属性也可以覆写, 也具有多态!!!
属性覆写的规则:
-
var
只能覆写抽象的var, 有定义的var
不能覆写 -
val
可以覆写val
和没有参数def
5. 多态
scala
中属性也有多态, 多态中属性的引用也是使用子类对象的属性.
6. 特质trait
trait
的本质: 字节码之后, 还是接口,
trait
是支持多混入
class A extends t1 with t2 with t3...
注意: trait
的特性要比接口多, 但最常用的方式还是作为接口来使用.
叠加冲突
多个trait
有相同实现好的方法的时,会产生冲突
- 解决方案1: 在子类中把冲突的方法覆写
-
增一个
父trait
, 成菱形关系, 最终使用最后叠加的那个!!!- 初始化的是, 一个
trait
最多初始化一次 - 初始化的时候是从父开始, 然后从左到右
-
super.foo()
不是真正的找父类, 而是按照叠加的顺序向前找 -
super[T12].foo()
明确指定这个super
应该是哪个类
- 初始化的是, 一个
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v4M0s78770///E:\Images\markdown\1587905679513.png)]
.png)]
- 新增一个
父trait
, 成菱形关系, 最终使用最后叠加的那个!!!- 初始化的是, 一个
trait
最多初始化一次 - 初始化的时候是从父开始, 然后从左到右
-
super.foo()
不是真正的找父类, 而是按照叠加的顺序向前找 -
super[T12].foo()
明确指定这个super
应该是哪个类
- 初始化的是, 一个
特质继承类
-
方法1
class A{ def foo() = { println("A... foo") } } trait B extends A{ def eat() = { println("B ... eat") foo() } } // 但继承: extends 要么是A要么A的子类 class C extends A with B
-
使用自身类型(
selftype
)trait B{ // s就是A类型的对象 s: A => def eat()= { println("B ... eat") s.foo() } }
动态叠加
在java
中所有的继承关系都应该在定义类是确定好.
scala
支持特质的动态叠加. 在创建对象的时候, 可以临时只针对整个对象来叠加特质
object Trait5 {
def main(args: Array[String]): Unit = {
val h = new H with F1
h.foo()
}
}
class H
trait F1 {
def foo() = println("f1 foo...")
}
特质的字节码文件
Scala将特质编译成JVM的类和接口. 如果特质有具体方法, Scala会创建出一个伴生类, 该伴生类用静态方法存放特质的方法.
例如:
trait ConsoleLogger extends Logger {
def log(msg: String) { println(msg)}
}
编译后
public interface ConsoleLogger extends Logger { // 生成java接口
void log(String msg);
}
public class ConsoleLogger$class { // 生成java伴生类
public static void log(ConsoleLogger self, String msg)
println(msg);
}
7. 对象
单例对象
object 对象名{
def main(args: Array[String]){
}
}
java中的单例有两种, 饿汉式, 懒汉式, 涉及到多线程还需要双重判断
伴生类和伴生对象
一个类可以有一个和其同名的对象, 该对象就称为该类的伴生对象
-
class
的名字和object
的名字相同 - 可以互相访问对方的私有成员
- 伴生类和伴生对象必须在同一个
.scala
文件中 - 将来编译成字节码之后, 站在
java
的角度, 伴生对象中的都是为成为静态成员, 伴生类中的成员都会成为非静态成员.
apply
函数的特点是可以直接调用, 在scala
中, 任何对象也可以像函数一样去调用
// 函数调用:
函数名(参数)
// 对象调用
对象名(参数)
注意:
-
其实函数也可以通过apply进行调用. (方法不行), 如果是方法, 先把方法转成函数在使用.
在Scala语言中, 函数也是对象, 每一个对象都是scala.FunctionN(1-22)的实例
-
伴生对象的
apply
, 通常情况是返回***伴生类的对象***, 然后在外面创建对象的时候, 可以省略new
-
普通类中的
apply
, 一般根据具体的业务逻辑来实现. -
apply
也可以重载
8. 枚举
继承Enumeration
示例:
object Test {
def main(args: Array[String]): Unit = {
println(Color.RED)
}
}
// 枚举类
object Color extends Enumeration {
val RED = Value(1, "red")
val YELLOW = Value(2, "yellow")
val BLUE = Value(3, "blue")
}
9. 内部类
类型投影: Outer#Inner
def foo(obj: Outer#Inner)
示例:
两种方式在内部类中调用外部类成员:
- 通过Outer.this
- 通过自身类型that =>
object InnerDemo1 {
def main(args: Array[String]): Unit = {
val outer1 = new Outer("outer1")
val inner1 = new outer1.Inner
val outer2 = new Outer("outer2")
val inner2 = new outer2.Inner
inner1.foo(inner1)
inner1.foo(inner2)
}
}
class Outer(val outerName: String) {
// 自身类型
that =>
val a = 20
class Inner {
val a = 10
def foo(obj: Outer#Inner) = {
println("Inner ... foo")
println("inner = " + a)
println("inner = " + this.a)
// 外部类的对象
println("outer = " + Outer.this.a) //通过Outer.this, 调用外部类对象成员方法一
println("outerName = " + that.outerName) //通过自身类型that =>, 调用外部类对象成员方法方法二
}
}
}
输出
Inner ... foo
inner = 10
inner = 10
outer = 20
outerName = outer1
Inner ... foo
inner = 10
inner = 10
outer = 20
outerName = outer1
上一篇: Java之面向对象
下一篇: Spring的IOC和AOP的基本使用