Thursday, April 19, 2018

Make BiConsumer accept object

Leave a Comment

I have a list of column definitions for a JTable TableModel with the column "B" having a setter BiConsumer which accepts a BauwerkOption class and a string.

When I try to set the string in the line with "...accept..." I get following error:

The method accept(Selektierung.BauwerkOption, capture#4-of ? extends Object) in the type BiConsumer<Selektierung.BauwerkOption,capture#4-of ? extends Object> is not applicable for the arguments (Selektierung.BauwerkOption, Object) 

What is wrong with my code? Is it even possible what I am trying to do?

public class TableModelSelektierung extends DefaultTableModel {     private static final long serialVersionUID = -5921626198599251183L;     private List<BauwerkOption> data;     private static List<ColDef<BauwerkOption, ? extends Object>> DEF = new ArrayList<>();     static {         DEF.add(new ColDef<BauwerkOption, String>("A", (o) -> o.getBauwerkstyp()));         DEF.add(new ColDef<BauwerkOption, String>("B", (o) -> o.getBezeichnung())                                                                                             .withSetValueAtFunction((i, o) -> i.setBauwerkstyp(o)));         DEF.add(new ColDef<BauwerkOption, String>("C", (o) -> o.getNutzungsart()));         DEF.add(new ColDef<BauwerkOption, Double>("D", (o) -> o.getDurchmesser()));     }      @Override     public int getColumnCount() {         return DEF.size();     }      @Override     public boolean isCellEditable(int row, int column) {         return DEF.get(row).getValueSetterFunction() == null;     }      @Override     public int getRowCount() {         if (data != null) {             return data.size();         }         return 0;     }      @Override     public String getColumnName(int column) {         return DEF.get(column).getTitle();     }      public void setData(List<BauwerkOption> data) {         this.data = data;         fireTableDataChanged();     }      public BauwerkOption getObjectAt(int row) {         return data.get(row);     }      @Override     public void setValueAt(Object aValue, int row, int column) {         if (DEF.get(column).getValueSetterFunction() != null) {             DEF.get(column).getValueSetterFunction().accept(getObjectAt(row), aValue);         }     }      @Override     public Object getValueAt(int row, int column) {         BauwerkOption o = getObjectAt(row);         return DEF.get(column).getValueGetterFunction().apply(o);     }  }   class ColDef<InputObjekt, OutputValue> {     private String title;     private Function<InputObjekt, OutputValue> valueGetterFunction;     private BiConsumer<InputObjekt, OutputValue> valueSetterFunction;      public ColDef(String title, Function<InputObjekt, OutputValue> valueGetterFunction) {         this.title = title;         this.valueGetterFunction = valueGetterFunction;     }      public ColDef<InputObjekt, OutputValue> withSetValueAtFunction(BiConsumer<InputObjekt, OutputValue> valueSetterFunction) {         this.valueSetterFunction = valueSetterFunction;         return this;     }      public Function<InputObjekt, OutputValue> getValueGetterFunction() {         return valueGetterFunction;     }      public String getTitle() {         return title;     }      public BiConsumer<InputObjekt, OutputValue> getValueSetterFunction() {         return valueSetterFunction;     }  }  class BauwerkOption {       public BauwerkOption() {      }      public String getBezeichnung() {         return "";     }      public String getBauwerkstyp() {         return "";     }      public Double getDurchmesser() {          return 0d;     }      public String getNutzungsart() {         return "todo";     }        public void setBauwerkstyp(String s) {      } } 

3 Answers

Answers 1

There is no clean way of doing this, since the type of the Object parameter in setValueAt cannot be changed.

If you replace the following code, it will be as clean as you can get:

This is just for convenience (you don't need own lambdas):

private static List<ColDef<BauwerkOption, ?>> DEF = new ArrayList<>(); static {     DEF.add(new ColDef<BauwerkOption, String>("A", BauwerkOption::getBauwerkstyp));     DEF.add(new ColDef<BauwerkOption, String>("B", BauwerkOption::getBezeichnung).withSetValueAtFunction(BauwerkOption::setBauwerkstyp));     DEF.add(new ColDef<BauwerkOption, String>("C", BauwerkOption::getNutzungsart));     DEF.add(new ColDef<BauwerkOption, Double>("D", BauwerkOption::getDurchmesser)); } 

In ColDef simply add this method without changing anything else:

@SuppressWarnings("unchecked") public BiConsumer<InputObjekt, Object> getObjectValueSetterFunction() {     return (BiConsumer<InputObjekt, Object>) valueSetterFunction; } 

And then replace setValueAt in TableModelSelektierung with:

@Override public void setValueAt(Object aValue, int row, int column) {     if (DEF.get(column).getValueSetterFunction() != null) {         DEF.get(column).getObjectValueSetterFunction().accept(getObjectAt(row), aValue);     } } 

Keeps your generics in place but works around the Object restriction you get from inheriting. You should probably add type checks in setValueAt to make sure the object parameter is of the correct type.

Answers 2

the message

The method accept(Selektierung.BauwerkOption, capture#4-of ? extends Object) in the type BiConsumer<Selektierung.BauwerkOption,capture#4-of ? extends Object> is not applicable for the arguments (Selektierung.BauwerkOption, Object) 

says, that DEF is declared as a list of ColDef<BauwerkOption, ? extends Object> and expects something, that extends an Object and not an Object itself. so if you remove ? extends from the declaration of DEF, it should work

Edit

to be more specific in your code, you could create Value-Object-Classes for Bauwerkstyp, Bezeichnung, Nutzungsart, Durchmesser, etc. that extend the Type you want, for example String or Double and implement an Interface

public ColDefStringValue implements IColDefValue<String> {     @Override     public String getValue() {         return ...;     } } 

the interface could look like this

public interface ColDefValue<T> {     T getValue(); } 

the Interface could be used instead of ? extends Object or Object

private static List<ColDef<BauwerkOption, IColDefValue<?>>> DEF = new ArrayList<>(); 

Answers 3

The problem is with ColDef#getValueSetterFunction(). This returns a BiConsumer<BauwerkOption, ?>, where the question mark refers to some type extending from Object. There's no way to know which specific type this is but it is definitely not Object itself. Passing a value of type Object to the acceptmethod in TableModelSelektierung#setValueAt() will thus fail with:

incompatible types: java.lang.Object cannot be converted to capture#1 of ? 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment