六、Scala面向对象编程(中级)

目录

 

一、包

1、回顾-Java包的三大作用

2、Scala包的基本介绍

3、包对象

4、包的可见性

5、包的引入

二、面向对象编程方法

1、封装

2、继承

A、基本介绍

B、重写方法

C、Scala中类型检查和转换

D、Scala中超类的构造

E、覆写字段

3、抽象类

4、匿名子类

5、继承层级


一、包

1、回顾-Java包的三大作用

  • 区分相同名字的类
  • 当类很多时,可以很好的管理类
  • 控制访问范围
  • java中包名和源码所在的系统文件目录结构要一致,并 且编译后的字节码文件路径也和包名保持一致。

2、Scala包的基本介绍

和Java一样,Scala中管理项目可以使用包,但Scala中的包的功能更加强大,使用也相对复杂些,下面我们学习Scala包的使用和注意事项

(1)Scala包的三大作用(和Java一样)

  • 区分相同名字的类
  • 当类很多时,可以很好的管理类
  • 控制访问范围
  • Scala中包名和源码所在的系统文件目录结构要可以不一致,但是编译后的字节码文件路径和包名会保持一致(这个工作由编译器完成)。

(2)命名规范:

一般是小写字母+小圆点一般是     com.公司名.项目名.业务模块名

(3)Scala会自动引入的常用包

java.lang.*  
scala包
Predef包

(4)Scala包注意事项和使用细节

scala进行package 打包时,可以有如下形式。

 

/**
  * 1、com.xiaohulu 表示创建了包com.xiaohulu,在{}中,
  * 我们可以继续写他的子包 scala_5_package//com.xiaohulu.scala_5_package,
  * 还可以写特质trait ,object
  * 2、即scala支持在一个文件中,可以同时创建多个包,以及给多个包创建类,trait,object
  * */
package   com.xiaohulu{//包com.xiaohulu
  package scala_5_package{
    import com.xiaohulu.testpackage.{Animal, Machine}
    //包com.xiaohulu.scala_5_package
    object Test{
    def main(args: Array[String]): Unit = {
       val animal = new Animal
      val machine = new Machine
      println(s"animal = ${animal.name},machine = ${machine.name} ")
    }
  }
  }
  package testpackage{//包com.xiaohulu.testpackage
    class Animal{//表示在包com.xiaohulu.testpackage 下创建类Animal
      val name ="cat"
    }
    class Machine{//表示在包com.xiaohulu.testpackage 下创建类Machine
    val name = "Jeep"
    }
  }
}
  • 包也可以像嵌套类那样嵌套使用(包中有包), 这个在前面的第三种打包方式已经讲过了,在使用第三种方式时的好处是:程序员可以在同一个文件中,将类(class / object)、trait 创建在不同的包中,这样就非常灵活了。

  • 作用域原则:可以直接向上访问。即: Scala中子包中直接访问父包中的内容, 大括号体现作用域。(提示:Java中子包使用父包的类,需要import)。在子包和父包 类重名时,默认采用就近原则,如果希望指定使用某个类,则带上包名即可。

  • 父包要访问子包的内容时,需要 import对应的类等

  • 可以在同一个.scala文件中,声明多个并列的package(建议嵌套的pakage不要超过3层)

  • 包名可以相对也可以绝对,比如,访问BeanProperty的绝对路径是: _root_. scala.beans.BeanProperty ,在一般情况下:我们使用相对路径来引入包,只有当包名冲突时,使用绝对路径来处理。如:

class Person {
  //第一种形式
  @BeanProperty var name: String = _
  //第二种形式, 和第一种一样,都是相对路径引入
  @scala.beans.BeanProperty var age: Int = _
  //第三种形式, 是绝对路径引入,可以解决包名冲突
  @_root_. scala.beans.BeanProperty var sex: String = _
}

3、包对象

(1)基本介绍:

可以包含类、对象和特质trait,但不能包含函数/方法或变量的定义。这是Java虚拟机的局限。为了弥补这一点不足,scala提供了包对象的概念来解决这个问题。

package com.xiaohulu {
  /** 包对象*/
  //1、每个包都可以有一个包对象。你需要在父包(com.xiaohulu)中定义它,且名称与子包一样。
  //2、使用package object scala 创建一个包对象scala 他是 com.xiaohulu.scala对应的包对象
  package object scala {
    var name = "jack"
    def sayOk(): Unit = {
      println("package object sayOk!")
    }
  }
  /** 包*/
  package scala {
    class Test {
      def test() : Unit ={
        //这里的name就是包对象scala中声明的name
        println(name)
        sayOk()//这个sayOk 就是包对象scala中声明的sayOk
      }
    }
    object TestObj {
      def main(args: Array[String]): Unit = {
        val t  = new Test()
        t.test()
        //因为TestObje和scala这个包对象在同一包,因此也可以使用
        println("name=" + name)
      }
    }
  }
}

(2)包对象的底层实现机制分析(重点)

  • 当创建包对象后,在该包下生成 public final class package 和 public final class package$
  • 通过 package$ 的一个静态实例完成对包对象中的属性和方法的调用。

(3)包对象的注意事项

  • 每个包都可以有一个包对象。你需要在父包中定义它。
  • 包对象名称需要和包名一致,一般用来对包的功能补充

4、包的可见性

(1)回顾-Java访问修饰符基本介绍

java提供四种访问控制修饰符号控制方法和变量的访问权限(范围):

  • 公开级别:用public 修饰,对外公开

  • 受保护级别:用protected修饰,对子类和同一个包中的类公开

  • 默认级别:没有修饰符号,向同一个包的类公开.

  • 私有级别:用private修饰,只有类本身可以访问,不对外公开.

Java访问修饰符使用注意事项

  • 修饰符可以用来修饰类中的属性,成员方法以及类

  • 只有默认的和public才能修饰类!,并且遵循上述访问权限的特点。

(2)Scala中包的可见性介绍

在Java中,访问权限分为: public,private,protected和默认。在Scala中,你可以通过类似的修饰符达到同样的效果。但是使用上有区别。

object 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)
  }
}
//当一个文件中,出现了class和object 同名时,则class伴生类,object为伴生对象
//scala去掉了static,代替之的是伴生类和伴生对象
//伴生类写费静态内容,伴生对象写静态内容
object Clerk{
  def test(c : Clerk): Unit = {
    //这里体现出在伴生对象中,可以访问c.sal
    println("test() name=" + c.name + " sal= " + c.sal)
  }
}

(3)Scala中包的可见性和访问修饰符的使用

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

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

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

  • protected为受保护权限,scala中受保护权限比Java中更严格,只能子类访问,同包无法访问 (编译器)

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

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

class Person{
//  增加包访问权限后
// 1.private同时起作用。不仅同类可以使用
// 2.同时com.xiaohulu.scala_5_package 中包下其他类也可以使用
//  说明:private也可以变化,比如protected[atguigu], 非常的灵活。
  private[scala_5_package] var name:String = "jack"
}

5、包的引入

  • 在Scala中,import语句可以出现在任何地方,并不仅限于文件顶部,import语句的作用一直延伸到包含该语句的块末尾。这种语法的好处是:在需要时在引入包,缩小import 包的作用范围,提高效率。
  • Java中如果想要导入包中所有的类,可以通过通配符*,Scala中采用下 _
  • 如果不想要某个包中全部的类,而是其中的几个类,可以采用选取器(大括号)
import scala.collection.mutable.{HashMap, HashSet}
  • 如果引入的多个包中含有相同的类,那么可以将不需要的类进行重命名进行区分,这个就是重命名。

import java.util.{ HashMap=>JavaHashMap, List}
import scala.collection.mutable._
var map = new HashMap() // 此时的HashMap指向的是scala中的HashMap
var map1 = new JavaHashMap(); // 此时使用的java中hashMap的别名
  • 如果某个冲突的类根本就不会用到,那么这个类可以直接隐藏掉。

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

二、面向对象编程方法

定义一个类时候,实际上就是把一类事物的共有的属性和行为提取出来,形成一个物理模型(模板)。这种研究问题的方法称为抽象。

面向对象编程有三大特征:封装、继承和多态。

1、封装

(1)封装(encapsulation)就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作

(2)优点

  • 隐藏实现细节
  • 提可以对数据进行验证,保证安全合理

(3)如何体现封装

  • 对类中的属性进行封装
  • 通过成员方法,包实现封装

(4)封装的实现步骤

  • 将属性进行私有化
  • 提供一个公共的set方法,用于对属性判断并赋值
def  setXxx(参数名 : 类型) : Unit = {     
//加入数据验证的业务逻辑     
属性 = 参数名  
}
  • 提供一个公共的get方法,用于获取属性的值
def getXxx() [: 返回类型] = {
    return 属性
}

(5)封装实例

class Person {
  var name: String = _
  //var age ; //当是public时,可以随意的进行修改,不安全
  private var age: Int = _
  private var salary: Float = _
  private var job: String =
  def setAge(age: Int): Unit = {
    if (age >= 0 && age <= 120) {
      this.age = age
    } else {
      println("输入的数据不合理");
      //可考虑给一个默认值
      this.age = 20
    }
  }
}

(6)Scala封装的注意事项和细节

  • Scala中为了简化代码的开发,当声明属性时,本身就自动提供了对应setter/getter方法,如果属性声明为private的,那么自动生成的setter/getter方法也是private的,如果属性省略访问权限修饰符,那么自动生成的setter/getter方法是public的

  • 因此我们如果只是对一个属性进行简单的set和get ,只要声明一下该属性(属性使用默认访问修饰符) 不用写专门的getset,默认会创建,访问时,直接对象.变量。这样也是为了保持访问一致性

  • 从形式上看 dog.food 直接访问属性,其实底层仍然是访问的方法。

  • 有了上面的特性,目前很多新的框架,在进行反射时,也支持对属性的直接反射

2、继承

A、基本介绍

(1)基本概念

继承可以解决代码复用,让我们的编程更加靠近人类思维.当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类(比如Student),在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends语句来声明继承父类即可。

和Java一样,Scala也支持类的单继承

(2)Scala继承的基本语法

class 子类名 extends 父类名  { 类体 }

(3)实例

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中....")
  }
}

(4)Scala继承优点

  • 代码的复用性提高了
  • 代码的扩展性和维护性提高了

(5)scala子类继承了什么

子类继承了所有的属性只是私有的属性不能直接访问, 需要通过公共的方法去访问

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")
  }
  def getN3(): Int ={
    return  n3
  }
}
class Sub extends Base {
  def sayOk(): Unit = {
    this.n1 = 20
    this.n2 = 40
    this.getN3()
    println("范围" + this.n1 + this.n2+this.getN3())
  }
}

B、重写方法

scala明确规定,重写一个非抽象方法需要用override修饰符,调用超类的方法使用super关键字

class Person {
  var name : String = "tom"
  def printName() {
    println("Person printName() " + name)
  }
}
class Emp extends Person {
  //这里需要显式的使用override
  override def printName() {
    println("Emp printName() " + name)
    super.printName()
  }
}

C、Scala中类型检查和转换

(1)基本介绍

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

  • classOf[String]就如同Java的 String.class 。
  • obj.isInstanceOf[T]就如同Java的obj instanceof T 判断obj是不是T类型。
  • obj.asInstanceOf[T]就如同Java的(T)obj 将obj强转成T类型。
object EncapTest {
  def main(args: Array[String]): Unit = {
    //     获取对象类型
    println( classOf[String])
    val s = "zhangsan"
    println(s.getClass.getName)
    //    这种是Java中反射方式得到类型
    println(s.isInstanceOf[String])
    println(s.asInstanceOf[String]) //将s 显示转换成String
//    
    var p = new Person
    val e = new Emp
    p = e //将子类对象赋给父类。
    p.name = "xxx"
    println(e.name)
    p.asInstanceOf[Emp].sayHi()
  }
}

class Person {
  var name : String = "tom"
  def printName() {
    println("Person printName() " + name)
  }
}
class Emp extends Person {
  //这里需要显式的使用override
  override def printName() {
    println("Emp printName() " + name)
    super.printName()
  }
  def sayHi(): Unit = {
    println("#### hi emp class" )
  }
}

(2)类型检查和转换的最大价值在于:可以判断传入对象的类型,然后转成对应的子类对象,进行相关操作,这里也体现出多态的特点。

object EncapTest {
  def main(args: Array[String]): Unit = {
    val stu = new Student
    val emp = new Emp
    test(stu)
    test(emp)
  }
  //  多态代码
  //  在oop中,父类的引用可以接收所有子类的引用,多态(参数多态)
  def test(person: Person):Unit={
//    使用scala中类型检查和转换,来判断调用哪个子类的方法
    if (person.isInstanceOf[Emp]){
//       person.asInstanceOf[Emp] 对person的类型没有任何影响,而是返回的是Emp类型
      person.asInstanceOf[Emp].showInfo()
    }else if (person.isInstanceOf[Student]){
      person.asInstanceOf[Student].cry()
    }else{
      println("类型转换失败")
    }
  }
}
class Person {
  var name : String = "tom"
  def printName() {
    println("Person printName() " + name)
  }
  def sayOk():Unit ={
    println("Person sayOk() " + name)
  }
}
class  Student extends  Person{
  val stuId = 100
  override def printName(): Unit = {
    println("Student printName() " + name)
  }
  def  cry():Unit={
    println(s"Student id = $stuId")
  }
}
class Emp extends Person {
  val empId = 800
  //这里需要显式的使用override
  override def printName() {
    println("Emp printName() " + empId)
    super.printName()
  }
  def showInfo():Unit={
    println(s"Emp id = $empId")
  }
}

D、Scala中超类的构造

(1)Java中超类的构造

从代码可以看出:在Java中,创建子类对象时,子类的构造器总是去调用一个父类的构造器(显式或者隐式调用)。

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() {
        //这里会隐式调用super(); 就是无参的父类构造器A()
        System.out.println("B()");
    }
    public B(String name) {
        super(name);
        System.out.println("B(String name)" + name);
    }
}

(2)Scala中超类的构造

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

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

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

(3)实例

object EncapTest {
  def main(args: Array[String]): Unit = {
    val person = new Person("person")
    val student = new Student("student")
    student.printInfo()
    person.printInfo()
  }
}
class Person(inName: String) { //父类的构造器
  var name:String = inName
  def printInfo(): Unit ={
    println("person printInfo() ="+ name)
  }
}
class Student (stuName: String) extends Person(stuName) {
  val stuId : Int = 20

  override def printInfo(): Unit = {
    println("Student printInfo() ="+ stuId)
    super.printInfo()
  }
}

E、覆写字段

在Scala中,子类改写父类的字段,我们称为覆写/重写字段。覆写字段需使用 override修饰。

在Java中只有方法的重写,没有属性/字段的重写,准确的讲,是隐藏字段代替了重写。

(1)Java中为什么字段不能被重写?

输出的结果如下:

可以看出成员变量不能像方法一样被重写。当一个子类定义了一个跟父类相同 名字的字段,子类就是定义了一个新的字段。这个字段在父类中被隐藏的,是不可重写的。

Java如何访问隐藏字段

  • 采用父类的引用类型,这样隐藏的字段就能被访问了,像上面所给出的例子一样。
  • 将子类强制类型转化为父类类型,也能访问到隐藏的字段。

这个主要涉及到java里面一个字段隐藏的概念,父类和子类定义了一个同名的字段,不会报错。但对于同一个对象,用父类的引用去取值(字段),会取到父类的字段的值用子类的引用去取值(字段)则取到子类字段的值在实际的开发中,要尽量避免子类和父类使用相同的字段名,否则很容易引入一些不容易发现的bug

(2)Java另一重要特性: 动态绑定机制

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;
  }
}
    /**
      * Java动态绑定机制
      * 1、若调用的是方法,则JVM会将方法和对象的内存地址绑定
      * 2、若调用的是一个属性,则没有动态绑定机制,则在哪里调用,就返回对应的值
      */
//    将一个子类对象的地址赋给了父类,
    A obj = new B();
    //40,若将B的sum方法注销,父类中又调用了 getI 方法,
    // getI也是先去B中寻找方法,所以结果为30
    System.out.println(obj.sum());
    //30,若将B的 sum1 方法注销,父类中访问元素 i ,
    // i 则直接去父类中的 i ,所以结果为20
    System.out.println(obj.sum1());

(3)Scala覆写字段入门例子

object EncapTest {
  def main(args: Array[String]): Unit = {
    val obj : A = new B()
    val obj2 : B = new B()
// obj.age 调用obj.age()方法,obj为父类的对象,但是JVM的动态绑定机制
//(原理参考(2)Java另一重要特性: 动态绑定机制),obj实际上是取调用obj2.age()
// obj2.age 调用 obj2.age()方法,直接访问 obj2.age()
    println(s"obj age = ${obj.age} ,obj2 age = ${obj2.age}")//20,20
  }
}
class A {
  val age : Int = 10//会生成public age()方法
}
class B extends A {
  override val age : Int = 20//会生成public age()方法
}

(4)Scala覆写字段的注意事项和细节

  • def只能重写另一个def(即:方法只能重写另一个方法)

  • val只能重写另一个val 属性重写不带参数的def

object EncapTest {
  def main(args: Array[String]): Unit = {
    val bbb = new BBBB
    //该行代码会报错,因为 val 值不可改变,加入可以实现不报错,
    // 则相当于调用了父类的public name_$eq()方法,
    bbb.name = "jack"
//    调用子类的public name()方法,这样就出现了数据的设置和获取方法不一致
    println(bbb.name)
  }
}
class AAAA {
  //底层会生成public name()和 public name_$eq(),若为val 则只会生成public name()方法
  var name: String = ""
}
class BBBB extends AAAA {
  //底层会生成public name(),复写了主类public name(),public name_$eq()没有被复写
  override  val name: String = "jj"
}
object EncapTest {
  def main(args: Array[String]): Unit = {
    val b = new B
    println(s"b.sal = ${b.sal}")//0
    val b2:A = new B
//    访问方法,动态绑定机制,还是去调用子类方法
    println(s"b2.sal = ${b2.sal}")//0
  }
}
class A {
  def sal(): Int = {
    return 10
  }
}
class B extends A {
//  底层会实现一个public sal()方法
  override val sal : Int = 0
}
  • var只能重写另一个抽象的var属性

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

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

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

//若A中有一个抽象的字段,
//1、抽象字段:没有初始化的属性
//2、具有抽象属性的类必须是抽象类
//3、对于抽象属性,底层不会生成对应的属性声明,而是生成两个对应的抽象方法name()和name_eq()
//
abstract class A {
  var name:String//抽象属性
  var age:Int =10
}
class Sub extends A {
//  子类继承抽象的父类,必须满足如下条件之一:子类为抽象类或者重写父类的方法
//  下面代码会生成两个对应的非抽象方法name()和name_eq()
//  override可写可不写,表面上是重写,实际上是实现
  override var name = ""
//  override var age:Int = 10//error,只可以重写抽象属性
}

3、抽象类

(1)基本介绍

  • 在Scala中,通过abstract关键字标记不能被实例化的类。
  • 方法不用标记abstract,只要省掉方法体即可。
  • 抽象类可以拥有抽象字段,
  • 抽象字段/属性就是没有初始值的字段
abstract class Animal{
  var name : String //抽象的字段
  var age : Int // 抽象的字段
  var color : String = "black"
  def cry()
}

(2)抽象类基本语法

抽象类的价值更多是在于设计,是设计者设计好后,让子类继承并实现抽象类(即:实现抽象类的抽象方法)

abstract class Person() { // 抽象类 
  var name: String // 抽象字段, 没有初始化
  def printName // 抽象方法, 没有方法体
}

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

  • 抽象类不能被实例

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

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

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

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

  • 抽象方法和抽象属性不能使用private、final 来修饰,因为这些关键字都是和重写/实现相违背的。

  • 抽象类中可以有实现的方法和抽象属性(子类覆写抽象属性实际上是通过覆写对应测scala生成的方法实现的)

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

4、匿名子类

(1)基本介绍

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

(2)java匿名子类使用

abstract class A2{
    abstract public   void cry();
}
A2 obj = new A2() {
            @Override
            public void cry() {
                System.out.println("okook!");
            }
};

(3)scala匿名子类使用

object EncapTest {
  def main(args: Array[String]): Unit = {
    var monster = new Monster {
      override var name: String = "牛魔王"
      override def cry(): Unit = {
        println("牛魔王哼哼叫唤..")
      }
    }
    monster.cry()
  }
}
abstract class Monster{
  var name : String
  def cry()
}

5、继承层级

(1)Scala继承层级一览图

  • subtype : 子类型

  • implicit Conversion 隐式转换

  • class hierarchy : 类层次

(2)继承层级图细节

  • 在scala中,所有其他类都是AnyRef的子类,类似Java的Object。

  • AnyVal和AnyRef都扩展自Any类。Any类是根节点

  • Any中定义了isInstanceOf、asInstanceOf方法,以及哈希方法等

  • Null类型的唯一实例就是null对象。可以将null赋值给任何引用,但不能赋值给值类型的变量

  • Nothing类型没有实例。它对于泛型结构是有用处的,举例:空列表Nil的类型是List[Nothing],它是List[T]的子类型,T可以是任何类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值