This page is "part 2" of the UserInput
tutorial, so if you haven't read the first part. I suggest you do before you go any further.
focused on rationalizing the process
of user input, which is notoriously fiddly and verbose, into methods that you can reuse. In this chapter, we plan to show you how to build on that by creating your own utility class (or classes).
There are two sections to this part - a "simple" one, and a more "object-oriented" one:
The first helps beginners to create their own utility class so that they don't have to constantly copy code when they're writing exercises that need user input. It's simple (especially if you read UserInput), but it has some drawbacks.
The second suggests a way to build a more flexible input object.
After which you may be interested in going on to UserInputPartIII
, which shows a way to build a proper input framework
that you can extend and configure any way you like. Needless to say, this part is MUCH longer, and will take more time to write; so if you see a notice at the start of it saying "[under construction]", please be patient.
The simple way - A basic utility class
You will probably have run across utility classes in your lessons already - for example: Math
- and their structure is almost always the same:
They are not instantiated.Their methods are static, so they are called via the class name, eg: Math.log(2.0).
and the method for setting one up always follows the same pattern
There are three things to note about the above declaration:
The class is public, as you will probably want anyone to be able to use it.It has a private no-args constructor. This prevents anyone from accidentally instantiating it.It is also final. This is important because you don't want anyone to be able to extend it. The fact is that, since it can't be instantiated, it can't actually be extended either; but adding the final makes that point clear to anyone using it - ie, it's basically a form of documentation.
So...now what do we do?
Well, the UserInput
page showed you how to set up generic input methods, so now you simply move (note: move, not
copy) them to your new class, viz:
Hopefully, most of the methods should be familiar from the UserInput
page. We've simply transferred them to a class that combines them all, so that they can be used by anyone. I've also added a rangeString()
helper method for creating the "range" part of a message.
Note also the special comments that I've included for all the public methods. These are javadoc
comments, and I advise you to get to know them, because they provide the wonderful documentation you get in the API
And now we change our old class to use the utility methods - ie, something like this:
And you can now use those Input
methods in any class that needs input
Improving the Input class (slightly more advanced)
You may have noticed that there's still quite a lot of duplicated code in our Input
class. The getInteger()
methods, for example, are virtually identical except for the type-specific calls they make. Wouldn't it be nice if we could write ones that simply take the type
of thing we want to convert to?
At this point, a lot of people (especially beginners) immediately start looking at reflection
, because it seems like an answer to all your prayers for dynamic type-checking.
My advice: DON'T.
Reflection can be a very powerful tool in the right situations, but it should be used very
sparingly because it's verbose, error-prone, difficult to test
, and SLOW.
Furthermore, it should only be used in cases where clients really don't
know what a type is going to be until runtime
, and that's not the case here. When we call an Input
method, we DO
know what type we want; we simply want a way to generalise our methods.
So what can we do instead? Answer: Create a class or interface that encapsulates our "type". That's
how Object-Orientation works.
In our case, the only thing that's specific in our input process is the conversion
; everything else is generic. And for types that don't
need converting? Simple: don't convert; just return them.
So, putting that into practise, we might come up with something like this:
and then we can write member types for our Input
class as follows:
and for types that don't
or indeed, just leave it out altogether. After all, a String
can be obtained by simply calling Input.input()
And just in case you're not familiar with the constructs above, they are anonymous classes
- a very useful thing to get to know.
If the above isn't very clear yet, let's see what happens when we put it in our Input
class. Pay particular attention to the get()
methods that replace the type-specific ones we had before:
Note the static
qualifier on the Type
interface. To be honest, I'm not sure it's absolutely necessary, but I always put it in to remind myself that the definition is nested
; and it IS important when you're defining classes
Do you see what's happened? No more overloaded methods, and no (or very little) duplicated code. Furthermore, we can add new member types (for example, a LONG type) as we find a need, and we shouldn't need to change anything else. Indeed, clients can supply their own types
if they want to, by simply writing conversion classes that implement Input.Type
. And that
is what Object-orientation is all about.
And now our calling class will look like this:
If you find the definitions of the generic methods confusing, don't worry about it too much for the moment. The first Input
style will probably do you just fine until you learn a bit more about generics
There is also a slight problem with what we've written so far: The error message. It simply says "Invalid input", when we probably want something a bit more descriptive. This could easily be remedied by adding a getType()
method to our Type
interface, but I'll leave that up to your ingenuity.
It should also be added that this is only ONE way of doing it; there are many others.
However, there are still a few drawbacks to the "utility class" approach. Try and see if you can work out what some of them are before you read on.
Basically, the class is rather brittle, and a lot of that is due to the fact that it can't be instantiated, and its methods are all static
It only allows one prompt prefix: "Please enter".It assumes that messages are always directed to System.out.You have to pass the Scanner to every method. Wouldn't it be nice to be able to set that up once and just use it for every input?It doesn't allow the user to break out of the input process, even if they just "don't get it". It might be nice, for example, to give them a certain number of tries before the class simply gives up and throws an Exception.
All the above can be easily remedied, but you need to use an object
rather than a utility.
A more 'Objective' way - An Input object
Right, so we want to to include the things mentioned above in the "Drawbacks" section. The simplest way to do that is to make Input
a class that we can
instantiate, rather than a utility, and add them as fields that can be set up at construction time. Let's have a look at what that might look like:
Pretty simple, no? The main difference so far is that now our constructors are public
. We still keep the class final
because we really don't want anyone extending it (yet).
And the rest of the Input
class doesn't need to change too much, except that its methods will no longer be static
. We also need to add logic to keep track of the number of tries on each input.
Let's have a look at what it might look like. I've used the second version
as my basis:
Note that we've had to add quite a bit of extra logic to keep track of the number of attempts. This is because the value is independent
of all the other things we're doing, which makes things a bit tricky. However, the "business" methods are still pretty much the same, apart from the fact that they are no longer static
, and we don't have to supply a Scanner to them.
TryCount is a non-static nested class, because it needs to be associated with a specific Input instance, so it can access its maxTries value. There are probably many ways we could have done this, but a mutable "count" object seemed to me the clearest. As you'll see later, there is another approach that is arguably even better.The increment() method throws IllegalStateException when the number of tries is exceeded. An alternative (and probably better) approach is to define your own custom Exception; again, you'll see that in action later.I've changed the internal loops back to the "do forever" (while (true)) style, because we have to increment the count each time we iterate.I've added a description() method to our Type interface, so that error messages are more specific. We could have done this at any point in the development, but now seemed like an opportune time.
And now our calling class will look something like this:
As you see: very little different from before apart from the initial setup.
Phew. A lot
of code. I bet you never thought you would be writing this much back when you started, but I go back to what I said right at the beginning of this tutorial:
User input is tough
It's all in one place, so it can be used any time you need an input value.You never need to write it again.(Very important) It's NOT in main() any more.A lot of the bulk is documentation, which I strongly urge you to do as you write.We've developed the class incrementally.
However, we're still
not finished yet. There's no doubt that our input object is a lot more flexible than before, but that "try count" logic is pretty cumbersome - and kind of ugly.
shows you how to adapt what we've written into a proper input framework
, but it does assume some knowledge of Java Generics
. If you don't feel you're quite ready yet, you should
still be able to use what you've read so far to write a decent utility class that will last you until you're ready for the "next stage of evolution".