Forum Stats

  • 3,826,747 Users
  • 2,260,702 Discussions
  • 7,897,071 Comments

Discussions

To extend ArrayList, or to not extend ArrayList

807580
807580 Member Posts: 33,048 Green Ribbon
edited Jun 29, 2010 3:50PM in Java Programming
Hey guys,

I'm trying to figure out best practices for some lists that I am working with and was hoping someone could set the record straight. It would seem that the best way for me to do what I want to do is to extend ArrayList, but I have read around that it's not the best practice. Basically, I'm working with some lists and want to pull out certain data from them, or set some of the data that's not set, or change some of the data depending on what it says. A good example is the one below, where say I have a list of cats and want to find out which one is the biggest. Excuse the extreme amount of code, but I want to get my point across. Here are three different ways to do this, first being extending ArrayList, next being making a new class with an internal ArrayList, and the last is just having a method in the main class or some other class to complete the task. I've also shown how you'd use them:
public class CatList1 extends ArrayList<Cat>
{
    public int maxSize()
    {
        int max = 0;
        for(Cat c: this)
        {
            if(c.getSize() > max)
                max = c.getSize();
        }
        return max;
    }
}
public class CatList {
    ArrayList<Cat> myCats = new ArrayList<Cat>();

    public int maxSize()
    {
        int max = 0;
        for(Cat c: myCats)
        {
            if(c.getSize() > max)
                max = c.getSize();
        }
        return max;
    }
}
public class NewClass
{
    public static void main(String[] args)
    {
        NewClass go = new NewClass();
        
        //extend ArrayList
        CatList1 someCats = new CatList1();
        Cat fluffy = new Cat();
        someCats.add(fluffy);
        int largest = someCats.maxSize();

        //new CatList object with internal ArrayList
        CatList moreCats = new CatList();
        Cat socks = new Cat();
        moreCats.myCats.add(socks);
        largest = moreCats.maxSize();
        
        //method in our main class
        ArrayList<Cat> lotsOfCats = new ArrayList<Cat>();
        Cat jack = new Cat();
        lotsOfCats.add(jack);
        largest = go.maxSize(lotsOfCats);
    }

    public int maxSize(ArrayList<Cat> cats)
    {
        int max = 0;
        for(Cat c: cats)
        {
            if(c.getSize() > max)
                max = c.getSize();
        }
        return max;
    }
}
There might be some syntax and other errors in there (i.e. using main to do stuff) and I know I probably didn't encapsulate everything (myCats comes to mind), but you get the idea. So is any of these the best way to do this? Extending ArrayList seems to make things the easiest. The second technique adds an extra level of referencing when you start working with the list itself. The third just seems messy since you're not tying the method to any relevant object. Is there a better way to do this? I read something somewhere about using delegation methods, but not sure what that is and if it's supported in netbeans.
«1

Comments

  • 807580
    807580 Member Posts: 33,048 Green Ribbon
    You might want to read this article
  • 796440
    796440 Member Posts: 19,179 Gold Trophy
    Why extend ArrayList? Is it because the functionality you're creating is really for a special kind of ArrayList, and doesn't make sense in a LinkedList? Does it only make sense for Lists, not for other Collections, like Sets? If it really is functionality that only makes sense as a special kind of ArrayList, then yes, extending ArrayList is the right approach. Otherwise, it's almost certainly not.

    In particular, if you're talking about finding a max value, then it definitely doesn't sound like a job for an extended AL. Clearly we can perform the same operation on a LinkedList or a HashSet.

    So the next question is, do you extend Collection, say with StatsCollection, that adds methods like biggest(), smallest(), etc.? Or do you create a separate class that accepts collections as arguments to its methods and performs operations on them? To my mind, the job of a Collection, List, Set, etc. is to be a container for multiple items of the same type. If you want to operate on the contents of those items--in ways that depend on those items' types, after all, the way measure the size of a Cat is not going to be the same as measuring the size of a String or a Date when looking for "biggest"--that seems to me like the job of a separate class.
  • 807580
    807580 Member Posts: 33,048 Green Ribbon
    jverd, thank you for the response. You certainly make good points, and it's true that for my implementation the functionality I am looking for does not only make sense as a special kind of ArrayList. It's just tough to think of where the functionality should actually go. Inherently, my first thought is that if you have an object holding a group of something, and you need to perform a fairly simple task on that group, why not let the object holding the group do it? It doesn't seem like that's going to hurt anyone else upfront, but if your program is truly supposed to be expandable, I suppose this isn't the best approach.

    George, I don't really think that article is applicable to me. I am certainly not looking to just cut down on how complicated the definitions of things are. If you'll see my post count, I still am quite the newb when it comes to Java and I really am just trying to figure out the basics of good designing. It seems that just putting that method in some other relevant object would make the most sense. Either that, or learn about collections and such since I really haven't touched them yet. I did however crack open the java book I have to the "collections with generics" section which I haven't read yet; it will probably help out a bunch.
  • 807580
    807580 Member Posts: 33,048 Green Ribbon
    You shouldn't write most of that code at all. Use java.util.Collections.max instead. All you'd write is the Comparator.
  • 796440
    796440 Member Posts: 19,179 Gold Trophy
    blizzak wrote:
    jverd, thank you for the response. You certainly make good points, and it's true that for my implementation the functionality I am looking for does not only make sense as a special kind of ArrayList. It's just tough to think of where the functionality should actually go. Inherently, my first thought is that if you have an object holding a group of something, and you need to perform a fairly simple task on that group, why not let the object holding the group do it?
    Yeah, that makes sense, and my thoughts kind of go that way too. It's why I get annoyed that I have to do Collections.sort(list) instead of list.sort().

    The thing is, there's a slippery slope, and once you add a bit of functionality--albeit valid, related functionality--then you can add a little more, and a little more, and next thing you know, you've got a "god object." We have to draw the line somewhere, and since we're using standard classes that already have that line drawn, I'd tend to go with leaving their line intact.

    It's certainly not a hard science, and there's no one right answer.

    If I were designing my own collections framework up front, might I add methods like sort, min, max, average, frequency, and so on? Maybe. It seems like a handful of those most common methods could make sense to exist on the collections themselves. However, those classes already have a fairly large number of methods, so adding another half dozen or more gets us to the point of bloat. Plus then we come back to the "one responsibility" principle. The collection's job is basically just to store the values. It seems to make sense to draw the line there and create separate classes for various kinds of operations on those values.

    However, I will add one more wrinkle, just to muddy the waters a bit more and give us all something to ponder over our Friday evening libations. (What, you guys don't discuss OO principles when you're hanging out at a bar, trying to pick up chicks? Weirdos.)

    What I said before about creating a separate class to perform the operations--and in fact the whole "one responsibility" principle--is somewhat in contention with another OO tenet: Tell, don't ask.

    [http://pragprog.com/articles/tell-dont-ask]
    [http://stackoverflow.com/questions/169450/arent-information-expert-tell-dont-ask-at-odds-with-single-responsibility-pri]
    [http://www.google.com/search?q=tell+don%27t+ask+OO]

    That is, "tell, don't ask," which is basically encapsulation and data hiding, says we shouldn't be asking the Collection for its data so that we can operate on it. We should instead tell the Collection what we want it to do. It then knows the best way to use its data to do that for us. So by this principle, it seems the Collection should have these methods.

    As is usually the case, there is no one right, cut-and-dried answer. Each principle has its place, and neither one is absolute.

    Personally I would go with "tell, don't ask" for the stuff that's already there--adding, searching, iteration, etc.--and "one responsibility" for the various manipulations--sorting, max, min, etc.
  • 807580
    807580 Member Posts: 33,048 Green Ribbon
    edited Jun 26, 2010 5:28PM
    Here is another possible solution to this issue. It implements List. Just add your additional custom functions you need to it. Note this may be better approach of your original idea of extending arrayList since List is an interface that may callers of any java collection may want to deal with rather than a specific implementation of a list such as arrayList. It also allows you to change to some other type of collection than arrayList later on if you find a better collection without the callers having to change their code. List only exposes 23 functions that callers typically use whereas ArrayList exposes 32. The downside is you need to implement the List functions in order to implement it (as was done below). Personally, I never use the below code and if I was you I wound use it sparingly. In most cases, I just return a List object to the caller and hide the type of colection it uses to hold the objects.

    Also, note I call the class Cats and not CatList. I think its bad form to have the word 'List' in the class name. The fact that Cats is plural implies its some type of collection without having to have the word List in it.
    package cats;
    
    
    import java.util.Collection;
    import java.util.Iterator;
    import java.util.List;
    import java.util.ArrayList;
    import java.util.ListIterator;
    
    
    public class Cats implements List<Cat> {
    
    
    List<Cat> cats= new ArrayList<Cat>();
    
    
    
    
    @Override
    public boolean add(Cat cat) {
    return cats.add(cat);
    }
    
    
    @Override
    public void add(int index, Cat cat) {
    cats.add(index, cat);
    }
    
    
    @Override
    public boolean addAll(Collection<? extends Cat> catItems) {
    return cats.addAll(catItems);
    }
    
    
    @Override
    public boolean addAll(int index, Collection<? extends Cat> catItems) {
    return cats.addAll(index, catItems);
    }
    
    
    @Override
    public void clear() {
    cats.clear();
    }
    
    
    @Override
    public boolean contains(Object cat) {
    return cats.contains(cat);
    }
    
    
    @Override
    public boolean containsAll(Collection<?> catItems){
    return cats.containsAll(catItems);
    }
    
    
    @Override
    public Cat get(int index) {
    return cats.get(index);
    }
    
    
    @Override
    public int indexOf(Object index) {
    return cats.indexOf(index);
    }
    
    
    @Override
    public boolean isEmpty() {
    return cats.isEmpty();
    }
    
    
    @Override
    public Iterator<Cat> iterator() {
    return cats.iterator();
    }
    
    
    @Override
    public int lastIndexOf(Object cat) {
    return cats.lastIndexOf(cat);
    }
    
    
    @Override
    public ListIterator<Cat> listIterator(){
    return cats.listIterator();
    }
    
    
    @Override
    public ListIterator<Cat> listIterator(int index) {
    return cats.listIterator(index);
    }
    
    
    @Override
    public boolean remove(Object index) {
    return cats.remove(index);
    }
    
    
    @Override
    public Cat remove(int index) {
    return cats.remove(index);
    }
    
    
    @Override
    public boolean removeAll(Collection<?> catItems) {
    return cats.removeAll(catItems);
    }
    
    
    @Override
    public boolean retainAll(Collection<?> catItems) {
    return cats.retainAll(catItems);
    }
    
    
    @Override
    public Cat set(int index, Cat cat) {
    return cats.set(index, cat);
    }
    
    
    @Override
    public int size() {
    return cats.size();
    }
    
    
    @Override
    public List<Cat> subList(int fromIndex, int toIndex) {
    return cats.subList(fromIndex, toIndex);
    }
    
    
    @Override
    public Object[] toArray() {
    return cats.toArray();
    }
    
    
    @Override
    public <T> T[] toArray(T[] cat) {
    return cats.toArray(cat);
    }
    
    
    }
  • 796440
    796440 Member Posts: 19,179 Gold Trophy
    njb7ty wrote:
    Here is another possible solution to this issue. It implements List.
    This is kind of what I was talking about when I said, "So the next question is, do you extend Collection..."

    I have a couple of suggestions. It's of course up to the OP whether they are appropriate for him.

    1) Don't just define a concrete class that implements a particular Collection. Instead, if you're going to go this route (which I still don't necessarily think is the best), define a type via a new interface, say, CalculatingCollection <E> extends Collection<E>, or, CalculatingList<E> extends List<E>. Define the types on that interface.

    2) Even if you don't define an interface, and go right for a class, do it something like CalculatingCollection<E> implements Collection<E>, and then take a Collection as a constructor arg. This is basically the Decorator Pattern.

    3) Is your new functionality only intended to deal with one class, like Cat? If so, you can change the <E>s above to <Cat> or <E extends Cat>. If it's intended to handle other types--Dog, Human, Date, String, Integer, etc.--you'll have to decide how you want to determine "size" or whatever you're calculating min, max, etc. on.
  • 807580
    807580 Member Posts: 33,048 Green Ribbon
    edited Jun 27, 2010 4:28AM
    blizzak wrote:
    I'm trying to figure out best practices
    Rule number one in OO is to separate type from implementation. You design in terms of type and then you're free to change implementation at will.

    In your case this means making CatList an interface. Now if you feel the list you had in mind corresponds well with one of the collection interfaces you can define CatList in terms of that, like
    public interface CatList extends List<Cat>  {}
    // or
    public interface CatList extends Collection<Cat>  {}
    // or roll your own
    public interface CatList {
       // 
    }
    The next step is to provide a suitable implementation for CatList, like
    class CatList_1 implements CatList extends ArrayList<Cat> {
       // suitable use of the inherited ArrayList to implement CatList
    }
    This implementation class has default access so it can be hidden in a package. To get a CatList object users call a static public creation method like,
    public static CatList createCatList(.....) { // create object of public type
       return new CatList_1();   // package private implementation
    }
    This approach will cleanly separate the type from the implementation. Types will be public, implementations will be package private. Still an off-the-shelf implementation in the form of a standard collection it used for convenience. It can be replaced later with some other collection or your more suitable own implementation.
  • 807580
    807580 Member Posts: 33,048 Green Ribbon
    Guys, I'd like to thank you all for your responses, they all have given me some very good insight into how to approach this. There's lots of suggestions here which seem like they'll keep things very generic and robust but I think I'm going to tell myself to KISS. And after reading through the collections with generics section of the book I have, I think that means just writing some comparators. Never written them before, but I'll have to do a couple for maxes and mins and some sorting no doubt. We'll see how it goes.

    Hopefully anyone in the future who thinks of extending ArrayList will find this thread and it'll really open their eyes hah.
  • 807580
    807580 Member Posts: 33,048 Green Ribbon
    Comparators are awesome, thank you guys for steering me in the right direction. One little question though, do I have to put the @Override in the comparator below the class definition? See below:
    class SizeCompare implements Comparator<Cat>
        {   
            //@Override
            public int compare(Cat one, Cat two)
            {
                return one.getSize().compareTo(two.getSize());
            }
        }
    Netbeans gives me a warning if I don't, but everything works fine. Any harm in leaving it out? I'll likely put it in just to suppress that warning...
This discussion has been closed.