Lets say I have an ArrayList of a object User so ArrayList<user>. A User object has a property userID.
Rather than iterating the list myself and adding userIDs to a separate list, is there and API call where I could pass it the property I want on that object and have a list of these properties returned to me? Had a look through the API and nothing stood out.
Looking for a solution in Java 7 or <.
You can do this using lambdas expressions (Java 8) :
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class Test {
public static void main(String args[]){
List<User> users = Arrays.asList(new User(1,"Alice"), new User(2,"Bob"), new User(3,"Charlie"), new User(4,"Dave"));
List<Long> listUsersId = users.stream()
.map(u -> u.id)
.collect(Collectors.toList());
System.out.println(listUsersId);
}
}
class User {
public long id;
public String name;
public User(long id, String name){
this.id = id;
this.name = name;
}
}
Output :
[1, 2, 3, 4]
Snippet here.
Ugliest solution using reflection :
public class Test {
public static void main (String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException{
List<User> users = Arrays.asList(new User(1,"Alice"), new User(2,"Bob"), new User(3,"Charlie"), new User(4,"Dave"));
List<Object> list = get(users,"id");
System.out.println(list);
}
public static List<Object> get(List<User> l, String fieldName) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException{
Field field = User.class.getDeclaredField(fieldName);
field.setAccessible(true);
List<Object> list = new ArrayList<>();
for(User u : l){
list.add(field.get(u));
}
field.setAccessible(false);
return list;
}
}
class User {
private long id;
private String name;
public User(long id, String name){
this.id = id;
this.name = name;
}
}
Output :
[1, 2, 3, 4]
You can use Guava's Collections2#transform method to have the same result.
List<Integer> userIDs = Collections2.transform(users, new Function<User, Integer> (){
public Integer apply(User user) {
return user.getUserID();
}
});
Guava supports Java 7 and lower, so if you want to use an external library, the above will work in your case.
Unfortunately, you will have to do similar logic for any other objects and their inner fields you might have. It's not as a generic solution as the reflection one, it's just a more compact one.
It sounds like you want to use a Map.
Maps use a Key, Value pair. You can assign the userID as the key and the actual user object as the value.
You can read more here
Related
So I have this GameType class:
public class GameType {
private final String name;
public GameType(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
and I have a list of my game types, so, basically I want to print all of my GameType classes' name field and I am using Java 8, this is what I have done so far:
List<String> list = new ArrayList<>();
gameTypes.forEach(gameType -> list.add(gameType.getName()));
System.out.println(list);
So, what I am asking is, is there a better way to do that?
You can try this:
System.out.println(gameTypes.stream()
.map(GameType::getName)
.collect(Collectors.toList()));
What is wrong with the JXpath expression in the code below? The following code always throws this exception:
org.apache.commons.jxpath.JXPathNotFoundException: No value for xpath: countries[gdp=4]
However I expected the country Germany to be returned. Whats wrong here?
import org.apache.commons.jxpath.JXPathContext;
public class Test {
private final Country[] countries = {
new Country("US", 20),
new Country("China", 13),
new Country("Japan", 5),
new Country("Germany", 4),
new Country("UK", 3)};
public static void main(String[] args) {
Object result = JXPathContext.newContext(new Test()).getValue("countries[gdp=4]");
System.out.println(result);
}
public Country[] getCountries() {
return countries;
}
}
-
class Country{
private String name;
private int gdp;
public Country(String name, Integer gdp){
this.name=name;
this.gdp=gdp;
}
#Override
public String toString() {
return name;
}
public String getName() {
return name;
}
public int getGdp() {
return gdp;
}
}
I don't know enough about the jxpath lib you are using. But the xpath expression should be like this:
/countries/country[gdp=4]
It started to work after I replaced:
Object result = JXPathContext.newContext(new Test()).getValue("countries[gdp=4]");
By:
Iterator result = JXPathContext.newContext(new Test()).iterate("countries[gdp=4]");
I'd like to find objects in a list based on having a certain property.
For example, say I have a list of objects of this class:
class Person {
private String name;
private String id;
}
I know that I can get all of the names of people in the list by using:
Collection<String> names = CollectionUtils.collect(
personList,
TransformerUtils.invokerTransformer("getName"));
but what I want to do is get a list of the Person objects whose name is, for example, "Nick". I don't have Java 8.
I see you are using Apache Common Utils, then you can use:
CollectionUtils.filter( personList, new Predicate<Person>() {
#Override
public boolean evaluate( Person p ) {
return p.getName() != null && p.getName().equals( "Nick" );
}
});
If you don't want to use Java 8 streams, simply loop through the list and check the elements manually:
ArrayList<Person> filteredList = new ArrayList<Person>();
for(Person person : personList) {
if(person.getName().equals("Nick")) filteredList.add(person);
}
Using Apache collection utils (which you are already using), you can use the filter method which creates a subset of your collection consisting of items which match a given filter, combined with a Predicate that matches "Nick" based on your Transformer and transformedPredicate:
CollectionUtils.filter(names, PredicateUtils.transformedPredicate(
TransformerUtils.invokerTransformer("getName"), PredicateUtils.equalPredicate("Nick")));
That said, you may want to reconsider using such a heavily functional approach before you migrate to Java 8. Not only will you have a lot of redundant Apache collections code after you migrate, the solutions prior to the introduction of closures and lambdas tend to be verbose and often less readable than the imperative alternatives.
Note: this modifies the existing collection in-place. If that is not the desired behavior, you'll need to create a copy before calling the filter method.
You could try using a HashMap.
HashMap<String, Integer> points = new HashMap<String, Integer>();
points.put("Amy", 154);
points.put("Dave", 42);
points.put("Rob", 733);
System.out.println(points.get("Dave"));
This would only be an example, but you could check and instead of having the String, Integer, you could try Integer, String and have an if statement. Just an idea though.
I am considering that you might have a collection of different objects that have this property name. For that I would recommend you to do parent for those components and perform the search on it with generics:
For example:
Parent class Name.java
public class Name {
private String name;
public Name(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Class Pet.java
public class Pet extends Name{
public Pet(String name) {
super(name);
}
}
And class Person.java
public class Person extends Name{
public Person(String name) {
super(name);
}
}
Job class that doesn't have the property:
public class Job {
private String description;
public Job(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
Having that, you can check the instance of it and compare what you want:
public static void main(String ... args) {
List<Object> objectList = new ArrayList();
objectList.add(new Person("Nick"));
objectList.add(new Person("Nik"));
objectList.add(new Person("Nikc"));
objectList.add(new Person("Ncik"));
objectList.add(new Pet("Nick"));
objectList.add(new Pet("Nik"));
objectList.add(new Pet("Nikc"));
objectList.add(new Pet("Ncik"));
objectList.add(new Job("Nick"));
objectList.add(new Job("Nik"));
objectList.add(new Job("Nikc"));
objectList.add(new Job("Ncik"));
for (Object o : extractObjectsMatching("Nick", objectList)){
System.out.println(((Name) o).getName());
}
}
public static List<Object> extractObjectsMatching(String name, List<Object> objectList){
List<Object> matches = new ArrayList<>();
for (Object e : objectList){
if (e instanceof Name && ((Name) e).getName().contains(name)){
matches.add(e);
}
}
return matches;
}
If the Object class is not an instance of Name, means that will not have the property that you are looking for, just ignore, otherwise, compare with the information that you want and store to retrieve.
Is it possible to avoid creating stream inside current stream from the same collection like in below example to collect some data (listOfA is used two times to create stream) ?
List<A> listOfA = Arrays.asList(new A(1L, "A1", "V1"), new A(2L, "A2", "V1"), new A(1L, "A1", "V2"));
List<B> listOfB = listOfA.stream().map(r -> new B(r.getId(), r.getName(),
listOfA.stream().filter(r2 -> r.getId().equals(r2.getId())).map(A::getVal).collect(toSet())
)).distinct().collect(toList());
class A {
private final Long id;
private final String name;
private final String val;
A(Long id, String name, String val) //constructor
//getters
}
class B {
private final Long id;
private final String name;
private final Set<String> values;
B(Long id, String name, Set<String> values) //constructor
//getters
#Override
public boolean equals(Object o) {
...
return id.equals(a.id);
}
//hashCode
}
The final result should be a list of 2 objects:
B{id=1, name='A1', values=[V1, V2]}
B{id=2, name='A2', values=[V1]
Thanks in advance!
I'm not sure what you question aims at. If the question is what the minimal changes are that are necessary in order to avoid re-creating the stream, then I have to answer: I don't know. However, it seems like your approach is overly complicated. The chain of map, collect, filter, distinct and collect that is built there is really hard to understand.
After a short rant...
Maybe my fear that future Java programs will all look like this, and thus become completely unmaintainable is not justified. Maybe one just has "to get used to" this programming style. Maybe there's a short period when people are too eager with using the new language features, and it will come back to a "normal" style (and a "healthy" level of functional elements) sooner or later. But I, personally, think that a method like createBsByMergingTheValuesOfAs() would be sufficient and appropriate here.
... I'd like to suggest using a dedicated Collector, which already offers much of the infrastructure for mutable reduction that you seem to be emulating with this chain of operations:
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class StreamCollectTest
{
public static void main(String[] args)
{
List<A> listOfA = Arrays.asList(
new A(1L, "A1", "V1"),
new A(2L, "A2", "V1"),
new A(1L, "A1", "V2"));
Map<Long, B> result = listOfA.stream().collect(
Collectors.toConcurrentMap(
// The "id" will be the key of the map
a -> a.getId(),
// The initial value stored for each key will be a "B"
// whose set of values contains only the element of
// the corresponding "A"
a -> new B(a.getId(), a.getName(),
new LinkedHashSet<String>(Collections.singleton(a.getVal()))),
// Two "B"s with the same key will be merged by adding
// all values from the second "B" to that of the first
(b0,b1) -> { b0.values.addAll(b1.values); return b0; }));
System.out.println(result);
}
static class A
{
private final Long id;
private final String name;
private final String val;
A(Long id, String name, String val)
{
this.id = id;
this.name = name;
this.val = val;
}
public Long getId()
{
return id;
}
public String getName()
{
return name;
}
public String getVal()
{
return val;
}
}
static class B
{
private final Long id;
private final String name;
private final Set<String> values;
B(Long id, String name, Set<String> values)
{
this.id = id;
this.name = name;
this.values = values;
}
#Override
public String toString()
{
return id+","+name+","+values;
}
}
}
It prints
{1=1,A1,[V1, V2], 2=2,A2,[V1]}
So the values() of the resulting map should be exactly what you are looking for.
I am writing a deserializer method, which looks like so:
public <T> T deserialize(Object[] result, String[] fields, Class<T> type);
So basically I will be passed in a result array of data which is all objects, and a class type T which I need to convert the data in the array to the types in the given class, and create a new class of type T and return it. The String[] fields is the field names corresponding to the data in Object[] result. The field names will correspond to the Class T.
The casting will need to use reflection of the given class to find out the type of each field.
eg.
result = ["Mike", "London", 28];
fields = ["name", "location", "age" ];
Class T =
public class GivenClass{
private String name;
private String location;
private Integer age;
public GivenClass(String name, String location, Integer age){
this.name = name;
this.location = location;
this.age = age;
}
}
Class implementation
static class GivenClass {
private String name;
private String location;
private Integer age;
public GivenClass(String name, String location, Integer age) {
this.name = name;
this.location = location;
this.age = age;
}
public GivenClass(Map<String, Object> data) throws Exception {
for (Field f : GivenClass.class.getDeclaredFields())
f.set(this, data.get(f.getName()));
}
public Map<String, Object> serialize() throws Exception {
Map<String, Object> fields = new HashMap<String, Object>();
for (Field f : GivenClass.class.getDeclaredFields())
fields.put(f.getName(), f.get(this));
return fields;
}
#Override
public String toString() {
return "age=" + age + ", location=" + location + ", name=" + name;
}
}
Example:
public static void main(String[] args) throws Exception {
GivenClass o1 = new GivenClass("Mike", "London", 28);
Map<String, Object> serialized = o1.serialize();
GivenClass o2 = new GivenClass(serialized);
System.out.println(o2.toString());
}
Output:
age=28, location=London, name=Mike
You need to do the conversion yourself. Reflections doesn't convert (it will only check the type of an object is already correct)
Reflections won't give you the names of method/constructor parameters. (You can get them from the debug byte code but that's a real pain)
The approach I take is to use the convention that the constructor parameters are in the same order as the fields. You will also want to assume the type of constructor parameters and field types match. ;)
I would also use primitives instead of wrappers whenever possible. Use int unless you want null to be a valid option. If this is the case you should think about how you want to represent this. For text I usually use empty strings or blank field for null or NaN depending on the context.
The problem with this, is that in Java it's unable to fetch the parameter names of a constructor.
For this particular example, you'll need a default constructor, with which you could create an empty object.
public GivenClass() {
super();
}
Then you could use reflection to get the fields of the class, and then set the appropriate value for them.
But I think it would be much easier to annotate your constructor, and then fetch the annotation informations in your deserialize method. In this case you won't need to fetch the fields and create an empty constructor.
Example:
You need to create a annotation like this:
#Target({ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface Property
{
String value();
}
And then you can use it in your constructor like this:
public GivenClass(#Property("name") String name, #Property("location") String location, #Property("age") Integer age) {
// ...
}
As Peter Lawrey says, casting does not convert a string into an integer.
If your bean follows the standard bean conventions (ie you have getters & setters), then you can use BeanUtils. BeanUtils does some standard conversions, and you can add more by adding a Convertor.
See the following example:
import org.apache.commons.beanutils.BeanUtils;
public class BeanUtilsTest {
public static class Obj {
private int number;
private String string;
public void setNumber(int number) {
this.number = number;
}
public void setString(String string) {
this.string = string;
}
public String toString() {
return "number=" + number + " string=" + string;
}
}
public static void main(String args[]) throws Exception {
String[] values = new String[] { "1", "two" };
String[] properties = new String[] { "number", "string" };
Obj obj = new Obj();
for (int i = 0; i < properties.length; i++) {
BeanUtils.setProperty(obj, properties[i], values[i]);
}
System.out.println("obj=" + obj);
}
}
This produces as output:
obj=number=1 string=two
Note that the above example has only setters, but still works.