Some programming languages provide a feature with the rather strange name “duck typing”. Unfortunately, Java is not one of them. In this post, I am going to briefly explain what duck typing is, and how to achieve at least a similar effect using the method references introduced in Java 8.
The concept of duck typing is probably best known from JavaScript. The term “duck typing” probably originated in a poem by the American author James Whitcomb Riley, which says:
“When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.”
What does that have to do with programming? Let us envision a function that expects a duck object as a parameter. In this function, we want to call up the duck’s “quack” method. In many cases, we do not actually care whether the object submitted really is a duck, as long as it has a “quack” method with a corresponding signature. In duck typing, the question “Is it a duck?” is not, in fact, determined by the actual type of the object itself, but rather by the object’s properties.
The AJAX-Funktion von JQuery constitutes another, more realistic example. It expects an argument that is an object containing a url value and a success callback (over-simplified; in fact, there are numerous other options in JQuery).
Java does not support duck typing. The method signature has to specify the correct type for each argument. For example, an argument has to implement the “duck” interface. It is not enough for a class to have the same methods that are provided for in the interface; no, it is necessary to actually write implements Duck. The following example, which is based on the JQuery AJAX example, illustrates this:
public interface Request {
String getUrl();
void callback(String result);
}
public class MyRequest implements Request {
public String getUrl() {
return "http://blog.saxsys.de";
}
public void callback(String result) {
System.out.println(result);
}
}
public void ajax(Request request) {
...
}
I defined a Request interface and an implementing class called MyRequest. The getUrl method returns the target address, and our AJAX framework calls the callback method as soon as the response is received. As the ajax method requires an argument of the Request type, I can use an instance of MyRequest without difficulty. If, for example, I have the following class, the situation is entirely different:
public class OtherRequest {
public getUrl(){
return "http://stage-sgs.dsinet.de";
}
public void callback(String result) {
System.out.println(result);
}
}
Even though this class also has the two required methods with an identical signature, the ajax method would refuse an instance of this class because the class does not implement the Request interface. This can often be very annoying, especially if you cannot modify a given class yourself, i.e. you cannot simply add implements Request, e.g. if it comes from a third-party library.
Ad-Hoc implementation of interfaces
Some other programming languages handle this differently: Haskell, for example, does not have “interfaces” like Java, but Haskell’s “typeclasses” are more or less equivalent to interfaces. And here you can add types to any typeclass. at a later time, irrespective of the type definition. Thus, I could add my OtherRequest type to the Request typeclass without having to modify the source code of OtherRequest. After which, I can use OtherRequest values as parameters for functions that require the Request type.
Let us look at an example:
class Request r where
getUrl :: r -> String
ajax :: (Request r) => r -> IO ()
ajax r = do print $ getUrl r
This defines a Request type class with a getUrl function that receives a specific request and returns a string. Consequently, every data type added to this type class has to provide such a function. Among them is the ajax function that receives a request and returns an IO action. The implementation shown here is merely a placeholder that does not actually execute an AJAX request, but only displays the URL in the command line. For this purpose, it can use the getUrl function because the latter has been defined in the type class. Thus, this corresponds to our AJAX library code.
In our program, we can now define our own data type for our request. In order to be able to use the AJAX framework, we have to add our data type to the “Request” type class:
data OtherRequest = OtherRequest {url::String} deriving (Show)
instance Request OtherRequest where
getUrl = url
x = OtherRequest "http://www.saxsys.de"
ajax x
The data keyword is used to define a new type in Haskell, which in our case is called “OtherRequest” and has a “url” string. With this, Haskell generates a “url” function with the signature OtherRequest -> String, i.e. it receives an OtherRequest value and returns a string.
Instance adds our type to the type class. The line “getUrl = url” means that the “url” function of our type is to be used if “getUrl” is called by the type class. We could also specify a different implementation at this point. This is similar to the situation in Java, where a method prescribed by an interface is implemented in a class.
The end of the code segment shows how we can use this. We create an “OtherRequest” value and label it “x”. Then, the “ajax” function is called with x as an argument. Since we have added “OtherRequest” to the “Request” type class, this call works as expected.
As Haskell is not an object-oriented language, this is not completely equivalent to Java. But the important point is that defining our type and adding it to the type class (i.e. implementing the interface) are two independent processes that can be executed in independent sections of the code. Even if the “OtherRequest” type came from a third-party library, we could still add it to the type class we need in order to be able to use it for our program. Therefore, this option is a good substitute for duck typing in terms of extensibility, in particular because it is even possible to abstract from the specific name (see “getUrl = url”).
Dynamic or static typing
Duck typing is usually primarily mentioned in connection with dynamically typed languages such as JavaScript. With those, objects are checked for the necessary methods and attributes at runtime. But duck typing is also possible with static typing. In that case, the verification of whether an object “is a duck” is done at the compile time. Typescript is a good example. Our Request example above could look like this in TypeScript:
interface Request {
getUrl(): string
callback(result: String)
}
class MyRequest implements Request {
getUrl() {
return "http://example.org";
}
callback(result: String) {
console.log(result);
}
}
class OtherRequest {
getUrl() {
return "http://example.org";
}
callback(result: String) {
console.log(result);
}
}
function ajax(request: Request) {
}
var r1 = new MyRequest();
var r2 = new OtherRequest();
ajax(r1);
ajax(r2);
The ajax function expects an argument of the Request interface type. However, the compiler will also accept an instance of OtherRequest, even though this class does not expressly implement the interface—the fact that all the methods with the correct signature exist is sufficient. You can test the code live here.
Back to Java. As mentioned above, Java does not support duck typing. However, using the method references in Java 8 can achieve a similar effect that can be very useful in certain situations, even if it is less elegant. Especially if the class to be used cannot or is not supposed to be modified.
The idea is to not only pass the object to a function, but also to specify how the function can get the necessary methods of the object. Let us look at the AJAX example again:
public void ajax(Request request) {
String url = request.getUrl();
...
String result = ...
request.callback(result);
}
public <T> void ajax(T request, Function<T,String> urlExtractor, BiConsumer<T, String> callback) {
String url = urlExtractor.apply(request);
...
String result = ...;
callback.accept(request, result);
}
If you do not have much experience with the functional classes and interfaces of Java 8, the signature of the second overloaded ajax method may take some getting used to for you. First, the method defines a generic type parameter T without specifying any type limits. Consequently, any desired instance can be submitted as the first argument, provided that the functions submitted as the second and third argument match this type. The second argument is a function that returns a string (namely the URL) for a given object of the type T. The third argument submitted is a BiConsumer, i.e. a function that accepts two arguments (the Request object and the result string again) and does not have a return value. With lambdas, this function can now be called as follows:
MyOtherRequest myRequest = new MyOtherRequest();
ajax(myRequest, req -> req.getUrl(), (req, result) - req.callback(result));
Method references make it more legible:
OtherRequest otherRequest = new OtherRequest();
ajax(myRequest, OtherRequest::getUrl, OtherRequest::callback);
The line reads like this: Take this Request object. To get the URL, call the getUrl method on the Request object. To process the result, please call the callback method on the object. This approach is not duck typing, but you can use it in situations where you would otherwise wish to use duck typing. In a sense, the method signature expresses requirements for a given object that have to be supported by the object. The fact that the actual name of the methods does not matter with this option is another interesting aspect. OtherRequest could just as well have called its callback method “CallMe_Now”. Incidentally, we could also have done the ajax signature differently in this case:
public void ajax(Supplier<String> urlSupplier, Consumer<String> callback) {
String url = urlSupplier.get();
...
callback.accept("das Result");
}
// Aufruf
ajax(otherRequest::getUrl, otherRequest::callback);
In this case, we directly submit a function for obtaining the URL and a function for processing the result. From the perspective of the ajax function, it does not matter whether there is a Request object or what type it is. What matters is the difference when calling the method: The method references in this case refer to the otherRequest instance (with a lower case initial), whereas in the example above, the method references referred to the class (OtherRequest with an upper case initial). From an API design point of view, this is the best alternative because the ajax method makes the least assumptions regarding the requester, which makes it highly composable. Nevertheless, there are situations where the “quasi-duck typing” option described above is necessary, and where you really want to receive objects as parameters. For example, we used this option when we developed the Model-Wrapper, which is a part of mvvmFX, in order to make the getter and setter methods of a wrapped object known.
Conclusion
The question of whether Java as a language should directly support duck typing, similar to the TypeScript alternative described above, is controversial. Just because a class has the same method signatures does not necessarily mean that it is truly compatible or technically appropriate. My impression is that many Java developers are rather defensive anyway, as evinced e.g. by the discussion about “final” with respect to methods and classes. Therefore, I believe it is unlikely that Java will get true duck typing anytime soon. The possibility of an ad-hoc implementation of interfaces, as described for Haskell above, does not seem to be on the list of extensions to be expected for the Java language in the near future either.
In conclusion, we can state that the functional interfaces of Java 8 provide at least some relief in this respect. Instead of being forced to define specific, limited types for method arguments, they allow for the use of any desired arguments, provided that the interpretation of these arguments is clarified by means of functions. This facilitates the integration of third-party code and reduces coupling. And with method references, you can achieve an even better technical expression than with pure lambda expressions.
But once again, the most important realization is this: It is always worthwhile to think outside the Java box and see what is possible with other programming languages and whether you can adapt that to suit your own needs.