Scala基础
- scala基础
- Scala介绍
- 第一个scala代码
- object和class的区别
- 关键区别
- 伴生类和伴生对象:
- 字节码解析
- 在java中创建三个类
- 反编译代码
- 编译User.class源码后的结果
- 编译Emp.class源码后的结果
- 注释
- Scala类型推断&至简原则
- 变量
- var和val之间的区别
- 可变变量
- 不可变变量
- 标识符
- 基本规则
- 特殊符号
- 数据类型
- 任意值类型
- 任意引用类型
- 不同类型的数据转换
- 自动(隐式)转换
- 强制类型转换
- 循环控制
- for循环
- 循环守卫
- 设置步长
- 双层for循环
- 引入变量
- for循环的返回值
- java中线程对象有yeild方法,在scala中如何调用
- while循环
- 循环中断
- 流程控制
- 分支判断
- 分支控制都是有返回值的
- 运算符
- 关系运算符
- 等号原理说明
- 加法运算
- 字符串
- 拼接方式
- 输入输出
- 输入
- 从控制台获取数据
- 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 {// 快捷键 maindef 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.classLast modified 2024-4-24; size 499 bytesMD5 checksum 1d09d609093679118c6e95baffbcedf3Compiled from "User.java"
public class com.test.Userminor version: 0major version: 52flags: 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: Iflags: ACC_PUBLIC, ACC_STATICpublic com.test.User();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 9: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this Lcom/test/User;static {};descriptor: ()Vflags: ACC_STATICCode:stack=2, locals=0, args_size=00: bipush 302: putstatic #2 // Field age:I5: 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;)V13: returnLineNumberTable:line 11: 0line 14: 5line 15: 13
}
SourceFile: "User.java"
这个里面可以清晰的看到,age是在静态代码块中进行复制的。
编译Emp.class源码后的结果
警告: 二进制文件Emp包含com.test.Emp
Classfile /Users/lida/private/project/qmtools/out/production/scala/com/test/Emp.classLast modified 2024-4-24; size 505 bytesMD5 checksum a790a625f63a08555848173ef4d808a6Compiled from "Emp.java"
public class com.test.Empminor version: 0major version: 52flags: 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: Iflags: ACC_PUBLIC, ACC_STATIC, ACC_FINALConstantValue: int 30public com.test.Emp();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 9: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this Lcom/test/Emp;static {};descriptor: ()Vflags: ACC_STATICCode:stack=2, locals=0, args_size=00: 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;)V8: returnLineNumberTable:line 14: 0line 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.getInputStreamval 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.getOutputStreamstream.write(100)stream.close()println("向服务器发送给数据,100")socket.close();}
}
序列化
网络中传输的是256之间的ASCALL码,也可以理解为就是传输的数字
可以在网络中传输字节码,可以将对象转换为字节码,这个叫做序列化。
将字节码转换为对象,称之为反序列化