- 为什么要学习scala?
- 什么是scala?
- 实战初级scala
唯一的目的就是:能够看懂Spark源码;
学习新的一门编程语言(第二门):是很快的;(类比着学,所有的编程语言都是思路相通的)只不过语法不一样;
- 编程语言:java,php,.net,c++,scala,python
- 因为所有的编程语言只是发送指令,电脑干活;电脑都是由一堆的硬件组成;(电路板);只认二进制数据(0,1);电脑只识别:二进制语言;人类的语言电脑听不懂;(才有了操作系统做为翻译);编程语言相当于各个领域;
- 世界上有各种各样的语言;中国人说汉语,法国人说法语;他们两个人之间是无法沟通的,得需要找一个懂英语的人,翻译(英语是世界通用语言);人类学习母语是很累的,但是再学习第二语言是很快的;不同的语言描述的都是同一个事物,只不过有不同的叫法而已
- 门门通,不如一门精;你学习好一门编程语言(学精);如果将来的系统需要用到其它的编程,你放心,肯定会有这门编程语言的高手也与你对接;(接口)通过网络传输,传输的内容是json;
1.介绍scala
Scala:它不是一门新的编程语言,而是一个结合体;
scala要想运行,必须安装jdk;它的编译和运行在jdk基础之上;(scala就是java的一个框架而已)
它的语法和javascript很像; 一个方法的参数类型居然还可以是方法;(函数编程)(和lambda表达式的思想很
像)
官网:https://www.scala-lang.org/;它都会有一段话来描述一下这个xx是什么东东;
文档:https://docs.scala-lang.org/
2.入门
1)在scala中,类型可是方法;在java中类型(基本数据类型和引用类型;在javascript中类型又多了一个function;scala就是把javascript和java结合起来)。
2)要定义一个变量,或者准备要使用一个类型,必须定义变量是什么类型;(强类型语言);scala是一个弱类型语言(变量不需要指定类型);javascript也是一个弱类型语言。
3)java文件和scala文件都是编译成class文件;在java文件中定义的类,在scala可以使用,反过来也是可以的;
可以直接在浏览器上码scala代码;
打开:https://scalafiddle.io
在左侧窗格中粘贴println(“Hello, world!”)
代码块:直接用大括号括起来,代码块
函数和方法类似,但是两个不相等;
方法是被def修饰,
函数是木有被def修饰
剩下的都一样
3.名词介绍
Java | Scala |
---|---|
Interface | traits |
枚举 | Case class |
4.统一类型
Scala:的类型有哪些,scala写变量的时候不需要写类型(人家自己推断,你可以省略也可以加上)
1.Any是所有类型的超类型,也称为顶级类 型。它定义了一些通用的方法如equals、hashCode和toString。Any有两个直接子类:AnyVal和AnyRef。
2.AnyVal代表值类型。有9个预定义的非空的值类型分别是:Double、Float、Long、Int、Short、Byte、Char、Unit和Boolean。Unit是不带任何意义的值类型,它仅有一个实例可以像这样声明:()。所有的函数必须有返回,所以说有时候Unit也是有用的返回类型。
3.AnyRef代表引用类型。所有非值类型都被定义为引用类型。在Scala中,每个用户自定义的类型都是AnyRef的子类型。如果Scala被应用在Java的运行环境中,AnyRef相当于java.lang.Object。
4.Nothing和Null
4.1.Nothing是所有类型的子类型,也称为底部类型。没有一个值是Nothing类型的。它的用途之一是给出非正常终止的信号,如抛出异常、程序退出或者一个无限循环(可以理解为它是一个不对值进行定义的表达式的类型,或者是一个不能正常返回的方法)。
4.2.Null是所有引用类型的子类型(即AnyRef的任意子类型)。它有一个单例值由关键字null所定义。Null主要是使得Scala满足和其他JVM语言的互操作性,但是几乎不应该在Scala代码中使用。我们将在后面的章节中介绍null的替代方案。
5.类
Scala中的类是用于创建对象的蓝图,其中包含了方法、常量、变量、类型、对象、特质、类,这些统称为成员。类型、对象和特质将在后面的文章中介绍。
6.特质
特质 (Traits) 用于在类 (Class)之间共享程序接口 (Interface)和字段 (Fields)。 它们类似于Java 8的接口。 类和对象 (Objects)可以扩展特质,但是特质不能被实例化,因此特质没有参数。
7.元组
1)Map:是一个集合,集合中的每一个元素都有key和value(共两个);
2)元组也是一个集合,集合中的每一个元素都有22值
3)在 Scala 中,元组是一个可以容纳不同类型元素的类。 元组是不可变的。
4)当我们需要从函数返回多个值时,元组会派上用场。
5)Scala 中的元组包含一系列类:Tuple2,Tuple3等,直到 Tuple22。 因此,当我们创建一个包含 n 个元素(n 位于 2 和 22 之间)的元组时,Scala 基本上就是从上述的一组类中实例化 一个相对应的类,使用组成元素的类型进行参数化。
8. 混编–mixin
/* 这是一个抽象类 */
abstract class A {
val message: String
}
/* 这是一个类,实现了抽象类A */
class B extends A {
val message = "I'm an instance of class B"
}
/* 这是一个特质,居然还可以继承自一个抽象类 */
trait C extends A {
def loudMessage = message.toUpperCase()
}
/*
一个类实现一个接口使用extends;
如果这个类有父类(使用extends);接口要换成with
*/
class D extends B with C
val d = new D
println(d.message) // I'm an instance of class B
println(d.loudMessage) // I'M AN INSTANCE OF CLASS B
9.高阶函数
1)高阶函数是指使用其他函数作为参数、或者返回一个函数作为结果的函数。在Scala中函数是“一等公民”,所以允许定义高阶函数。这里的术语可能有点让人困惑,我们约定,使用函数值作为参数,或者返回值为函数值的“函数”和“方法”,均称之为“高阶函数”。
2)说白了:就是方法的参数或者返回值,居然还可以是一个函数(高阶函数)
3)既然Scala编译器已经知道了参数的类型(一个单独的Int),你可以只给出函数的右半部分,不过需要使用_代替参数名(在上一个例子中是x)
10.柯里化
方法可以定义多个参数列表,当使用较少的参数列表调用多参数列表的方法时,会产生一个新的函数,该函数接收剩余的参数列表作为其参数。这被称为柯里化。
/* scala中是可以方法套用方法
* 方法定时的时候有两个参数列表;
*
* 类似于:方法的重载
* */
def fun3(a:Int)(c:Int,b:Int) :Unit =
{
println("---a--" + a);
println("---b--" + b);
print("---c--" + c);
}
/* 调用方法 */
fun3(2)(3,4);
/* 第一步 fun3(2)(3,4)
* fun4==fun3(30)
* 第二步
* fun4(40,50)
* 使用一个方法进行了替代(3,4)
* _表示省略后面的形参列表;
*
* 柯里化:
* */
def fun4 = fun3(30)_;
/* 调用方法 */
fun4(40,50);
11.隐藏参数
1)隐式(IMPLICIT)参数
如果要指定参数列表中的某些参数为隐式(implicit),应该使用多参数列表。例如:
def execute(arg: Int)(implicit ec: ExecutionContext) = ???
2)方法可以具有 隐式 参数列表,由参数列表开头的 implicit 关键字标记。 如果参数列表中的参数没有像往常一样传递, Scala 将查看它是否可以获得正确类型的隐式值,如果可以,则自动传递。
3)Scala 将查找这些参数的位置分为两类:
Scala 在调用包含有隐式参数块的方法时,将首先查找可以直接访问的隐式定义和隐式参数 (无前缀)。
然后,它在所有伴生对象中查找与隐式候选类型相关的有隐式标记的成员。
4)更加详细的关于 Scala 到哪里查找隐式参数的指南请参考 常见问题
5)在下面的例子中,我们定义了一个方法 sum,它使用 Monoid 类的 add 和 unit 方法计算一个列表中元素的总和。 请注意,隐式值不能是顶级值。
/* 定义了一个抽象类 */
abstract class Monoid[A] {
/* 把两个参数相加,得到一个返回值(通过方法名判断出来) */
def add(x: A, y: A): A
/* 定义一个变量,unit:木有返回值 */
def unit: A
}
/* object:单例的对象 */
object ImplicitTest {
/* 创建了一个string类型的对象 */
implicit val stringMonoid: Monoid[String] = new Monoid[String] {
/* 对这里面的两个方法做了实现;java.lang.String这个类里面有一个方法叫concat */
def add(x: String, y: String): String = x concat y
def unit: String = ""
}
/* 创建了一个int类型的对象 */
implicit val intMonoid: Monoid[Int] = new Monoid[Int] {
/* 对这里面的两个方法做了实现 */
def add(x: Int, y: Int): Int = x + y
def unit: Int = 0
}
/* 参数是一个List,我们在定义方法的时候无法知道这个List里面放的是什么类型
要想知道List里面放的是什么类型,只有在调用方法的时候才会知道
如何知道在调用方法的时候,
如果List里面放的是Int,就默认的调用intMonoid
如果List里面放的是String,就默认的调用stringMonoid
要想实现这一个效果,就得加上implicit;(隐式参数)
*/
def sum[A](xs: List[A])(implicit m: Monoid[A]): A =
if (xs.isEmpty) m.unit
else m.add(xs.head, sum(xs.tail))
def main(args: Array[String]): Unit = {
/* 这个List里面放的是int */
println(sum(List(1, 2, 3))) // uses IntMonoid implicitly
/* 这个List里面放的是String */
println(sum(List("a", "b", "c"))) // uses StringMonoid implicitly
}
}
12.实战scala初级
标准的HelloWorld:
package com.jinghangzz.scala
/**
* scala的HelloWorld
*/
object HelloWorld {
/**
* def:(修饰符)
* main:方法名
* 形参:变量:类型
* Unit:void,此方法木有返回值
*/
def main(args: Array[String]): Unit = {
/* 两种写法
* scala中特有的写法;可以省略掉
* System.out.println
* 就是在写scala代码的时候,每一行结束的时候分号可写可不写(建议写上)
* */
println("==南宋四大名将==")
/* 是java的写法;
* java和scala通用
* */
System.out.println("==岳飞,韩世忠,刘光世,张俊==");
}
}
简化版本的HelloWorld
package com.jinghangzz.scala
/**
* 是HelloWorld的简化版本
* 省掉main方法
*/
object HwApp extends App {
println("==岳飞,韩世忠,刘光世,张俊==");
}
概览
package com.jinghangzz.scala
/**
* 基础知识
* 变量(主要)和常量
* 变量有三个要素:类型,名字值;
* 类型有哪些:
* 名字:(标识符)
* 值:讲类型的时候,已经讲过了每一个类型的取值范围
* 在定义方法的时候,如果是def修饰的方法,=后面不能跟上>,
* 如果是在定义一个函数funtion,后面得跟上>
*/
object Base {
def main(args: Array[String]): Unit = {
println("==宋太祖:(赵匡胤)==");
/*
* java代码:int i = 0 ; (变量三要素)
* */
var i = 10
println("i====" + i );
var j = 20 ;
println("j====" + j );
j = 30 ;
println("j====" + j );
/* 10 + 30 = 40 */
println("i + j = " + (i + j )) ;
/*
* 常量:final i = 0 ;
* val:常量 不能修改值
* */
val z = 0 ;
/* 最全的写法
* var 变量名:类型 = 值
* */
var a:Int = 10 ;
/* 函数:方法;五要素
* 修饰符,方法名,形参,方法体;返回值
*
* 木有名字;匿名函数
* */
//(a:Int,b:Int) => {a + b} ;
/* 函数起了一个名字
* 先定义方法,再拿一个变量接收,这个写法和javascript一样
*
* 在java中,最外层是类,第一层:属性和方法;是否允许有第三层也是方法;(方法绝对不可以套方法)
* 而scala允许方法调用方法
* */
var fun1 = (a:Int,b:Int) => {a + b}
/* 上面的写法和下面的写法一样 */
def fun2(a:Int,b:Int) :Int = {a + b}
/* 调用函数 */
var result = fun1(10,30);
println("==结果==" + result);
/* scala中是可以方法套用方法
* 方法定时的时候有两个参数列表;
*
* 类似于:方法的重载
* */
def fun3(a:Int)(c:Int,b:Int) :Unit =
{
println("---a--" + a);
println("---b--" + b);
print("---c--" + c);
}
/* 调用方法 */
fun3(2)(3,4);
/* 第一步 fun3(2)(3,4)
* fun4==fun3(30)
* 第二步
* fun4(40,50)
* 使用一个方法进行了替代(3,4)
* _表示省略后面的形参列表;
*
* 柯里化:
* */
def fun4 = fun3(30)_;
/* 调用方法 */
fun4(40,50);
/* 所有的代码都可以省略
* 如果方法的形参木有,小括号可以省略
* 如果方法木有返回值,那么方法名后面跟的是:返回值可以省略
* 如果大括号(方法体)只有一行,那大括号可以省略
* */
def fun5 = println("a + b ===== 测试一下");
/* 调用
* 如果方法木有形参,小括号可以省略
* */
fun5;
/* ========创建一个对象======== */
var p1 = new Person(23,"赵匡胤");
println("==toString==" + p1.toString())
/* 调用方法 */
p1.say
var p2:Person = new Person(24,"赵匡义");
p2.say
/* 类比较的是栈的上值 */
println("p1 == p2===" + (p1 == p2) )
/* 创建案例类 */
var po1 = Point(3,4)
var po2 = Point(3,4);
println("==po1==" + po1.toString())
/* 案例类默认已经重写了equals方法,它比较的就是值 */
println("po1 == po2===" + (po1 == po2) )
/* object调用方法
* object使用的时候可以不用new对象
* */
IdFactory.create ;
IdFactory.create ;
}
}
/* 创建一个新的类
* 后面可以直接跟上属性,这叫构造方法
* */
class Person(age:Int,name:String)
{
/**
* 说话的方法
* def say() :Unit = {}
*/
def say = println(this.age + "===say==" + this.name)
}
/*
* 样例类:(Case Class)
* 样例类一般不用于可变的对象,并且可以做值比较(只有四类八种基本数据类型;==)
* 样例类可以不用new来创建对象
* 样例类:toString:默认就是构造方法,同时把值带上了
* Point(点)
*/
case class Point(x:Int,y:Int)
/**
object;对象的意思 ,
默认就是单例;一个jvm只能有一个java对象
*/
object IdFactory
{
var i:Int = 0 ;
/**
* 方法
*/
def create =
{
println("i的值:" + i );
/* 自加1 */
i = i + 1 ;
}
}
特质(接口)
package com.jinghangzz.scala
/**
* 特质:接口
*/
object TraitTest {
def main(args: Array[String]): Unit = {
println("==赵光义(宋太宗)==赵炅")
/* 创建对象 */
var p1 = new Per;
p1.eat
p1.sleep
/* 创建一个男人 */
var m:Man = new Man();
m.eat();
m.sleep;
}
}
/**
* 定义一个接口
*/
trait IAdmin
{
/* 抽象方法 */
def eat():Unit ;
/* 抽象方法 */
def sleep;
}
/**
* 一个类实现一个接口,要重写接口中所有的方法
*/
class Per extends IAdmin {
def eat(): Unit = {
println("=Per==eat=");
}
def sleep: Unit = {
println("=Per==sleep=");
}
}
/**
* 写一个男人,继承自Per
*/
class Man extends Per
{
/* 对父类的方法不满意,要重写
* 必须加上override
* */
override def eat() :Unit =
{
println("==Maneat==");
}
}
13.统一类型+类的代码
package com.jinghangzz.scala
/**
* 基础类型
*/
object BaseValue {
def main(args: Array[String]): Unit = {
println("==aa==");
/* 调用方法 */
//base_01
//base_02
base_03
}
/**
* 统一类型
*/
def base_01
{
/* 定义一个List */
var list = List(1,"a","b",true,() =>{} );
/*
* foreach:这个方法有一个参数;这个参数的类型是函数;
* java中:方法的参数是接口,而且接口只一个抽象方法;(lambda表达式的前题条件)
* foreach:方法的参数是函数;目前做的工作是调用;
* 提示:f: Any => U;函数有一个参数,木有返回值
* */
list.foreach( t => println("==循环=:" + t) );
}
/**
* 类
*/
def base_02
{
/* 定义一个类
* 属性后面直接跟值:表示默认值
*
* 不带val或var的参数是私有的,仅在类中可见。
* */
class Person(var age:Int = 24,var name:String)
{
/* 私有的属性,是为了提供读写器 */
private var _weight:Int = 0 ;
/* get方法 */
def weight = _weight ;
/* set方法
* 注意下对于setter方法的特殊语法:这个方法在getter方法的后面加上_=,后面跟着参数
* */
def weight_= (weight:Int) :Unit =
{
this._weight = weight
}
override def toString() :String =
{
/* 如果有返回值,最后一行代码,可以把return省略 */
//"name:" + this.name + ";age:" + this.age ;
/* s:占位符:$(占位符)后面直接跟上属性的名字 */
s"name:$name;age:$age"
}
}
/* 创建对象 */
var p1 = new Person(20,"张三");
println("toString:" + p1.toString());
/* 创建对象
* 属性=值,单独的赋值,另外的一个,表示使用默认值
* */
var p2 = new Person(name="李四");
println("toString:" + p2.toString());
println("==p2.weight==" + p2.weight)
/* 设置值 */
//p2.weight_=(300);
p2.weight = 300 ;
println("==p2.weight==" + p2.weight)
}
/**
* 元组
*/
def base_03 =
{
/* 创建一个元组 */
var tuple2 = ("张三",20):Tuple2[String,Int];
println("toString:" + tuple2);
/* 取第二个值 */
println("key:" + tuple2._1);
println("value:" + tuple2._2);
/* 就是一个类 */
var tuple3 = new Tuple2("李四",20);
println("key:" + tuple3._1);
println("value:" + tuple3._2);
/* 第三种写法 */
var tuple4 = ("王五",30);
println("key:" + tuple4._1);
println("value:" + tuple4._2);
/* 解构元组 */
var (name,age) = tuple4 ;
println("name" + name);
println("age:" + age);
/* 准备一个List
* List里面放的可以是元组;tuple2
* */
var list = List( ("张三",20),("李四",30), ("王五",40));
/*
* 参数是一个函数;
* f: Any => U
* f: ((String, Int)) => U
* */
list.foreach( t => println(t._1 + "==循环==>" + t._2))
/* 模式匹配 */
list.foreach( t =>
{
/* 模式匹配特别像switch case
* case后面跟的是条件;
* */
t match
{
case ("张三",age) => println("张三,你老了,年龄:" + s"$age");
case temp if(temp._1 == "李四") => {
/* 如果访问的是元组;s后面的占位符要使用${变量名._2} */
println(temp._1 + ",你更老了,年龄:" + s"${temp._2}");
}
case _ => println("===你更老===");
}
})
/* List的另外一种循环
* <-右边是集合,左边是临时的变量
*
* 把方法的名字叫数学符号;+,-,++
* */
for( (name,age) <- list)
{
println(name + "==循环==>" + age)
}
/* ==========高阶函数==========
* 方法的参数是函数;
* 方法的返回值也可以是函数
* */
var list2 = List(1,35,73,10)
/* 第一种写法 */
var doubleInt = (t:Int) =>
{
var t2 = t * 2 ;
println("第一种写法==循环==" + t2 )
};
list2.foreach(doubleInt)
/* 第二种写法 */
list2.foreach( t =>
{
var t2 = t * 2 ;
println("第二种写法==循环==" + t2 )
} );
/* 第三种写法
* =>左半已经可以自动推断参数的类型,那就可直接省略掉
* _不能和其它类型运算
* */
list2.foreach( println(_) )
/* 柯里化 */
var result = list2.foldLeft(0)(_ + _);
println("----result----" + result )
}
}