第 18 章 泛型、上下界、视图界定、上下文界定

一、泛型

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
每天进步一点点,也许某一天你也会变得那么渺小!!!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值