Scala语言学习笔记——面向对象编程

本文围绕Scala展开,介绍了Scala代码的反编译,揭示伴生对象底层是单例类。阐述了Scala面向对象特性,包括类定义、属性、构造器等。还讲解了包的使用、面向对象编程的继承等内容,以及伴生对象的概念和apply方法,方便对象初始化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.Scala代码的反编译

/**
  * @author huleikai
  * @create 2019-05-17 18:41
  */
object TestScala {
  def main(args: Array[String]): Unit = {
    println("Hello Scala")
  }
}

  上面的Scala中的伴生对象的代码,我们通过反编译来看看它底层的源码的神秘面纱,如下所示:

/**
 * @author huleikai
 * @create 2019-05-17 18:43
 */
public class TestScala {
    public static void main(String[] paramArrayOfString) {
        //TestScala..MODULE$.main(paramArrayOfString);
        TestScala$.MODULE$.main(paramArrayOfString);
    }
}
//我们可以理解scala在运行时,做了一个包装,我们平时在object伴生对象中的代码其实最终在这儿
final class TestScala$ {
    public static final TestScala$ MODULE$;

    static {
        MODULE$ = new TestScala$();
    }

    public void main(String[] args) {
        System.out.println("Hello Scala");
    }
    private TestScala$() { MODULE$ = this; }
}

  从上面反编译的结果,我们可以看出,其实我们在Scala中的伴生对象,其实底层就是一个被final修饰的类,并且这个类是单例的!

2.Scala 面向对象

  Scala语言是面向对象的。Scala语言来自于Java,所以天生就是面向对象的语言,由于历史原因,Java中还存在着非面向对象的内容例如:基本类型(比如 int、float) ,null,静态方法等,但是Scala是纯粹的面向对象语言,即在Scala中,真正的实现一切皆为对象!

2.1 Scala中定义类

    在 Scala 中,类并不用声明为 public。在 Scala 文件中,文件名可以不用和类名一致,Scala 源文件中可以包含多个类,所有这些类都具有公有可见性,即这些类默认都是被public修饰的。

 类定义语法class 类名(构造器参数列表) {}

class Student(sid:Int, var sname:String){}

2.2 Scala类中的属性

/**
  * @author huleikai
  * @create 2019-05-20 12:20
  */
object Student {
  def main(args: Array[String]): Unit = {
    val student = new Student()
    student.name="Tom"
    student.age=18
    student.gender="男"
    printf("学生:\n 姓名: %s 性别: %s 年龄: %d",student.name,student.gender,student.age)
  }
}
class Student{
  var name:String = _
  var age:Int = _
  var gender:String = _
}

  我们通过反编译工具反编译出Student.class类,其底层内容如下所示:

public class Student {
  private String name;
  private int age;
  private String gender;

  public static void main (String[] paramArrayOfString) {
    Student..MODULE$.main(paramArrayOfString);
  }

  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 String gender() {
    return this.gender;
  }
  public void gender_$eq(String x$1) {
    this.gender = x$1;
  }
}

   通过反编译字节码文件,我们可以看出,当我们在Scala中声明一个class类,底层该类默认的修饰符为public,在Scala类中声明的属性,默认的修饰符则为private私有的,那为什么我们可以在Scala的伴生对象中直接使用对象.属性名的方式进行该属性值得赋值及获取呢?通过反编译文件我们可以清晰的知道,当我们在Scala类中声明一个属性时,Scala的底层会同时帮我们生成该属性类似Java中的getter和setter方法,唯一的区别就是生成的getter和setter方法名与Java中的有些不同,例如:Student中的name属性,Scala底层会帮我们生成一个名为name_$eq(String x$1)name()的两个方法,这两个方法对应Java中的setName(String name)和getName(),而当我们在object伴生对象中调用该属性时,其实底层调用的是对应的这两个方法。

 Scala类属性的声明方式:

class Student {
  //用val修饰的变量是只读属性,有getter但没有setter,相当与Java中用final修饰的变量
  val id = "100"
  //用var修饰的变量既有getter又有setter
  var age: Int = 18
  //类私有字段,只能在当前类的内部或伴生对象object中使用
  private var name: String = "黄渤"
  //被private[this]修饰的变量或方法只能在本类中访问使用,它的同名伴生对象都不能访问
  private[this] val nickname = "影帝"
}

 ① 在Scala类中,被val修饰的属性是只读属性,即只有getter方法但没有setter方法,相当于Java中使用final修饰的变量

 ② 在Scala类中,被var修饰的属性既有getter方法也有setter方法

 ③ 在Scala类中,被private修饰的类私有属性,只能在当前类的内部或者伴生对象object中使用,其他地方无法访问

 ④ 在Scala类中,被private[this]修饰的类属性或者方法只能在本类中访问使用,即使它的同名伴生对象都不能访问

 ⑤ 在Scala的属性上加上@BeanProperty这个注解,会自动生成Java规范的 setXxx/getXxx 方法,并且对原来底层自动生成类似xxx(),xxx_$eq()方法,没有冲突,二者可以共存

注意:在Scala类中被private修饰的属性,跟普通属性底层都是一样的会生成两个类似getter和setter方法,那么Scala是怎么控制该私有属性的访问权限呢?答案是其底层会将Scala中使用private修饰的变量名采用包名的方法进行声明,例如:我声明的Student.class的包名是com.hlk.scala.Student,那么在Student类中声明的被private修饰的属性名则为:private String com$hlk$scala$Student$$name

2.3 Scala类构造器

   在Scala中,构造器分为主构造器和辅助构造器,主构造器只能有一个,但是辅助构造器可以有一个或者根据参数列表重载多个,下面小亦就带领大家来看看Scala语言中的主构造器和辅助构造器:

① 主构造器

  每个类都有主构造器,主构造器的参数直接放置类名后面,与类交织在一起,主构造器会执行类定义中的所有语句,当在创建对象时,需要进行相关初始化操作时,可以将初始化语句放在主构造器{}中,同样也可以在类中添加或重写相关方法。

  主构造器中直接被val或者var修饰的变量,就是这个类的成员变量。如果没有被var或者val修饰,那么,就相当于是这个主构造器的一个普通参数,但是这个普通参数如果被 {} 中的某个方法使用了的话,那么就会提升成一个被 private[this] val 修饰的本类私有成员变量主构造器中的代码无论如何在构造一个对象的时候都被会执行

class Student(sid:Int, var sname:String, val myid:Int, 
private var mynickname:String,private[this] var gender:String){}

 注意:现在的这个sid就是一个主构造器的一个普通参数,如果这个参数被{}内的任意一个方法使用,那么此时这个参数就提升为一个只能被本类访问的私有成员变量private[this] val sid,声明为private[this] var gender:String这个参数被var修饰,那么它是一个引用可变的成员变量,然后又被private[this]修饰,所以它只能在本类中被访问使用,就连同名的伴生对象也不可访问。

② 辅助构造器

   如果禁用掉了主构造器(即主构造器被 private 修饰),则必须使用辅助构造函数来创建对象。 类的主构造器声明为:

class Queen private(val name: String, prop: Array[String], private var age: Int = 18){}

  私有的主构造器只能在同名的伴生对象中调用创建对象,在其他地方都不可以使用主构造器new创建该类的对象,所以此时就需要定义一个辅助构造器,在别的地方进行使用。在辅助构造器中,定义的变量不能指定权限修饰符在辅助构造器中定义的构造器参数不能被var或val修饰辅助构造器中的参数列表必须包含主构造器的中声明的构造参数,且不能和主构造器的参数完全一样

 辅助构造函数具有两个特点: 

    a.辅助构建器的名称为 this,Java 中的辅助构造函数与类名相同,这常常会导致修改类名时出现不少问题,Scala 语言避免了这样的问题

    b.调用辅助构造函数时,必须先调用主构造函数或其它已经定义好的构造函数

class Student private(name: String, age: Int, private[this] var gender: String) {
  private[this] var salary:Double = _
  //用this关键字定义辅助构造器
  def this(name: String, age: Int, gender: String, salary: Double) {
    //每个辅助构造器必须以主构造器或其他的辅助构造器的调用开始
    this(name, age, gender)
    this.salary = salary
    println("执行辅助构造器")
  }
}

3.Scala中的包package

3.1 Scala中包的可见性和访问修饰符的使用

① 当属性访问权限为默认时,从底层看属性是private的,但是因为提供了xxx_$eq()[类似setter]/xxx()[类似getter] 方法,因此从使用效果看是何地方都可以访问

② 当方法访问权限为默认时,默认为public访问权限

③ private为私有权限,只在类的内部伴生对象中可

④ protected为受保护权Scala中受保护权限比Java中更严格,只能子类访问,同包无法访问

⑤ 在Scala中没有public关键字,不能用public显式的修饰属性和方法

 包访问权限(表示属性有了限制,同时包也有了限制)点和Java不一样,体现出Scala包使用的灵活性

package com.hlk.scala
class Person {
/**
 * 增加包访问权限后
 * 1.private同时起作用,不仅同类可以使用 
 * 2.同时com.hlk.scala中包下其他类也可以使用
 */
  private[scala] val name="hello"
  //当然,也可以将可见度延展到上层包,private也可以变化,比如protected[hlk], 非常的灵活
  private[hlk] val description="zhangsan"
}

3.2 Scala入包的细节和注意事项

① 在Scala中,import语句可以出现在任何地方,并不仅限于文件顶部,import语句作用直延伸到包含该语句的块末尾。这种语法的好处是:在需要时在引入包,缩小import 包的作用范围,提高效率

② Java中如果想要导入包中所有的类,可以通过通配符 * Scala中采用

 果不想要某个包中全部的类,而是其中的几个类,可以采用选取器(号{})

def testPackage(): Unit = {
    import scala.collection.mutable.{HashMap, HashSet}
    var map = new HashMap()
    var set = new HashSet()
}

④ 如果引入的多个包中含有相同的类,那么可以将不需要的类进行重命名进行区分,这个就是重命名

import java.util.{ HashMap=>JavaHashMap, List}
import scala.collection.mutable._
var map = new HashMap()   // 此时的HashMap指向的是Scala中的HashMap
var map1 = new JavaHashMap();  // 此时使用的java中hashMap的别名

⑤ 果某个冲突的类根本就不会用到,那么这个类可以直接隐藏

import java.util.{ HashMap=>_, _} //含义为引入java.util包的所有类,但是忽略HahsMap类
//此时的HashMap指向的是scala中的HashMap, 而且idea工具的提示也不会显示java.util的HashMaple
var map = new HashMap() 

4.Scala中的面向对象编程-继承、封装、多态

4.1 Scala中类型检查和转换

  在Scala中,要测试某个对象是否属于某个给定的类,可以用isInstanceOf方法,asInstanceOf方法将引用转换为子类的引用。classOf获取对象的类名。

 ① classOf[String]就如同JavaString.class

 ② obj.isInstanceOf[T]就如同Javaobj instanceof T  判断obj是不是T类型

 ③ obj.asInstanceOf[T]就如同Java(T)obj  将obj强转成T类型

//获取对象类型
println(classOf[String])
val s = "zhangsan"
println(s.getClass.getName)
//这种是Java中反射方式得到类型
println(s.isInstanceOf[String])
println(s.asInstanceOf[String]) //将s显示转换成String
var p = new Person2
val e = new Emp
p = e //将子类对象赋给父类
p.name = "xxx"
println(e.name)
p.asInstanceOf[Emp].sayHi()

4.2 Scala中父类的构造器

① 类有一个主构造器和任意数量的辅助构造器,而每个辅助构造器都必须先调用主构造(也可以是间接调用)

class Person {
  var name = "zhangsan"
  println("Person...")}
class Emp extends Person {
  println("Emp ....")
  def this(name : String) {
    this // 必须调用主构造器
    this.name = name
    println("Emp 辅助构造器~")
  }
}

② 主构造器可以调用父类的构造器。辅助构造器不能直接调用父类的构造器。在Scala的构造器中,你不能调用super(params)

class Person(name: String) { //父类的构造器
}
class Emp (name: String) extends Person(name) { //将子类参数传递给父类构造器,这种写法√
  //super(name)     (×) 没有这种语法
  def  this() {
    super("abc") // (×)不能在辅助构造器中调用父类的构造器
  }
}

4.3 Scala中重写父类属性

  在Java中只有方法的重写,没有属性/字段的重写。在Scala中,子类改写父类的字段,我们称为覆写/重写字段。写字段需使用 override修饰。

class A {
  val age : Int = 10
}
class B extends A {
  override val age : Int = 20
}
val obj1 : A = new B()
val obj2 : A = new A()
val obj3 : B = new B()
printf("obj1: %d , obj2: %d, obj3: %d",obj1.age,obj2.age,obj3.age) //obj1: 20 , obj2: 10, obj3: 20

 ① 一个属性没有初始化,那么这个属性就是抽象属性

 ② 抽象属性在编译成字节码文件时,属性并不会声明,但是会自动生成抽象方法,所以类必须声明为抽象类

 ③ 如果是覆写一个父类的抽象属性,那么override 关键字可省略 [原因:父类的抽象属性,生成的是抽象方法,因此就不涉及到方法重写的概念,因此override可省略]

4.4 Scala中的抽象类

  抽象类基本语法:

   abstract class Person() { // 抽象类

      var name: String   // 抽象字, 没有初始化

      def printName   // 抽象方, 没有方法体

   }

 Scala抽象类使用的注意事项和细节讨论:

 ① 抽象类不能被实例化

 ② 象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract抽象

 ③ 一旦类包含抽象方法或者抽象属性,则这个类必须声明为abstract

 ④ 抽象方法不能有主体,不允许使用abstract修饰

 ⑤ 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法和抽象属性除非它自己也声明为abstract

 ⑥ 抽象方法和抽象属性能使用privatefinal 修饰,因为这些关键字都是和/实现违背的

 ⑦ 抽象类中可以有实现的方法

 ⑧ 子类重写抽象方法不需要override,写上也不会错

 ⑨ 在子类中覆盖抽象类的抽象方法时,可以指定 override 关键字也可以不指定,但是在子类中重写抽象父类中已实现的非抽象方法时,必须指定override关键字

 ⑩ 抽象类中声明的抽象字段,子类在继承抽象父类时必须在子类自己的构造器或大括号{}中对抽象字段进行初始化或重写

  

4.5 Scala 匿名子类

  Scala和Java一样,可通过包含带有定义或重写的代码块的方式创建一个匿名的子

abstract class Monster {
  var name: String
  def cry()
}
var monster = new Monster {
  override var name: String = "牛魔王"
  override def cry (): Unit = {
  println ("牛魔王哼哼叫唤..")
  }
}

4.6 Scala 继承 

    Scala中的继承和Java中的基本一致,关键词也是extends, 在子类覆盖父类方法之后,如果我们在子类中就是要调用父类的被覆盖的方法,就可以使用 super 关键字,显式地指定要调用父类的方法。

5.Scala静态概念——伴生对象

   Java中有静态属性也有静态方法以及静态类,那Scala中有静态类或者静态属性方法吗?在某些应用场景下,我们可能不需要创建对象,而是想直接调用方法,但是 Scala 语言并不支持静态成员,没有静态方法和静态字段,Scala 通过单例对象 object 来解决该问题,因为在object对象中声明的所有成员都是静态成员,可以直接通过object名.成员名的方式调用,跟Java中类的静态成员使用一样,需要注意的是,object中默认声明的成员变量或方法的权限修饰符为public,可以在其他的非同名的class或非同名的object中通过object.成员名方式调用,但是权限修饰符为private或者protected的成员,在其他非同名的class或非同名的object中不能访问使用,但是在该object同名的伴生类中可以使用该object中声明为任何权限修饰符的成员。 因为不需要去 new,所以object本身就是一个单例对象。

 伴生对象的小结:

 ① Scala中伴生对象采用object关键字声明,伴生对象中声明的全"静态"容,可以通过伴生对象名称直接调

 ② 生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一

 ③ 伴生对象中的属性和方法都可以通伴生对象名(类名)直接调用访

 ④ 语法角度来讲,所谓的伴生对象其实就是类的静态方法和成员的集

 ⑤ 从技术角度来讲,Scala还是没有生成静态的内容,object伴生对象中声明的属性或方法,底层仍然是一个非static静态的属性或方法,只不过是将伴生对象生成了一个新的类进行了封装,实现属性和方法的调用

 ⑥ 从底层原理看,伴生对象实现静态特性是依赖于 public static final  MODULE$ 实现的

 ⑦ 伴生对象的声明应该和伴生类的声明在同一个源码文件(如果不在同一个文件中会运行错误)但是如果没有伴生类,也就没有所谓的伴生对象了,所以放在哪里就无所谓

 ⑧ 如果 class A 独立存在,那么A就是一个类, 如果 object A 独立存在,那么A就是一个"静态"性质的对象[类对象], object A中声明的属性和方法可以通过 A.属性 和 A.方法 来实现调用

 ⑨ 在Scala中,如果声明一个Person.class伴生类,再声明一个同名的伴生对象object Person,底层会生成Person.class和Person$.class两个文件,其中Person$.class就是object Person伴生对象生成的字节码文件,如果只声明一个名为Person的object伴生对象,没有伴生类,底层也会生成Person.class和Person$.class两个文件

伴生对象-apply方法:

  在伴生对象中定义apply方法,可以实现:val 对象名 = 类名(参数...) 方式来创建对象实例:

  

   在Scala集合和数组中,可以通过 val intList = List(1,2,3) 这种方式创建初始化一个列表对象,其实它相当于调用 val in = List.apply(1,2,3)方法,只不过 val intList = List(1,2,3) 这种创建方式更简洁一点,但我们必须明确的是这种创建方式仍然避免不了 new,它后面的实现机制仍然是 new 的方式,只不过我们自己在使用的时候可以省去 new 的操作。通常我们会在类的伴生对象中定义 apply 方法,当遇到【类名(参数 1,...参数 n)】时 apply 方法会自动被调用。总之apply方法主要是用来方便用户去初始化一些对象时更加方便简洁。 

注:如果博主的博客对你有所帮助,记得帮忙点个赞,因为整理不易啊!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值