避免Scala内存泄漏-Scala构造函数
我正在阅读《Scala中的编程》一书,在第6章中的类避免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
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)