4

I'm trying to figure out how I can get an output of remaining slots available when 1 object is removed.

    ListOfMembers = new ArrayList<>(100); which caps my list at 100.

Whenever I delete 1 from the list, I need to print out the remaining space in the ArrayList.

    public boolean DeleteMember() {
        Scanner in = new Scanner(System.in);
        System.out.println("Enter the membership number: ");
        String pno = in. nextLine();
        for(int i=0;i<ListOfMembers.size();++i) {
            if(ListOfMembers.get(i).getMembershipNumber().equals(pno)) {
                ListOfMembers.remove(i);
                System.out.println("Space available: " +?????);
                return true;
            }
        }
                System.out.println("Numbership number does not exist");
        return false;
    }

Using System.out.println("Space available: " +ListOfMembers.size()) will provide the count of entries and I'm trying to have the opposite.

0xh3xa
  • 4,801
  • 2
  • 14
  • 28
BenKaz
  • 45
  • 5

4 Answers4

4

You seem to misunderstand how arraylist works.

new ArrayList<>(100) does not cap the list. The 100 is merely a hint.

ArrayList is defined as allowing infinite* growth, and it has no facilities to limit how many items can be in them.

ArrayList works 'under the hood' by having an array that holds your elements. The problem is, java does not allow arrays to grow or shrink. ArrayList solves this problem with two tricks:

  1. By keeping track of the length of the ArrayList internally, the ArrayList impl can use an array that is 'too large'.
  2. If you've filled up your arraylist to have as many items in it as the 'backing array is large', and you add another item, arraylist has a problem - the backing array is out of room. ArrayList will then make a new, larger array, copy over all the elements from the old array, now add the new item (as there is room; this new array is larger), and then get rid of the old array.

The only thing that 100 does in your constructor is serve as a hint: How large should the backing array be made initially. Out of the box (just new ArrayList<>()), you get the default hint of 10.

Try it! run this:

List<String> list = new ArrayList<String>(100);
for (int i = 0; i < 200; i++) list.add("" + i);
System.out.println(list.size());

That code will compile fine, run fine, and print 200. Thus proving that the '100' has absolutely nothing to do with capping the size of this list.

So, how DO you cap a size of an arraylist?

You don't. Arraylist cannot do that. Instead, you wrap, or extend. For serious code bases, I strongly recommend wrapping, but for a simple exercise, extending can make your code a little shorter:

public class LimitedList<T> extends ArrayList<T> {
    private final int limit;

    public LimitedList(int limit) {
        this.limit = limit;
    }

    @Override public boolean add(T in) {
        if (size() >= limit) throw new IllegalStateException("List is full");
        super.add(in);
    }

    @Override public boolean addAll(Collection<T> in) {
        // left as an exercise for the reader
    }

    @Override public boolean addAll(int index, Collection<T> in) {
        // left as an exercise for the reader
    }

    @Override public boolean add(int idx, T in) {
        // left as an exercise for the reader
    }

    @Override public List<T> subList(int from, int to) {
        // this one gets complicated!
        // let's just not allow it.
        throw new UnsupportedOperationException();
    }

    public int available() {
        return limit - size();
    }
}

NB: As you can see, you have to be very careful and override every method that may grow the list; this is why making a new type that doesn't extend ArrayList at all, and instead has 1 field of type ArrayList (and 1 field of int for the limit, of course), can be better: Now you explicitly have to think about every method list has, instead of praying you covered all the ones that add things.

*) well, pragmatically speaking, you can't have more than 2^31-1 elements.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • Reading the specification of `List`, it says that implementations can simply not implement `add`; or refuse to add elements based on type; but it doesn't say that a list can refuse to add an element based on its current *size*. Placing a cap on the size thus violates Liskov Substitutability. You can define a `LimitedList implements Collection`, but it can't be a `java.util.List`. – Andy Turner Jul 10 '20 at 19:25
  • 1
    @AndyTurner java's very own List.of() throws an exception if you try to add. You can say 'that breaks liskov' to 99.99% of all answers for anything related to java.util. Perhaps rigid application of such concepts are best left to academic languages like haskell. If you need more proof, Map says that it'll compare keys on equality, but WeakHashMap goes out of its way to say that in sharp contrast to the documented rules, WeakHashMap doesn't. This should be ample proof that the java community and ecosystem doesn't hold much truck with the notion that you __can't__ do this. – rzwitserloot Jul 10 '20 at 19:29
  • Yes: that's covered by "implementations can simply not implement add". You can point to a couple of cases, like WeakHashMap and IdentityHashMap, but these are quite exceptional, and *strongly* documented as not being for general purpose use. – Andy Turner Jul 10 '20 at 19:30
  • @rzwitserloot Great post, I'm thankful for your time about this. – BenKaz Jul 10 '20 at 19:37
  • *If* you can live with violating the specification, a far easier way to implement this is to extend `AbstractList`, because this provides skeletal implementations of a number of methods (e.g. `subList`). You can back it with an `ArrayList`. – Andy Turner Jul 10 '20 at 19:39
1

Reading the specification of List, it says that implementations can simply not implement add; or refuse to add elements based on type, or some property of the element; but it doesn't say that a list can refuse to add an element based on the list's current size. Placing a cap on the size thus violates Liskov Substitutability.

You can define a LimitedSizeList implements Collection, but it can't be a true implementation of java.util.List.

You can easily implement LimitedSizeList by extending AbstractCollection:

class LimitedSizeList<E> extends AbstractCollection<E> {
  private final List<E> list = new ArrayList<>();
  private final int capacity;

  LimitedSizeList(int capacity) {
    this.capacity = capacity;
  }

  // Fill in the methods described in the Javadoc:
  @Override
  public Iterator<E> iterator() { return list.iterator(); }

  @Override
  public int size() { return list.size(); }

  @Override
  public boolean add(E element) {
    // Collection.add does allow you to throw an IllegalStateException
    // https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html#add-E-
    if (remainingCapacity() <= 0) throw new IllegalStateException("Full");
    return list.add(element)
  }

  // You don't have to, but you might want to consider overriding
  // addAll, in order to make trying to add too-large a collection
  // failure atomic (that is, it fails to add any rather than some).

  // And then provide a method to report the free capacity:
  int remainingCapacity() {
    return capacity - size();
  }
}

This is a far cleaner way to approach the problem than attempting to extend ArrayList (not just because of the contract violation, but also for all the reasons to prefer composition over inheritance).

Of course, if you really want it to be an invalid List (or you can guarantee that it won't need to be treated as a general-purpose List), you can instead extend AbstractList: the methods you need to implement are different, but there are relatively few, and they're quite easy. However, violating contracts is a good way to get surprising bugs in surprising places in your code.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
1

Capacity is an internal metric that is used to dynamically increase the available space used by the ArrayList<>() Usually, it is of no consequence to the user how this is managed since it is an internal issue.

However, being able to set the capacity allows the user to specify a value indicative of the initial contents that the list will hold. The advantage is that it allows the list to fill the backing array without having to continually readjust the capacity resulting in a lot of copying of objects.

Adjust capacity

At any time you can also increase the capacity by using the ensureCapacity() method.

Reduce capacity

And you can also release unused capacity by using trimToSize() method. This may free up memory within the JVM.

But none of the above will prevent you from adding addtional entries to the list. They simply allow one to make optimum choices based on a priori knowledge of the data.

WJS
  • 36,363
  • 4
  • 24
  • 39
0

You can use reflection to achieve this task to get the capacity first, then subtract the actual size

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");
        list.add("h");
        try {
            Field field = list.getClass().getDeclaredField("elementData");
            field.setAccessible(true);
            int cap = Array.getLength(field.get(list));
            System.out.println("The capacity: " + cap);
            System.out.println("The size: " + list.size());
            System.out.println("The remaining capacity: " + (cap - list.size()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

, output

The capacity: 10
The size: 6
The remaining capacity: 4
0xh3xa
  • 4,801
  • 2
  • 14
  • 28
  • 2
    Using reflection to bypass the data encapsulation of classes is a real bad idea. And you conveniently omitted the security warnings, one of which was **WARNING: All illegal access operations will be denied in a future release**. This would also fail if a `securityMonitor` was in place. – WJS Jul 10 '20 at 19:48