zoukankan      html  css  js  c++  java
  • 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 = "u957fu65b9u5f62";//长方形
        }
    }
    

    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 = "u6b63u65b9u5f62";//正方形
        }
    }
    

    (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)"u6211u5c31u662fu6700u5e05");//我就是最帅
        }
    }
    

    抽象类

    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文件的作用

  • 相关阅读:
    timeout in asp.net
    ASP.NET_SessionId vs .ASPXAUTH why do we need both of them?
    Visual paradigm软件介绍
    OJ网站程序员必备
    c++异常详解
    C++STL之双端队列容器
    GPU的线程模型和内存模型
    C++ 中memset 勿要对类使用
    trait与policy模板技术
    C++标准库
  • 原文地址:https://www.cnblogs.com/youngchaolin/p/12360647.html
Copyright © 2011-2022 走看看