Why am i not allowed to do this?
public abstract class A {}
public class B extends A {}
...
public ArrayList<A> foo()
{
return new ArrayList<B>();
}
I changed to public since there are so many people that love to point stupid errors.
Why should i have to write ALL this code. Just to satisfy Java's non-senses?
public List<A> foo()
{
List<A> aList = new ArrayList<A>();
List<B> bList = new ArrayList<B>();
/* fill bList*/
for (B b : bList)
{
aList.add(b);
}
return aList;
}
An ArrayList<B> is not an ArrayList<A>. You can't add any arbitrary A into it, for example. Or as I like to think of it: a bunch of bananas isn't a fruitbowl. When you try to add an apple to a bunch of bananas, it rolls off...
You can use wildcards to make it work though:
public ArrayList<? extends A> foo()
{
return new ArrayList<B>();
}
See the Java Generics FAQ for more details.
EDIT: To answer your specific question of why you need to write all that extra code: you don't. Just create an ArrayList<A> within foo() to start with. There's no need to copy the contents of one list to another.
If you still object to Java's behaviour, what would you want to happen with the following code?
// Doesn't compile, fortunately...
List<String> strings = new List<String>();
List<Object> objects = strings;
objects.add(new Date());
String string = strings.get(0); // Um, it's a Date, not a String...
a) For one thing, function does not exist in Java. Java methods have the format
modifiers <type_parameters[,type_parameter]*>? return_type method_name (
[parameter[,parameter]*]?
) [throws exceptiontype[, exceptiontype]*]{ method_body }
b) Here's how to do it:
public List<? extends A> foo()
{
return new ArrayList<B>();
}
c) I changed the method signature to List. It's bad practice to have implementation types in your class' external API if an appropriate interface exists.
because ArrayList<B>() is not ArrayList<A>. it is not extended from it
B extends A doesn't mean ArrayList<B>() extends ArrayList<A>()
Related
private <Y> void meth(
MyObj ds, MultiValueMap<String, List> mvm, Class<Y> data) {
if(data.isAssignableFrom(Employee.class)) {
for (Employee rd : (List<Employee>) mvm.get(0).get(1)) {
for (String cName : (List<String>) mvm.get(0).get(0)) {
ds.setCellValue((String)rd.getDataElement(cName));
}
}
}
if(data.isAssignableFrom(Department.class)) {
for (Department rd : (List<Department>) mvm.get(0).get(1)) {
for (String cName : (List<String>) mvm.get(0).get(0)) {
ds.setCellValue((String)rd.getDataElement(cName));
}
}
}
//some more similar if conditions as above
}
In above, I have like similar 10 if conditions, how to avoid duplicate code in above? Do I need to use any Java 8 Function classes as parameters to avoid duplicate code (or) have to use any extra generics code?
So it looks like that what you need is inheritance and not generics.
In your if condition you always cast and call the same method on the Object.
So what you can do is e.g. define an interface looking something like this:
public interface MyInterface {
String getDataElement(String name);
}
And implement it in your Employee, Department and other classes you have.
If the method always does the same you can use default or an abstract class to not always write the same:
public interface MyInterface {
default String getDataElement(String name) {
//do your thing
return value;
}
}
public abstract class MyAbstractClass {
public String getDataElement(String name) {
//do your thing
return value;
}
}
Now you can change your meth method to this:
private void meth(MyObj ds, MultiValueMap<String, List> mvm) {
List<MyInterface> list = (List<MyInterface>) mvm.get(0).get(1));
for (MyInterface rd : list) {
List<String> cNames = (List<String>) mvm.get(0).get(0);
for (String cName : cNames) {
ds.setCellValue((String) rd.getDataElement(cName));
}
}
}
While generic methods can be applied, they won't solve the actual scenario you're trying to solve for (an earlier edit of this answer was an attempt before I thought through it more).
Taking a step back, this looks like the problem statement ("what" you're solving for, not "how"):
Iterate all lists present in a map – in your posted code, this would be: MultiValueMap<String, List> mvm
Accomodate different object types – you posted Employee and Department as examples
Whatever the list contains (Employee, Department, etc), you want to call getDataElement()
As the other answer by Ausgefuchster describes, this can be solved with interfaces. I voted that answer up, but wanted to provide more
more detail and examples.
Step 1: define an interface
Regardless of concrete class, your map contains lists of things which have getDataElement(), so make an interface which captures that:
interface HasDataElement {
String getDataElement();
}
Step 2: classes implement the interface
For this answer, I made up a few simple classes – A, B, and C – which implement the interface, but otherwise only return a string when getDataElement() is called. In your code, you would modify Employee, Department, etc. to implement your new interface.
class A implements HasDataElement {
#Override
public String getDataElement() {
return "A";
}
}
class B implements HasDataElement {
#Override
public String getDataElement() {
return "B";
}
}
class C implements HasDataElement {
#Override
public String getDataElement() {
return "C";
}
}
Step 3: handle the map
I'm using built-in types, so Map<String, List> instead of your posted code which uses MultiValueMap<String, List>. I think this difference is not significant, but pointing it out anyway.
The method signature below specifies that the map isn't just <String, List>, but further specifies that the list itself must contain things which extend the HasDataElement interface: List<? extends HasDataElement>.
Inside the method, the same List<? extends HasDataElement> type shows up in the first loop.
Once inside, the concrete class of item isn't relevant – we know it conforms to the HasDataElement interface, so we can call item.getDataElement().
private static void processAllDataElements(Map<String, List<? extends HasDataElement>> map) {
for (List<? extends HasDataElement> list : map.values()) {
for (HasDataElement item : list) {
System.out.println(item.getDataElement());
}
}
}
Example usage
Here's a simple example, along with output, that creates a few different lists of the various classes A, B, and C. It then creates a map and adds all three lists.
List<A> listOfA = List.of(new A[]{new A()});
List<B> listOfB = List.of(new B[]{new B(), new B()});
List<C> listOfC = List.of(new C[]{new C(), new C(), new C()});
Map<String, List<? extends HasDataElement>> map = new HashMap<>();
map.put("A", listOfA);
map.put("B", listOfB);
map.put("C", listOfC);
processAllDataElements(map);
A
B
B
C
C
C
Giving the diagram below, I want to know the following:
How to make sure that a class car can have a GPS of type A or B not both?
Because of the max multiplicity of 1, all your example diagram is missing is a generalization set that is {complete, disjoint}. Your diagram now says {incomplete, overlapping}, by default, which means an instance can be a member of both A and B, or just a member of GPS.
What about using generics like this:
public class A<T extends B> {
List<T> list = new ArrayList<>();
public List<T> getList() {
return list;
}
public void setList(List<T> list) {
this.list = list;
}
}
public class B {
}
public class C extends B {
}
public class D extends B {
}
Then you will instantiate class A with the desired subtype of B like this:
A<C> a = new A<>();
C c = new C();
D d = new D();
a.getList().add(c); //works fine
a.getList().add(d); //compile error
Taking the fruit basket from your comment, you need to specify a multiplicity for 0..n instead of the 1.
If you want all instances to be the same subclass you need to add a constraint in any way. If your tool does not permit it, just add a note with { all instances ofBmust have the same type } or the like.
Requirement:
Need my method to return List of one of the two specified objects.
Need to store the result of the above method as a List of one the two specified objects.
Declared a method which returns a Generic List based on wildcards.
Got the declaration part right, but method call fails.
class Z{/*TemplateClass*/}
public class A extends Z{}
public class B extends Z{}
Test Class:
public List<? extends Z> getGenericList(boolean test){
if(test){
List<A> aList = new ArrayList<A>();
aList.add(new A());
return aList;
}
else{
List<B> bList = new ArrayList<B>();
bList.add(new B());
return bList;
}
}
Here's my requirement. I need something like the below code.
List<A> testAList = getGenericList(true);
List<B> testBList = getGenericList(false);
Gives me a compiler error.
Type mismatch: cannot convert from List< capture#1-of ? extends Z > to
List< A > Type mismatch: cannot convert from List< capture#2-of ? extends
Z > to List< B >
I understand the logic behind this. What if we pass a false and store the returned List of B's as a List of A's which is wrong.
But when I use the below code:
List<? extends Z> testList = getGenericList(true);
It compiles fine. But it beats my purpose. I'm not able to access the methods in A or B as the testList.get(index) returns Z type objects where I hope to get either A or B.
If my code is absolutely wrong, then any pointers in the right direction would be appreciated.
You can cast the result which would make the compiler errors go away but you'd have an unsafe cast which seems like a code smell:
List<A> testAList = (List<A>) getGenericList(true);
List<B> testBList = (List<B>)getGenericList(false);
Also if the boolean input is not a strict requirement you can do something like this as well:
public <T extends Z> List<T> getGenericList(Class<T> klass) {
List<T> list = new ArrayList<T>();
try {
list.add(klass.newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return list;
}
Not going to happen. You are asking for a type that simply cannot be expressed in the java type system, something like List<A> + List<B>.
The closest thing to that is a form of continuation passing style; you create another abstract class like HomogeneousListAB with a visitor interface and accept method. Then you can return one of those from getGenericList.
abstract class HomogeneousListAB {
interface Visitor {
void ifListA( List< A > la );
void ifListB( List< B > la );
}
abstract public accept( Visitor v );
}
// inside getGenericList true branch
final List< A > laOut = // whatever
return new HomogeneousListAB() {
public accept( Visitor v ) {
v.ifListA( laOut );
}
};
Suppose we're dealing with the following set up:
A extends B
Main does:
public void doIt() {
List<A> listOfA = new ArrayList<A>();
listOfA.add(new A());
listOfA.add(new A());
C c = new C();
// We know that really, listOfA is going to be modified
// in some way and will be returned to us, thus, it is
// ok to cast result back to what we know it to be
List<A> a = (List<A>) c.doSomethingWithListOfA(listOfA);
}
When C has a method
public List<? extends B> doSomethingWithListOfA(List<? extends B>
listOfObjectsThatExtendB) {
return someFormofListA;
}
The cast of (List) c.doSomethingWithListOfA(listOfA); comes back with - Type safety: Unchecked cast from List<capture#1-of ? extends B> to List<A> warning.
I wonder is it possibly because some other class may also extend B thus making compiler unsure whether in fact is it a list of A or possibly X that extends B?
Other then suppressing the warning, how can one check for this? (unchecked cast)
It's not type-safe because that method could return a list of any type which is a subtype of B -- which may or may no be A.
How about writing this:
public <T extends B> List<T> doSomethingWithListOfA(List<T> listOfObjectsThatExtendB) {
return listOfObjectsThatExtendB;
}
and now casting is no longer needed:
List<A> a = c.doSomethingWithListOfA(listOfA);
I've been looking around to see if I find something to help me with my problem, but no luck until now. I've got the following classese:
public interface ISort<T> {
public List<T> sort(List<T> initialList);
}
public abstract class Sort<T> implements ISort<T> {
private Comparator<? super T> comparator;
public Sort(Comparator<? super T> comparator) {
this.comparator = comparator;
}
#Override
public List<T> sort(List<T> initialList) {
ArrayList<T> list = new ArrayList<T>(initialList);
Collections.sort(list, comparator);
return list;
}
}
public abstract class InternalTreeItem<T> {
public abstract String getValue();
}
public class D extends InternalTreeItem<Integer> {
private Integer i;
public D(Integer i) {
this.i = i;
}
#Override
public String getValue() {
return i.toString();
}
public Integer getInteger() {
return i;
}
}
public class DComparator implements Comparator<D> {
#Override
public int compare(D o1, D o2) {
return o1.getInteger() - o2.getInteger();
}
}
public class DSort extends Sort<D> {
public DSort(Comparator<D> comparator) {
super(comparator);
}
public DSort() {
super(new DComparator());
}
}
And the test class:
public class TestClass {
#Test
public void test1() {
List<InternalTreeItem<?>> list= new ArrayList<InternalTreeItem<?>>();
list.add(new D(1));
list.add(new D(10));
list.add(new D(5));
ISort<?> sorter = new DSort();
sorter.sort(list);
}
}
The compiler gives an error at the line
sorter.sort(list);
and states
The method sort(List<capture#2-of ?>)
in the type ISort<capture#2-of ?>
is not applicable for the arguments
(List<InternalTreeItem<?>>)
Ok, after a couple of hours and help from a friend, we realized the problem lies with Collections#sort(List<T> list, Comparator<? super T> c) in the abstract class Sort, as I use a Comparator<? extends T>.
I use generics, as I have 2 models, one model's super class is a generic abstract subclassed by 35 classes, and the second model actually has 2 different super classes, which combined, are subclassed by again 35 classes. These hierarchies are given, there's nothing I can do to modify them.
The model here is very simple, but you get the point. Also, there's a factory, that depending on the type of T, returns one sorter, or another.
Can any one please help and provide a solution for my issue (that is to sort a generic list; the parameter type can be a generic superclass or one of it's subclasses).
Thanks and best regards,
Domi
One way to approach this is to use a wrapper class for the classes that you cannot change.
So in your example you want to order a list of object D, based on an Integer value. By putting your objects in a wrapper and then adding this to the list, you can expose the value you wish to sort the list by.
For example, you could define an interface like:
private interface SortableListItem<T> extends Comparable<SortableListItem<T>> {
public T getValue();
}
Then, create a wrapper class for D:
public class DWrapper implements SortableListItem<Integer> {
private D item;
public DWrapper(D item) {
this.item = item;
}
public Integer getValue() {
return item.getInteger();
}
public int compareTo(SortableListItem<Integer> o) {
return getValue().compareTo(o.getValue());
}
}
From here it is pretty simple to create and sort your list:
D item1= new D(1);
D item2= new D(10);
D item3= new D(5);
DWrapper wrapper1 = new DWrapper(item1);
DWrapper wrapper2= new DWrapper(item2);
DWrapper wrapper3= new DWrapper(item3);
List<SortableListItem<Integer>> sortableList = new ArrayList<SortableListItem<Integer>>();
sortableList.add(wrapper1 );
sortableList.add(wrapper2);
sortableList.add(wrapper3);
Collections.sort(sortableList);
You can of course make the wrapper class accept a more generic object - the key is that each object returns a value (in this case an Integer) that the List can be sorted by.
The variable sorter is of type ISort<?>. It could have, say, an ISort<String> assigned to it. The sort method takes an argument of List<T> where T could be String. Clearly you cannot use List<InternalTreeItem<?>> for List<String>, so fortunately the compiler points out the error.
(Note: It's generally a good idea to keep to coding conventions. No I Hungarian prefixes, or single letter class names.)
Running your code what I can deduce is that you get a compile error since it is not possible to capture the wildcard that you specify in below line of class TestClass:
ISort<?> sorter = new DSort();
As I understand an occurrence of wild card is taken to stand for some unknown type and from your code it is not possible to infer the type (for the compiler).
But looking at the code, the class DSort is not written in a way to take type parameters
and any attempt to pass type parameters during creation of instance of DSort gave the error:
The type DSort is not generic; it cannot be parameterized with arguments
But you mention that you cannot alter the code of the modules (i.e I presume of classes DSort etc).
So one way to fix the error would be to not use generics during creation of instance of ISort.
The below code works and the prints the the sorted output (1,5,10)
List<InternalTreeItem<?>> list= new ArrayList<InternalTreeItem<?>>();
list.add(new D(1));
list.add(new D(10));
list.add(new D(5));
// no generic arguments
ISort sorter = new DSort();
List<InternalTreeItem<?>> sortedList = sorter.sort(list);
for(InternalTreeItem i:sortedList) {
System.out.println(i.getValue());
}
but results in a warning of the form ISort is a raw type. References to generic type ISort should be parameterized. But having code that uses generic and having warning of this form is not a good practice . This warning implies that the compiler cannot give cast-iron guarantee about the implicit casts it does to use generics.
If feasible, I think the better solution would be to see how the modules class can re-designed.