大数据——Scala进阶(begin)

本文详细介绍了Scala中的统一类型系统,包括Any、AnyVal和AnyRef,以及Nothing和Null的概念。进一步讲解了类的定义、构造器、私有成员和getter/setter的使用。探讨了特质的定义、使用和子类型概念,以及如何通过混入实现类的组合。此外,还涵盖了元组、高阶函数、嵌套方法、多参数列表和案例类的特性,提供了丰富的代码示例和解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、统一类型(Unified Types


1.1   Scala类型层次解构  

改图摘自:https://docs.scala-lang.org/zh-cn/tour/unified-types.html
Scala类型层次解构

Any 是所有类型的超类型,也称为顶级类型。

Any 定义了通用的方法:equalshashCodetoString

Any 有两个子类:AnyVal AnyRef

AnyVal 代表值类型。分为四类八种 + Unit

四类八种:四类(整型,浮点型,字符型,布尔型)

整型:Byte(字节型)、Short(短整型)、Int(整型)、Long(长整型)

浮点型:Float(单精度浮点型)、Double(双精度浮点型)

字符型:Char(字符型)

布尔型:Boolean(字符型)

Unit:不带任何意义的值类型,仅有一个实例可以像这样声明:( )

AnyRef 代表引用类型。所有非值类型都被定义为引用类型。

AnyRef 包含 ListOptionYourClass

1.2   类型转换  

值类型阔以按照此方向进行转换

1.3   Nothing 和 Null   

Nothing 是所有类型的子类型(底部类型)。

作用:给出非正常终止的信号(抛异常、程序退出、无限循环)

不对值进行定义的表达式类型,或是一个不能正常返回的方法。

Null 是所有引用类型的子类型。

作用:主要是使得Scala 满足和其他JVM语言的互操作性。

有一个被关键字 null 定义的单例值。

 

二、类(Classes


2.1  类定义  

类的定义:关键字 class + 标识符(类名首字母应大写)

使用关键字 new 创建类的实例,

类没有定义任何构造器,默认只有一个无参构造器。

方法重写(Override)

toString方法

def toString: String = {

}

重写toString方法

override def toString: String = {
    #重写toString方法的方法体
}

特殊使用:s

class Person(name: String = "名字",job: String = "工作")
{
  override def toString() : String = {
      
    //"名字:" + this.name + ",工作:" + job;
    s"名字是$name,工作是$job";
    
  }
}
var person01 = new Person()
println("重写toString():"+person01);
var person02 = new Person("小明","学生")
println("重写toString():"+person02);
var person03 = new Person(name="小明")
println("重写toString():"+person03);
var person04 = new Person(job="学生")
println("重写toString():"+person04);

 

2.2  构造器   

类的有参构造器:在类名后面的小括号内阔以定义类的参数。

class Point(var x: Int,var y: Int)

给类的有参构造器的参数设置默认值。阔以在创建类的实例时,带名传参。

class Point(var x: Int = 0,var y: Int = 0)

Task:设置 Point 的 y 值,并打印到控制台。

val point2 = new Point(y = 22);
println(point2.y);    //Console: 22

总结:

1、使用关键字 class 可以定义类。class 类名

2、在类名后的小括号内阔以设置类的参数。类名(var 参数1: 参数类型1,var 参数2: 参数类型2)

3、在创建类的实例时,阔以通过参数名为类的参数赋值。类名(参数=值)

4、阔以通过类的对象名调用类的参数值。对象名.参数名

 

2.3  私有成员和 Getter/Setter  

class Person{
    private var _name = "名字"
    private var _role = "角色"
    
    //Getter方法
    def name = _name
    def role = _role
    
    //Setter方法
    def name_= (_name: String) = this._name = _name
    def role_= (newRole: String) = _kind = newRole
    
    //重写toString()方法
    override def toString(): String = {
        _name + "是" + _kind;
    }


}

总结

1、使用关键字 private 对类的成员私有化。private var 成员变量

2、Setter方法的特殊语法:这个方法在Getter方法的后面加上 _=

Setter的方法名 = Getter的方法名    +   _=

3、不带 val 或者 var 的参数是私有的,仅在类中可见。

class Point(x: Int,y: Int)
val point = new Point(1,2)
point.x    // <-- does not compile 不能编译

 

 

三、特质(Traits)


特质  用于在类之间共享程序接口(Interface)和字段(Fileds)。类似于Java 8的接口(Interface)。

类和对象阔以扩展特质,但是特质不能被实例化,因此特质没有参数

3.1  如何定义特质   

使用关键字 trait + 标识符  定义特质:

trait HairColor

特质作为泛型类型和抽象方法非常有用。

3.2  使用特质   

使用关键字 extends 来扩展特质。

使用关键字 override 来实现特质里面的抽象成员

3.3  子类型   

凡是需要特质的地方,都阔以由该特质的子类型来替换。

3.4  源码   

/**
 * 特质测试:
 * 		1、定义特质
 * 		2、使用特质
 * 		3、特质实例化
 * 		4、子类型
 */
object Scala_03_TraitTest {
  
  def main(args: Array[String]){
    
    //3、特质实例化
    val sex = new judgeSex("男");
    var result = sex.sex();
    println(result);
    
    
    val dog = new Dog("哮天犬");
    val cat = new Cat("加菲猫");
    
    val animals = ArrayBuffer.empty[Pet];
    animals.append(dog);
    animals.append(cat);
    animals.foreach(pet => println(pet.name));
    
  }
  
}

//2、使用特质
class judgeSex(sex: String) extends Sex[String] {
  private var man = "男";
  override def isMan: Boolean = if (sex==man) true else false;
  override def sex(): String = {
    if(isMan){
      "他是大猪蹄子"
    } else "她是小可爱";
      
  }
  
}

//4、子类型
class Cat(val name: String) extends Pet
class Dog(val name: String) extends Pet



//1、定义一个特质
trait Sex[T]{
    def isMan: Boolean
    def sex(): T
    
}

trait Pet{
  val name: String
}

控制台(Console)打印结果

他是大猪蹄子
哮天犬
加菲猫

 

四、元组(Tuples)


元组是可以容纳不同类型的元素的类。类似于Java中的 List<Object>

object Scala_03_TupleTest {
  def main(args: Array[String]){
    //1、创建元组
    println("=========1、创建元组==========")
    val tuple = ("糖果",5);
    println("元组:" + tuple)
    
    //2、访问元素
    println("=========2、访问元素==========")
    println("这种" + tuple._1 + "的售价是:" + tuple._2 + "元");
    
    //3、解构元组数据(为元组的元素起别名)
    println("=========3、解构元组数据(为元组的元素起别名)==========")
    val (name,price) = tuple;
    println("这种" + name + "的售价是:" + price +"元");
    
    //4、元组解构用于模式匹配
    println("=========4、元组解构用于模式匹配==========")
    val shop = List(("卫龙辣条",5),("冰红茶",3),("脆司令",1),("乡巴佬鸡蛋",1));
    println("待解构元组:"+shop);
    shop.foreach(t => {
      t match {
        case ("卫龙辣条",price) => println(s"卫龙辣条的价格是 $price 元");
        case b if (b._1 == "冰红茶") => println(f"冰红茶的价格是 ${b._2} 元");
        case (c,price) if (c == "脆司令") => println(c + "的价格是" + price + "元");
        case _ => println("略……");
      }
    })
    
    //5、在for 表达式中的元组的使用
    println("=========5、使用增强for循环遍历元组数据==========")
    for((a,b) <- shop)
    {
      println(a + "的价格是" + b + "元");
    }
    
    
  }
}

控制台(Console)输出结果

=========1、创建元组==========
元组:(糖果,5)
=========2、访问元素==========
这种糖果的售价是:5元
=========3、解构元组数据(为元组的元素起别名)==========
这种糖果的售价是:5元
=========4、元组解构用于模式匹配==========
待解构元组:List((卫龙辣条,5), (冰红茶,3), (脆司令,1), (乡巴佬鸡蛋,1))
卫龙辣条的价格是 5 元
冰红茶的价格是 3 元
脆司令的价格是1元
略……
=========5、使用增强for循环遍历元组数据==========
卫龙辣条的价格是5元
冰红茶的价格是3元
脆司令的价格是1元
乡巴佬鸡蛋的价格是1元

五、通过混入(Mixin)来组合类(Class Composition with Mixins)


官网定义:当某个特质被用于组合类时,被称为混入。


个人理解:特质(C) 被一个继承了父类(B)的类(A)实现的时候,即为混入。

代码表示为:(诸如以下形式存在的情况下,称为混入。)

class A extends B with C
Java和Scala关于继承和接口所用关键字的区别
 JavaScala
继承父类extends
实现接口(特质)implementswith

Java中:一个类只能继承一个父类,实现多个接口。使用关键字 extends 继承父类,使用关键字 implements 实现接口。

Scala中:一个类只能有一个父类可以有多个混入。使用关键字 extends 继承父类,使用关键字 with 实现特质。

源码

object Scala_05_MixinTest {
  
  def main(args: Array[String]){
    
    val d = new D;
    println(d.message);
    println(d.loudMessage);
    
  }
  abstract class A{
      val message: String
  }
  class B extends A{
    // B类 为message 赋值
    val message = "Hello World!";
  }
  trait C extends A{
    //特质C 的方法将message转换为大写
    def loudMessage = message.toUpperCase();
  
  }
  class D extends B with C
  /**
   * D类拥有自B类继承的值为"Hello Wolrd"的message属性,
   * 又拥有特质C的将message转换为大写的loudMessage的方法
   */
  
  
  
}

控制台(Console)打印结果

Hello World!
HELLO WORLD!

 

六、高阶函数(Higher-order Functions)


官方定义:使用其他函数作为参数、或者返回一个函数作为结果的函数。

使用函数值作为参数,或者返回值为函数值的 "函数" 和 "方法",均称为 "高阶函数"。

使用高阶函数的原因之一:减少冗余的代码

强制转换方法为函数   

可以传入一个对象方法作为高阶函数的参数

接收函数作为参数的函数   

返回函数的函数   

6.4  源码   

object Scala_06_HigherFuncTest {
  
  def main(args: Array[String]){
    
    var balances = Seq(2000,7000,4000);
    
    var pay01 = (x: Int) => x * 0.05;
    var charge = balances.map(pay01);
    
    // 遍历数组
    println("========手续费为5%:========")
    charge.foreach(f => {    
    	println("手续费为" + f);
    })
    
    charge = balances.map(x => x * 0.03);
    println("========手续费为3%:========")
    charge.foreach(f => {    
    	println("手续费为" + f);
    })
    
    // _ 代表balances中的每一个元素
    charge = balances.map(_ * 0.01);
    println("========手续费为1%:========")
    charge.foreach(f => {    
    	println("手续费为" + f);
    })
    
    
    //1、强制转换方法为函数
    println("=========1、强制转换方法为函数=========")
    var wwf = Charge(balances);
    var result = wwf.charge;
    result.foreach(r => {
      println("手续费:" + r);
    })
    
    //2、接收函数作为参数的函数
    println("=========2、接收函数作为参数的函数=========")
    var balances02 = List(2000.0,7000.0,4000.0)
    
    println("smallPromotion——————" + smallPromotion(balances02));
    println("bigPromotion——————" + bigPromotion(balances02));
    println("hugePromotion——————" + hugePromotion(balances02));
    
    
    //3、返回函数的函数
    println("=========3、返回函数的函数=========")
    def urlBuilder(ssl: Boolean,domainName: String): (String,String) => String = {
        val schema = if (ssl) "https://" else "http://"
        (endpoint: String,query: String) => s"$schema$domainName/$endpoint?$query"
      
    }
    val domainName = "www.example.com"
    def getURL = urlBuilder(ssl=true,domainName)
    val endpoint = "users"
    val query = "id=1"
    val url = getURL(endpoint,query);
    println(url);
    
    
    
  }
  // 定义一个样例类
  case class Charge(balances: Seq[Int]){
    private def pay(temp: Int) = temp * 0.01;
    def charge: Seq[Double] = balances.map(pay);
  }
  
  
  //2、接收函数作为参数的函数
  private def promotion(salaries: List[Double], promotionFunction: Double => Double): List[Double] =
  salaries.map(promotionFunction)

  def smallPromotion(salaries: List[Double]): List[Double] =
  promotion(salaries, salary => salary * 1.1)

  def bigPromotion(salaries: List[Double]): List[Double] =
  promotion(salaries, salary => salary * math.log(salary))

  def hugePromotion(salaries: List[Double]): List[Double] =
  promotion(salaries, salary => salary * salary)
  
}

控制台(Console)打印结果

========手续费为5%:========
手续费为100.0
手续费为350.0
手续费为200.0
========手续费为3%:========
手续费为60.0
手续费为210.0
手续费为120.0
========手续费为1%:========
手续费为20.0
手续费为70.0
手续费为40.0
=========1、强制转换方法为函数=========
手续费:20.0
手续费:70.0
手续费:40.0
=========2、接收函数作为参数的函数=========
smallPromotion——————List(2200.0, 7700.000000000001, 4400.0)
bigPromotion——————List(15201.804919084165, 61975.657996262154, 33176.19856040811)
hugePromotion——————List(4000000.0, 4.9E7, 1.6E7)
=========3、返回函数的函数=========
https://www.example.com/users?id=1

七、嵌套方法(Nested Methods)


在Scala中可以嵌套定义方法。

源码

object Scala_07_NestedMethodsTest {
  
  def main(args: Array[String]){
 
    def factorial(x: Int): Int = {
      def fact(x: Int,accumulator: Int): Int = {
        if(x <= 1) accumulator
        else fact(x - 1,x * accumulator)
      }
      fact(x, 1);
    }
    
    println("2!= 1x2 =" + factorial(2));
    println("3!= 1x2x3 =" + factorial(3));
    
  }
  
}

 

八、多参数列表(柯里化)(Multiple Parameter Lists Currying)


方法可以定义多个参数列表,当使用较少的参数列表调用多参数列表的方法时,会产生一个新的函数,该函数接收剩余的参数列表作为其参数。这被称为柯里化。

单一的函数参数

存在单一的函数参数时,可以传递匿名函数作为参数使语法更为简洁。

隐式参数

如果指定参数列表中的某些参数为隐式,应该使用多参数列表。 

源码

object Scala_08_CarryingTest {
  
  def main(args: Array[String]){
    
    val numbers = List(1,2,3,4,5,6,7,8,9,10);
    // 0+1+2+3+4+5+6+7+8+9+10=55
    
    //传递匿名函数作为参数
    var res = numbers.foldLeft(0)((m,n) => m + n);
    println(res);
    //不使用传递匿名函数作为参数
    res = numbers.foldLeft(0)({(m: Int,n: Int) => m + n});
    println(res);
    //Scala的类型推断
    res = numbers.foldLeft(0)(_ + _);    
    println(res);
    
  }
  
  
}

控制台(Console)打印结果

55
55
55

九、案例类(Cass Classes)


案例类非常适合用于不可变的数据

9.1  案例类的定义   

使用关键字  case class + 类名 + 参数列表(可为空)组成

9.2  案例类的实例化   

案例类的实例化没有使用关键字 new

案例类有一个默认的 apply 方法来负责对象的创建

案例类的参数是公开的常量

案例类的参数是公开的常量

9.3  比较   

案例类在比较的时候是按值比较而非按引用比较

9.4  拷贝   

通过 copy 方法创建可以创建一个案例类实例的浅拷贝

9.5  拓展  浅拷贝和深拷贝   

基本数据类型:直接存储在栈(stack)中的数据
引用数据类型:存储的是该对象在栈中引用,真实的数据存放在堆内存里

引用数据类型在栈中存储了指针(内存地址,哈希码hashCode),该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

9.6  源码   

object Scala_09_CaseClassTest {
  
  
  def main(args: Array[String]){
    
    println("——————酒逢知己千杯少————————");
    
    //案例类的定义
    case class Book(name: String)
    //案例类的实例化
    var book01 = Book("西游记");  
    //案例类属性的调用
    println("这本书名字叫《"+ book01.name +"》");
    
    //案例类的参数为公开的常量,可以访问,不可重新赋值
    //book01.name = "红楼梦";
    
    book01 = Book("三国演义");
    println("这本书名字叫《"+ book01.name +"》");
    
    
    var book1 = Book("水浒传");
    var book2 = Book("水浒传");
    var book3 = Book("聊斋志异");
    
    println("book1:"+book1+"\nbook2:"+book2+"\nbook3:"+book3);
    
    println("book1 == book2的结果:" + (book1 == book2));
    println("book2 == book3的结果:" + (book2 == book3));
    println("book3 == book1的结果:" + (book3 == book1));
    
    
    case class Relationship(name: String,partner: String)
    
    val wukong = Relationship("悟空","唐僧");
    println(wukong.name+"的搭档是:"+wukong.partner);
    
    val tangseng = wukong.copy(name = wukong.partner, partner = wukong.name);
    println(tangseng.name+"的搭档是:"+tangseng.partner);
    
    
    
  }
  
  
}

控制台(Console)打印结果

——————酒逢知己千杯少————————
这本书名字叫《西游记》
这本书名字叫《三国演义》
book1:Book(水浒传)
book2:Book(水浒传)
book3:Book(聊斋志异)
book1 == book2的结果:true
book2 == book3的结果:false
book3 == book1的结果:false
悟空的搭档是:唐僧
唐僧的搭档是:悟空

十、模式匹配(Pattern Matching)


模式匹配:检查某个值是否匹配某一个模式的机制,一个成功的匹配同时会将匹配值解构为其组成部分。


个人理解:判断某一结果属于哪一种情况,从而会有什么影响。

10.1  语法   

在Java中

switch(key){
    case value1:
        //result1;
        break;
    case value2:
        //result2;
        break;
    default:
        //default result;
        break;
}

在scala中

key match{
    case value1 => result1;
    case value2 => result2;
    case _ => "other result"

}

其中 _ 相当于 Java中switch case 语句中的 default

10.1  模式守卫(Pattern gaurds)

使用模式守卫的目的是:让匹配更具体。 在模式后加上 if <boolean expression>

key match{
    case value1 if value1.contains("s") => result1;
    ……
}

10.2  仅匹配类型   

abstract class Device
case class Phone(model: String) extends Device {
  def screenOff = "Turning screen off"
}
case class Computer(model: String) extends Device {
  def screenSaverOn = "Turning screen saver on..."
}

def goIdle(device: Device) = device match {
  case p: Phone => p.screenOff
  case c: Computer => c.screenSaverOn
}

10.3  密封类   

特质(trait)和类(class)可以用 sealed 标记为密封的。意味着其所有子类都必须与之定义在相同文件中,从而保证所有子类型都是已知的。

sealed abstract class Furniture
case class Couch() extends Furniture
case class Chair() extends Furniture

def findPlaceToSit(piece: Furniture): String = piece match {
  case a: Couch => "躺在沙发上"
  case b: Chair => "坐在椅子上"
}

10.4  备注   

Scala的模式匹配语句对于使用案例类表示的类型非常有用,同时也可以利用提取器对象中的 unapply方法;来定义非案例类对象的匹配。

 

十一、单例对象(Singleton Objects)


单例对象有且只有一个实例,是延迟创建的,当它第一次被使用时创建。

单例对象的定义

object SingletonObject

伴生对象

当一个单例对象和某个类共享一个名称时,这个单例对象称为 伴生对象。 同理,这个类被称为是这个单例对象的 伴生类。类和它的伴生对象可以互相访问其私有成员。使用伴生对象来定义那些在伴生类中不依赖于实例化对象而存在的成员变量或者方法。

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值