Interfaces
-
Why do Java method signatures require a type?
public static void discuss(Cat c, Dog d) { d.bark(); c.meow(); d.bark(); d.bark(); }
- Could you make a language that just allowed
public static discuss(c, d)
? - How easy/hard would it be to implement a language that way?
- Actually, there are many languages written that way (python, ruby, JavaScript, python, etc.)
- What is the advantage of requiring the type?
- The compiler can catch more errors.
- For example, you can tell before you even run the program that
c.bark()
is a problem.
- What is the disadvantage?
- More syntax.
- Java, C, C++ and most other compiled languages that require you to specify types are called statically typed.
- In general, a statically typed language won’t let you run the program unless you can verify that (almost) all of the statements are valid.
- Most interpreted languages are dynamically typed: It is up to the programmer to make sure that
objects have the methods that are called. (i.e., given
c.bark()
, it is up to the programmer to make surec
is an object with abark
method.)- If an object doesn’t have the necessary method, it is a run-time failure (i.e., the program crashes)
- Dynamically typed languages are not inferior, it just means that you have to be more thorough when testing.
- The important part of a type in Java is not what the object is, but what it does — which methods can be called.
- In other words, the compiler is primarily concerned with the object’s interface: The formal ways we are allowed to interact with it.
- As an analogy: The LEDs and the set of buttons on your bedroom alarm clock is it’s interface. There’s lots of interesting stuff inside; but, you only look at the numbers and push the buttons. Likewise, there is lots of interesting stuff inside an object; but, when you use it in your code, you only interact with the public interface.
- This is related to the general principle of separating interface from implementation. In theory, you could swap out the guts of your clock with something from a completely different company. But, as long as the buttons worked the same way, it wouldn’t matter. Software applies this principle also.
- In Java, you can leverage this by defining an interface separate from the specific object that implements that interface.
-
For example, let’s generalize the concept of an animal:
public interface Animal { public String speak(); }
- This tells the compiler that anything that is an
Animal
can speak. -
Now, we can tell the compiler which classes implement this interface:
public class Cat implements Animal { public String speak() { return "Meow"; } }
-
The benefit is that we can now write algorithms that no longer care which specific animals we are dealing with:
public static void converse(Animal a, Animal b) { System.out.println(a.speak()); System.out.println(b.speak()); System.out.println(a.speak()); System.out.println(b.speak()); }
- Notice that the
converse
method doesn’t care about the specifics of whata
andb
are, it just needs to be assured that both objects canspeak
. - Also notice that if your variable is of type
Animal
, you may only useAnimal
methods.- Try to call
a.rollOver()
. it will be a syntax error – even if the the object passed toconverse
is actually aDog
.
- Try to call
- Add another
Animal
class and show that VS Code can fill in missing interface methods.
Using interfaces in “real life”
InputStream
and OutputStream
- Many input sources can be abstracted as a “pipe” through which bits flow.
- Imagine writing one letter on a ping pong ball and stuffing it through a pipe. The reader can then pull them out one at a time and re-construct the message.
- This mental model applies to any “stream” of data:
- Keyboard
- File
- Network connection
- Look at https://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html
- It doesn’t matter where the bytes come from as long as you can read them the same way
(i.e., using the same interface)
- (It’s kind of like a large package box at an apartment: You don’t care who puts the package in the box – just that you can take it out.)
- Look at
SumData
in the sample code
GUI Layout
- Look at
OneFileCalculator.java
in the sample code. - Notice that we can put many different types of “widgets” into a
JPanel
(buttons, labels, text fields, etc.) - Look at the API https://docs.oracle.com/javase/8/docs/api/java/awt/Container.html
- The
add
method takes a type ofComponent
. - It doesn’t matter to the
JPanel
whether you are adding a button, label, text field, etc. It only matters that you added something that can draw itself and knows it’s size. - (GUI is much more complicated than this; but, this is the relevant point for now.)
Listeners
- Think about Java Swing listeners. They are a way of telling Java what code to run when you push a button.
-
Conceptually, we just want to pass code to a button as a parameter:
// This is *not* real Java code. I'm trying to illustrate an abstract principle. JButton button = JButton("Push me") button.onClick(System.out.println("You pushed me"); ++numPushes);
- Up until Java 8 there was no way to do this. We could only pass objects.
- So, how to we put code inside objects?
- In a method!
- How does the button know which method to call?
- There has to be a convention / documentation (i.e., it’s just part of the spec – the same way you know which button to push on your radio).
- Let’s construct a simple button (more specifically, an object with a listener)
- Add an
addListener
method.
- Add an
- First issue: Need a type on the parameter to the
addListener
method. What should the type be?- It should be an interface
- As we’re writing the code for the button, we have no knowledge of what the actual code (i.e., “implementation”) will be. We only know the method name. Thus, this is a classic use of an interface. (“There shall be a button. It shall have a
doIt
method. That’s all we care about.) - Add the
ButtonListener
interface. - The challenge is then to keep our listener code from getting “separated” from the rest of our code.
- Consider
MessageButtonListener
- Code is separated from the main “flow” of the app. (Most code is in
main
; with a chunk stuck up inMessageButtonListener
) - There is a lot of typing just to define an object that is only going to be instantiated once.
- Think about abstracting
MessageButtonListener
so you can specify the message. You have to add parameters, instance methods and such – again a lot of typing for a conceptually simple operation.
- Code is separated from the main “flow” of the app. (Most code is in
- To try and take the edge off all this extra typing, Java added things like inner classes and anonymous inner classes.
- Better, but still a lot of typing.
- And confusing for new students.
- The real Java Swing
JButton
does the same thing. Except:- The interface is called
ActionListener
- The method is
actionPerformed
and it takes anActionEvent
parameter
- The interface is called
- Java 8 added “lambdas” which let you pass code as a parameter.
- This syntax is really just interfaces under the hood.
Listeners in the controller
- Putting the listener in the controller is (mostly) the same as putting it in the view – you’re just passing the object through an extra step.
- When using inner classes and lambdas, the key benefit is that you listener has the controller objects in scope instead of the view objects.
- Show
mvcCalculator
in the Sample Code. - Review how Qwixx works.
- With the Qwixx number button listeners, we have a more interesting problem:
- We want a listener on each button
- We don’t want to have to add each listener “by hand” like we did for the “Roll” and “Pass” buttons.
- Instead, we want to use a loop.
- But, the listener needs to know which row and column was clicked.
- This info can’t come as parameters because that simply isn’t part of the
ActionListener#actionPerformed
method. - Cool thing about inner classes / lambdas: You can access variables in the enclosing scope.
- Well, kind of: You can only access them if they won’t change between the time you set up the lambda and when the code actually runs.
- We fis this in
addNumberButtonListener
by copyingrow
andcolumn
(which do change as you move through the loops) intofinal
variables that are local to the body of the loop. - This need to save copies in
final
variables is a quirk of Java. I’m not aware of any other languages with a similar requirement.
- But, notice we are still in the View. How do we transfer control back to the Controller?
- Same idea as the listener. We just need different interface: one that takes
int
s instead of anEvent
object.
- Same idea as the listener. We just need different interface: one that takes
- We could write our own interface; but, this is a common enough need that Java puts together some for us:
- The package
java.util.function
provides a list of interfaces with different method signatures. - Look at
BiConsumer
: It contains a single method calledapply
that takes two ints as parameters — just what we need. - The Controller creates a lambda that takes two ints and passes it to
addNumberButtonListener
. - The method
addNumberButtonListener
can then call this interface’sapply
method with the row and column.
- The package
- It’s not necessary to understand all of this “magic” at this point.
- As long as you understand what code in the controller runs when you click a number button, you can successfully complete the project.
- If you want to understand the “magic”, I’m happy to explain it and re-explain it.
- If you are trying to decide how much time and effort to invest in understanding the magic, consider this:
- Most of the complexity (e.g., the functional interface stuff) is necessary to make the static type checking of the compiler work.
- Doing the same thing in a dynamically typed language like JavaScript is much simpler.
- Of course, the cost of the simplicity in JavaScript is that you are more likely to have run-time bugs.