Scala基础
scala基础
Scala介绍
Scala是Scalable Language两个单词的缩写,表示可伸缩,是一门完整的计算机编程语言,作者马丁
Scala来自于Java语言
Java语言来自于C语言:跨平台
马丁也是javac的开发作者
Scala是一个完全面向对象的语言
Scala是基于Java语言开发的,所以运行环境也是基于JVM
Scala是一个面向函数式编程语言,更适合迭代式数据计算
后面的Spark,Fink计算框架底层都是使用Scala进行开发。Kafka也是采用Scala语言开发的。
第一个scala代码
package com.aaa
object HelloWorld01 {
// 快捷键 main
def main(args: Array[String]): Unit = {
System.out.println("Hello World");
println("Hello Scala");
}
}
解析:
- package:包,等同于java中的package
- object: 声明对象(单例)通常用于封装静态方法和常量,不能实例化。
- scala是一个完全面向对象的语言,但是java中的静态语法不是面向对象的。
- def:声明方法的关键字。
- main:Scala语言程序入口
- main(…)小括号表示方法参数列表,可以有参数,也可以没有参数,由多个参数使用逗号隔开
- args: Array[String]:方法的参数
- java=》String[] arg
- java语言是一个强类型语言,在编译时就需要明确类型,所以类型很重要。开发时,类型并不是那么重要
- scala=》arg:String[]
- scala语言是基于java开发的所以也是强类型语言
- 作者认为参数名称更重要,开发程序时用的多,为了方便记忆使用,所以将名称放在前面,类型放在后面,为了使用方便,将参数名称和参数类型使用冒号分隔开。
- Array[String]:表示参数类型
- scala语言是一个完全面向对象的语言,所以数组也是对象,也有自己的类型 scala语言中括号中的String表示泛型
- def main:Unit
- scala语言中方法的声明也符合scala的原则
- 方法名(参数列表):方法类型
- scala语言是基于java开发,是一个完全面向对象的语言,方法的返回值也是对象,也应该有相应的类型,但是,没有返回值这个事本身也是一个返回,也需要有类型:Unit(void)
- =:赋值
- 将代码块逻辑赋值给一个方法名
- {} :方法体
- System.out.println(“Hello World”); :java代码,那么java的代码可以在scala代码中直接使用
- scala也提供了简化的代码操作:println(“Hello World”);
- 代码没有分号结尾
- scala语言中认为一行代码最好完成一段逻辑,不要多个逻辑操作在一行完成,会比较乱,如果一行代码就是一段逻辑,那么就不要使用分号进行区分,不写的话,并不代表没有,而是编译时候,会进行补全,如果需要代码多行才能完成逻辑,需要使用分号进行隔开。
注意事项:
scala语言中没有静态语法,java语言中的静态操作在scala中如何使用呢?
scala采用新的关键字object来模拟静态语法,可以通过对象名称.方法/变量实现静态操作。
如果使用object关键字声明一个对象,那么在编译时也会编译出对应的class文件?
object关键字声明的对象类型和当前编译后的class文件的类型不一样,多了一个$,会生成两个class文件1.HelloWorld01.class
2.HelloWorld01$.class(单例)
HelloWorld01:单例对象名称,同时也是类名
object和class的区别
在 Scala 中,object 和 class 都是定义类型的关键字,但它们有着不同的用途和特性。
- class:
- 定义类:class 用来定义类,是面向对象编程中的一个基本概念。类是对象的模板,可以包含属性(字段)和方法(函数)。
- 实例化:类是模板,只有通过实例化类才能创建一个对象。使用 new 关键字来创建类的实例。
- 支持继承和多态:class 支持继承、特质、方法覆盖等面向对象的特性。
class Person(val name: String, val age: Int) {
def greet(): Unit = {
println(s"Hello, my name is $name and I am $age years old.")
}
}
val person = new Person("Alice", 30)
person.greet() // 输出:Hello, my name is Alice and I am 30 years old.
- 特点:
- 可以实例化。
- 支持构造器(如上例的 name 和 age)。
- 可以有成员变量和方法。
- 支持继承。
- object
- 定义单例对象:object 用于定义一个 单例对象,它会自动创建一个类的唯一实例。它是 Scala 中的一个特殊结构,通常用于声明常量、工具类、伴生对象等。
- 不能实例化:object 不能通过 new 创建实例,因为它本身就是一个类的单一实例。
- 伴生对象:在 Scala 中,object 通常与 class 配对,称为 伴生对象和 伴生类。伴生对象通常用于封装静态方法和常量。
object MySingleton {
val name = "Singleton"
def greet(): Unit = {
println(s"Hello from $name!")
}
}
MySingleton.greet() // 输出:Hello from Singleton!
- 特点:
- 只有一个实例。
- 可以定义方法、字段和常量。
- 常用于定义工具类、工厂方法等。
- 常与类(class)搭配使用,作为伴生对象(companion object)。
关键区别
特性 | class | object |
---|---|---|
定义内容 | 定义一个类,作为模板 | 定义一个单例对象,只有一个实例 |
实例化 | 可以实例化多个对象 | 只能有一个实例,不能通过 new 创建 |
用途 | 用于创建实例化的对象,支持继承、方法、字段等 | 常用于工具类、常量、单例模式等 |
伴生关系 | 可以与 object 作为伴生类一起使用 | 与 class 作为伴生对象一起使用,封装类的静态方法 |
实例化方式 | 使用 new 创建实例 | 自动创建单例实例,不能使用 new 创建 |
伴生类和伴生对象:
在 Scala 中,类和对象常常作为伴生类(companion class)和伴生对象(companion object)成对出现。伴生对象和伴生类可以访问彼此的私有成员,并且它们必须位于同一个源文件中。
class MyClass(val name: String)
object MyClass {
def apply(name: String): MyClass = new MyClass(name) // 工厂方法
}
val obj = MyClass("Alice") // 通过伴生对象的 apply 方法创建实例
println(obj.name) // 输出:Alice
字节码解析
在java中创建三个类
public class Emp {
// 被final修饰
public static final int age=30;
static {
System.out.println("emp....");
}
}
public class User {
public static int age=30;
static {
System.out.println("user....");
}
}
public class Test {
public static void main(String[] args) {
System.out.println(User.age);
System.out.println(Emp.age);
}
}
运行结果
user....
30
30
Emp中的age被final修饰后,没有打印静态代码块中的内容。
这是为什么呢?
反编译代码
通过javap命令来进行反编译代码
/ 进入idea中target中的class文件夹下,打开Termail控制台。
使用命令
javap -v User
javap -v emp
编译User.class源码后的结果
警告: 二进制文件User包含com.test.User
Classfile /Users/lida/private/project/qmtools/out/production/scala/com/test/User.class
Last modified 2024-4-24; size 499 bytes
MD5 checksum 1d09d609093679118c6e95baffbcedf3
Compiled from "User.java"
public class com.test.User
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #7.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #6.#21 // com/test/User.age:I
#3 = Fieldref #22.#23 // java/lang/System.out:Ljava/io/PrintStream;
#4 = String #24 // user....
#5 = Methodref #25.#26 // java/io/PrintStream.println:(Ljava/lang/String;)V
#6 = Class #27 // com/test/User
#7 = Class #28 // java/lang/Object
#8 = Utf8 age
#9 = Utf8 I
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = Utf8 LineNumberTable
#14 = Utf8 LocalVariableTable
#15 = Utf8 this
#16 = Utf8 Lcom/test/User;
#17 = Utf8 <clinit>
#18 = Utf8 SourceFile
#19 = Utf8 User.java
#20 = NameAndType #10:#11 // "<init>":()V
#21 = NameAndType #8:#9 // age:I
#22 = Class #29 // java/lang/System
#23 = NameAndType #30:#31 // out:Ljava/io/PrintStream;
#24 = Utf8 user....
#25 = Class #32 // java/io/PrintStream
#26 = NameAndType #33:#34 // println:(Ljava/lang/String;)V
#27 = Utf8 com/test/User
#28 = Utf8 java/lang/Object
#29 = Utf8 java/lang/System
#30 = Utf8 out
#31 = Utf8 Ljava/io/PrintStream;
#32 = Utf8 java/io/PrintStream
#33 = Utf8 println
#34 = Utf8 (Ljava/lang/String;)V
{
public static int age;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC
public com.test.User();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/test/User;
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: bipush 30
2: putstatic #2 // Field age:I
5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #4 // String user....
10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: return
LineNumberTable:
line 11: 0
line 14: 5
line 15: 13
}
SourceFile: "User.java"
这个里面可以清晰的看到,age是在静态代码块中进行复制的。
编译Emp.class源码后的结果
警告: 二进制文件Emp包含com.test.Emp
Classfile /Users/lida/private/project/qmtools/out/production/scala/com/test/Emp.class
Last modified 2024-4-24; size 505 bytes
MD5 checksum a790a625f63a08555848173ef4d808a6
Compiled from "Emp.java"
public class com.test.Emp
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#21 // java/lang/Object."<init>":()V
#2 = Fieldref #22.#23 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #24 // emp....
#4 = Methodref #25.#26 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #27 // com/test/Emp
#6 = Class #28 // java/lang/Object
#7 = Utf8 age
#8 = Utf8 I
#9 = Utf8 ConstantValue
#10 = Integer 30
#11 = Utf8 <init>
#12 = Utf8 ()V
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 LocalVariableTable
#16 = Utf8 this
#17 = Utf8 Lcom/test/Emp;
#18 = Utf8 <clinit>
#19 = Utf8 SourceFile
#20 = Utf8 Emp.java
#21 = NameAndType #11:#12 // "<init>":()V
#22 = Class #29 // java/lang/System
#23 = NameAndType #30:#31 // out:Ljava/io/PrintStream;
#24 = Utf8 emp....
#25 = Class #32 // java/io/PrintStream
#26 = NameAndType #33:#34 // println:(Ljava/lang/String;)V
#27 = Utf8 com/test/Emp
#28 = Utf8 java/lang/Object
#29 = Utf8 java/lang/System
#30 = Utf8 out
#31 = Utf8 Ljava/io/PrintStream;
#32 = Utf8 java/io/PrintStream
#33 = Utf8 println
#34 = Utf8 (Ljava/lang/String;)V
{
public static final int age;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 30
public com.test.Emp();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/test/Emp;
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String emp....
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 14: 0
line 15: 8
}
SourceFile: "Emp.java"
从Test.class的代码中就可以看到,获取User中的age时,是获取的静态代码中的数据。但是Emp中的age是常量
注释
object Scala01_Desc {
def main(args: Array[String]): Unit = {
// 注释,说明、描述代码的作用
// 单行注释
// TODO 注释
/*
多行注释
可以将说明性的内容
跨行
*/
/**
* 文档注释
*/
}
}
Scala类型推断&至简原则
Scala语言的目的是为了简化代码的开发,让程序更加容易
提出至简原则:能简单就简单,能简洁就简洁,能省则省,这里的能省则省,并不是不写,而是编译器可以帮助我们补充完整
java和scala是一个强类型的语言,那么变量的取值也就确定了。
如果变量的值可以确定,那么变量的类型也就确定了。所以写程序时,类型可以省略,由编译器补充完整。
var name = "lisi";
注意:多态不适用这个至简原则
变量
变量是一种使用方便的占位符,用于饮用计算机内存地址,变量创建后会占用一定的内存空间,基于变量的数据类型,操作系统会进行内存分配并且决定什么将被储存在保留内存中。因此,通过给变量分配不同的数据类型,你可以在这些变量中存储整数、小数或者字母。
- 声明
变量的类型在变量名之后等号之前声明。
详解:
// scala 中声明变量需要采用特殊的方式
// 1。采用关键字var|val关键字声明
// 2.变量类型放在变量名称后面
// 3。变量名称和类型之间使用冒号分割
// 4。scala中变量必须显示初始化
var name:String="张三";
val email:String="zhangsan@111.com";
name="李四";
// email="lisi@111.com";
var和val之间的区别
var 关键字修饰的变量,值可以改变,称之为可变变量
val 关键字修饰的变量,值不可以改变,称之为不可变变量
var和val关键字声明的变量,在编译后是没有区别的,但是在编译时会有约束
可变变量
值可以改变的变量,称之为可变变量,但是变量类型无法发生改变,scala中可变变量使用关键字var进行声明
var username:String="lisi"
不可变变量
值一旦初始化后无法改变的变量,称之为不可变变量。Scala中不可变变量使用关键字val进行声明,类似于Java语言中的final关键字。
val username:String="lisi"
val使用的更加频繁一些。
标识符
基本规则
一般情况下,所谓的标识符就是起名
类名、对象名、方法名、参数名
java中的标识符规则:
1.数字、字符、下划线、美元符号
2.数字不能开头
3.长度没有限制
4.不鞥识关键字或者保留子
5.区分大小写
6.可以转换为unicode编码的文件都可以声明为标识符。
scala中的标识符的基本规则和java中识一致的。
如果非要使用和关键字同名的标识符,可以增加使用反引号
val `private`="123";
特殊符号
scala是一个完全面向函数式编程语言,所以可以声明特殊符号作为标识符。
val * ="zhangsan";
使用特殊符号作为Scala标识符,规则不要记,错了改就完了。如果非要记,常用在代码中的符号不要使用:=,{},[],(),‘’,“”
如果采用特殊符号形成特殊文字,标识特殊的含义,称之为颜文字。
因为特殊符号在编译后会被编译器进行转换,所以可以使用
// 在定义的时候两种方式都可以进行定义
val :-> = "zhangsan";
// 编译后的格式
val
$colon$minus$greater = "zhangsan";
注意:不适用$开头
数据类型
任意值类型
scala与java有着相同的数据类型,但是又有不一样的地方
- java的数据类型
- ava的数据类型有基本数据类型和引用类型
基本数据类型:byte、short、int、long、float、double、char、boolean
引用类型:Object、数组、字符串、包装类、集合、POJO对象等
- ava的数据类型有基本数据类型和引用类型
- scala的数据类型
- scala中没有基本数据类型
所有的基本数据类型在scala中都有专门的类型
任意值类型
任意引用类型
- scala中没有基本数据类型
Unit的返回值是一个()
StringOps是一个和字符串有关联的数据类型
object Scala01_DataType {
def main(args: Array[String]): Unit = {
println(test())
}
def test():Unit={
}
}
任意引用类型
所有的java类型,所有的scala类型,scala类型都是引用类型
引用类型的取值为空时,一般会赋值为null,但是null本身也应该有类型NULL,也表示对象
val name:Null=null;
Nothing
一般常用于异常处理
def test1():Nothing={
throw new Exception("")
}
不同类型的数据转换
val i:Int=10;
val a:String="10";
println(i)
println(a)
// 赋值值类型
val c:AnyVal=i;
// 赋值引用类型
val d:AnyRef=a;
// 赋值任意类型
val f:Any=i;
println(c)
println(d)
println(f)
// 不能进行赋值,因为数据类型不正确
// val j:Int=null;
// println(j)
自动(隐式)转换
// Byte和Int两个类型之间没有任何的关系,所以不应该能够转换
// scala底层对类型进行了转换,所以代码上看不出来,所以称之为隐式(自动)转换
val b1:Byte=10;
val b2:Int=b1;
println(b2)
强制类型转换
// 可以进行显式转换
val b1:Byte=10;
val b2:Int=b1.toInt;
println(b2)
循环控制
for循环
var array = 1 to 5;
var array1 = 1 until 5;
var array2 = Range(1, 5);
for (i: Int <- array) {
println(i)
}
for (i: Int <- array1) {
println(i)
}
for (i: Int <- array2) {
println(i)
}
for (i <- array2) {
println(i)
}
循环守卫
var array2 = Range(1, 5);
for (i <- array2 if i!=3) {
println(i)
}
设置步长
var array3 = 1 to 5 by 2;
for (i <- array3) {
println(i)
}
for (i <- Range(1,6,2)) {
println(i )
}
双层for循环
for (i <- 1 to 5; j <- 1 to 5) {
println(i + " " + j)
}
引入变量
for (i <- 1 to 5) {
var j = i - 1;
println(i + " " + j)
}
// 简化
for (i <- 1 to 5; j = i - 1) {
println(i + " " + j)
}
for循环的返回值
如果想要将集合中的数据进行处理后返回,可以采用特殊的关键字:yield
var result=for(i<-1 to 5) yield{
i*2
}
println(result);
java中线程对象有yeild方法,在scala中如何调用
Thread.`yield`()
while循环
while (true){
println("*")
}
循环中断
scala中没有break,continue
import scala.util.control.Breaks
import scala.util.control.Breaks._
Breaks.breakable {
for (i <- 1 to 5) {
if (i == 3) {
Breaks.break()
}
println("i=" + i)
}
}
breakable {
for (i <- 1 to 5) {
if (i == 3) {
break()
}
println("i=" + i)
}
}
println("结束")
流程控制
分支判断
- 1.if可以独立使用,称为单分支
val age=30;
if(age==30){
println("年龄等于30")
}
- 2.if可以和else联合使用,称为双分支
val age=3;
if(age==30){
println("年龄等于30")
}else{
println("年龄不等于30")
}
- 3.与elseif,else联合使用,称为多分支
val age=40;
if(age==30){
println("年龄等于30")
}else if(age>30){
println("年龄大于30")
}else{
println("年龄不等于30")
}
分支控制都是有返回值的
// 这里的返回结果其实就是满足条件后的最后一行代码的执行结果
// 返回结果是所有结果的通用类型
val result=if(age==30){
println("年龄等于30");
"壮年";
}
println(result)
注意:scala中没有三元运算符,也没有switch语法
运算符
关系运算符
- 双等号
scala 中的双等号可以理解为非空的equals操作。
比较内存地址值 使用eq方法
val name1=new String("123");
// val name1=null;
val name2=new String("123");
println(name1==name2)
println(name1.equals(name2))
// 比较内存地址值
println(name1 eq name2 )
等号原理说明
在java中使用++运算(使用临时变量将值进行暂存)有歧义,容易理解错误,所以在scala中没有这样的语法,所以采用+=的方式来进行替代。
加法运算
var r=1.+(1);
var r1=1+1+2;
println(r)
println(r1)
字符串
scala中没有字符串,使用的是java中的字符串,它本身没有字符串
拼接方式
// + 拼接
println("name="+name1);
// 传值字符串拼接
printf("name=%s\n",name1);
// 插值字符串拼接
println(s"name=${name1}");
// 多行字符串 |顶格字符串
// 常用于json和sql字符串
println(
"""
|hello
|scala
|""".stripMargin);
println(
"""
#hello
#scala
#""".stripMargin('#'));
输入输出
输入
从控制台获取数据
val age:Int = scala.io.StdIn.readInt()
println(age);
在控制台输入完成后,需要进行回车
scala获取文件中的数据,需要采用特殊的对象
val source:BufferedSource = Source.fromFile("");
val iter = source.getLines()
while (iter.hasNext){
println(iter.next())
}
source.close();
输出
scala中没有输出,采用的是java中的输出
val writer = new PrintWriter("")
writer.println("test")
writer.println("scala")
writer.flush()
writer.close()
网络
Scala中进行网络交互,也是采用的java中的
- server端代码
object Scala03_Net_Server {
def main(array: Array[String]):Unit={
val server = new ServerSocket(9999)
println("服务器启动成功,等待客户端的链接。。。")
val client = server.accept()
val stream = client.getInputStream
val i = stream.read()
println(i)
stream.close()
client.close()
server.close()
}
}
- client端代码
object Scala03_Net_Client {
def main(array: Array[String]):Unit={
// 创建服务器
val socket = new Socket("localhost",9999)
println("链接成功,向服务器发送数据")
val stream = socket.getOutputStream
stream.write(100)
stream.close()
println("向服务器发送给数据,100")
socket.close();
}
}
序列化
网络中传输的是256之间的ASCALL码,也可以理解为就是传输的数字
可以在网络中传输字节码,可以将对象转换为字节码,这个叫做序列化。
将字节码转换为对象,称之为反序列化