Scala之面向对象编程
文章目录
1. 类与对象
定义类
scala语法中,类并不声明为public,所有这些类都具有公有可见性 (即默认就是public),[修饰符在后面再详解].
一个Scala源文件可以包含多个类.
属性/成员变量
属性的定义语法同变量,示例:[访问修饰符] var 属性名称 [:类型] = 属性值
属性的定义类型可以为任意类型,包含值类型或引用类型
Scala中声明一个属性,必须显示的初始化 ,然后根据初始化数据的类型自动推断,属性类型可以省略(这点和Java不同)。
如果赋值为null,则一定要加类型,因为不加类型, 那么该属性的类型就是Null类型
1 2 3 4 5 6 class Person { var age : Int = 10 var sal = 8090.9 var Name = null var address : String = null }
如果在定义属性时,如果用var修饰,则可暂时不赋值,也可以使用符号_
下划线),让系统分配默认值。val类型的成员变量,必须要自己手动初始化,不能使用符号_
(下划线)
1 2 3 4 5 6 7 8 class Person { var age:Int =_ var name:String =_ } class Person1 ( ) { val name: String ="小明" val age: Int =10 }
java中的boolean初始值也是false
不同对象的属性是独立,互不影响,一个对象对属性的更改,不影响另外一个。
属性的高级部分和构造器(构造方法/函数)相关,我们把属性高级部分放到构造器那里讲解。
如何创建对象
1 val | var 对象名 [:类型] = new 类型()
如果我们不希望改变对象的引用(即:内存地址), 应该声明为val 性质的,否则声明为var, scala设计者推荐使用val ,因为一般来说,在程序中,我们只是改变对象属性的值,而不是改变对象的引用。
scala在声明对象变量时,可以根据创建对象的类型自动推断,所以类型声明可以省略,但当类型和后面new对象类型有继承关系即多态时 ,就必须写了
访问属性 :对象名.属性名;
方法
Scala中的方法其实就是函数,声明规则请参考函数式编程中的函数声明。
1 2 3 def 方法名(参数列表) [:返回值类型] = { 方法体 }
当我们scala开始执行时,先在栈区开辟一个main栈。main栈是最后被销毁
当scala程序在执行到一个方法时,总会开一个新的栈。
每个栈是独立的空间,变量(基本数据类型)是独立的,相互不影响(引用类型除外)
当方法执行完毕后,该方法开辟的栈就会被jvm机回收。
2. 构造器
Java构造器基本语法
1 2 3 [修饰符] 方法名(参数列表){ 构造方法体 }
在Java中一个类可以定义多个不同的构造方法,构造方法重载
如果程序员没有定义构造方法,系统会自动给类生成一个默认无参构造方法(也叫默认构造器),比如 Person (){}
一旦定义了自己的构造方法(构造器),默认的构造方法就覆盖了,就不能再使用默认的无参构造方法,除非显示的定义一下,即: Person(){};
Scala构造器
和Java一样,Scala构造对象也需要调用构造方法,并且可以有任意多个构造方法(即scala中构造器也支持重载)。
Scala类的构造器包括: 主构造器 和 辅助构造器
基本语法
1 2 3 4 5 6 7 class 类名(形参列表) { // 主构造器 // 类体 def this(形参列表) { // 辅助构造器 } def this(形参列表) { //辅助构造器可以有多个... } }
辅助构造器可以有多个,编译器通过不同参数来区分
Scala构造器注意事项和细节
Scala构造器作用是完成对新对象的初始化,构造器没有返回值。
主构造器的声明直接放置于类名之后 [反编译]
主构造器会执行类定义中的所有语句,这里可以体会到Scala的函数式编程和面向对象编程融合在一起,即:构造器也是方法 (函数),传递参数和使用方法和前面的函数部分内容没有区别
1 2 3 4 5 6 7 8 9 def main (args: Array [String ]): Unit = { val ab=new AB } class AB { var cd=3 if (cd==3 ) println("ABABAB" ) }
如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略
1 2 3 4 5 class AA {} var a1=new AA var a2=new AA ()
辅助构造器名称为this(这个和Java是不一样的),多个辅助构造器通过不同参数列表进行区分, 在底层就是构造器重载 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 def main (args: Array [String ]): Unit = { val p=new Person ("小明" ) } class Person ( ) { var name: String = _ var age: Int = _ def this (name: String ) { this () this .name = name } def this (name: String , age: Int ) { this () this .name = name this .age = age } def this (age: Int ) { this ("匿名" ) this .age = age } }
如果想让主构造器变成私有的,可以在()之前加上private,这样用户只能通过辅助构造器来构造对象了【反编译查看】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 object Test6 { def main (args: Array [String ]): Unit = { val p=new Person ("wxl" ) } } class Person private ( ) { var name: String = _ var age: Int = _ def this (name: String ) { this () this .name = name } }
辅助构造器的声明不能和主构造器的声明一致 ,会发生错误(即构造器名重复)
3. 属性高级
(1)Scala类的主构造器的形参未用任何修饰符修饰,那么这个参数是局部变量
scala示例:
1 2 3 4 5 6 7 8 9 10 class Worker1 (abc: String ) {a var name = abc var age:Int =_ def this (inName: String , age: Int ) { this (inName) var t=abc this .age = age }
scala反编译如下,可以看到,abc这个主构造器的参数没有类似于get的方法,因此外界不能访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class Worker1 { private final String abc; private String name; private int age; public Worker1 (String abc) { this .name = abc; } public String name () { return this .name; } public void name_$eq(String x$1 ) { this .name = x$1 ; } public int age () { return this .age; } public void age_$eq(int x$1 ) { this .age = x$1 ; } public Worker1 (String inName, int age) { this (inName); String t = this .abc; age_$eq(age); } }
(2)Scala类的主构造器的形参用var修饰,那么该参数是这个类的成员变量,且提供了get和set功能,外界可读可写
1 2 3 4 5 6 7 8 9 10 11 class Worker2 (var abc: String ) { var name = abc var age:Int =_ def this (inName: String , age: Int ) { this (inName) var t=abc this .age = age } }
scala反编译为java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class Worker2 { private String abc; public String abc () { return this .abc; } public void abc_$eq(String x$1 ) { this .abc = x$1 ; } private String name = abc(); private int age; public String name () { return this .name; } public void name_$eq(String x$1 ) { this .name = x$1 ; } public int age () { return this .age; } public void age_$eq(int x$1 ) { this .age = x$1 ; } public Worker2 (String abc) {} public Worker2 (String inName, int age) { this (inName); String t = abc(); age_$eq(age); } }
(3)Scala类的主构造器的形参用val修饰,那么该参数是这个类的成员变量,且只提供了get,外界只能读
1 2 3 4 5 6 7 8 9 10 11 class Worker3 (val abc: String ) { var name = abc var age:Int =_ def this (inName: String , age: Int ) { this (inName) var t=abc this .age = age } }
scala反编译为java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class Worker3 { private final String abc; private String name; private int age; public String abc () { return this .abc; } public Worker3 (String abc) { this .name = abc; } public String name () { return this .name; } public void name_$eq(String x$1 ) { this .name = x$1 ; } public int age () { return this .age; } public void age_$eq(int x$1 ) { this .age = x$1 ; } public Worker3 (String inName, int age) { this (inName); String t = abc(); age_$eq(age); } }
(4)如果类的成员变量使用val关键字声明,则该属性必须手动初始化,不能用下划线初始化 ,并且类中默认只提供了get方法,所以只能读不能写【案例+反编译】
1 2 3 4 class Person1 ( ) { val name: String ="小明" val age: Int =10 }
scala反编译成java代码,可以看到,没有提供set方法
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Person1 { private final String name = "; public String name() { return this.name; } private final int age = 10; public int age() { return this.age; } }
(5) 如果类的成员变量使用var关键字声明,则该可以用下划线初始化,并且类中默认提供了get和set方法,所以可读可写
scala语言
1 2 3 4 class Person ( ) { var name: String = _ var age: Int = _ }
scala反编译为java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Person { private String name; private int age; public String name() { return this .name; } public void name_$eq(String x$1 ) { this .name = x$1 ; } public int age() { return this .age; } public void age_$eq(int x$1 ) { this .age = x$1 ; }
(6)JavaBeans规范定义了Java的属性是像getXxx()和setXxx()的方法。许多Java工具(框架)都依赖这个命名习惯。为了Java的互操作性。将Scala字段加@BeanProperty
时,这样会自动生成规范的 setXxx/getXxx 方法。这时可以使用 对象.setXxx() 和 对象.getXxx() 来调用属性。
scala示例
1 2 3 4 class Worker4 (val abc: String ) { var name = abc @BeanProperty var age:Int =_ }
反编译为java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class Worker4 { private final String abc; private String name; private int age; public String abc () { return this .abc; } public Worker4 (String abc) { this .name = abc; } public String name () { return this .name; } public void name_$eq(String x$1 ) { this .name = x$1 ; } public int age () { return this .age; } public void age_$eq(int x$1 ) { this .age = x$1 ; } public int getAge () { return age(); } public void setAge (int x$1 ) { age_$eq(x$1 ); } }
4. 对象创建的流程
1 2 3 4 5 6 7 8 9 class Person {var age: Short = 90 var name: String = _def this (n: String , a: Int ) {this ()this .name = nthis .age = a}} var p : Person = new Person ("小倩" ,20 )
流程分析(面试题-写出)
加载类的信息(属性信息,方法信息)
在内存中(堆)开辟空间
使用父类的构造器(主和辅助)进行初始
使用主构造器对属性进行初始化 【age:90, naem nul】
使用辅助构造器对属性进行初始化 【 age:20, naem 小倩 】
将开辟的对象的地址赋给 p这个引用
5. 包
5.1 Scala包的特点概述
Java包的三大作用
区分相同名字的类
当类很多时,可以很好的管理类
控制访问范围
Java打包语法:如 package com.atguigu;
Java如何引入包:如 import java.awt.*;
Scala包的基本介绍
和Java一样,Scala中管理项目可以使用包,但Scala中的包的功能更加强大,使用也相对复杂些,下面我们学习Scala包的使用和注意事项。
1 2 3 4 5 6 7 8 9 10 11 package com.atguigu.chapter02.xhclass Cat {} package com.atguigu.chapter02.xmclass Cat {} var cat1 = new com.atguigu.chapter02.xh.Cat ()println("cat1" + cat1) var cat2 = new com.atguigu.chapter02.xm.Cat ()println("cat2" + cat2)
基本语法:package 包名
,和java相同
Scala包的三大作用(和Java一样)
区分相同名字的类
当类很多时,可以很好的管理类
控制访问范围
命名规范 :com.公司名.项目名.业务模块名
比如:
1 2 3 4 com.atguigu.oa.model com.atguigu.oa.controller com.sina.edu.user com.sohu.bank.order
Scala会自动引入的常用包 (不需要自己再手动引入)
1 2 3 java.lang.* (这个java也会自动引入) scala._ ,如scala.List就不用手动引入就可以使用,但是其子包不能使用,比如scala.io.*里面的内容就不能使用 scala.Predef._ (一般很多的隐式转换都在该包下),如Predef.println等等常用方法可以使用
Scala中包名和源码所在的文件目录可以不一致 ,但是编译后的字节码文件(.class文件)路径和包名会保持一致(这个工作由编译器完成)。
5.2 Scala包注意事项和使用细节
1)scala进行package 打包时,可以有如下形式。
上面三种打包方式完全等价
包也可以像嵌套类那样嵌套使用(包中有包), 这个在前面的第三种打包方式已经讲过了,在使用第三种方式时的好处是:程序员可以在同一个文件中,将类(class / object)、trait 创建在不同的包中,这样就非常灵活了。
作用域原则:可以直接向上访问。即: Scala中子包中直接访问父包中的内容 , 大括号体现作用域。
(提示:Java中子包使用父包的类,需要import)。在子包和父包 类重名时,默认采用就近原则,如果希望指定使用某个类,则带上包名即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.atguigu {class User {} object Monster {} class Dog {} package scala {class User {} object Test { def main (args: Array [String ]): Unit = { var dog = new Dog () println("dog=" + dog) var u = new User () println("u=" + u) var u2 = new com.atguigu.User () println("u2=" + u2) } }
父包要访问子包的内容时,需要import对应的类等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.atguigu { import com.atguigu.scala.Tiger class User { } package scala { class Tiger {} } object Test2 { def main (args: Array [String ]): Unit = { import com.atguigu.scala.Tiger val tiger = new Tiger () println("tiger=" + tiger) } } }
可以在同一个.scala文件中,声明多个并列的package(建议嵌套的pakage不要超过3层)
包名可以相对也可以绝对,比如,访问BeanProperty的绝对路径是:_root_. scala.beans.BeanProperty
,在一般情况下:我们使用相对路径来引入包,只有当包名冲突时,使用绝对路径来处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Manager (var name: String ) { @_root_.scala.beans.BeanProperty var age: Int = _ } object TestBean { def main (args: Array [String ]): Unit = { val m = new Manager ("jack" ) println("m=" + m) } }
5.3 包对象
基本介绍:包可以包含类、对象和特质trait,但不能包含函数/方法或变量的定义。这是Java虚拟机的局限。
1 2 3 4 5 6 7 8 9 10 package Test { package scala { var name = "" def test1 () { } } }
为了弥补这一点不足,scala提供了包对象的概念来解决这个问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.atguigu { package object scala { var name = "jack" def sayOk (): Unit = { println("package object sayOk!" ) } } package scala { object TestObj { def main (args: Array [String ]): Unit = { println("name=" + name) sayOk() } } } }
包对象的注意事项
每个包都可以有一个包对象,而且只能有一个。你需要在父包中定义它。
在包对象中可以定义变量和方法,在包对象中定义的变量和方法就可以在对应的子包中使用
包对象名称需要和子包名一致,一般用来对包的功能补充
5.4 包的可见性
java提供四种访问控制修饰符号控制方法和变量的访问权限(范围):
公开级别:用public 修饰,对外公开
受保护级别:用protected修饰,对子类和同一个包中的类公开
默认级别:没有修饰符号,向同一个包的类公开.
私有级别:用private修饰,只有类本身可以访问,不对外公开.
Java访问修饰符使用注意事项
修饰符可以用来修饰类中的属性,成员方法以及类
只有默认的和public才能修饰类!,并且遵循上述访问权限的特点。
Scala中包的可见性
在Java中,访问权限分为: public,private,protected和默认。在Scala中,你可以通过类似的修饰符达到同样的效果。但是使用上有区别。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package testAobject Testvisit { def main (args: Array [String ]): Unit = { val c = new Clerk () c.showInfo() Clerk .test(c) }} class Clerk { var name : String = "jack" private var sal : Double = 9999.9 def showInfo (): Unit = { println(" name " + name + " sal= " + sal) }} object Clerk { def test (c: Clerk ): Unit = { println("test() name=" + c.name + " sal= " + c.sal) } }
当属性访问权限为默认时,从底层看属性是private的,但是因为提供了xxx_$eq()[类似setter]/xxx()[类似getter] 方法,因此从使用效果看是任何地方都可以访问)
当方法访问权限为默认时,默认为public访问权限
private为私有权限,只在类的内部和伴生对象中可用
protected为受保护权限,scala中受保护权限比Java中更严格,只能子类访问,同包无法访问 (编译器)
在scala中没有public关键字,即不能用public显式的修饰属性和方法。
包访问权限(表示属性和方法有了限制。同时包也有了限制 ),这点和Java不一样,体现出Scala包使用的灵活性。
下面的实例,相当于在private的基础上,扩大了访问权限
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package testA.layneobject Testvisit1 { def main (args: Array [String ]): Unit = { val c = new Clerk () val sal=c.sal println(sal) }} class Clerk { var name : String = "jack" private [layne] var sal : Double = 9999.9 private def showInfo (): Unit = { println(" name " + name + " sal= " + sal) } }
5.5 包的引入
Scala引入包也是使用import, 基本的原理和机制和Java一样,但是Scala中的import功能更加强大,也更灵活。
因为Scala语言源自于Java,所以java.lang包中的类会自动引入到当前环境中,而Scala中的scala包和Predef包的类也会自动引入到当前环境中,即起其下面的类可以直接使用。
如果想要把其他包中的类引入到当前环境中,需要使用import
Scala引入包的细节和注意事项
在Scala中,import语句可以出现在任何地方,并不仅限于文件顶部,import语句的作用一直延伸到包含该语句的块末尾。这种语法的好处是:在需要时在引入包,缩小import 包的作用范围,提高效率。
1 2 3 4 5 6 7 class User { import scala.beans.BeanProperty @BeanProperty var name : String = "" } class Dog { @BeanProperty var name : String = "" }
Java中如果想要导入包中所有的类,可以通过通配符*,Scala中采用下 _
如果不想要某个包中全部的类,而是其中的几个类,可以采用选取器(大括号
1 2 3 4 5 def test (): Unit = { import scala.collection.mutable.{HashMap , HashSet } var map = new HashMap () var set = new HashSet () }
如果引入的多个包中含有相同的类,那么可以将不需要的类进行重命名进行区分,这个就是重命名。
1 2 3 4 import java.util.{ HashMap =>JavaHashMap , List }import scala.collection.mutable._var map = new HashMap () var map1 = new JavaHashMap ();
如果某个冲突的类根本就不会用到,那么这个类可以直接隐藏掉。
1 2 import java.util.{ HashMap =>_, _} var map = new HashMap ()
6. 封装、继承、多态
6.1 封装
Scala封装的注意事项和细节
Scala中为了简化代码的开发,当声明属性时,本身就自动提供了对应setter/getter方法,如果属性声明为private的,那么自动生成的setter/getter方法也是private的,如果属性省略访问权限修饰符,那么自动生成的setter/getter方法是public的。
因此我们如果只是对一个属性进行简单的set和get ,只要声明一下该属性(属性使用默认访问修饰符) 不用写专门的getset,默认会创建,访问时,直接对象.变量。这样也是为了保持访问一致性
从形式上看 dog.food 直接访问属性,其实底层仍然是访问的方法, 看一下反编译的代码就明白
有了上面的特性,目前很多新的框架,在进行反射时,也支持对属性的直接反射
6.2 继承
Java继承的简单回顾 class 子类名 extends 父类名 { 类体 }
,子类继承父类的属性和方法
(1)和Java一样,Scala也支持类的单继承
Scala继承的基本语法
1 class 子类名 extends 父类名 { 类体 }
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Person { var name: String = _ var age: Int = _ def showInfo (): Unit = { println("学生信息如下:" ) println("名字:" + this .name) } } class Student extends Person { def studying (): Unit = { println(this .name + "学习 scala中...." ) } }
(2)子类继承了所有的属性,只是私有的属性不能直接访问,需要通过公共的方法去访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 object Extends02 { def main (args: Array [String ]): Unit = val sub = new Sub () sub.sayOk() } } class Base { var n1: Int = 1 protected var n2: Int = 2 private var n3: Int = 3 def test100 (): Unit = { println("base 100" ) } protected def test200 (): Unit = { println("base 200" ) } private def test300 (): Unit = { println("base 300" ) } } class Sub extends Base { def sayOk (): Unit = { this .n1 = 20 this .n2 = 40 println("范围" + this .n1 + this .n2) } }
(3)scala明确规定,重写一个非抽象方法需要用override修饰符,调用超类的方法使用super关键字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Person { var name: String = "tom" def printName () { println("Person printName() " + name) } } class Emp extends Person { override def printName () { println("Emp printName() " + name) super .printName() } }
6.3 类型检查和转换
要测试某个对象是否属于某个给定的类,可以用isInstanceOf方法。用asInstanceOf方法将引用转换为子类的引用。classOf获取对象的类名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package testA.layneobject T3 { def main (args: Array [String ]): Unit = { println(classOf[String ]) println(classOf[Emp ]) val s = "zhangsan" println(s.getClass.getName) println(s.isInstanceOf[String ]) println(s.asInstanceOf[String ]) var p = new Person val e = new Emp p = e p.name = "layne" println(e.name) p.asInstanceOf[Emp ].printName() } } class Person { var name: String = "tom" def printName () { println("Person printName() " + name) } } class Emp extends Person { override def printName () { println("Emp printName() " + name) super .printName() } }
classOf[String]就如同Java的 String.class 。
obj.isInstanceOf[T]就如同Java的obj instanceof T 判断obj是不是T类型。
obj.asInstanceOf[T]就如同Java的(T)obj 将obj强转成T类型。
6.4 超类的构造
在Java中,创建子类对象时,子类的构造器总是去调用一个父类的构造器(显式或者隐式调用)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class A { public A () { System.out.println("A()" ); } public A (String name) { System.out.println("A(String name)" + name); } } class B extends A { public B () { System.out.println("B()" ); } public B (String name) { super (name); System.out.println("B(String name)" + name); } }
在Scala中,类有一个主构器和任意数量的辅助构造器,而每个辅助构造器都必须先调用主构造器(也可以是间接调用),这点在前面我们说过了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Person { var name = "zhangsan" println("Person..." ) } class Emp extends Person { println("Emp ...." ) def this (name: String ) { this this .name = name println("Emp 辅助构造器~" ) } }
Scala中,只有主构造器可以调用父类的构造器。辅助构造器不能直接调用父类的构造器。在Scala的构造器中,你不能调用super(params)
1 2 3 4 5 6 7 8 class Person (name: String ) { } class Emp (name: String ) extends Person (name ) { def this () { super ("abc" ) } }
举一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package testA.layneobject T4 { def main (args: Array [String ]): Unit = { val stu:PersonA =new Student ("Layne" ) stu.printName() } } class PersonA (pname:String ) { var name: String = "tom" println("Welcome to PersonA:" +pname) def printName () { println("Person printName() " + name) } } class Student (studentname:String ) extends PersonA (studentname ) { var sno:Int =20 println("Welcome to Student:" +studentname) override def printName (): Unit ={ println("Student priceName()" +name) super .printName() } }
输出
1 2 3 4 Welcome to PersonA:Layne Welcome to Student:Layne Student priceName()tom Person printName() tom
6.5 覆盖属性/方法
在Scala中,子类改写父类的字段,我们称为覆写/重写字段。覆写字段需使用override修饰。
现在,回顾一下java的动态绑定,下面可以看到,对象a都是调用的子类方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package testA.layne;public class T5 { public static void main (String[] args) { A a = new B(); System.out.println(a.sum()); System.out.println(a.sum1()); } } class A { public int i = 10 ; public int sum () { return getI() + 10 ; } public int sum1 () { return i + 10 ; } public int getI () { return i; } } class B extends A { public int i = 20 ; public int sum () { return i + 20 ; } public int getI () { return i; } public int sum1 () { return i + 10 ; } }
来看一个scala覆盖字段的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package testA.layneobject T6 { def main (args: Array [String ]): Unit = { val obj : AA = new BB () val obj2 : BB = new BB () println(obj.age) println(obj.age) } } class AA { val age : Int = 10 } class BB extends AA { override val age : Int = 20 }
def只能重写另一个def(即:方法只能重写另一个方法)
val只能重写另一个val 属性 或 重写不带参数的def
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class AAAA { var name: String = "" } class BBBB extends AAAA { override val name: String = "jj" } class AAAA { var name: String = "" } class BBBB extends AAAA { override var name: String = "jj" } class A {def sal (): Int = { return 10 }} class B extends A {override val sal : Int = 0 }
var只能重写另一个抽象的var属性
1 2 3 4 5 6 7 8 abstract class A03 { val age:Int =10 var name:String } class B03 extends A03 { override val age:Int =20 var name:String =_ }
抽象属性:声明未初始化的变量就是抽象的属性,抽象属性在抽象类
var重写抽象的var属性小结:
一个属性没有初始化,那么这个属性就是抽象属性
抽象属性在编译成字节码文件时,属性并不会声明,但是会自动生成抽象方法,所以类必须声明为抽象类
上面的代码反编译为java代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class ABC { private int a = 10 ; public int a() { return this .a; } public void a_$eq(int x$1 ) { this .a = x$1 ; } private final int b = 20 ; public int b() { return this .b; } public class B03 extends A03 { private final int age = 20 ; private String name; public int age() { return this .age; } public String name() { return this .name; } public void name_$eq(String x$1 ) { this .name = x$1 ; } }
如果是覆写一个父类的抽象属性,那么override 关键字可省略 [原因:父类的抽象属性,生成的是抽象方法,因此就不涉及到方法重写的概念,因此override可省略,当然也可以写override]
6.6 抽象类
抽象类基本语法
1 2 3 4 abstract class Person ( ) { var name: String def printName // 抽象方法 , 没有方法体 }
说明:抽象类的价值更多是在于设计,是设计者设计好后,让子类继承并实现抽象类(即:实现抽象类的抽象方法)
Scala抽象类使用的注意事项和细节讨论
抽象类不能被实例
抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法
一旦类包含了抽象方法或者抽象属性,则这个类必须声明为abstract
抽象方法不能有主体,不允许使用abstract修饰。
如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法和抽象属性,除非它自己也声明为abstract类。(java里面只要实现抽象方法就行了)
6)抽象方法和抽象属性不能使用private、final 来修饰,因为这些关键字都是和重写/实现相违背的。
抽象类中可以有实现的方法
子类重写抽象方法不需要override,写上也不会错
匿名子类
和Java一样,可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类.
回顾-java匿名子类使用
1 2 3 4 5 6 7 8 9 10 abstract class A2 { abstract public void cry () ; } A2 obj = new A2() { @Override public void cry () { System.out.println("okook!" ); } };
scala匿名子类
1 2 3 4 5 6 7 8 9 10 11 12 13 abstract class Monster { var name: String def cry () } var monster = new Monster { override var name: String = "牛魔王" override def cry (): Unit = { println("牛魔王哼哼叫唤.." ) } }
继承层级
在scala中,所有其他类都是AnyRef的子类,类似Java的Object。
AnyVal和AnyRef都扩展自Any类。Any类是根节点
Any中定义了isInstanceOf、asInstanceOf方法,以及哈希方法等。
Null类型的唯一实例就是null对象。可以将null赋值给任何引用,但不能赋值给值类型的变量。
Nothing类型没有实例,它是任何类型的子类型,可以交给一个变量和函数。它对于泛型结构是有用处的,举例:空列表Nil的类型是List[Nothing],它是List[T]的子类型,T可以是任何类。
7. 静态概念与伴生对象
回顾下Java的静态概念
1 2 public static 返回值类型 方法名(参数列表) {方法体} 静态属性...
Java中静态方法并不是通过对象调用的,而是通过类对象调用的,所以静态操作并不是面向对象的。
Scala中静态的概念-伴生对象
Scala语言是完全面向对象(万物皆对象)的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。但是为了能够和Java语言交互(因为Java中有静态概念),就产生了一种特殊的对象来模拟类对象,我们称之为类的伴生对象 。这个类的所有静态内容都可以放置在它的伴生对象中声明和调用
伴生对象的快速入门
1 2 3 4 5 6 7 class ScalaPerson { var name : String = _ } object ScalaPerson { var sex : Boolean = true } println(ScalaPerson .sex)
Scala中伴生对象采用object关键字声明,伴生对象中声明的全是 "静态"内容,可以通过伴生对象名称直接调用 。
伴生对象对应的类称之为伴生类 ,伴生对象的名称应该和伴生类名一致。
伴生对象中的属性和方法都可以通过伴生对象名 (类名)直接调用访问,伴生类可以直接使用伴生对象中的属性和方法【看第(5)的反编译】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 object Test12 { def main (args: Array [String ]): Unit = { println(ScalaPerson1 .sex) val s=new ScalaPerson1 println(s.name) val a=ScalaPerson1 println(a.sex) } } object ScalaPerson1 { var sex : Boolean = true } class ScalaPerson1 { var name : String = _ }
从语法角度来讲,所谓的伴生对象其实就是类的静态方法和成员的集合
从技术角度来讲,scala还是没有生成静态的内容,只不过是将伴生对象生成了一个新的类,实现属性和方法的调用 。[反编译看源码]
将(3)中scala反编译为java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class ScalaPerson1 { private String name; public static void sex_$eq(boolean paramBoolean) { ScalaPerson1 $.MODULE $.sex_$eq(paramBoolean); } public static boolean sex() { return ScalaPerson1 $.MODULE $.sex(); } public String name() { return this .name; } public void name_$eq(String x$1 ) { this .name = x$1 ; } } public final class ScalaPerson1$ { public static ScalaPerson1 $ MODULE $; private boolean sex; public boolean sex() { return this .sex; } public void sex_$eq(boolean x$1 ) { this .sex = x$1 ; } private ScalaPerson1 $() { MODULE $ = this ; this .sex = true ; } }
从底层原理看,伴生对象实现静态特性是依赖于 public static final MODULE$ 实现的。
如果 class A 独立存在,那么A就是一个类, 如果 object A 独立存在,那么A就是一个"静态"性质的对象[即类对象 ], 此时在 object A中声明的属性和方法可以通过 A.属性
和 A.方法
来实现调用
8)如果伴生对象和伴生类同时出现 ,伴生对象的声明应该和伴生类的声明在同一个源码文件中(如果不在同一
个文件中会运行错误,会提示类已存在的错误),但是如果没有伴生类,也就没有所谓的伴生对象了,所以放在哪里就无所谓了。
当一个文件中,存在伴生类和伴生对象时,文件的图标会发生变化
伴生对象-apply方法
先来看几个例子:
示例1
1 2 3 4 5 6 7 8 9 10 11 12 13 object Test15 { def main (args: Array [String ]): Unit = { val t1 = new TestA t1("layne1" ) } } class TestA { def apply (param:String ){ println("TestA apply method called:" + param) } }
示例2
1 2 3 4 5 6 7 8 9 10 11 12 object Test16 { def main (args: Array [String ]): Unit = { TestB ("layne2" ) } } object TestB { def apply (param:String ){ println("TestB apply method called:" + param) } }
可以看出,apply方法调用:用括号传递给类对象 或伴生对象 一个或多个参数时,Scala会在相应的类或对象中查找方法名为apply且参数列表与传入的参数一致的方法,并用传入的参数来调用该apply方法 ,并获取apply方法的返回值。
另外,apply方法也支持重载。
在Scala中,我们把所有类的构造方法以apply方法的形式定义在它的伴生对象当中,这样伴生对象的方法就会自动被调用,调用就会生成类对象。
示例3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package Test object Test17 { def main (args: Array [String ]): Unit = { val mycar=Car ("tom" ) mycar.info() } } class Car (name:String ) { def info (){ println("Car name is " + name) } } object Car { def apply (name:String ) = new Car (name) }
Scala之所以用apply方法是为了保持它的对象和函数之间使用的一致性,因为Scala融合了面向对象和函数式两种编程风格,它是一种混合式的编程。
面向对象调用:对象.方法
函数式调用:函数(参数)
Scala中函数式的调用可以转化成对象调用,同理对象调用也可以被转化成函数的调用。
1 2 3 4 5 6 7 val mycar=Car ("tom" )mycar.info() val t1 = new TestA t1("layne1" )
8 接口&trait(特征)
8.1 scala中的接口与trait
回顾Java接口
1 2 3 4 //声明接口 interface 接口名 //实现接口 class 类名 implements 接口名1,接口2
在Java中, 一个类可以实现多个接口。
在Java中,接口之间支持多继承
接口中属性都是常量
接口中的方法都是抽象的
Scala接口
从面向对象来看,接口并不属于面向对象的范畴,Scala是纯面向对象的语言,在Scala中,没有接口。
Scala语言中,采用特质trait(特征)来代替接口的概念,也就是说,多个类具有相同的特征(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明。 理解trait 等价于(interface + abstract class)
trait 的声明
Serializable就是scala的一个特质:
1 2 3 object T1 extends Serializable { }
在scala中,java中的接口可以当做特质使用。
8.2 trait 的使用
一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了extends关键字,如果有多个特质或存在父类,那么需要采用with关键字连接
可以把特质可以看作是对继承的一种补充,Scala的继承是单继承,也就是一个类最多只能有一个父类,这种单继承的机制可保证类的纯洁性,比c++中的多继承机制简洁。但对子类功能的扩展有一定影响,所以我们认为: Scala引入trait特征,第一可以替代Java的接口, 第二个也是对单继承机制的一种补充。
Scala提供了特质(trait),特质可以同时拥有抽象方法和具体方法,一个类可以实现/继承多个特质。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package traitPackageobject T1 { def main (args: Array [String ]): Unit = { val a:Trait01 =new AA a.sayOK() a.sayHello() val trait01Obj=new Trait01 { override def sayOK (): Unit = { println("trait01Obj sayOk" ) } } trait01Obj.sayHello() trait01Obj.sayOK() } } trait Trait01 { def sayOK () def sayHello ():Unit ={ println("Trait01 sayHello" ) } } class AA extends Trait01 { override def sayOK (): Unit ={ println("AA sayOk" ) } }
上面的代码反编译为java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public interface Trait01 { static void $init$(Trait01 $this ) {} default void sayHello () { Predef$.MODULE$.println("Trait01 sayHello" ); } void sayOK () ; } public class AA implements Trait01 { public void sayHello () { Trait01.sayHello$(this ); } public AA () { Trait01.$init$(this ); } public void sayOK () { Predef$.MODULE$.println("AA sayOk" ); } }
特质中没有实现的方法就是抽象方法。类通过extends继承特质,通过with可以继承多个特质
所有的java接口都可以当做Scala特质使用
1 2 3 4 5 6 7 8 9 trait Logger { def log (msg: String ) } class Console extends Logger with Cloneable with Serializable { def log (msg: String ) { println(msg) } }
和Java中的接口不太一样的是特质中的方法并不一定是抽象的,也可以有非抽象方法(即:实现了的方法)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 trait Operate { def insert ( id : Int ): Unit = { println("保存数据=" +id) } } trait DB extends Operate { override def insert ( id : Int ): Unit = { print("向数据库中" ) super .insert(id) } } class MySQL extends DB {}
8.3 带有特质的对象
除了可以在类声明时继承特质以外,还可以在构建对象时混入特质 ,扩展目标类的功能
此种方式也可以应用于对抽象类功能进行扩展
动态混入是Scala特有的方式(java没有动态混入),可在不修改类声明/定义的情况下,扩展类的功能,非常的灵活,耦合性低 。
动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package traitPackageobject T2 { def main (args: Array [String ]): Unit = { var oracle = new OracleDB with Operate3 oracle.insert(999 ) val mysql = new MySQL3 with Operate3 { override def connect (): Int = { println("hello mysql connect" ) 1 +1 } } mysql.insert(4 ) mysql.connect() } } trait Operate3 { def insert (id: Int ): Unit = { println("插入数据 = " + id) } } class OracleDB {} abstract class MySQL3 { def connect ():Int }
在Scala中创建对象共有几种方式
new 对象
apply 创建
匿名子类方式
动态混入
8.4 叠加特质
叠加特指是本质上也是动态混入
构建对象的同时如果混入多个特质,称之为叠加特质,仔细看下面的代码注释,理解叠加特质的含义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 package traitPackageobject T3 { def main (args: Array [String ]): Unit = { val mysql1 = new MySQL4 with DB4 with File4 mysql1.insert (888 ) } } trait Operate4 { println("Operate4..." ) def insert (id: Int ) } trait Data4 extends Operate4 { println("Data4" ) override def insert (id: Int ): Unit = { println("(Data4)插入数据 = " + id) } } trait DB4 extends Data4 { println("DB4" ) override def insert (id: Int ): Unit = { print("(DB4)向数据库" ) super .insert(id) } } trait File4 extends Data4 { println("File4" ) override def insert (id: Int ): Unit = { print("(File4)向文件" ) super .insert(id) } } class MySQL4 {}
在特质中重写抽象方法特例
看下面的代码
1 2 3 4 5 6 7 8 9 trait Operate5 { def insert (id : Int ) } trait File5 extends Operate5 { def insert ( id : Int ): Unit = { println("将数据保存到文件中.." ) super .insert(id) } }
运行代码,编译无法通过,报错信息为:
1 2 method insert in trait Operate5 is accessed from super. It may not be abstract unless it is overridden by a member declared `abstract' and `override' super.insert(id)
因为我们在特质File5中调用super的insert方法,而Operate5的该方法却没有具体实现,因此报错
解决方法
方式1 : 去掉 super()…
方式2: 声明 abstract override,编译通过
1 2 3 4 5 6 7 8 9 trait Operate5 { def insert (id : Int ) } trait File5 extends Operate5 { abstract override def insert ( id : Int ): Unit = { println("将数据保存到文件中.." ) super .insert(id) } }
理解 abstract override 的小技巧分享:
可以这里理解,当我们给某个方法增加了abstract override
后,就是明确的告诉编译器,该方法确实是重写了父特质的抽象方法,但是重写后,该方法仍然是一个抽象方法(因为没有完全的实现,需要其它特质继续实现[通过混入顺序 ])
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package traitPackageobject T4 { def main (args: Array [String ]): Unit = { val mysql5 = new MySQL5 with DB5 with File5 mysql5.insert(123 ) } } trait Operate5 { def insert (id : Int ) } trait File5 extends Operate5 { abstract override def insert ( id : Int ): Unit = { println("(File5)将数据保存到文件中.." ) super .insert(id) } } trait DB5 extends Operate5 { def insert ( id : Int ): Unit = { println("(DB5)将数据保存到数据库中.." ) } } class MySQL5 {}
输出:
1 2 (File5)将数据保存到文件中.. (DB5)将数据保存到数据库中..
结合上面的代码,看4个案例
1 2 3 4 5 6 7 8 var mysql2 = new MySQL5 with DB5 mysql2.insert(100 ) var mysql3 = new MySQL5 with File5 mysql2.insert(100 ) var mysql4 = new MySQL5 with File5 with DB5 mysql4.insert(100 ) var mysql4 = new MySQL5 with DB5 with File5 mysql4.insert(100 )
8.5 特质中的字段和方法
富接口:即该特质中既有抽象方法,又有非抽象方法
1 2 3 4 5 6 trait Operate { def insert ( id : Int ) def pageQuery (pageno:Int , pagesize:Int ): Unit = { println("分页查询" ) } }
特质中的具体字段
特质中可以定义具体字段,如果初始化了就是具体字段,如果不初始化就是抽象字段。混入该特质的类就具有了该字段,字段不是继承,而是直接加入类,成为自己的字段。
特质中的抽象字段
特质中未被初始化的字段在具体的子类中必须被重写。
8.6 特质构造顺序
特质也是有构造器的,构造器中的内容由“字段的初始化”和一些其他语句构成。具体实现请参考“特质叠加”。
第一种特质构造顺序(声明类的同时混入特质)
调用当前类的超类构造器
第一个特质的父特质构造器
第一个特质构造器
第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行
第二个特质构造器
…重复4,5的步骤(如果有第3个,第4个特质)
当前类构造器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package traitPackageobject T5 { def main (args: Array [String ]): Unit = { val ff1 = new FF () println(ff1) val ff2 = new KK () println(ff2) } } trait AA { println("A..." ) } trait BB extends AA { println("B...." ) } trait CC extends BB { println("C...." ) } trait DD extends BB { println("D...." ) } class EE { println("E..." ) } class FF extends EE with CC with DD { println("F...." ) } class KK extends EE { println("K...." ) }
输出:
1 2 3 4 5 6 7 8 9 10 E ...A ...B ....C ....D ....F ....traitPackage.FF @1 b40d5f0 E ...K ....traitPackage.KK @ea 4a92b
第2种特质构造顺序(在构建对象时,动态混入特质)
调用当前类的超类构造器
当前类构造器
第一个特质构造器的父特质构造器
第一个特质构造器.
第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行
第二个特质构造器
…重复5,6的步骤(如果有第3个,第4个特质)
当前类构造器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package traitPackageobject T6 { def main (args: Array [String ]): Unit = { val f=new MB with F3 with F2 } } trait F1 { println("F1..." ) } trait F2 extends F1 { println("F2..." ) } trait F3 extends F1 { println("F3..." ) } class MA { println("MA..." ) } class MB extends MA { println("MB...." ) }
输出
1 2 3 4 5 MA... MB.... F1... F3... F2...
分析两种方式对构造顺序的影响
第1种方式实际是构建类对象, 在混入特质时,该对象还没有创建。
第2种方式实际是构造匿名子类,可以理解成在混入特质时,对象已经创建了
8.7 扩展类的特质
特质可以继承类,以用来拓展该类的一些功能
1 2 3 4 5 trait LoggedException extends Exception { def log (): Unit ={ println(getMessage()) } }
所有混入该特质的类,会自动成为那个特质所继承的超类的子类
1 2 3 4 5 6 7 8 9 10 11 12 13 trait LoggedException extends Exception { def log (): Unit = { println(getMessage()) } } class UnhappyException extends LoggedException { override def getMessage = "错误消息!" }
如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类,否则就会出现了多继承现象,发生错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package traitPackageobject T7 { def main (args: Array [String ]): Unit = { } } trait LoggedException extends Exception { def log (): Unit = { println(getMessage()) } } class UnhappyException extends LoggedException { override def getMessage = "错误消息!" } class UnhappyException2 extends IndexOutOfBoundsException with LoggedException { override def getMessage = "错误消息!" } class CCC {} class UnhappyException3 extends CCC with LoggedException {}
这个错误信息如下:
1 2 3 4 illegal inheritance; superclass CCC is not a subclass of the superclass Exception of the mixin trait LoggedException class UnhappyException3 extends CCC with LoggedException{
当然,如果该特质没有超类,则就没有这个限制了。即如果LoggedException没有超类,则对CCC没有任何限制了
1 2 3 4 5 6 7 8 9 10 11 12 class CCC {} trait AAA {} trait BBB { } class UnhappyException4 extends CCC with AAA with BBB {}
对于上面这个例子来说,要么CCC、AAA、BBB有相同的父类,要么没有父类
8.8 自身类型(selftype)
自身类型:主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型。
所谓的循环依赖是指:两个类互相依赖,互相是对象父类或子类
1 2 3 4 5 6 7 8 9 10 11 12 trait Logger { this : Exception => def log (): Unit ={ println(getMessage) } } class Console extends Logger {} class Console extends Exception with Logger//对吗?正确,先继承了Eception ,再混入该特质
2.9 嵌套类
9.1 嵌套类的使用
在Scala中,你几乎可以在任何语法结构中内嵌任何语法结构。如在类中可以再定义一个类,这样的类是嵌套类,其他语法结构也是一样。嵌套类类似于Java中的内部类。
Java内部类的分类
从定义在外部类的成员位置上来看,
成员内部类(没用static修饰)
和静态内部类(使用static修饰),
定义在外部类局部位置上(比如方法内)来看:
分为局部内部类(有类名)
匿名内部类(没有类名)
这里我们就回顾一下成员内部类和静态内部类。
Scala嵌套类的使用1
请编写程序,定义Scala 的成员内部类和静态内部类,并创建相应的对象实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class ScalaOuterClass { class ScalaInnerClass { } } object ScalaOuterClass { class ScalaStaticInnerClass { } } val outer1 : ScalaOuterClass = new ScalaOuterClass ();val outer2 : ScalaOuterClass = new ScalaOuterClass ();val inner1 = new outer1.ScalaInnerClass ()val inner2 = new outer2.ScalaInnerClass ()val staticInner = new ScalaOuterClass .ScalaStaticInnerClass () println(staticInner)
Scala嵌套类的使用2
请编写程序,在内部类中访问外部类的属性
方式1
内部类如果想要访问外部类的属性,可以通过外部类对象访问。即:访问方式:外部类名.this.属性名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class ScalaOuterClass { var name: String = "scott" private var sal: Double = 1.2 class ScalaInnerClass { def info () = { println("name = " + ScalaOuterClass .this .name + " age =" + ScalaOuterClass .this .sal) } } } object ScalaOuterClass { class ScalaStaticInnerClass { } }
方式2
内部类如果想要访问外部类的属性,也可以通过外部类别名访问(推荐)。即:访问方式:外部类名别名.属性名 【外部类名.this 等价 外部类名别名】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class ScalaOuterClass { myOuter => class ScalaInnerClass { def info () = { println("name = " + ScalaOuterClass .this .name + " age =" + ScalaOuterClass .this .sal) println("-----------------------------------" ) println("name = " + myOuter.name + " age =" + myOuter.sal) } } var name: String = "scott" private var sal: Double = 1.2 }
9.2 类型投影
先看一段代码,引出类型投影
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package traitPackageobject T10 { def main (args: Array [String ]): Unit = { val outer1: ScalaOuterClass3 = new ScalaOuterClass3 (); val outer2: ScalaOuterClass3 = new ScalaOuterClass3 (); val inner1 = new outer1.ScalaInnerClass3 () val inner2 = new outer2.ScalaInnerClass3 () inner1.test(inner1) inner2.test(inner2) inner1.test(inner2) } } class ScalaOuterClass3 { myOuter => class ScalaInnerClass3 { def test (ic: ScalaInnerClass3 ): Unit = { System .out.println(ic) } } }
Java中的内部类从属于外部类,因此在java中 inner1.test(inner2) 就可以,因为是按类型来匹配的。
Scala中内部类从属于外部类的对象,所以外部类的对象不一样,创建出来的内部类也不一样,无法互换使用
比如你使用ideal 看一下在inner1.test()的形参上,它提示的类型是 outer1.ScalaInnerClass
解决方式-使用类型投影
类型投影是指:在方法声明上,如果使用 外部类#内部类
的方式,表示忽略内部类的对象关系,等同于Java中内部类的语法操作,我们将这种方式称之为 类型投影(即:忽略对象的创建方式,只考虑类型)。
把上面的代码改为如下所示的代码,inner1.test(inner2)就不报错了
1 2 3 4 5 6 7 8 9 class ScalaOuterClass3 { myOuter => class ScalaInnerClass3 { def test (ic: ScalaOuterClass3 #ScalaInnerClass3 ): Unit = { System .out.println(ic) } } }