Blog-Archiv

Donnerstag, 11. August 2016

Optimize Java Code Using Inner Classes

Java is an object-oriented language providing everything for code-reusage except multiple inheritance in implementation-classes. Nevertheless even skilled programmers sometimes have problems to avoid code duplications. One of the cases I saw very often is caused by the fact that it is impossible to return more than one value from a Java method (like we can pass several parameters to it).

Example

Imagine an application that needs to find the lowest index of several alternative strings in a text. For example, we need to find one of "select", "from", "where" in a query text, and we need to find the one that is leftmost. We want to know

  1. the string that matched, and
  2. its index.

To be able to do this more than once (to find all of "select", "from", "where" sequentially), we pass a search-start-index as parameter.

Here is an implementation that I called SearchMultipleTerms1.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class SearchMultipleTerms1
{
    private String text;
    private String [] searchTerms;
    
    public SearchMultipleTerms1(String text, String [] searchTerms) {
        this.text = text;
        this.searchTerms = searchTerms;
    }
    
    public int getLowestIndex(int searchStartIndex) {
        final String matchingTerm = getMatchingTerm(searchStartIndex);
        return text.indexOf(matchingTerm, searchStartIndex);
    }
    
    public String getMatchingTerm(int searchStartIndex) {
        return indexOf(searchStartIndex);
    }
    
    private String indexOf(int startIndex) {
        int leftMostIndex = Integer.MAX_VALUE;
        String leftMostToken = null;
        for (String searchTerm :  searchTerms)    {
            final int i = text.indexOf(searchTerm, startIndex);
            if (i >= 0 && i < leftMostIndex)    {
                leftMostIndex = i;
                leftMostToken = searchTerm;
            }
        }
        return leftMostToken;
    }

}

The constructor receives the text to search and the search-terms. It provides the search through the public methods getLowestIndex() and getMatchingTerm(). Mind that the indexOf() implementation can not break the loop at the first match, because it needs to find the match with the lowest index, thus it needs to try all search-terms.

Now this looks quite straightforward, but it has a small code-smell (that I use here to demonstrate how useful inner classes are:-). The implementation of getLowestIndex() first searches the matching term, and then, again, estimates its index in text, although the preceding search already did this. This is not only a code-duplication, it is also a performance-leak, because text.indexOf() is called twice.

Reason for that problem is that the indexOf(startIndex) method can return just one value, not both index and term.

Refactoring

To fix this I create an inner class that holds both index and the matched term. I use a static inner class, because I do not need access to instance-fields of the outer object.

    private static class SearchTermAndIndex
    {
        public final int index;
        public final String searchTerm;
        
        public SearchTermAndIndex(int index, String searchTerm) {
            this.index = index;
            this.searchTerm = searchTerm;
        }
    }

Mind that this class does not violate encapsulation principles because the member fields are public. The final modifier makes them immutable, thus nothing can change them after construction, and we don't need to provide public getter methods.

Now the indexOf() method must be re-written to return an instance of that class.

    private SearchTermAndIndex indexOf(int startIndex) {
        int leftMostIndex = Integer.MAX_VALUE;
        String leftMostToken = null;
        for (String searchTerm :  searchTerms)    {
            final int i = text.indexOf(searchTerm, startIndex);
            if (i >= 0 && i < leftMostIndex)    {
                leftMostIndex = i;
                leftMostToken = searchTerm;
            }
        }
        return new SearchTermAndIndex(
                (leftMostToken == null) ? -1 : leftMostIndex,
                leftMostToken);
    }

Of course also the callers of indexOf() must be re-written to use the inner class.

Here is the complete new implementation, called SearchMultipleTerms2.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class SearchMultipleTerms2
{
    private String text;
    private String [] searchTerms;
    
    public SearchMultipleTerms2(String text, String [] searchTerms) {
        this.text = text;
        this.searchTerms = searchTerms;
    }
    
    public int getLowestIndex(int searchStartIndex) {
        return indexOf(searchStartIndex).index;
    }
    
    public String getMatchingTerm(int searchStartIndex) {
        return indexOf(searchStartIndex).searchTerm;
    }
    
    
    private static class SearchTermAndIndex
    {
        public final int index;
        public final String searchTerm;
        
        public SearchTermAndIndex(int index, String searchTerm) {
            this.index = index;
            this.searchTerm = searchTerm;
        }
    }
    
    
    private SearchTermAndIndex indexOf(int startIndex) {
        int leftMostIndex = Integer.MAX_VALUE;
        String leftMostToken = null;
        for (String searchTerm :  searchTerms)    {
            final int i = text.indexOf(searchTerm, startIndex);
            if (i >= 0 && i < leftMostIndex)    {
                leftMostIndex = i;
                leftMostToken = searchTerm;
            }
        }
        return new SearchTermAndIndex(
                (leftMostIndex == Integer.MAX_VALUE) ? -1 : leftMostIndex,
                leftMostToken);
    }

}

Summary

Don't hesitate to create inner classes, that's the way it should be in an object-oriented language. Make them static by default, unless you need the fields of the outer object.




Keine Kommentare: