Scala学习-包、类、特质和权限修饰符等

接下来记录一下scala面向对象的相关知识,包括包、类、抽象类、特质和权限修饰符相关的内容。

scala中,包package的声明比较灵活,可以对比java,如果是java,包的声明必须放在文件最前面。

(1)包结构可以分开写,以下两种方式都可以。

//package clyang.oop.packagex
package clyang
package oop
package packagex

(2)scala文件中可以声明多个包,如果声明位置不在文件的最前面,需要使用大括号。如声明package a,它不是写在文件首行,需要添加{}在后面,这样的包是普通包,里面是不能定义函数的,即使定义了函数编译也不会通过。如果想在package里定义函数,需要使用包对象,参考package object b,它的类型就是package object,可以在里面定义函数,并且可以调用包对象中的函数。

package clyang
package oop
package packagex

//package可以写成上面的格式

/**
 * 如果一个包中想定义函数,需设置为包对象
 */
object PackageDemo1 {
  def main(args: Array[String]): Unit = {
      //对用包对象中的方法
      println(b.sum(1,2))
  }
}

//1 普通包
package a{
  //普通包中不能定义函数,编译报错
  //def sum(i:Int,j:Int)=i+j
}

//2 包对象
package object b{
  //包对象中才可以定义函数
  def sum(i:Int,j:Int)=i+j
}

main方法中调用包对象中sum方法后。

3

(3)包名起名应该尽量简短,符合scala的write less的风格.

//原始包名
package clyang.oop.packagex
//简短的写法
package c.o.p

(4)导包时,如果用到同一个包下的多个类,会将这些类放到{}中,不像java每行一个类。如下,当使用了java.util包下的Date和Scanner,会将它们放到一起,java中的话就是平展开了。

导包时如果导入了同一个包下的多个类,可以使用通配符下划线'_'来代替,类似java中的'*'。

package clyang.oop.packagex

import java.util.{Date, Scanner} //导入一个包下的多个类,放到{}中
//import java.util._ //通配符下划线的使用

object PackageDemo2 {
  def main(args: Array[String]): Unit = {
    var Date=new Date()
    var scan=new Scanner(System.in)
  }
}

(5)当导入不同包下的同名类时,为了代码中更好的区分同名类和具有可读性,可以给类设置别名。

package clyang.oop.packagex

//可以为同名类取别名来区分
import java.util.{Date => utilDate} 
import java.sql.{Date => sqlDate} 

object PackageDemo2 {
  def main(args: Array[String]): Unit = {
    //使用时不会引起误解
    var utilDate=new utilDate()
    var sqlDate=new sqlDate(2019,12,26)
  }
}

(6)scala中import语句位置比较灵活,可以放在任意位置,如上面的代码可以修改为如下形式,效果一样。

package clyang.oop.packagex

import java.util.{Date => utilDate} 

object PackageDemo2 {
  def main(args: Array[String]): Unit = {
    
    var utilDate=new utilDate()
    //import语句可以定义在这里
    import java.sql.{Date => sqlDate} 
    var sqlDate=new sqlDate(2019,12,26)
  }
}

scala中也有类的定义,类似java,类是是用于创建对象的蓝图或范本,关于scala类的知识点,总结如下。另外为了更好的理解类的信息,使用了反编译,将scala编译后的class文件反编译成java文件,其中反编译软件使用Luyten。

(1)如果一个类中没有任何的属性和方法,则这个类的大括号{}可以省略不写。

(2)类中定义的属性,需要给定一个初始值,不像java直接不给值编译也不报错,但是scala中必须给,当不确定初始值的时候,用下划线'_'来代替。

package clyang.oop.classx

object ClassDemo1 {
  def main(args: Array[String]): Unit = {
    var s=new Star
    s.name="messi"
  }
}

class Star{
  //当属性值不确定时,可以给定下划线代替
  var name:String=_
  var score:Int=_
  private var assist:Int=_
}
//类中没有任何属性和方法,可以省略大括号
class Club

(3)scala中定义的属性,默认是public,scala代码编译后也会变成.class文件,反编译成java文件后,可以看出属性值都会使用private修饰,并对外提供public修饰的get、set方法(name和score属性)。如果属性值使用private修饰,则get、set方法使用private修饰(assist属性),即不对外不提供访问。

以下是Star类反编译后的代码。

package clyang.oop.classx;

import scala.reflect.*;

@ScalaSignature(bytes = "略")
public class Star
{   
    //一律使用private修饰
    private String name;
    private int score;
    private int assist;
    //提供get方法
    public String name() {
        return this.name;
    }
    //提供set方法,只是set方法名为name_$eq
    public void name_$eq(final String x$1) {
        this.name = x$1;
    }
    
    public int score() {
        return this.score;
    }
    
    public void score_$eq(final int x$1) {
        this.score = x$1;
    }
    //private修饰的属性,set、get方法使用private修饰,对外不提供访问
    private int assist() {
        return this.assist;
    }
    
    private void assist_$eq(final int x$1) {
        this.assist = x$1;
    }
}

以下是ClassDemo1反编译后的代码,可以看出当scala中写成s.name="messi时",实际上是执行了set方法。

package clyang.oop.classx;

public final class ClassDemo1$
{
    public static final ClassDemo1$ MODULE$;
    
    static {
        new ClassDemo1$();
    }
    
    public void main(final String[] args) {
        final Star s = new Star();
        //调用了name_$eq方法,实际就是set方法
        s.name_$eq("messi");
    }
    
    private ClassDemo1$() {
        MODULE$ = this;
    }
}

(4)scala中定义有参构造方法的方式和java中不太一样,可以直接在类后面直接给定参数,这样给定后的参数默认就是类的属性,类中无需再次定义,如name和score属性无需在类中再定义一次了。

package clyang.oop.classx

object ClassDemo2 {
  def main(args: Array[String]): Unit = {
    //使用构造方法来创建对象
    var s=new BigStar("messi",18)
    s.printInfo(20)
  }
}

//构造方法
protected class BigStar(name:String,score:Int){
  //var name:String=_
  //var score:Int=_

  //这个过滤条件,在反编译后会添加到有参数构造方法中
  if (score<0)
    throw new IllegalArgumentException

  //定义一个函数
  def printInfo(assist:Int): Unit ={
    //score=30 //提示给一个常量赋值
     println(name+" has assists is "+assist)
  }
}

//控制台
messi has assists is 20

Process finished with exit code 0

通过反编译后发现,构造方法的参数name和score是final修饰的。如果在类中在定义一个普通函数printInfo,反编译后发现其参数assist也是使用final修饰,在代码中如果想对参数score赋值30会提示不能给一个常量赋值。这样scala中无论是构造函数还是普通函数,里面的参数默认都是常量

另外对score的过滤条件,反编译后也会进入构造方法。

package clyang.oop.classx;

import scala.reflect.*;
import scala.*;
import scala.collection.mutable.*;
import scala.runtime.*;

@ScalaSignature(bytes = "略")
public class BigStar
{
    private final String name;
    //普通函数的参数,也使用final修饰
    public void printInfo(final int assist) {
        Predef$.MODULE$.println((Object)new StringBuilder().append((Object)this.name).append((Object)" has assists is ").append((Object)BoxesRunTime.boxToInteger(assist)).toString());
    }
    //name和score使用final修饰
    public BigStar(final String name, final int score) {
        this.name = name;
        //过滤条件进入构造方法
        if (score < 0) {
            throw new IllegalArgumentException();
        }
    }
}

(5)scala中有主构造器和辅助构造器的概念,主构造器用于创建对象(一般不给参数),辅助构造器用于给对象的属性赋值,并且辅助构造器还能重载。

为了更好的理解,需要借助业务场景,如果定义一个FootballPlayer类,在初始化创建对象的时候,就需要给定能力值(属性,如技巧值,体力值,身高体重等),同时这个对象在随后的岁月中,还可以变化其能力值,就需要能对属性值能进行修改。如果使用上面创建对象的方式,在类后定义属性,就把能力值定死了,怎么改也改不了。这个时候就需要借助主构造器和辅助构造器一起完成,参考如下代码。

其中类后面的括号,代表无参数主构造器,里面的的this(args...)代表辅助构造器,并且辅助构造器中还需要调用主构造器先创建对象,随后才能对对象中的属性值赋值。并且类还提供了changeValue方法,可以对属性值进行修改。通过控制台可以看出实现了业务场景。

package clyang.oop.classx

object ClassDemo3 {
  def main(args: Array[String]): Unit = {
    var f=new FootballPlayer("clyang",77,80,171)
    println(f)
    //过了5年
    f.changeValue(5)
    println(f)
  }
}

class FootballPlayer(){//主构造器
  //属性
  var name:String=_
  var skillValue:Int=_
  var powerValue:Int=_
  var height:Double=_

  //辅助构造器
  def this(name:String,skillValue:Int,powerValue:Int,height:Double){
    //调用主构造器
    this()
    this.name=name
    this.skillValue=skillValue
    this.powerValue=powerValue
    this.height=height
  }

  //可以修改属性值
  def changeValue(year:Int): Unit ={
    if(year<=10){
      this.skillValue+=1*year
      this.powerValue+=1*year
    }else{
      this.skillValue-=1*year
      this.powerValue-=1*year
    }
  }

  //toString方法
  override def toString = s"FootballPlayer($name, $skillValue, $powerValue, $height)"
}

//控制台
FootballPlayer(clyang, 77, 80, 171.0)
FootballPlayer(clyang, 82, 85, 171.0)

Process finished with exit code 0

以下是FootballPlayer反编译后的代码,可以看出主构造器对应无参构造方法,辅助构造器对应有参构造方法,并且有参构造方法中先创建对象,然后再给对象赋值,底层调用了set方法。普通函数changeValue,底层也是调用了set和get方法。

package clyang.oop.classx;

import scala.reflect.*;
import scala.*;
import scala.collection.*;
import scala.runtime.*;

@ScalaSignature(bytes = "略")
public class FootballPlayer
{
    private String name;
    private int skillValue;
    private int powerValue;
    private double height;
    //get set
    public String name() {
        return this.name;
    }
    
    public void name_$eq(final String x$1) {
        this.name = x$1;
    }
    
    public int skillValue() {
        return this.skillValue;
    }
    
    public void skillValue_$eq(final int x$1) {
        this.skillValue = x$1;
    }
    
    public int powerValue() {
        return this.powerValue;
    }
    
    public void powerValue_$eq(final int x$1) {
        this.powerValue = x$1;
    }
    
    public double height() {
        return this.height;
    }
    
    public void height_$eq(final double x$1) {
        this.height = x$1;
    }
    
    //普通函数,调用了get set
    public void changeValue(final int year) {
        if (year <= 10) {
            this.skillValue_$eq(this.skillValue() + 1 * year);
            this.powerValue_$eq(this.powerValue() + 1 * year);
        }
        else {
            this.skillValue_$eq(this.skillValue() - 1 * year);
            this.powerValue_$eq(this.powerValue() - 1 * year);
        }
    }
    
    @Override
    public String toString() {
        return new StringContext((Seq)Predef$.MODULE$.wrapRefArray((Object[])new String[] { "FootballPlayer(", ", ", ", ", ", ", ")" })).s((Seq)Predef$.MODULE$.genericWrapArray((Object)new Object[] { this.name(), BoxesRunTime.boxToInteger(this.skillValue()), BoxesRunTime.boxToInteger(this.powerValue()), BoxesRunTime.boxToDouble(this.height()) }));
    }
    //主构造器对应无参构造方法
    public FootballPlayer() {
    }
    
    //辅助构造器对应有参构造方法
    public FootballPlayer(final String name, final int skillValue, final int powerValue, final double height) {
        //先调用无参数构造方法
        this();
        //set get修改值
        this.name_$eq(name);
        this.skillValue_$eq(skillValue);
        this.powerValue_$eq(powerValue);
        this.height_$eq(height);
    }
}

(6)辅助构造器可以重载,上面的例子,可以在FootballPlayer类中添加一个属性,忠诚度属性,添加一个辅助构造方法,构成重载。当传入的参数只有一个值时,就匹配新增的辅助构造器,因此控制台打印出"FootballPlayer(null, 0, 0, 0.0, 100)"。

package clyang.oop.classx

object ClassDemo3 {
  def main(args: Array[String]): Unit = {
    var f=new FootballPlayer("clyang",77,80,171)
    println(f)
    f.changeValue(5)
    println(f)
    //辅助构造器使用
    var f2=new FootballPlayer(100)
    println(f2)
  }
}

class FootballPlayer(){
  //原属性
  var name:String=_
  var skillValue:Int=_
  var powerValue:Int=_
  var height:Double=_

  //辅助构造器
  def this(name:String,skillValue:Int,powerValue:Int,height:Double){
    this()
    this.name=name
    this.skillValue=skillValue
    this.powerValue=powerValue
    this.height=height
  }

  //添加一个属性 忠诚度
  var loyalty:Int=_
  //辅助构造器可以重载
  def this(loyalty:Int){
    this()
    this.loyalty=loyalty
  }

  def changeValue(year:Int): Unit ={
    if(year<=10){
      this.skillValue+=1*year
      this.powerValue+=1*year
    }else{
      this.skillValue-=1*year
      this.powerValue-=1*year
    }
  }
  
  override def toString = s"FootballPlayer($name, $skillValue, $powerValue, $height, $loyalty)"
}

//控制台
FootballPlayer(clyang, 77, 80, 171.0, 0)
FootballPlayer(clyang, 82, 85, 171.0, 0)
FootballPlayer(null, 0, 0, 0.0, 100)

Process finished with exit code 0

(7)一般来说主构造器是不给参数的,也可以给参数,参考如下代码的矩形类。

可以定义一个正方形类来继承矩形类,并且可以重写父类方法,对于java来说没有重写属性的说话,scala中还可以对同名属性进行重写。另外java中支持多继承,但是scala中只支持单继承。

package clyang.oop.classx

object ClassDemo4 {
  def main(args: Array[String]): Unit = {
    //向上造型,主要手动指定类型
    var s:Rectangle=new Square(4)
    println(s.getGirth) //16
    println(s.name) //正方形
  }
}

//矩形类
class Rectangle(x:Int,y:Int){
  //方法
  def getGirth=2*(x+y)
  //属性
  val name:String="长方形"
}

//单继承,再定义一个正方形来求面积
class Square(x:Int) extends Rectangle(x,x){
  //override关键字,重写父类方法
  override def getGirth=4*x
  //当存在和父类同名的属性时,也需要使用override关键字
  override val name:String="正方形"
}

查看反编译代码,发现除了方法被重写,还重写了属性的get方法(name方法),并且属性在构造方法里被替换。

Rectangle类反编译

package day02.clyang.oop.classx;

import scala.reflect.*;

@ScalaSignature(bytes = "略")
public class Rectangle
{
    private final int x;
    private final int y;
    private final String name;
    
    public int getGirth() {
        return 2 * (this.x + this.y);
    }
    
    public String name() {
        return this.name;
    }
    
    public Rectangle(final int x, final int y) {
        this.x = x;
        this.y = y;
        this.name = "\u957f\u65b9\u5f62";//长方形
    }
}

Square类反编译

package day02.clyang.oop.classx;

import scala.reflect.*;

@ScalaSignature(bytes = "略")
public class Square extends Rectangle
{
    private final int x;
    private final String name;
    
    @Override
    public int getGirth() {
        return 4 * this.x;
    }
    
    @Override
    public String name() {
        return this.name;
    }
    
    public Square(final int x) {
        super(this.x = x, x);
        this.name = "\u6b63\u65b9\u5f62";//正方形
    }
}

(8)scala中没有static关键字,如果要实现类似java中静态的效果,可以将方法或者属性定义在object里。如下代码中可以Cal.add(1,2)来直接调用函数,有类似java中工具类调用静态方法的味道。

package clyang.oop.staticy

/**
  * object中所有方法和属性均为静态
  */
object StaticDemo1 {
  def main(args: Array[String]): Unit = {
    //直接调用,有静态的效果
    println(Cal.add(1,2))
  }
}

object Cal{//可以通过反编译查看到类用final修饰
  //可以通过反编译查看到方法用static修饰
  def add(i:Int,j:Int)=i+j
}

通过反编译查看发现,object Cal反编译后会形成两个文件,分别是Cal.class和Cal$.class。

Cal.class

这个类可以看出,add方法被static修饰,说明是一个静态方法,静态方法里调用了Cal$...。

package clyang.oop.staticy;

import scala.reflect.*;

@ScalaSignature(bytes = "略")
public final class Cal
{
    public static int add(final int i, final int j) {
        return Cal$.MODULE$.add(i, j);
    }
}

Cal$.class

继续查看Cal$.class的内容,其相比Cal.class更加丰满,发现Module...就是当前对象,上面调用Module...的add方法,说明使用了对象的add方法。虽然调用object的方法,看似是静态方法,其实还是调用对象的方法,这也符合scala面向对象的特点。

package clyang.oop.staticy;

public final class Cal$
{
    public static final Cal$ MODULE$;
    
    static {
        new Cal$();
    }
    
    public int add(final int i, final int j) {
        return i + j;
    }
    
    private Cal$() {
        MODULE$ = this;
    }
}

(9)object中只能定义静态的方法和属性,如果一个类既要有静态的方法,又要有非静态的方法,就可以定义同名的object和class,其中静态的定义在object中,非静态的定义在class文件中,两者构成伴生关系,object是class的伴生对象,class是object的伴生类。

package clyang.oop.staticy

/**
  * 既需要静态方法又需要非静态方法,可以定义同名的class和object
  */
object StaticDemo2 {
  def main(args: Array[String]): Unit = {
    val c=new Calc
    //非静态调用
    c.printTest
    //静态调用
    Calc.max(1,2)
  }
}
//非静态定义在class
class Calc{//object的伴生类
  def printTest={
    println("我就是最帅")
  }
}
//静态定义在object
object Calc{//class的伴生对象
  def max(x:Int,y:Int)=if (x>y) x else y
}

查看 Calc类反编译后的代码,发现依然只有两个文件,其中object Calc和class Calc中定义的代码都编译到了一起,合并到Calc.class文件。可以看出object中定义的方法和class中定义的方法都在这个文件中,并且前者用static修饰,依然调用实例对象的max方法,而后者就是普通的方法。

package  clyang.oop.staticy;

import scala.reflect.*;
import scala.*;

@ScalaSignature(bytes = "略")
public class Calc
{   
    //object中定义的方法
    public static int max(final int x, final int y) {
        return Calc$.MODULE$.max(x, y);
    }
    //class中定义的方法
    public void printTest() {
        Predef$.MODULE$.println((Object)"\u6211\u5c31\u662f\u6700\u5e05");//我就是最帅
    }
}

抽象类

scala中也有抽象类,它定义类时,使用abstract关键字即可,在定义方法时,无需再写abstract,只需指定方法名和返回值类型。具体实现类中实现方法的逻辑,使用override关键字。

package clyang.oop.abstractx

object AbstractDemo1 {
  def main(args: Array[String]): Unit = {
    var c:Shape=new Circle(5)
    println(c.getGirth)
    println(c.getArea)
  }
}
//抽象类
abstract class Shape{
  //求周长的抽象方法
  def getGirth:Double
  //求面积的抽象方法
  def getArea:Double
  //下面不是抽象方法,已经实现了
  def test="hehe I am test"
}
//实现类
class Circle(r:Double) extends Shape{
  override def getGirth: Double = 2*3.14*r
  override def getArea: Double = 3.14*r*r
}

//控制台
31.400000000000002
78.5

Process finished with exit code 0

特质

scala中没有接口关键字,但是有特质来代替,以下是特质的一些基本知识。

(1)特质定义后,如果想让一个类具有某种特质,需要混入或者继承特质,如果类有父类,如Bicycle,使用with混入特质,如果类没有父类,如Car,则继承特质。

(2)特质可以多混入,如Ferrari类,同时混入Move和Bar特质,有等少特质就使用多少个with。

(3)特质中还可以定义实体方法和属性,如Move特质中定义了speedup方法和feeling属性。

(4)如果在实例化一个对象时想混入特质,也可以,如byd,实现了Move特质,因此可以跑可以加速,但是byd2因为没有实现Move特质,因此不能跑也不能加速。

(5)定义byd混入特质的时候,不能extends继承某个父类,因为不确定BYD类是否有父类,如果可以写将违反单继承的约定。

package clyang.oop.abstractx

object TraitDemo1 {
  def main(args: Array[String]): Unit = {
    //调用
    var b=new Bicycle
    b.speed
    var c=new Car
    c.speed
    var f=new Ferrari
    f.speed
    f.dance

    //对象混入特质
    var byd=new BYD with Move {//能混入特质,但是不能extends某个父类
      override def speed: Unit = {
        println("我是国产车,我开的是放心")
      }
    }
    byd.speed
    byd.speedup

    //没有混入Move特质的对象不能调用speed和speedup方法
    var byd2=new BYD
    //byd2.speed
    //byd2.speedup
  }
}

//特质
trait Move{
  //方法,没有指定返回值,就是返回Unit
  def speed

  //特质中可以定义实体方法
  def speedup={println("我能加速")}

  //特质中可以定义属性
  var feeling:String=_

}

class Vehicle

//有父类,使用with混入特质
class Bicycle extends Vehicle with Move {
  //实现特质中抽象方法
  override def speed: Unit = {
    println("我是自行车,我开50迈")
  }
}

//没有父类,使用extend继承特质
class Car extends Move{
  override def speed: Unit = {
    println("我是小轿车,我开150迈")
  }
}

//特质
trait Bar{
  def dance
}

//特质多混入
class Ferrari extends Vehicle with Move with Bar{
  override def speed: Unit = {
    println("我是豪车,我开300迈")
  }
  override def dance: Unit = {
    println("我是豪车,我能开着它去酒吧跳舞")
  }
}

//对象混入特质
class BYD

控制台打印结果。

我是自行车,我开50迈
我是小轿车,我开150迈
我是豪车,我开300迈
我是豪车,我能开着它去酒吧跳舞
我是国产车,我开的是放心
我能加速

Process finished with exit code 0

权限修饰符

scala中权限修饰符主要为public(不写)、protected和private,可以用来修饰类、方法和属性,如果没写权限修饰符,默认就是public,这个的权限范围跟java一样,此外private修饰的,也只能在本类中使用,跟java类似。

权限修饰符 本类 子类 同包类 其他类
默认不写-public true true true true
protected true true false false
private true false false false

(1)public,默认就是public,它修饰的成员在任何地方都可以被访问。

定义了Super类后,里面定义了一个name属性和一个method方法,默认不写任何修饰符,则是public修饰。在本类中可以访问,子类Sub中也可以访问,同包的Same类也可以访问。

package clyang.oop.privilege

/**
  * protected、public和private修饰符
  */

//本类
class Super {

   var name:String=_
   def method(): Unit = {
    println("我是方法")
  }

  class Inner{
    name="clyang"
    method()
  }

}

//子类
class Sub extends Super {
  name="clyang"
  method()
}

//同包类
class Same {
  new Super().method()
  new Super().name="clyang"

}

其他类中,也可以访问,所以默认就是任何地方都可以访问。

package clyang.oop.other

import clyang.oop.privilege.Super

/**
  * 其他类可以访问public修饰的成员
  */
class Other {
  new Super().method()
  new Super().name="clyang"

}

(2)protected,和java中的有点区别,scala中使用protected修饰的,只能在本类和子类中使用,同包类中不可用,而java中同包类中是可以用的。依然使用上面的代码,修改属性和方法的权限修饰符,可以看到如下结果。

同包类中不可以使用,对属性和方法的访问都报错'Symbol xxxx is inaccessible from this place'。

package clyang.oop.privilege

/**
  * protected、public和private修饰符
  */

//本类
class Super {
   //protected修饰属性和方法
   protected var name:String=_
   protected def method(): Unit = {
    println("我是方法")
  }

  class Inner{
    name="clyang"
    method()
  }

}

//子类
class Sub extends Super {
  name="clyang"
  method()
}

//同包类
class Same {
  new Super().method() //报错
  new Super().name="clyang" //报错
}

其他类中,跟java一样,是不可以访问的。

package clyang.oop.other

import clyang.oop.privilege.Super

/**
  * 其他类不可以访问protected修饰的成员
  */
class Other {
  new Super().method() //报错
  new Super().name="clyang" //报错

}

(3)private,和java类似,其修饰的成员只能在本类访问,如本类的内部类可以访问。但子类、同包类和其他类均不能访问。

package clyang.oop.privilege

/**
  * protected、public和private修饰符
  */

//本类
class Super {
   //private修饰属性和方法
   private var name:String=_
   private def method(): Unit = {
    println("我是方法")
  }
  //内部类中可以使用
  class Inner{
    name="clyang"
    method()
  }

}

//子类
class Sub extends Super {
  name="clyang" //报错
  method()  //报错
}

//同包类
class Same {
  new Super().method()  //报错
  new Super().name="clyang" //报错

}

其他类中,跟java一样,是不可以访问的,跟protected的结果一样,代码略。

作用域

scala中提供了比java更加细粒度的权限控制,引入了作用域。使用protected[x]或private[x]来修饰成员,可以做到对权限的精确控制,其中x可以是类、包和单例对象。如果不加作用域,以private修饰的成员为例,它只能在本类中访问,加了x后,可以在x范围内被访问到。可以理解为"这个成员除了对[x]中的类或[x]中的包中的类及它们的伴生对像可见外,对其它所有类都是private权限"。

具体可以参考如下代码,其中privilege包下有两个子包,P1和P2,P1包下有两个类Country和Test,还有Test的伴生对象Test,P2包下只有一个类Test,从代码编译提示来看,有如下结果。

(1)protected修饰的方法method,如果不指定作用域P1,在同包下的Test类和Test伴生对象,都不可以使用,均为不可见,指定后均可以访问。同时,因为P2包是P1包的平级包,对method仍然不可以访问,如果P2包是P1包的子孙包,就可以访问到method。

(2)private修饰的类Country,按理是不能在P2包下访问的,当给定作用域为privilege包下,因为P2包在privilege包下,P2包中就可以访问到了。

(3)private修饰的成员rank,因为指定this实例作用域,因此只能通过this.rank访问到,another.rank就访问不到。

(4)private修饰的成员food,由于指定了作用域privilege,在P2包中也能访问到,P2包下test方法中another.food无报错。

(5)private修饰的内部类成员population,原本只能在内部类访问到,当指定作用域为Country类后, new India().population=1200无报错,因为它是在Country类内部进行访问。

package clyang.oop.privilege

/**
  * 作用域保护 protected[x]和private[x],x可以是类、包和单例对象
 */

//包1
package P1{
    //x是包
    private[privilege] class Country{
        //x是包
        protected[P1] def method(){println("method")}
        //x是当前实例
        private[this] var rank = 1
        //x是包
        private[privilege] var food="咖喱"

        //测试
        def help(another:Country): Unit ={
          println(another.method())
          println(this.rank)
          println(another.rank) //报错
          println(another.food)
         }

        //内部类
        class India{
          //x是类
          private[Country] var population = 1000
          //内部类可以用
          method()
        }

        //测试private[Country]
        new India().population=1200

    }

    //测试protected[P1]
    class Test{
      new Country().method() //不指定P1就报错
    }

    //测试protected[P1]是否对伴生对象也可见
    object Test{
      var c=new Country()
      c.method() //不指定P1就报错
    }
}

//包2
package P2{
    import P1._
    class Test{
      def help(another:Country): Unit ={
        println(another.method()) //报错
        println(another.food)
      }

    }
}

想判断添加作用域修饰后成员可不可访问,可以通过判断这个作用域是不是包含访问地所在的范围,以包为例,参考如下代码,可以看出如下结果。

(1)C包下定义的Messi类,当指定作用域B后,只能在C、D、E包下及其子孙包下访问。因为这三个包都在B包下,属于同级包。

(2)C包下的Messi类,只有当指定作用域A包,才能在F包中访问,因为F包和C包有共同的顶级包=>A包。

(3)G包下的Ronald类,因为指定作用域E包,因此H包下能访问,D包由于和E是同级包,访问不到。

package clyang.oop.privilege

/**
  * 多层包测试作用域
  */

package A

package B{

  package C{
      //1 定义类Messi
      private[B] class Messi
  }

  package D{

    import clyang.oop.privilege.A.B.C.Messi
    //Messi类指定作用域B包,这里能访问到,不指定访问不到
    class Test{
        new Messi()
        //Ronald类指定作用域E包,只有E包下才能访问到
        new Ronald()
      }
  }

  package E{
    package G{

      //2 定义类Ronald
      private[E] class Ronald

      import clyang.oop.privilege.A.B.C.Messi
      //Messi类指定作用域B包,这里能访问到,不指定访问不到
      class Test{
        new Messi()
      }
    }

    package H{

      import clyang.oop.privilege.A.B.E.G.Ronald
      //Ronald类指定作用域E包,这里能访问到,不指定访问不到
      class Test{
        new Ronald()
      }
    }
  }

}

package F{

  import clyang.oop.privilege.A.B.C.Messi
  //Messi类必须指定作用域A包,才能访问到
  class Test{
    new Messi()
  }
}

上面代码结果以及包的关系简单示意图如下。

以上,是对scala面向对象相关知识的理解总结,可能有不对的地方,后续还需要修正,仅供参考。

参考博文:

(1)https://blog.csdn.net/starkpan/article/details/86633228 伴生类伴生对象

(2)https://www.cnblogs.com/amunote/p/5582303.html

(3)https://www.runoob.com/scala/scala-access-modifiers.html 菜鸟教程

(4)https://www.runoob.com/java/java-inheritance.html 继承

(5)https://blog.csdn.net/smile_from_2015/article/details/80686836 scala编译后的两个class文件的作用

posted @ 2020-02-25 11:16  斐波那切  阅读(498)  评论(0编辑  收藏  举报