我正在阅读“Scala编程”一书,并在第6章的类Rational
的实现中遇到了一些问题。
这是我Rational
课程的初始版本(基于本书)
class Rational(numerator: Int, denominator: Int) {
require(denominator != 0)
private val g = gcd(numerator.abs, denominator.abs)
val numer = numerator / g
val denom = 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
}
这里的问题是,字段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")
}
}
它的输出将是:
Field: denom
2
Field: numer
1
Field: g
1
我在Scala Wiki找到的解决方案涉及以下内容:
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)
组成的元组的副本!
Field: denom
2
Field: numer
1
Field: x$1
(1,2)
有没有办法用上面的算法在Scala中构造一个理性,而不会导致任何内存泄漏?
谢谢,
Flaviu Cipcigan
答案 0 :(得分:13)
你可以这样做:
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
}
答案 1 :(得分:12)
配套对象可以提供您所需的灵活性。它可以定义一个替换构造函数的“静态”工厂方法。
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可以计算并用于导出两个构造函数值。
答案 2 :(得分:6)
你可以这样做:
val numer = numerator / gcd(numerator.abs, denominator.abs)
val denom = denominator / gcd(numerator.abs, denominator.abs)
当然你必须做两次计算。但是,优化通常是内存/空间和执行时间之间的权衡。
也许还有其他方法,但是程序可能会变得过于复杂,如果有一个地方优化很少为时过早,那就是脑力优化:)。例如,您可能会这样做:
val numer = numerator / gcd(numerator.abs, denominator.abs)
val denom = denominator / (numerator / numer)
但它并不一定使代码更容易理解。
(注意:我实际上没有尝试过,所以请自担风险。)
答案 3 :(得分:6)
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) {
// ...
}
答案 4 :(得分:3)
......实际上,我不知道这是如何构成“内存泄漏”的。
您在类实例的范围内声明了一个final字段,然后显然对它“挂起”感到惊讶。你期待什么行为?
我在这里遗漏了什么吗?
答案 5 :(得分:0)
我遇到过这篇文章,您可能会发现它很有用: http://daily-scala.blogspot.com/2010/02/temporary-variables-during-object.html
看来你可以这样写:
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
}
答案 6 :(得分:0)
可能像:
def g = gcd(numerator.abs, denominator.abs)
而不是val