面向对象编程
1 抽象
抽象类抽象方法
抽象就是不完整,有抽象类和抽象方法(只有声明没有实现)
abstract class User{
def test() : Unit
}
抽象类无法直接实例化,需要由子类继承后完成实例化,子类继承抽象类后可以声明为抽象类,也可以将父类的抽象方法补充完整
def main(args: Array[String]): Unit = {
var user = new child()
}
abstract class User{
def test() : Unit
}
class Child extends User{
def test(): Unit ={
}
scala中不完整的方法就是抽象的,所以不需要添加abstract关键字。
抽象属性
scala中属性也可以是抽象的,只有声明,没有初始化,底层实现就是抽象方法
abstract class User{
var name:String
}
class Child extends User{
var name:String = "zhangsan"
}
java中没有抽象属性,那么底层如何实现,反编译后的代码如下
编译时不会在类中声明属性,而是会声明属性的get,set方法,并且是抽象的
public abstract static class User {
public abstract String name();
public abstract void name_$eq(final String x$1);
}
编译时,子类实现抽象属性会在类中声明为私有属性,同时提供属性的get,set方法,并且是公共的
public static class child extends Scala09_Object_Abstract_1.User {
private String name = "zhangsan";
public String name() {
return this.name;
}
public void name_$eq(final String x$1) {
this.name = x$1;
}
}
子类重写父类方法
子类重写父类的抽象方法,直接补充完整即可,重写父类完整方法,需要添加override关键字,只是为了明确,并没有其他含义,一般推荐只要重写方法都添加abstract关键字
abstract class User{
def test(): Unit ={
}
def test1(): Unit
}
class Child extends User{
override def test(): Unit = {
}
override def test1(): Unit ={
}
}
子类重写父类属性
子类可以重写父类的抽象属性,补充完整即可,子类可以重写父类的完整属性,必须添加override关键字
abstract class User{
var name : String
var age : Int = 10
}
class Child extends User{
var name : String = "zhangsan"
override var age : Int = 20
}
注意var修饰的属性不可以被重写,需要修改为val
如果var能够重写,以下代码本意为将User中的age从10变为20,但是由于对属性的赋值age = 30
等同于调用属性的set方法,又因为有动态绑定机制,set方法为成员方法,会查找其实际内存(Child中的set方法),所最终实现的效果为将20变为了30,此为其一,对属性的访问println(age)
其实等同于调用属性的get方法,get方法又是成员方法,同理会取到Child的age,此为其二
所以想重写完整方法,为避免歧义,必须使用val修饰
def main(args: Array[String]): Unit = {
new Child().test()
}
abstract class User{
var name : String
var age : Int = 10
def test(): Unit ={
age = 30
println(age)
}
}
class Child extends User{
var name : String = "zhangsan"
override var age : Int = 20
}
2 特质
在java中
接口和当前类有关,和父子类没有任何的关系
public class TestInterface {
public static void main(String[] args) {
I i = new B();
System.out.println(i);
System.out.println(B.class.getInterfaces().length); //从B类中查看接口的数量 0
System.out.println(A.class.getInterfaces().length); // 1
}
}
interface I{
}
class A implements I{
}
class B extends A{
}
以下代码称为多态的传递
I i = new B();
I i = new A() //实现类代替接口出现
A a = new B() //子类代替父类对象出现
在scala中,将多个对象中相同的特征从对象中剥离出来,形成一个新的特征,称之为trait(特征)
如果一个对象符合这个特征,那么可以将这个特征加入到这个对象,加入的过程称为混入(extends)
如果一个类只有一个特征时,采用extends关键字进行混入
如果一个类有多个特征,第一个特征采用extends,其他的使用with
如果类存在父类,并同时具备某些特征需要使用extends关键字继承父类,使用with关键字混入特征
trait Eat{
def eat(): Unit
}
trait Runnable{
def run(): Unit
}
class Person extends Eat with Runnable {
override def eat(): Unit = {
println("Peat...")
}
override def run(): Unit = {
println("Prun...")
}
}
class Child extends Person with Eat with Runnable {
override def eat(): Unit = {
println("Ceat...")
}
}
动态混入
解耦合,增加扩展性,所谓耦合就是你的变化影响到了我
OCP原则(Open Close Principe)可以让程序进行扩展,但是不能修改以前的代码,scala的动态混入机制就提供了很大的帮助
def main(args: Array[String]): Unit = {
val user = new User() with UpdateUser
user.insertUser()
user.updateUser()
}
class User{
def insertUser(): Unit ={
println("insert user...")
}
}
trait UpdateUser{
def updateUser(): Unit ={
println("update user...")
}
}
trait原理
不能将trait简单的理解为接口,可以将trait理解为接口和抽象类的结合体
可以将下列代码重的trait理解为接口,一个类实现了一个接口
trait Test{
def test(): Unit
}
class User extends Test {
override def test(): Unit = {
}
以上代码反编译结果如下
public interface Test {
void test();
}
public static class User implements Scala09_Object_trait_2.Test {
public void test() {
}
}
java中的接口和类没有任何关系,更不能继承类,但是scala中的特征可以继承类
可以将以下代码中的trait理解为抽象类,一个类继承了抽象类,抽象类又继承了其他类
trait Test extends Exception{
def test(): Unit
}
class User extends Test {
override def test(): Unit = {
}
反编译结果
public interface Test {
void test();
}
public static class User extends Exception implements Scala09_Object_trait_2.Test {
public void test() {
}
}
那么,trait理解为抽象类,是一个类,那么它也可以有伴生对象
trait MyTrait {
}
object MyTrait{
}
初始化顺序
父类的特质 > 父类 > 特质1,特质2 > 当前类
trait Test extends Test2 {
println("a")
}
trait Test1{
println("d")
}
trait Test2{
println("e")
}
class Person{
println("b")
}
class User extends Person with Test with Test1 with Test2{
println("c")
}
分别从抽象类和接口的角度去理解,有多个特质,从左到右依次初始化,将Test2看做抽象类,将Test,Test1看做接口,接口先于子类初始化,输出beadc,初始化只会做一次
功能的叠加
java中不能多继承是因为存在钻石问题
钻石问题:当多个类实现了父类的方法,同名且功能不同,子类同时继承同一级别的多个父类时,调用哪个方法
scala使用了一种功能叠加的方式在当前环境下解决钻石问题
super不是父特质的意思,是上一级(个)的意思,只在编译时起作用,在运行时没有super
def main(args: Array[String]): Unit = {
new MySQL().OperData()
}
trait Oper{
def OperData(): Unit ={
println("操作数据")
}
}
trait DB extends Oper {
override def OperData(): Unit = {
print("向数据库中")
super.OperData()
}
}
trait Log extends Oper{
override def OperData(): Unit = {
print("向日志文件中")
super.OperData()
}
}
class MySQL extends DB with Log {
}
上述代码输出向日志文件中向数据库中操作数据
,如果不想让DB的OperData执行,可以在Log的super后面添加一些内容
trait Log extends Oper{
override def OperData(): Unit = {
print("向日志文件中")
super[Oper].OperData()
}
}
删除Log中的[Oper],添加
trait Other extends Oper{
override def OperData(): Unit = {
print("为了测试")
super[Oper].OperData()
}
}
会发现Log和DB都没有执行,可以观察到直接从当前(Other)直接调到Oper执行OperData()
3 扩展
通过反射修改字符串
不可变字符串中不可变指的是底层的char数组内存地址不可变
def main(args: Array[String]): Unit = {
val s = " a b "
//获取类信息
val unit = classOf[String]
//获取field域
val field = unit.getDeclaredField("value")
//设置访问权限
field.setAccessible(true)
//获取字符串
val obj = field.get(s)
//转换类型
val chars = obj.asInstanceOf[Array[Char]]
//修改
chars(2) = 'C'
print(s)
}
枚举类和应用类
// 枚举类
object Color extends Enumeration {
val RED = Value(1, "red")
val YELLOW = Value(2, "yellow")
val BLUE = Value(3, "blue")
}
以下代码输出bca,详情见类的初始化
object Scala11_Object_App {
println("bbb")
def main(args: Array[String]): Unit = {
println("aaa")
}
println("ccc")
}
为避免歧义,见以下代码
// 应用类
object AppTest extends App {
println("aaa")
println("bbb")
println("ccc")
}
顺序执行
Type定义新类型
使用type关键字可以定义新的数据数据类型名称,本质上就是类型的一个别名
type JavaKVMap = java.util.HashMap[String,String]
private val map: JavaKVMap = new JavaKVMap()