热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入掌握Scala面向对象编程与Spark源码解析

在第二课中,我们将深入探讨Scala的面向对象编程核心概念及其在Spark源码中的应用。首先,通过详细的实战案例,全面解析Scala中的类和对象。作为一门纯面向对象的语言,Scala的类设计和对象使用是理解其面向对象特性的关键。此外,我们还将介绍如何通过阅读Spark源码来进一步巩固对这些概念的理解。这不仅有助于提升编程技能,还能为后续的高级应用开发打下坚实的基础。

 

第二课:Scala面向对象彻底精通及Spark源码阅读


 

 

 


一、 Scala中的类、object实战详解

Scala是纯面向对象语言谈面向对象,一定要谈类,类把数据和代码封装起来。

 


跟Java一样,Scala也是用关键字class定义类。示例如下:

 

scala> class HiScala{

     | private var name = "Spark"

     | def sayName(){println(name)}

     | def getName = name

     | }

defined class HiScala

 

以上代码,定义了一个名称为HiScala的类,默认情况下是public级别,所以public关键字可以不写

ü 定义了一个属性name,可变变量,访问级别为private,在类的外面不能访问!

ü 定义一个函:sayName

ü 定义一个函数:getName

在Scala中,变量与类中的方法是同等级的,可以直接相互赋值。

 

 


创建类实例

 

scala> val scal = new HiScala

scal: HiScala = HiScala@1c655221

//此时,scal就是HiScala类的一个实例。但是,在Scala我们一边不会用new创建的实例,而是apply工厂方法模式创建。

 

//调用scal的sayName方法,用于打印出name成员的值。

scala> scal.sayName()

Spark

 

//由于sayName没有传参数,所以可以把括号去掉,这样更简洁。

scala> scal.sayName

Spark

 

//调用getName方法,访问name成员的值

scala> scal.getName

res2: String = Spark

//此时确实返回了name的值。

 

//该示例name私有的,所以不能直接访问scal实例name,如下访问就出错了。

scala> scal.name

:10: error: variable name in class HiScala cannot be accessed in HiScala

              scal.name

                   ^


getset

ScalagetsetJavagetset很大的差异

Scala中,如果给变量前定义了private,那么Scala解释器会给这个变量自动生成privategetset ;

如果变量前没有定义了private,那么解释器会给这个变量自动生成publicgetset方法,这样可以直接访问类的实例对象的成员变量,实际访问的是setget方法

 

//我们重新改造上面的类,把属性name前面private访问级别去掉。

scala> class HiScala{

     |        var name = "Spark"   //去掉了private关键字

     |        def sayName(){println(name)}

     |        def getName = name

     |        }

defined class HiScala

 

scala> val scal = new HiScala

scal: HiScala = HiScala@1e6d1014

 

//访问name属性, 值Spark

scala> scal.name

res4: String = Spark

//此时虽然是访问属性name,其实不是直接访问var指定的变量name,而是Scala解释器自动name生成public级别getset方法

 

//修改name属性

scala> scal.name="Scala"

scal.name: String = Scala

 

//再次访问name属性, 值已经变为Scala

scala> scal.name

res5: String = Scala

 

 


自定义的getset

scala> class Person {

     |     private var myName = "Flink"

     |     def name = this.myName        //自定义get方法

     |     def name_=(newName : String){  //自定义set方法,注意下划线和等号之间没有空格!

     |         myName = newName

     |         println("Hi " + myName)        

     |     }

     | }

defined class Person

 

// luck为Person 类实例

scala> val luck = new Person

luck: Person = Person@6a5fc7f7

 

//通过自定义的get方法,访问myName属性

scala> luck.name

res0: String = Flink

 

//调用自定义的set方法,修改了myName属性

scala> luck.name = "Spark"

Hi Spark

luck.name: String = Spark

 

//再次访问myName属性,发现属性值已经变成Spark

scala> luck.name

res2: String = Spark

 

//修改上面的示例,仅仅暴露属性get方法,没有为其复写set方法。但是提供一个名为update方法用来修改属性的值

scala> class Person {

     |     private var myName = "Flink"

     |     def name = this.myName  //get方法

     |     def update(newName : String){

     |         myName = newName

     |         println("Hi " + myName)        

     |    }

     | }

defined class Person

 

// luck为Person 类实例

scala>   val luck = new Person

luck: Person = Person@7c469c48

 

//通过自定义的get方法,访问myName属性值,初始值为Flink

scala> luck.name

res4: String = Flink

 

//直接通过set方法来修改myName,会提示出错,因为没有复写set方法

scala> luck.name="Hadoop"

:9: error: value name_= is not a member of Person

       luck.name="Hadoop"

            ^

 

//通过额外提供的update方法来修改属性

scala> luck.update("Hadoop")

Hi Hadoop

 

//再次查看myName属性值,发现已经变成了Hadoop

scala> luck.name

res6: String = Hadoop

 

 


private[this]

private[this]至关重要的,Spark源码中随处可见。

代表属性方法为对象私有!在类私有的基础上强一层的控制。

 

//定义一个类:Person

scala> class Person {

     |     private var myName = "Flink"

     |     def name = this.myName  

     |     def update(newName : String){  

     |         myName = newName

     |         println("Hi " + myName)        

     |     }

     |     def talk(p:Person) = {

     |         println("hello:"+p.name)

     |     }  

     | }

defined class Person

 

//定义 p1Person对象

scala> val p1=new Person

p1: Person = Person@14555e0a

//定义 p2Person对象

scala> val p2=new Person

p2: Person = Person@1b2abca6

 

//p1myName 属性值改为p1

scala> p1.update("p1")

Hi p1

 

//p2的myName 属性值改为p2

scala> p2.update("p2")

Hi p2

 

//查看p1myName 属性,此时已经改为p1

scala> p1.name

res14: String = p1

 

//查看p2的myName 属性,此时已经改为p2

scala> p2.name

res15: String = p2

 

//调用p1talk方法,传入p2对象,打印出p2myName 属性

scala> p1.talk(p2)

hello:p2

 

 

//我们修改一下代码,看看下面代码:

scala> class Person {

     |     private[this] var name = "Flink"

     |     def update(newName : String){  

     |         name = newName

     |         println("Hi " + name)        

     |     }

     |     def talk(p:Person) = {

     |         println("hello:"+p.name)

     |     }  

     | }

:15: error: value name is not a member of Person

               println("hello:"+p.name)

                                  ^

//此时,定义类报错!因为talk方法中p参数是Person的对象,name属性被限制private[this],所以只能Person类内部使用,Person类的对象无权使用。也就是Person类中,update方法中可以使用name,但是talk方法访问对象pname是不允许的!

 

// private[this]改成private之后,下面的写法能正常定义,代码如下:

scala> class Person {

     |     private var name = "Flink"

     |     def update(newName : String){  

     |         name = newName

     |         println("Hi " + name)        

     |     }

     |     def talk(p:Person) = {

     |         println("hello:"+p.name)

     |     }  

     | }

defined class Person

scala>

 


构造器的重载

scala> class Person {

     |     private[this] var name = "Flink"

     |     private[this] var age = 10

     |     def update(newName : String){  

     |         name = newName

     |         println("Hi " + name)        

     |     }

     |  

     |         

     |     //重载的构造器,首先调用默认的构造器

     |     def this(name:String){

     |       this()

     |       this.name=name

     |     }

     |     

     |     //重载的构造器,调用上面已经存在的构造器

     |     def this(name:String, age:Int){

     |       this(name)

     |       this.age=age  

     |     }

     | }

defined class Person

 

 

Spark源码中,构造器重载非常常见,如下图所示:

 

(图一)

从图一的源代码可以看出,SparkContext类重写了有很多构造器,在创建SparkContext实例对象时,传递不同的参数来调用相应的构造器

 

Spark中,与类名放在一起的构造器为默认构造器,如下所示:

 

(图二)

 

默认构造器可以带参数也可以不带参数,在图二所示的源代码SparkContext类中,默认构造器带有一个参数。初始化类成员的工作,都放在默认构造器中完成。在SparkContext类中,creationSite、allowMultipleContexts等变量,就是在SparkContext类的默认构造器中完成。

 


object

 

定义一个类同名的object对象,里面存放静态的成员或者方法,把该object对象称之为类的伴生对象

查看SparkContext源码,如下图三所示:

 

(图三)

三中,SparkContextobject为SparkContext伴生对象,里面存放一系列静态的成员静态的方法。而且,当我们第一次调用SparkContext时,SparkContext伴生对象会被执行一次,仅此一次

 

 

下面是一个示例:

 

(图4)

上图object PersonclassPerson的伴生对象,class Person称之为object Person伴生类。伴生对象适合里面定义一些工具方法,以及存放一些全局唯一的常量,节省空间。

 

 

//第一次调用:Person伴生对象getSalary方法

//此时,我们发现:Person伴生对象被初始化,打印出字符串Scala

scala> Person.getSalary

Scala

res34: Double = 0.0

 

//第二次调用:Person伴生对象getSalary方法

//此时,我们发现:Person伴生对象再需要初始化,没有打印出字符串Scala

scala> Person.getSalary

res35: Double = 0.0

以上示例中确实验证了:伴生对象会被执行一次,仅此一次

 

Scala中,我们在定义的对象时,一般不会new类,再传入参数方式来定义而是直接用类名,或者类名加参数方式。例如,我们重温下面的示例:

我们构造一个数组,其实是调用Array的伴生对象object Arrayapply方法

scala> val array=Array(1,2,3)

array: Array[Int] = Array(1, 2, 3)

 

scala> val array=Array.apply(1,2,3)

array: Array[Int] = Array(1, 2, 3)

以上两种定义数组的方式效果是一样的。apply方法就是当前类的伴生对象的工厂方法。延伸一下,Java水平比较高才编程人员,在构造Java对象时,一般来说不会直接new一个类,而是通过工厂方法模式来创建。而Scala语言中,天生就支持这次模式,所以在具体类对象构造时,一般是在伴生类的伴生对象apply方法去实现!这样可以控制对象的生成。


二、 Scala中的抽象类、接口实战详解


abstract

抽象使用abstract关键,以Spark源码RDD类为示例,如下图5所示:

 

(图5)

子类去继承抽象类,跟Java一样,也是使extends关键字。如JdbcRDD就继承RDD抽象类,如下:

class JdbcRDD[T: ClassTag](

    sc: SparkContext,

    getConnection: () => Connection,

    sql: String,

    lowerBound: Long,

    upperBound: Long,

    numPartitions: Int,

    mapRow: (ResultSet) => T = JdbcRDD.resultSetToObjectArray _)

  extends RDD[T](sc, Nil) with Logging {

那么JdbcRDD就可以使用父类RDD所有它可以使用的属性和方法。

当然,子类也可以覆盖父类的属性和方法。注意,这里面有个前提,父类的属性和方法没有加final

 

一个抽象类,那么里面肯定定义了某个方法,但是没有实现体,只有方法的说明。比如:

 

(图六)

上图代码块中,compute就是RDD抽象类的一个方法,只有说明,没有实现体。

 


override

在上面JdbcRDD子类中,因为JdbcRDD继承RDD抽象类,那么在JdbcRDD中一定要复写compute方法。如下所示:

 

使用override关键字表示复写父类该方法除了覆盖父类的方法,也可以使用override覆盖父类的val属性父类在定义属性没有具体的值,就是抽象属性,在子类中,子类必须覆盖该属性。

 


trait

相当于Java接口在trait定义的类可以定义抽象方法,但是又没有具体的实现子类可以使用extends关键字来继承父类

 

Spark源码中的RDD类为示例,RDD继承Serializable和Logging两个父类。一个子类如果继承多个父类,那么继承第一个父类使用extends关键字,后面的父类使用with关键字。RDD继承的这两个父类,定义时都使用trait关键字来定义。Scala语法中,不允许一个子类同时继承多个抽象父类,但是允许同时继承多个trait父类

abstract class RDD[T: ClassTag](

    @transient private var _sc: SparkContext,

    @transient private var deps: Seq[Dependency[_]]

  ) extends Serializable with Logging {

 

 

下面Serializable类的源码:

 

 

trait用途,更多的是作为工具方法的容器我们把通用的功能,放在trait一个子类,如果需要把很多通用的功能都混进来,那么就需要继承多个trait父类,继承的第一个父类使用extends,后面的父类使用withSpark源码Master就是一个示例,继承了多个父类。

 

 


三、 综合案例及Spark源码解析

 

阅读Spark源码SparkContext类,熟悉第一课本课的知识点。

DT大数据梦工厂

 


推荐阅读
  • 技术日志:深入探讨Spark Streaming与Spark SQL的融合应用
    技术日志:深入探讨Spark Streaming与Spark SQL的融合应用 ... [详细]
  • 如何高效启动大数据应用之旅?
    在前一篇文章中,我探讨了大数据的定义及其与数据挖掘的区别。本文将重点介绍如何高效启动大数据应用项目,涵盖关键步骤和最佳实践,帮助读者快速踏上大数据之旅。 ... [详细]
  • 在使用sbt构建项目时,遇到了“对象apache不是org软件包的成员”的错误。本文详细分析了该问题的原因,并提供了有效的解决方案,包括检查依赖配置、清理缓存和更新sbt插件等步骤,帮助开发者快速解决问题。 ... [详细]
  • JUnit下的测试和suite
    nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
  • 本指南从零开始介绍Scala编程语言的基础知识,重点讲解了Scala解释器REPL(读取-求值-打印-循环)的使用方法。REPL是Scala开发中的重要工具,能够帮助初学者快速理解和实践Scala的基本语法和特性。通过详细的示例和练习,读者将能够熟练掌握Scala的基础概念和编程技巧。 ... [详细]
  • 字节跳动深圳研发中心安全业务团队正在火热招募人才! ... [详细]
  • 本文详细探讨了OpenCV中人脸检测算法的实现原理与代码结构。通过分析核心函数和关键步骤,揭示了OpenCV如何高效地进行人脸检测。文章不仅提供了代码示例,还深入解释了算法背后的数学模型和优化技巧,为开发者提供了全面的理解和实用的参考。 ... [详细]
  • 如何在Spark数据排序过程中有效避免内存溢出(OOM)问题
    本文深入探讨了在使用Spark进行数据排序时如何有效预防内存溢出(OOM)问题。通过具体的代码示例,详细阐述了优化策略和技术手段,为读者在实际工作中遇到类似问题提供了宝贵的参考和指导。 ... [详细]
  • 当需要确保对象的状态在创建后不可更改时,使用记录(Record)类型是一个理想的选择。本文探讨了如何通过记录类型实现对象的不可变性,并提供了一个简单的示例来说明其用法。 ... [详细]
  • 近期尝试从www.hub.sciverse.com网站通过编程手段获取数据时遇到问题,起初尝试使用WebBrowser控件进行数据抓取,但发现使用GET方法翻页时,返回的HTML代码始终相同。进一步探究后了解到,该网站的数据是通过Ajax异步加载的,可通过HTTP查看详细的JSON响应。 ... [详细]
  • Spark中使用map或flatMap将DataSet[A]转换为DataSet[B]时Schema变为Binary的问题及解决方案
    本文探讨了在使用Spark的map或flatMap算子将一个数据集转换为另一个数据集时,遇到的Schema变为Binary的问题,并提供了详细的解决方案。 ... [详细]
  • 本文介绍了如何使用 Spark SQL 生成基于起始与终止时间的时序数据表。通过 `SELECT DISTINCT goods_id, get_dt_date(start_time, i) as new_dt` 语句,根据不同的时间间隔 `i` 动态填充日期,从而构建出完整的时序数据记录。该方法能够高效地处理大规模数据集,并确保生成的数据表准确反映商品在不同时间段的状态变化。 ... [详细]
  • 第二章:Kafka基础入门与核心概念解析
    本章节主要介绍了Kafka的基本概念及其核心特性。Kafka是一种分布式消息发布和订阅系统,以其卓越的性能和高吞吐量而著称。最初,Kafka被设计用于LinkedIn的活动流和运营数据处理,旨在高效地管理和传输大规模的数据流。这些数据主要包括用户活动记录、系统日志和其他实时信息。通过深入解析Kafka的设计原理和应用场景,读者将能够更好地理解其在现代大数据架构中的重要地位。 ... [详细]
  • 美团优选推荐系统架构师 L7/L8:算法与工程深度融合 ... [详细]
  • 本文探讨了协同过滤算法在推荐系统中的应用,重点介绍了基于用户和基于物品的两种协同过滤方法。通过引入相似度评估技术和交替最小二乘优化技术,显著提升了推荐系统的准确性和鲁棒性。实验结果表明,该方法在处理大规模数据集时表现出色,能够有效提高用户满意度和系统性能。 ... [详细]
author-avatar
robinqianqcs521
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有