1

I would like to check constructor arguments and refuse to construct throwing IllegalArgumentException in case the arguments set is not valid (the values don't fit in expected constraints). And same check should work while setting same argument set while modifying the object. How to code this in Scala?

  scala> class Rectangle (var Length:Double, var Width:Double){
   | if (Length <0)
   | throw new IllegalArgumentException("The Length must be Non-negative")
   | if (Width <0)
   | throw new IllegalArgumentException("The Width must be Non-negative")
   | def setLength(l:Double) = Length = l
   | }
  defined class Rectangle

  scala> var R = new Rectangle (-9, -9)
  java.lang.IllegalArgumentException: The Length must be Non-negative
at Rectangle.<init>(<console>:9)

  scala> var R = new Rectangle (0, -9)
  java.lang.IllegalArgumentException: The Width must be Non-negative
at Rectangle.<init>(<console>:11)



   scala> var R = new Rectangle(9, 9)
   R: Rectangle = Rectangle@1590164


   scala> R.Length
   res7: Double = 9.0

   scala> R.Width
   res8: Double = 9.0

   scala> R.setLength(18)

   scala> R.Length
   res10: Double = 18.0

   scala> R.setLength(-9)
   // R.setLength should not the set the Length to -9.   **************************
   scala> R.Length
   res12: Double = -9.0
Optimight
  • 2,989
  • 6
  • 30
  • 48

4 Answers4

2

You need a property setter/getter methods.

class Rectangle(private var l: Int, private var w: Int) {
  require(l > 0, "Invalid length: " + l + " <0 ")
  require(w > 0, "Invalid width: " + w + " <0 ")
  def length = l
  def width = w
  def length_=(len: Int) { require(len > 0, "Invalid length: " + len + " < 0"); l = len }
  def width_=(wid: Int) { require(wid > 0, "Invalid width: " + wid + " < 0"); w = wid }
  override def toString = length + ", " + width
}
Prince John Wesley
  • 62,492
  • 12
  • 87
  • 94
2

Must it be mutable?

case class Rectangle(length:Double, width:Double) {
  if (length < 0)
    throw new IllegalArgumentException("The Length must be Non-negative")
  if (width < 0)
    throw new IllegalArgumentException("The Width must be Non-negative")
  def setLength(l: Double) = copy(length=l)
}
Debilski
  • 66,976
  • 12
  • 110
  • 133
  • Yes. It must be mutable.@Debilski – Optimight Jun 02 '12 at 13:49
  • From where can I learn more about copy(length=l) function/operator? Any article / links /pdf? – Optimight Jun 02 '12 at 14:15
  • @user1417244 you can use `scalac -Xprint:typer`. For example: `echo 'case class Rectangle(length:Double, width:Double)' > test.scala && scalac -Xprint:typer test.scala`. You'll get this: `def copy(length: Double = length, width: Double = width): Rectangle = new Rectangle(length, width)` – senia Jun 02 '12 at 14:46
  • @senia Thank you very much. Great Help. Should I get details of such command(s) from online scala documentations? – Optimight Jun 02 '12 at 16:04
  • @Debilski Mutable or Immutable - What is preferred? In my imaginations the I am relating a real life Rectangle with the instance of Rectangle (object) created in my system. I just want to change the values of the instance as per the change in length and width of real life Rectangle. Your answer is creating another instance of Rectangle. What is the best strategy - should I change the reference of Rectangle (RealLife) to another instance? – Optimight Jun 02 '12 at 16:11
  • @user1417244 You can get it from `scalac -help` or here: http://www.scala-lang.org/docu/files/tools/scalac.html . – senia Jun 02 '12 at 17:13
  • @user1417244 Short answer: use immutable while you can. There are plenty of papers on this topic, for instance [on stackoverflow](http://stackoverflow.com/questions/8014750/what-are-the-real-advantages-of-immutable-collections) and ["Immutable object trade-offs" in "Programming in Scala"](http://www.artima.com/pins1ed/functional-objects.html#6.2) – senia Jun 02 '12 at 17:28
2

Mutable state is a bad idea, but if you do need it and don't want two points of validation, you can use a slightly modified solution of Prince John Wesley:

class Rectangle(l: Int, w: Int) {

  private[this] var _length: Int = _
  private[this] var _width: Int = _
  def length: Int = _length
  def width: Int = _width
  def length_=(len: Int) { require(len > 0, "Invalid length: " + len + " < 0"); _length = len }
  def width_=(wid: Int) { require(wid > 0, "Invalid width: " + wid + " < 0"); _width = wid }

  length = l
  width = w

  override def toString = length + ", " + width
}
senia
  • 37,745
  • 4
  • 88
  • 129
  • Your code works absolutely fine. Can you guide what does '_ (underscore)' thing means in "_length" & "length_" ? From where can I learn more about them? – Optimight Jun 03 '12 at 00:27
  • @Optimight It's an initialization to the default value. See [this answer on stackoverflow](http://stackoverflow.com/a/8001132/406435). – senia Jun 03 '12 at 04:38
2

I don't like the answers mentioned so far. Your problem is that parameters must be valid to create an object. The other solutions abort object creation but in my opinion what you really want is not an abortion but some checks before creation of the object. To realize this you need factories:

object Rectangle {
  def apply(length:Double, width:Double): Rectangle = {
    require(length >= 0, "The Length must be Non-negative")
    require(width >= 0, "The Width must be Non-negative")
    new Rectangle(length, width)
  }
}
class Rectangle private(val length:Double, val width:Double) {
  def length_=(l: Double) = Rectangle(l, width)
}

The only problem here is, that we are not allowed to create a case class when we want to define our own apply-Method (with same method signature as the class constructor).

kiritsuku
  • 52,967
  • 18
  • 114
  • 136
  • I tried code in your answer. But it allows to create new Rectangle (-1,-1), which should not happen due to non-negative arguments. Kindly guide. – Optimight Jun 03 '12 at 00:22
  • Your point - Object creation should not aborted is absolutely valid and most desirable. Instead of new Rectangle(-1,-1) ... var Rect = Rectangle(-1, -1) works fine as desired. – Optimight Jun 03 '12 at 02:33
  • @Optimight: Yes, my fault. That should not work. I set the ctor to private. – kiritsuku Jun 03 '12 at 08:41
  • After defining Class and Object ...when I tried to create new Rectangle I got: That entry seems to have slain the compiler. Shall I replay your session? I can re-run each line except the last one. Please guide. – Optimight Jun 03 '12 at 09:35
  • This seems to be a bug in the compiler. I got it, too. In 2.10 it is fixed. Try something like: `val r = new Rectangle(-1, -1)` – kiritsuku Jun 03 '12 at 10:00