Using Map with TableView in JavaFX - java

I am trying display a Map in TableView in JavaFX. The code below works just fine but the issue I am having is that any future Map's update after initialize doesn't show in the TableView.
public class TableCassaController<K,V> extends TableView<Map.Entry<K,V>> implements Initializable {
#FXML private TableColumn<K, V> column1;
#FXML private TableColumn<K, V> column2;
// sample data
Map<String, String> map = new HashMap<>();
map.put("one", "One");
map.put("two", "Two");
map.put("three", "Three");
public TableCassaController(ObservableMap<K,V> map, String col1Name, String col2Name) {
System.out.println("Costruttore table");
TableColumn<Map.Entry<K, V>, K> column1 = new TableColumn<>(col1Name);
column1.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Map.Entry<K, V>, K>, ObservableValue<K>>() {
#Override
public ObservableValue<K> call(TableColumn.CellDataFeatures<Map.Entry<K, V>, K> p) {
// this callback returns property for just one cell, you can't use a loop here
// for first column we use key
return new SimpleObjectProperty<K>(p.getValue().getKey());
}
});
TableColumn<Map.Entry<K, V>, V> column2 = new TableColumn<>(col2Name);
column2.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Map.Entry<K, V>, V>, ObservableValue<V>>() {
#Override
public ObservableValue<V> call(TableColumn.CellDataFeatures<Map.Entry<K, V>, V> p) {
// for second column we use value
return new SimpleObjectProperty<V>(p.getValue().getValue());
}
});
ObservableList<Map.Entry<K, V>> items = FXCollections.observableArrayList(map.entrySet());
this.setItems(items);
this.getColumns().setAll(column1, column2);
}
Now, if I try to update the map, this update won't show in the TableView.

use ReadOnlyObjectWrapper
TableColumn<Map.Entry<K, V>, K> column1 = new TableColumn<>(col1Name);
column1.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue().getKey()));
TableColumn<Map.Entry<K, V>, V> column2 = new TableColumn<>(col2Name);
column2.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue().getValue()));

Related

Sorting Hashmap by values from High to low

I am trying to sort a hashmap that has a structure of by the value from high to Low.
I have created a function below to sort the data.
public static void SortDataHighToLow (Map <String, Integer> UnsortedMap){
List <Integer> list = new ArrayList(UnsortedMap.keySet());
Collections.sort(list,new Comparator <Integer>(){
#Override
public int compare(Integer arg0, Integer arg1) {
return arg0-arg1;
}
});
Map <String, Integer> sortedMap = new LinkedHashMap<>();
for (Integer keys: list){
sortedMap.put(UnsortedMap.toString(), keys);
}
System.out.println(sortedMap);
}
I am recieving the error below:
Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
I believe my error is caused by the for() above that I cannot read the Key value.
What adjustment should I make?
Thanks for the help.
Upon the answer came from #deHaar my problem got resolved. The code is below.
private static <K, V> Map<K, V> sortByValue(Map<K, V> map) {
List<Entry<K, V>> list = new LinkedList<>(map.entrySet());
Collections.sort(list, new Comparator<Object>() {
#SuppressWarnings("unchecked")
public int compare(Object o1, Object o2) {
return ((Comparable<V>) ((Map.Entry<K, V>) (o1)).getValue()).compareTo(((Map.Entry<K, V>) (o2)).getValue());
}
});
Map<K, V> result = new LinkedHashMap<>();
for (Iterator<Entry<K, V>> it = list.iterator(); it.hasNext();) {
Map.Entry<K, V> entry = (Map.Entry<K, V>) it.next();
result.put(entry.getKey(), entry.getValue());
}
return result;
}
This is a comparator that do the job:
public class MapKeyByValueComparator<K, T> implements Comparator<K> {
private final Map<K, T> map;
private final Comparator<T> comparator;
public MapKeyByValueComparator(Map<K, T> map, Comparator<T> comparator) {
this.map = map;
this.comparator = comparator;
}
#Override
public int compare(K o1, K o2) {
int ritem = comparator.compare(map.get(o1), map.get(o2));
// CAN NOT RETURNS 0, otherwise key with the same value will be overridden
if (ritem == 0) {
ritem = 1;
}
return ritem;
}
}
And then you can use a TreeMap as:
Map<something, somethig> map = new TreeMap<>(comparator):
map.addAll(...);
But PAY ATTENTION, this brokes the contract of Comparable
It is strongly recommended (though not required) that natural orderings be consistent with equals. This is so because sorted sets (and sorted maps) without explicit comparators behave "strangely" when they are used with elements (or keys) whose natural ordering is inconsistent with equals. In particular, such a sorted set (or sorted map) violates the general contract for set (or map), which is defined in terms of the equals method.

How to make group operation generic

Suppose I have this grouping:
Iterable<WorkExperience> list =
Arrays.asList
(
new WorkExperience(2001, "2001"),
new WorkExperience(2001, "2002"),
new WorkExperience(2001, "2003"),
new WorkExperience(2002, "2004")
);
Stream<WorkExperience> stream = StreamSupport.stream(list.spliterator(), false);
Map<Integer, List<String>> map = stream
.collect(Collectors.groupingBy(WorkExperience::getYear,
Collectors.mapping(WorkExperience::getYearName, Collectors.toList())));
It builds a map which contains work experience objects grouped by year. Works fine. How to make this grouping operation generic?
Ideally I want to do next:
Map<Integer, List<String>> map = new Grouping(list, WorkExperience::getYear, WorkExperience::getYearName).value();
List<Object> list2 = Arrays.asList(new Object(), new Object());
Map<Integer, List<String>> map2 = new Grouping(list2, (obj) -> obj.hashCode, (obj) -> obj.toString).value();
Basically you need to use generics where you use concrete class now.
public static <T, K, V> Map<K, List<V>> groupBy(
Iterable<T> list,
Function<T, K> keyMapper,
Function<T, V> valueMapper) {
Stream<T> stream = StreamSupport.stream(list.spliterator(), false);
return stream
.collect(
Collectors.groupingBy(keyMapper,
Collectors.mapping(valueMapper, Collectors.toList())));
}
This it GroupUtils from one of my project. I use it to group collection by some key field, when only single element for each key, or multiple ones.
public class GroupUtils {
public static <K, V> Map<K, List<V>> groupMultipleBy(Collection<V> data, Function<V, K> classifier) {
return CollectionUtils.isEmpty(data) ? Collections.emptyMap() : data.stream().collect(
Collectors.groupingBy(classifier, Collectors.mapping(Function.identity(), Collectors.toList())));
}
public static <K, V> Map<K, V> groupSingleBy(Collection<V> data, Function<V, K> keyMapper) {
return groupSingleBy(data, keyMapper, Function.identity());
}
public static <K, V, S> Map<K, V> groupSingleBy(Collection<S> data, Function<S, K> keyMapper, Function<S, V> valueMapper) {
return Optional.ofNullable(data).orElse(Collections.emptyList()).stream().collect(Collectors.toMap(keyMapper, valueMapper));
}
}

Generic HashMap with Lists and putIfAbsent

In Java, I want to add a getOrAdd method to a regular map, just like putIfAbsent on a ConcurrentHashMap.
Furthermore, for a certain key I want to store a list of items. Here's my attempt:
public class ListMap<K, V> extends HashMap<K, V> {
private HashMap<K, List<V>> map;
public ListMap() {
map = new HashMap<K, List<V>>();
}
public List<V> getOrAdd(K key) {
if (map.containsKey(key)) {
return map.get(key);
} else {
List<V> l = new ArrayList<V>();
map.put(key, l);
return l;
}
}
}
However, if someone wanted to iterate over a ListMap, he would need to cast the values explictly.
ListMap<Integer, MyClass> listMap = new ListMap<Integer, MyClass>();
for (Map.Entry<Integer, MyClass> entry : listMap.entrySet()) {
List<MyClass> val = (List<MyClass>) entry.getValue();
}
Is there a way of extending the HashMap class by some methods without creating a subclass? ( I've seen this in C#)
How can the ListMap class be modified such that one can get a ListMaps's value (List) without casting?
Instance of your class will be also HashMap so you don't need to, or even shouldn't add another field just to support getOrAdd method because other inherited and not overridden methods will not be referring to map field but to this instance.
So instead of adding separate field
private HashMap<K, List<V>> map;
change extending type of your ListMap to
public class ListMap<K, V> extends HashMap<K, List<V>>
^^^^^^^
and change your getOrAdd method to not use map field but this
public List<V> getOrAdd(K key) {
if (containsKey(key)) {
return get(key);
} else {
List<V> l = new ArrayList<V>();
put(key, l);
return l;
}
}
This change will let you use your map like
ListMap<Integer, String> listMap = new ListMap<Integer, String>();
for (Map.Entry<Integer, List<String>> entry : listMap.entrySet()) {
List<String> val = entry.getValue();//NO CASTING NEEDED
}
You can just extend HashMap like this:
public class ListMap<K, V> extends HashMap<K, List<V>> {
...
}

Convert List to Map and filter null keys

Using java 8 streams I want to convert a list into a map like described in the solution to Java 8 List<V> into Map<K, V>. However, I want to filter to remove entries with certain keys (for instance if the key is null) without doing the conversion of the value to a key twice.
For example I could do the filtering prior to collect such as
Map<String, Choice> result =
choices.stream().filter((choice) -> choice.getName() != null).collect(Collectors.toMap(Choice::getName,
Function.<Choice>identity());
In my case the logic to get the key is more complex than simply getting a field property, and I would like to avoid doing the logic first in the filter and again in the keyMapper function of Collectors.toMap
How can I convert the list to a map using a custom keyMapper function and filter certain values based on the new key?
If you want to calculate the key only once, you can use the stream method map to convert the stream to a stream of tuples, filter the tuples based on the key, and finally create the map from the tuples:
Map<String, Choice> result = choices.stream()
.map(c -> new AbstractMap.SimpleEntry<String, Choice>(c.getName(), c))
.filter(e -> e.getKey() != null)
.collect(toMap(e -> e.getKey(), e -> e.getValue()));
Here's a custom collector for what you want:
public class FilteredKeyCollector<T, K, V> implements Collector<T, Map<K, V>, Map<K, V>> {
private final Function<? super T,? extends K> keyMapper;
private final Function<? super T,? extends V> valueMapper;
private final Predicate<K> keyFilter;
private final EnumSet<Collector.Characteristics> characteristics;
private FilteredKeyCollector(Function<? super T,? extends K> keyMapper, Function<? super T,? extends V> valueMapper, Predicate<K> keyFilter) {
this.keyMapper = keyMapper;
this.valueMapper = valueMapper;
this.keyFilter = keyFilter;
this.characteristics = EnumSet.of(Collector.Characteristics.IDENTITY_FINISH);
}
#Override
public Supplier<Map<K, V>> supplier() {
return HashMap<K, V>::new;
}
#Override
public BiConsumer<Map<K, V>, T> accumulator() {
return (map, t) -> {
K key = keyMapper.apply(t);
if (keyFilter.test(key)) {
map.put(key, valueMapper.apply(t));
}
};
}
#Override
public BinaryOperator<Map<K, V>> combiner() {
return (map1, map2) -> {
map1.putAll(map2);
return map1;
};
}
#Override
public Function<Map<K, V>, Map<K, V>> finisher() {
return m -> m;
}
#Override
public Set<Collector.Characteristics> characteristics() {
return characteristics;
}
}
And using it:
Map<String, Choice> result = choices.stream()
.collect(new FilteredKeyCollector<>(
Choice::getName, // key mapper
c -> c, // value mapper
k -> k != null)); // key filter
If you accept doing it in 2 steps you could first collect the map and then remove unwanted keys:
Map<String, Choice> result = choices.stream()
.collect(Collectors.toMap(c -> c.getName(), c -> c);
result.keySet().removeIf(k -> k == null);

builder for HashMap

Guava provides us with great factory methods for Java types, such as Maps.newHashMap().
But are there also builders for java Maps?
HashMap<String,Integer> m = Maps.BuildHashMap.
put("a",1).
put("b",2).
build();
There is no such thing for HashMaps, but you can create an ImmutableMap with a builder:
final Map<String, Integer> m = ImmutableMap.<String, Integer>builder().
put("a", 1).
put("b", 2).
build();
And if you need a mutable map, you can just feed that to the HashMap constructor.
final Map<String, Integer> m = Maps.newHashMap(
ImmutableMap.<String, Integer>builder().
put("a", 1).
put("b", 2).
build());
Not quite a builder, but using an initializer:
Map<String, String> map = new HashMap<String, String>() {{
put("a", "1");
put("b", "2");
}};
Since Java 9 Map interface contains:
Map.of(k1,v1, k2,v2, ..)
Map.ofEntries(Map.entry(k1,v1), Map.entry(k2,v2), ..).
Limitations of those factory methods are that they:
can't hold nulls as keys and/or values (if you need to store nulls take a look at other answers)
produce immutable maps
If we need mutable map (like HashMap) we can use its copy-constructor and let it copy content of map created via Map.of(..)
Map<Integer, String> map = new HashMap<>( Map.of(1,"a", 2,"b", 3,"c") );
This is similar to the accepted answer, but a little cleaner, in my view:
ImmutableMap.of("key1", val1, "key2", val2, "key3", val3);
There are several variations of the above method, and they are great for making static, unchanging, immutable maps.
Here is a very simple one ...
public class FluentHashMap<K, V> extends java.util.HashMap<K, V> {
public FluentHashMap<K, V> with(K key, V value) {
put(key, value);
return this;
}
public static <K, V> FluentHashMap<K, V> map(K key, V value) {
return new FluentHashMap<K, V>().with(key, value);
}
}
then
import static FluentHashMap.map;
HashMap<String, Integer> m = map("a", 1).with("b", 2);
See https://gist.github.com/culmat/a3bcc646fa4401641ac6eb01f3719065
A simple map builder is trivial to write:
public class Maps {
public static <Q,W> MapWrapper<Q,W> map(Q q, W w) {
return new MapWrapper<Q, W>(q, w);
}
public static final class MapWrapper<Q,W> {
private final HashMap<Q,W> map;
public MapWrapper(Q q, W w) {
map = new HashMap<Q, W>();
map.put(q, w);
}
public MapWrapper<Q,W> map(Q q, W w) {
map.put(q, w);
return this;
}
public Map<Q,W> getMap() {
return map;
}
}
public static void main(String[] args) {
Map<String, Integer> map = Maps.map("one", 1).map("two", 2).map("three", 3).getMap();
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
}
}
You can use:
HashMap<String,Integer> m = Maps.newHashMap(
ImmutableMap.of("a",1,"b",2)
);
It's not as classy and readable, but does the work.
HashMap is mutable; there's no need for a builder.
Map<String, Integer> map = Maps.newHashMap();
map.put("a", 1);
map.put("b", 2);
Using java 8:
This is a approach of Java-9 Map.ofEntries(Map.entry(k1,v1), Map.entry(k2,v2), ...)
public class MapUtil {
import static java.util.stream.Collectors.toMap;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Stream;
private MapUtil() {}
#SafeVarargs
public static Map<String, Object> ofEntries(SimpleEntry<String, Object>... values) {
return Stream.of(values).collect(toMap(Entry::getKey, Entry::getValue));
}
public static SimpleEntry<String, Object> entry(String key, Object value) {
return new SimpleEntry<String, Object>(key, value);
}
}
How to Use:
import static your.package.name.MapUtil.*;
import java.util.Map;
Map<String, Object> map = ofEntries(
entry("id", 1),
entry("description", "xyz"),
entry("value", 1.05),
entry("enable", true)
);
There's ImmutableMap.builder() in Guava.
I had a similar requirement a while back. Its nothing to do with Guava but you can do something like this to be able to cleanly construct a Map using a fluent builder.
Create a base class that extends Map.
public class FluentHashMap<K, V> extends LinkedHashMap<K, V> {
private static final long serialVersionUID = 4857340227048063855L;
public FluentHashMap() {}
public FluentHashMap<K, V> delete(Object key) {
this.remove(key);
return this;
}
}
Then create the fluent builder with methods that suit your needs:
public class ValueMap extends FluentHashMap<String, Object> {
private static final long serialVersionUID = 1L;
public ValueMap() {}
public ValueMap withValue(String key, String val) {
super.put(key, val);
return this;
}
... Add withXYZ to suit...
}
You can then implement it like this:
ValueMap map = new ValueMap()
.withValue("key 1", "value 1")
.withValue("key 2", "value 2")
.withValue("key 3", "value 3")
Here's one I wrote
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public class MapBuilder<K, V> {
private final Map<K, V> map;
/**
* Create a HashMap builder
*/
public MapBuilder() {
map = new HashMap<>();
}
/**
* Create a HashMap builder
* #param initialCapacity
*/
public MapBuilder(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
/**
* Create a Map builder
* #param mapFactory
*/
public MapBuilder(Supplier<Map<K, V>> mapFactory) {
map = mapFactory.get();
}
public MapBuilder<K, V> put(K key, V value) {
map.put(key, value);
return this;
}
public Map<K, V> build() {
return map;
}
/**
* Returns an unmodifiable Map. Strictly speaking, the Map is not immutable because any code with a reference to
* the builder could mutate it.
*
* #return
*/
public Map<K, V> buildUnmodifiable() {
return Collections.unmodifiableMap(map);
}
}
You use it like this:
Map<String, Object> map = new MapBuilder<String, Object>(LinkedHashMap::new)
.put("event_type", newEvent.getType())
.put("app_package_name", newEvent.getPackageName())
.put("activity", newEvent.getActivity())
.build();
You can use the fluent API in Eclipse Collections:
Map<String, Integer> map = Maps.mutable.<String, Integer>empty()
.withKeyValue("a", 1)
.withKeyValue("b", 2);
Assert.assertEquals(Maps.mutable.with("a", 1, "b", 2), map);
Here's a blog with more detail and examples.
Note: I am a committer for Eclipse Collections.
This is something I always wanted, especially while setting up test fixtures. Finally, I decided to write a simple fluent builder of my own that could build any Map implementation -
https://gist.github.com/samshu/b471f5a2925fa9d9b718795d8bbdfe42#file-mapbuilder-java
/**
* #param mapClass Any {#link Map} implementation type. e.g., HashMap.class
*/
public static <K, V> MapBuilder<K, V> builder(#SuppressWarnings("rawtypes") Class<? extends Map> mapClass)
throws InstantiationException,
IllegalAccessException {
return new MapBuilder<K, V>(mapClass);
}
public MapBuilder<K, V> put(K key, V value) {
map.put(key, value);
return this;
}
public Map<K, V> build() {
return map;
}
Underscore-java can build hashmap.
Map<String, Object> value = U.objectBuilder()
.add("firstName", "John")
.add("lastName", "Smith")
.add("age", 25)
.add("address", U.arrayBuilder()
.add(U.objectBuilder()
.add("streetAddress", "21 2nd Street")
.add("city", "New York")
.add("state", "NY")
.add("postalCode", "10021")))
.add("phoneNumber", U.arrayBuilder()
.add(U.objectBuilder()
.add("type", "home")
.add("number", "212 555-1234"))
.add(U.objectBuilder()
.add("type", "fax")
.add("number", "646 555-4567")))
.build();
// {firstName=John, lastName=Smith, age=25, address=[{streetAddress=21 2nd Street,
// city=New York, state=NY, postalCode=10021}], phoneNumber=[{type=home, number=212 555-1234},
// {type=fax, number=646 555-4567}]}

Categories

Resources