栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

真的学不动了:除了 class , 也该了解 Type classes 了

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

真的学不动了:除了 class , 也该了解 Type classes 了

通过 instance 关键字可以创建类型类实例,下面展示了针对于于 Float 和 Int 的 Eq 类型类实例

instance Eq Int where

(==) = eqInt

(/=) = neInt

instance Eq Float where

(==) = eqFloat

(/=) = neFloat

复制代码

我们假设 eqInt、neInt、eqFloat、neFloat 都已经由标准库实现了

这样就可以直接用 == 和 /= 函数对 Int 和 Float 进行判等了

– 判断 Int 的相等性

== 1 2

/= 2 4

– 判断 Float 的相等性

== 1.2 1.2

/= 2.4 2.1

复制代码

在调用 == 或 /= 函数时,编译器会根据参数类型自动找到类型类实例,然后调用类型类实例的函数执行调用。

如果用户需要自定义判等函数,只需要实现自己的类型类实例即可。

此时你可能会不自觉的和最开始提到的继承方案做一个对比,我画了两个图,可以参考一下

  • 继承方案的类型结构是一个层次型的

  • Type classes 方案的类型结构是线性的

如果仅仅从结构上来看的话,它们之间的差别就像 Comparable 和 Comparator 一样。

[](()Scala 与 Type classes Pattern

===============================================================================================

目前的 Java 是无法实现 Type classes 的,但同为 JVM 的语言,多范式的 Scala 却可以实现。

与 Haskell 不一样, Type classes 在 Scala 中并不是一等公民,也就是没有直接的语法支持,但借助于强大的 隐式系统 我们也能实现 Type classes,由于实现的步骤比较公式化,也就被称之为 Type classes Pattern (类型类模式)。

在 Scala 中实现 Type classes Pattern 大致分为 3 个步骤

  1. 定义 Type class

  2. 实现 Type class 实例

  3. 定义包含 隐式参数 的函数

还是以前面提到的判等问题为需求,按照前面总结的模式步骤来实现一个 Scala 版的 Type classes 解决方案。

第一步定义 Type class,实际就是定义一个带泛型参数的 trait

trait 也类似于 Java 的 interface,不过更加强大

trait Eq[T] {

def eq(a: T, b: T): Boolean

}

复制代码

接着我们针对 String、Int 来实现两个类型类实例

object EqInstances {

implicit val intEq = new Eq[Int] {

override def eq(a: Int, b: Int) = a == b

}

implicit val stringEq = instance[String]((a, b) => a.equals(b))

def instance[T](func: (T, T) => Boolean): Eq[T] = new Eq[T] {

override def eq(a: T, b: T): Boolean = func(a, b)

}

}

复制代码

stringEq 和 intEq 采用了不同的构造方式

  • stringEq 实例我采用的是类似于 Java 的匿名类进行构造

  • intEq 实例则采用了高阶函数来实现

两个实例都被 implicit 关键字修饰,一般称之为 隐式值 ,作用会在后面讲到。

最后一步,来实现一个带隐式参数的 same 函数, 其实调用类型类实例来判断两个值是否相等

object Same {

def same[T](a: T, b: T)(implicit eq: Eq[T]): Boolean = eq.eq(a, b)

}

复制代码

  • implicit eq: Eq[T] 就是隐式参数, 调用方可以不用主动传入,编译器会在作用域内查找匹配的隐式值传入 (这就是为什么前面的实例需要被 implicit 修饰)

最后来进行调用验证一下,在调用时我们需要先在当前作用域内通过 import 关键字导入 类型类实例 (主要是为了让编译器能找到这些实例)

import EqInstances._

Same.same(1, 2)

Same.same(“ok”, “ok”)

// 编译错误:no implicits found for parameter eq: Eq[Float]

Same.same(1.0F, 2.4F)

复制代码

可以看见,针对 Int 和 Strin 《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 g 类型的 same 函数调用能通过编译, 而当参数是 Float 时调用就会提示编译错误,这就是因为编译器在作用域内没有找到可以处理 Float 类型的 Eq 实例。

关于 Scala 隐式查找的更多规则可以查看 docs.scala-lang.org/tutorials/F…

到这儿其实就差不多了,但是这样的写法在 Scala 里其实不是很优雅,我们可以再通过一些小技巧优化一下

  • 将 same 函数改为 apply 函数,可以简化调用

  • 使用 context bound 优化隐式参数,别慌,context bound 实际就是个语法糖而已

object Same {

def apply[T: Eq](a: T, b: T): Boolean = implicitly[Eq[T]].eq(a, b)

}

// 使用 apply 作为函数, 调用时可以不用写函数名

Same(1, 1)

Same(“hello”, “world”)

复制代码

简单说一下 context bund,首先泛型的定义 由 T 变成了 [T: Eq] ,这样就可以用 implicitly [Eq[T]] 让编译器在作用域内找到一个 Eq[T] 的隐式实例,context bound 可以让函数的签名更加简洁。

在 Scala 中,类型类的设计其实随处可见,典型的就有 Ordered 。

[](()回望 Java

==========================================================================

以判等问题引出 Type classes 有一些不足,我们只意识到了与 OOP 的继承是一个不一样的判等解决方案,不妨再回到 Java 做一些其他的比较。

以 Comparator[T] 接口为例,在 Java 中我们经常在集合框架中这样使用

List list = new ArrayList<>();

list.sort(Comparator.naturalOrder())

复制代码

如果将其改造成为 Type classes 的话

trait Comparator[T] {

def compare(o1: T, o2: T): Int

}

object Instances {

// …

}

复制代码

List 的 sort 方法也需要改为带隐式参数的方法前面,这样我们就不需要显示的传 Compartor 实例了

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/845783.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号