1
\$\begingroup\$

I am writing some examples of Java programs on GitHub rsp/ppj for people to learn and I want them to be as simple and compact as possible, to work on Java 1.7.x (a.k.a. Java 7) and to not use any non-standard libraries that need installation.

This question is about my WhereEven example:

a function of an int[] array that returns an int[] array with only even numbers

(The method can internally use any other data types if it makes sense.)

Here is how my example looks like right now:

static int[] even(int[] t) {
    int i, j;
    for (i=j=0; i < t.length; i++)
        if (t[i] % 2 == 0) j++;
    int[] r = new int[j];
    for (i=j=0; i < t.length; i++)
        if (t[i] % 2 == 0) r[j++] = t[i];
    return r;
}

I am not satisfied with that code because it is much more complex than the same in JavaScript - even using the old ES5 syntax (few years older than Java 7) it is:

function even(t) {
    return t.filter(function(x){ return x % 2 == 0; });
}

It is even better in Scheme, which is amazing considering the fact that this is a language that's 40 years old:

(define (even t)
    (filter even? t))

But I can't get to the solution that is even remotely as simple and readable in Java. Of course I can add comments and more meaningful variable names, but the point is that in Scheme and JS the code is simple and the meaning is obvious as it is. What I am looking for is a way to write a better code, not to write the same code but with better names.

What in your opinion is "the best" way to improve the above code to be most useful for an educational example like this? Maybe the shortest way in terms of lines/characters, the most readable way, the most idiomatic way, the way using best practices, the most crazy way? I am looking forward to every suggestion of my code improvement that would help people learn something about Java and maybe I'll put few different approaches in my tutorial.

The only requirement is taking an int[] array, returning a new int[] array (but internally the method can use any other data types if it makes sense), working on Java 1.7.x (a.k.a. Java 7) and not using any non-standard classes.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ Use Java8, and before you say not, then consider that you are comparing against ES6 which was only released after Java8 (Java 8 in March 2014 - 18 months ago - ES6 June 2015 - 4 months ago). So, Use current technology, luke. \$\endgroup\$
    – rolfl
    Nov 16, 2015 at 22:55
  • \$\begingroup\$ Note that an int[] array may not be the idiomatic data structure to use in the first place. \$\endgroup\$ Nov 16, 2015 at 22:58
  • \$\begingroup\$ See github.com/rsp/ppj/issues/1 for an answer I was writing, when they closed it as off-topic. \$\endgroup\$
    – holroy
    Nov 16, 2015 at 22:59
  • \$\begingroup\$ @200_success Thanks for your comment. I improved the question to address the issues raised in the comments and to conform to the guidelines that you referenced. I hope it can be reopened now. Thanks. \$\endgroup\$
    – rsp
    Nov 17, 2015 at 8:00
  • 1
    \$\begingroup\$ IntStream.of(t).filter(i -> (i & 1) == 0).toArray() <- done. \$\endgroup\$
    – rolfl
    Nov 17, 2015 at 11:22

2 Answers 2

4
\$\begingroup\$

First... choose the right problem or choose what you want them to learn

Let me first question your assumptions (pure Java 7, no "extra" libraries and array filtering). I do so under the premise that you want your students not just learn "pure Java 7" (ain't best idea neither commercially nor academically, nor will it make them greater programmers).

Also, why I keep typing "extra libraries" in "":

  • most of the projects now have Guava or Apache Commons
  • new Java has lambdas (for a reason and due to serious demand)
  • there are at least 2 popular libraries that enable Java 7 to use FP-like programming style: Javaslang, Lambda4Java (IIRC)

This is not "for fun", but because there was need for it. Among mainstream languages none treat "backward compatibility" so seriously as Java does. Despite deprecating APIs since version 1.1, they have yet to cut a thing. This means, there's lots of OLD stuff there.

Also, the problem you have (filtering data) is VERY FP friendly. FP languages among first operations have map and filter. Java at it's inception targeted C++ people, who wrote programs iteratively (for loops, step-by-step instructions to solve problems). FP works on streams and pipelines (data flows, and while it does, we do things).

So, yes, Scheme (FP language) is awesome for your problem. So is anything that has decent FP features (JS can be argued so).

Pure Java, without Apache Commons, Javaslang, Lambdas etc. will be hard-pressed for it:

  • you want to filter data
  • you want to use arrays (in Java objects are central concept, arrays are closer to primitives)
  • in Java, Collections are arrays equivalents of objects

Summarizing this long intro:

Filtering is FP-concept, FP-friendly languages or strict FP languages are therefore better. If you choose examples like that and want to work with Java, work with Java Collections, libraries geared towards filtering/collections/data pipelines and Java 8 that introduces Lambdas.

Java 8 and streams

First proposition for you, uses streams, introduced in Java 8. There's a library that tries to make lambdas (and perhaps streams?) available in Java 7, so you may be able to show same code with it.

static void java8even(int[] t) {
    IntStream iWantEvens = IntStream.of(t);

    iWantEvens
        .filter(i -> i % 2 == 0)
        .forEach(new IntConsumer() {
            @Override
            public void accept(int value) {
                System.out.println(value);
            }
        });
}

I build a java.util.Stream with Stream.of(input array). Then I filter the stream and using forEach I on the fly create an IntConsumer that just prints the value. forEach performs an action that you define on every stream element. I could've used toArray and then function would - instead of having side effect - return an array, which I'd then have to print in main, iterating over it. It might look simpler and be 1-2 lines terser, but it's less memory efficient.

Answer within all your limits

With all that said, Java 7, no "extra" libs:

/**
 * Created by LAFK on 17.11.15.
 */
public class EvenFilter {

    static ArrayList evens = new ArrayList();

    public static void main(String[] args) {
        int[] simpleTable = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

        for (int i: simpleTable) {
            if (i%2 == 0) {
                evens.add(i);
            }
        }
        System.out.println(evens);
    }
}

I used ArrayList without any type-safety. I then iterated over input array and added elements meeting criteria to ArrayList. Then, I printed out ("returned") ArrayList.

Why?

  • Java ain't that great with arrays (try printing an array without iterating over it's elements, weak API, fixed length of an array, while Java Lists can grow and shrink over time)
  • even more so with primitives, like int, try getting that into collections!
  • Java API for collections ain't that great as well, compared to modern languages or FP ones, but it's better than what you can do with just array
  • I don't need the fixed size with lists

I think similar thing can be done with a StringBuffer and were you to process something more text-oriented, that would be better choice.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for an amazing answer. +1, I think I'll add both Java 7 and Java 8 to my examples. Reading your answer was well worth losing my own reputation for posting this question and getting it voted down. Thanks! \$\endgroup\$
    – rsp
    Nov 17, 2015 at 11:54
2
\$\begingroup\$

It's been a while since I dabbled in Java, but if you don't care that much on memory efficiency you could use the first half of this code, and if you need for the returned array to be of exact size use also the second part to return only an array of given size:

static int[] even(int[] source) {
    int even_count;

    // Loop and calculate remainder only once, into tmp array
    int[] tmp = new int[source.length];
    for (i = even_count = 0; i < source.length; i++) {
        if (source[i] % 2 == 0) {
           tmp[even_count++] = source[i];
        }
    }

    // Make exact size copy
    int[] result = new int[even_count];
    System.arraycopy(tmp, 0, result, 0, evencount);

    return r;
}

Code is untested, but should work, I think. And use braces, not one-liners which hinders readability a lot. Better naming is also good. Especially when writing code which is supposed used as example code, you should strive for code which is easy to read and understand.

\$\endgroup\$
2
  • \$\begingroup\$ Good use of array copy and nice heads up about memory efficiency. Similarly, since we're creating new array anyway, we can use Arrays.copyOf, as it uses array copy underneath and creates new array (system method only copies into existing one). \$\endgroup\$ Nov 18, 2015 at 10:28
  • \$\begingroup\$ Thanks for your answer - both for the code and the advice on naming and formatting. The original goal of my question was just to improve my example but now I think that I will write a tutorial explaining the process of making incremental improvements step by step, starting from your advice and then going through examples by @LIttleAncientForestKami to finally settle on the most advanced example of using Java 8 features. Thanks. \$\endgroup\$
    – rsp
    Nov 18, 2015 at 11:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.