Scala面向对象
extend App
object ObjectTest extends App { //可不用写main方法,extend App特质也可以实现跟main方法一样的功能 println(123) }
scala中面向对象的编程语法和java基本类似,但是有区别
* 1)scala中的包和java一致
* 2) scala中import可以用于导包,但是java.lang的包自动的包含(跟java中一样,java.lang包不用导入就可直接使用)
scala中import才是真正的导包,java中import是导类(某个包中的具体类)
* 3) scala中声明类和java一致
* 4) scala中声明属性和java语法规则有变化
不需要public关键字(可以使用private、protected),类型在后,名称在前,使用var| val声明
包
java中的包:https://www.cnblogs.com/shengyang17/p/10031596.html
在使用上完全可以通过对类的起名进行约束达到对类的区分,所以包的语法就变得有点可有可无的感觉, 所以scala从语法上对包进行扩展
1) 源码所在的路径可以和包名不一致
2) 源码中package关键字可以多次声明,使用时组合在一起使用
package test
package test1
3) scala可以将包当成对象来用,但是只能声明类,接口
package test { class A { } }
package中只能声明类或接口,主要是受到JVM的限制,所以scala为了让开发方便
使用了 包对象 概念,可以声明属性和方法,类似于伴生对象
4) scala中的包有作用域的概念:子包可以直接访问父包的内容,但是父包访问子包需要导入
* package test { * class A { * } * package test1 { //可以嵌套使用,父包里边嵌套包test1 * class B { * } * } * }
比如:
package Test{ //包里边只能声明类或接口,可以嵌套包 import com.atguigu.objects.Test.test1.B class A{ def test(): Unit ={ val b = new B //父包访问子包需要导入 } } package object test1{ //包对象;可以声明属性和方法;把package去掉就是 类似伴生对象| 内部类(里边是静态属性和方法,可直接访问) var i = 123 } package test1{ class B{ def test(): Unit ={ val a = new A //子包可以直接访问父包的内容 test1.i } } } }
import
scala语法源自java,所以会自动导入java.lang包中所有的类
scala还自动导入其他的包:scala,还有伴生对象Predef(预先声明了,类似静态导入了)中所有的内容
scala如果导入某个包中所有的类,会使用下划线来代替星号:import java.util._
scala中可以将同个包中的多个类使用花括号封装(一个包中有很多很多类,_全包导入会有很多,可使用{ }): import java.util.{ HashMap, List }
scala中可以给类起别名,防止误会: import java.util.{ HashMap=>JavaHashMap } 导入的是java中的HashMap
scala可以将不想使用的类给隐藏掉:import java.util.{Date=>_}
scala为了防止使用包的时候产生误会(有可能起的包名和系统的包名一样),可以使用绝对包名:_root_.java.util.HashMap
Field
scala中声明类的属性应该明确的初始化,否则发生错误
如果不知道赋什么值,可以使用下划线由编译期赋初始值
scala中如果属性没有访问修饰符,那么默认为公共权限public,而且scala会同时为属性生成公共的set/get方法,自动生成
private int age; private int age() { return this.age; } private void age_$eq(int x$1) { this.age = x$1; }
如果声明属性的权限为私有private,那么set/get方法也是私有的
scala可以增加注解@BeanProperty 可以动态添加 set/get方法 setName, getName;@BeanProperty和private不能同时使用;
protected关键字在scala中,只能同父类,子类能访问,其他类无法访问,即使是同包也不行
scala中同包访问需要增加特殊的语法操作 权限 加[包名]
object TestField { def main(args: Array[String]): Unit = { val emp: Emp = new Emp() //emp.name = "smile" //自动为属性生成get/set方法,emp.name()可理解为它的方法,可不加() //emp.name_=("kris") //因为它的底层是$eq //println(emp.name) emp.setName("kk") // println(emp.getName) } } class Emp{ //公共的 @BeanProperty var name: String = _ //增加注解@BeanProperty 可以动态添加 set/get方法 //var name: String = _ //私有的 private var age: Int = _ //不能写null,Int值是对象属AnyVal,null是引用属AnyRef //受保护的 protected var email: String = _ //同包下的 private[objects] var address: String = _ } class EmpSub { def test(): Unit={ var emp = new Emp() emp.address= "" //子类即继承extend E可以访问,其他类不能访问,本包中的也不可以; // 要想同包可以访问需加[包名] 要先var emp = new Emp() 再emp. } }
方法
函数(功能的封装); 从属(方法中会有), 从属哪个对象
所谓的方法,其实就是函数,所以语法规则和函数是完全一样。伴生对象即伴随一个类所产生的对象;
函数没有重载、重写,把它写到Object中就是方法了,就可以进行重载了,写在main方法里边就是函数
Object-->只有伴生对象,伴随类所产生的对象;
Class-->是伴生类,伴随对象所产生的类;
可互相转换;
通过伴生对象获取创建伴生类 (静态的放在伴生对象里边); 在java中通过静态方法获得它的对象--单例模式
//伴生类 class Student { } //伴生对象 object Student{ //伴生对象中可以声明apply方法,可以通过调用此方法来构建伴生类对象 //用法:这种方式构建对象时,是可以不需要使用new关键字 def apply(name: String): Student = new Student() }
object TestMethod { def main(args: Array[String]): Unit = { // 通过伴生对象获取创建伴生类 val student: Student = Student("kris") //等同于调用apply方法 println(student) //com.atguigu.objects.Student@ea4a92b } }
for (i <- Range(1, 10, 2)){ //这里的Range也是没有用new
}
scala中构造方法比较特殊
* scala中的类其实也是函数,可以在类声明时名称后增加小括号作为参数列表,表示构造方法
// 在scala中,构建对象,等同于调用类的构造方法,执行方法体的内容 // scala中构造方法可以声明在其他位置 var a = new A("kris") var a1 = new A var a2 = new A("smile", 20) } class A(s: String){ //类也是函数,参数列表; 有参,主构造方法 // 既是类体 也是构造方法体 println("s=" + s) //s=kris def this(s: String, i: Int){ this("kris") //加this表构建对象, 从构造方法 } // 构造方法(辅助)在辅助构造方法中一定要直接或间接的调用主构造方法 def this()={ this("smile", 18) //间接调用主构造方法,它去调用的def this(s: String, i: Int)这个构造方法,它再去调用主 }
主构造器加private就变成私有化的了,外界不能去访问
object ObjectTest extends App { val clazz = new TestClasses() //这时它调用的就是def this这个辅助构造器了; too many arguments for constructor TestClasses println(clazz) } class TestClasses private(name: String){ //不想用主构造方法,就用辅助构造方法可在主构造前加上private,私有的不能在外边调用 def this(){ this("kris") //辅助构造方法无参的;要去调用主构造方法(有参) } }
外界想访问可通过伴生对象来调用伴生类中的私有属性或方法
object ObjectTest extends App { val clazz = TestClasses("alex") //这里不用加new了 直接有伴生对象不需new ,applay直接传值 println(clazz) } class TestClasses private(name: String){ //不想用主构造方法,就用辅助构造方法可在主构造前加上private,私有的不能在外边调用 def this(){ this("kris") //辅助构造方法无参的;要去调用主构造方法(有参) } } object TestClasses{ // 伴生对象可以访问伴生类中私有的属性或方法 def apply(name: String): TestClasses = new TestClasses(name) }
类
父类的抽象方法可以由子类补充完整,而不需要重写,但是如果想要重写具体的方法,需要增加override关键字(抽象方法可不加)
父类的抽象属性可以由子类补充完整,而不需要重写,如果需要重写具体的属性,属性必须声明val, 增加override关键字(抽象属性可不加)
父类| 重写,快捷键ctrl+o,IDEA工具却不认为是重写里边没有,ctrl+i可以调出来IDEA认为它叫实现;
object TestClass { def main(args: Array[String]): Unit = { val bb = new CC() bb.test println(bb.name) println(bb.age) } } // 声明父类 abstract class BB{ //声明抽象属性,如果一个属性没有初始化就称之抽象属性 var name: String val age: Int = 20 //variable age cannot override a mutable variable,应该声明为val不可变的 // 声明抽象方法 def test def test1(): Unit={ } } // 声明类 class CC extends BB{ // 类可以重写父类的属性 var name: String = "kris" // 如果子类重写父类中具体属性,必须采用override关键字 override val age: Int = 18 // 类可以重写父类的方法,抽象方法可以不加override,只要重写了就可以 def test: Unit = { println("heihei") } override def test1: Unit = { //如果一模一样的把test1拿过来在java中可以,但在scala中需要加overwrite;非抽象方法需要加overwrite }
object TestClass { def main(args: Array[String]): Unit = { val student: Student1 = new Student1 println(student.name) //null kris } } class Person{ val prefix = "person_" val name = prefix + "kris" } // 如果父类的属性依赖于子类的属性,那么运行时,会出现错误,因为子类还没初始化呢; // 可以采用特殊的语法结构提前让属性初始化,解决这个问题。 class Student1 extends Person{ override val prefix: String = "student1_" }
查看字节码
person.class public class Person{ private final String prefix = "person_"; private final String name = new StringBuilder().append(prefix()).append("kris").toString(); //这个append方法不会走下边prefix(),动态绑定技术,调用一个成员方法时会看看它的实际内存是什么-->new Student1-->有这个prefix, return this.prefix;
但父类还没初始化完成,它调了子类一次子类要等父类初始化完成再初始化,所以此时是null //上边这个它什么时候执行的呢?构造对象顺序;先构造父类的属性赋值,再构建子类的给它赋值; public String prefix(){ return this.prefix; } public String name() { return this.name; } } Student1.class public class Student1 extends Person{ private final String prefix = "student1_"; public String prefix() { return this.prefix; } //多了这样一个方法 }
先做初始化再做其他的
extend{ 直接写,马上做把这个属性给覆盖掉 },可看作代码块
class Person{ val prefix = "person_" val name = prefix + "kris" } // 如果父类的属性依赖于子类的属性,那么运行时,会出现错误 // 可以采用特殊的语法结构提前让属性初始化,解决这个问题。 class Student1 extends { override val prefix: String = "student1_" }with Person //特殊的语法结构
构造方法的构造顺序
父类--->子类
object TestClass2 { def main(args: Array[String]): Unit = { val stu = new Student2("kris") //子类的有参构造, 先调this, 走Student2,再父类 } } class Person2{ println("父类构造方法") def this(name: String){ //虽然写了但是没有调用它,应该显式的调用它 this() println("父类构造方法:有参数") } } // 显示调用父类的构造方法, 其实就是在继承父类的时候,同时传递参数; class Student2 extends Person2("kk"){ println("子类构造方法") def this(name: String){ this() println("子类构造方法:有参数") } }
如果没有 Person2("kk")它(父类的有参构造的调用),结果是:
父类构造方法
子类构造方法
子类构造方法:有参数
加上之后,调用了父类的有参构造,结果:main方法中调用的是子类的有参构造,先去执行this-->主类的Student2--->父类Person2的有参构造,它不会马上调,先this() --->调用父主构造方法;===>再去调用父类的有参构造==>子类的主构造==>子类的有参构造
父类构造方法
父类构造方法:有参数
子类构造方法
子类构造方法:有参数
object TestClass3 { def main(args: Array[String]): Unit = { val stu = new Person3("jing", 18) } } // 主构造方法不一定是无参 // 如果构造方法中的参数增加了var,val关键字,那么scala会自动添加类的属性 class Person3(var name: String, age: Int){ name是个局部变量,如果写个name属性,它们的范围是一样的,为了防止冲突,scala不让写name属性了; 加var name就可以在main中直接调用了,把它当中属性;如果是val是不能改,但是可以取 println(name + "\t" + age) }
特质trait
* java : 接口 implements
scala : 特质(特征)extends
scala中没有接口的概念,有类似的特质的概念,trait
scala中可以继承with特质,但也可以同时继承extends父类
如果一个类有父类,同时具有特质,那么在执行时,会首先执行父类的构造方法,然后执行特质的操作,接着执行类的构造方法
如果同时父类也继承了相同的特质,那么父类的特质先执行,而子类的特质就不会再执行.
scala可以将java中的接口当成特质来用 比如with Serializable
trait MyTrait { println("MyTrait") } object TestInterface { def main(args: Array[String]): Unit = { new MyPerson } } class MyParent extends MyTrait{ println("MyParent") } class MyPerson extends MyParent with MyTrait{ println("MyPerson") } /* class MyParent, 父类没有继承特质 MyParent MyTrait MyPerson * */ //如果子父类有相同特质,父类也继承了相同的特质class MyParent extends MyTrait /* MyTrait MyParent MyPerson */
object TestInterface1 { def main(args: Array[String]): Unit = {
方法一:继承 /* val mysql = new MySQL mysql.insert()*/ //方法二: 将特质动态混入到类中 val mysql = new MySQL with DB mysql.insert() //向数据库中插入数据 } } trait Operate{ def insert(): Unit = { println("插入数据") } } trait DB extends Operate{ override def insert(): Unit = { //具体的实现方法加override print("向数据库中") super.insert() //重写父类中的方法,使用super.去调用它 } } trait File extends Operate{ override def insert(): Unit = { print("向文件中") super.insert() } } /*class MySQL extends DB{ }*/ class MySQL{ }
trait中的方法可以是抽象的,也可以有具体实现,具体的类都含有相应的方法
trait在继承时,从左到右继承,但是调用方法时,从右向左调用
trait方法调用时,super不是上一级(不是它父子的上一级,是上一个)的意思,是上一个trait
如果在super关键字后面增加中括号,其中增加泛型,那么可以调用指定特质的方法
trait在scala中可以继承其他的类,比如继承trait Operate extends Exception
class MySQL extends File with DB
先DB(它的上一级是File)--->File(它的上一级是Operate)--->Operate
向数据库中向文件中插入数据
super[Operate].insert() 是明确的告诉它不走上一个,走上一级
object TestInterface1 { def main(args: Array[String]): Unit = { val mysql = new MySQL mysql.insert() //将特质动态混入到类中 /* val mysql = new MySQL with DB mysql.insert() //向数据库中插入数据*/ } } trait Operate{ def insert(): Unit = { println("插入数据") } } trait DB extends Operate{ override def insert(): Unit = { //具体的实现方法加override print("向数据库中") super[Operate].insert() //重写父类中的方法,使用super.去调用它 } //加[Operate]就是不走上一个,走上一级 ===>向数据库中插入数据 } trait File extends Operate{ override def insert(): Unit = { print("向文件中") super.insert() } } /*class MySQL extends DB{ }*/ /* class MySQL{ }*/ class MySQL extends File with DB{ }
- scala中特质和接口的区别?
在Scala中没有接口的概念,因为接口并不是面向对象的,有的只是特质,当多个对象具有相同的特征(属性或行为)时,就可以声明特质(trait),然后由具体的类来继承,但是由于不是接口,所以不要实现,而是继承,多个特质使用with连接
没有父类 class 类名 extends 特质1 with 特质2 with 特质3 .. 有父类 class 类名 extends 父类 with 特质1 with 特质2 with 特质3
这种方式更灵活,不像java中,实现是多继承,类是单继承;
也可以在构建对象时,动态混入特质,这种方式非常好,可以动态给对象添加功能。
new Mysql with DB
类信息
object TestClassInfo { def main(args: Array[String]): Unit = { //val clazz: Class[_] = Student.getClass //.var // classOf[T] == T.class val studentClass: Class[Student] = classOf[Student] val student = new Student //student.asInstanceOf[Emp] //student.isInstanceOf[Emp] type S = Student val s: S = new S student.+("kris") //可省略 student + "kk",就是拼接字符串的意思 "*" * 2 "*".*(2) //只要是单一的参数都可以把()和.去掉 1.to(2) } }
类似枚举
object ObjectTest extends App { println(123) println(Color.RED) } object Color extends Enumeration{ //java中的枚举(访问对象); K V,通过K可以访问它的value val RED = Value(0, "red") val YELLOW = Value(1, "yellow") }