I am not very proficient in Java. I am using java 1.8. I have the following class structure.
class Parent {
}
class Child1 extends Parent {
}
class Child2 extends Parent {
}
.
.
.
class Childn extends Parent {
}
I am looking to write 2 functions say f1() and f2(). f1() takes a list of a child class objects and converts the list into a list of parent class objects and returns that list. f2() takes the list returned from f1(), or for that matter a list of any child class objects, and converts it back to the list of original child class objects.
// childlist is a list of objects of any child class
function parentlist f1(List childlist) {
convert list to list of objects of Parent class
return list as parentlist
}
function childlist f2(List parentlist) {
converts list to list of original child class objects with which the parentlist was created
return the list as childlist
}
The functions needs to be generic, not involve casts, and should work for any child class. How can this be done?
The reason why f1
is confusing is that this is never actually necessary in a well-designed system. If you find yourself wanting to upcast a list in order to pass it to a method, that method should accept List<? extends Parent>
instead of List<Parent>
.
The reason why f2
is confusing is that it is fundamentally unsafe, there is no way to determine what generic type a list was initially created with. This is because generics are enforced by the compiler, and generally don't actually exist at runtime. The generic type of parameters can be examined with reflection, but that's about it.
In any case, let me take a crack at it. For this question to be satisfiable, your methods need to be updated to accept the type of the returned list.
import java.util.List;
import java.util.ArrayList;
public final class ListCast {
/** Upcast a list of elements whose classes subclass "parentClass" */
public static <P> List<P> f1(Class<P> parentClass, List<? extends P> childList) {
List<P> ret = new ArrayList<>(childList.size());
for (P p : childList) ret.add(parentClass.cast(p));
return ret;
}
/** Downcast a list of elements which are known to be instances of "childClass" */
public static <P, C extends P> List<C> f2(Class<C> childClass, List<P> parentList) throws IllegalArgumentException {
List<C> ret = new ArrayList<>(parentList.size());
for (P next : parentList) {
if (childClass.isInstance(next)) {
ret.add(childClass.cast(next));
} else {
throw new IllegalArgumentException(
"List member " + next + " is not an instance of " +
childClass.getName()
);
}
}
return ret;
}
}
Then you can do, for example:
List<String> stringList = List.of("foo", "bar", "baz");
List<CharSequence> charSequenceList = f1(CharSequence.class, stringList);
List<String> stringList2 = f2(String.class, charSequenceList);
If you only want f1
, you could create a list proxy. This list would have to be unmodifiable because the List
interface does not provide a way to gracefully reject an add
operation. However, as a plus, the returned list will "read through" to the original list. The reason there are 2 list implementations here is to respect the RandomAccess marker interface- this will ensure that wrapping the list through f1
wont degrade its performance.
import java.util.*;
public final class ListCast {
/** Upcast a list of elements which subclass "parentClass" */
public static <P> List<P> f1(Class<P> parentClass, List<? extends P> childList) {
if (childList instanceof RandomAccess) {
return new RandomAccessCastList<>(parentClass, childList);
} else {
return new SequentialCastList<>(parentClass, childList);
}
}
//
private static final class RandomAccessCastList<P> extends AbstractList<P> implements RandomAccess {
private final Class<P> parentClass;
private final List<?> backing;
RandomAccessCastList(Class<P> parentClass, List<?> backing) {
this.parentClass = parentClass;
this.backing = backing;
}
@Override
public int size() {
return this.backing.size();
}
@Override
public P get(int i) {
return this.parentClass.cast(this.backing.get(i));
}
}
private static final class SequentialCastList<P> extends AbstractSequentialList<P> {
private final Class<P> parentClass;
private final List<?> backing;
SequentialCastList(Class<P> parentClass, List<?> backing) {
this.parentClass = parentClass;
this.backing = backing;
}
@Override
public int size() {
return this.backing.size();
}
@Override
public P get(int index) {
return this.parentClass.cast(this.get(index));
}
@Override
public ListIterator<P> listIterator(int i) {
return new Iter<>(this.parentClass, this.backing.listIterator(i));
}
//
private static final class Iter<T> implements ListIterator<T> {
private final Class<T> clazz;
private final ListIterator<?> backing;
Iter(Class<T> clazz, ListIterator<?> backing) {
this.clazz = clazz;
this.backing = backing;
}
@Override
public boolean hasNext() {
return this.backing.hasNext();
}
@Override
public T next() {
return this.clazz.cast(this.backing.next());
}
@Override
public boolean hasPrevious() {
return this.backing.hasPrevious();
}
@Override
public T previous() {
return this.clazz.cast(this.backing.previous());
}
@Override
public int nextIndex() {
return this.backing.nextIndex();
}
@Override
public int previousIndex() {
return this.backing.previousIndex();
}
@Override
public void remove() {
this.throwUnmodifiable();
}
@Override
public void set(T t) {
this.throwUnmodifiable();
}
@Override
public void add(T t) {
this.throwUnmodifiable();
}
private void throwUnmodifiable() {
throw new UnsupportedOperationException("Cannot update CastList");
}
}
}
}
The functions needs to [...] not involve casts
You will not be able to avoid casts; a very common and inexpensive operation.