你好,游客 登录
背景:
阅读新闻

Scala编程教程:Scala + IntelliJ IDEA

[日期:2018-09-06] 来源:cnblgos  作者:万箭穿心,习惯就好。 [字体: ]

 学习路上的新起点:大数据Scala + Spark +(HDFS + HBase),本文主要介绍下Scala的基本语法和用法吧。最后再简单介绍一种Java开发工具IntelliJ IDEA的使用。

Scala

Scala语言是一种面向对象语言,结合了命令式(imperative)和函数式(functional)编程风格,其设计理念是创造一种更好地支持组件的语言。

Object-Oriented Meets Functional

特性

  • 多范式(Multi-Paradigm)编程语言,类似Java、C#;
  • 继承面向对象编程和函数式编程的特性;
    •   面向对象:[1]. 子类继承,[2]. 混入(Mixin)机制;
    •   函数式:支持高阶函数、嵌套函数,模式匹配,支持柯里化(Currying)
    •   类型推断(Type Inference):根据函数体中的具体内容推断方法的返回值类型
  • 更高层的并发模型,可伸缩;
  • 静态类型,编译时检查;
  • 允许与Java互操作(将来会支持与.Net互操作);

与Java对比

  • 如果用作服务器端开发,Scala的简洁、灵活性是Java无法比拟的;
  • 如果是企业级应用开发、Web开发,Java的强大是Scala无法匹敌的;

Scala和Groovy

Groovy的优势在于易用性以及与Java无缝衔接,更注重实效思想,脚本化;

Scala的优势在于性能和一些高级特性,静态类型,更具有学术思想;

关于Lift

一个使用了Scala的开源的Web应用框架,可以使用所有的Java库和Web容器。其他框架:Play、Bowler等。

推荐:Play FrameworkSlick,具体学习待续... ...

  • Play:The High Velocity Web Framework For Java and Scala
  • Slick:Functional Relational Mapping for Scala

环境

Scala SDK: scala-2.10.4.msi

安装路径最好不要带空格和中文字符,否则,在 cmd 命令行窗口执行 scala -version 会报错 此时不应有 \scala\bin\scala.bat) 的问题

安装完成后,确保 ..\lib 目录下包含如下文件:

scala-compiler.jar, scala-library.jar, scala-reflect.jar

注意,.msi 版本是安装版,.zip 解压即可。

在 cmd 命令行中,测试:

Note:本地可以安装多个版本的 Scala SDK,以 scala 2.11 为例:

1
2
3
// scala-2.11.8.zip
Scala_2.11_Home:D:\scala-2.11.8
Path:%Path%; %Scala_2.11_Home%\bin

使用时,用哪个SDK,在Path变量中就将哪个SDK放在前面即可。

基础语法问题

Scala程序的执行入口是提供main方法的独立单例对象。每个Scala函数都有返回值,每个Scala表达式都有返回结果。

在Scala中没有静态的概念,所有东西都是面向对象的。其实object单例对象只是对静态的一种封装而已,在.class文件层面,object单例对象就是用静态(static)来实现的。

一切皆为对象

Scala中所有的操作符都是方法,操作符面向的使用者都是对象

Scala编程的一个基本原则上,能不用var,尽量不用var,能不用mutable变量,尽量不用mutable变量,能避免函数的副作用,尽量不产生副作用。

  • 对于任何对象,如果在其后面使用(),将调用该对象的apply方法,arr(0) == arr.apply(0)
  • 如果对某个使用()的对象赋值,将该对象的update方法,arr(0)=value == arr.update(0,value)

如果一个方法只有一个参数,可以不用 括号和. 来调用这个方法。

字段 + 方法 + 函数

scalac编译器会为类中的var字段自动添加setter和getter方法,会为类中的val方法自动添加getter方法。 其中,getter方法和字段名相同。

在源码中,所有对字段的显示访问,都会在class中编译成通过getter和setter方法来访问。

方法作用于对象,方法是对象的行为。方法定义格式:

1
def 方法名称(参数列表):返回值 = { 方法体 }

函数定义格式:

1
2
val 函数名称 = { (参数列表) => { 函数体 } },即 val 函数名称 = { Lambda表达式 }
或 val 函数名称 : (入参类型 => 出参类型) = { 入参 => 函数体 }

方法名意味着方法调用,函数名只代表函数本身

关于方法(Method)和函数(Function)

  • 函数是一等公民,使用val语句可以定义函数,def语句定义方法;
  • 函数是一个对象,继承自FuctionN,函数对象有curried,equals,isInstanceOf,toString等方法,而方法不具有;
  • 函数是一个值,可以给val变量赋值,方法不是值、也不可以给val变量赋值;
  • 通过将方法转化为函数的方式 method _method(_) 实现给val变量赋值;
  • 若 method 有重载的情况,方法转化为函数时必须指定参数和返回值的类型;
  • 某些情况下,编译器可以根据上下文自动将方法转换为函数;
  • 无参数的方法 method 没有参数列表(调用:method),无参数的函数 function 有空列表(调用:function());
  • 方法可以使用参数序列,转换为函数后,���须用 Seq 包装参数序列;
  • 方法支持默认参数,函数不支持、会忽略之;

参考学习Scala:Scala中的字段和方法Scala中Method和Function的区别 - 简书

参数

  • 按名传参:By-name parameter,def fun(param: => T)。Evaluated every time it's used within the body of the function
  • 按值传参:By-value parameter。Evaluated before entry into the function/method

推荐将按值传参(def fun(msg: String))改为按名称传参(def fun(msg: =>String))。这样参数会等到实际使用的时候才会计算,延迟加载计算,可以减少不必要的计算和异常。

case class

1
case class PubInterfaceLog(interfaceId: Int, busiType: String, respTime: Long, publicId: Long)

最重要的特性是支持模式匹配,Scala官方表示:

It makes only sense to define case classes if pattern matching is used to decompose data structures.

其他特性如下:

  • 编译器自动生成对应的伴生对象和 apply() 方法;
  • 实例化时,普通类必须 new,而 case class 不需要;
  • 主构造函数的参数默认是 public 级别,默认在参数为 val;
  • 默认实现了 equals 和 hashCode,默认是可以序列化的,也就是实现了Serializable,toString 更优雅;
  • 默认生成一个 copy 方法,支持从实例 a 以部分参数生成另一个实例 b;

参考Scala case class那些你不知道的知识

apply() 方法

apply方法是Scala提供的一个语法糖

  • 类名+括号,调用对象的apply方法 
  • 对象名+括号,调用类的apply方法

对apply方法的简单测试:(其中,带 new -- class ApplyTest,不带 new -- object ApplyTest)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class ApplyTest {
    println("class ApplyTest")
    def apply() {
        println("class APPLY method")
    }
}
object ApplyTest {
    println("object ApplyTest")
    def apply() = {
        println("object APPLY method")
        new ApplyTest()
    }
}
 
// 对象名+括号,调用类的apply方法
val a1 = new ApplyTest()
a1() // == a1.apply()
// 输出 class ApplyTest, class APPLY method
 
// 类名+括号,调用对象的apply方法
val a2 = ApplyTest()
// 输出 object ApplyTest, object APPLY method, class ApplyTest
 
val a2 = ApplyTest()
a2()
// 输出 object ApplyTest, object APPLY method, class ApplyTest, class APPLY method
 
val a3 = ApplyTest
// 输出 object ApplyTest
 
val a3 = ApplyTest
a3()
// 输出 object ApplyTest, object APPLY method, class ApplyTest

几个零碎的知识点

  • Array 长度不可变, 值可变;Tuple 亦不可变,但可以包含不同类型的元素;
  • List 长度不可变, 值也不可变,::: 连接两个List,:: 将一个新元素放到List的最前端,空列表对象 Nil
  • _:通配符,类似Java中的*下划线 "_ " 的用法总结
  • _=:自定义setter方法;
  • _*:参数序列化,将参数序列 Range 转化为 Seq
  • i to j : [i, j]; i until j : [i, j)
  • lazy val表示延迟初始化,无lazy var

映射 + 对偶 + 元组

映射由对偶组成,映射是对偶的集合。对偶即键值对,键->值,(键, 值),是最简单的元组。元组是不同类型的值的聚集。

映射是否可变表示整个映射是否可变,包括元素值、映射中元素个数、元素次序等:

  • 不可变映射:直接用(scala.collection.mutable.)Map声明,维持元素插入顺序,支持 += 、-=;
  • 可变映射:用scala.collection.immutable.Map声明,不维持元素插入顺序,支持 +、-;

构造器

1主构造器

1
2
3
class Student(var ID : String, var Name : String) {
    println("主构造器!")
}
  • 主构造器直接跟在类名后面,主构造器的参数最后会被编译成字段
  • 主构造器执行时,会执行类中所有的语句
  • 如果主构造器参数声明时不加val或var,相当于声明为private级别

2从构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Student(var ID : String, var Name : String) {
    println("主构造器!")
 
    var Age : Int = _
    var Address : String = _
    private val Email : String = Name + ID + "@163.com"
 
    println("从构造器!")
    def this(ID:String, Name:String, Age:Int, Address:String) {
        this(ID, Name)
        this.Age = Age
        this.Address = Address
    }
}
  • 从构造器定义在类内部,方法名为this
  • 从构造器必须先调用已经存在的主或从构造器

集合

Scala语言的一个设计目标是可以同时利用面向对象和面向函数的方法编码,因此提供的集合类分成了可以修改的集合类和不可以修改的集合类两大类型。Scala除了Array和List,还提供了Set和Map两种集合类。

  • 通过 scala.collection.JavaConversions.mapAsScalaMap 可将 Java 的 Map 转换为 Scala 类型的 Map;
  • 通过 scala.collection.JavaConversions.mapAsJavaMap 可将 Scala 的映射转换为 Java 类型的映射;
  • toMap 方法将对偶集合转化为映射

OptionNoneSome

一致目标:所有皆为对象 + 函数式编程,在变量和函数返回值可能不会引用任何值的时候使用 Option 类型

  • 在没有值的时候,使用None;
  • 如果有值可以引用,使用Some来包含这个值;

None 和 Some 均为 Option 的子类,但是 None 被声明为一个对象,而不是一个类.

伴生对象

当单例对象(object Name,Singleton Object)与类(class Name)同名时,该单例对象称作类的伴生对象(Companion Object),该类称作单例对象的伴生类。没有伴生类的单例对象称为孤立对象(Standalone Object),最常见的就是包含程序入口main()的单例对象。

  • 同名且在同一个源文件中定义
  • 可以互相访问其私有成员

参考Scala学习笔记-伴生对象于孤立对象

  • 伴生对象的属性、方法指向全局单例对象 MODULE$
  • 伴生类的属性、方法被定义为是对象相关的

单例对象在第一次访问时初始化,不可以new、不能带参数,而类可以new、可以带参数。

  • 伴生对象中定义的字段和方法, 对应同名.class类中的静态方法,对应同名$.class虚构类中的成员字段和方法
  • 伴生类中定义的字段和方法, 对应同名.class类中的成员字段和成员方法

同名.class类中的静态方法,会访问单例的虚构类的实例化对象, 将相关的逻辑调用转移到虚构类中的成员方法中,即:.class类提供程序的入口,$.class提供程序的逻辑调用。

  • 伴生对象中的逻辑,转移到$.class虚构类中去处理
  • 伴生类中的逻辑,转移到.class同名类中的成员方法中去处理

通过伴生对象来访问伴生类的实例,提供了控制实例个数的机会。虚构类的单例性,保证了伴生对象中信息的唯一性。

那么:伴生对象如何体现单例呢?

因为伴生类不是单例的,如何实现单例模式呢?方法2种:

1)将伴生类中的所有逻辑全部移到单例对象中,去除伴生类, 单例对象成为孤立对象, 该孤立对象天然就是单例的

2)若必须存在伴生类,如何保证伴生类是单例的? 将伴生类的主构造器私有, 并在伴生对象中创建一个伴生类的对象, 该对象就是唯一的

1
2
3
4
5
6
7
8
9
class A private {
}
object A {
  val single = new A()
}
// true
var a1 = A.single
var a2 = A.single;
println("a1 eq a2 : " + (a1 eq a2))

参考学习Scala:孤立对象的实现原理学习Scala:伴生对象的实现原理

Trait

特质,类似有函数体的 Interface,利用关键字 with 混入。

IO读写

Scala本身提供 Read API,但是 Write API 需要利用Java的API,文件不存在会自动创建。

1Read

1
2
3
import scala.io.Source
val content = Source.fromURL(url).getLines()    // 网络资源
val lines = Source.fromFile(filePath).getLines()  // 文件

2Write

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.io.File
import java.io.PrintWriter
import java.io.FileWriter
import java.io.BufferedWriter
 
// 文件首次写入,ok; 不支持追加
val writer = new PrintWriter(new File("sqh.txt"))
writer.println("xxx")
 
// 支持追加;但是有换行问题
val writer = new FileWriter(new File("sqh.txt"), true)
writer.write("xxx")
 
// 支持追加,换行解决方法1
val writer = new BufferedWriter(new FileWriter(new File("sqh.txt"), true))
writer.write("xxx")
writer.newLine()
 
// 支持追加,换行解决方法2
val writer = new PrintWriter(new FileWriter(new File("sqh.txt"), true))
writer.println("xxx")
 
writer.flush()
writer.close

注意,关闭流前,先刷新。PrintWriter 不支持追加,FileWriter 支持追加,但存在换行问题。可以将 FileWriter 包装成 BufferedWriter 或 PrintWriter 解决换行问题。

参考 

IntelliJ IDEA

Eclipse是常用的Java开发工具。此次转向另一个开发工具IntelliJ IDEA。Intellij IDEA是jetbrain开发的一个IDE,用于Java、Groovy、Scala等的开发。IntelliJ IDEA是商业软件,有二个版本:

  • Ultimate Edition:商业版本,功能完善,免费试用30天;
  • Community Edition:社区免费版本,功能有所精减,开源且license是Apache 2.0,可用于google android开发;

工具下载最新版本信息

version:2017.1
build:IC-171.3780.107

具体参考:IntelliJ IDEA 官网

功能特点

  • 智能补全功能、快捷键功能;
  • Java开发常用插件均已集成,maven、git、svn、tomcat、jetty、jrebel、Gradle等;

IntelliJ IDEA是一个屡获殊荣的Java和Groovy开发环境,认为是当前Java开发效率最快的IDE工具。

The most intelligent IDE for Java platform.

IDEA安装配置

IDEA的环境配置、插件、工具等均在:FIle | Settings | ...

注意,Scala SDK的版本号需要IntelliJ IDEA的支持;Scala Plugins的版本号必须跟本机IntelliJ IDEA的版本号(build.txt)匹配,下载的.zip格式的Scala插件无需解压、放到Intellij安装的plugins目录下,点击 Install plugin from disk... 直接安装zip文件即可(同时最好将其解压到 IDEA 的 /plugin 目录下)。

  • IDE: IntelliJ IDEA Community Edition 2017.1.exe
  • Java SDK: jdk-8u101-windows-x64.zip
  • Scala SDK: scala-2.12.1.msi/zip
  • Scala Plugins: scala-intellij-bin-2017.1.15.zip
  • Intellij IDEA 已集成 maven 插件

然后,可以进行个性化设置,在 File | Settings | ... 下:

  • 显示工具条:菜单 | View | ToolBar,选中
  • 显示行号:... | Editor | General | Appearance | show line numbers
  • 注释不顶格:Code Style | Java | Code Generation -> Comment Code
  • 字体、代码风格设置:... | Editor | General | Colors & Fonts,参考:http://www.phperz.com/article/15/0923/159059.html
    •  UI界面字体
    •  代码编辑区字体
  • 基于Sublime主题设置:不同的monokai主题的jar包,IDEA Color Themes
  • 禁止IDEA自动重新打开上次的项目:... | Appearance & Behavior | System Settings | Reopen last project on startup
  • 打开多个IDEA窗口:... | Appearance & Behavior | System Settings | Open project in new window,然后File–Reopen即可

运行调试

其他快捷键

  • 注释:单行(ctrl + /)、多行(ctrl + shift + /)
  • ALT+Enter:智能提示
  • Shift+F6:批量更改
  • ALT+F7类似于EClipse的 show call history,查看被谁调用

基本使用

IDEA 的工作空间叫 Project ,一个 Project 下提倡放一个项目。.idea 文件夹和 ProjectName.iml 是 IDEA 建立的辅助文件夹和文件,类似于 Eclipse 工作空间下建立的 .settings 文件夹和 .classpath 、.project 文件。

在 IntelliJ IDEA 里面 “new Project” 就相当于 Eclipse 的 “workspace”,而 “new Module” 才是创建一个工程项目。

1Java 程序

  • File | New | Project | Java ...,新建项目工程;
  • 文件夹 src 右键,New | Java Class (Kind : Class) ...,添加 .java 代码;

2Scala 程序

  • File | New | Project | Scala ...,新建项目工程;
  • 文件夹 src 右键,New | Scala Class (Kind : Object) ...,添加 .scala 代码;

参考:Instruction:Your first lines of code | Scala

Scala 默认为每个源文件加入如下引用

  • import java.lang._
  • import scala._
  • import Predef._

编译器 scalac 将 .scala 源文件编译成 .class 文件,Java虚拟机加载并运行 .class 文件。关于 FileName.scala 编译生成的 FileName.class 和 FileName$.class 文件的理解,参考:scala编译的class字节码实现。其中,以$结尾的类就是所谓的虚构类(Synthetic Class)。

源码中的孤立对象A中的字段和方法,都被编译成以孤立对象A的名字命名的 A.class 中的静态方法, 这些静态方法都会通过单例的虚构类 A$.class 的实例化对象访问相应的实例方法。虚构类是传统意义上的单例模式,并且在类初始化时就创建唯一的对象。

  • 源码中单例对象的所有字段和方法都会在虚构类中有相对应的成员
  • 虚构类中的信息只和单例对象有关,单例对象的伴生类不会影响虚构类中的内容

3Java + Scala 程序

IDEA 支持 JavaScala 代码互操作。

关于利用 Maven 构建工程项目的问题:利用IntelliJ IDEA与Maven开始你的Scala之旅

Maven 项目最重要三个参数的设置页面,这三个参数分别:

GroupIdArtifactIdVersion

利用 maven+plugin 方式构建 scala 应用,.java 可以与 .scala 在一个项目中混合使用。

推荐目录结构

遇到的问题

Error:scalac: Error: object VolatileLongRef does not have a member create

这是因为 scala version mismatch problem: XxxRef.create was added from Scala 2.11,统一开发环境和系统的scala版本即可。

IDEA 中提示:Warning:java: 源值1.5已过时, 将在未来所有发行版中删除

IDEA 中提示:Warning:java: 目标值1.5已过时, 将在未来所有发行版中删除

这是因为 IDEA 默认把项目的源代码版本设置为 jdk1.5,目标代码设置为 jdk1.5,需设置为本地 jdk 版本号。

在项目 pom.xml 文件中添加:

1
2
3
4
<properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
</properties>

或者修改 Maven 的 Settings.xml 文件添加如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
<profile>
    <id>jdk-1.8</id>
    <activation>
        <activeByDefault>true</activeByDefault>
        <jdk>1.8</jdk>
    </activation>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
    </properties>
</profile>

其中,Settings.xml 文件在 Maven 插件的安装路径。

文件修改后,IDEA会在右上角提示 "Enable Auto-Import",点击会自动引入jar,之后IDEA将在源码jdk版本、目标jdk版本使用Maven中配置的jdk版本。

java.io.IOException: Could not locate executable null\bin\winutils.exe in the Hadoop binaries.

这是 Hadoop 环境变量的配置问题,解决方法:

1
2
Hadoop_Home:Hadoop 解压路径
Path:%Path%;%Hadoop_Home%\bin

或者,直接在程序代码中添加

1
System.setProperty("hadoop.home.dir", "Hadoop解压路径");

如果 Hadoop_Home 为空,必然���引起上述问题。具体参考:http://www.cnblogs.com/hyl8218/p/5492450.html

Hadoop "Unable to load native-hadoop library for your platform" warning

这只是一个 Warning,并不会影响 Hadoop 的功能。Compiled Version 的问题,用 idea64.exe 打开重新编译即可。

具体参考:http://stackoverflow.com/questions/19943766

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/E:/SqhTest/lib/spark-streaming-kafka-assembly_2.10-1.3.0.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/E:/SqhTest/lib/spark-assembly-1.3.0-hadoop2.7.0.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
log4j:WARN Continuable parsing error 39 and column 23
log4j:WARN The content of element type "log4j:configuration" must match "(renderer*,throwableRenderer?,appender*,plugin*,(category|logger)*,root?,(categoryFactory|loggerFactory)?)".

这是 SLF4J 多重绑定的问题,出现的原因有两种情况:

  • 直接引用了相同的 日志.jar包
  • 不同的 .jar包 引用了相同的 日志jar包

 不过,该问题不会影响日志记录和程序功能,但是尽量避免之。

具体参考https://www.oschina.net/question/93435_174549http://stackoverflow.com/questions/22896243

说明

目前运行环境为 Spark 1.3 + Scala SDK 2.10.4,如何使用 高版本Spark + Scala SDK 2.11/2.12,有待于进一步研究。

现阶段,先用此配置学习之

参考

收藏 推荐 打印 | 阅读: