scala基本入门
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了scala基本入门相关的知识,希望对你有一定的参考价值。
[TOC]
一、scala概述
1.1 简介
? scala是一种多范式的编程语言,其设计的初衷是要集成面向对象编程和函数式编程的各种特性。Scala运行于Java平台(Java虚拟机),并兼容现有的Java程序。它也能运行于CLDC配置的Java ME中。目前还有另一.NET平台的实现,不过该版本更新有些滞后。Scala的编译模型(独立编译,动态类加载)与Java和C#一样,所以Scala代码可以调用Java类库(对于.NET实现则可调用.NET类库)。
1.2 scala安装和配置
? scala运行于JVM之上,所以得先安装jdk,安装过程看之前的文章吧。先到<http://www.scala-lang.org/>下载scala安装包。这里我用的版本是 scala2.11.8。windows上安装基本就是点点点,这里就不演示了。安装好之后就默认就配置好了PATH环境变量。
? 在Linux上安装的话,就下载对用的 .tgz包,然后解压到指定目录。配置PATH环境变量,这过程基本就是这样。最后在命令行下输入 scala,成功进入scala命令行即为成功(和Linux安装jdk一毛一样)
二、scala基础
要注意一点,就是在scala中,任何数据都是对象,可以通过数据本身调用一些方法,而在java中需要将数据赋值给一个引用变量之后,通过引用变量才能调用方法。如
"1".toInt() 这个在scala中是可以运行的,但是类似这样的方式不能在java中运行
2.1 常用数据类型
2.1.1数值类型:
Byte,Short,Int,Long,Float,Double
Byte: 8位有符号数字,从-128 到 127
Short: 16位有符号数据,从-32768 到 32767
Int: 32位有符号数据
Long: 64位有符号数据
例子:scala命令行中定义一个Int变量
val a:Byte = 10
a+10
得到:res9: Int = 20
这里的res9是新生成变量的名字
2.1.2 字符类型
Char和String,前者是单字符,后者是字符串
对于字符串,在scala中可以进行插值操作,如:
scala> val a="king"
a: String = king
scala> s"my name is ${a}"
res1: String = my name is king
注意:前面有个s;相当于执行:"My Name is " + s1
2.1.3 Unit类型
相当于java中的void类型,一般常用于函数方法的返回值类型
2.1.4 Nothing类型
一般是执行过程中,产生的异常exception的类型就是nothing
2.2 变量的声明和使用
var 变量名:类型=value
val 变量名:类型=value
其中类型可以省略,scala会根据value的类型自动推导变量的类型的。
例子:
var a:Int=8
var和val的区别:
val引用指向的内存地址不能改变,但是里面的内容可变。
var引用指向的内存地址可变。而且定义时,必须初始化
如:
scala> val a=2
a: Int = 2
scala> a=3
<console>:12: error: reassignment to val
a=3
^
scala> var b=2
b: Int = 2
scala> b=3
b: Int = 3
可以看到,没办法给a重新赋值
2.3 函数的初步使用
2.3.1 scala内置函数
scala有许多内置函数,可以直接使用,比如 scala.math 包下的各种数学函数
import scala.math._ 导入math下的所有函数
scala> import scala.math._
import scala.math._
scala> max(2,3)
res0: Int = 3
2.3.2 自定义函数
def 函数名称 ([参数名:参数类型]*) : 返回值类型 = {}
例子:
1、求和
scala> def sum(x:Int,y:Int) : Int = x+y
sum: (x: Int, y: Int)Int
scala> sum(10,20)
res4: Int = 30
scala> var a = sum(10,20)
a: Int = 30
2、求阶乘,采用递归方式
scala> def myFactor(x:Int) : Int = {
| //实现
| if(x<=1)
| 1
| else
| x*myFactor(x-1)
| }
myFactor: (x: Int)Int
scala> myFactor(3)
res5: Int = 6
注意:没有return语句的话,函数的最后一句话就是函数的返回值。
上面函数有分支,1 和 x*myFactor(x-1) 都有可能是函数的最后一句话。
2.4 scala条件语句和循环语句
2.4.1 条件判断语句if/else:
if (判断条件) {}
else if (判断条件) {}
else {}
2.4.2循环语句:
for循环
//定义个列表,方便讲解操作for循环
var list = List("Mary","Tom","Mike")
println("-----for循环第一种写法-------")
for( s <- list) println(s)
// <- 代表scala中的提取符,把list中的每一个元素都提取出来,赋给s
println("-----for循环第二种写法-------")
//打印名字长度大于3 加判断。可以加上判断
for{
s <- list
if(s.length > 3)
} println(s)
println("-----for循环第三种写法-------")
//打印名字长度小于等于3 加判断
for(s <- list if s.length <= 3 ) println(s)
println("-----for循环第四种写法-------")
//使用yield关键字 作用:产生一个新的集合
//把list中的每个元素都变成大写,返回一个新的集合。
var newList = for{
s <- list
s1 = s.toUpperCase //把名字变成大写
} yield (s1) //使用yield将处理后的元素变成新的集合
for( s <- newList) println(s)
while循环:
println("-----while循环写法-------")
//定义一个循环变量
var i = 0
while(i < list.length){
println(list(i))
//自增
//i++ 这种自增在scala是不可行的
i += 1
}
println("-----do while循环写法-------")
//定义循环变量
var j = 0
do{
println(list(j))
j+=1
} while (j < list.length)
foreach函数迭代:
println("-----foreach用法-------")
list.foreach(println)//相当于 for( s <- list) println(s)
/**
* foreach说明
*
* foreach相当于循环
* list.foreach(println) 使用了高阶函数(函数式编程)
*
* 还有一个循环 map
* foreach和map的区别:
* foreach没有返回值,map有返回值
* spark中类似。
*/
第二种foreach写法:
list.foreach{
case xxxxx
}
直接内部使用case进行模式匹配
2.4.3 嵌套循环和break语句
/**
* 题目:判断101-200之间有多少个素数
*
* 程序分析:
* 判断素数的方法:
* 用一个数分别去 除 2 到sqor(这个数),如果能被整除,则表明此数不是素数,反之是素数
* 举例:101 2~根号(101)
*
* 程序实现方法:
* 定义两层循环
* 第一层 101-200
* 第二层 2 - 根号(第一层)
* 判断能被整除,则不是素数
*
*/
println("-----循环嵌套-------")
var count : Int = 0 //保存素数的个数
var index_outer : Int = 0 //外层循环变量
var index_inner : Int = 0 //内层循环变量
//until 语句,x until y表示x到y,不包括y
for (index_outer <- 101 until 200 ){
var b = false //标示是否可以被整除
breakable{
index_inner = 2
while (index_inner <= sqrt(index_outer)){
if(index_outer % index_inner == 0){
//可以被整除
b = true
break
}
index_inner += 1
}
}
if (!b){
count +=1
}
}
println("个数为: " + count)
break用法:
将需要break的语句块用 breakable{} 包含起来,然后里面使用break语句进行break
2.4.4 嵌套循环实现冒泡排序
/**
* 冒泡排序
* https://www.cnblogs.com/morethink/p/8419151.html#%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F
*
* 算法分析:
* 1、比较相邻元素,如果第一个比第二个大,就交换位置
* 2、对每一对相邻元素做相同的工作,这一步做完后,最后的元素会是最大的数
* 3、针对所有元素,重复以上步骤。
*
* 程序分析:
* 1、两层循环
* 2、外层循环控制比较的次数
* 3、内层循环控制到达位置,也就是结束比较的位置
*/
println("------------冒泡排序--------------")
var listSort = LinkedList(3,9,1,6,5,7,10,2,4)
var startIndex:Int = 0
var secondIndex:Int = 0
var tmp:Int = 0
for (startIndex <- 0 until(listSort.length - 1)) {
for (secondIndex <- startIndex + 1 until(listSort.length)) {
if (listSort(startIndex) > listSort(secondIndex)) {
tmp = listSort(startIndex)
listSort(startIndex) = listSort(secondIndex)
listSort(secondIndex) = tmp
}
}
}
2.5 scala函数的参数
2.5.1 函数参数的求值策略
call by value:
对函数实参求值,并且只求一次
例子:
def test(x:Int)
call by name:
函数的实参在函数体内部用到的时候,才会被求值
例子:
def test(x:=>Int)
注意变量名和类型之间的符号,多了个 => ,不要漏了
可能这么讲,还不清楚,下面看看例子
scala> def test1(x:Int,y:Int) : Int = x+x
test1: (x: Int, y: Int)Int
scala> test1(3+4,8)
res0: Int = 14
scala> def test2(x: => Int,y: => Int) : Int = x+x
test2: (x: => Int, y: => Int)Int
scala> test2(3+4,8)
res1: Int = 14
执行过程对比:
test1 --> test1(3+4,8) --> test1(7,8) --> 7+7 --> 14
test2 --> test2(3+4,8) --> (3+4)+(3+4) --> 7+7 --> 14
注意这里,在没用到实参之前,实参一直是3+4,而不是7
下面来一个更明显的例子:
//x 是 call by value , y 是 call by name
def bar(x:Int,y: => Int) : Int = 1
定义一个死循环函数
def loop() : Int = loop
调用 bar 函数:
1、bar(1,loop)
2、bar(loop,1)
哪种方式会产生死循环?
第二种方式
解析:
1、y 每次在函数中用到时,才会被求值,bar函数没有用到y,所以不会调用loop。
2、x call by value ,对函数参数求值(无论是否用到),并且只求一次,所以会产生死循环。
2.5.2 函数参数类型
1、默认参数
当你没有给参数赋值的时候,就使用默认值。
scala> def fun1(name:String="Tom") : String = "Hello " + name
fun1: (name: String)String
scala> fun1("Andy")
res0: String = Hello Andy
scala> fun1()
res1: String = Hello Tom
2、代名参数
过代名参数可以确定给哪个参数赋值。
scala> def fun2(str:String="Good Morning ", name:String="Tom ", age:Int=20)=str + name + " and the age is " + age
fun2: (str: String, name: String, age: Int)String
scala> fun2()
res2: String = Good Morning Tom and the age is 20
//这里指定给哪个默认参数赋值
scala> fun2(name="Mary ")
res3: String = Good Morning Mary and the age is 20
3、可变参数
类似于java中的可变参数,即 参数数量不固定,就是在普通参数后面加个 *
例:
求多个数字的和:
def sum(args:Int*) = {
var result = 0
for(s <- args) result += s
result
}
//这里就是可变参数
scala> def sum(args:Int*) = {
| var result = 0
| for(s <- args) result += s
| result}
sum: (args: Int*)Int
scala> sum(1,2,4)
res4: Int = 7
scala> sum(1,2,4,3,2,5,3)
res5: Int = 20
2.6 懒值lazy
定义:如果常量(也就是val定义的)是lazy的,他的初始化会被延迟,推迟到第一次使用该常量的时候,例子:
scala> val x : Int = 10
x: Int = 10
scala> val y:Int = x+1
y: Int = 11
y 不是 lazy,定义后立即触发计算
scala> lazy val z : Int = x+1
z: Int = <lazy>
z 是lazy,初始化会被推迟,定义时不会出发计算。
scala> z
res6: Int = 11
当我们第一次使用z的时候,才会触发计算。
扩展:
Spark的核心是RDD(数据集合),Spark提供很多方法,操作RDD,算子。
算子分为两种:
1、Transformation : 延时加载,不会触发计算
2、Action : 会触发计算
再看一个例子:
(1)读一个存在的文件
scala> lazy val words = scala.io.Source.fromFile("H:\tmp_files\student.txt").mkString
words: String = <lazy>
scala> words
res7: String =
1 Tom 12
2 Mary 13
3 Lily 15
(2)读一个不存在的文件
scala> val words = scala.io.Source.fromFile("H:\tmp_files\studen1231312312t.txt").mkString
java.io.FileNotFoundException: H: mp_filesstuden1231312312t.txt (系统找不到指定的文件。)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at scala.io.Source$.fromFile(Source.scala:91)
at scala.io.Source$.fromFile(Source.scala:76)
at scala.io.Source$.fromFile(Source.scala:54)
... 32 elided
会产生异常
scala> lazy val words = scala.io.Source.fromFile("H:\tmp_files\studen1231312312312t.txt").mkString
words: String = <lazy>
如果是懒值不会产生异常,因为定义的时候并没有执行,所以不会产生异常
2.7 异常exception
和java类似,使用try catch finally捕捉和处理异常
try {}
catch {
case ex: exception_type1 => {
异常处理代码
}
case ex: exception_type2 => {
异常处理代码
}
case _:Exception => {
这里是包含所有exception,如果上面的没有匹配到就到这里来了
}
} finally {
xxxx
}
2.8 数组
数组类型:
Array[T](N)定长数组,需指定数组长度
ArrayBuffer 可变长数组,需要另外导入包import scala.collection.mutable._
Array操作:
scala> val a = new Array[Int](10)
a: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
初始化赋值,默认值0
scala> val b = new Array[String](15)
b: Array[String] = Array(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null)
scala> val c = Array("Tom","Mary","Andy")
c: Array[String] = Array(Tom, Mary, Andy)
scala> val c = Array("Tom","Mary",1)
c: Array[Any] = Array(Tom, Mary, 1)
未声明 Array类型,直接赋值,Array中是Any类型,即任何类型都可以
scala> val c:Array[String]=Array("Tom","Andy",1)
<console>:11: error: type mismatch;
found : Int(1)
required: String
val c:Array[String]=Array("Tom","Andy",1)
当声明 Array[String] 时,数组元素必须全为String
^
scala> val c:Array[String]=Array("Tom","Andy")
c: Array[String] = Array(Tom, Andy)
ArrayBuffer操作:
scala> import scala.collection.mutable._
import scala.collection.mutable._
mutable代表可变
scala> val d = ArrayBuffer[Int]()
d: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer()
往数组中添加元素
scala> d += 1
res8: d.type = ArrayBuffer(1)
访问数组指定下标的元素:d(index)
删除指定元素(不是删除指定下标的元素):
scala> y-=3
res6: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1)
数组常见操作:
1、遍历数组:
scala> var a = Array("Tom","Andy","Mary")
a: Array[String] = Array(Tom, Andy, Mary)
scala> for(s<-a) println(s)
Tom
Andy
Mary
scala> a.foreach(println)
Tom
Andy
Mary
2、求最大和最小值
scala> val myarray = Array(1,2,7,8,10,3,6)
myarray: Array[Int] = Array(1, 2, 7, 8, 10, 3, 6)
scala> myarray.max
res16: Int = 10
scala> myarray.min
res17: Int = 1
3、排序
表示使用某个比较函数进行排序
scala> myarray.sortWith(_>_)
res18: Array[Int] = Array(10, 8, 7, 6, 3, 2, 1)
scala> myarray.sortWith(_<_)
res19: Array[Int] = Array(1, 2, 3, 6, 7, 8, 10)
解释:myarray.sortWith(_>_)
完整:myarray.sortWith((a,b)=>{if(a>b) true else false})
(a,b)=>{if(a>b) true else false} 是匿名函数,没有名字,传入两个参数 a b,返回值是bool
sortWith(_>_) 是高阶函数,即参数是函数
多维数组:
和Java一样,通过数组的数组来实现
定义一个固定长度的二维数组
scala> val matrix = Array.ofDim[Int](3,4)
matrix: Array[Array[Int]] = Array(
Array(0, 0, 0, 0),
Array(0, 0, 0, 0),
Array(0, 0, 0, 0)
)
scala> matrix(1)(2)=10
scala> matrix
res21: Array[Array[Int]] = Array(
Array(0, 0, 0, 0),
Array(0, 0, 10, 0),
Array(0, 0, 0, 0))
定义一个二维数组,其中每个元素都是一个一维数组,其长度不固定
scala> var triangle = new Array[Array[Int]](10)
triangle: Array[Array[Int]] = Array(null, null, null, null, null, null, null, null, null, null)
scala> for(i <- 0 until triangle.length)
| triangle(i)=new Array[Int](i+1)
scala> triangle
res23: Array[Array[Int]] = Array(
Array(0),
Array(0, 0),
Array(0, 0, 0),
Array(0, 0, 0, 0),
Array(0, 0, 0, 0, 0),
Array(0, 0, 0, 0, 0, 0),
Array(0, 0, 0, 0, 0, 0, 0),
Array(0, 0, 0, 0, 0, 0, 0, 0),
Array(0, 0, 0, 0, 0, 0, 0, 0, 0),
Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
2.9 映射Map
Map[K,V]() 泛型类型可以根据传入的值自动确定,如果创建为空的话,KV都是nothing类型
scala.collection.mutable.Map 是可变的Map,添加进去的KV可更改
scala.collection.immutable.Map 是不可变Map
例子:
// 初始化赋值方式1
scala> val scores = Map("Tom" -> 80, "Mary"->77,"Mike"->82)
scores: scala.collection.mutable.Map[String,Int] = Map(Mike -> 82, Tom -> 80, Mary -> 77)
// 初始化赋值方式2
scala> val chineses = Map(("Tom",80),("Mary",60),("Lily",50))
chineses: scala.collection.mutable.Map[String,Int] = Map(Tom -> 80, Lily -> 50, Mary -> 60)
映射的操作:
1、获取映射中的值
scala> chineses("Tom")
res25: Int = 80
scala> chineses("To123123m")
java.util.NoSuchElementException: key not found: To123123m
at scala.collection.MapLike$class.default(MapLike.scala:228)
at scala.collection.AbstractMap.default(Map.scala:59)
at scala.collection.mutable.HashMap.apply(HashMap.scala:65)
... 32 elided
解决:先判断key是否存在
if(chineses.contains("To123123m")){
chineses("To123123m")
} else {
1
}
scala> if(chineses.contains("To123123m")){
| chineses("To123123m")
| } else {
| 1}
res27: Int = 1
//获取指定key的value,如果key不存在就返回-1,类似于返回默认值
scala> chineses.getOrElse("To123123m",-1)
res28: Int = -1
//get方法如果对应的key不存在,value返回为none
scala> chineses.get("dlfsjldkfjlsk")
res29: Option[Int] = None
使用get方法不会报错
scala> chineses.get("Tom")
res30: Option[Int] = Some(80)
Option None Some 三个类型,在后面做同时的讲解。
==========================================================
2、更新映射中的值
scala> chineses
res31: scala.collection.mutable.Map[String,Int] = Map(Tom -> 80, Lily -> 50, Mary -> 60)
scala> chineses("Tom")
res32: Int = 80
scala> chineses("Tom")=100
scala> chineses
res34: scala.collection.mutable.Map[String,Int] = Map(Tom -> 100, Lily -> 50, Mary -> 60)
==========================================================
3、映射的迭代
可以使用 for foreach
scala> chineses
res35: scala.collection.mutable.Map[String,Int] = Map(Tom -> 100, Lily -> 50, Mary -> 60)
scala> for(s <- chineses) println(s)
(Tom,100)
(Lily,50)
(Mary,60)
scala> chineses.foreach(println)
(Tom,100)
(Lily,50)
(Mary,60)
foreach本质是一个 高阶函数。
==========================================================
4、添加值到映射中
scala> a+="tom"->"king" 前面是K,后面是V
2.10元祖Tuple
元组是不可变类型,不能添加元素和修改元素。Scala 的 Tuple : 是不同类型值的集合
Tuple的声明:
scala> val t1 = Tuple(1,0.3,"Hello")
<console>:14: error: not found: value Tuple
val t1 = Tuple(1,0.3,"Hello")
^
scala> val t1 = Tuple3(1,0.3,"Hello")
t1: (Int, Double, String) = (1,0.3,Hello)
Tuple3 代表 Tuple 中有三个元素
可以使用下面的方式来定义任意元素个数的元祖
scala> val t1 = (1,0.3,"Hello")
t1: (Int, Double, String) = (1,0.3,Hello)
scala> val t1 = (1,0.3,"Hello",1,12,5,"all")
t1: (Int, Double, String, Int, Int, Int, String) = (1,0.3,Hello,1,12,5,all)
访问元素:注意这个特殊的符号
scala> t1._1 访问元祖中的第一个元素
res38: Int = 1
scala> t1._3 访问元祖中的第3个元素
res39: String = Hello
遍历tuple:
注意:Tuple中没有提供foreach函数,我们需要使用 productIterator
遍历分成2个步骤:
1、使用productIterator 生成迭代器
2、遍历
scala> t1.productIterator.foreach(println)
1cala>
0.3
Hello
1
12
5
all
2.11 文件操作
//读取文件,source本身是一个按字符存储的可迭代对象,默认是单个字符的
val source = scala.io.Source.fromFile(fileName, 字符集)
//将整个文件内容读取成一个字符串
source.mkString
//按行读取,一次一行
source.getLines()
//按单个字符读取
for(c<-source) {
println(c)
}
//从URL中读取数据内容
val source2 = scala.io.Source.fromFile(URL, UTF-8)
//读取二进制文件,scala没有自己实现相应的类,所以是直接调用java的类
val file=new File(filename)
val in=new FileInputStream(file)
val buffer=new Array[Byte](file,.length().toInt)
in.read(buffer)
//写入文件
val out=new PrintWriter(filename)
out.println(xxxxx) 写入内容
使用properties类解析resources目录下的properties配置文件:
们可以自定定义个 xx.properties 配置文件,然后使用load方法来读取。
配置文件的基本格式为 key=value 的格式,模块会自定解析成kv格式。
例子:
object Dataloader {
def main(args: Array[String]): Unit = {
val properties = new Properties()
//注意这里因为要采用编译后的文件路径,所以这里是获取resource目录下文件在编译后的路径的输入流
val propertiesStream = Dataloader.getClass.getClassLoader.getResourceAsStream("dataloader.properties")
properties.load(propertiesStream)
println(properties.getProperty("spark.local.cores"))
}
}
三、scala面向对象特性
3.1 类的定义
scala中类的定义和java类似,同样使用class关键字定义类,但是class关键字前面不存在public等这些修饰符。例子:
class Student1 {
//定义学生的属性
private var stuId : Int = 0
private var stuName : String = "Tom"
private var age : Int = 20
//定义成员方法(函数) get set
def getStuName() : String = stuName
def setStuName(newName :String) = this.stuName = newName
def getStuAge() : Int = age
def setStuAge(newAge : Int) = this.age = newAge
}
实际上scala会给属性自动生成对应的get和set方法,但是要注意以下原则:
1、默认情况下,如果属性没有用任何修饰符修饰,那么默认是private,但是自动生成的get和set方法是公有的.
2、而如果显式的声明属性为私有(显式使用private关键字),如:private var a = 10,那么生成的get和set方法都是私有的。只能在伴生对象中使用,或者在本类中使用
3、如果使用 private [this] var a = 10,这样定义属性,表示该属性完全不能被外部访问(包括伴生类),且不会生成set和get方法
4、如果值希望scala生成get方法,不生成set方法,可以将其定义为常量,因为常量的值不可改变
================== 本质 ===================
自动生成的set和get方法名是和属性名一致的,如:
var student = new Student1()
student.stuId 这里实际上是直接调用该属性的get方法的,只不过方法同名而已
student.age=10 这里实际上调用属性的set方法的
3.2 内部类
scala的内部类没有java这么复杂,就是简单在外部类中,直接定义一个类而已。例子:
class Student2 {
//定义学生的属性
private var stuName : String = "Tom"
private var stuAge : Int = 20
//定义一个数组保存学生的课程成绩
private var courseList = new ArrayBuffer[Course]()
//定义一个函数,用于添加学生的课程成绩
def addNewCourse(cname:String,grade:Int) = {
//创建课程的成绩信息
var c = new Course(cname,grade)
//添加到学生的对象中
courseList += c
}
//定义课程类 主构造器就是写在类的后面
class Course(var courseName:String,var grade:Int){
//定义属性
//定义函数
}
}
可以通过外部类的方法来获取内部类的对象。
或者通过以下方式创建内部类对象
val test = new InnerClassTest() 只有当外部类对象是用val 定义时才可以
var testIn = new test.myClass("king")
注意,下面的方式定义是会报错的
var test = new InnerClassTest() 这里是var
var testIn = new test.myClass("king")
个人理解是:
scala中,内部类是属于外部类的对象的,不是属于外部类的(这是官方的话)
所以内部类对象也是跟随这外部类对象。要是外部类对象用var定义,表示其是可变的
要是它变了,内部类对象还怎么通过外部类对象的引用获取自己?
3.3 类的构造器
3.3.1 主构造器
主构造器就是在定义类的后面同时定义的,如:
class Student3(var stuName : String , var age:Int)
括号里面的就是主构造器,只能是一些属性。
要注意一点,就是,括号里面定义的属性变量,前面的var或者val千万不能丢,如果丢掉了,则该参数只能被当做一个类内不可变参数使用,不能被当做类的字段,既外部不能访问该变量。就会造成以下后果,看例子:
//首先两个类,有主构造器,有var和没var 的区别
class a(var name:String)
class b(name:String)
object IODemo {
def main(args: Array[String]): Unit = {
//这里是可以正常创建对象的
val king = new a("king")
val wang = new b("wang")
//到这里就有问题了
king.name 这里是可以的
wang.name 这会无法访问name这个属性,因为压根没有,只是作为class中的一个普通变量而已
}
}
还有一个点,就是如果类里面有任何代码没有包含在类中的任何方法中时,实际上在创建类对象时,这些代码是会被执行的,所以其实也算是主构造器的一部分。如:
class a(var name:String){
println("hahha") //这个语句在创建对象时是会执行的
def test()={
println("test")
}
}
3.3.2 辅助构造器
一个类可以有多个辅助构造器,通过关键字 this 来实现,如:
class Student3(var stuName : String , var age:Int) {
//属性
private var gender:Int = 1
//定义一个辅助构造器,辅助构造器可以有多个
//辅助构造器就是一个函数,只不过这个函数的名字叫 this
def this (age : Int){
this("Mike",age) // new Student3("Mike",age)
println("这是辅助构造器")
}
def this (){
this(10)// new Student3("Mike",10)
println("这是辅助构造器2")
}
}
3.4 object对象(伴生对象)
object对象是scala中比较特殊的对象,有几个特性
1、Object中的内容都是静态的。所以里面定义的属性,方法都是静态的,可以直接通过类名调用,无需创建对象
2、scala中,没有static关键字,所有static都定义在object中。比如main函数
3、如果class的名字,跟object的名字一样,就把这个object叫做类的伴生对象。伴生对象。
4、main函数需要写在object中,但是不一定必须写在伴生对象中
5、本质上来说,object其实类似于一个单例对象,本身就是静态,不需要实例化对象来调用里面定义的方法和属性。
所以从这点上,可以算是弥补了scala没有静态属性的一个缺陷
使用object对象的单例对象特性的例子:
1、生成信用卡号
object CreditCard {
//定义一个变量保存信用卡卡号
private [this] var creditCardNumber : Long = 0
//定义函数来产生卡号
def generateCCNumber():Long = {
creditCardNumber += 1
creditCardNumber
}
//测试程序
def main(args: Array[String]): Unit = {
//产生新的卡号
println(CreditCard.generateCCNumber())
println(CreditCard.generateCCNumber())
println(CreditCard.generateCCNumber())
println(CreditCard.generateCCNumber())
println(CreditCard.generateCCNumber())
println(CreditCard.generateCCNumber())
println(CreditCard.generateCCNumber())
}
}
2、扩展使用,App这个类
定义object类,继承App这个类,可以省略main函数,默认object中的所有代码都在main函数里面。如:
object AppTest extends App {
println("test") 这个会直接执行
}
3.5 apply方法
说到这里,前面的估计会有有疑问,为什么有时候创建对象时,需要用new关键字,有时候又不需要了呢?这就要说到apply这个方法了。如:
var t1 = Tuple3(1,0.1,"Hello")
可以看到,创建一个tuple对象,但是并没有用new关键字,其实省略new 关键字时,是调用了该类的伴生对象中的apply方法。apply方法一般会返回创建好的类的对象,要注意以下几点:
1、使用apply方法,让程序更加简洁。
2、apply方法必须写在伴生对象中。因为直接调用apply方法需要是静态的,只能写在object伴生对象中
3、这种方式类似于通过类名.method 的方式来创建对象,
用java的方式来理解的话,即object是一个静态类,可以直接使用类名
调用里面的静态方法
4、而且apply方法内部,其实也是使用了 new 来创建对应类的对象的
例子:
//定义一个原始类
class Student4 (var stuName : String)
//定义上面类的伴生对象
object Student4{
//定义apply方法
def apply(name : String) = {
println("调用apply方法")
//返回原始类的对象
new Student4(name)
}
//测试程序
def main(args: Array[String]): Unit = {
//通过主构造器创建学生对象
var s1 = new Student4("Tom")
println(s1.stuName)
//通过apply方法创建学生对象,可以不写new关键字
var s2 = Student4("Mary")
println(s2.stuName)
}
}
3.6 继承
3.6.1 普通继承
scala中,也是使用extends关键字继承父类的,而且也只允许单继承,如:
//定义父类
class Person(val name: String,val age:Int){
//定义函数
def sayHello() : String = "Hello " + name + " and the age is " + age
}
//定义子类
/**
*class Emplyee(val name:String,val age:Int,val salary:Int) extends Person(name,age)
* 上述写法报错
* 如果想用子类的属性,覆盖父类的属性。直接这样写不行 需要override
*
*override就是希望使用子类中的值 覆盖父类中的值
*
*/
class Emplyee(override val name:String,override val age:Int,val salary:Int) extends Person(name,age){
//重写父类的方法
override def sayHello(): String = "子类中的sayHello"
}
覆盖父类的方法或者属性时,都需要在最前面加上override关键字
3.6.2 匿名子类继承
object Demo1 {
def main(args: Array[String]): Unit = {
//创建Person对象
var p1 = new Person("Tom",20)
println(p1.name+" "+p1.age)
println(p1.sayHello())
//创建Employee对象
var p2 : Person = new Emplyee("Mike",25,1000)
println(p2.sayHello())
//通过匿名子类来实现继承:没有名字的子类,就叫匿名子类
var p3 : Person = new Person("Mary",25){
override def sayHello(): String = "匿名子类中的sayHello"
}
println(p3.sayHello())
}
}
3.6.3 抽象类和抽象字段
抽象类其实就是使用abstract关键字定义的类,如:
abstract class Teacher {
var i:Int
var name:String="king"
def test = {
}
def sayHello
def talk:Int
}
其中,有以下几点注意
1、抽象类中,没有初始化的字段为称为抽象字段,或者叫属性,如果初始化了就不是抽象字段了。同样的,没有实现的方法就称为抽象方法。
2、抽象类只能用于继承,不能实例化对象
3、非抽象子类中,继承了抽象父类,必须初始化抽象字段以及实现抽象方法。
初始化抽象字段时,可以在子类的主构造器或者在类实现中初始化也可以。但是在主构造器中可以不用立马初始化,可以等实例化对象时传入参数进行初始化。而在类实现中则必须立马初始化
4、抽象类中对于抽象属性,只会自动生成对应的get方法,不会生成set方法
5、继承抽象父类也是使用extends关键字。
3.7 trait特质
在scala中,一般只支持单继承,但是有时候需要多继承,所以就出现了trait,它可以让子类多继承。trait的定义基本和抽象类类似。如:
//定义父类
trait Human{
//定义抽象属性
val id : Int
val name : String
}
//定义代表动作的trait
trait Action{
//定义一个抽象函数
def getActionName():String
}
//定义子类 从两个父类继承
class Student5(val id:Int,val name:String) extends Human with Action{
override def getActionName(): String = "Action is running"
}
从上面也可以看到,继承的trait的时候,用到了extends + with 两个关键字,当父类多于一个时,则必须使用with关键字继承,且该类必须是trait。而extends后面的类可以是trait或者其他class类。多重继承如下:
class Father extends class1 with trait1 with trait2 with 。。。。。。{
}
3.8 包和包对象
scala包的使用和java类似,而且scala中,import语句可以在任何地方。一般情况下,java和scala的包下面,只能有类,对象,特质,不能直接定义函数以及变量。而scala的包对象可以解决这个问题。Scala中的包对象可以包含:常量,变量,方法,类,对象,trait(特质)。如:
package object MyPackage {
def test = {
}
var x:Int = 0
class a {}
}
然后可以直接通过包对象名直接调用里面的方法和变量了
四、scala中的集合
scala中集合一般分为可变集合和不可变集合,分别在 scala.collection.mutable(可变)和scala.collection.immutable(不可变)包下。不可变集合中不允许添加元素,不允许更改某个元素的值,允许删除元素。
4.1 列表List
List[T]() 不可变列表
LinkedList[T]() 可变列表
List[T]相关操作:
scala> val nameList = List("Tom","Andy")
nameList: List[String] = List(Tom, Andy)
scala> val intList = List(1,2,3)
intList: List[Int] = List(1, 2, 3)
空列表
scala> val nullList : List[Nothing] = List()
nullList: List[Nothing] = List()
二维列表
scala> val dim:List[List[Int]] = List(List(1,2,3),List(10,20))
dim: List[List[Int]] = List(List(1, 2, 3), List(10, 20))
返回第一个元素
nameList.head
返回除去最后一个元素的列表
nameList.tail
访问指定index 的元素
nameList(index)
LinkedList[T]相关操作:
定义可变列表
scala> val myList = scala.collection.mutable.LinkedList(1,2,3,4,5)
warning: there was one deprecation warning; re-run with -deprecation for details
myList: scala.collection.mutable.LinkedList[Int] = LinkedList(1, 2, 3, 4, 5)
遍历修改列表
var cur = myList 指向nameList的内存地址引用
while(cur != Nil){
cur.elem = cur.elem*2 迭代每个元素,并将每个元素 *2
cur = cur.next
}
col:+ ele
//将元素的添加到集合的尾部(seq)
ele +:col
//将元素添加到集合的头部(seq)
col + (ele,ele)
//将其他集合添加到集合尾部(set/map)
col -(ele,ele)
//将子集合从集合中删除(set/map/ArrayBuffer)
col1 ++ col2
//将其他集合添加到集合尾部(Iterator)
col2 ++: col1
//将其他集合添加到集合的头部(Iterator)
ele::list
//将元素添加到list头部(list)
list2::list1
//将其他list2添加到list1
的头部(list)
list1:::list2
//将其他list2添加到list1的尾部(list)
4.2 序列
序列分为Vector,Range,两个都是不可变的
Vector操作:
Vector是一个带下标的序列,可以通过下标(索引号),来访问Vector元素
scala> var v = Vector(1,2,3,4,5,6)
v: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3, 4, 5, 6)
Range操作:
Range:是一个整数的序列
第一种写法:
scala> Range(0,5)
res48: scala.collection.immutable.Range = Range(0, 1, 2, 3, 4)
解释:从0开始,不包含5
第二种写法:
scala> print(0 until 5)
Range(0, 1, 2, 3, 4)
第三种写法:前后闭区间
scala> print(0 to 5)
Range(0, 1, 2, 3, 4, 5)
两个Range可以相加
scala> (‘0‘ to ‘9‘) ++ (‘A‘ to ‘Z‘)
res51: scala.collection.immutable.IndexedSeq[Char] = Vector(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z)
Range转换成list
scala> 1 to 5 toList
warning: there was one feature warning; re-run with -feature for details
res52: List[Int] = List(1, 2, 3, 4, 5)
4.3 集合set
不重复元素的集合,默认是HashSet,与java类似,且不可变,相关操作
创建一个set
scala> var s1 = Set(1,2,10,8)
s1: scala.collection.immutable.Set[Int] = Set(1, 2, 10, 8)
注意:属于immutable
scala> s1 + 10
res53: scala.collection.immutable.Set[Int] = Set(1, 2, 10, 8)
scala> s1 + 7
res54: scala.collection.immutable.Set[Int] = Set(10, 1, 2, 7, 8)
返回一个新Set
scala> s1
res55: scala.collection.immutable.Set[Int] = Set(1, 2, 10, 8)
s1本身未改变
创建一个可排序Set
scala> var s2 = scala.collection.mutable.SortedSet(1,2,3,10,8)
s2: scala.collection.mutable.SortedSet[Int] = TreeSet(1, 2, 3, 8, 10)
判断元素是否存在:
scala> s1.contains(1)
res56: Boolean = true
判断一个集是否是另一个集的子集
scala> var s2 = Set(1,2,10,8,7,0)
s2: scala.collection.immutable.Set[Int] = Set(0, 10, 1, 2, 7, 8)
scala> s1
res57: scala.collection.immutable.Set[Int] = Set(1, 2, 10, 8)
scala> s1 subsetOf(s2)
res58: Boolean = true
集的运算:union 并集, intersect 交集,diff 差集
scala> var set1 = Set(1,2,3,4,5,6)
set1: scala.collection.immutable.Set[Int] = Set(5, 1, 6, 2, 3, 4)
scala> var set2 = Set(5,6,7,8,9,10)
set2: scala.collection.immutable.Set[Int] = Set(5, 10, 6, 9, 7, 8)
scala> set1 union set2
res59: scala.collection.immutable.Set[Int] = Set(5, 10, 1, 6, 9, 2, 7, 3, 8, 4)
scala> set1 intersect set2
res60: scala.collection.immutable.Set[Int] = Set(5, 6)
scala> set1 diff set2
res61: scala.collection.immutable.Set[Int] = Set(1, 2, 3, 4)
scala> set2 diff set1
res62: scala.collection.immutable.Set[Int] = Set(10, 9, 7, 8)
五、scala中的函数
前面说的到普通函数的定义方式为:
关键字 def 函数名(参数):返回值 = 函数实现
除了普通函数之外,还有其他函数。
5.1 匿名函数
没有名字的函数,定义方式为:
(参数列表)=> {函数实现}
注意,是 => 不是 =
一般来说,匿名函数就是用来临时定义一个函数的,只使用一次,常常和高阶函数结合使用。例子:
调用匿名函数
定义一个数组,把数组中每个元素乘以三
把Array(1, 2, 3)中的每个元素,传到匿名函数中(x:Int) => x*3
scala> Array(1,2,3).map((x:Int) => x*3)
res1: Array[Int] = Array(3, 6, 9)
把 (x:Int) => x*3 作为map的函数参数传入 ---> 高阶函数
(_,1)这个是啥意思 _+_ 都是匿名函数。
定义匿名函数的时候,参数列表可以省略,直接用函数体,如:
(i:int)=>i*2 可以省略为 _*2,其中_ 就表示参数,输出类型自动检测
要注意一点:
匿名函数的输入参数中,不强制需要指定参数的类型,可以自动检测,如:
xxx.map(pair=>pair._2.toString)
map里面的匿名函数是合法的,没有指定输入参数类型,也是OK的。
但是普通函数是一定要指定参数类型的。
5.2 高阶函数
高阶函数就是参数列表中使用另外的函数作为参数。使用方式:
定义一个高阶函数:
def 函数名(传入函数名,假设为f:(入参类型)=>(出参类型),参数2,。。。。) = 处理逻辑,这里面会调用传入的函数
例子:
object IODemo {
def main(args: Array[String]): Unit = {
//调用高阶函数,并传入函数作为参数
highFun(innerFun,2)
}
//高阶函数,有两个参数,一个是函数f,指定入参和出参类型均为Int,另一个参数是x:Int
def highFun(f:(Int)=>(Int),x:Int) = {
f(x)
}
//这个是接着用来传入到高阶函数中的函数
def innerFun(x:Int) = {
x+2
}
}
除了使用已有函数作为高阶函数的参数之外,也可以使用匿名函数,如:
highFun((x:Int)=>x*3, 3),(x:Int)=>x*3就是个匿名函数
5.3 scala中常用高阶函数
5.3.1 map函数
相当于一个循环,对某个集合中的每个元素进行操作(按照接收函数中的逻辑),返回一个处理后的数据的新的集合。例子:
scala> var numbers = List(1,2,3,4,5,6,7,8,9,10)
numbers: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> numbers.map((i:Int)=>i*2)
res8: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
在(i:Int)=>i*2中,i是循环变量,整个函数
map函数不改变numbers值
scala> numbers
res10: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
匿名函数可以简写为;
_ 相当于循环变量 i
_*2 与 (i:Int)=>i*2 功能相同
_+_ 与 (i:Int,j:Int)=>i+j
注意:如果传给map的是一个Map类型的,如果key没有给map中的函数处理,只处理value,那么最后map处理后返回的新的集合就只包含value
5.3.2 foreach函数
和map类似,唯一不同的就是它没有任何返回值
scala> numbers
res11: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> numbers.foreach(_*2)
scala> numbers
res13: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> numbers.foreach(println(_))
1
2
3
4
5
6
7
8
9
10
scala> numbers.map(_*2).foreach(println)
2
4
6
8
10
12
14
16
18
20
5.3.3 filter
过滤,选择满足的数据,返回true则保留,false则丢弃。最终返回的新集合中包含满足条件的元素。例子:
举例:查询能够被2整除的数字
scala> numbers
res15: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> numbers.filter((i:Int)=>i%2==0)
res16: List[Int] = List(2, 4, 6, 8, 10)
说明:(i:Int)=>i%2==0 如果是true 就返回
5.3.4 zip
合并两个集合,例子:
scala> List(1,2,3).zip(List(4,5,6))
res18: List[(Int, Int)] = List((1,4), (2,5), (3,6))
scala> List(1,2,3).zip(List(4,5))
res19: List[(Int, Int)] = List((1,4), (2,5))
可以看到,他是两个集合中的元素两两集合成一个元祖,并且按照最少元素的list为准
scala> List(3).zip(List(4,5))
res20: List[(Int, Int)] = List((3,4))
5.3.5 partition
根据断言(就是某个条件,可以通过一个匿名函数来实现)的结果,进行分区。函数返回的类型必须是boolean类型的。最后也就是返回true的为一个分区,为false的为另一个分区。如:
把能够被2整除的分成一个区,不能整除的分成另一个区
scala> numbers
res21: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> numbers.partition((i:Int)=>i%2==0)
res22: (List[Int], List[Int]) = (List(2, 4, 6, 8, 10),List(1, 3, 5, 7, 9))
5.3.6 find
查找第一个满足条件(断言)的元素
举例:查找第一个能够被3整除的数字
scala> numbers.find(_%3==0)
res23: Option[Int] = Some(3)
5.3.7 flatten
把嵌套的结果展开
scala> List(List(2,4,6,8,10),List(1,3,5,7,9)).flatten
res24: List[Int] = List(2, 4, 6, 8, 10, 1, 3, 5, 7, 9)
合并成为一个集合
5.3.8 faltmap
相当于map+flatten
scala> var myList = List(List(2,4,6,8,10),List(1,3,5,7,9))
myList: List[List[Int]] = List(List(2, 4, 6, 8, 10), List(1, 3, 5, 7, 9))
scala> myList.flatMap(x=>x.map(_*2))
res25: List[Int] = List(4, 8, 12, 16, 20, 2, 6, 10, 14, 18)
执行过程:
1、将List(2,4,6,8,10)和List(1,3,5,7,9)循环调用了x=>x.map(_*2) 这里x代表某个List,里面的_表示list中的元素
List(4, 8, 12, 16, 20) 和 List(2, 6, 10, 14, 18)
2、合并成一个List
List(4, 8, 12, 16, 20, 2, 6, 10, 14, 18)
5.4 闭包
闭包也就是函数的嵌套。在一个函数里面,包含了另外一个函数的定义,可以在内函数中访问外函数的变量。如:
def mulBy(factor:Double)=(x:Double)=>x*factor里面调用了外函数的factor
外 内
5.5 柯里化
柯里化函数,是把具有多个参数的函数,转换成为一个函数链,每个节点上都是单一参数函数
def add(x:Int,y:Int)=x+y
def add(x:Int)(y:Int)=x+y
以上两个函数定义时等价的
说明:
普通函数:
def add(x:Int,y:Int)=x+y
柯里化函数:利用上面函数闭包的方式
def add(x:Int)=(y:Int)=>x+y
简写:
def add(x:Int)(y:Int)=x+y
scala> def add(x:Int)(y:Int)=x+y
add: (x: Int)(y: Int)Int
scala> add(1)(2)
res28: Int = 3
5.6 解答疑问
有时候调用方法时,方法名后面不需要加括号,有时候需要?为什么呢?
主要看定义函数的时候,后面有没有带括号,当然这是在函数没有参数的情况下,有参数肯定就有括号的。
当函数是无参函数,且定义时没有带括号,那么调用时必须不带括号
当函数是无参函数,且定义是带括号了,那么调用时括号可有可无
同样的情况在定义类的时候也是类似的,不重复
六、高阶特性
6.1 scala中特殊的类型
* Any 表示任意类型 相当于java中的Object
* Unit 表示无值,相当于 void
* Nothing Nothing类型在Scala的类层级的最低端;它是任何其他类型的子类型
* Null 是所有应用类型的子类,值为null
*
* 特殊的类型:
* Option : Scala Option(选项)类型用来表示一个值是可选的(有值或者无值)
* Some :如果值存在,Option 就是 Some
* None : 如果值不存在,Option 就是 None
*
* scala> var myMap = Map("Andy"->90)
myMap: scala.collection.immutable.Map[String,Int] = Map(Andy -> 90)
scala> myMap.get("Andy")
res0: Option[Int] = Some(90)
scala> myMap.get("ykjdfhsdajfkajshd")
res1: Option[Int] = None
*
* Nil类型:是一个空的List
*
* 四个N总结:None Nothing Null Nil
* None : 如果map中的值不存在,Option 就是 None
* Nothing : 如果方法抛出异常,返回值类型是Nothing,Nothing是任何其他类型的子类型
* Null : 可以赋值给所有引用类型,不能赋值给值类型。
* Nil : 空的List
6.2模式匹配
类似于java中的switch/case,用法:
xx match {
case xx1=>。。。。 匹配1
case xx2=>。。。。 匹配2
case _ => ...... 这里是默认值的意思,相当于default
}
例子:
object Demo1 {
def main(args: Array[String]): Unit = {
//1、相当于switch case
var chi = ‘-‘
var sign = 0 //标识符 如果 chi 为 ‘-’ 则sign赋值为-1
chi match {
case ‘+‘ => sign = 1
case ‘-‘ => sign = -1
case _ => sign=0 // _ 表示其他的值
}
println(sign)
/**
* 2、Scala 的守卫:匹配某种类型的所有值 case _ if
*
* 举例 匹配所有的数字,如果ch2是一个数字,则digit赋值为ch2
*/
var ch2 = ‘6‘
var digit : Int = -1
ch2 match {
case ‘+‘ => println("这是一个加号")
case ‘-‘ => println("这是一个减号")
case _ if Character.isDigit(ch2) => digit = Character.digit(ch2,10)// 10 表示 10进制
case _ => println("其他")
}
println(digit)
/**
* 3、在模式匹配中使用变量
*/
var mystr = "Hello World"
//取出某个字符,赋给模式匹配的变量
mystr(7) match {
case ‘+‘ => println("这是一个加号")
case ‘-‘ => println("这是一个减号")
case ch => println(ch)//case语句中使用变量 ch代表传递进来的字符
}
/**
* 4、匹配类型:相当于java中的instanceof
*/
var v4 : Any = 100 // 最终 v4 是一个整数
v4 match {
case x : Int => println("这是一个整数")
case s : String => println("这是一个字符串")
case _ => println("其他类型")
}
/**
* 5、匹配数组和列表
*/
var myArray = Array(1,2,3)
myArray match {
case Array(0) => println("数组中只有一个0")
case Array(x,y) => println("数组中包含两个元素")
case Array(x,y,z) => println("数组中包含3个元素")
case Array(x,_*) => println("这是一个数组,包含多个元素")
}
var myList = List(1,2,3,4,5,6)
myList match {
case List(0) => println("列表中只有一个0")
case List(x,y) => println("列表中包含两个元素,和是: " + (x+y))
case List(x,y,z) => println("列表中包含3个元素,和是: "+ (x+y+z))
case List(x,_*) => println("这是一个列表,包含多个元素,和是: " + myList.sum)
}
}
}
6.3 样本类case
样本类比起普通的类,多了一个特点,可以用上面的case语句中,作为匹配类型,普通的类是不可以的。其他用法和普通类一致。定义方式:
case class a(x:int.....) {}
注意一点,样本类中,自动会将所有字段都声明为val,所以我们声明字段时可以省略val关键字
具体其实没什么好说的,用法很简单。常用来作为一些数据的存储类
6.4 泛型
scala中泛型的定义和java类似,这里不重复,直接讲用法。
6.4.1 泛型类
定义类的时候,带有一个泛型,如:
class Father[T]{
xxxxx
}
定义的方式和java中的一样,scala中很多都带有泛型,比如:
Array[T]
List[T]
Map[K,V]
6.4.2 泛型函数
定义函数的时候,定义一个泛型,如:
def mkArray[T:ClassTag]
ClassTag解释:表示在scala在运行时候的状态信息,这里表示调用时候的数据类型
例子:
scala> import scala.reflect.ClassTag
import scala.reflect.ClassTag
定义了一个泛型数组,其中elem:_*表示所有元素
scala> def mkArray[T:ClassTag](elem:T*) = Array[T](elem:_*)
mkArray: [T](elem: T*)(implicit evidence$1: scala.reflect.ClassTag[T])Array[T]
6.5 隐式转换
6.5.1 隐式转换函数
通常意思是scala自动将某些类型转换为指定类型,只要用户定义相关的隐式转换函数,scala会自动根据隐式函数的入参和出参类型来调用。例子:
class Fruit(name:String){
def getFruitName() : String = name
}
class Monkey(f:Fruit){
//输出
def say() = println("Monkey like " + f.getFruitName())
}
object ImplicitDemo {
def main(args: Array[String]): Unit = {
//定义一个水果对象
var f : Fruit = new Fruit("Banana")
f.say()
/**
* 问题:可否 f.say
* 直接写会报错,因为 Fruit里面没有say函数
* 但是 Monkey里面有say函数
*
* 如果可以把 Fruit 转换成 Monkey的话,就可以调用say
*
* 所以 我们定义隐式转换函数
*
*/
}
//定义隐式转换函数
implicit def fruit2Monkey(f:Fruit):Monkey = {
new Monkey(f)
}
/**
* 注意:谨慎使用隐式转换,隐式转换会导致scala可读性进一步变差
*
* 隐式转换函数命名:xxx2xxx
*/
}
6.5.2 隐式参数
隐式参数就是在函数定义时,在每个参数前面加上一个 implicit关键字,该参数就是隐式参数。例子:
定义一个带有隐式参数的函数
scala> def testParam(implicit name:String) = println("The value is " + name)
testParam: (implicit name: String)Unit
接着定义一个隐式变量
scala> implicit val name:String = "AAAAAA"
name: String = AAAAAA
传入参数调用方法,可以运行的
scala> testParam("dfsfdsdf")
The value is dfsfdsdf
没有传递参数时,自动寻找已定义的隐式变量,就是前面的name,当前,调用隐式变量时并不是按照名字调用的
scala> testParam
The value is AAAAAA
有多个隐式变量时,如果同类型的有多个,会报错。如果是不同类型就可以
scala> implicit val name2:String = "AAAAAACCCCCCCCCCCCCC"
name2: String = AAAAAACCCCCCCCCCCCCC
scala> testParam
<console>:18: error: ambiguous implicit values:
both value name of type => String
and value name2 of type => String
match expected type String
testParam
注意:定义隐式参数时,只能有一个隐式参数,而且有时候为了便于看,会将隐式参数和普通参数分开定义,如:
def test1(c:Int)(implicit a:Int): Unit = {}
注意,普通参数一定要在前面的括号,不能放在后面
还有一个特别的例子,将隐式参数和隐式转换函数结合:
定义一个隐式参数,实现如下需求:找到两个值中比较小的那个值
100 23 ----> 23
"Hello" "ABC"--->ABC
def smaller[T](a:T,b:T)(implicit order : T => Ordered[T]) = if(a<b) a else b
scala> def smaller[T](a:T,b:T)(implicit order : T => Ordered[T]) = if(a<b) a else b
smaller: [T](a: T, b: T)(implicit order: T => Ordered[T])T
scala> smaller(100,23)
res1: Int = 23
scala> smaller("Hello","ABC")
res2: String = ABC
首先泛型T,我们不确定是否能比较的,也就是里面有没有 < 这个方法(不要奇怪,< 是一个方法名,不是符号),因为我们需要确定是否有,则我们定义一个隐式转换函数和参数:
implicit order : T => Ordered[T]
首先这是一个匿名的隐式转换函数,将T转为Order[T],并作为参数order的值
6.5.3 隐式类
在类名前加 implicit关键字
作用:通过隐式类来增强某个类的功能,会将某个类的对象转换为隐式类对象,然后就可调用隐式类中定义的方法了。例子:
object ImplicitClassDemo {
def main(args: Array[String]): Unit = {
//执行两个数字求和
println("两个数字的和是: " + 1.add(2)) // 使用 1.add(2) 替换 1+2
/**
* 1.add(2) 报错 因为 1 没有 add方法
*
* 定义一个隐式类来增强 1 的功能
*
* 执行过程:
* 首先 把 1 转换成 Calc(1)
* 再调用Calc(1) add方法
是根据隐式类中接收的参数类型来自动调用的
*/
implicit class Calc(x:Int){
def add(y:Int):Int = x + y
}
}
}
6.6 泛型的上界和下界
有时候想限制定义的泛型只能是某些类的时候,就会用到上下界的概念。
例子:
(*)规定泛型的取值范围
举例:定义一个泛型:T
类的继承关系 A---->B----->C----->D 箭头指向子类
可以规定T的取值范围 D <: T <: B
T 的取值范围只能是 B C D
<: 就是上下界表示方法
(*)定义:
上界 s <: T 规定了S的类型必须是T的子类或本身
下界 u >: T 规定了U的类型必须是T的父类或本身
关于下界的一个小问题:
class Father
class Son extends Father
class Son1 extends Son
class Son2 extends Son1
object Demo2 {
def main(args: Array[String]): Unit = {
var father : Father = new Son2
fun2(father)
//fun2(new Son2) 这个能正常运行,为什么?
主要是因为子类的对象可以赋给父类的引用,类似于自动转型。所以new son2的时候,实际上是可以当做是父类的对象的,所以可以正常运行。由此可以看出,在继承的情况下其实泛型的下界是没什么用的。其实也就是多态的原因
}
def fun[T<:Son](x:T)={
println("123")
}
def fun2[T>:Son](x:T): Unit ={
println("456")
}
}
6.7 视图界定
上界和下界的一种扩展,除了可以接收上界和下界规定的类型之外,还可以接收能够通过隐式转换过去的类型。如:
用法 :
def addTwoString[T<%String](x:T,y:T) = x + " " + y
含义:
1、可以接收String及其子类
2、可以接收能够转换成String的其他类型。这个是重点,也就是隐式转换来的
例子:
scala> def addTwoString[T<%String](x:T,y:T) = x + " **** " + y
addTwoString: [T](x: T, y: T)(implicit evidence$1: T => String)String
scala> addTwoString(1,2)
<console>:14: error: No implicit view available from Int => String.
addTwoString(1,2)
报错解决:定义隐式转换函数,把int转换成String
scala> implicit def int2String(n:Int):String=n.toString
warning: there was one feature warning; re-run with -feature for details
int2String: (n: Int)String
scala> addTwoString(1,2)
res13: String = 1 **** 2
分析执行过程:
1、调用 int2String 方法,把Int转换成String(scala在后台调用,不需要显示调用)
2、调用addTwoString方法,拼接字符串
隐式转换函数,不需要显示调用它,它就能被调用。
6.8 协变和逆变
将泛型变量的类型嫁接到泛型类中
概念:
协变:泛型变量的值可以是本身类型或者其子类型,
逆变:泛型变量的值可以是本身类型或者其父类型
表示:
协变 + class A[+T] 表示将T泛型的类型嫁接到A中
逆变 -
6.9 Ordered和Ordering比较
这两个类主要用于比较时使用
Ordered类似于java的comparable接口
Ordering类似于java的compartor接口。
trait Ordered[A] extends scala.Any with java.lang.Comparable[A] {
def compare(that : A) : scala.Int
def <(that : A) : scala.Boolean = { /* compiled code */ }
def >(that : A) : scala.Boolean = { /* compiled code */ }
def <=(that : A) : scala.Boolean = { /* compiled code */ }
def >=(that : A) : scala.Boolean = { /* compiled code */ }
def compareTo(that : A) : scala.Int = { /* compiled code */ }
}
这其实就是一个trait,定义了< 等方法,不用奇怪,这就是方法名。
子类可以继承该trait,重写里面的方法,该类就可以用于比较了。
trait Ordering[T] extends java.lang.Object with java.util.Comparator[T] with scala.math.PartialOrdering[T] with scala.Serializable {
this : scala.math.Ordering[T] =>
def tryCompare(x : T, y : T) : scala.Some[scala.Int] = { /* compiled code */ }
def compare(x : T, y : T) : scala.Int
override def lteq(x : T, y : T) : scala.Boolean = { /* compiled code */ }
override def gteq(x : T, y : T) : scala.Boolean = { /* compiled code */ }
override def lt(x : T, y : T) : scala.Boolean = { /* compiled code */ }
override def gt(x : T, y : T) : scala.Boolean = { /* compiled code */ }
override def equiv(x : T, y : T) : scala.Boolean = { /* compiled code */ }
def max(x : T, y : T) : T = { /* compiled code */ }
def min(x : T, y : T) : T = { /* compiled code */ }
override def reverse : scala.math.Ordering[T] = { /* compiled code */ }
def on[U](f : scala.Function1[U, T]) : scala.math.Ordering[U] = { /* compiled code */ }
class Ops(lhs : T) extends scala.AnyRef {
def <(rhs : T) : scala.Boolean = { /* compiled code */ }
def <=(rhs : T) : scala.Boolean = { /* compiled code */ }
def >(rhs : T) : scala.Boolean = { /* compiled code */ }
def >=(rhs : T) : scala.Boolean = { /* compiled code */ }
def equiv(rhs : T) : scala.Boolean = { /* compiled code */ }
def max(rhs : T) : T = { /* compiled code */ }
def min(rhs : T) : T = { /* compiled code */ }
}
ordering的方法就比较多,用法类似
以上是关于scala基本入门的主要内容,如果未能解决你的问题,请参考以下文章