How to search a generic collection object in java - java

I am working on java and currently trying to search a list of object of a particular DummyUserList type.
public class DummyUserList implements Serializable {
String firstName;
String lastName;
String city;
----
----
setter andd getter will go here.
}
and
public static void main(String[] args) {
List<DummyUserList> searchListObj = new ArrayList<DummyUserList>();
searchListObj.add(dummyUserList1);
searchListObj.add(dummyUserList2);
searchListObj.add(dummyUserList3);
String toBeSearched = "Singhaniya";
List<DummyUserList> result = searchListObj.stream()
.filter(a -> Objects.equals(a.lastName, toBeSearched))
.collect(Collectors.toList());
System.out.println("result--->" + result);
}
Currently, the above code is searching DummyUserList object on the basis of lastName. It's working fine, but how can I pass a generic collection object to the filter method for searching?
I am trying in the following way: I am passing a generic collection object and it's supposed to be search and return a list of searched object, but I'm getting a syntax error saying "searchText can not be resolved or is not a field".I want to do something like below code.
public static <T> List<T> search(Collection<T> collectionObject,
String searchProperty, String searchText) {
List<T> result = collectionObject.stream()
.filter(a -> Objects.equals(((List<T>) collectionObject).searchText))
.collect(Collectors.toList());
}
How do I pass a generic collection to the above method for searching? Is it possible to search a dynamic collection object?

If you search a Collection<T>, each element of the collection is of type T. Not of type List<T>. So your cast doesn't make sense.
Objects.equals() takes two arguments, not one.
And you can't just pass the name of a property as a String (you would need reflection to access the field). Instead, you need a way to transform each T object into one of its properties of type String, in order to compare this property with the search text. So you need a Function<T, String>:
public static <T> List<T> search(Collection<T> collectionObject,
Function<T, String> searchPropertyAccessor, String searchText) {
List<T> result = collectionObject.stream()
.filter(item -> Objects.equals(searchPropertyAccessor.apply(item), searchText))
.collect(Collectors.toList());
}
And you would call it this way:
List<User> foundUsers = search(users, User::getLastName, "Doe");
Note that I chose to rename your DummyUserList class to User, since what this class represents is a user, not a user list. Also note that I used a getter to access its last name, as public fields are a bad idea in general, and prevent the use of method references, which are quite handy and expressive.

Related

Is Java List of multiple types possible

yesterday I stumbled upon some strange Java/Spring/IntelliJ behavior that I was not able to explain.
This is a Spring Boot application made with jdk1.8.0_152.
I ran this simple SQL to fill my DB:
CREATE TABLE TEST_ORGANIZATION
(
ID NUMBER(19) NOT NULL,
NAME VARCHAR(255) NOT NULL
);
INSERT INTO TEST_ORGANIZATION (ID, NAME) VALUES ('1', 'test1');
INSERT INTO TEST_ORGANIZATION (ID, NAME) VALUES ('2', 'test2');
Here's my Entity class:
#Data
#NoArgsConstructor
#Entity
public class TestOrganization {
#Id
private Long id;
#NotNull
private String name;
}
And my JPA Repository:
public interface TestOrganizationRepository extends JpaRepository<TestOrganization, Long> {
#Query("SELECT new map(id as key, name as value) FROM TestOrganization")
List<Map<String, String>> findAllAndMapById();
}
And this is where things get confusing.
I've written a simple unit test to check for the values, but turns out it fails on second assert:
#Test
public void shouldGetDocumentByName() {
List<String> values = testOrganizationRepository.findAllAndMapById()
.stream()
.flatMap( m -> m.values().stream() )
.collect( Collectors.toList() );
assertThat( values ).isNotEmpty();
assertThat( values ).allMatch( n -> n instanceof String );
}
When debugging with IntelliJ, it shows values like this:
How is this possible? Why am I able to have Long values within a List of String?
Also:
values.add(1L) // fails to compile
values.get(0).getClass().name // returns java.lang.String
values.get(1).getClass().name // returns java.lang.Long
This is possible because of type erasure in Java. In short, this means that in the runtime Java doesn't know about your generic type, so any List object operates with Object type. The generics are made to have the compile-type safety.
But you can read about type erasure in more details, for example here.
The same behavior you can emulate by yourself:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
addToList(list);
System.out.println(list);
}
private static void addToList(List list) {
list.add(1L);
list.add(42);
list.add("String");
list.add(new Runnable() {
#Override
public void run() {}
});
}
This code works fine as long as you don't try to operate with the list entries as a Strings. But when you add something like:
for (String s : list) {
System.out.println(s);
}
You will get java.lang.ClassCastException.
So in compile time, you work with List<String>, but in the runtime, Java only knows about List.
yes you can, here is small example
public static void main(String args) {
List objects = new ArrayList();
objects.add("string");
objects.add(1L);
List<String> onlyStrings = objects;
}
you can cast your non generic list to List if you wish so (you get bunch of compiler warnings).
Consider the following example :
public static void main(String[] args) {
List a = new ArrayList();
a.add("a");
a.add(1L);
List<String> b = new ArrayList(a);
System.out.println(b);
}
This outputs
[a, 1]
The above is legal, as java performs type erasure, thus discarding the element type information at runtime. This means that every List is essentially a List and can contain multiple class instances.
public interface List<E> extends Collection<E>
gets converted to
public interface List extends Collection
at compile time.
If E was bound, i.e. E extends MyClass, it would be converted to MyClass.
In your case, you are instantiating a map for every row, without type bounds, you are flatmapping it and then collecting it to a List, which is effectively the same as my example, and thus, legal.

How to use AssertThat to check the property value of objects in a list?

I am to using asserThat to verify properties of objects in an ArrayList.
I am able to use assertThat if none of the objects contains data to be tested but I am not able to figure how to use assertThat if only one of the objects contain data to be tested.
Sample Code:
public class dataNeeded {
String data;
String modifiedDate;
public String getData(){
return data;
}
......
}
public ArrayList<dataNeeded> returnsNeededData(){
...
}
List<dataNeeded> dataToBeTested = returnsNeededData();
dataToBeTested.forEach(data->assertThat(data.getData(), not(equalTo("No"))));
List<dataNeeded> dataToBeTested2 = returnsNeededData();
// I need to verify if one of the objects in this list has it's data as "No"
dataToBeTested.forEach(data->assertThat(data.getData(), ???);
You can map to List<String> via the following function (uses Java 8 Streams) to extract all data strings
private List<String> mapToDataStrings(List<dataNeeded> neededData) {
return neededData.stream()
.map(dataNeeded -> dataNeeded.getData())
.collect(toList());
}
and then use the everyItem/hasItem function in Matchers to make assertions on the resulting list of strings:
List<String> dataToBeTested = mapToDataStrings(returnsNeededData());
assertThat(dataToBeTested, everyItem(not("No")));
List<String> dataToBeTested2 = mapToDataStrings(returnsNeededData());
assertThat(dataToBeTested2, hasItem("No"));
With everyItem you can assert that every element in the list is not "No" and hasItem asserts that there is at least one element in the list with value "No".
Alternatively you could implement a feature matcher to avoid the mapping function.

Best practice for method with Map return

Suppose i have a method which has map as return type and uses generics.
I would like to know what is the best practice of filling that Map object.
Please see the snippet.
public Map<String,?> getEmployeeInfo(String query) {
Map<String,Object> dataMap = new HashMap<String,Object>();
// do some op.
String empId = "abc123";
List<Long> projectIds = new ArrayList<Long>();
List<String> performanceGoals = new ArrayList<String>();
dataMap.put("empId",empId);
dataMap.put("projectIds",projectIds);
dataMap.put("performanceGoals",performanceGoals);
return dataMap;
}
The best practise is: Don't use this.
Make a Class Employee with members
public class Employee {
String id;
List<Long> projectIds;
List<String> performanceGoals;
...
}
And you method changes to:
public Employee getEmployeeInfo(String query) {
...
update for clarification why returning Map is bad in general
If your method returns:
Map<String,?> or Map<String,? extends Object> you say (in slang):
Hey look here, I am returning something. Store it in a variable called "something", because I don't say anything about the value.
If you write this method, you have to ensure, that you know every single line of code, where you work with your Map.
Lets say I would like to change employeeId from String to Integer. This will lead to really bad RuntimeExceptions and ClassCastExceptions.

Add all items from HashSet<T> to Map<String,T>

I have a HashSet<T> of objects that I want to insert into a Map<String,T> where the String key is a property of T. What is most efficient way to do this? I'm guessing there is a better way than what I have currently:
Map<String,T> myMap = new HashMap<String,T>();
HashSet<T> mySet = new LinkedHashSet<T>();
... Add some T's to mySet
for(T t : mySet){
myMap.put(t.getObjectKey(), t);
}
You can achieve that in Java 8 using lambda expressions :
Map<String, T> myMap = mySet.stream()
.collect(Collectors.toMap(x -> x.getObjectKey(),x -> x));
where T is the type of your objects and assuming that x.getObjectKey() returns a String.
Demo here.
The Guava library allows you to use a more functional-style way to express roughly what you're doing, but it's not going to be more efficient, and with the current state of the Java programming language it won't be particularly pretty either.
Until we get lambda expressions in Java 8, you're pretty much stuck doing exactly what you've done.
A bit late to answer, bu i will make an answer: If i required to have options something like this, i would design a custom HashMap for myself following OOP tradition: allowing any type of Collection of type Set and List.
The Object class which are going to be mapped as (key, value) pair entry in HashMap<K, V> should have a function getRelationalKey() with return type K. Hence we need to mark the object class as a type Relational for forcing the implementation. We will use the help of an interface:
interface Relational<T>
{
public T getRelationalKey();
}
The HashMap<K, V> should have a function putAllFromRCollection(Collection) to put all such Object of the collection to the Map. Hence, the function declaration with signature should be something like: putAllFromRCollection(Collection<? extends Relational<K>> collection. Hence, the custom HashMap: MyHashMap can have an implementation like:
class MyHashMap<K, V> extends HashMap<K, V>
{
public void putAllFromRCollection(Collection<? extends Relational<K>> collection)
{
for(Relational<K> relObj: collection)
{
put(relObj.getRelationalKey(), (V)relObj);
}
}
}
It is time for a test case: Lets declare a class:
class ATestClass implements Relational<String>
{
public String name;
public String address;
public ATestClass(String name, String address) {
this.name = name;
this.address = address;
}
#Override
public String getRelationalKey() {
return name;// or address or whatever should be regarded as a key
}
}
And add some object of this class to a HashSet:
HashSet<ATestClass>testSet = new HashSet<>();
testSet.add(new ATestClass("AName", "A-Adress"));
testSet.add(new ATestClass("BName", "B-Adress"));
testSet.add(new ATestClass("CName", "C-Adress"));
testSet.add(new ATestClass("DName", "D-Adress"));
MyHashMap<String, ATestClass>myMap = new MyHashMap<>();
myMap.putAllFromRCollection(testSet);
System.out.println(myMap);
Output:
{
CName=CollectionsTest.ATestClass#c21495,
BName=CollectionsTest.ATestClass#14b7453,
DName=CollectionsTest.ATestClass#1d5550d,
AName=CollectionsTest.ATestClass#1186fab
}
The advantages are:
we are now allowed to define required data type of the key to make the relation with the object
set a specific target field of Class as the Key matching type of the HashMap'S key
Allowing any type of Collection<E> instead of just a HashSet
In this implementation however, i have avoided to provide code for resizing the map with the size of the provided Collection for performance achievement. Please refer to the putAll(Map) function's source for details.

Any collection object to hold list of combination of more than 2 elements?

Is there a collection object or a approach to hold a combination of elements?
For instance, I need to create a list that contains the combination of the elements name, age, height and weight.
Creating an object for this is not a good idea in my case. Because the number of fields keep changing.
I need to create this list to pass to a query.
Any solution?
class MyContainer {
String someString;
int someInt;
}
List <MyContainer> myList = new List<>();
Something like that!?
I donĀ“t know exactly, what you mean by "Creating an object for this is not a good idea in my case". You could as an alternative create a List<Object> and put in whatever you have or even a List<List<Object>> if you want to have a List of a number of grouped objects.
The best approach would be to make an Object with all the possible elements in it.
class myObject {
String name;
Integer age;
Float weight;
// Etc
}
Or have a base class then have another class which extends this with additional elements
class myExtendedObject extends myObject{
String streetAddress;
String city;
// etc;
}
Then if you don't have an element set it to null... you could always build your query from the object itself by including a method to return your query, juct check if its null and not include in your query (Assuming you mean an sql style query)
public String buildQuery{
String query = "Select * from blahtable Where ";
query += (name != null)?" name = " + name : "";
// etc, or what ever your query needs to be
return query
}
Other wise you could just have a method which returns a map of your elements then you know what the type of each element is based on the key
public Map<String, Object> getElements{
Map<String, Object> myMap = new HashMap<String, Object>();
if(name != null)
myMap.put("Name", name);
// etc
return myMap
}
What about just using a Map for that and use attribute name as key (e.g. Weight )?
You can use any combination of attributes you want and it would be convenient to pass such collection to the query
Consider Enum map should you require more column names type safety

Categories

Resources