标签:
本节简单介绍下scala中的类.scala是一种多范式编程语言,支持面向对象编程,同时也支持函数式编程。本节简单介绍
scala面向对象编程中的类.类是对事物的抽象,是对象的模板.我们先来看一个简单的scala中的类:
package chapter05.sec1 class Team { var name:String="" val history:Int=0
}
这个类比较简单,我们先从简单的开始,抽丝剥茧式的慢慢深入.我们定义一个类Team,代表NBA的球队.Team类中包
含两个字段name和history,其中name是普通变量(易变的,因为nba球队的名字是可以更改的); history是常量,代表
球队的历史.在scala中的普通类中字段必须赋初值.我们反编译(使用jd-gui)一下Team类的字节码文件,如下:
package chapter05.sec1; import scala.reflect.ScalaSignature; /** *①通过类的声明那一行可以看出在scala中类的默认的访问控制修饰符是public * .实际上在scala中是不存在public关键字的.scala中默认修饰符对应于java的public */ public class Team{ /** * ②通过如下两行的反编译结果可以得出scala中默认的 * 访问控制修饰符为private */ private String name = ""; private final int history = 0;
/** * name_$eq其实是name_= * 因为在字节码文件中不允许标识符出现= * ③通过如下两个方法可以看出在scala中普通变量name(易变变量,即使用var声明的变量) * 默认会生成name_=和name方法.对应于java中的getter/setter方法 */ public void name_$eq(String x$1) { this.name = x$1; } public String name() { return this.name; } /** *④通过如下方法可以看出在scala中常量history默认只会生成history方法. * 对应于java中的getter.这也是比较容易理解的:因为常量不能重新赋值嘛 */ public int history() { return this.history; } }
我们通过观察反编译的结果,至少能得出如下结论:
1.在scala中类的默认访问控制修饰符是public.从上述的标注①可以看出
2.在scala中字段的默认访问控制修饰符是private,从上述的标注②
3.对于普通变量(使用var声明的变量)variable,scala编译器会生成variable和variable_=方法.对应java的get/set.
且variable和variable_=方法的访问控制修饰符是public的.
4.对于常量(val声明的变量)variable,scala编译器默认会生成public的variable方法.对应于java中的get方法
通过上述第3、4点结论,我们发现var变量默认情况下生成的variable和variable_=、val生成的variable方法都是具有
public访问属性,这样的话就在一定程度上破坏了对象的封装性.其实我们在声明变量的时候可以使用private关键字.这
样的话生成测variable和variable_=方法也都具有private可见性了.如下:我们先定义一个类
package chapter05.sec1 class TestPrivate { private var variable_private="" protected var variable_protected="" }
然后我们来反编译这个类
package chapter05.sec1; import scala.reflect.ScalaSignature; @ScalaSignature(bytes="") public class TestPrivate{ private String variable_private = ""; private String variable_protected = ""; /** *①通过如下两个方法可以看出在scala中如果一个var变量variable在声明的时候 * 使用了private关键字.那么生成的variable和variable_=方法也是private的 */ private void variable_private_$eq(String x$1) { this.variable_private = x$1; } private String variable_private() { return this.variable_private; } /** *②通过如下两个方法可以看出在scala中如果一个var变量variable在声明的时候 * 使用了protected关键字.那么生成的variable和variable_=方法是public的!! * 这种实现不知道是基于什么考虑。但是在scala中虽然如下两个方法是public的 * 但是还是只有在该类的子类中才能访问的到(估计是使用了@ScalaSignature中 * 的元数据) */ public void variable_protected_$eq(String x$1) { this.variable_protected = x$1; } public String variable_protected() { return this.variable_protected; } }
从上述的反编译结果中我们可以得出如下结论
1.使用private声明的变量variable.编译器生成的variable和variable_=方法也是private可见性的.
2.使用protected声明的变量variable.编译器生成的variable和variable_=方法是public可见性的.
对于上述第2点比较奇怪,我做了如下一个实验:
从实验结果可以看出虽然scala中生成的variable和variable_=方法的访问控制修饰符是public,但是我们也只能在子类
中才能访问。个人感觉是scala编译器从@ScalaSignature注解中保存了一些元数据。也就是说这种实现方式是依赖于
scala编译器的.如果我们把这个类拿到java中用的话,任何类都可以访问和设置此字段.如下:
import chapter05.sec1.TestPrivate; public class Team { public static void main(String[] args) { TestPrivate tp = new TestPrivate(); tp.variable_protected_$eq("nihao"); System.out.println(tp.variable_protected()); } }
上述Java代码能正常运行。
严格来说java中的private字段或者方法是类级私有的.在使用Java时我们平常所说的"private的作用域范围是对象私
有的",这种说法其实是不正确的.类级私有指的是该类的所有实例之间都能互相访问彼此的private字段.看个例子:
//如下是Java代码 package chapter05; public class Team { private String name="default"; /** * Java中的private访问控制修饰符的可见范围是类级别。 * 我们在调用该方法的时候肯定是在Team类的一个实例(比如instance1)上调用的。 * 方法的参数是Team类型的另外一个实例otherTeam. * 所以最终的结果是:我们在instance1实例中访问了otherTeam实例的私有成员. */ public void agenda(Team otherTeam){ System.out.println("这是跟"+otherTeam.name+"球队之间的赛程"); } }
在上述的例子中我们在agenda方法的参数中传入了Team类的一个实例.我们在当前实例(比如instance1)中可以访问
otherTeam实例的私有成员.
我们再看另外一段Java代码:
在一个非Team类的实例中访问Team实例的私有成员会报错.
在scala中默认的private的访问控制修饰符的可见性跟Java中保持一致.但是Scala中还有另外一种更加严格的访问控
制修饰符,即private[this].这种访问控制修饰符所修饰的成员只能被当前实例所访问。来看一段scala代码:
在上面的代码中我们可以看到对象私有这种访问控制修饰符所修饰的成员只能被当前实例使用.尝试使用其他实例的对
象私有成员在编译期就会报错.
由于scala最终会把scala文件编译成字节码.而java字节码本来就没有对象私有这种语法,那scala是如何做到的呢?个
人感觉scala在@ScalaSignature注解中记录了一些元数据.潜台词也就是说使用了这种语法的类,在Java中这种特性
是会退化的.
构造函数在面向对象编程中是必备的概念.但是在Java中我们却没有听到或者很少听到主构造函数这种说法.在Java中
比较常见的说法是默认构造函数(无参构造函数)和带参构造函数.由于在scala中构造函数的写法跟Java中非常不同.我
们本小节就来简单介绍下scala的构造函数.本小节主要介绍主构造器.
在scala中主构造器指的紧跟在类名后面的构造器,这种写法是Java中所没有的.看代码:
package chapter05.sec3 /** * 紧跟在类名后面声明的构造函数称之为主构造函数. * scala追求极致的简洁.这种声明构造函数的方式确实比Java中简洁不少 */ class Team(teamName:String) { val name:String=teamName }
在上述代码中我们就声明了Team类的主构造函数.主构造函数接受一个String类型的参数.那么主构造方法的方法体
在哪里声明呢?实际上在scala的类中不属于任何方法和变量声明的语句都会被scala收集进主构造方法.如下:
//Java源代码 package chapter05.sec3 class TeamBody(name:String) { //下面这句语句会被收集进scala的主构造器 println("I am in constructor!") def hello()=println("Hello") //下面这句语句也会被收集进scala的主构造器 println("Hello World") } //反编译结果 import scala.Predef.; import scala.reflect.ScalaSignature; @ScalaSignature(bytes="") public class TeamBody{ public void hello(){ Predef..MODULE$.println("Hello"); } public TeamBody(String name){ Predef..MODULE$.println("I am in constructor!"); Predef..MODULE$.println("Hello World"); } }
scala中的主构造函数具有如下几种常见的使用方式:
1.主构造器中的参数使用var修饰.如下:
//Scala代码 package chapter05.sec3 class TeamVar(var name:String) { } //反编译结果 package chapter05.sec3; import scala.reflect.ScalaSignature; @ScalaSignature(bytes="太长.省略") public class TeamVar{
private String name; public TeamVar(String name) {} public void name_$eq(String x$1){ this.name = x$1; } public String name(){ return this.name; } }
在这种使用方式下.scala编译器会把主构造器参数当作类的字段(以variable为例).并生成字段公有的variable和vari-
able_=方法.我们还可以在var前加上private关键字.这时候生成的variable和variable_=方法也是私有的.
2.主构造器中的参数使用val修饰,如下:
//Java源代码 package chapter05.sec3 class TeamVal(val name:String) { } //反编译结果 package chapter05.sec3; import scala.reflect.ScalaSignature; @ScalaSignature(bytes="") public class TeamVal{ private final String name; public TeamVal(String name) {} public String name(){ return this.name; } }
在这种使用方式下.scala编译器会把主构造函数的参数当作类的字段(以vaiable为例).并生成字段共有的variable方法
.我们还可以在val前面加上private关键字.这时候生成的variable方法也是私有的.
3.主构造器参数不加val也不加var,这种情况又分为两小类:
3.1.主构造器参数没有在类的方法参数中或者变量声明中使用到.看代码:
//Java源代码 package chapter05.sec3 class TeamOnly(name:String) { println(name) } //反编译结果 package chapter05.sec3; import scala.Predef.; import scala.reflect.ScalaSignature; @ScalaSignature(bytes="") public class TeamOnly{ public TeamOnly(String name){ Predef..MODULE$.println(name); } }
由于构造函数参数没有在方法参数和变量声明中用到.所以不会生成参数对应的字段.
3.2.主构造器参数在类的方法参数/方法体或者变量声明中被用到。看代码:
//Java源代码 package chapter05.sec3 class TeamOnly2(name:String) { def display(){ println(name) } } //反编译结果 package chapter05.sec3; import scala.Predef.; import scala.reflect.ScalaSignature; @ScalaSignature(bytes="") public class TeamOnly2{ private final String name; public void display(){ Predef..MODULE$.println(this.name); } public TeamOnly2(String name) {} }
在这种方式下会把构造器参数生成类的字段.此字段是private[this]可见性的。
调用主构造方法的构造方法被称为辅助构造方法.在Java中定义构造方法,需要让方法名跟类名保持相同.如果我们在一
个Java类中有多个构造函数.那么如果由于某种原因我们修改了此类的名字,那么所有的构造函数的名字也都要一一修
改.scala的设计者考虑到了这种变动.所以在scala中除了主构造函数外,其他构造函数的函数名都是this.
辅助构造方法必须以调用主构造函数或者其他构造函数开始.看代码吧:
//Java源代码 package chapter05.sec3 /** * AuxTeam类声明了一个主构造函数,接受一个Int类型的参数 */ class AuxTeam(val histoty:Int){ var name:String="" var coach:String="" /** * 辅助构造函数.必须以调用主构造函数开始。 */ def this(his:Int,name:String){ this(his) this.name=name } /** * 辅助构造函数.必须以调用主构造函数开始。 */ def this(his:Int,name:String,coach:String){ this(his) this.name=name this.coach=coach } } //测试代码 package chapter05.sec3 object MainApp extends App { val auxTeam = new AuxTeam(1970,"Los Angeles Clippers","Doc Rivers") println(auxTeam.histoty) println(auxTeam.name) println(auxTeam.coach) }
scala中默认的取值器和设值器的命名规范不符合JavaBean的规范.但是很多框架都依赖于标准的JavaBean规范.在
scala中我们可以通过使用@BeanProperty注解来强制scala按照JavaBean规范来生成getter和setter.如下:
//Java源代码 package chapter05.sec3 import scala.beans.BeanProperty class TeamJavaBean { @BeanProperty var name:String = _ } //反编译结果 package chapter05.sec3; import scala.reflect.ScalaSignature; @ScalaSignature(bytes="") public class TeamJavaBean{ private String name; //下面的取值器和设值器是按照JavaBean规范来的 public String getName(){ return name(); } public void setName(String x$1){ this.name = x$1; } //下面的取值器和设值器是按照scala的方式来的 public void name_$eq(String x$1){ this.name = x$1; } public String name(){ return this.name; } }
再将scala的内部类之前,我们先来看一下Java的内部类.我们直接看Java代码。
//Java代码
package chapter05.sec4; import java.util.ArrayList; import java.util.List; //该类是简单抽象出的NBA球队 public class Team { //该字段代表球队名 private String name; //无参构造 public Team(){} public Team(String name){ this.name=name; } //内部私有成员.代表球队中球员的集合 private List<Player> players = new ArrayList<>(); //Team球队的内部类,代表球员 class Player{ private String name; private int age; public Player(){} public Player(String name,int age){ this.name=name; this.age=age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return this.name+" : "+this.age; } } //签约球员 public void addPlayer(Player player){ this.players.add(player); } //交易球员或者球员退役 public void removePlayer(Player player){ this.players.remove(player); } //打印球队和球员信息 public void display(){ System.out.println("球队名:"+name); System.out.println("=========球员=========="); for(Player player:players){ System.out.println(player.getName()+" : "+player.getAge()); } System.out.println(); } }
上面的代码中,我们首先定义了Team类,代表NBA球队.Team类包含一个私有成员name,代表球队名.还包括一些成员
方法.除此之外还包含一个内部类Player,代表球员.接下来我们来看看在Java中是如何使用内部类的。
//Java代码 package chapter05.sec4; public class MainApp { public static void main(String[] args) { //实例化一个马刺队 Team spurs = new Team("马刺队"); //实例化马刺队中的帕克 Team.Player parker = spurs.new Player("Tony Parker",33); //马刺2001年签约帕克 spurs.addPlayer(parker); //打印球队信息 spurs.display(); //实例化雷霆队 Team thunder = new Team("雷霆队"); //实例化雷霆队的杜兰特 Team.Player durant = thunder.new Player("Kevin Durant",27); //雷霆2007年签约杜兰特 thunder.addPlayer(durant); //打印雷霆队信息 thunder.display(); System.out.println(parker.getClass() == durant.getClass()); //把杜兰特加入马刺队 spurs.addPlayer(durant); spurs.display(); } }
在Java中内部类从属于外部类,跟外部类的实例没有从属关系.由于内部类是外部类的成员(跟成员变量、成员方法)类似
.所以如果我们想要实例化一个内部类的实例,那么我们必须先实例化一个外部类的实例.在上述代码中,我们首先实例
化外部类实例spurs,然后使用Java内部类的特殊语法spurs.new Player()这种方式来实例化内部类.也就是说在Java中
内部类从属于外部类,跟外部类的实例没有关系.我们使用 外部类实例.new 内部类() 这种方式来实例化内部类的时候,
外部类实例的作用仅仅是帮助我们得到内部类.
在代码的最后两行,我们把通过外部类实例thunder实例化的内部类实例durant加入到了外部类实例spurs的成员变量
中,最后打印spurs的信息.我们看下结果:
我们用同样的例子来学习scala的内部类.先看代码:
//scala代码 package chapter05.sec4 class Team(var name:String) { var players = List[Player]() class Player(var name:String,var age:Int){ override def toString():String={ this.name+" : "+this.age } } def addPlayer(player:Player){ players = player :: players } def removePlayer(player:Player){ players = players.filter { _ != player } } def display(){ println("球队名:"+name) println("=========球员==========") for(player <- players) println(player) println() } }
我们可以看到实现同样的功能.scala代码要比java代码简洁很多.再来看下实验主代码:
package chapter05.sec4 object MainApp extends App{ //实例化一个马刺队 val spurs = new Team("马刺队") //实例化马刺队中的帕克 val parker = new spurs.Player("Tony Parker",33) spurs.addPlayer(parker) spurs.display() //实例化雷霆队 val thunder = new Team("雷霆队") //实例化雷霆队的杜兰特 val durant = new thunder.Player("Kevin Durant",27) thunder.addPlayer(durant) thunder.display() println(parker.getClass() == durant.getClass()) //下面注释的这条语句很报错 //spurs.addPlayer(durant) }
在scala中默认情况下内部类是隶属于外部类的实例的.而不是外部类本身.我们创建内部类的实例需要使用如下语法:
new 外部类实例.内部类() 由于这时候内部类是绑定到外部类实例上的.所以如果我们尝试通过外部类实例thunder
创建的内部类实例durant添加到外部类实例spurs的成员变量中就会报错如下:
type mismatch; found : chapter05.sec4.MainApp.thunder.Player required: chapter05.sec4.MainApp.
spurs.Player
由于Java的字节码并没有这种语义.个人感觉是通过往@ScalaSignature注解中添加元数据完成的。
如果我们要想让scala的内部类表现的更像Java的内部类.我们可以使用那个 类型投影 或者 伴生类 来实现.类型投影
和伴生类的细节我们后面章节很讲到.这里我把代码贴出来,大家感兴趣的可以提前看下:
1.通过使用类型投影来让scala的内部类的表现跟Java中一致
//Scala代码 package chapter05.sec5 class Team(var name:String) { var players = List[Team#Player]() class Player(var name:String,var age:Int){ override def toString():String={ this.name+" : "+this.age } } def addPlayer(player:Team#Player){ players = player :: players } def removePlayer(player:Team#Player){ players = players.filter { _ != player } } def display(){ println("球队名:"+name) println("=========球员==========") for(player <- players) println(player) println() } } //Scala测试类 package chapter05.sec4 import chapter05.sec5.Team object MainApp extends App{ //实例化一个马刺队 val spurs = new Team("马刺队") //实例化马刺队中的帕克 val parker = new spurs.Player("Tony Parker",33) spurs.addPlayer(parker) spurs.display() //实例化雷霆队 val thunder = new Team("雷霆队") //实例化雷霆队的杜兰特 val durant = new thunder.Player("Kevin Durant",27) thunder.addPlayer(durant) thunder.display() println(parker.getClass() == durant.getClass()) //下面注释的这条语句很报错 spurs.addPlayer(durant) spurs.display() }
我们把出现Player的地方都换成了Team#Player.Team#Player的意思是所有实例的Player类。
2.通过使用伴生类来让scala的内部类的表现跟Java中一致
//Scala代码 package chapter05.sec5 class Team2(var name:String) { var players = List[Team2.Player]() def addPlayer(player:Team2.Player){ players = player :: players } def removePlayer(player:Team2.Player){ players = players.filter { _ != player } } def display(){ println("球队名:"+name) println("=========球员==========") for(player <- players) println(player) println() } } object Team2{ class Player(var name:String,var age:Int){ override def toString():String={ this.name+" : "+this.age } } } //Scala主程序 package chapter05.sec5 object MainApp extends App { //实例化一个马刺队 val spurs = new Team2("马刺队") //实例化马刺队中的帕克 val parker = new Team2.Player("Tony Parker",33) spurs.addPlayer(parker) spurs.display() //实例化雷霆队 val thunder = new Team2("雷霆队") //实例化雷霆队的杜兰特 val durant = new Team2.Player("Kevin Durant",27) thunder.addPlayer(durant) thunder.display() println(parker.getClass() == durant.getClass()) //下面注释的这条语句很报错 spurs.addPlayer(durant) spurs.display() }
标签:
原文地址:http://www.cnblogs.com/sysman/p/4500173.html