I have a problem in a Java project.
The code where error born is the following:
HashMap<String, LinkedList<? extends User>> signedUpUsers =
new HashMap<>(Map.of(
"Administrator", new LinkedList<Administrator>(),
"Employee", new LinkedList<Employee>(),
"Customer", new LinkedList<Customer>()));
for (String userName : userNameList)
{
userPropertyValue = usersProperties.getProperty(userName).split(",");
String password = userPropertyValue[0].replaceAll("\\s", "");
String role = userPropertyValue[1].replaceAll("\\s", "");
if (role.equals("Administrator"))
{
signedUpUsers.get("Administrator").add(new Administrator(userName, password));
}
else if (role.equals("Customer"))
{
signedUpUsers.get("Customer").add(new Customer(userName, password));
}
else
{
signedUpUsers.get("Employee").add(new Employee(userName, password));
}
}
It gives me an error when I try to add new elements in each list of hashmap, when I create instances, intellij tells me:
Required type: capture of ? extends User
Provided: Customer (or Employee or Administrator)
But why, if Customer, Employee and Administrator are all subclasses of User?
What I should change? My intention is to have an HashMap which contains all signed up users (I saved them in a .properties file which is corrected red because I saw that), where keys are roles of users (Administrators, Employees and Customers), and the value of each key is a LinkedList of each user with that role.
I also tried to use super instead of extends, but in that case I solve this error, but a new error appear in creating the hashmap with Map.of() (because Administrator, Customer and Employee aren't superclass of User).
The code works if I have 3 different lists declared directly with 3 roles objects, but I wanted the hashmap because I want to return the whole signed up users divided by their role.
Thanks to all, I hope I was clear in explaining.
The reason for the compiler error has been covered in #Thomas's comment: To the compiler, signedUpUsers.get("Administrator") is a LinkedList<? extends User>, not knowing that under the "Administrator" key, you stored a LinkedList<Administrator> (and not e.g. a LinkedList<Employee>, so the compiler does not allow adding an Administrator.
Your signedUpUsers variable shows some significant generics over-engineering. You declare
HashMap<String, LinkedList<? extends User>> signedUpUsers =
new HashMap<>(Map.of(
"Administrator", new LinkedList<Administrator>(),
"Employee", new LinkedList<Employee>(),
"Customer", new LinkedList<Customer>()));
I suggest to change that to
HashMap<String, LinkedList<User>> signedUpUsers =
new HashMap<>(Map.of(
"Administrator", new LinkedList<User>(),
"Employee", new LinkedList<User>(),
"Customer", new LinkedList<User>()));
You might ask "But now I don't have the type safety that I can only store Administrator instances under the "Administrator" key." But that type safety also wasn't possible with the first version (at run-time, the LinkedList<Administrator> is just a LinkedList and will happily accept any Object, and at compile-time LinkedList<? extends User> will not allow adding anything).
If you want type safety for the lists, throw away the Map approach, and create a class UserList:
public class UserList {
private List<Administrator> administrators;
private List<Employee> employees;
private List<Customer> customers;
// add constructor, getters, adders etc. here
}
This will easily give the desired type safety.
Related
I have the following domain classes Trip and Employee:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Trip {
private Date startTime;
private Date endTime;
List<Employee> empList;
}
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Employee {
private String name;
private String empId;
}
I have a list of Trip instances. And I want to create a map of type Map<String,List<Trip>> associating id of each employee empId with a list of trips using Stream API.
Here's my attempt:
public static void main(String[] args) {
List<Trip> trips = new ArrayList<>();
Map<Stream<String>, List<Trip>> x = trips.stream()
.collect(Collectors.groupingBy(t -> t.getEmpList()
.stream().map(Employee::getEmpId)
));
}
How can I generate the map of the required type?
When the type of map is Map<String,List<Trip>> it gives me a compilation error:
Unresolved compilation problem: Type mismatch:
cannot convert from Map<Object,List<Trip>> to Map<String,List<Trip>>
To group the data by the property of a nested object and at the same time preserve a link to the enclosing object, you need to flatten the stream using an auxiliary object that would hold references to both employee id and enclosing Trip instance.
A Java 16 record would fit into this role perfectly well. If you're using an earlier JDK version, you can implement it a plain class (a quick and dirty way would be to use Map.Entry, but it decreases the readability, because of the faceless methods getKey() and getValue() require more effort to reason about the code). I will go with a record, because this option is the most convenient.
The following line is all we need (the rest would be automatically generated by the compiler):
public record TripEmployee(String empId, Trip trip) {}
The first step is to flatten the stream data and turn the Stream<Trip> into Stream<TripEmployee>. Since it's one-to-many transformation, we need to use flatMap() operation to turn each Employee instance into a TripEmployee.
And then we need to apply collect. In order to generate the resulting Map, we can make use of the collector groupingBy() with collector mapping() as a downstream. In collector mapping always requires a downstream collector and this case we need to provide toList().
List<Trip> trips = // initializing the list
Map<String, List<Trip>> empMap = trips.stream()
.flatMap(trip -> trip.getEmpList().stream()
.map(emp -> new TripEmployee(emp.getEmpId(), trip))
)
.collect(Collectors.groupingBy(
TripEmployee::empId,
Collectors.mapping(TripEmployee::trip,
Collectors.toList())
));
A Java 8 compliant solution is available via this Link
Not sure which Java version you are using but since you have mentioned Stream, I will assume Java 8 at least.
Second assumption, not sure why but looking at your code (using groupingBy ) you want the whole List<Trip> which you get against an empId in a Map.
To have the better understanding first look at this code (without Stream):
public Map<String, List<Trip>> doSomething(List<Trip> listTrip) {
List<Employee> employeeList = new ArrayList<>();
for (Trip trip : listTrip) {
employeeList.addAll(trip.getEmployee());
}
Map<String, List<Trip>> stringListMap = new HashMap<>();
for (Employee employee : employeeList) {
stringListMap.put(employee.getEmpId(), listTrip);
}
return stringListMap;
}
You can see I pulled an employeeList first , reason being your use case. And now you can see how easy was to create a map out of it.
You may use Set instead of List if you're worried about the duplicates.
So with StreamApi above code could be:
public Map<String, List<Trip>> doSomethingInStream(List<Trip> listTrip) {
List<Employee> employeeList = listTrip.stream().flatMap(e -> e.getEmployee().stream()).collect(Collectors.toList());
return employeeList.stream().collect(Collectors.toMap(Employee::getEmpId, employee -> listTrip));
}
You can take care of duplicates while creating map as well, as:
public Map<String, List<Trip>> doSomething3(List<Trip> listTrip) {
List<Employee> employeeList = listTrip.stream().flatMap(e -> e.getEmployee().stream()).collect(Collectors.toList());
return employeeList.stream().collect(Collectors.toMap(Employee::getEmpId, employee -> listTrip, (oldValue, newValue) -> newValue));
}
Like the first answer says, if you are Java 16+ using record will ease your task a lot in terms of model definition.
Using Java 8 stream
You can use the below approach to get the desired results using stream function groupingBy.
Since you have mentioned above to use java 8, so my solution is inclined to java 8 itself.
Logic:
Here,
First I have created an additional list of EmployeeTripMapping object
with Trip data corresponding to the empId by iterating the
listOfTrips.
I have used Collectors.groupingBy on the List<EmployeeTripMapping>
and grouped the data based on the empId and using Collectors.mapping
collect the list of Trip corresponding to the empId.
Few Suggestions:
Records in java 14 : As I can see in your problem statement, you
are using lombok
annotations to create getters, setters and constructors, so instead of
that we can replace our data classes
with records. Records are immutable classes that require only the type
and name of fields. We do not need to create constructor, getters,
setters, override toString() methods, override hashcode and equals
methods. Here
JavaTimeAPI in java 8: Instead of Date, you can use LocalDateTime available in java time API in java 8. Here
Code:
public class Test {
public static void main(String[] args) {
Trip t1 = new Trip(LocalDateTime.of(2022,10,28,9,00,00),
LocalDateTime.of(2022,10,28,18,00,00),
Arrays.asList(new Employee("emp1","id1")));
Trip t2 = new Trip(LocalDateTime.of(2021,10,28,9,00,00),
LocalDateTime.of(2021,10,28,18,00,00),
Arrays.asList(new Employee("emp1","id1")));
Trip t3 = new Trip(LocalDateTime.of(2020,10,28,9,00,00),
LocalDateTime.of(2020,10,28,18,00,00),
Arrays.asList(new Employee("emp2","id2")));
Trip t4 = new Trip(LocalDateTime.of(2019,10,28,9,00,00),
LocalDateTime.of(2019,10,28,18,00,00),
Arrays.asList(new Employee("emp2","id2")));
List<Trip> listOfTrips = Arrays.asList(t1,t2,t3,t4);
List<EmployeeTripMapping> empWithTripMapping = new ArrayList<>();
listOfTrips.forEach(x -> x.getEmpList().forEach(y ->
empWithTripMapping.add(new EmployeeTripMapping(y.getEmpId(),x))));
Map<String,List<Trip>> employeeTripGrouping = empWithTripMapping.stream()
.collect(Collectors.groupingBy(EmployeeTripMapping::getEmpId,
Collectors.mapping(EmployeeTripMapping::getTrip,
Collectors.toList())));
System.out.println(employeeTripGrouping);
}
}
EmployeeTripMapping.java
public class EmployeeTripMapping {
private String empId;
private Trip trip;
//getters and setters
}
Output:
{emp2=[Trip{startTime=2020-10-28T09:00, endTime=2020-10-28T18:00, empList=[Employee{empId='emp2', name='id2'}]}, Trip{startTime=2019-10-28T09:00, endTime=2019-10-28T18:00, empList=[Employee{empId='emp2', name='id2'}]}],
emp1=[Trip{startTime=2022-10-28T09:00, endTime=2022-10-28T18:00, empList=[Employee{empId='emp1', name='id1'}]}, Trip{startTime=2021-10-28T09:00, endTime=2021-10-28T18:00, empList=[Employee{empId='emp1', name='id1'}]}]}
So right now, I am making a simple java banking program. It allows you to add a customer and deposit/withdraw funds. Right now, I have 3 classes: Main, Bank, and Customer. Right now I have it so that when you add a customer, it asks you for a name. But right now I am having trouble naming them. I want
Customer to have a username as the object name. For example, if I typed in Bob1789 as the username, the program would do:
Customer Bob1789 = new Customer("Bob1789");
If I typed in randomcustomer123 the program would do:
Customer randomcustomer123 = new Customer("randomcustomer123");
So basically, whatever I type in the box from the scanner, to be passed to the Customer name.
Customer (whatever was typed in the scanner) = new Customer((whatever was typed in the scanner));
I have tried to do this, but java always assumes that the scanner.NextLine() is the Object name.
Is there any way to do this?
You can use a HashMap<String, Customer> for this. This allows you to store name-customer pairs.
HashMap<String, Customer> allCustomers = new HashMap<>();
To create a new customer and put it into the map,
String customerName = scanner.nextLine();
allCustomers.put(customerName, new Customer(customerName));
To get a customer with a specific name, use this:
allCustomers.get("some name");
Don't know why you want to use obj name. Probably you need to use HashMap<String, Customer> where name is the key and value is the object.
Map<String, Customer> map = new HashMap<>();
to add map.put("yourName", obj);
to fetch map.pget("yourName");
I have a Staff class which contains a list of roles for that particular staff member. I also have staffList which holds the staff objects.
How do I get an element from the rolesList for that specific instance of the class?
I have tried:
staffList.get(0).getRolesList().get(0)
Which should be the first element of the rolesList from the first element of the staffList, but it just ruturns null.
I also tried:
rolesList.get(staffList.get(0))
rolesList.getIndexOf(staffList.get(0).getRolesList.get(0)
All return null
If I just get the value direct from the rolesList using get index it will display no problem.
I think it is getting a version of the rolesList but not the one that is in that particular Staff object
I have created a new Staff Member and a role in the roleList then used the list when I construct the staff object, so using the getRolesList method of that object and then get the index it should return the value from within the roleList but it isn't:
private List<Staff> staffList = new ArrayList();
ArrayList<Role> roleList = new ArrayList();
roleList.add(Role.DRIVER);
testDriver = new Staff("Mike", "Joy", roleList);
testStaffList.add(testDriver);
GetRolesList() Code
public List<Role> getRoleList() {
return roleList;
}
I basically want to get the stored Role from the objects rolelist within the testdriver object.
testDriver --> roleList --> Role.DRIVER (or whatever the Role happens to be)
(a) We do not see the line of code adding testDriver to the staffList.
staffList.add( testDriver );
(b) You are missing parens on your call to getRolesList.
(c) You need to do some basic debugging. In the debugger or in extra code, look at:
size of staffList
Staff s = staffList.get(0)
List roles = s.getRolesList()
size of roles
Bonus tip… Apparently you are using an enum for Role. If so, you should consider using an EnumSet rather than a List for less memory usage and faster execution. EnumSet is a specialized implementation of Set. More discussion on another Question. Not part of your problem, just a tip.
Set<Role> roles = EnumSet.of( Role.DRIVER , Role.MECHANIC );
Staff testDriver = new Staff( "Mike" , "Joy" , roles );
Could you give your full source..
That's not clear. I think u wanna add your staff object into staff list.. If it is
Stafflist.add(new Staff("a","b",role list));
Add all you want
Then try to get ..
I'm trying to build a form generating class and i might have hit a glitch somewhere in my logic statement.
You have two string arrays.
String[] fieldNames;
String[] fieldTypes;
Both their lengths should be the same. Each value in the fieldName[] corresponds to a value in the fieldTypes[] array.
I want to create various fields depending on the values stated in the fieldTypes array and assign the created fields the name specified in the fieldNames array.e.g.
String[] fieldNames = {"Name", "Phone", "Gender"}
String[] fieldTypes = {"TextFied","ComboBox", "RadioButton"}
The field types and names can vary. They can be whatever you want them to be.
Now, using the above info, how do i assign the fieldNames to the fieldTypes so I can use them in the data processing? i.e
TextField name = new TextField();
ComboBox phone = new ComboBox();
RadioButton gender = new RadioButton();
I've been mulling this over for a week now and there doesn't seem to be any solution to this online. Or rather I haven't been able to find one. I someone could point me in the right direction i'll be greatful
You could use a Map of String and Class, as such:
// This is for AWT - change class bound to whatever super class or interface is
// extended by the elements of the framework you are using
Map<String, Class<? extends Component>> fields = new LinkedHashMap<String, Class<? extends Component>>();
fields.put("Name", TextField.class);
The Map is a LinkedHashMap so you can keep the order of the keys.
Once you retrieve a value through the get method, you can get the class of the desired component and act upon.
Edit
Here's how to retrieve the component through reflexion. Note that it's not the only solution and might not be the "cleanest"...
try {
Component foo = fields.get("Name").newInstance();
System.out.println(foo.getClass());
}
catch (Throwable t) {
// TODO handle this better
t.printStackTrace();
}
Output:
class java.awt.TextField
I'm tring to create an arraylist of different class instances. How can I create a list without defining a class type? (<Employee>)
List<Employee> employees = new ArrayList<Employee>();
employees.add(new Employee());
Employee employee = employees.get(0);
You could create a list of Object like List<Object> list = new ArrayList<Object>(). As all classes implementation extends implicit or explicit from java.lang.Object class, this list can hold any object, including instances of Employee, Integer, String etc.
When you retrieve an element from this list, you will be retrieving an Object and no longer an Employee, meaning you need to perform a explicit cast in this case as follows:
List<Object> list = new ArrayList<Object>();
list.add("String");
list.add(Integer.valueOf(1));
list.add(new Employee());
Object retrievedObject = list.get(2);
Employee employee = (Employee)list.get(2); // explicit cast
List<Object> objects = new ArrayList<Object>();
objects list will accept any of the Object
You could design like as follows
public class BaseEmployee{/* stuffs */}
public class RegularEmployee extends BaseEmployee{/* stuffs */}
public class Contractors extends BaseEmployee{/* stuffs */}
and in list
List<? extends BaseEmployee> employeeList = new ArrayList<? extends BaseEmployee>();
List anyObject = new ArrayList();
or
List<Object> anyObject = new ArrayList<Object>();
now anyObject can hold objects of any type.
use instanceof to know what kind of object it is.
I believe your best shot is to declare the list as a list of objects:
List<Object> anything = new ArrayList<Object>();
Then you can put whatever you want in it, like:
anything.add(new Employee(..))
Evidently, you will not be able to read anything out of the list without a proper casting:
Employee mike = (Employee) anything.get(0);
I would discourage the use of raw types like:
List anything = new ArrayList()
Since the whole purpose of generics is precisely to avoid them, in the future Java may no longer suport raw types, the raw types are considered legacy and once you use a raw type you are not allowed to use generics at all in a given reference. For instance, take a look a this another question: Combining Raw Types and Generic Methods
How can I create a list without defining a class type? (<Employee>)
If I'm reading this correctly, you just want to avoid having to specify the type, correct?
In Java 7, you can do
List<Employee> list = new ArrayList<>();
but any of the other alternatives being discussed are just going to sacrifice type safety.
If you can't be more specific than Object with your instances, then use:
List<Object> employees = new ArrayList<Object>();
Otherwise be as specific as you can:
List<? extends SpecificType> employees = new ArrayList<? extends SpecificType>();
I see that all of the answers suggest using a list filled with Object classes and then explicitly casting the desired class, and I personally don't like that kind of approach.
What works better for me is to create an interface which contains methods for retrieving or storing data from/to certain classes I want to put in a list. Have those classes implement that new interface, add the methods from the interface into them and then you can fill the list with interface objects - List<NewInterface> newInterfaceList = new ArrayList<>() thus being able to extract the desired data from the objects in a list without having the need to explicitly cast anything.
You can also put a comparator in the interface if you need to sort the list.
I know this is an old question, but there's a nice and easy way to do this (it works with the mostly recent versions of ElasticSearch Rest API).
The search object goes like:
SearchResponse<JsonData> search = client.search(s -> s
.index(index)
.query(query),
JsonData.class);
And then I iterate over the response like this:
for (Hit<JsonData> hit: search.hits().hits()) {
String stringSource = hit.source().toString();
MySavedRegister mySavedRegister = mapper.readValue(stringSource, mySavedRegister .class);
mylist.add(esGenericEvent);
}
Where mySavedRegister stands for the class that has the hits' sources parameters.