【问题标题】:Returning a list of inner class objects返回内部类对象的列表
【发布时间】:2018-09-26 13:12:32
【问题描述】:

我从 Java 转向 Scala。我想创建一个Outer 类,它定义(1)一个内部Inner 案例类和(2)一个返回Inner 对象列表的方法。请注意,在 Java 中,内部类的类型无疑是 Outer.Inner。

我发现有两种方法可以引用内部类的类型:outerObject.Inner 或Outer#Inner,正如here 所讨论的那样。 我应该使用哪个来返回 Inner 对象的列表?

例如,我可以这样实现我的 Scala 代码:

// Implementation 1
class Outer {
    case class Inner(id: String)
    def build(input: List[String]): List[Inner] = {
        var result = scala.collection.mutable.ListBuffer[Inner]()
        input.foreach { s =>
            val inner = Inner(s)
            result += inner
        }
        result.toList
    }
}

或者像这样:

// Implementation 2
class Outer {
    case class Inner(id: String)
    def build(input: List[String]): List[Outer#Inner] = {
        var result = scala.collection.mutable.ListBuffer[Outer#Inner]()
        input.foreach { s =>
            val inner = Inner(s)
            result += inner
        }
        result.toList
    }
}

唯一的区别是build() 返回List[Inner] 或List[Outer#Inner]。

假设我的代码的调用者运行这个:

val input: List[String] = List("A", "B", "C")
val outer = new Outer()
val output = outer.build(input)

那么对于实现 1,调用者看到输出是:

output: List[outer.Inner] = List(Inner(A), Inner(B), Inner(C))

对于实现 2,调用者看到:

output: List[Outer#Inner] = List(Inner(A), Inner(B), Inner(C))

Scala 中的首选(惯用)方法是什么?

相关问题:

【问题讨论】:

    标签: scala


    【解决方案1】:

    欢迎来到 Scala!

    首先,有一种更简洁、更有效的方式来创建您的实现,我将在下面展示。

    // Implementation 1
    class Outer {
      case class Inner(id: String)
      def build(input: List[String]): List[Inner] = input.map(s => Inner(s))
    }
    
    // Implementation 2
    class Outer {
      case class Inner(id: String)
      def build(input: List[String]): List[Outer#Inner] = input.map(s => Inner(s))
    }
    

    使用map 意味着您不需要任何丑陋的var 元素,也不需要构建ListBuffer 然后将其转换为List。

    至于哪个更惯用,这取决于您要做什么。第一种形式返回Inners 的path-dependent 列表。可以限制它与创建它的Outer 实例一起使用。在第二种形式中,返回的Inner 的类型不能绑定到特定的Outer 实例,但可以在任何任何 Inner 实例可接受的地方使用。因此,后者的限制性更强,而前者的限制更少。考虑以下几点:

    // Implementation 1
    class Outer {
      case class Inner(id: String)
      def build(input: List[String]): List[Inner] = input.map(s => Inner(s))
      def getId(inner: Inner): String = inner.id
    }
    
    // Implementation 2
    class Outer {
      case class Inner(id: String)
      def build(input: List[String]): List[Outer#Inner] = input.map(s => Inner(s))
      def getId(inner: Outer#Inner): String = inner.id
    }
    

    然后尝试对每个实现评估以下内容:

    val input: List[String] = List("A", "B", "C")
    val outer = new Outer()
    val output = outer.build(input)
    val outer2 = new Outer()
    outer2.getId(output.head)
    

    使用 implementation 1,您会在最后一条语句中收到类型不匹配错误,但使用 implementation 2 不会。

    更新:我忘了提这个(可能是为了避免进一步混淆),但是path-dependent 版本是通用版本的子类型。 (即this.Inner 是Outer#Inner 的子类型。)因此您可以将Inner 的path-dependent 版本传递给需要any @ 的函数987654337@,但不是相反。

    也就是说,以下将起作用:

    class Outer {
      case class Inner(id: String)
      def build(input: List[String]): List[Inner] = input.map(Inner)
      def getId(inner: Outer#Inner): String = inner.id
    }
    
    val input: List[String] = List("A", "B", "C")
    val outer = new Outer()
    val output = outer.build(input)
    val outer2 = new Outer()
    outer2.getId(output.head)
    

    但这不会:

    class Outer {
      case class Inner(id: String)
      def build(input: List[String]): List[Outer#Inner] = input.map(Inner)
      def getId(inner: Inner): String = inner.id
    }
    
    val input: List[String] = List("A", "B", "C")
    val outer = new Outer()
    val output = outer.build(input)
    val outer2 = new Outer()
    outer2.getId(output.head)
    

    (请注意,我还在每个方法中都使用了更简洁的build 方法。)

    所以,总结一下……

    返回新 Inner 实例的函数(属于 Outer)应该返回 path-dependent 形式,因为这也是通用形式的子类型,并提供最大的灵活性. (但是,请记住,可以从 Outer 实例的控件之外创建可公开访问的 Inner 类实例,并且可以是调用者需要的任何类型。)

    然而,更重要的是在接受现有 Inner 实例时使用的类型签名:

    • 如果操作(属于Outer 实例)在Inner 实例和自身之间具有强耦合(并且Inner 实例通常会保持对其Outer 实例的引用),这样提供属于不同 Outer 实例的 Inner 将是错误的,那么您应该只接受 path-dependent 形式。

    • 但是,如果操作适用于 any Inner 实例,包括接受方法根本不属于 Outer 的情况,那么它应该接受泛型表格。

    【讨论】:

    • 谢谢。 Scala 的内部类路径依赖列表有什么优势?这能解决什么问题?
    • @stackoverflowuser2010 在这个特定的示例中,使用 pathdependent 表单显然没有明显的好处。事实上,它可能导致的问题多于解决的问题。但是,每个Inner 也链接到它所属的Outer 实例。假设Outer 具有Inner 正在通过该引用进行操作的其他数据成员。如果您将由一个Outer 实例创建的Inner 传递给另一个Outer 实例,它仍在访问原始Outer 的成员,这可能会引起混淆。
    • @stackoverflowuser2010 请参阅 this answer 以获得一个很好的例子。
    • @stackoverflowuser2010 还有另一种通过type 语句声明path-dependent 类型的方法,它不一定依赖于内部类。请参阅 Odersky、Spoon 和 Venners 所著的Scala 编程第三版的第 20 章(尤其是 20.7 章)。
    • @stackoverflowuser2010 为所有额外的 cmets 道歉! :-) 只是想让您知道我更新了答案以更好地解释何时使用每种类型。在实践中,做出必要的区分应该很容易。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-21
    • 1970-01-01
    相关资源
    最近更新 更多