Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/scala/19.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
避免Scala内存泄漏-Scala构造函数_Scala_Memory Leaks - Fatal编程技术网

避免Scala内存泄漏-Scala构造函数

避免Scala内存泄漏-Scala构造函数,scala,memory-leaks,Scala,Memory Leaks,我正在阅读《Scala中的编程》一书,在第6章中的类Rational的实现中遇到了一些问题 这是我的Rational类的初始版本(基于本书) 这里的问题是,字段g在类的生命周期内保持不变,即使不再访问。通过运行以下模拟程序可以看到此问题: object Test extends Application { val a = new Rational(1, 2) val fields = a.getClass.getDeclaredFields for(field <- fie

我正在阅读《Scala中的编程》一书,在第6章中的类
Rational
的实现中遇到了一些问题

这是我的
Rational
类的初始版本(基于本书)

这里的问题是,字段g在类的生命周期内保持不变,即使不再访问。通过运行以下模拟程序可以看到此问题:

object Test extends Application {

  val a = new Rational(1, 2)
  val fields = a.getClass.getDeclaredFields

  for(field <- fields) {
    println("Field name: " + field.getName)
    field.setAccessible(true)
    println(field.get(a) + "\n")
  }  

}
我在网站上找到的解决方案包括:

class Rational(numerator: Int, denominator: Int) {
  require(denominator != 0)

  val (numer, denom) = { 
    val g = gcd(numerator.abs, denominator.abs)
    (numerator / g, denominator / g)
  }

  override def toString  = numer + "/" + denom

  private def gcd(a: Int, b: Int): Int =
    if(b == 0) a else gcd(b, a % b)

  // other methods go here
}
在这里,字段g只是其块的本地字段,但是,在运行这个小型测试应用程序时,我发现了另一个字段
x$1
,它保存了由
(numer,denom)
组成的元组的副本

是否有任何方法可以使用上述算法在Scala中构造rational,而不会导致任何内存泄漏

谢谢

Flaviu Cipcigan

您可以这样做:

val numer = numerator / gcd(numerator.abs, denominator.abs)
val denom = denominator / gcd(numerator.abs, denominator.abs)
object Rational {
    def gcd(a: Int, b: Int): Int =
        if(b == 0) a else gcd(b, a % b)
}

class Rational private (n: Int, d: Int, g: Int) {
    require(d != 0)

    def this(n: Int, d: Int) = this(n, d, Rational.gcd(n.abs, d.abs))

    val numer = n / g

    val denom = d / g

    override def toString = numer + "/" + denom

}
当然你得计算两次。但是,优化通常是内存/空间和执行时间之间的权衡

也许还有其他方法,但是程序可能会变得过于复杂,如果有一个地方的优化很少是不成熟的,那就是脑力优化:)。例如,您可能可以这样做:

val numer = numerator / gcd(numerator.abs, denominator.abs)
val denom = denominator / (numerator / numer)
但这并不一定能让代码更容易理解


(注意:我实际上并没有尝试过这个,所以使用它的风险自负。)

伴生对象可以提供您所需的灵活性。它可以定义替换构造函数的“静态”工厂方法

object Rational{

    def apply(numerator: Int, denominator: Int) = {
        def gcd(a: Int, b: Int): Int = if(b == 0) a else gcd(b, a % b)
        val g = gcd(numerator, denominator)
        new Rational(numerator / g, denominator / g)
    }
}

class Rational(numerator: Int, denominator: Int) {
  require(denominator != 0)

  override def toString  = numerator + "/" + denominator
  // other methods go here, neither access g
}

val r = Rational(10,200)

在工厂方法的范围内,g可以计算并用于推导两个构造函数值。

Thomas Jung的示例存在一个小问题;它仍然允许您使用分子和分母中的公共术语创建Rational对象-如果您使用“new”自己而不是通过同伴对象创建Rational对象:

val r = new Rational(10, 200) // Oops! Creating a Rational with a common term
您可以通过要求客户端代码始终使用伴随对象来创建Rational对象来避免这种情况,方法是将隐式构造函数设置为私有:

class Rational private (numerator: Int, denominator: Int) {
    // ...
}
您可以这样做:

val numer = numerator / gcd(numerator.abs, denominator.abs)
val denom = denominator / gcd(numerator.abs, denominator.abs)
object Rational {
    def gcd(a: Int, b: Int): Int =
        if(b == 0) a else gcd(b, a % b)
}

class Rational private (n: Int, d: Int, g: Int) {
    require(d != 0)

    def this(n: Int, d: Int) = this(n, d, Rational.gcd(n.abs, d.abs))

    val numer = n / g

    val denom = d / g

    override def toString = numer + "/" + denom

}

。。。实际上,我不明白这是如何构成“内存泄漏”的

您在类实例的作用域内声明了最后一个字段,然后对它“挂起”感到惊讶。你期望什么样的行为


我是不是遗漏了什么

我偶然发现了这篇文章,您可能会发现它很有用:

看来你可以这样写:

class Rational(numerator: Int, denominator: Int) {
  require(denominator != 0)

  val (numer,denom) = {
      val g = gcd(numerator.abs, denominator.abs)
      (numerator/g, denominator/g)
  }

  override def toString  = numer + "/" + denom

  private def gcd(a: Int, b: Int): Int =
    if(b == 0) a else gcd(b, a % b)

  // other methods go here, neither access g
}
可能是:

def g = gcd(numerator.abs, denominator.abs)

而不是val

同样的问题:谢谢,很抱歉再次问这个问题:)。链接帖子中的答案澄清了我的问题。你确认了
denom
numer
是真正的值吗?如果它们是
def denom=x$1.\u 2
形式的“唯一”访问器方法,我一点也不会感到惊讶。这不是内存泄漏,而是内存开销。谢谢,您的第二个解决方案可以工作(尽管我没有做过任何严格的测试),并且消除了任何多余的字段,开销可以忽略不计。谢谢您的回答,我也在考虑一家工厂,但这会增加一些复杂因素。例如,用户可以调用对象的构造函数(例如,new Rational(10,20))并在此过程中创建一个无效的Rational。可以向构造函数中添加require(gcd(分子、分母)==1),或者使类构造函数私有,并强制用户使用工厂。我不知道什么是最好的。。。工厂对于Rational来说似乎有点过分了:)注意,因为工厂方法的名称是
apply
,所以它可以这样调用:
Rational(10,20)
。这不是过分了——这是正确的答案。这对于scala来说是非常典型的,并且是推荐的模式-private-ctor并使用配套的apply.:)问题是没有明确的方法来定义只在对象构造期间使用的临时变量,就像使用Java构造函数一样。这不是形式上的内存泄漏,因为它仍然可以从全局或局部变量访问(这就是GC不清理它的原因)。这当然是一个非正式意义上的“泄漏”,即数据仍然存在,即使你永远不会需要它。我认为标题的选择有点误导。
def g = gcd(numerator.abs, denominator.abs)