一文学会Scala【Scala一站式学习笔记】

news2025/1/18 20:13:28

文章目录

  • 为什么要学习Scala语言
  • 什么是Scala
  • 如何快速掌握Scala语言
  • Scala环境安装配置
    • Scala命令行
  • Scala的基本使用
    • 变量
    • 数据类型
    • 操作符
    • if 表达式
    • 语句终结符
    • 循环
    • 高级for循环
  • Scala的集合体系
    • 集合
    • Set
    • List
    • Map
    • Array
    • ArrayBuffer
    • 数组常见操作
    • Tuple
    • 总结
  • Scala中函数的使用
    • 函数的定义
    • 函数的参数
    • 特殊的函数-过程
    • lazy
  • Scala面向对象编程
    • 类-class
    • constructor
      • 主constructor
    • 对象-object
    • 伴生对象
      • apply
    • main方法
    • 接口-trait
  • Scala函数式编程
    • 什么是函数式编程
    • 函数赋值给变量
    • 匿名函数
    • 高阶函数
    • 常用高阶函数
      • map的使用:
      • flatMap
      • foreach
      • filter
      • reduceLeft
    • 案例:函数式编程
  • Scala高级特性
    • 模式匹配
      • 对变量的值进行模式匹配
      • 变量类型的模式匹配
      • case class与模式匹配
      • Option与模式匹配
    • 隐式转换
    • 案例:狗也能抓老鼠

为什么要学习Scala语言

最直接的一点就是因为我们后面要学的Spark框架需要用到Scala这门语言。但是Spark其实是同时支持Scala语言和Java语言的,为什么非要学Scala呢,使用java它难道不香吗?
这就要说第二点了:看下面的代码,使用Spark统计文件内单词出现的次数这个需求,使用java代码和scala代码的区别是有多么的明显,在代码量上来说,scala是完胜java的,所以在实际工作中开发spark代码,我们都是需要使用scala的,使用java实现函数式编程太别扭了,代码量太大,这个就是我们学习scala的最直接的原因。

image.png

image.png

什么是Scala

Scala是一门多范式的编程语言,它是一种类似Java的编程语言,它设计的初衷是为了实现可伸缩的语言、并集成面向对象编程和函数式编程的各种特性。Scala基于Java虚拟机,也就是基于JVM的一门编程语言。所有Scala代码,都需要编译为字节码,然后交由Java虚拟机来运行。
Scala和Java可以无缝相互操作,Scala可以任意调用Java代码,这个特性是非常好的

如何快速掌握Scala语言

scala语言本身是很反人类的,特别是你熟悉了例如java之类的编程语言之后再来学scala,会感觉到既爱又恨
那我们如何快速掌握Scala语言的使用呢?
首先大家在学习的过程中需要对比分析Java和Scala在使用上的区别,这样可以加深我们的理解。然后没事的时候可以尝试着用Scala代码改写你之前的Java代码。

最后的最后,交给大家一个绝招,一个终极方案,那就是多练!多练!多练。因为针对编程语言,其实就是需要掌握它的一些基本语法,大家都是有其它语言编程基础的,在这学习Scala语言,其实主要就是掌握这门语言和其它语言的一些不同之处,只要把这些东西捋顺了,那上手也是很容易的。

Scala环境安装配置

注意:由于Scala是基于Java虚拟机的,所以使用 Scala 之前必须先安装 Java,Java我们已经安装过了。那在这里我们先到官网下载Scala安装包
Scala现在有三个主要在使用的版本, 2.11,2.12,2.13。目前的话2.12使用的比较多,所以我们就使用这个版本

image.png

image.png

下载好了之后就需要安装了
安装很简单,直接解压就可以了,在这里我们把scala的安装包解压到了D盘下面的scala目录下

D:\scala\scala-2.12.11 

接下来就该配置SCALA_HOME和PATH环境变量了,和Java的操作是一样的

SCALA_HOME=D:\scala\scala-2.12.11
PATH=...%SCALA_HOME%\bin...

进入cmd命令行,验证一下,输入scala命令,可以看到下面界面就说明安装配置成功了。

C:\Users\yehua>scala
Welcome to Scala version 2.10.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.
Type in expressions to have them evaluated.
Type :help for more information.
scala>

Scala命令行

Scala命令行也称为Scala解释器(REPL),它会快速编译Scala代码为字节码,然后交给JVM来执行
这里的REPL表示:Read(取值)-> Evaluation(求值)-> Print(打印)-> Loop(循环)
在Scala命令行内,输入Scala代码,解释器会直接返回结果
如果你没有指定变量来存放计算的值,那么值默认的名称会显示为res开头的变量,而且会显示结果的数据类型

scala> 1+1
res0: Int = 2

在后面可以继续使用res0这个变量,以及它里面存放的值
例如:

scala> 5 * res0
res1: Int = 10

scala的命令行也有自动补全功能,使用起来还是比较方便的
输入res,按键盘上的tab键,下面就会列出目前以res开头的变量名称

Scala的基本使用

变量

Scala中的变量分为两种:可变 var 和 不可变 val

  • 可变var:可以随时修改var声明的变量的值
  • 不可变val:val声明的变量,值不能被修改,否则会报错: error: reassignment to val
scala> var a = 1
a: Int = 1
scala> a = 2
a: Int = 2
scala> val b = 1
b: Int = 1
scala> b = 2
<console>:8: error: reassignment to val
b = 2

注意:在实际工作中,针对一些不需要改变值的变量,通常建议使用val,这样可以不用担心值被错误的修改(等于java中的final类型)。这样可以提高系统的稳定性和健壮性!

无论声明val变量,还是声明var变量,都可以手动指定变量的类型
如果不指定,Scala会自动根据值,进行类型推断 val c = 1 等价于 val c: Int = 1

scala> val c = 1
c: Int = 1
scala> val c: Int = 1
c: Int = 1

数据类型

Scala中的数据类型可以分为两种,基本数据类型和增强版数据类型
基本数据类型有: Byte、Char、Short、Int、Long、Float、Double、Boolean
增强版数据类型有: StringOps、RichInt、RichDouble、RichChar 等。scala使用这些增强版数据类给基本数据类型增加了上百种增强的功能
例如:RichInt提供的有一个to函数, 1.to(10) ,此处Int会先隐式转换为RichInt,然后再调用其to函数

scala> 1.to(10)
res2: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7,8,9,10)

注意,to函数还可以这样写

scala> 1 to 10
res3: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7,8,9,10)

使用基本数据类型,直接就可以调用RichInt中对应的函数

scala> 1.toString()
res4: String = 1

操作符

Scala的算术操作符与Java的算术操作符没有什么区别
比如 +、-、*、/、% 等,以及 &、|、^、>>、<< 等
注意:Scala中没有提供++、–操作符
我们只能使用+和- ,比如count = 1,count++是错误的,必须写做count += 1

scala> var count = 1
count: Int = 1
scala> count++
<console>:9: error: value ++ is not a member of Int
count++
^
scala> count += 1
scala> count
res8: Int = 2

if 表达式

在Scala中,if表达式是有返回值的,就是if或者else中最后一行语句返回的值,这一点和java中的if是不一样的,java中的if表达式是没有返回值的
例如: val age = 20; if (age > 18) 1 else 0

scala> val age = 20
age: Int = 20
scala> if(age > 18) 1 else 0
res9: Int = 1

在这因为if表达式是有返回值的,所以可以将if表达式赋予一个变量

scala> val res = if(age > 18) 1 else 0
res: Int = 1

由于if表达式是有值的,而if和else子句的值的类型可能还不一样,此时if表达式的值是什么类型呢?
注意:Scala会自动进行推断,取两个类型的公共父类型

例如,if(age > 18) 1 else 0,表达式的类型是Int,因为1和0都是Int
例如,if(age > 18) “old” else 0,此时if和else的值分别是String和Int,则表达式的值是Any类型,Any是String和Int的公共父类型

scala> if(age > 18) 1 else 0
res12: Int = 1
scala> if(age > 18) "old" else 0
res13: Any = old

如果if后面没有跟else,则默认else的值是Unit,也可以用()表示,类似于java中的void或者null
例如,val age = 12; if(age > 18) “old”。此时就相当于if(age > 18) “old” else ()。
此时表达式的值是Any

scala> if(age > 18) "old" else ()
res17: Any = ()

如果想在scala REPL中执行多行代码,该如何操作?
使用 :paste 和 ctrl+D 的方式
:paste 表示代码块的开始
ctrl+D 表示代码块的结束

语句终结符

Scala默认不需要语句终结符,它将每一行作为一个语句
如果一行要放多条语句,则前面的语句必须使用语句终结符
语句终结符和Java中的一样,就是我们平时使用的分号

scala> val age = 20; if(age > 18) 1 else 0
age: Int = 20
res0: Int = 1

循环

  • print和println
    在讲循环之前,先来看一下打印命令print和println
    print打印时不会加换行符,而println打印时会加一个换行符,这个特性和Java中的打印语句的特性是一样的
  • for循环
    for循环本身的特性就没什么好说的了,直接上案例,主要注意一下scala中的for和java中的for在语法层面的区别
scala> :paste
// Entering paste mode (ctrl-D to finish)
val n = 10
for(i <- 1 to n)
println(i)
// Exiting paste mode, now interpreting.
1
2
3
4
5
6
7
8
9
10
n: Int = 10

这里面的to可以换成until

scala> :paste
// Entering paste mode (ctrl-D to finish)
val n = 10
for(i <- 1 until 10)
println(i)
// Exiting paste mode, now interpreting.
1
2
3
4
5
6
7
8

对比两次执行的结果发现
1 to 10 可以获取1~10之间的所有数字
1 until 10可以获取1~9之间的所有数字

所以在这需要注意了,to 和 until 其实都是函数,一个是闭区间,一个是开区间,具体用哪个就要看你的需求了

for循环针对字符串还可以用

scala> for(c <- "hello scala") println(c)
h
e
l
l
o
s
c
a
l
a

注意:在这里我在for循环后面没有使用花括号,都省略了,主要是因为for循环的循环体代码就只有一行,如果有多行,就需要使用花括号了,否则,最终执行的结果就不是我们想要的

  • while循环
    while循环,它的用法和java中的while也是很像的,主要看一下语法层面的区别
scala> :paste
// Entering paste mode (ctrl-D to finish)
var n = 10
while(n>0){
println(n)
n -= 1
}
// Exiting paste mode, now interpreting.
10
9
8
7
6
5
4
3
2
1
n: Int = 0

高级for循环

最后来看一下高级for循环的用法
if守卫
if守卫模式,假设我们想要获取1~10之间的所有偶数,使用普通的for循环,需要把每一个数字都循环出来,然后判断是否是偶数。
如果在for循环里面使用if守卫,可以在循环的时候就执行一定的逻辑,判断数值是否是偶数。

scala> for(i <- 1 to 10 if i % 2 == 0) println(i)
2
4
6
8
10

这是if守卫模式的用法
for推导式
for推导式,一个典型例子是构造集合

我们在使用for循环迭代数字的时候,可以使用yield指定一个规则,对迭代出来的数字进行处理,并且创建一个新的集合。

scala> for(i <- 1 to 10) yield i *2
res16: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 4, 6, 8, 10, 12,14,16,18,20)

Scala的集合体系

Scala中的集合体系,集合在工作中属于经常使用的数据结构

首先看一下整个集合体系结构,这个结构与Java的集合体系非常相似

image.png

集合的顶层接口是Iterable,Iterable接口下面还有一些子接口, Set、Seq、Map
这几个子接口下面有具体的实现类

  • set下面有HashSet、LinkedHashSet、SortedSet等等
  • seq下面有List、Buffer、Range等等
  • Map下面有HashMap、SortedMap、LinkedHashMap等等
  • 其中Buffer下面还有两个常用的,ArrayBuffer、ListBuffer

这个集合体系的时候,还会关联讲到 Array和Tuple 这两个数据结构

集合

Scala中的集合是分成可变和不可变两类集合的

  • 其中可变集合就是说,集合的元素可以动态修改
  • 而不可变集合就是说,集合的元素在初始化之后,就无法修改了

可变集合:在 scala.collection.mutable 这个包下面
不可变集合:在 scala.collection.immutable 这个包下面
我们在创建集合的时候,如果不指定具体的包名,默认会使用不可变集合

Set

Set代表一个没有重复元素的集合
这个集合的特性和Java中Set集合的特性基本一样
Set集合分为可变的和不可变的集合,默认情况下使用的是不可变集合
Set可以直接使用,并且不需要使用new关键字,来看一下

scala> val s = Set(1,2,3)
s: scala.collection.immutable.Set[Int] = Set(1, 2, 3)

本来Set是一个接口,但是却可以创建对象,更神奇的是竟然还不需要使用new关键字,这就有点颠覆我们的认知了

注意了,在学习Scala的时候,可以拿Java来进行对比,加深理解,但是不要全部拿Java里面的知识点来硬套,因为它们两个有些地方还是不一样的。

image.png

只要前面带有object的,可以直接创建对象,并且不需要使用new关键字所以set可以直接使用。

注意:默认情况下直接创建的set集合是一个不可变集合,在这可以看到是在immutable包里面
的,不可变集合中的元素一经初始化,就不能改变了,所以初始化后再向里面添加元素就报错了。


scala> val s = Set(1,2,3)
s: scala.collection.immutable.Set[Int] = Set(1, 2, 3)
scala> s += 4
<console>:9: error: value += is not a member of scala.collection.immutable.Set
s += 4

但是注意,我使用s + 4 这种操作是可以的, 因为 s + 4 返回的是一个新的集合了,相当于在之前的集合的基础上,创建一个新的集合,新的集合包含之前集合的元素和我们新增的4这个元素这个大家需要能够区分开。如果想要创建一个可变的set集合,可以使用mutable包下面的set集合,显式指定包名

scala> val s = scala.collection.mutable.Set(1,2,3)
s: scala.collection.mutable.Set[Int] = Set(1, 2, 3)
scala> s += 4
res34: s.type = Set(1, 2, 3, 4)

Set常用子类有: HashSet、LinkedHashSet、SortedSet

  • HashSet:这个集合的特点是:集合中的元素不重复、无序
  • LinkedHashSet:这个集合的特点是:集合中的元素不重复、有序,它会用一个链表维护插入顺序,可以保证集合中元素是有序的
  • SortedSet:这个集合的特点是:集合中的元素不重复、有序,它会自动根据元素来进行排序

先看HashSet

image.png

HashSet集合分为可变和不可变之分, immutable 包下面的是不可变的,后期无法新增元素。
在这里可以使用new关键字,也可以不使用,因为HashSet既是class,又是object,但是包名需要指定,否则无法识别

scala> val s = new scala.collection.mutable.HashSet[Int]()
s: scala.collection.mutable.HashSet[Int] = Set()
scala> s +=1
res35: s.type = Set(1)
scala> s +=2
res36: s.type = Set(1, 2)
scala> s +=5
res38: s.type = Set(1, 5, 2)

List

List属于Seq接口的子接口,List代表一个不可变的列表

image.png

创建一个list

scala> val l = List(1, 2, 3, 4)
l: List[Int] = List(1, 2, 3, 4)

注意:为什么有的地方需要写类的全路径,而有的不需要呢?
由于immutable包是默认导入的,所以不需要导包,但是也会有个别虽然在immutable包下面的,但是不写全路径还是报错,原谅它把,反正你都带全路径肯定是没有问题的,后期我们会使用idea来开发,也不需要考虑包名的问题,不过在这为了演示起来更加清晰,就使用scala的命令行了。
针对List有 head 、 tail 以及 :: 这几个操作
先演示一下 head、tail 操作

scala> l.head
res49: Int = 1
scala> l.tail
res51: List[Int] = List(2, 3, 4)
  • head:表示获取List中的第一个元素
  • tail:表示获取List中第一个元素之后的所有元素

那其实head和tail就可以获取list中的所有元素了
通过 :: 操作符,可以将head和tail的结果合并成一个List

scala> l.head :: l.tail
res52: List[Int] = List(1, 2, 3, 4)

针对List中的元素进行迭代和前面讲的Set集合的迭代是一样的

scala> val l = List(1, 2, 3, 4)
l: List[Int] = List(1, 2, 3, 4)
scala> for(i <- l) println(i)
1
2
3
4

在这里List是不可变的列表,在实际工作中使用的时候会很不方便,因为我们很多场景下都是需要向列表中动态添加元素,这个时候该怎么办呢?
Scala还提供的有一个ListBuffer
ListBuffer:可以支持动态增加或者移除元素

scala> val lb = scala.collection.mutable.ListBuffer[Int]()
lb: scala.collection.mutable.ListBuffer[Int] = ListBuffer()
scala> lb +=1
res56: lb.type = ListBuffer(1)
scala> lb +=2
res57: lb.type = ListBuffer(1, 2)
scala> lb +=5
res58: lb.type = ListBuffer(1, 2, 5)
scala> lb -=5
res59: lb.type = ListBuffer(1, 2)

ListBuffer也可以直接使用for循环迭代

scala> for(i <- lb) println(i)
1
2

Map

Map是一种可迭代的键值对(key/value)结构

创建一个不可变的Map

scala> val ages = Map("jack"->30,"tom"->25,"jessic"->23)
ages: scala.collection.immutable.Map[String,Int] = Map(jack -> 30, tom -> 25,
scala> ages("jack")
res100: Int = 30

创建一个可变的Map

scala> val ages = scala.collection.mutable.Map("jack"->30,"tom"->25,"jessic"-
ages: scala.collection.mutable.Map[String,Int] = Map(jessic -> 23, jack -> 30
scala> ages("jack")
res101: Int = 30

还有一种创建Map的简易方式,这种方式创建的是不可变Map

scala> val ages = Map(("jack",30),("tom",25),("jessic"->23))
ages: scala.collection.immutable.Map[String,Int] = Map(jack -> 30, tom -> 25,

更新map中的元素

scala> ages("jack") = 31
scala> ages
res104: scala.collection.mutable.Map[String,Int] = Map(jessic -> 23, jack ->

增加多个元素

scala> ages += ("hehe" -> 35, "haha" -> 40)
res105: ages.type = Map(hehe -> 35, jessic -> 23, jack -> 31, tom -> 25

移除元素

scala> ages -= "hehe"
res106: ages.type = Map(jessic -> 23, jack -> 31, tom -> 25, haha -> 40)

遍历
遍历map的entrySet

scala> for ((key, value) <- ages) println(key + " " + value)
jessic 23
jack 31
tom 25
haha 40

最后看一下Map的几个子类
HashMap、SortedMap和LinkedHashMap

  • HashMap:是一个按照key的hash值进行排列存储的map
  • SortedMap:可以自动对Map中的key进行排序【有序的map】
  • LinkedHashMap:可以记住插入的key-value的顺序

HashMap分为可变和不可变的,没有什么特殊之处
在这主要演示一下SortedMap和LinkedHashMap

Array

Scala中Array的含义与Java中的数组类似,长度不可变
由于Scala和Java都是运行在JVM中,双方可以互相调用,因此Scala数组的底层实际上就是Java数组。数组初始化后,长度就固定下来了,而且元素全部根据其类型进行初始化

scala> val a = new Array[Int](10)
a: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
scala> a(0)
res65: Int = 0
scala> a(0)=1
scala> a(0)
res67: Int = 1

也可以直接使用Array()创建数组,元素类型自动推断

scala> val a = Array("hello", "world")
scala> a(0)
res68: String = hello
scala> val a1 = Array("hello", 30)
a1: Array[Any] = Array(hello, 30)

如果想使用一个长度可变的数组,就需要使用到ArrayBuffer了

ArrayBuffer

Scala中ArrayBuffer与Java中的ArrayList类似,长度可变
ArrayBuffer:添加元素、移除元素
如果不想每次都使用全限定名,则可以预先导入ArrayBuffer类

scala> import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.ArrayBuffer
  • 初始化

使用ArrayBuffer()的方式可以创建一个空的ArrayBuffer
注意:也支持直接创建并且初始化ArrayBuffer(1,2,3,4)

scala> val b = new ArrayBuffer[Int]()
b: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer()
  • 添加元素
    使用+=操作符,可以添加一个元素,或者多个元素
    b += 1 或者 b += (2, 3, 4, 5)
scala> b += 1
res69: b.type = ArrayBuffer(1)
scala> b += (2, 3, 4, 5)
res70: b.type = ArrayBuffer(1, 2, 3, 4, 5)

使用insert()函数可以在指定位置插入元素,但是这种操作效率很低,因为需要移动指定位置后的所有元素向3号角标的位置添加一个元素 30

scala> b.insert(3,30)
scala> b
res72: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 30, 4
  • 移除元素

使用 remove() 函数可以移除指定位置的元素

注意:Array与ArrayBuffer可以互相进行转换

  • b.toArray:ArrayBuffer转Array
  • a.toBuffer:Array转ArrayBuffer

数组常见操作

  • 遍历Array和ArrayBuffer的两种方式
    由于Array和ArrayBuffer都是有角标的,所以在迭代数组中元素的时候除了可以使用前面迭代集合的
    方式还可以使用角标迭代
scala> val b=ArrayBuffer(1, 2, 3, 4, 5)
b: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 4, 5)
scala> for(i <- b) println(i)
1
2
3
4
5
scala> for(i <- 0 until b.length ) println(b(i))
1
2
3
4
5
  • 求和、求最大值
scala> val a = Array(3, 2, 1, 4, 5)
a: Array[Int] = Array(3, 2, 1, 4, 5)
scala> val sum = a.sum
sum: Int = 15
scala> val max = a.max
max: Int = 5
  • 数组排序
scala> scala.util.Sorting.quickSort(a) 
scala> a
res99: Array[Int] = Array(1, 2, 3, 4, 5)

Tuple

Tuple:称之为元组,它与Array类似,都是不可变的,但与数组不同的是元组可以包含不同类型的元素Tuple中的元素角标从 1 开始

注意:目前 Scala 支持的元组最大长度为 22 ,对于更大长度可以使用集合或数组

scala> val t = (1, 3.14, "hehe")
t: (Int, Double, String) = (1,3.14,hehe)
scala> t._1
res117: Int = 1
scala> t._3
res118: String = hehe

总结

前面讲了很多集合体系中的数据结构,有的是可变的,有的是不可变的,有的是既是可变的又是不可变的,听起来有点乱,在这里我们总结一下
可变集合: LinkedHashSet、ListBuffer、ArrayBuffer、LinkedHashMap
不可变集合: List、SortedMap
可变+不可变集合: Set、HashSet、SortedSet、Map、HashMap
还有两个编外人员:
Array、Tuple
Array:长度不可变,里面的元素可变
Tuple:长度不可变,里面的元素也不可变

Scala中函数的使用

函数的定义

在Scala中定义函数需要使用 def 关键字,函数包括函数名、参数、函数体。Scala要求必须给出函数所有参数的类型,但是函数返回值的类型不是必须的,因为Scala可以自己根据函数体中的表达式推断出返回值类型。函数中最后一行代码的返回值就是整个函数的返回值,不需要使用return,这一点与Java不同,java中函数的返回值是必须要使用return的
下面来实现一个单行函数和多行函数

  • 单行函数
scala> def sayHello(name: String) = print("Hello, " + name)
sayHello: (name: String)Unit
scala> sayHello("Scala")
Hello, Scala
  • 多行函数
scala> :paste
// Entering paste mode (ctrl-D to finish)
def sayHello(name: String, age: Int) = {
	println("My name is "+name+",age is "+age)
	age
}
// Exiting paste mode, now interpreting.
sayHello: (name: String, age: Int)Int
scala> sayHello("Scala",18)
My name is Scala,age is 18
res120: Int = 18

函数的参数

  • 默认参数
    在Scala中,有时候我们调用某些函数时,不希望给出参数的具体值,而是希望使用参数自身默认的值,此时就需要在定义函数时使用默认参数。如果给出的参数不够,则会从左往右依次应用参数。
scala> def sayHello(fName: String, mName: String = "mid", lName: String = "last")
sayHello: (fName: String, mName: String, lName: String)String
scala> sayHello("zhang","san")
res122: String = zhang san last
  • 带名参数
    在调用函数时,也可以不按照函数定义的参数顺序来传递参数,而是使用带名参数的方式来传递。
scala> def sayHello(fName: String, mName: String = "mid", lName: String = "last")
sayHello: (fName: String, mName: String, lName: String)String
scala> sayHello(fName = "Mick", lName = "Tom", mName = "Jack")
res127: String = Mick Jack Tom
  • 可变参数
    在Scala中,有时我们需要将函数定义为参数个数可变的形式,则此时可以使用变长参数来定义函数。
scala> :paste
// Entering paste mode (ctrl-D to finish)
def sum(nums: Int*) = {
	var res = 0
	for (num <- nums) res += num
	res
}
// Exiting paste mode, now interpreting.
sum: (nums: Int*)Int
scala> sum(1,2,3,4,5)
res129: Int = 15

特殊的函数-过程

在Scala中,定义函数时,如果函数体直接在花括号里面而没有使用=连接,则函数的返回值类型就是Unit,这样的函数称之为过程
过程通常用于不需要返回值的函数
过程还有一种写法,就是将函数的返回值类型显式定义为Unit
比较一下这四种写法的区别

def sayHello(name: String) = "Hello, " + name
def sayHello(name: String): String = "Hello, " + name
def sayHello(name: String) { "Hello, " + name }
def sayHello(name: String): Unit = "Hello, " + name

前面两种写法的效果是一样的,都是函数
后面两种写法的效果是一样的,都是过程

lazy

Scala提供了lazy特性,如果将一个变量声明为lazy,则只有在第一次使用该变量时,变量对应的表达式才会发生计算
什么场景下需要使用lazy特性呢?
这种特性对于特别耗时的操作特别有用,比如打开文件这个操作。

scala> import scala.io.Source._
import scala.io.Source._
scala> lazy val lines = fromFile("D://test.txt").mkString
lines: String = <lazy>

即使D://test.txt文件不存在,代码也不会报错,只有变量使用时才会报错,这就是lazy这个特性。

Scala面向对象编程

在这里我们主要学习Scala中的类、对象和接口
注意:
Scala中类和java中的类基本是类似的
Scala中的对象时需要定义的,而java中的对象是通过class new出来的
Scala中的接口是trait,java中的接口是interface

类-class

首先看一下类
Scala中定义类和Java一样,都是使用 class 关键字
和Java一样,使用new关键字创建对象
那下面来看一个具体案例
定义Person类,

class Person{
var name = "scala"
def sayHello(){
	println("Hello,"+name)
}
def getName= name
}

注意:如果在定义方法的时候指定了(),那么在调用的时候()可写可不写,如果在定义方法的时候没指定(),则调用方法时肯定不能带()

scala> val p = new Person()
p: Person = Person@23b8d9f3
scala> p.sayHello()
Hello,scala
scala> p.sayHello
Hello,scala
scala> println(p.getName)
scala
scala> println(p.getName())

constructor

类创建好了,下面我们来看一下类中的构造函数,
Scala类中的构造函数可以分为主构造函数和辅助构造函数
这两种构造函数有什么区别呢?
主constructor:类似Java的默认构造函数 this()
辅助constructor:类似Java的重载构造函数 this(name,age)

主constructor

那先来看一下主构造函数
Scala的主constructor是与类名放在一起的,与Java不同,Java中的构造函数是写在类内部的
注意:在类中,没有定义在任何方法或者是代码块之中的代码就是主constructor的代码

先定义一个Student类

class Student(val name:String,val age:Int){
println("your name is " + name + ", your age is " + age)
}

创建对象

scala> new Student("zs",19)
your name is zs, your age is 19
res8: Student = Student@3134153d

在创建对象的时候,类中的println语句执行了,说明这个语句属于主构造函数中的代码,主constructor中还可以通过使用默认参数,来给参数设置默认值

class Student(val name:String = "jack",val age:Int = 20){
println("your name is " + name + ", your age is " + age)
}

创建对象

scala> new Student()
your name is jack, your age is 20
res10: Student = Student@7ddd84b5
scala> new Student("tom",18)
your name is tom, your age is 18
res11: Student = Student@a09303

Scala中,可以给类定义多个辅助constructor,类似于java中的构造函数重载。辅助constructor之间可以互相调用,但是第一行必须调用主constructor
来看一个案例:
给学生指定姓名和年龄信息

class Student {
var name = "jack"
var age = 10
def this(name: String) {
this()
this.name = name
}
def this(name: String, age: Int) {
this(name)
this.age = age
}
}

执行

scala> val s = new Student("tom")
s: Student = Student@1a538ed8
scala> val s = new Student("mick",30)
s: Student = Student@319642db

对象-object

我们学习的scala中的class和java中的class是类似的,下面来看一个java中没有的内容,叫Object
那大家可能有疑问了,Java中也有object,通过class就可以创建object
但是注意了,在scala中,我们可以直接定义一个object,就像定义class一样。
object:相当于class的单个实例,通常在里面放一些静态的field或者method
object不能定义带参数的constructor,只有空参的constructor
第一次调用object的方法时,会执行object的constructor,也就是执行object内部不在任何方法中的代码,因为它只有空参的构造函数。但是注意,object的constructor的代码只会在他第一次被调用时执行一次,以后再次调用就不会再执行了object通常用于作为单例模式的实现,或者放class的一些静态成员,比如工具方法。object可以直接使用,不能new创建一个object,使用object关键字

object Person {
	var age = 1
	println("this Person object!")
	def getAge = age
}

执行,直接通过Object的名称调用属性或者方法即可,类似于Java中的静态类

伴生对象

前面学习了class和object,那下面再来看一个特殊的概念,伴生对象
如果有一个class,还有一个与class同名的object,那么就称这个object是class的 伴生对象 ,class是object的 伴生类

注意:伴生类和伴生对象必须存放在一个.scala文件之中
伴生类和伴生对象最大特点在于可以互相访问private field

创建一个Person object和Person class

object Person {
private val fdNum= 1
	def getFdNum = fdNum
}

class Person(val name: String, val age: Int) {
	def sayHello = println("Hi, " + name + ",you are " + age + " years old!" +
}

执行

scala> new Person("tom",20).sayHello
Hi, tom,you are 20 years old!, and you have 1 friend.
scala> Person.fdNum
<console>:9: error: value fdNum is not a member of object Person
Person.fdNum
^
scala> Person.getFdNum
res26: Int = 1

apply

apply是object中非常重要的一个特殊方法,通常在伴生对象中实现apply方法,并在其中实现构造伴生类对象的功能
在创建对象的时候,就不需要使用new Class的方式,而是使用Class()的方式,隐式调用伴生对象的
apply方法,这样会让对象创建更加简洁

例如:Array的伴生对象的apply方法就实现了接收可变数量的参数,以及会创建一个Array对象
val a = Array(1, 2, 3, 4, 5)

image.png

从Array object的源码中可以看出来,它里面就是在apply方法内部使用new Array创建的对象。下面我们来自己定义一个伴生类和伴生对象

class Person(val name: String){
	println("my name is,"+name)
}
object Person {
	def apply(name: String) = {
		println("apply exec...")
		new Person(name)
	}
}

执行

scala> new Person("tom")
my name is,tom
res29: Person = Person@63917fe1
scala> Person("tom")
apply exec...
my name is,tom
res30: Person = Person@35e74e08

注意:在这里new Person(“zhang”) 等于 Person(“zhang”),都是获取Person的对象
只不过Person(“zhang”)是用的object中apply方法, 而new Person(“zhang”)是直接基于class创建的。这两种方式肯定Person(“zhang”)这种方式使用起来更加方便简洁

main方法

接下来看一下scala中的main方法和Java一样,在Scala中如果要运行一个应用程序,必须有一个main方法,作为入口
Scala中的main方法必须定义在object中,格式为 def main(args: Array[String])
这就需要在编辑器中操作了,我们可以使用eclipse或者idea,但是eclipse对scala的支持不太好,所以建议使用idea。

首先确认一下idea中是否集成了scala语言插件
打开idea,点击 configure–>plugins

image.png

确认scala的插件是否已经安装

image.png

接着创建maven项目

image.png

点击开启自动引入依赖

到这还没完,因为此时我们是无法创建scala代码的,这个项目中也没有集成scala的sdk,只有java的

image.png

接下来就需要给这个项目添加scala的sdk了

image.png

image.png

image.png

image.png

添加成功以后是这样的

image.png

此时查看项目的依赖,发现scala的sdk已经添加进来了。

image.png

最终点击ok即可

以后再创建想要创建scala的maven项目,只需要进入到这个界面确认项目中是否有scala的依赖,没有的话直接点击右边的加号按钮添加即可

image.png

image.png

idea的scala开发环境配置好了,但是我一般还是喜欢再增加一些配置
目前项目的src目录下有一个java目录,这个目录表示是放java代码的,当然了你在里面写scala代码肯定是没有问题的。只是针对我这种稍微有点强迫症的用起来就有点别扭了。
在实际工作中可能我们一个项目既需要使用到java代码,也需要使用到scala代码,所以最好还是建议把java代码和scala代码分开存放,这样比较清晰

这样创建的scala目录是有问题的,你有没有发现这个目录的颜色和java目录的颜色都不一样因为你在这直接创建的scala目录是一个普通的目录,而java那个目录是一个source根目录。
所以我们也需要把scala目录变为source根目录。这样操作

image.png

这样操作之后,就发现scala的目录颜色就正常了
image.png

下面就可以正式开始写代码了,scala相关的代码就放到scala目录中
现在scala目录下创建包: com.imooc.demo
然后创建一个scala的object
先选中Scala Class

image.png

image.png

Scala 开发HelloWorld程序

package com.imooc.demo
/**
* Created by xuwei
*/
object mainDemo {
def main(args: Array[String]): Unit = {
println("hello scala!")

}
}

执行代码,右键run即可

这就是Scala中main方法的用法,注意:main方法只能定义在object中,不能定义在class中

接口-trait

scala中的接口,这个接口也是比较特殊的
Scala中的接口称为trait,trait类似于Java中的interface
在triat中可以定义抽象方法

类可以使用 extends 关键字继承trait,无论继承类还是trait统一都是使用 extends 这个关键字。
类继承trait后,必须实现trait中的抽象方法,实现时不需要使用 override 关键字。
scala不支持对类进行多继承,但是支持对trait进行多重继承,使用 with 关键字即可
下面我们就来看一个接口多继承的案例

object PersonDemo {
def main(args: Array[String]): Unit = {
val p1 = new Person("tom")
val p2 = new Person("jack")
p1.sayHello(p2.name)
p1.makeFriends(p2)
}
}
trait HelloTrait { def sayHello(name: String)}
trait MakeFriendsTrait { def makeFriends(p: Person)}
class Person(val name: String) extends HelloTrait with MakeFriendsTrait {
def sayHello(name: String) = println("Hello, " + name)
def makeFriends(p: Person) = println("Hello, my name is " + name + ", your
}

Scala函数式编程

什么是函数式编程

Scala是一门既面向对象,又面向过程的语言。因此在Scala中有非常好的面向对象的特性,可以使用Scala来基于面向对象的思想开发大型复杂的系统和工程;而且Scala也面向过程,因此Scala中有函数的概念。在Scala中,函数与类、对象一样,都是一等公民,所以说scala的面向过程其实就重在针对函数的编程了,所以称之为函数式编程

函数赋值给变量

Scala中的函数是一等公民,可以独立定义,独立存在,而且可以直接将函数作为值赋值给变量。
Scala的语法规定,将函数赋值给变量时,必须在函数后面加上空格和下划线。
来看一个案例:将函数赋值给变量

scala> def sayHello(name: String) { println("Hello, " + name) }
sayHello: (name: String)Unit
scala> val sayHelloFunc = sayHello _
sayHelloFunc: String => Unit = <function1>
scala> sayHelloFunc("scala")
Hello, scala

匿名函数

Scala中的函数也可以不需要命名,这种函数称为匿名函数
匿名函数的语法格式:(参数名: 参数类型) => 函数体
(参数名: 参数类型) :是函数的参数列表
可以将匿名函数直接赋值给某个变量

看一个例子

scala> val sayHelloFunc = (name: String) => println("Hello, " + name)
sayHelloFunc: String => Unit = <function1>
scala> val sayHelloFunc = (name: String) => {println("Hello, " + name)}
sayHelloFunc: String => Unit = <function1>

注意:如果函数体有多行代码,则需要添加{}

高阶函数

由于函数是一等公民,所以说我们可以直接将某个函数作为参数传入其它函数。
这个功能是极其强大的,也是Java这种面向对象的编程语言所不具备的
这个功能在实际工作中是经常需要用到的。
接收其它函数作为当前函数的参数,当前这个函数也被称作高阶函数 (higher-order function)
看一个例子:
先定义一个匿名函数,赋值给变量sayHelloFunc

val sayHelloFunc = (name: String) => println("Hello, " + name)

再定义一个高阶函数,这个高阶函数的参数会接收一个函数
参数: (String) => Unit 表示这个函数接收一个字符串,没有返回值

def greeting(func: (String) => Unit, name: String) { func(name) } 

使用:

scala> greeting(sayHelloFunc, "scala")
Hello, scala

或者还可以这样用,直接把匿名函数的定义传过来也是可以

scala> greeting((name: String) => println("Hello, " + name),"scala")
Hello, scala

高阶函数可以自动推断出它里面函数的参数类型,对于只有一个参数的函数,还可以省去小括号

# 先定义一个高阶函数
def greeting(func: (String) => Unit, name: String) { func(name) }
# 使用高阶函数:完整写法
greeting((name: String) => println("Hello, " + name), "scala")
# 使用高阶函数:高阶函数可以自动推断出参数类型,而不需要写明类型
greeting((name) => println("Hello, " + name), "scala")
# 使用高阶函数:对于只有一个参数的函数,还可以省去其小括号
greeting(name => println("Hello, " + name), "scala")

常用高阶函数

刚才是我们自己实现的高阶函数,其实我们在工作中自己定义高阶函数的场景不多,大部分场景都是去使用已有的高阶函数。下面我们来看几个常见的高阶函数

  • map:对传入的每个元素都进行处理,返回一个元素
  • flatMap:对传入的每个元素都进行处理,返回一个或者多个元素
  • foreach:对传入的每个元素都进行处理,但是没有返回值
  • filter:对传入的每个元素都进行条件判断,如果返回true,则保留该元素,否则过滤掉该元素
  • reduceLeft:从左侧元素开始,进行reduce操作

map

map的使用:

scala> Array(1, 2, 3, 4, 5).map(num=>{num * 2})
res38: Array[Int] = Array(2, 4, 6, 8, 10)

可以简写为:

scala> Array(1, 2, 3, 4, 5).map(_ * 2)
res40: Array[Int] = Array(2, 4, 6, 8, 10)

flatMap

flagMap的使用

scala> Array("hello you","hello me").flatMap(line=>line.split(" "))
res53: Array[String] = Array(hello, you, hello, me)

可以简写为:

scala> Array("hello you","hello me").flatMap(_.split(" "))
res54: Array[String] = Array(hello, you, hello, me)

foreach

foreach的使用

scala> Array(1, 2, 3, 4, 5).map(_ * 2).foreach(num=>println(num))
2
4
6
8
10

可以简写为:

scala> Array(1, 2, 3, 4, 5).map(_ * 2).foreach(println(_))
2
4
6
8
10

filter

filter的使用

scala> Array(1, 2, 3, 4, 5).filter(num=>num % 2 == 0)
res46: Array[Int] = Array(2, 4)

可以简写为:

scala> Array(1, 2, 3, 4, 5).filter(_ % 2 == 0)
res47: Array[Int] = Array(2, 4)

reduceLeft

reduceLeft的使用
表示先对元素1和元素2进行处理,然后将结果与元素3处理,再将结果与元素4处理,依次类推
spark中有一个reduce函数,和这个函数的效果一致

scala> Array(1, 2, 3, 4, 5).reduceLeft((t1,t2)=>t1+t2)
res50: Int = 15

这个操作操作就相当于 1 + 2 + 3 + 4 + 5

可以简写为:

scala> Array(1, 2, 3, 4, 5).reduceLeft( _ + _)
res49: Int = 15

案例:函数式编程

统计多个文本内的单词总数
使用scala的io包读取文本文件内的数据

val lines01 = scala.io.Source.fromFile("D://a.txt").mkString
val lines02 = scala.io.Source.fromFile("D://b.txt").mkString

注意:下面这一行是核心代码,使用了链式调用的函数式编程

lines.flatMap(_.split( " ")).map((_, 1)).map(_._2).reduceLeft(_ + _) 

lines.flatMap(.split( " ")) :表示对每一行数据使用空格进行切割,返回每一个单词
.map((
, 1)) :针对每一个单词,都转成tuple类型,tuple中的第1个元素是这个单词,第2个元素表示单词出现的次数1
.map(_.2) :迭代每一个tuple,获取tuple中的第2个元素
.reduceLeft(
+ _) :对前面获取到的元素进行累加求和

Scala高级特性

模式匹配

模式匹配是Scala中非常有特色,非常强大的一种功能。
模式匹配,其实类似于Java中的 switch case 语法,即对一个值进行条件判断,然后针对不同的条件,进行不同的处理
不过Scala没有Java中的 switch case 语法,但是,Scala提供了更加强大的 match case 语法,就是这个模式匹配
Java的 switch case 仅能匹配变量的值,而Scala的 match case 可以匹配各种情况,比如:变量的类型、集合的元素,有值没值

对变量的值进行模式匹配

match case语法格式:变量 match { case 值 => 代码 }
如果值为下划线,则代表了不满足以上所有情况下的默认处理

def demo1(day: Int) {
day match {
case 1 => println("Monday")
case 2 => println("Tuesday")
case 3 => println("Wednesday")
case _ => println("none")
}
}

执行

scala> demo1(1)
Monday
scala> demo1(4)
none

注意:match case中,只要一个case分支满足并处理了,就不会继续判断下一个case分支了,这一点与Java不同,java的switch case需要用break停止向下执行

变量类型的模式匹配

Scala的模式匹配一个强大之处就在于,可以直接匹配类型,而不是值!!!这点是java的switch case绝对做不到的
语法格式:变量 match { case 变量: 类型 => 代码 }
典型的一个应用场景就是针对异常的处理

import java.io._
def processException(e: Exception) {
e match {
case e1: IllegalArgumentException => println("IllegalArgumentException "
case e2: FileNotFoundException => println("FileNotFoundException " + e2)
case e3: IOException => println("IOException " + e3)
case _: Exception => println("Exception " )
}
}

执行

scala> processException(new Exception())
Exception

在try-catch异常中的应用

try {
val lines02 = scala.io.Source.fromFile("D://test02.txt").mkString
} catch {
case ex: FileNotFoundException => println("no file")
case ex: IOException => println("io exception")
case ex: Exception => println("exception")
}

case class与模式匹配

Scala中提供了一种特殊的类,用 case class 进行声明,中文可以称为样例类
case class 其实有点类似于Java中的JavaBean的概念。即只定义field,会由Scala在编译时自动提供get和set方法,但是没有其它的method
case class 的主构造函数接收的参数通常不需要使用var或val修饰,Scala自动就会使用val修饰(但是如果你自己使用var修饰,那么还是会按照var来,在这用哪个区别都不大)
Scala自动为 case class 定义了伴生对象,也就是object,并且定义了apply()方法,该方法接收主构造函数中相同的参数,并返回 case class 对象
下面来看一个案例:,先定义三个class

class Person
case class Teacher(name: String, sub: String) extends Person
case class Student(name: String, cla: String) extends Person

再创建一个函数

def check(p: Person) {
p match {
case Teacher(name, sub) => println("Teacher, name is " + name + ", sub is
case Student(name, cla) => println("Student, name is " + name + ", cla is
case _ => println("none")
}
}

执行

scala> check(new Student("tom","class1"))
Student, name is tom, cla is class1
scala> check(new Person())
none

Option与模式匹配

Scala有一种特殊的数据类型,叫做Option。
Option有两种值,一种是Some,表示有值,一种是None,表示没有值
Option通常会用于模式匹配中,用于判断某个变量是有值还是没有值,这比null来的更加简洁明了
看这个案例

val ages = Map("jack" -> 18, "tom" -> 30, "jessic" -> 27)
def getAge(name: String) {
val age = ages.get(name)
age match {
case Some(age) => println("your age is " + age)
case None => println("none")
}
}

执行

scala> getAge("jack")
your age is 18
scala> getAge("hehe")
none

隐式转换

Scala的隐式转换,允许手动指定将某种类型的对象转换成其它类型的对象。
Scala的隐式转换,最核心的就是定义隐式转换函数,即implicit conversion function 。
隐式转换函数与普通函数唯一的语法区别是要以implicit开头而且最好要定义函数返回类型。
隐式转换非常强大的一个功能,就是可以在不知不觉中加强现有类型的功能。也就是说,我们可以为某个普通类定义一个加强类,并定义对应的隐式转换函数,这样我们在使用加强类里面的方法的时候,Scala会自动进行隐式转换,把普通类转换为加强类,然后再调用加强类中的方法
Scala默认会自动使用两种隐式转换
1:源类型,或者目标类型的伴生对象里面的隐式转换函数
2:当前程序作用域内可以用唯一标识符表示的隐式转换函数

def check(p: Person) {
	p match {
	case Teacher(name, sub) => println("Teacher, name is " + name + ", sub is
	case Student(name, cla) => println("Student, name is " + name + ", cla is
	case _ => println("none")
}

scala> check(new Student("tom","class1"))
Student, name is tom, cla is class1
scala> check(new Person())

val ages = Map("jack" -> 18, "tom" -> 30, "jessic" -> 27)
def getAge(name: String) {
	val age = ages.get(name)
	age match {
	case Some(age) => println("your age is " + age)
	case None => println("none")
	}
}

scala> getAge("jack")
your age is 18
scala> getAge("hehe")
none

如果隐式转换函数不在上述两种情况下的话,那么就必须手动使用import引入对应的隐式转换函数。
通常建议,仅仅在需要进行隐式转换的地方,比如某个函数内,用import导入隐式转换函数,这样可以缩小隐式转换函数的作用域,避免不需要的隐式转换。

案例:狗也能抓老鼠

通过隐式转换实现,狗也具备猫抓老鼠的功能

class cat(val name: String){
	def catchMouse(){println(name+" catch mouse")}
}
class dog(val name: String)
	implicit def object2Cat (obj: Object): cat = {
	if (obj.getClass == classOf[dog]) {
		val dog = obj.asInstanceOf[dog]
		new cat(dog.name)
	}
	else Nil
}

执行

scala> val d = new dog("d1")
d: dog = dog@7f0e0db3
scala> d.catchMouse()
d1 catch mouse

大部分的场景是我们只需要使用import。导入对应的隐式转换函数就可以了,在这个案例中我们是自己手工实现了一个隐私转换函数,因为他们都在一个作用域内,所以就不需要import了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1177819.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

双十一运动健身好物推荐,这几款健身好物一定不要错过!

双十一购物狂欢节又要到了&#xff0c;又要到买买买的时候了&#xff01;相信有很多想健身的小白还在发愁不知道买啥装备&#xff1f;别急&#xff0c;三年健身达人这就给你们分享我的年度健身好物&#xff01; 第一款&#xff1a;南卡Runner Pro4s骨传导耳机 推荐理由&#…

批量导出有道云笔记并放入Logseq

出发点 大学的时候用有道云笔记&#xff0c;积累了不少文章和总结 随这这几年的网盘等业务关停&#xff0c;个人重要数据依赖互联网总感觉没有安全感 前几年换成了纯本地的双链笔记Logseq&#xff0c;感觉非常简洁好用&#xff08;LogseqPC端/移动端Synology Dive同步&#…

【渗透测试】垂直越权(高危)、水平越权(中危)

目录 一、简介1.1 水平越权&#xff08;中危&#xff09;1.2 垂直越权&#xff08;高危&#xff09;1.3 方便记忆方法 二、修复方案2.1 水平越权修复2.2 垂直越权修复 一、简介 1.1 水平越权&#xff08;中危&#xff09; 漏洞危害&#xff1a; 水平越权 是相同级别&#xff0…

LangChain+LLM实战---私有化部署RAG大模型,ChatGLM2-6B、Baichuan2-13B

图1&#xff1a;RAG的架构流程 经过之前一段时间的捣腾&#xff0c;个人感觉我们的RAG应用已经来到了一个全新的层面&#xff0c;在语义理解&#xff08;相关度&#xff09;和准确度上都有了长足进步。 但是问题来了。之前和菊厂的业务交流中&#xff0c;对方明确提出一些客户…

Android 扩大View可点击区域范围

有时候会遇到这种需求&#xff1a;本身控件显示在很小的范围内&#xff0c;但是要求扩大可点击的区域。根据官方文档https://developer.android.com/develop/ui/views/touch-and-input/gestures/viewgroup?hlzh-cn#delegate可以得知通过 TouchDelegate 类&#xff0c;让父视图…

Qt 各种数据类型

目录 1. 基础类型 2. log 输出 3. 字符串类型 3.2 QByteArray 构造函数 数据操作 子字符串查找和判断 遍历 查看字节数 类型转换 3.3 QString 4. QVariant 4.1 标准类型 4.2 自定义类型 5. 位置和尺寸 5.1 QPoint 5.2 QLine 5.3 QSize 5.4 QRect 6. 日期和…

Halcon WPF 开发学习笔记(0):开篇介绍

文章目录 文章专栏Halcon是什么&#xff1f;安装教学视频链接简单来说 Halcon快速开发环境确认新建项目 文章专栏 Halcon开发 Halcon是什么&#xff1f; 史上最全VisionPro和Halcon 的详细对比 Halcon简述 Halcon基础大全&#xff08;基础算子、高阶算子、数组、分割、字符检测…

AI视频智能分析系统在线监测垃圾满溢/堆放/暴露解决方案

一、背景需求 随着我国城市化进程的加快和居民生活水平的提高&#xff0c;垃圾围城的现象越来越严重。垃圾桶溢满、垃圾长时间暴露等现象&#xff0c;不仅严重污染了生态环境&#xff0c;同时也极大影响了市容市貌&#xff0c;并且对居民的身体健康也构成了威胁&#xff0c;因…

Figma软件的缺点和替代软件推荐

说到Figma软件&#xff0c;相信没有人不知道设计行业的人&#xff0c;尤去年Adobe以200亿美元收购Figma软件的消息&#xff0c;对设计行业影响很大。可想而知&#xff0c;Figma软件在设计行业人士眼中的地位是毋庸置疑的。的确&#xff0c;Figma软件的功能非常强大&#xff0c;…

JavaScript_document对象_方法_创建元素

1、document.createElement() document.createElement方法用来生成元素节点&#xff0c;并返回该节点 2、document.createTextNode() document.createTextNode方法用来生成文本节点&#xff08;Text实例&#xff09;&#xff0c;并返回该节点。它的参数是文本节点的内容 3、…

Win系统强制删除文件/文件夹

Win系统强制删除文件/文件夹 前言系统磁盘清理360强制删除NPM删除 前言 Win系统的用户删除文件/文件夹时&#xff0c;可能由于权限问题导致文件无法正常删除&#xff0c;下文介绍解决方案。当常规的删除不起作用时&#xff0c;可使用如下方案进行删除&#xff0c;包含系统磁盘…

V-REP和Python的联合仿真

机器人仿真软件 各类免费的的机器人仿真软件优缺点汇总_robot 仿真 软件收费么_dyannacon的博客-CSDN博客 课程地址 https://class.guyuehome.com/p/t_pc/course_pc_detail/column/p_605af87be4b007b4183a42e7 课程资料 guyueclass: 古月学院课程代码 旋转变换 旋转的左乘与…

灵魂拷问:读取 excel 测试数据真的慢吗?

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

64位Office API声明语句第112讲

跟我学VBA&#xff0c;我这里专注VBA, 授人以渔。我98年开始&#xff0c;从源码接触VBA已经20余年了&#xff0c;随着年龄的增长&#xff0c;越来越觉得有必要把这项技能传递给需要这项技术的职场人员。希望职场和数据打交道的朋友&#xff0c;都来学习VBA,利用VBA,起码可以提高…

Linux开发工具的使用(vim、gcc/g++ 、make/makefile)

文章目录 一 &#xff1a;vim1:vim基本概念2:vim的常用三种模式3:vim三种模式的相互转换4:vim命令模式下的命令集- 移动光标-删除文字-剪切/删除-复制-替换-撤销和恢复-跳转至指定行 5:vim底行模式下的命令集 二:gcc/g1:gcc/g的作用2:gcc/g的语法3:预处理4:编译5:汇编6:链接7:函…

【ARFoundation学习笔记】ARFoundation基础(下)

写在前面的话 本系列笔记旨在记录作者在学习Unity中的AR开发过程中需要记录的问题和知识点。难免出现纰漏&#xff0c;更多详细内容请阅读原文。 文章目录 TrackablesTrackableManager可跟踪对象事件管理可跟踪对象 Session管理 Trackables 在AR Foundation中&#xff0c;平面…

大厂面试题-b树和b+树的理解

为了更清晰的解答这个问题&#xff0c;从三个方面来回答&#xff1a; a.了解二叉树、AVL树、B树的概念 b.B树和B树的应用场景 1.B树是一种多路平衡查找树&#xff0c;为了更形象的理解&#xff0c;我们来看这张图。 二叉树&#xff0c;每个节点支持两个分支的树结构&#xff…

第十五届全国交通运输领域青年学术会议,和鲸 Heywhale 携手龙船科技联合发布科研服务解决方案

2023年10月29日&#xff0c;由中国交通运输协会青年科技工作者工作委员会主办&#xff0c;集美大学承办的“第十五届全国交通运输领域青年学术会议”在一片热烈的氛围中圆满落幕。 本届会议以“低碳•智能•安全•可持续综合交通发展创新”为主题&#xff0c;围绕综合立体交通…

JavaScript_document对象_方法_获取元素

1、document.getElementsByTagName 2、 document.getElementsByClassName() document.getElementsByClassName方法返回一个类似数组的对象&#xff08;HTMLCollection实例&#xff09;&#xff0c;包括了所有class名字符合指定条件的元素&#xff0c;元素的变化实时反映在返回…

FL Studio21.2中文高级版数字音乐工作站(DAW)

FL Studio是一款功能强大的数字音乐工作站&#xff08;DAW&#xff09;&#xff0c;软件提供了丰富的功能和工具&#xff0c;使音乐制作变得更加轻松和富有创意性。而在其中&#xff0c;一个关键的功能就是Fruity Wrapper&#xff0c;它在FL Studio中扮演着重要的角色。接下来给…