一、泛型
1、基本介绍
1. 如果我们要求函数的参数可以接受任意类型。可以使用泛型,这个类型可以代表任意的数据类型。
2. 例如List,在创建List时,可以传入整型、字符串、浮点数等等任意类型。那是因为List在类定义时引用了泛型。
比如在Java中:public interface List<E> extends Collection<E>
2、泛型应用案例1 --> 普通的写法
要求
1. 编写一个Message类;
2. 可以构建Int类型的Message,String类型的Message;
3. 要求使用泛型来完成设计,(说明:不能使用Any)。
示例代码:
package com.lj.akka.generic
/**
* @author Administrator
* @create 2020-03-26
*/
object GenericTest01 {
def main(args: Array[String]): Unit = {
val int_res = new IntMessage[Int](5).get
println("int_res:" + int_res) // int_res:5
val str_res = new StrMessage[String]("hello world~").get
println("str_res:" + str_res) // str_res:hello world~
}
}
// 在 Scala 定义泛型用[T](T这个字母名称可以随便起), s为泛型的引用
abstract class Message[T](s: T) {
def get = s
}
// 子类扩展的时候,约定了具体的类型
// Int类型的泛型
class IntMessage[Int](s: Int) extends Message(s)
// String类型的泛型
class StrMessage[String](s: String) extends Message(s)
上述代码缺点:由于每个类型都需要重新定义一个类去继承泛型Message[T](s: T),繁琐不够优雅。
3、Scala泛型应用案例2 --> 优雅的写法
要求:
1. 请设计一个EnglishClass (英语班级类),在创建EnglishClass的一个实例时,需要指定[ 班级开班季节(spring,autumn,summer,winter)、
班级名称、班级类型];
2. 开班季节只能是指定的,班级名称为String, 班级类型是(字符串类型 "高级班", "初级班"..) 或者是Int类型(1, 2, 3 等)
3. 使用泛型来完成本案例。
示例代码:
package com.lj.akka.generic
import com.lj.akka.generic.SeasonEnum.SeasonEnum
/**
* @author Administrator
* @create 2020-03-26
*/
object GenericTest02 {
def main(args: Array[String]): Unit = {
val class01 = new EnglishClass[SeasonEnum, String, String](SeasonEnum.春, "大数据", "进阶班")
println(class01.toString) // 春季招收了一个班级,班级名称:大数据,此班级类型:进阶班
val class02 = new EnglishClass[SeasonEnum, String, Int](SeasonEnum.春, "大数据",1)
println(class02.toString) // 春季招收了一个班级,班级名称:大数据,此班级类型:1
}
}
// 泛型
class EnglishClass[A, B, C](classSeason: A, className: B, classType: C) {
override def toString: String = {
classSeason + s"季招收了一个班级,班级名称:${className},此班级类型:${classType}"
}
}
// 季节采用枚举类型
object SeasonEnum extends Enumeration {
type SeasonEnum = Value // 自定义SeasonEm,是Value类型,这样才能使用
val 春, 夏, 秋, 冬 = Value
}
4、Scala泛型应用案例3
要求:
1. 定义一个函数,可以获取各种类型的List的中间index的值;
2. 使用泛型完成。
示例代码:
package com.lj.akka.generic
/**
* @author Administrator
* @create 2020-03-26
*/
object GenericTest03 {
def main(args: Array[String]): Unit = {
val list01 = List(1, 3, 5, 7, 13, 6, 9)
val list02 = List("Leo", "Jack", "Amy", "Tom", "Java")
val list03 = List(5, "Jack", true, "Tom", 3.2)
println("list01:" + getRes[Int](list01)) // 7
println("list02:" + getRes[String](list02)) // Amy
println("list03:" + getRes[Any](list03)) // true
}
// 泛型函数
def getRes[T](list: List[T]): T = {
list(list.size / 2)
}
}
二、类型约束:上界(Upper Bounds)
1、上界(Upper Bounds)介绍和使用
java中上界
在Java泛型里表示某个类型是A类型的子类型,使用extends关键字,这种形式叫upper bounds(上限或上界),语法如下:
<T extends A>
//或用通配符的形式:
<? extends A>
Scala中上界
在scala里表示某个类型是A类型的子类型,也称上界或上限,使用“<: ”关键字,语法如下:
[T <: A]
//或用通配符:
[_ <: A]
2、Scala中上界应用案例
要求:
1. 编写一个通用的类,可以进行Int之间、Float之间等实现了Comparable接口的值直接的比较//java.lang.Integer;
2. 分别使用传统方法和上界的方式来完成,体会上界使用的好处。
传统方法示例代码:
package com.lj.akka.upperbounds
/**
* @author Administrator
* @create 2020-03-26
*/
object UpperBoundsTest01 {
def main(args: Array[String]): Unit = {
// 传统方法使用
println("res:" + (new CompareInt(10, 13).greater)) // 13
// 上界方法使用方式
// 第1种方式:这种写法太繁琐
val upperRes01 = new CompareUpperBounds(Integer.valueOf(12), Integer.valueOf(17))
println("upperRes01:" + upperRes01.greater) // 17
// 第2种方式:这种写法太繁琐(使用Java的类型转换)
val upperRes02 = new CompareUpperBounds(java.lang.Float.valueOf(12.3f), java.lang.Float.valueOf(8.9f))
println("upperRes01:" + upperRes02.greater) // 12.3
// 第3种方式:使用了隐式转换(推荐)
// implicit def float2Float(x: Float) = java.lang.Float.valueOf(x)
//映射转换 Predef.scala
val upperRes03 = new CompareUpperBounds[java.lang.Float](2.5f, 6.8f)
println("upperRes01:" + upperRes03.greater) // 6.8
}
}
// 传统方法实现两个Int值的比较(Float类型类似)
class CompareInt(n1: Int, n2: Int) {
def greater = {
if (n1 > n2) {
n1
} else {
n2
}
}
}
/**
* 上界的方式实现两个值的比较
* 说明:
* 1. [T <: Comparable[T]]: 表示T类型是Comparable的子类型;
* 2. 即传入的T类型要继承Comparable的接口;
* 3. 继承之后就可以使用compareTo方法;
* 4. 这样的写法(使用上界的写法)通用性比传统的方法要好的多
*/
class CompareUpperBounds[T <: Comparable[T]](n1: T, n2: T) {
def greater: T = {
if (n1.compareTo(n2) > 0) {
n1
} else {
n2
}
}
}
3、Scala中上界课程测试(深度理解上界的含义)
示例代码:
package com.lj.akka.upperbounds
/**
* @author Administrator
* @create 2020-03-26
*/
object UpperBoundsTest02 {
def main(args: Array[String]): Unit = {
println(UpperBoundsTest(Seq(new Birds, new Birds))) // List(Brids Sounds..., Brids Sounds...)
println(UpperBoundsTest(Seq(new Animals, new Animals))) // List(Animal Sounds..., Animal Sounds...)
println(UpperBoundsTest(Seq(new Animals, new Birds))) // List(Animal Sounds..., Brids Sounds...)
// println(UpperBoundsTest(Seq(new Earth, new Earth))) // × 因为Earth不是Animals的子类
}
// 上界
// 这里的:things map(_.sounds())可以不加点(things.map(_.sounds()))
def UpperBoundsTest[T <: Animals](things: Seq[T]) = things map(_.sounds)
}
class Earth {
def sounds(): String = {
"Hello~"
}
}
class Animals extends Earth{
override def sounds(): String = {
"Animal Sounds..."
}
}
class Birds extends Animals {
override def sounds(): String = {
"Brids Sounds..."
}
}
三、类型约束:下界(Lower Bounds)
1、下界(Lower Bounds)介绍和使用
Java中下界
在Java泛型里表示某个类型是A类型的父类型,使用super关键字,语法如下:
<T super A>
//或用通配符的形式:
<? super A>
Scala中下界
在Scala的下界或下限,使用 “>: ”关键字,语法如下:
[T >: A]
//或用通配符:
[_ >: A]
2、Scala中下界应用实例
示例代码:
package com.lj.akka.lowerbounds
/**
* @author Administrator
* @create 2020-03-26
*/
object LowerBoundsTest {
def main(args: Array[String]): Unit = {
// 使用
// List(Animals Sounds..., Animals Sounds...)
println(lowerBoundsTest(Seq(new Animal02, new Animal02)).map(_.sounds()))
// List(Birds Sounds..., Birds Sounds...)
// 因为子类中sounds()方法覆盖了父类中的方法,所以执行的时候回调用子类中的sounds()方法。
println(lowerBoundsTest(Seq(new Birds02, new Birds02)).map(_.sounds()))
// List(Animals Sounds..., Birds Sounds...)
println(lowerBoundsTest(Seq(new Animal02, new Birds02)).map(_.sounds()))
// List(Hello~, Hello~)
println(lowerBoundsTest(Seq(new Earth02, new Earth02)).map(_.sounds()))
// 这个错误原因是Moon类跟Animal类无关,此时会按照Object类处理,因为Object类中没有sounds方法。
// println(lowerBoundsTest(Seq(new Moon, new Moon)).map(_.sounds())) // ×
}
// 下界
def lowerBoundsTest[T >: Animal02](things: Seq[T]) = things
}
class Earth02 {
def sounds(): String = {
"Hello~"
}
}
class Animal02 extends Earth02 {
override def sounds(): String = {
"Animals Sounds..."
}
}
class Birds02 extends Animal02 {
override def sounds(): String = {
"Birds Sounds..."
}
}
class Moon {
def sounds(): String = {
"Moon"
}
}
下界的使用小结
def lowerBoundsTest[T >: Animal02](things: Seq[T]) = things
1. 对于下界,可以传入任意类型;
2. 传入和Animal02直系的,是Animal02父类的还是父类处理,是Animal02子类的按照Animal02处理,如果子类中
覆盖了父类的方法,则执行子类中的方法;
3. 和Animal02无关的,一律按照Object处理;
4. 也就是下界,可以随便传,只是处理是方式不一样;
5. 不能使用上界的思路来类推下界的含义。
四、类型约束:视图界定(View Bounds)
1、视图界定基本介绍
“<%”的意思是“view bounds”(视界),它比“<:”适用的范围更广,除了所有的子类型,还允许隐式转换类型。
def method [A <% B](arglist): R = ... 等价于:
def method [A](arglist)(implicit viewAB: A => B): R = ...
或等价于:
implicit def conver(a:A): B = …
“<%”除了方法使用之外,class声明类型参数时也可使用:
class A[T <% Int]
2、视图界定应用案例1
package com.lj.akka.viewbounds
/**
* @author Administrator
* @create 2020-03-26
*/
object ViewBoundsTest01 {
def main(args: Array[String]): Unit = {
// 使用
println(new VBCompare(Integer.valueOf(11), Integer.valueOf(15)).greater) // 15
// 同时它也支持上界使用方式
println(new VBCompare(java.lang.Float.valueOf(2.6f), java.lang.Float.valueOf(4.9f)).greater) // 4.9
println(new VBCompare[java.lang.Float](6.9f, 11.3f).greater) // 11.3
// 针对上面的小数比较,在视图界定里可以如下写法
// 此时的视图界定“<%”会发生隐式转换
println(new VBCompare(13.9f, 14.7f).greater) // 14.7
}
}
class VBCompare[T <% Comparable[T]](n1: T, n2: T) {
def greater = if (n1.compareTo(n2) > 0) n1 else n2
}
3、视图界定应用案例2(继承Ordered接口)
需求:
使用视图界定的方式,比较两个Person对象的年龄大小。
示例代码:
package com.lj.akka.viewbounds
/**
* @author Administrator
* @create 2020-03-26
*/
object ViewBoundsTest02 {
def main(args: Array[String]): Unit = {
val p01 = new VBPerson("Jack", 24)
val p02 = new VBPerson("Tom", 19)
println(new PersonCompare(p01, p02).greater) // Jack -> 24
}
}
class VBPerson(val name: String, val age: Int) extends Ordered[VBPerson] {
override def compare(that: VBPerson): Int = {
this.age -that.age
}
override def toString: String = {
this.name + " -> " + this.age
}
}
class PersonCompare[T <% Ordered[T]](obj1: T, obj2: T) {
def greater: T = {
if (obj1.compareTo(obj2) > 0) {
obj1
} else {
obj2
}
}
}
4、视图界定应用案例3(自己写隐式转换【推荐】)
需求:
使用视图界定的方式,比较两个Person对象的年龄大小。
示例代码:
package com.lj.akka.viewbounds
/**
* @author Administrator
* @create 2020-03-26
*/
object ViewBoundsTest03 {
// 自己写隐式转换
implicit def VBPerson02ToOrderedVBPerson02(p: VBPerson02) = new Ordered[VBPerson02] {
override def compare(that: VBPerson02): Int = {
p.age - that.age
}
}
def main(args: Array[String]): Unit = {
val p01 = new VBPerson02("Leo", 28)
val p02 = new VBPerson02("Amy", 34)
println(new VBCompare02(p01, p02).greater) // Amy -> 34
}
}
class VBPerson02(val name: String, val age: Int) {
override def toString: String = {
this.name + " -> " + this.age
}
}
class VBCompare02[T <% Ordered[T]](obj1: T, obj2: T) {
def greater: T = {
if (obj1.compareTo(obj2) > 0) {
obj1
} else {
obj2
}
}
}
四、类型约束:上下文界定(Context Bounds)
1、基本介绍
与view bounds一样context bounds(上下文界定)也是隐式参数的语法塘。为语法上的方便,引入了”上下文界定”这个概念。
2、应用实例
要求:使用上下文界定+隐式参数的方式,比较两个Person对象的年龄大小;
要求:使用Ordering实现比较。
示例代码:
package com.lj.akka.contextbounds
/**
* @author Administrator
* @create 2020-03-26
*/
object ContextBoundsTest01 {
// 定义一个隐式值 Ordering[CBPerson]类型
implicit val cbPersonCompare: Ordering[CBPerson] = new Ordering[CBPerson] {
override def compare(p1: CBPerson, p2: CBPerson): Int = {
p1.age - p2.age
}
}
def main(args: Array[String]): Unit = {
val p1 = new CBPerson("Tom", 25)
val p2 = new CBPerson("Leo", 35)
println(new CBCompare1(p1, p2).greater) // Leo -> 35
println(new CBCompare2(p1, p2).greater) // Leo -> 35
println(new CBCompare3(p1, p2).greater) // Leo -> 35
}
}
// 定义一个普通的CBPerson类
class CBPerson(val name: String, val age: Int){
override def toString: String = {
this.name + " -> " + this.age
}
}
/**
* 方式1:
* 说明:
* 1. [T: Ordering]:泛型
* 2. obj1: T, obj2: T:接受T类型的对象
* 3. implicit comparator: Ordering[T]:是有一个隐式参数
*/
class CBCompare1[T: Ordering](obj1: T, obj2: T)(implicit comparator: Ordering[T]) {
def greater: T = {
if (comparator.compare(obj1, obj2) > 0) {
obj1
} else {
obj2
}
}
}
/**
* 方式2:将隐式参数放到方法内
*/
class CBCompare2[T: Ordering](obj1: T, obj2: T) {
def greater: T = {
def fun(implicit comparator: Ordering[T]) = comparator.compare(obj1, obj2) // 返回一个数字
// 判断函数fun返回的值是否大于零,从而确定返回的是哪个值
if (fun > 0) {
obj1
} else {
obj2
}
}
}
/**
* 方式3:使用implicitly语法塘,最简单(推荐使用)
*/
class CBCompare3[T: Ordering](obj1: T, obj2: T) {
def greater: T = {
/**
* 这句话会发生隐式转换,获取到隐式值cbPersonCompare
* 底层仍然使用编译器来完成绑定(赋值的)工作
*/
val comparator = implicitly[Ordering[T]]
if (comparator.compare(obj1, obj2) > 0) {
obj1
} else {
obj2
}
}
}
小结:Ordered和Ordering的区别
1. Ordering继承了java中的Comparator接口,而Ordered继承了java的Comparable接口。
2. 而在java中的Comparator是一个外部比较器(需要定义一个类来实现比较器),而Comparable则是一个内部
比较器,在类内部重载compareTo函数。
五、协变(covariant)、逆变(contravariant)、不可变(invariant)
1、基本介绍
Scala的协变(+),逆变(-),协变covariant、逆变contravariant、不可变invariant
对于一个带类型参数的类型,比如 List[T],如果对A及其子类型B,满足 List[B]也符合List[A]的子类型,那么
就称为covariance(协变) ,如果 List[A]是 List[B]的子类型,即与原来的父子关系正相反,则称为contravariance
(逆变)。如果一个类型支持协变或逆变,则称这个类型为variance(翻译为可变的或变型),否则称为invariance(不
可变的)。
在Java里,泛型类型都是invariant,比如 List<String> 并不是 List<Object> 的子类型。而scala支持,可以在
定义类型时声明(用加号表示为协变,减号表示逆变),如:
trait List[+T] // 在类型定义时声明为协变这样会把List[String]作为List[Any]的子类型。
2、应用实例
在这里引入关于这个符号的说明,在声明Scala的泛型类型时,“+”表示协变,而“-”表示逆变
C[+T]:如果A是B的子类,那么C[A]是C[B]的子类,称为协变
C[-T]:如果A是B的子类,那么C[B]是C[A]的子类,称为逆变
C[T]:无论A和B是什么关系,C[A]和C[B]没有从属关系。称为不变.
示例代码:
package com.lj.akka.covariantcontravariant
/**
* @author Administrator
* @create 2020-03-26
*/
object Test01 {
def main(args: Array[String]): Unit = {
// 协变:new的对象类型是变量类型额子类或者同类
val t01: Tmp01[Sub] = new Tmp01[Sub]("Hello Tmp01...") // ok
// val t02: Tmp01[Sub] = new Tmp01[Super]("Hello Tmp01...") // error
val t03: Tmp01[Super] = new Tmp01[Sub]("Hello Tmp01...") // ok
// 逆变:new的对象类型是变量类型的父类或者同类
val t04: Tmp02[Sub] = new Tmp02[Sub]("Hello Tmp02...") // ok
val t05: Tmp02[Sub] = new Tmp02[Super]("Hello Tmp02...") // ok
// val t06: Tmp02[Super] = new Tmp02[Sub]("Hello Tmp02...") // error
val t07: Tmp02[Super] = new Tmp02[Super]("Hello Tmp02...") // ok
// 不变:new的对象类型只能是变量类型的同类
val t08: Tmp03[Sub] = new Tmp03[Sub]("Hello Tmp03...") // ok
// val t09: Tmp03[Sub] = new Tmp03[Super]("Hello Tmp03...") // error
// val t10: Tmp03[Super] = new Tmp03[Sub]("Hello Tmp03...") // error
val t11: Tmp03[Super] = new Tmp03[Super]("Hello Tmp03...")
}
}
// 协变(covariant)
class Tmp01[+A](title: String) {
override def toString: String = title
}
// 逆变(contravariant)
class Tmp02[-A](title: String) {
override def toString: String = title
}
// 不变(invariant)
class Tmp03[A](title: String) {
override def toString: String = title
}
class Super
class Sub extends Super
对以前的知识回顾,加深基础知识!
学习来自:北京尚硅谷韩顺平老师—尚硅谷大数据技术之Scala
每天进步一点点,也许某一天你也会变得那么渺小!!!