Skip to content

Commit a4154ca

Browse files
committed
added code for ch01 -- Default and static methods in interfaces
1 parent 7c8935f commit a4154ca

File tree

15 files changed

+636
-22
lines changed

15 files changed

+636
-22
lines changed

01-default-static-interface-methods.md

Lines changed: 118 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,137 @@
1-
## Default and Static Methods for Interfaces
1+
Default and Static Methods for Interfaces
2+
--------
23

3-
We all understand that we should code to interfaces. Interfaces give client a contract which they should use without relying on the implementation details(i.e. classes). Hence, promoting **loose coupling**. Designing clean interfaces is one of the most important aspect of API design. One of the SOLID principle **[Interface segregation](https://en.wikipedia.org/wiki/Interface_segregation_principle)** talks about designing smaller client-specific interfaces instead of designing one general purpose interface. Interface design is the key to clean and effective API's for your libraries and applications.
4+
We all understand that we should code to interfaces. Interfaces give client a contract which they should use without relying on the implementation details(i.e. classes). Hence, promoting **[loose coupling](https://en.wikipedia.org/wiki/Loose_coupling)**. Designing clean interfaces is one of the most important aspect of API design. One of the SOLID principle **[Interface segregation](https://en.wikipedia.org/wiki/Interface_segregation_principle)** talks about designing smaller client-specific interfaces instead of designing one general purpose interface. Interface design is the key to clean and effective API's for your libraries and applications.
45

5-
You can follow the **7 Days with Java 8** series at http://shekhargulati.com/7-days-with-java-8/
6+
If you have designed any API then with time you would have felt the need to add new methods to the API. Once API is published it becomes impossible to add methods to an interface without breaking existing implementations. To make this point clear, let's suppose you are building a simple `Calculator` API that supports `add`,`subtract`, `divide`, and `multiply` operations. We can write `Calculator` interface as shown below. ***To keep things simple we will use int.***
67

7-
If you have designed any API then with time you would have felt the need to add new methods to the API. Once API is published it becomes impossible to add methods to an interface without breaking existing implementations. To make this point clear, let's suppose you are building a client API for a social network and one of the API's that you want to design is to offer user suggestions based on user initials. So, if someone calls your API method with initial `sh` then you would suggest users like `Shekhar`, `Shankar`, `Shane`. So, you came up with `UserSuggester` interface as shown below.
8+
```java
9+
public interface Calculator {
10+
11+
int add(int first, int second);
12+
13+
int subtract(int first, int second);
14+
15+
int divide(int number, int divisor);
16+
17+
int multiply(int first, int second);
18+
}
19+
```
20+
21+
To back this `Calculator` interface you created a `BasicCalculator` implementation as shown below.
822

923
```java
10-
public interface UserSuggester {
11-
List<User> suggestions(String initials);
24+
public class BasicCalculator implements Calculator {
25+
26+
@Override
27+
public int add(int first, int second) {
28+
return first + second;
29+
}
30+
31+
@Override
32+
public int subtract(int first, int second) {
33+
return first - second;
34+
}
35+
36+
@Override
37+
public int divide(int number, int divisor) {
38+
if (divisor == 0) {
39+
throw new IllegalArgumentException("divisor can't be zero.");
40+
}
41+
return number / divisor;
42+
}
43+
44+
@Override
45+
public int multiply(int first, int second) {
46+
return first * second;
47+
}
1248
}
1349
```
1450

15-
API turned out to be very useful to the API consumers and some of the consumers decided to have their own implementation of the `UserSuggester` interface. After talking to your users you came to know that most of them would like to have a way to specify filter criteria as well so that they can filter down user suggestions. It looked a very simple API change so you added one more method to the API -- `suggestions(String initials, Predicate<User> predicate)`.
51+
## Static Factory Methods
52+
53+
Calculator API turned out to be very useful and easy to use. Users just have to create an instance of `BasicCalculator` and then they can use the API. You start seeing code like the one shown below.
54+
55+
```java
56+
Calculator calculator = new BasicCalculator();
57+
int sum = calculator.add(1, 2);
58+
59+
BasicCalculator cal = new BasicCalculator();
60+
int difference = cal.subtract(3, 2);
61+
```
62+
63+
Oh no!! Users of the API are not coding to `Calculator` interface instead they are coding to implementation. Your API didn't enforced users to code to interfaces as the `BasicCalculator` class was public. If you make `BasicCalculator` package protected then you would have to provide a static factory class that will take care of providing the `Calculator` implementation. Let's improve the code to handle this.
1664

65+
First, we will make `BasicCalculator` package protected so that users can't access the class directly.
1766
```java
18-
public interface UserSuggester {
19-
List<User> suggestions(String initials);
20-
List<User> suggestions(String initials, Predicate<User> predicate);
67+
class BasicCalculator implements Calculator {
68+
// rest remains same
2169
}
2270
```
2371

24-
Adding a method to an interface broke the source compatibility of the API. Adding methods to an interface leads to compilation failures when classes that depend on the interface don't implement the method. This means users who were implementing `UserSuggester` interface would have to add implementation for `suggestions(String initials, Predicate<User> predicate)` otherwise their code will not compile. This is a big problem for API designers as it makes difficult to evolve API. Prior to Java 8, it was not possible to have method implementations inside interfaces. This often becomes a problem when it was required to extend an API i.e. adding one or more methods to the interface definition.
72+
Next, we will write a factory class that will give us the `Calculator` instance as shown below.
2573

26-
Java 8 introduced two ways to declare method with implementations inside an interface. These are:
74+
```java
75+
public abstract class CalculatorFactory {
2776

28-
* **static methods**: This allows users to declare static methods inside an interface. `Stream` interface has few static helper methods like `of`, `empty`, `concat`, etc. The static methods inside an interface could be used to replace static helper classes that we normally create to define helper methods associated with a type. For example, `Collections` class is a helper class that defines various helper methods to work with Collection and associated interfaces. The methods define in `Collections` class could easily be added to `Collection` or its child interface. For example, `addAll` method in the `Collections` class can be added to `Collection` interface as a static method.
77+
public static Calculator getInstance() {
78+
return new BasicCalculator();
79+
}
80+
}
81+
```
82+
83+
Now, users will be forced to code to `Calculator` interface and they will not have access to implementations.
84+
85+
Although we have achieved our goal but we have increased the circumference area of our API by adding a new class `CalculatorFactory`. Now users of the API have to learn about one more class before they can use the API effectively. This was the only solution available before Java 8.
86+
87+
**Java 8 allows you to declare static methods inside an interface**. This will allow API designers to define static utility methods like `getInstance` in the interface itself. Hence keeping API short and lean. The static methods inside an interface could be used to replace static helper classes(`CalculatorFactory`) that we normally create to define helper methods associated with a type. For example, `Collections` class is a helper class that defines various helper methods to work with Collection and associated interfaces. The methods define in `Collections` class could easily be added to `Collection` or its child interface.
88+
89+
The above code can be improved in Java 8 by adding a static `getInstance` method in the `Calculator` interface itself.
2990

3091
```java
31-
public static<T> Stream<T> of(T... values) {
32-
return Arrays.stream(values);
92+
public interface Calculator {
93+
94+
static Calculator getInstance() {
95+
return new BasicCalculator();
96+
}
97+
98+
int add(int first, int second);
99+
100+
int subtract(int first, int second);
101+
102+
int divide(int number, int divisor);
103+
104+
int multiply(int first, int second);
105+
33106
}
34107
```
35108

36-
* **default methods**: This allows users to provide default implementations to methods defined in the interface. The class implementing the interface is not required to provide implementation of the method. If implementing class provides the implementation then that implementation would be used else default implementation is used. `List` interface has few default methods defined in it like `replaceAll`, `sort`, and `splitIterator`.
109+
## Evolving API with time
110+
111+
Some of the consumers decided to either extend the `Calculator` API by adding methods like `remainder` or write their own implementation of `Calculator` interface. After talking to your users you came to know that most of them would like to have a `remainder` method added to `Calculator` interface. It looked a very simple API change so you added one more method to the API.
112+
113+
```java
114+
public interface Calculator {
115+
116+
static Calculator getInstance() {
117+
return new BasicCalculator();
118+
}
119+
120+
int add(int first, int second);
121+
122+
int subtract(int first, int second);
123+
124+
int divide(int number, int divisor);
125+
126+
int multiply(int first, int second);
127+
128+
int remainder(int number, int divisor); // new method added to API
129+
}
130+
```
131+
132+
Adding a method to an interface broke the source compatibility of the API. This means users who were implementing `Calculator` interface would have to add implementation for `remainder` method otherwise their code will not compile. This is a big problem for API designers as it makes difficult to evolve API. Prior to Java 8, it was not possible to have method implementations inside interfaces. This often becomes a problem when it was required to extend an API i.e. adding one or more methods to the interface definition.
133+
134+
To allow API's to evolve with time, Java 8 allows users to provide default implementations to methods defined in the interface. These are called **default** or **defender** methods. The class implementing the interface is not required to provide implementation of these methods. If implementing class provides the implementation then implementing class method implementation will be used else default implementation will be used. `List` interface has few default methods defined like `replaceAll`, `sort`, and `splitIterator`.
37135

38136
```java
39137
default void replaceAll(UnaryOperator<E> operator) {
@@ -45,19 +143,17 @@ default void replaceAll(UnaryOperator<E> operator) {
45143
}
46144
```
47145

48-
We can solve our API problem by defining a default method as shown below.
146+
We can solve our API problem by defining a default method as shown below. Default methods are usually defined using already existing methods -- `remainder` is defined using `subtract`, `multiply`, and `divide` methods.
49147

50148
```java
51-
default List<User> suggestions(String initials, Predicate<User> predicate) {
52-
Objects.requireNonNull(initials);
53-
Objects.requireNonNull(predicate);
54-
return suggestions(initials).stream().filter(predicate).collect(toList());
149+
default int remainder(int number, int divisor) {
150+
return subtract(number, multiply(divisor, divide(number, divisor)));
55151
}
56152
```
57153

58154
## Multiple inheritance
59155

60-
A class can extend a single class but can implement multiple interfaces. Now that it is feasible to have method implementations in interfaces Java has multiple inheritance of behavior. Java already has multiple inheritance at type level but now it also has multiple inheritance at behavioral level. There are three resolution rules that helps decide which method will be picked:
156+
A class can extend a single class but can implement multiple interfaces. Now that it is feasible to have method implementations in interfaces Java has multiple inheritance of behavior. Java already has multiple inheritance at type level but now it also has multiple inheritance at behavior level. There are three resolution rules that helps decide which method will be picked:
61157

62158
1. Methods declared in classes win over method defined in interfaces.
63159
```java

code/.gitignore

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
### JetBrains template
2+
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
3+
4+
*.iml
5+
6+
## Directory-based project format:
7+
.idea/
8+
# if you remove the above rule, at least ignore the following:
9+
10+
# User-specific stuff:
11+
# .idea/workspace.xml
12+
# .idea/tasks.xml
13+
# .idea/dictionaries
14+
15+
# Sensitive or high-churn files:
16+
# .idea/dataSources.ids
17+
# .idea/dataSources.xml
18+
# .idea/sqlDataSources.xml
19+
# .idea/dynamic.xml
20+
# .idea/uiDesigner.xml
21+
22+
# Gradle:
23+
# .idea/gradle.xml
24+
# .idea/libraries
25+
26+
# Mongo Explorer plugin:
27+
# .idea/mongoSettings.xml
28+
29+
## File-based project format:
30+
*.ipr
31+
*.iws
32+
33+
## Plugin-specific files:
34+
35+
# IntelliJ
36+
/out/
37+
38+
# mpeltonen/sbt-idea plugin
39+
.idea_modules/
40+
41+
# JIRA plugin
42+
atlassian-ide-plugin.xml
43+
44+
# Crashlytics plugin (for Android Studio and IntelliJ)
45+
com_crashlytics_export_strings.xml
46+
crashlytics.properties
47+
crashlytics-build.properties
48+
49+
# Created by .ignore support plugin (hsz.mobi)
50+
### Java template
51+
*.class
52+
53+
# Mobile Tools for Java (J2ME)
54+
.mtj.tmp/
55+
56+
# Package Files #
57+
*.jar
58+
*.war
59+
*.ear
60+
61+
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
62+
hs_err_pid*
63+
### Gradle template
64+
.gradle
65+
build/
66+
67+
# Ignore Gradle GUI config
68+
gradle-app.setting
69+
70+
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
71+
!gradle-wrapper.jar
72+

code/build.gradle

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
group 'com.shekhargulati'
2+
version '1.0-SNAPSHOT'
3+
4+
apply plugin: 'java'
5+
6+
sourceCompatibility = 1.8
7+
8+
repositories {
9+
mavenCentral()
10+
}
11+
12+
dependencies {
13+
testCompile group: 'junit', name: 'junit', version: '4.12'
14+
}
51 KB
Binary file not shown.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#Sun Nov 22 18:13:21 IST 2015
2+
distributionBase=GRADLE_USER_HOME
3+
distributionPath=wrapper/dists
4+
zipStoreBase=GRADLE_USER_HOME
5+
zipStorePath=wrapper/dists
6+
distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-bin.zip

0 commit comments

Comments
 (0)