面向对象编程三大特征
面向对象编程有三大特征:封装、继承和多态。
封装介绍
封装(encapsulation)就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作。
面向对象编程三大特征
基本介绍
面向对象编程有三大特征:封装、继承和多态。
封装介绍
封装(encapsulation)就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作。
封装的理解和好处
- 隐藏实现细节
- 提可以对数据进行验证,保证安全合理
如何体现封装
- 对类中的属性进行封装
- 通过成员方法,包实现封装
封装的实现步骤
1.将属性进行私有化
2.提供一个公共的set方法,用于对属性判断并赋值
def setXxx(参数名 : 类型) : Unit = {
//加入数据验证的业务逻辑
属性 = 参数名 }
3.提供一个公共的get方法,用于获取属性的值
def getXxx() [: 返回类型] = { return 属性 }
案例:
不能随便查看人的年龄等隐私,并对输入的年龄进行合理的验证[要求1-120之间]。
class Person { //var age ; //当是public时,可以随意的进行修改,不安全 private var age: Int = _ def setAge(age: Int): Unit = { if (age >= 0 && age <= 120) { this.age = age } else { println("输入的数据不合理"); //可考虑给一个默认值 this.age = 20 } } } object DemoTest{ def main(args: Array[String]): Unit = { var p=new Person p.setAge(150) } }
Scala封装的注意事项和细节
- Scala中为了简化代码的开发,当声明属性时,本身就自动提供了对应setter/getter方法,如果属性声明为private的,那么自动生成的setter/getter方法也是private的,如果属性省略访问权限修饰符,那么自动生成的setter/getter方法是public的。
- 如果只是对一个属性进行简单的set和get ,只要声明一下该属性(属性使用默认访问修饰符) 不用写专门的getset,默认会创建,访问时,直接对象.变量。这样也是为了保持访问一致性。
- 从形式上看通过 "对象名.属性名" 直接访问属性,通过反编译的代码可知其实底层仍然是访问的方法
- 有了上面的特性,目前很多新的框架,在进行反射时,也支持对属性的直接反射
面向对象编程-继承
Java继承的简单回顾
class 子类名 extends 父类名 { 类体 }
- 子类继承父类的属性和方法
继承基本介绍和示意图
继承可以解决代码复用,当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类(比如Student),在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends语句来声明继承父类即可。
Java与Scala类的类继承都是单继承,据说是jvm的原因(我会回头看看补充)
Scala继承的基本语法
class 子类名 extends 父类名 { 类体 }
案例:
class Student extends Person { override var age: Int = 10 def studying(): Unit = { println(this.name + "学习 scala中....") } } class Person { var name: String = _ var age: Int = _ def showInfo(): Unit = { println("名字:" + this.name) } } object TestMain { def main(args: Array[String]): Unit = { } }
Scala继承的便利
提高代码的扩展性和维护性--当我们修改父类时,对应的子类就会继承相应的方法和属性
如何继承?子类继承了所有的属性方法,只是私有的属性方法不能直接访问,需要通过公共的方法去访问私有方法和属性。
class Base { private val n3: Int = 3 var n1: Int = 1 protected var n2: Int = 2 def get_n3(): Int = { return n3 } } class Sub extends Base { var n1_1: Int =this.n1 var n2_1: Int =this.n2 var n3_1: Int =this.get_n3() }
重写方法和属性
scala明确规定,
- 重写一个非抽象方法需要用override修饰符,调用超类的方法使用super关键字
- 可以重写父类val属性,也需要加override,var属性无法重写。
class Person { var name : String = "wqbin" val age : Int = 0 def printName() { println(s"Person is name:$name age:$age") } } class Student extends Person { //这里需要显式的使用override override val age : Int = 25 //这里需要显式的使用override override def printName() { println(s"Student is name:$name age:$age" ) super.printName() } } object TestMain{ def main(args: Array[String]): Unit = { var st=new Student st.printName() } }
为什么不能重写var 属性?
或者说你想重写,没必要在在子类重新定义和override,因为引用是var的,所以重新赋值就好。
class Student extends Person {
name="wang"
//这里需要显式的使用override
override val age : Int = 25
//这里需要显式的使用override
override def printName() {
println(s"Student is name:$name age:$age" )
}
}
object TestMain{
def main(args: Array[String]): Unit = {
val p=new Person()
val st:Person=new Student().asInstanceOf[Person]
println(st.getClass)
st.printName()
p.printName()
}
}
抽象属性
抽象属性:声明未初始化的变量就是抽象的属性,抽象属性在抽象类
var重写抽象的var属性小结
一个属性没有初始化,那么这个属性就是抽象属性
抽象属性在编译成字节码文件时,属性并不会声明,但是会自动生成抽象方法,所以类必须声明为抽象类
如果是覆写一个父类的抽象属性·,那么override 关键字可省略 [原因:父类的抽象属性,生成的是抽象方法,因此就不涉及到方法重写的概念,因此override可省略]
override字段或方法的注意事项和细节
- def只能重写父类的def(即:方法只能重写父类方法,不能重写属性)
- val不能重写var,只能重写一个val 属性 或 重写不带参的def
- var只能重写父类抽象的var属性,不能重写父类同名方法以及实var。【后面讲】
class Person { var name : String = "wqbin" val sex="male" def getName(i:String):String={ println("getName() in Person") "wqbin"+i } def getAge:Int={ println("getName() in getAge") 20 } } class Student extends Person { name="wang" override val sex="female" // override val getName="wang" //报错因为父类方法getName带带参数 // override var getAge=100 //报错,var只能重写抽象var override val getAge =25 } object TestMain{ def main(args: Array[String]): Unit = { val st=new Student println(st.getAge) } }
在Java中用隐藏字段代替了重写
回顾:在Java中只有方法的重写,没有属性/字段的重写,准确的讲,是隐藏字段代替了重写。
先让我们看一下下面这个例子,新建两个Sub对象,一个指向Sub类型的引用,一个指向 Super类型的引用。
我们创建了两个Sub对象,但是为什么第二个对象打印出来的结果是"Super"呢?
在java官方提供的tutorial有一段关于隐藏字段的明确定义:
Within a class, a field that has the same name as a field in the superclass hides the superclass’s field, even if their types are different.
Within the subclass, the field in the superclass cannot be referenced by its simple name. Instead, the field must be accessed through super.
Generally speaking, we don’t recommend hiding fields as it makes code difficult to read.
从上面这段解释中,我们可以看出成员变量不能像方法一样被重写。当一个子类定义了一个跟父类相同 名字的字段,子类就是定义了一个新的字段。这个字段在父类中被隐藏的,是不可重写的。
如何访问隐藏字段?
- 采用父类的引用类型,这样隐藏的字段就能被访问了,像上面所给出的例子一样。
- 将子类强制类型转化为父类类型,也能访问到隐藏的字段。
java隐藏字段小结:
这个主要涉及到java里面一个字段隐藏的概念,父类和子类定义了一个同名的字段,不会报错。
但对于同一个对象,用父类的引用去取值(字段),会取到父类的字段的值,用子类的引用去取值(字段),则取到子类字段的值。
在实际的开发中,要尽量避免子类和父类使用相同的字段名,否则很容易引入一些不容易发现的bug。
【这里就是动态绑定,下面我们会有专门的文章会讲】
Scala中类型检查和转换
基本介绍
要测试某个对象是否属于某个给定的类,可以用isInstanceOf方法。用asInstanceOf方法将引用转换为子类的引用。classOf获取对象的类名。
- 对象名.getClass 获取该对象的数据类型。
- classOf[String]就如同Java的 String.class 。
- obj.isInstanceOf[T]就如同Java的obj instanceof T 判断obj是不是T类型。
- obj.asInstanceOf[T]就如同Java的(T)obj 将obj强转成T类型。
类型检查和转换的最大价值在于:可以判断传入对象的类型,然后转成对应的子类对象,进行相关操作,这里也体现出多态的特点。
父类类类型的可以引用子类对象。
object TestMain{ def main(args: Array[String]): Unit = { // var p=new Person() val st:Person=new Student().asInstanceOf[Person] println(st.getClass) st.printName() } }
回顾java中的动态绑定
package com.xgo; class A { public int i = 100; public int sum1() { return getI() + 10; } public int sum2() { return i + 20; } public int getI() { return i; } } class B extends A { public int i = 10; public int sum1() { return i + 1; } public int getI() { return i; } public int sum2() { return i + 2; } } public class DynBind{ public static void main(String[] args) { A a = new B(); System.out.println("a.i===>"+a.i); System.out.println("a.sum1()===>"+a.sum1()); //? System.out.println("a.sum2()===>"+a.sum2()); //? } }
scala的抽象类
基本介绍
在Scala中,通过abstract关键字标记不能被实例化的类。方法不用标记abstract,只要省掉方法体即可。抽象类可以拥有抽象字段,抽象字段/属性就是没有初始值的字段。
抽象类基本语法
abstract class 抽象类名 { // 抽象类 var/val 抽象属性1: String // 抽象字段, 没有初始化 def 抽象方法1 // 抽象方法, 没有方法体 var/val 实现属性1... def 实现方法... }
案例:
abstract class Person { val sex:String val birth:Int=1994 var age:Int var name:String="wqbin" def eat() def say(content :String) def cry(): Unit ={ print("cry in abstract class Person ") } }
反编译:
public abstract class Person { //val public abstract String sex(); private final int birth = 1994; public int birth(){return this.birth; } //var public abstract int age(); public abstract void age_$eq(int paramInt); private String name = "wqbin"; public String name(){return this.name;} public void name_$eq(String x$1){this.name = x$1;} //def public abstract void eat(); public abstract void say(String paramString); public void cry(){Predef..MODULE$.print("cry in abstract class Person ");} }
Scala抽象类使用的注意事项和细节讨论
- 抽象类不能被实例
- 抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法
- 一旦类包含了抽象方法或者抽象属性,则这个类必须声明为abstract
- 抽象方法不能有主体,不允许使用abstract修饰。
- 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法和抽象属性,除非它自己也声明为abstract类。
- 抽象方法和抽象属性不能使用private、final 来修饰,因为这些关键字都是和重写/实现相违背的。
- 抽象类中可以有实现的方法.
- 子类重写抽象方法与抽象属性(var/val)不需要override,写上也不会错.
abstract class Person { val sex:String val birth:Int=1994 var age:Int var name:String="wqbin" def eat() def say(content :String) def cry(): Unit ={ print("cry in abstract class Person ") } } class Student extends Person{ override val sex: String = "male" //子类重写抽象方法/属性不需要override,写上也不会错. var age: Int = 25 def eat(): Unit = { println("Student eat...") } override def say(content: String): Unit = { println("Student "+content) } }
反编译:
public class Student extends Person { private final String sex = "male"; public String sex(){return this.sex;} private int age = 25; public int age(){return this.age;} public void age_$eq(int x$1){this.age = x$1;} public void eat(){Predef..MODULE$.println("Student eat...");} public void say(String content){Predef..MODULE$.println(new StringBuilder().append("Student ").append(content).toString());} }
抽象类重写注意事项:
- 抽象属性在编译成字节码文件时,属性并不会声明,但是会自动生成抽象方法,所以类必须声明为抽象类
- 如果是覆写一个父类的抽象属性,那么override 关键字可省略。
- 抽象属性val不能重写成var,其他的都可以
匿名子类
基本介绍
和Java一样,可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类.
java匿名子类:
abstract class Anom{ abstract public void f1(); } A1 obj = new Anom() { @Override public void f1() { System.out.println("OK!"); } };
scala匿名子类:
abstract class Anom{ var name : String def f1() } var monster = new Anom { override var name: String = _ override def cry(): Unit = { System.out.println("OK!"); } }
附注:scala继承层级
- subtype : 子类型
- implicit Conversion 隐式转换
- class hierarchy : 类层次
继承层级图小结
- 在scala中,所有其他类都是AnyRef的子类,类似Java的Object。
- AnyVal和AnyRef都扩展自Any类。Any类是根节点
- Any中定义了isInstanceOf、asInstanceOf方法,以及哈希方法等。
- Null类型的唯一实例就是null对象。可以将null赋值给任何引用,但不能赋值给值类型的变量
- Nothing类型没有实例。它对于泛型结构是有用处的,举例:空列表Nil的类型是List[Nothing],它是List[T]的子类型,T可以是任何类。