Scala中的类
1 类的基本操作
1.1 类的定义
类或者类型,就是对客观的一类事物的抽象。用一个class关键字来描述,在这个类中可以拥有这一类事物的属性,行为等等。
或者说就是用计算机的语言来描述的一类事物,就是类,在java,scala中都是用关键字class来标识。
因为类是对一类事物的抽象,所以不具备具体的行为执行能力,要想完成具体的操作,就需要使用该类的实例或者对象。
//创建scala中的一个类
object classTest {
def main(args: Array[String]): Unit = {
/*
scala中类对应对象的构建,和java一模一样 使用关键字new,
以及构造函数来完成实例的创建
scala可以使用默认的无参构造器完成实例创建,
同时一类如果有无参构造器,在创建对象的时候,可以省略掉()
*/
val p = new Person()
// val p1 = new Person
p.setName("goodera")
p.setAge(169)
p.show()
}
}
class Person {
/*
_的作用就相当于java中private String name;
如果要使用_,前面的成员变量就应该指定数据类型
*/
private var name:String = _
private var age = 13
def show(): Unit = {
println(s"name=${name}\tage=${age}")
}
def setName(name:String):Unit = {
//成员变量和局部变量命名冲突之后使用this关键字来进行区分
this.name = name
}
def getName() = this.name
//单行函数
// def setAge(age:Int) = this.age = age
def setAge(age:Int): Unit = {
if(age < 0 || age > 150) {
throw new RuntimeException("地球不欢迎你~")
}
this.age = age
}
def getAge = this.age
}
1.2 getter/setter操作
在scala中使用@BeanProperty注解来给成员变量提供getter/setter,该注解和private访问权限修饰符不可共存.
这种方式定义类似java bean的操作,在scala中几乎不用,java bean的主要作用是?:
说白了就是在各个业务线/层之间传递数据,scala提供了另外的一种结果来模拟java中的bean–case class(样例类,样本类)。
class Student {
@BeanProperty var name:String = _
private var age = 13
}
1.3 类的构造
-
java中的构造器
类的构造函数,在java中一个类的构造函数(器),可以分为有参构造器或者无参的构造器,或者默认构造器的说法。所谓默认构造器指的就是,用户不指定/创建相关构造器,而虚拟机会自动的为我们的类添加一个无/空参的构造器,用于对象的创建。
在java中一个类,可以拥有若干个构造器,既可以拥有无参构造器,也可以拥有有参构造器,如果用户提供了构造器,此时便不会再有默认的构造器了。
- scala中的构造器
scala中有两类构造器——主构造器和辅助构造器。
构造器的说明
object classTest {
def main(args: Array[String]): Unit = {
val p = new Person("goodera", 24)
p.show()
}
}
class Person() {
private var name:String = _
private var age:Int = _
def Person(): Unit = {
println("--Person()--是默认的构造器吗?")
}
//自定义构造器 自定义构造器的第一句话必须要调用其它构造器
def this(name:String, age:Int) {
this()
this.name = name
this.age = age
println("---------this(name:String, age:Int)---------")
}
println("-------------构造器~----------------")
def show(): Unit = {
println(s"name=${name}\tage=${age}")
}
}
本例中的这个和类的定义交织在一起的默认的构造器,是scala类中最重要的一个构造器——主构造器(默认的构造器是无参主构造器,定义在类名和类的{之间,省略了())。
其余的自定义构造器,也就是在类中通过def this(xxx)定义的构造器称之为辅助构造器,同时辅助构造器的第一句话,必须要通过this来调用本类主构造器或者其它辅助构造器。
object constructorTest {
def main(args: Array[String]): Unit = {
val person = new Person(24)
person.show()
}
}
class Person(var name:String, var age:Int) {
def Person(): Unit = {
println("--Person()--是默认的构造器吗?")
}
def this() {
this("嘿嘿", 20)
println("---this() ---")
}
def this(name:String) {
this()
this.name = name
println("---this(name:String) ---")
}
def this(age:Int) {
this("goodera")
this.age = age
println("---this(age:Int) ---")
}
println("-------------构造器~----------------")
def show(): Unit = {
println(s"name=${name}\tage=${age}")
}
}
-------------构造器~----------------
---this() ---
---this(name:String) ---
---this(age:Int) ---
name=goodera age=77
1.4 内部类
主要用到这个内部类的原因,就在于如果将两个事物,每一个事物在java中都是类class,将这两个可能具有包含关系,比如人Person,心脏Heart,平行去定义,就不大符合实际的情况。而Heart是人这一类事物内部的一类其它事物,能够直接访问人这一类事物中的很多属性,如果将Heart定义在外部,要想获取人的血液,就需要在Heart并行的创建人Person对象,再进行其它操作,显然不合理。
对于这类型的操作,我们就是用类的嵌套来完成定义,或者这种结构称之为嵌套类或者内部类。
- java版本的内部类
public class InnerClass {
public static void main(String[] args) {
//创建内部类Inner的对象
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.show();
}
}
class Outer {
int x = 5;
class Inner {
int x = 6;
public void show() {
int x = 7;
System.out.println("x = " + x);
System.out.println("inner class x = " + this.x);
System.out.println("Outer class x = " + Outer.this.x);
}
}
}
- scala版本的内部类
object InnerClass{
def main(args: Array[String]): Unit = {
val outer = new Outer
val inner = new outer.Inner()
inner.show()
}
}
class Outer { ooo =>
val x = 5
class Inner { iii =>
val x = 6
def show(): Unit = {
val x = 7
System.out.println("x = " + x)
System.out.println("inner class x = " + iii.x)
System.out.println("Outer class x = " + ooo.x)
}
}
}
总结:
scala的内部类和java的内部类的主要区别就在内部类对象的创建方式不一样。其次scala为了灵活的在内部类的局部访问内部类成员或者外部类的成员,可以给内部类和外部类的引用提供一个别名,方便操作。
也就是说,上例中这iii.x <=> this.x, ooo.x <=> Outer.this.x
内部类使用蛮多的一个地方:就是匿名内部类。
1.5 object对象
scala中的除了class以外,还有两个与此平级的结构——一个是Object对象,一个是Trait特质。为啥要有这个Object呢?
主要的原因在于,在java中的一个class既可以拥有非静态的成员,也可以拥有静态static的成员。但是在scala中的class结构中,只能拥有非静态。为了给scala中的类也来提供类似于java中的静态成员的功能,于是乎就有了Object对象这个结构。
1.5.1 object对象
java中的main函数是静态,此时在class中定义的main是无法运行,但是吧class ObjectClass替换成object ObjectClass便可以运行,主要的原因在于,被object所修饰的结构中的所有的成员都是static静态。
主要作用:
- 给scala类提供程序运行的入口,静态的main函数
- 给scala类也来提供静态成员——scala类的伴生对象来实现。
1.5.2 单例
java中的单例
- 饿汉式
/*
单例设计模式之饿汉式
所谓单例,指的就是本类只能拥有一个实例对象
为了实现这个目标,就是私有化构造器
此时外部便无法访问本类的实例对象
于是乎,创建一个静态的返回值为本类引用的方法
在第二步创建的这个方法中返回的本类实例对象需要在本类成员位置上提前创建好
*/
class Singleton {
private Singleton(){}
private static Singleton singleton = new Singleton();
public static Singleton getInstance() {
return singleton;
}
}
- 懒汉式 —>死记硬背
class SingletonJava {
private Singleton(){}
private int x = 3;
private static Singleton singleton;
public static /*synchronized*/ Singleton getInstance() throws InterruptedException {
if(singleton == null) {
synchronized (Singleton.class) {
if(singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
scala中的单例
object SingletonScala {
def main(args: Array[String]): Unit = {
val s1 = Singleton.getInstance()
val s2 = Singleton.getInstance()
s1.x = 6
println(s1 == s2)
println(s1.x)
println(s2.x)
}
}
class Singleton private () {
var x = 5
}
object Singleton {
private val s = new Singleton
def getInstance():Singleton = {
s
}
}
1.5.3 伴生对象
在上述这个案例当中,同一个scala源文件中可以包含类名相同的一个class和object,这在java中是不被允许的;同时上述的这种单例的构造结果,可以看到对一个class似乎也提供了静态的功能。把这种结构,和class在同一个源文件(.scala)中同名object结构称之为该类的伴生对象,把该类称之为该object的伴生类。
伴生对象的主要作用:
一 为其伴生类提供类似java中的静态成员操作;
二 可以提供另外一种对象的创建方式(不使用new关键字)。
object CompanionObjOps {
def main(args: Array[String]): Unit = {
val t1 = new Teacher("goodera", 24, "IT")
t1.show()
val t2 = Teacher()
t2.show()
val t3 = Teacher("goodera", 24, "IT")
t3.show()
Teacher.teach()
}
}
class Teacher(name:String, age:Int, course:String) {
private val xxx = "shit"
def this() {
this(null, 0, null)
}
def show(): Unit = {
println(s"姓名为${name},年龄为${age}的老师,今年带的课程为${course}")
}
}
object Teacher {
def apply():Teacher = {
new Teacher()
}
def apply(name: String, age: Int, course: String): Teacher = {
val t = new Teacher(name, age, course)
t.xxx
t
}
def teach(): Unit = {
println("三人行必有我师,师者,传道受业解惑")
}
}
注意:
- 要想使用伴生对象创建本类的对象,必须要让本伴生对象复写一个apply方法,该apply方法的参数列表对应的是本类构造器的参数列表。
- 伴生对象,不仅可以访问对应伴生类的非私有成员,同时还可以访问对应伴生类的私有成员
- 伴生对象的使用,在scala中是非常非常广泛,举个例子,在工作中集合对象的创建几乎都用伴生对象来操作,还有spark中各个非常关键的对象的创建也是用伴生对象的形式。
2 类的继承体系
类与类之间的一个很重要关系——继承/扩展(extends)。
2.1 类的扩展
继承的特点:
- 子类可以继承父类的所有非私有(private),非静态的成员(变量和成员方法)。
- 可以对父类的相关方法进行覆盖/重写
- 也可以添加自己独有的成员
- 被final修饰的父类成员,子类不可以继承
- 被protected修饰的父类成员,子类可以继承
- 子类覆盖父类的方法的异常,必须要大于等于父类的异常
- 子类的访问权限必须要大于等于父类
案例说明:
class Person {
private var name:String = _
protected var age:Int = 0
def this(name:String, age:Int) {
this()
this.name = name
this.age = age
}
def show(): Unit = {
println(s"person's name is $name")
}
}
class Student extends Person {
age = 15
def this(age:Int) {
this()
this.age = age
}
override def show(): Unit = {
super.show()//子类调用父类的成员通过super关键字
println(s"Student's age is $age")
}
}
person's name is null
Student's age is 18
注意:
override在java中是一个注解,用来表示该方法是继承的,scala中是一个关键字,必须要添加在重写的方法前面,除非该方法是抽象的。
public class OverrideTest {
public static void main(String[] args) {
Fu f = new Zi();
f.llllllllllll();
}
}
class Fu {
public void llllllllllll(){
}
}
class Zi extends Fu {
@Override//错误的
public void lllllll11lll() {
//原因在于这个中多了两个数字1,看着和L挺接近的,但是是两个不同的方法,所以无法覆盖
//正是因为这一点,在定义Long的数据时候,尽量使用L来进行标识
}
@Override
public void llllllllllll() {
super.llllllllllll();
}
}
2.2 类型检查和转换
这里的类型检查,其实说的是isInstanceOf和强制类型转换。
- javaz中的类型操作
//java版本的类型判断
public class TypeJudge {
public static void main(String[] args) {
Person p1 = new Person("goodera", 24);
Person p2 = new Person("goodera", 24);
System.out.println(p1.equals(p2));
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if(!(o instanceof Person))//类型检查
return false;
Person that = (Person)o;//(强置)类型转换
return this.name.equals(that.name) && this.age == that.age;
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
- scala中的类型操作
在scala中也有类似于java中的类型检查和类型转换,这些操作,通常应用在多态(父类引用指向子类对象),类型的判断的时候。在scala中使用isInstanceOf来进行类型判断,用asInstanceOf进行类型转换。需要注意的是这两个操作都是对象的方法。
object EqualsTest {
def main(args: Array[String]): Unit = {
val w1 = new Worker("goodera", 24)
val w2 = new Worker("goodera", 24)
println(w1.equals(w2))
}
}
class Worker {
private var name:String = _
private var age:Int = _
def this(name:String, age:Int) {
this()
this.age = age
this.name = name
}
override def equals(obj: Any): Boolean = {
if(!obj.isInstanceOf[Worker]) //类型检查
false
else {//类型转换
val that = obj.asInstanceOf[Worker]
this.name.eq(that.name) && this.age == that.age
}
}
}
同时scala提供另一种更加简洁的写法,来处理这些类型检查和类型转换——模式匹配
override def equals(obj: Any): Boolean = {
obj match {//模式匹配
case that:Worker => {
this.name.eq(that.name) && this.age == that.age
}
case _ => false
}
}
2.3 受保护字段和方法
所谓受保护的字段和方法其实就是被访问权限修饰符protected所修饰的成员。被该关键字所修饰的成员有啥特点?:
java:只能被子类访问,同时必须要在本包下面被访问。
scala:
object ProtectTest {
def main(args: Array[String]): Unit = {
val dog = new Dog("黃色")
dog.show()
dog.age
}
}
class Animal {
private var name:String = _
protected var age = 3
def this(name:String, age:Int) {
this()
this.name = name
this.age = age
}
def show(): Unit = {
println(s"Animal: ${name}, ${age}")
}
}
class Dog extends Animal {
age = 4
private var color:String = _
def this(color:String) {
this()
this.color = color
}
override def show(): Unit = {
super.show()
println("color: " + color)
}
}
这里有一个错误,age不能像java中一样,在同一个包下面被直接访问。
但是scala中提供了一个更加强大的功能来精确地控制一个成员的访问权限。就是private和protected后面加上中括号[],[]里面写上要在哪一个范围内可以被访问,比如这里将其修改为protected[extendz],其中的这个extendz就是本类所在的包,于是age便可以被访问了。更加精确的是,只能在本包,及其子包下面被访问。
其中有一个比较特殊的就是private[this]或者protected[this]
class Animal {
private var name:String = _
protected[this] var age = 3
def this(name:String, age:Int) {
this()
this.name = name
this.age = age
}
def show(): Unit = {
println(s"Animal: ${name}, ${age}")
}
}
class Dog extends Animal {
age = 4
private var color:String = _
def this(color:String) {
this()
this.color = color
}
override def show(): Unit = {
super.show()
println("color: " + color)
}
def makeFriend(dog:Dog): Unit = {
println(s"${this.age}和另外的一個小狗${dog.age}來交朋友")
}
}
被**protected[this]**所修饰的变量,只能在本类,及其子类中被调用,但不可以被子类对象调用。
这种通过精确指定成员的访问权限修饰的方式在scala中是非常常见的。
总结:
scala中的访问权限修饰符就只有这么两个:private、protected,没有java中的public,所以我们在创建类的时候,不需要加public关键字,其实不加这个public就相当于java中的public。
2.4 超类的构造
在类的扩展中有个问题,子类没有办法给父类中的name进行直接赋值,所以尝试子类辅助构造器通过类似java中的super关键字去调用父类的相关构造器,但是出现如下异常:
也是就是,scala中子类无法通过super调用父类构造,但是在子类show方法中,却可以成功调度用super.show()方法,super在这里便是父类的一个实例引用,说明父类完成的构造,那么父类的构造是如何被调用的呢?
object ExtendsTest {
def main(args: Array[String]): Unit = {
val stu = new Student(18)
stu.show()
}
}
class Person {
private var name:String = _
protected var age:Int = 0
println("父类Person的主构造器-------")
def this(name:String, age:Int) {
this()
this.name = name
this.age = age
println("----父类Person的辅助构造器------")
}
def show(): Unit = {
println(s"person's name is $name")
}
}
class Student extends Person {
println("子类Student的主构造器")
age = 15
def this(age:Int) {
super()
this.age = age
println("----子类Student的辅助构造器------")
}
override def show(): Unit = {
super.show()
println(s"Student's age is $age")
}
}
结果:
父类Person的主构造器-------
子类Student的主构造器
----子类Student的辅助构造器------
person's name is null
Student's age is 18
在子类的主构造器中调用了父类的构造器,因为子类的辅助构造器必须要在第一句话上面使用this来调用本类的主构造器或者其他辅助构造器,所以就没有机会来调用父类的构造器,也就是说,子类只能通过主构造器来调用父类的构造器。
object ExtendsTest {
def main(args: Array[String]): Unit = {
val stu = new Student("goodera")
stu.show()
}
}
class Person {
private var name:String = _
protected var age:Int = 0
println("父类Person的主构造器-------")
def this(name:String, age:Int) {
this()
this.name = name
this.age = age
println("----父类Person的辅助构造器this(name:String, age:Int)------")
}
def this(name:String) {
this(name, 0)
this.name = name
this.age = age
println("----父类Person的辅助构造器this(name:String)------")
}
def show(): Unit = {
println(s"person's name is $name")
}
}
class Student(name:String) extends Person(name) {
println("子类Student的主构造器")
age = 15
def this(age:Int) {
this("zhangsan")
this.age = age
println("----子类Student的辅助构造器------")
}
override def show(): Unit = {
super.show()
println(s"Student's age is $age")
}
}
结果:
父类Person的主构造器-------
----父类Person的辅助构造器this(name:String, age:Int)------
----父类Person的辅助构造器this(name:String)------
子类Student的主构造器
person's name is goodera
Student's age is 15
2.5 重写字段和方法
在scala中也可以对字段进行重写,说白了就是在字段前面加上一个override关键字即可。最主要的作用就是父类中定义的非私有非protected的字段,子类如果想要定义个同名的字段,此时就需要使用override关键字完成覆盖,如果不用报错。
被覆盖的字段只能被val修饰。
方法的覆盖和java的覆盖是一样的。
2.6 匿名子类
匿名子类,其实说白了就是没有名字的类,通常匿名的类就是只调用一次的,没有必要进行定义,在运行时完成创建即可。
/**
* scala中的匿名子类
* 所谓匿名,指的就是没有名字,这个没有名字指的就是没有类的定义
* class XXX{
* }
* 这个匿名的子类是在运行时构建的
*/
object AnonymousSubClass {
def main(args: Array[String]): Unit = {
val p:Person = new Person("zhangsan", 13){
override def show(): Unit = {
println(s"person's name is $name, age is $age")
}
def show2(): Unit = {
println(s"person's name is $name, age is $age in show2")
}
}
//**匿名子类经常出现的地方就是:方法的参数**
}
}
匿名子类多用在方法的参数,eg:
/**
* java中的代理:
* 静态代理
* 动态代理
* 基于接口的动态代理(jdk提供)
* 基于子类的动态代理(cjlib)
*/
public interface SingerInterface {
void sing();
}
public class Singer implements SingerInterface {
private String name;
public Singer() {
}
public Singer(String name) {
this.name = name;
}
public void sing() {
System.out.println(name + ",我是歌手,我为大家歌唱~");
}
}
public class ProxyTest {
public static void main(String[] args) {
final Singer singer = new Singer("goodera");
//动态代理
SingerInterface si = (SingerInterface)Proxy.newProxyInstance(
singer.getClass().getClassLoader(),
singer.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("先给出场费,不给钱不唱~");//前置通知
//指定代理操作
Object ret = method.invoke(singer, args);
System.out.println("再来一曲~");//后置通知
return ret;
}
}
);
si.sing();
}
}
被匿名内部类访问的局部变量,必须要被final所修饰。
2.7 抽象类
很简单,概念和java中的抽象类一样,所谓抽象类,指在定义时被abstract关键字修饰,该类中的有抽象方法,或者说没有被实现,把这种类称之为抽象类。
scala中的抽象类也是使用abstract关键字来进行定义;同时该抽象类中既可以有抽象方法,也可以有非抽象方法;scala中的抽象方法可以省略abstract关键字。
abstract class Animal {
var color:String = _
/*abstract*/ def sleep()
def dead(): Unit = {
println("动物固有一死,或清蒸,或红烧~")
}
}
class Dog extends Animal {
def sleep(): Unit = {
println("小狗睡觉是趴着睡~")
}
override def dead(): Unit = {
super.dead()
println("但是,狗乃人类之伙伴,最好不要清蒸,或红烧,不道德")
}
}
class Horse extends Animal {
override def sleep(): Unit = {
println("小马驹睡觉是站着睡~")
}
}
说明:
子类覆盖父类的抽象方法,可以省略掉override关键。
2.8 抽象字段
和java中不同的地方,就是在scala中除了有抽象方法以外,还可以有抽象的字段。抽象字段,即只有字段的定义,没有进行初始化的字段。
abstract class AbstractFu {
/*abstract*/ val name:String
var age:Int
}
class AbstractZi extends AbstractFu {
/*override*/ val name = "zhangsan"
var age = 16
}
定义的这个val的抽象字段,子类只能进行一次初始化,后期则不可以进行修改;而var的变量可以进行任意的操作。
2.9 trait特质
scala中的这个继承和java的继承有一个缺陷,只能进行单继承,但是可以进行多层继承,但是多层继承又要要求,类与类之间必须具有继承关系,这显然不一定满足,还是有局限的。所以在java中推出了接口interface这个概念来满足多重继承,只不过这里不叫继承,而称之为多实现,使用关键字implements来连接,多个接口interface之间使用","进行分割。
scala呢?
scala对于同样的需求,设计出了另外一个结构——trait,特质。这个trait的功能要比java中的接口强大的多,不仅仅拥有抽象方法,还可以拥有非常抽象方法,同时可以多重扩展trait,扩展特质的时候使用关键extends,多个特质之间使用with进行连接。
所以如果说,一个trait特质中的所有方法都是抽象方法,那么该trait就可以当做java中的接口去对待。
- 特质的定义:
object traitTest {
def main(args: Array[String]): Unit = {
val cLog = new ConsoleLog
cLog.log("冬天来了,春天也就不远了~")
}
}
trait Log {
def log(msg:String)
def show(): Unit = {
println("trait中的非抽象方法")
}
}
class ConsoleLog extends Log {
override def log(msg: String): Unit = {
println("console--->" + msg)
}
}
class FileLog extends Log {
override def log(msg: String): Unit = {
println("file--->" + msg)
}
}
- 特质的多扩展
trait的功能要比java中的接口强大的多,不仅仅拥有抽象方法,还可以拥有非常抽象方法,同时可以多重扩展trait,扩展特质的时候使用关键extends,多个特质之间使用with进行连接。
//将日志信息发送到网络中区
class SocketLog extends Log with Serializable {
override def log(msg: String): Unit = {
println("socket--->" + msg)
}
}
- 特质的混入
当一个类的实例只需要进行一次符合某种特性的操作,而这种特性被定义在另外一个特质中,就可以通过scala特质特有的一个概念来处理——混入(mix in),混入并不会对整个类造成侵入性,只会对当前的对象产生一次性的影响。
object TraitMixIn {
def main(args: Array[String]): Unit = {
val cLog = new ConsoleLog
cLog.log("冬天来了,春天也就不远了~")
println("-----------------混入--------------")
val sLog = new SocketLog with Sercurit
sLog.log("我要飞得更高~")
sLog.validate()
}
}
trait Log {
def log(msg:String)
def show(): Unit = {
println("trait中的非抽象方法")
}
}
trait Sercurit {
def validate(): Unit = {
println("安全你我他,快乐给大家~")
}
/*def log(msg: String): Unit = {
println("Sercurit--->" + msg)
}*/
}
//将日志信息发送到网络中区
class SocketLog extends Log {
override def log(msg: String): Unit = {
println("socket--->" + msg)
}
}