bigfocalchord
bigfocalchord

Reputation: 563

Generalising methods of same logic in Java

Hi suppose we have a class which contains many array lists and contains very similar methods as below

public class Foo {
    private ArrayList<Rapper> rapperList = new ArrayList<>();
    private ArrayList<Musician> musicianList = new ArrayList<>();
    private ArrayList<Stadium> stadiumList = new ArrayList<>();

    public List<String> getRapperName() {
        ArrayList<String> rapperNameList = new ArrayList<>();
        for (Rapper r : rapperList) {
            rapperNameList.add(r.getRapperName());
        }
        return rapperNameList;
    } 

    public List<String> getMusicianName() {
        ArrayList<String> musicianNameList = new ArrayList<>();
        for (Musician m : musicianNameList) {
            musicianNameList.add(m.getMusicianName());
        }
        return musicianNameList;
    } //done

    public List<String> getStadiumID() {
        // exactly the same as above (using same logic)
    }

}

I have omitted the getStadiumID method code since it is quite similar to the ones above and all methods follow the same logic:

I feel like there is a lot of repeated code since I am essentially following the same logic and was wondering if there is a way of simplifying these methods into one. I was looking at generics in java but can't seem to get anything out of that.

Assume that my knowledge is only upto Java 7 (Anything above 7 I don't know lambdas and such)

Upvotes: 4

Views: 1678

Answers (4)

Namrata Shukla
Namrata Shukla

Reputation: 157

You can also make your class as a generic class that could be used for handling any object (Rapper, Musician, Stadium...)

public class GenericsDemo<T> {
private ArrayList<T> genericList = new ArrayList<>();

public ArrayList<T> getGenericList() {
    return genericList;
}

public void setGenericList(ArrayList<T> genericList) {
    this.genericList = genericList;
}

public List<String> getGenericName() {
    ArrayList<String> genericNameList = new ArrayList<>();
    for (T obj : genericList) {
        Field field = obj.getClass().getDeclaredField("name");
        Object nameValue = field.get(obj);
        genericNameList.add(nameValue );
    }
    return genericNameList;
}

}

from main class you create object of this class and use it for any type.

class GenericsMain {
GenericsDemo<Rapper> rapperObj = new GenericsDemo<>();
//create arraylist of any object
//call the setter method
//call the getname method

}

Upvotes: -1

Jhilton
Jhilton

Reputation: 405

OK... Sure you can use generics, but you might need to change up a couple of things.... I will be using Java 7 just in case, but with Java 8 things can get a little easier.

First going directly to the class you want to rewrite:

public class Foo<T extends NamedObject> { 
//We will get to what is NamedObject in a moment
    private ArrayList<T> list = new ArrayList<>();

    public List<String> getNames() {
        ArrayList<String> nameList = new ArrayList<>();
        for (T r : list) {
            nameList.add(r.getName());
        }
        return nameList;
    } 
}

Now you should be able to use the class like this, having one little compile error (Why? Because we havent got around about what NamedObject is supposed to be):

Foo<Rapper> rapperObj = new Foo<>();
rapperObj.getNames();
Foo<Musician > musicianObj = new Foo<>();
musicianObj.getNames();

We can define NamedObject (feel free to change the name for something that suits you) as an interface that defines one "getName" method, and make Rapper, Musician and any class that holds a name implement this method. Having "rapperName" in Rapper class, and "musicianName" in Musician class sounds a little redundant, having just "name" in Rapper should be enough for every one interpret it like the rapper's name (Don't you think) and makes it easier for us too.

public interface NamedObject {
    String getName();
}

public class Rapper implements NamedObject {
    //Instead of rapperName put only name
    @Override
    public String getName() { ... }
    ... //All the things you have in this class
}

Didn't had the time to test it, but this should do the trick.

Note: There might be actually another way to solve it without the interface, using Foo as an abstract class, and having one subclass for each object type

Upvotes: 1

Andy Turner
Andy Turner

Reputation: 140319

Essentially, what you are doing here is mapping every element in a list:

return list.stream().map(fn).collect(Collectors.toList());

You can wrap this in a method taking List<T> list and Function<T, S> fn, where S is the type of the elements of the list (e.g. String in the question code).

private static <T, S> List<S> makeList(List<T> list, Function<T, S> fn) {
  return list.stream().map(fn).collect(Collectors.toList());
}

You can then invoke like:

public List<String> getRapperName() {
  return makeList(rapperList, Rapper::getRapperName);
}

public List<String> getMusicianName() {
  return makeList(musicianList, Musician::getMusicianName);
}

You can implement this in pre-Java 8 code; but it won't be pretty.

First, you need a Function interface. You can either use the one from Guava, some other library; or just define one yourself if you don't want to take the dependency:

interface Function<T, S> {
  S apply(T input);
}

Then define the makeList method:

private static <T, S> List<S> makeList(List<T> list, Function<T, S> fn) {
    ArrayList<String> result = new ArrayList<>();
    for (T t : list) {
        result.add(fn.apply(t));
    }
    return result;
}

and then invoke like:

public List<String> getRapperName() {
  return makeList(rapperList, new Function<Rapper, String>() {
    @Override public String apply(Rapper r) {
      return r.getRapperName();
    }
  });
}

public List<String> getMusicianName() {
  return makeList(musicianList, new Function<Musician, String>() {
    @Override public String apply(Musician m) {
      return m.getMusicianName();
    }
  });
}

What a lot of messy syntax.

You can make it look a bit cleaner by pulling the Functions out into constants and so forth; but honestly, you've got more code here (and non-idiomatic code, at that) than just duplicating the code as you have currently.

Upvotes: 2

OldCurmudgeon
OldCurmudgeon

Reputation: 65821

In Java 8 we would use lambdas. This can be recoded in Java 7 like this:

public class Foo {
    private ArrayList<Rapper> rapperList = new ArrayList<>();
    private ArrayList<Musician> musicianList = new ArrayList<>();
    private ArrayList<Stadium> stadiumList = new ArrayList<>();

    public List<String> getRapperName() {
        return asStringList(rapperList, new StringGetter<Rapper>() {
            @Override
            public String get(Rapper it) {
                return it.getRapperName();
            }
        });
    }

    public List<String> getMusicianName() {
        return asStringList(musicianList, new StringGetter<Musician>() {
            @Override
            public String get(Musician it) {
                return it.getMusicianName();
            }
        });
    }

    interface StringGetter <T> {
        String get(T it);
    }

    private <T> List<String> asStringList(List<T> list, StringGetter<T> getter) {
        List<String> stringList = new ArrayList<>();
        for(T t: list) {
            stringList.add(getter.get(t));
        }
        return stringList;   
    }

}

Upvotes: 0

Related Questions