I have already blogged on two refactoring options NetBeans 7.2 provides in my aptly named posts NetBeans 7.2: Refactoring Parameterized Constructor As Builder and NetBeans 7.2: Refactoring Constructor As Static Factory. In this post, I look at another refactoring option introduced with NetBeans 7.2 that may be the most time-saving of them all: Introduce Local Extension.
Martin Fowler's Refactoring Home Page includes a "catalog of common refactorings", including the Introduce Local Extension refactoring. This particular refactoring is described on that page in this way (presenting the issue and the solution), "[Issue] A server class you are using needs several additional methods, but you can't modify the class. [Solution] Create a new class that contains these extra methods. Make this extension class a subclass or a wrapper of the original."
One of the new refactorings available with NetBeans 7.2 is "Introduce Local Extension" and it can be easily accessed by right-clicking on the class that one wants to provide a local extension for, selecting the "Refactor" option to expand that drop-down menu, and selecting "Introduce Local Extension." Alternatively, one can simply highlight the class of interest and use the keyboard: Alt+Shift+X. The former (using the drop-down menus in conjunction with right and then left mouse clicks) is depicted in the following screen snapshot after hovering over usage of the Java Date class in the source code (which is listed after the screen snapshot).
The above screen snapshot shows the result of right-clicking on the Date
type used for instance variable date
in the following source code listing.
package dustin.examples; import java.util.Date; /** * * @author Dustin */ public class Main { private Date date; /** * @param arguments the command line arguments */ public static void main(final String[] arguments) { } }
When the "Introduce Local Extension" refactoring is selected either by mouse clicking as shown above or through use of Alt+Shift+X, a wizard screen like the following is presented.
In this case, because I used Alt+Shift+X while hovering over the Date
data type, the wizard is ready to help me generate a local extension of Java's Date
. Note that if I had been hovering over most other parts of this class, the wizard would instead start helping me generate a local extension of my Main
class (the class loaded in the NetBeans editor window).
In compliance with earlier cited definition of implementations of the "Introduce Local Extension" refactoring, there are two ways the NetBeans 7.2 wizard allows for this: wrapper (composition) and subtype (implementation inheritance). If the "Wrapper" option is selected (as is the case here), then there are three more "Equality" options to choose from ("Delegate", "Generate", or "Separate"). I'll return to these later. For now, let's assume that "Subtype" is selected. The next screen snapshot shows how the other three options are no longer applicable and are grayed-out.
If I click the "Refactor" button at this point, my new class will also be named Date
but will be dustin.examples.Date
rather than java.util.Date
. In this case, to avoid any confusion, I'm going to change the generated class's name to DustinDate
.
It's also worth noting that there is a checkbox that allows me to indicate whether I want to replace the original class (Date
in this case) with the refactored local extension. In other words, I can have the refactoring not only create the subtype local extension, but I can have NetBeans replace my use of Date
in the Main
source code with my new class. This is shown in the next screen snapshot.
The next screen snapshot shows the results of clicking the "Refactor" button and having DustinDate
created as a subtype of java.util.Date
. Note that the use of Date
in Main
has been automatically updated to use the newly generated DustinDate
, leaving an unused import of java.util.Date
behind.
The next code listing is the source code of DustinDate
, which was generated solely by NetBeans as a Subtype "Introduce to Local Extension" refactoring of java.util.Date
.
package dustin.examples; import java.util.Date; /** * * @author Dustin */ public class DustinDate extends Date { /** * Allocates a <code>Date</code> object and initializes it so that * it represents the time at which it was allocated, measured to the * nearest millisecond. * * @see java.lang.System#currentTimeMillis() */ public DustinDate() { super(); } /** * Allocates a <code>Date</code> object and initializes it to * represent the specified number of milliseconds since the * standard base time known as "the epoch", namely January 1, * 1970, 00:00:00 GMT. * * @param date the milliseconds since January 1, 1970, 00:00:00 GMT. * @see java.lang.System#currentTimeMillis() */ public DustinDate(long date) { super(date); } /** * Allocates a <code>Date</code> object and initializes it so that * it represents midnight, local time, at the beginning of the day * specified by the <code>year</code>, <code>month</code>, and * <code>date</code> arguments. * * @param year the year minus 1900. * @param month the month between 0-11. * @param date the day of the month between 1-31. * @see java.util.Calendar * @deprecated As of JDK version 1.1, * replaced by <code>Calendar.set(year + 1900, month, date)</code> * or <code>GregorianCalendar(year + 1900, month, date)</code>. */ public DustinDate(int year, int month, int date) { super(year, month, date); } /** * Allocates a <code>Date</code> object and initializes it so that * it represents the instant at the start of the minute specified by * the <code>year</code>, <code>month</code>, <code>date</code>, * <code>hrs</code>, and <code>min</code> arguments, in the local * time zone. * * @param year the year minus 1900. * @param month the month between 0-11. * @param date the day of the month between 1-31. * @param hrs the hours between 0-23. * @param min the minutes between 0-59. * @see java.util.Calendar * @deprecated As of JDK version 1.1, * replaced by <code>Calendar.set(year + 1900, month, date, * hrs, min)</code> or <code>GregorianCalendar(year + 1900, * month, date, hrs, min)</code>. */ public DustinDate(int year, int month, int date, int hrs, int min) { super(year, month, date, hrs, min); } /** * Allocates a <code>Date</code> object and initializes it so that * it represents the instant at the start of the second specified * by the <code>year</code>, <code>month</code>, <code>date</code>, * <code>hrs</code>, <code>min</code>, and <code>sec</code> arguments, * in the local time zone. * * @param year the year minus 1900. * @param month the month between 0-11. * @param date the day of the month between 1-31. * @param hrs the hours between 0-23. * @param min the minutes between 0-59. * @param sec the seconds between 0-59. * @see java.util.Calendar * @deprecated As of JDK version 1.1, * replaced by <code>Calendar.set(year + 1900, month, date, * hrs, min, sec)</code> or <code>GregorianCalendar(year + 1900, * month, date, hrs, min, sec)</code>. */ public DustinDate(int year, int month, int date, int hrs, int min, int sec) { super(year, month, date, hrs, min, sec); } /** * Allocates a <code>Date</code> object and initializes it so that * it represents the date and time indicated by the string * <code>s</code>, which is interpreted as if by the * {@link Date#parse} method. * * @param s a string representation of the date. * @see java.text.DateFormat * @see java.util.Date#parse(java.lang.String) * @deprecated As of JDK version 1.1, * replaced by <code>DateFormat.parse(String s)</code>. */ public DustinDate(String s) { super(s); } }
Because the "subtype" approach was employed, only constructors had to be generated by NetBeans to use the class being extended. The public
or protected
"get", "set", and any other methods of Date
are automatically available to the extending DustinDate
class. There are sometimes limitations and disadvantages of using implementation inheritance (it has even been called evil). One such disadvantage is the inability to extend final classes. To illustrate, I'm move to introducing a local extension of java.lang.String instead of Date
.
String
Instance Variable Added)
package dustin.examples; /** * * @author Dustin */ public class Main { private DustinDate date; private String string; /** * @param arguments the command line arguments */ public static void main(final String[] arguments) { } }
Highlighting the String
datatype and selecting the "Introduce Local Extension" refactoring leads to a wizard screen like that which follows.
I was pleasantly surprised to see that NetBeans 7.2 does not allow for the "Subtype" option in this case (String
is final and cannot be extended). I must use "Wrapper," but can select one of the three "Equality" options for "Wrapper." I'm not going any further with String
in this example, but the wizard will generate local extensions as wrappers using any of the three "Equality" options. In all cases, certain methods in the generated class need to be removed or altered for the new code to compile. A screen snapshot of using this with String
using "Wrapper" and "Delegate" is shown next.
To more easily demonstrate NetBeans 7.2 refactoring local extension of wrapper type with the different options set, I had NetBeans generate most of the source code for a simple, all-new class called Person
that is shown in the next code listing.
package dustin.examples; import java.util.Objects; /** * * @author Dustin */ public final class Person { private String lastName; private String firstName; public Person(final String newLastName, final String newFirstName) { this.lastName = newLastName; this.firstName = newFirstName; } public String getLastName() { return this.lastName; } public void setLastName(String newLastName) { this.lastName = newLastName; } public String getFirstName() { return this.firstName; } public void setFirstName(String newFirstName) { this.firstName = newFirstName; } @Override public int hashCode() { int hash = 3; hash = 83 * hash + Objects.hashCode(this.lastName); hash = 83 * hash + Objects.hashCode(this.firstName); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Person other = (Person) obj; if (!Objects.equals(this.lastName, other.lastName)) { return false; } if (!Objects.equals(this.firstName, other.firstName)) { return false; } return true; } @Override public String toString() { return "Person{" + "lastName=" + lastName + ", firstName=" + firstName + '}'; } }
When using "Wrapper" type of local extension, there are three possible "Equality" selections. The next three screen snapshots show use of each with a class named appropriately for each. Note that I unchecked the option to change my use of Person
to this new class because I wanted to use that original Person
class to demonstrate refactoring for "Generate" and "Separate" equality settings in addition to "Delegate."
The three "Wrapper" generated classes (PersonDelegate
, PersonGenerate
, and PersonSeparate
are identical except how they override Object.equals(Object) and Object.hashCode(). Given this, I first show the code listing for PersonDelegate
and then show only the different equals
implementations for the three approaches.
package dustin.examples; /** * * @author Dustin */ public final class PersonDelegate { private Person delegate; public PersonDelegate(Person delegate) { this.delegate = delegate; } public PersonDelegate(final String newLastName, final String newFirstName) { this.delegate = new Person(newLastName, newFirstName); } public String getLastName() { return delegate.getLastName(); } public void setLastName(String newLastName) { delegate.setLastName(newLastName); } public String getFirstName() { return delegate.getFirstName(); } public void setFirstName(String newFirstName) { delegate.setFirstName(newFirstName); } public String toString() { return delegate.toString(); } public boolean equals(Object o) { Object target = o; if (o instanceof PersonDelegate) { target = ((PersonDelegate) o).delegate; } return this.delegate.equals(target); } public int hashCode() { return this.delegate.hashCode(); } }
As stated before the last code listing, only the implementation of "equals" and "hashCode" change depending on the equality setting chosen. To make the differences more obvious, only the equals
and hashCode
implementations of the three generated classes are shown next.
public boolean equals(Object o) { Object target = o; if (o instanceof PersonDelegate) { target = ((PersonDelegate) o).delegate; } return this.delegate.equals(target); } public int hashCode() { return this.delegate.hashCode(); }PersonGenerate.java equals Method - Generate Equality
@Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final PersonGenerate other = (PersonGenerate) obj; if (!Objects.equals(this.delegate, other.delegate)) { return false; } return true; } @Override public int hashCode() { int hash = 5; hash = 43 * hash + Objects.hashCode(this.delegate); return hash; }PersonSeparate.java equals Method - Separate Equality
public boolean equalsPersonSeparate(PersonSeparate o) { return this.delegate.equals(o.delegate); } public boolean equals(Object o) { Object target = o; if (o instanceof PersonSeparate) { target = ((PersonSeparate) o).delegate; } return this.delegate.equals(target); } public int hashCode() { return this.delegate.hashCode(); }
Looking at the different implementations of equals
andhashCode
helps us to see how they are different. The "Generate" equality approach generates the equals method for this new class using NetBeans's standard generation mechanism. Because I have the source set to JDK 1.7 in my NetBeans project, it takes advantage of the new (to Java 7) Objects class. The "Generate" approach also generates its hash code as if done for an all-new class rather than relying explicitly on the delegate's hash code. The other two "Equality" approaches ("Delegate" and "Separate") for generating a "Wrapper" both simply return the delegate's hash code.
The "Equality" setting for "wrapper" local extensions of Equality "Delegate" or "Separate" lead to the same implementations of equals
and hashCode
. The difference between the two is that "Separate" adds a new method (not part of the Objects
contract) called equalsXXXXXXXX where the XXXXXXXX represents the generated class's name [so it is called equalsPersonSeparate(PersonSeparate) in this case]. Note that this new method does not accept an Object
like equals
, but expects its own type.
NetBeans 7.2's online help covers all of this in more detail under "Introduce Local Extension Dialog Box." The following is an excerpt from that section.
- Equality. Select one of the following options to set how the equals and hashCode methods should be handled:
- Delegate. Select to delegate to the equals and hashCode methods of the original class.
- Generate. Select to generate new equals and hashCode methods using the IDE code generator.
- Separate. Select to separate the equals method into two. A new method is added to check if the original class equals the extension class.
Before ending this post, I want to point out how easy it is to use NetBeans to identify differences between the generated files. The next screen snapshot shows what it looks like when one right-clicks on the tab in the source code editor for the PersonDelegate.java
class/file. This brings up the drop-down menu that includes "Diff To ..." as an option.
I'm then presented with the option of the file to diff PersonDelegate
to in either the same package (or browser allows arbitrary location to be specified) or opened in the editor window. In this case, I have selected PersonGenerate
in the right side choices of files already open in the editor.
NetBeans displays the differences between PersonDelegate
and PersonGenerate
as shown in the next screen snapshot.
Most of the changes indicated by the colored bars on the far right are changes in class names. However, as the image above shows, the substantial changes are in the equality methods equals
and hashCode
.
The NetBeans diff shows that the primary difference between "Delegate" and "Separate" Equality in the corresponding generated Wrapper classes is the new method in the "Separate" class.
Finally, NetBeans shows that the differences between the "Generate" and "Separate" wrapper implementations.
Using NetBeans 7.2's "Wrapper" approach to the "Introduce Local Extension" refactoring provides an easy mechanism for employing the delegation pattern (as commonly understood these days rather than the more historically based use of it described in The Gang of Four is Wrong and You Don't Understand Delegation).
ConclusionThis post has focused on use of NetBeans 7.2's ability to automatically generate code based on the "Introduce Local Extension" refactoring using implementation inheritance ("Subtype") and composition ("Wrapper"). Along the way, the post also examined NetBeans's handy file differencing ("diff") capability.
No comments:
Post a Comment