RU beehive logo ITEC dept promo banner
ITEC 120
2008spring
ibarland,
jdymacek

homeinfoexamslectureslabshws
RecipeLawsliessyntaxjava.lang docsjava.util docs

lect07b-soln
Returning objects from methods (pt. I)
the design recipe reminded

We have seen that an object can take in an objects as a parameter: In lecture we wrote a method for PizzaServers, namely boolean hasHigherSalaryThan(PizzaServer rival). Then in lab, we wrote methods for class Date: first boolean isEqual(Date other) and then boolean comesBefore(Date other).

We can also write methods where we return an objects1. For example, let's write a function for a PizzaServer which takes in two possible work-Dates, and decides which one they prefer. For example, given the choice of working 2007.Feb.21 and 2007.Feb.28, a particular PizzaServer might have a preference for 2007.Feb.21.

Let's write this method together, using the steps of The Design Recipe. In addition to solving this particular problem about choosing a shift, what we're really trying to teach is the recipe. The same recipe guides you through all problems, even those which are much more complicated than the one we work on here.
See the lect07b-soln.html, for how each step corresponds to our problem at hand.

  1. Data analysis — What are the pieces of information at hand, for this problem, and which data types should be used to represent them in the program? (e.g., int, String, boolean, ...?)
    Solution:

    This function involves only two types of information: Dates, and PizzaServers. We have already developed classes to represent each of those, so we're done.

    However, if we were starting a problem from scratch, this is an interesting and essential step, to decide what what information will be represented with primitive types, and what information will use an entire class.)

  2. Examples of the data. Make a bunch of example instances.
    (If using BlueJ with unit test tools enabled, you can create these these instances by right-clicking on the class; then you can right-click on the test-class and select “Bench to test fixture”.
    Solution: We have already done this, for both PizzaServer and a Date. Here is a .jar file for a BlueJ project containing code and test cases for both PizzaServer and Date. (It is admittedly new to have a single project with two classes inside it, but we won't be requiring this for any upcoming homeworks.)
    (How to download .jar files into BlueJ)
  3. Signature — for each method (function), specify its name, what input parameters it needs (name and type), and what type of information it will return. (See our mantra for signatures)
    Solution:

    First: which class, does this method belong in -- is it a question we'll ask of PizzaServers, or of Dates? This is a question we ask PizzaServers, so it will be a method (behavior) of that class.

    What are the inputs types? (How many inputs?) What is the type of answer which will be given?

    	      Date whichDayToWork( Date d1, Date d2 )
    	  
    Of course, the choice of names is ours; try to be descriptive. (Imagine somebody who hasn't been thinking about this problem already, reading just the name of your method. Is the name helpful to them? Note that dayPreferred doesn't clue anybody in as to what the day is preferred for. Somebody might say their birthday is their preferred day, only to be duped into having to work on their birthday !-)

  4. Comments (written in javadoc). Describe what each parameter means (for example “the price of the item, in dollars”, or “the number of students officially enrolled in the course”) as well as the meaning of the return value.
    Solution:
      /** Which Date does this PizzaServer prefer for their next shift,
       *    given a choice of two upcoming Dates.
       * @param d1 The first  date to consider working on.
       * @param d2 The second date to consider working on.
       * @return which Date is preferred for this PizzaServer's next shift.
       *       It will be one of d1,d2.
       */
           
    Observe how the last line of the @return is actually the first place we ever mention that the answer will be one of the two inputs. (We may have been thinking this is a requirement of the problem, so it's good to make that thought explicit.)
  5. Complete the stub function, and compile. Note that you still haven't yet written any code which computes a solution.
    Solution:
    Date whichDayToWork( Date d1, Date d2 ) {
      return d1;  // stub
      }
    	 
    (What are other ways we might return a Date, for this stub function?)
  6. Test cases: Write 2-3 test cases (or more, as appropriate).
    Solution:

    We tell BlueJ to make a new test case2 The test suite included Date variables named today (that is, 2007.Feb.20, if you inspect the object), nextMonday (that is, 2007.Feb.26), nextFriday (that is, 2007.Mar.03), and moonLanding (that is, 1969.Jul.20). (Note how using descriptive variable names, back when we created the examples-of-the-data, has come in handy now. If we'd just named them all “date1”, “date2”, … it we be harder to use them now.

    We start by asking one of the two (brand-new) PizzaServers if they'd prefer to work (say) nextMonday or nextFriday. But what will the correct answer be? Note how this is the first moment where we actually start thinking closely about the correct answer!

    Sometimes, correctness is clear from context. If it's ever unclear, go back and ask your boss, or whoever had you write this program! In this case, where you might be writing SimPizzaServer&tm; your boss replies:

    After a thorough review of the Journals of PizzaServer Psychology, it has been determined that:
    If a PizzaServer has a balance of less than $100, then they prefer to work sooner, given two options. Otherwise, they prefer the later date.

    Thus: pizzaSer1's balance is currently $0.00, so pizzaSer1.whichDayToWork( nextMonday, nextFriday ) should return nextMonday as the correct answer. Go ahead and assert this, using BlueJ's as one of our tests. (Note how we can use variables like “nextMonday” as inputs, and to compare against outputs.)

    Another test case to ask: pizzaSer1.whichDayToWork( nextFriday, nextMonday ). Clearly this should give the same answer as before; we want to make sure our method (once it's working) isn't sensitive to which order its inputs come in on.

    What are some degenerate inputs to try? Is there a "0" value, for a Date? Not really, but what bout Dates which aren't valid? Dates in the past? Hmm, either of these would make the question absurd; we expect our inputs to be valid dates which are still in the future. It's fine to presume that3, but say so! Thinking about degenerate inputs makes us realize we need to modify our javadoc:

       * @param d1 The first  date to consider working on.  Must be a valid date, not in the past.
       * @param d2 The second date to consider working on.  Must be a valid date, not in the past.
    	 

    Here are four tests, all of which are stressing a slightly different aspect of the problem:

    assertEquals(nextMonday, pizzaSer2.whichDayToWork(nextMonday, nextFriday));
    assertEquals(nextMonday, pizzaSer2.whichDayToWork(nextFriday, nextMonday));
    assertEquals(nextMonday, pizzaSer2.whichDayToWork(nextMonday, nextMonday));
    	   
    Are we done? Definitely not!
    What entire category of tests have we not even considered?

    Yep, we need to consider PizzaServers who have money in the bank.

    pizzaSer2.work(50);
    assertEquals(nextFriday, pizzaSer2.whichDayToWork(nextMonday, nextFriday));
    assertEquals(nextFriday, pizzaSer2.whichDayToWork(nextFriday, nextMonday));
    assertEquals(nextMonday, pizzaSer2.whichDayToWork(nextMonday, nextMonday));
    	   
    These six tests are all necessary, for a comprehensive test suite.
    (You are welcome to have tests involving today, although don't be too misled by that variable's name: our programs don't actually have any idea of what day it actually is4, so whether we use a variable named today or a variable named nextMonday is irrelevant.

  7. The body of the function. This is the only non-automatic part of the process. Reflect on the test cases you worked through by hand: how did you get from the input to the output? What is the general case?

    Things to help you on your way:

    Often, the body of the function is just a return statement, although you might also name partial results using local variables, and use an if statement which contains a return statement or names partial results.


    Solution:

    Clearly, we want to use an if to determine what to return, because our answer is entirely dependent on whether or not we have $100 or more in the bank:

    Date whichDayToWork( Date d1, Date d2 ) {
      if (this.getBalance() < 100) {
        // ... d1.comesBefore(d2) ...
        }
      else { /* balance >= 100 */
        // ... d1.comesBefore(d2)...
        }
      
      }
    
    It's easier to write in the full skeleton (all the curly-brackets and the else branch) right now, before we start refining the innards of each case.

    Okay, let's tackle each of the two cases separately.

    1. First consider the case that we have less than $100. In that case, our answer is “the sooner of d1, d2”. (Our test cases earlier made us realize that d1 isn't necessarily sooner than d2.) So, how do we figure out which day comes sooner? We have sitting in front of us the true/false expression, d1.comesBefore(d2). Oh, knowing that whether d1 comes before d2 lets us pick out the one which comes sooner:
      if (d1.comesBefore(d2)) {
        return d1;
        }
      else {
        return d2;
        }
      	 
      That wasn't so bad at all. Sticking this into the less-than-$100 case gives us
      Date whichDayToWork( Date d1, Date d2 ) {
        if (this.getBalance() < 100) {
          if (d1.comesBefore(d2)) {
            return d1;
            }
          else {
            return d2;
            }
          }
        else { /* balance >= 100 */
          // ... d1.comesBefore(d2)...
          }
        }
      
      Note that our indentation is crucial, to making sure we aren't missing any open/close curly-braces. (Having filled in the skeleton earlier is now paying off.)
    2. Now, what if we have less than $100? That situation will be the complement; we want an if statement which calculates the later of the two Dates:
          if (d1.comesBefore(d2)) {
            return d2;
            }
          else {
            return d1;
            }
      
      Sticking this last piece of the puzzle into our skeleton gives:
      Date whichDayToWork( Date d1, Date d2 ) {
        if (this.getBalance() < 100) {
          if (d1.comesBefore(d2)) {
            return d1;
            }
          else {
            return d2;
            }
          }
        else { /* balance >= 100 */
          if (d1.comesBefore(d2)) {
            return d2;
            }
          else {
            return d1;
            }
          }
        }
      
    If we've been careful with each step, this code should at least compile. By slowly considering each case in order, we've finished the problem without ever having had to think too hard all at once.

    solution: PizzaServer.java, or the complete project (.jar). (See how to get .jar files into BlueJ.)

  8. Test — run the test cases you already wrote down from step 2. Does your program give the results you expect?
    Solution: Woo-hoo, all test cases pass! (If that weren't the case, we'd look at the first test which failed, and then step through the code line-by-line for that input (perhaps using a debugger), and see where our idea of a correct computation diverges from what the computer actually does.
  9. Refactor — review and (as necessary) rewrite your code. Does it obey the The Laws of Programming?
    Solution: Two problems with the current code:
    • 100 is a “magic number”. (See below)

      1. To do on your own Make $100 into a named constant.
      2. To discuss with a classmate: what if every PizzaServer had their own personal idea of how big a bank-balance is needed, before working becomes a lower priority. Discuss: How might we (re)write our Java program to take this into account?

    • The second problem with this code is not exactly repeated code, but… there is a task which is embedded into the middle of our code, which is begging to be made its own function: Given two dates, we have an if statement which computes which one comes sooner. (And another if statment to determine which one comes later.) The fact that we have rather ugly-looking nested ifs is a clue that we could pull that question into its own function.
      On your own:
      1. write a Date method sooner, which walks up to a Date, hands it a second Date, and the method gives back the sooner of those two Dates. (This is like min, but handling Dates rather than numbers.5) Follow all the steps of the design recipe.
      2. Modify whichDayToWork, to call sooner. This will (a) simplify the code, and (b) simplify any future situations, where you need to obtain the smaller of two Dates.
      3. Repeat the preceding two steps for the complementary notion later.

Returning new objects

Write a method nextDay. Use the recipe above. Note that steps I and II are already done; those steps apply to classes, and don't need to be repeated for further methods of a class.

First get a method working which may occasionally return an invalid date, but otherwise is correct.

Then, get a method which handles the difficult cases. Hint: Make the obvious candidate, and see if it's valid. If not, then make a second candidate, and check if that's valid. If not, then a third try should do the trick.
This will be tomorrow's lab.

(optional) the conditional operator

Java's if-else is a statement -- it is on its own line(s), and it will happen before or after other statements, but it can't be combined as a sub-part of larger statements. This is different than expressions, like “Math.sqrt(16)” or “(a && b)”, which might occur as sub-expressions of the larger expression “(Math.sqrt(16) > x) || (a && b))”.

It turns out, there is a statement form of if-else, using the punctuation “?” and “:”.

conditonal-operator ::= condition ? expressiontrue : expressionfalse
For example, if a Dates have a predicate isLeapYear, and d is an instance of Date, then the following expression evaluates to the number of days in February:
d.isLeapYear() ? 29 : 28
This can be combined into other expressions, like the right-hand-side of an <= expression:
d.getDay() <= (d.isLeapYear() ? 29 : 28)
And of course, we already know that a return statement can return any expression:
  // ...inside isValid:
  else if (this.getMonth()==2) {
    return this.getDay() <= (this.isLeapYear() ? 29 : 28)
  }
  else // ... consider other months besides February

Note that the conditional operator must contain answer-expressions for both condition-being-true and condition-being-false. (This is different from if, which can legally be written without an else.)

We can use the conditional operator to make the above code more concise:

Date whichDayToWork( Date d1, Date d2 ) {
  if (this.getBalance() < 100) {
    return (d1.comesBefore(d2)) ? d1 : d2;
    }
  else { /* balance >= 100 */
    return (d1.comesBefore(d2)) ? d2 : d1;
    }
  }
In this case, we are combining the conditional-operator into the expression portion of the return-statement (whose syntax is return expression).

Challenge exercise: re-write the above statement starting with the decision of which date comes first, and (in each case) asking about what the balance is:

Date whichDayToWork( Date d1, Date d2 ) {
  if (d1.comesBefore(d2)) {
    return                  ?         :       ;
    }
  else { /* d1 doesn't come before d2 */
    return                  ?         :       ;
    }
  }

Although most Java programmers tend to use if most of the time, the conditional expression is actually extremely handy, and you are encouraged to use it if you're comfortable with it.


1 Okay yes, the Constructor is already a method which returns an object, although we didn't actually write the return statement ourself.      

2Right-click on the test class, to find the Create Test Method... option.      

3Depending on whether your function will be called by other programmers or by end-users, and depending on how paranoid you are, you may want to actually check for these things. (See java's assert statement.) Ideally, just as we use type-declarations to help catch errors earlier (at compile time) it'd be nice to be able to specify the input type “valid, non-past Dates”, and have Java check for that automatically. (There are limits to trying to confirm this at compile time, but even so I'd like Java to automatically catch this error at run time and correctly blame the calling function. Type checking and run-time asserts are two partial solutions; it would be nicer if a language could even do more (even in the presence of inheritance).)      

4Well, we could call (new java.util.Date()), but that doesn't really affect our testing.      

5It would also be a great method to make static, if we'd learned about that yet. You see, the notion of the miminum is symmetric, and yet our code is asymmetric: we choose one of those two Dates to walk up to, and we pass it the other Date as a parameter. Really, like min, we'd like to just walk up to the Date class (the blueprint), and hand it two Dates, and have it decide which one comes sooner. It's an aesthetic offense, to have an asymmetric method computing a symmetric function.      

homeinfoexamslectureslabshws
RecipeLawsliessyntaxjava.lang docsjava.util docs


©2008, Ian Barland, Radford University
Last modified 2008.Feb.27 (Wed)
Please mail any suggestions
(incl. typos, broken links)
to iba�rlandrad�ford.edu
Powered by PLT Scheme