- Default Methods
- Extending Interfaces That Contain Default Methods
- Static Methods
- Integrating Default Methods into Existing Libraries
- Java 8 for Android Development: Default and Static Methods
- Write Cleaner Lambda Expressions, With Method References
- A Static Method
- An Instance Method of a Specific Object
- An Instance Method of an Arbitrary Object of a Particular Type
- Constructor References
- Add Default Methods to Your Interfaces
- Using Static Methods in Your Java 8 Interfaces
- So, Are Interfaces Essentially Just Abstract Classes?
- Apply the Same Annotation as Many Times as You Want
- Conclusion
Default Methods
The section Interfaces describes an example that involves manufacturers of computer-controlled cars who publish industry-standard interfaces that describe which methods can be invoked to operate their cars. What if those computer-controlled car manufacturers add new functionality, such as flight, to their cars? These manufacturers would need to specify new methods to enable other companies (such as electronic guidance instrument manufacturers) to adapt their software to flying cars. Where would these car manufacturers declare these new flight-related methods? If they add them to their original interfaces, then programmers who have implemented those interfaces would have to rewrite their implementations. If they add them as static methods, then programmers would regard them as utility methods, not as essential, core methods.
Default methods enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces.
The following class, SimpleTimeClient , implements TimeClient :
Suppose that you want to add new functionality to the TimeClient interface, such as the ability to specify a time zone through a ZonedDateTime object (which is like a LocalDateTime object except that it stores time zone information):
Following this modification to the TimeClient interface, you would also have to modify the class SimpleTimeClient and implement the method getZonedDateTime . However, rather than leaving getZonedDateTime as abstract (as in the previous example), you can instead define a default implementation. (Remember that an abstract method is a method declared without an implementation.)
You specify that a method definition in an interface is a default method with the default keyword at the beginning of the method signature. All method declarations in an interface, including default methods, are implicitly public , so you can omit the public modifier.
With this interface, you do not have to modify the class SimpleTimeClient , and this class (and any class that implements the interface TimeClient ), will have the method getZonedDateTime already defined. The following example, TestSimpleTimeClient , invokes the method getZonedDateTime from an instance of SimpleTimeClient :
Extending Interfaces That Contain Default Methods
When you extend an interface that contains a default method, you can do the following:
- Not mention the default method at all, which lets your extended interface inherit the default method.
- Redeclare the default method, which makes it abstract .
- Redefine the default method, which overrides it.
Suppose that you extend the interface TimeClient as follows:
Any class that implements the interface AnotherTimeClient will have the implementation specified by the default method TimeClient.getZonedDateTime .
Suppose that you extend the interface TimeClient as follows:
Any class that implements the interface AbstractZoneTimeClient will have to implement the method getZonedDateTime ; this method is an abstract method like all other non-default (and non-static) methods in an interface.
Suppose that you extend the interface TimeClient as follows:
Any class that implements the interface HandleInvalidTimeZoneClient will use the implementation of getZonedDateTime specified by this interface instead of the one specified by the interface TimeClient .
Static Methods
In addition to default methods, you can define static methods in interfaces. (A static method is a method that is associated with the class in which it is defined rather than with any object. Every instance of the class shares its static methods.) This makes it easier for you to organize helper methods in your libraries; you can keep static methods specific to an interface in the same interface rather than in a separate class. The following example defines a static method that retrieves a ZoneId object corresponding to a time zone identifier; it uses the system default time zone if there is no ZoneId object corresponding to the given identifier. (As a result, you can simplify the method getZonedDateTime ):
Like static methods in classes, you specify that a method definition in an interface is a static method with the static keyword at the beginning of the method signature. All method declarations in an interface, including static methods, are implicitly public , so you can omit the public modifier.
Integrating Default Methods into Existing Libraries
Default methods enable you to add new functionality to existing interfaces and ensure binary compatibility with code written for older versions of those interfaces. In particular, default methods enable you to add methods that accept lambda expressions as parameters to existing interfaces. This section demonstrates how the Comparator interface has been enhanced with default and static methods.
Consider the Card and Deck classes as described in Questions and Exercises: Classes. This example rewrites the Card and Deck classes as interfaces. The Card interface contains two enum types ( Suit and Rank ) and two abstract methods ( getSuit and getRank ):
The Deck interface contains various methods that manipulate cards in a deck:
The class PlayingCard implements the interface Card , and the class StandardDeck implements the interface Deck .
The class StandardDeck implements the abstract method Deck.sort as follows:
The method Collections.sort sorts an instance of List whose element type implements the interface Comparable . The member entireDeck is an instance of List whose elements are of the type Card , which extends Comparable . The class PlayingCard implements the Comparable.compareTo method as follows:
The method compareTo causes the method StandardDeck.sort() to sort the deck of cards first by suit, and then by rank.
What if you want to sort the deck first by rank, then by suit? You would need to implement the Comparator interface to specify new sorting criteria, and use the method sort(List list, Comparator c) (the version of the sort method that includes a Comparator parameter). You can define the following method in the class StandardDeck :
With this method, you can specify how the method Collections.sort sorts instances of the Card class. One way to do this is to implement the Comparator interface to specify how you want the cards sorted. The example SortByRankThenSuit does this:
The following invocation sorts the deck of playing cards first by rank, then by suit:
However, this approach is too verbose; it would be better if you could specify just the sort criteria and avoid creating multiple sorting implementations. Suppose that you are the developer who wrote the Comparator interface. What default or static methods could you add to the Comparator interface to enable other developers to more easily specify sort criteria?
To start, suppose that you want to sort the deck of playing cards by rank, regardless of suit. You can invoke the StandardDeck.sort method as follows:
Because the interface Comparator is a functional interface, you can use a lambda expression as an argument for the sort method. In this example, the lambda expression compares two integer values.
It would be simpler for your developers if they could create a Comparator instance by invoking the method Card.getRank only. In particular, it would be helpful if your developers could create a Comparator instance that compares any object that can return a numerical value from a method such as getValue or hashCode . The Comparator interface has been enhanced with this ability with the static method comparing :
In this example, you can use a method reference instead:
This invocation better demonstrates how to specify different sort criteria and avoid creating multiple sorting implementations.
The Comparator interface has been enhanced with other versions of the static method comparing such as comparingDouble and comparingLong that enable you to create Comparator instances that compare other data types.
Suppose that your developers would like to create a Comparator instance that could compare objects with more than one criteria. For example, how would you sort the deck of playing cards first by rank, and then by suit? As before, you could use a lambda expression to specify these sort criteria:
It would be simpler for your developers if they could build a Comparator instance from a series of Comparator instances. The Comparator interface has been enhanced with this ability with the default method thenComparing :
The Comparator interface has been enhanced with other versions of the default method thenComparing (such as thenComparingDouble and thenComparingLong ) that enable you to build Comparator instances that compare other data types.
Suppose that your developers would like to create a Comparator instance that enables them to sort a collection of objects in reverse order. For example, how would you sort the deck of playing cards first by descending order of rank, from Ace to Two (instead of from Two to Ace)? As before, you could specify another lambda expression. However, it would be simpler for your developers if they could reverse an existing Comparator by invoking a method. The Comparator interface has been enhanced with this ability with the default method reversed :
This example demonstrates how the Comparator interface has been enhanced with default methods, static methods, lambda expressions, and method references to create more expressive library methods whose functionality programmers can quickly deduce by looking at how they are invoked. Use these constructs to enhance the interfaces in your libraries.
Источник
Java 8 for Android Development: Default and Static Methods
Java 8 was a huge step forward for the programming language and now, with the release of Android Studio 3.0, Android developers finally have access to built-in support for some of Java 8’s most important features.
In this three-part series, we’ve been exploring the Java 8 features you can start using in your Android projects today. In Cleaner Code With Lambda Expressions, we set up our development to use the Java 8 support provided by Android’s default toolchain, before taking an in-depth look at lambda expressions.
In this post, we’ll look at two different ways that you can declare non-abstract methods in your interfaces (something that wasn’t possible in earlier versions of Java). We’ll also answer the question of, now that interfaces can implement methods, what exactly is the difference between abstract classes and interfaces?
We’ll also be covering a Java 8 feature that gives you the freedom to use the same annotation as many times as you want in the same location, while remaining backwards compatible with earlier versions of Android.
But first, let’s take a look at a Java 8 feature that’s designed to be used in combination with the lambda expressions we saw in the previous post.
Write Cleaner Lambda Expressions, With Method References
In the last post, you saw how you can use lambda expressions to remove lots of boilerplate code from your Android applications. However, when a lambda expression is simply calling a single method that already has a name, you can cut even more code from your project by using a method reference.
For example, this lambda expression is really just redirecting work to an existing handleViewClick method:
In this scenario, we can refer to this method by name, using the :: method reference operator. You create this kind of method reference, using the following format:
In our Floating Action Button example, we can use a method reference as the body of our lambda expression:
Note that the referenced method must take the same parameters as the interface—in this instance, that’s View .
You can use the method reference operator ( :: ) to reference any of the following:
A Static Method
If you have a lambda expression that calls a static method:
Then you can turn it into a method reference:
For example, if you had a static method PrintMessage in a MyClass class, then your method reference would look something like this:
An Instance Method of a Specific Object
This is an instance method of an object that’s known ahead of time, allowing you to replace:
So, if you had the following lambda expression:
Then introducing a method reference would give you the following:
An Instance Method of an Arbitrary Object of a Particular Type
This is an instance method of an arbitrary object that will be supplied later, and written in the following format:
Constructor References
Constructor references are similar to method references, except that you use the keyword new to invoke the constructor. For example, Button::new is a constructor reference for the class Button , although the exact constructor that’s invoked depends on the context.
Using constructor references, you can turn:
For example, if you had the following MyInterface interface:
Then you could use constructor references to create new Student instances:
It’s also possible to create constructor references for array types. For example, a constructor reference for an array of int s is int[]::new .
Add Default Methods to Your Interfaces
Prior to Java 8, you could only include abstract methods in your interfaces (i.e. methods without a body), which made it difficult to evolve interfaces, post-publication.
Every time you added a method to an interface definition, any classes that implemented this interface would suddenly be missing an implementation. For example, if you had an interface ( MyInterface ) that was used by MyClass , then adding a method to MyInterface would break compatibility with MyClass .
In the best case scenario where you were responsible for the small number of classes that used MyInterface , this behaviour would be annoying but manageable—you’d just have to set aside some time to update your classes with the new implementation. However, things could become much more complicated if a large number of classes implemented MyInterface , or if the interface was used in classes that you weren’t responsible for.
While there were a number of workarounds for this problem, none of them were ideal. For example, you could include new methods in an abstract class, but this would still require everyone to update their code to extend this abstract class; and, while you could extend the original interface with a new interface, anyone who wanted to use these new methods would then need to rewrite all their existing interface references.
With the introduction of default methods in Java 8, it’s now possible to declare non-abstract methods (i.e. methods with a body) inside your interfaces, so you can finally create default implementations for your methods.
When you add a method to your interface as a default method, any class that implements this interface doesn’t necessarily need to provide its own implementation, which gives you a way of updating your interfaces without breaking compatibility. If you add a new method to an interface as a default method, then every class that uses this interface but doesn’t provide its own implementation will simply inherit the method’s default implementation. Since the class isn’t missing an implementation, it’ll continue to function as normal.
In fact, the introduction of default methods was the reason that Oracle was able to make such a large number of additions to the Collections API in Java 8.
Collection is a generic interface that’s used in many different classes, so adding new methods to this interface had the potential to break countless lines of code. Rather than adding new methods to the Collection interface and breaking every class that was derived from this interface, Oracle created the default method feature, and then added these new methods as default methods. If you take a look at the new Collection.Stream() method (which we’ll be exploring in detail in part three), you’ll see that it was added as a default method:
Creating a default method is simple—just add the default modifier to your method signature:
Now, if MyClass uses MyInterface but doesn’t provide its own implementation of defaultMethod , it’ll just inherit the default implementation provided by MyInterface . For example, the following class will still compile:
An implementing class can override the default implementation provided by the interface, so classes are still in complete control of their implementations.
While default methods are a welcome addition for API designers, they can occasionally cause a problem for developers who are trying to use multiple interfaces in the same class.
Imagine that in addition to MyInterface , you have the following:
Both MyInterface and SecondInterface contain a default method with exactly the same signature ( defaultMethod ). Now imagine you try to use both of these interfaces in the same class:
At this point you have two conflicting implementations of defaultMethod , and the compiler has no idea which method it should use, so you’re going to encounter a compiler error.
One way to resolve this problem is to override the conflicting method with your own implementation:
The other solution is to specify which version of defaultMethod you want to implement, using the following format:
So if you wanted to call the MyInterface#defaultMethod() implementation, then you’d use the following:
Using Static Methods in Your Java 8 Interfaces
Similar to default methods, static interface methods give you a way of defining methods inside an interface. However, unlike default methods, an implementing class cannot override an interface’s static methods.
If you have static methods that are specific to an interface, then Java 8’s static interface methods give you a way of placing these methods inside the corresponding interface, rather than having to store them in a separate class.
You create a static method by placing the keyword static at the beginning of the method signature, for example:
When you implement an interface that contains a static interface method, that static method is still part of the interface and isn’t inherited by the class implementing it, so you’ll need to prefix the method with the interface name, for example:
This also means that a class and an interface can have a static method with the same signature. For example, using MyClass.staticMethod and MyInterface.staticMethod in the same class won’t cause a compile-time error.
So, Are Interfaces Essentially Just Abstract Classes?
The addition of static interface methods and default methods has led some developers to question whether Java interfaces are becoming more like abstract classes. However, even with the addition of default and static interface methods, there are still some notable differences between interfaces and abstract classes:
- Abstract classes can have final, non-final, static and non-static variables, whereas an interface can only have static and final variables.
- Abstract classes allow you to declare fields that are not static and final, whereas an interface’s fields are inherently static and final.
- In interfaces, all methods that you declare or define as default methods are inherently public, whereas in abstract classes you can define public, protected, and private concrete methods.
- Abstract classes are classes, and therefore can have state; interfaces cannot have state associated with them.
- You can define constructors inside an abstract class, something that’s not possible inside Java interfaces.
- Java only allows you to extend one class (regardless of whether it’s abstract), but you’re free to implement as many interfaces as you require. This means that interfaces typically have the edge when you require multiple inheritance, although you do need to beware the deadly diamond of death!
Apply the Same Annotation as Many Times as You Want
Traditionally, one of the limitations of Java annotations has been that you cannot apply the same annotation more than once in the same location. Try to use the same annotation multiple times, and you’re going to encounter a compile-time error.
However, with the introduction of Java 8’s repeating annotations, you’re now free to use the same annotation as many times as you want in the same location.
In order to ensure your code remains compatible with earlier versions of Java, you’ll need to store your repeating annotations in a container annotation.
You can tell the compiler to generate this container, by completing the following steps:
- Mark the annotation in question with the @Repeatable meta-annotation (an annotation that’s used to annotate an annotation). For example, if you wanted to make the @ToDo annotation repeatable, you’d use: @Repeatable(ToDos.class) . The value in parentheses is the type of container annotation that the compiler will eventually generate.
- Declare the containing annotation type. This must have an attribute that’s an array of the repeating annotation type, for example:
Attempting to apply the same annotation multiple times without first declaring that it’s repeatable will result in an error at compile-time. However, once you’ve specified that this is a repeatable annotation, you can use this annotation multiple times in any location where you’d use a standard annotation.
Conclusion
In this second part of our series on Java 8, we saw how you can cut even more boilerplate code from your Android projects by combining lambda expressions with method references, and how to enhance your interfaces with default and static methods.
In the third and final installment, we’ll be looking at a new Java 8 API that lets you process huge amounts of data in a more efficient, declarative manner, without having to worry about concurrency and thread management. We’ll also be tying together a few of the different features we’ve discussed throughout this series, by exploring the role that Functional Interfaces have to play in lambda expressions, static interface methods, default methods, and more.
And finally, even though we’re still waiting for Java 8’s new Date and Time API to officially arrive on Android, I’ll show how you can start using this new API in your Android projects today, with the help of some third-party libraries.
In the meantime, check out some of our other posts on Java and Android app development!
Источник