In Java EnumMaps have constant access time, because they can be implemented with an array using the ordinal value of the enum as an index.
If I have an EnumMap how can I get the Array?
Example:
enum Abc { A, B, C };
EnumMap<Abc, String> abc = new EnumMap<Abc, String>(Abc.class);
abc.put(Abc.A, "α");
abc.put(Abc.B, "β");
abc.put(Abc.C, "γ");
String[] abcarray = ?;
The variable abcarray should contain the same as if initialized in this way:
String[] abcarray = new String[] { "α", "β", "γ" };
I can not find a method in the documentation. Why is such a function missing?
You can go via the Map.values() method (remember that an EnumMap is just a specialized Map):
String[] abcarray = abc.values().toArray(new String[abc.size()]);
For the reason why no such method exists we can only speculate.
But there are two common themes in the collections framework, that come into play here:
allow complexer operations by chaining simpler operations (which reduces the total number of operations/methods that need to be implemented, while keeping the options available)
get rid of arrays: they have no real use once you're in "collection area"
It is a little abstracted... try :
String[] abcarray = abc.values().toArray(new String[abc.size()]);
Related
I have a HashMap groups which maps to ArrayList objects for its values. I wanted to re-assign the values given they meet a particular condition (i.e. in this case, they are present in another list) and came across the forEach method. Here's how I implemented it. I could not find any documentation as to whether it is able to transform my data hence my question.
Here's the code:
groups.get(request.getGroup()).forEach(
member -> {
int updatedMemberIndex = request.getGroupMembers().indexOf(member);
if (updatedMemberIndex != -1)
member = request.getGroupMembers().get(updatedMemberIndex);
}
);
And here are the method signatures for the relevant methods in request object:
List<GroupMember> getGroupMembers()
Group getGroup()
No, you cannot mutate the collection in this way. member is a local variable, so reassigning it in the if doesn't do anything.
You could use member to change properties of the GroupMember object itself, but that's not what you want.
It looks like you're trying to perform a map operation. You probably want something like:
List<GroupMember> newList = groups.get(request.getGroup()).stream().map(
member -> {
int updatedMemberIndex = request.getGroupMembers().indexOf(member);
return updatedMemberIndex != -1
? request.getGroupMembers().get(updatedMemberIndex)
: member;
}
).collect(Collectors.toList());
I have two list containing an important number of object with each N elements:
List<Foo> objectsFromDB = {{MailId=100, Status=""}, {{MailId=200, Status=""}, {MailId=300, Status=""} ... {MailId=N , Status= N}}
List <Foo> feedBackStatusFromCsvFiles = {{MailId=100, Status= "OPENED"}, {{MailId=200, Status="CLICKED"}, {MailId=300, Status="HARDBOUNCED"} ... {MailId=N , Status= N}}
Little Insights:
objectFromDB retrieves row of my database by calling a Hibernate method.
feedBackStatusFromCsvFiles calls a CSVparser method and unmarshall to Java objects.
My entity class Foo has all setters and getters. So I know that the basic idea is to use a foreach like this:
for (Foo fooDB : objectsFromDB) {
for(Foo fooStatus: feedBackStatusFromCsvFiles){
if(fooDB.getMailId().equals(fooStatus.getMailId())){
fooDB.setStatus(fooStatus.getStatus());
}
}
}
As far as my modest knowledge of junior developer is, I think it is a very bad practice doing it like this? Should I implement a Comparator and use it for iterating on my list of objects? Should I also check for null cases?
Thanks to all of you for your answers!
Assuming Java 8 and considering the fact that feedbackStatus may contain more than one element with the same ID.
Transform the list into a Map using ID as key and having a list of elements.
Iterate the list and use the Map to find all messages.
The code would be:
final Map<String, List<Foo>> listMap =
objectsFromDB.stream().collect(
Collectors.groupingBy(item -> item.getMailId())
);
for (final Foo feedBackStatus : feedBackStatusFromCsvFiles) {
listMap.getOrDefault(feedBackStatus.getMailId(), Colleactions.emptyList()).forEach(item -> item.setStatus(feedBackStatus.getStatus()));
}
Use maps from collections to avoid the nested loops.
List<Foo> aList = new ArrayList<>();
List<Foo> bList = new ArrayList<>();
for(int i = 0;i<5;i++){
Foo foo = new Foo();
foo.setId((long) i);
foo.setValue("FooA"+String.valueOf(i));
aList.add(foo);
foo = new Foo();
foo.setId((long) i);
foo.setValue("FooB"+String.valueOf(i));
bList.add(foo);
}
final Map<Long,Foo> bMap = bList.stream().collect(Collectors.toMap(Foo::getId, Function.identity()));
aList.stream().forEach(it->{
Foo bFoo = bMap.get(it.getId());
if( bFoo != null){
it.setValue(bFoo.getValue());
}
});
The only other solution would be to have the DTO layer return a map of the MailId->Foo object, as you could then use the CVS list to stream, and simply look up the DB Foo object. Otherwise, the expense of sorting or iterating over both of the lists is not worth the trade-offs in performance time. The previous statement holds true until it definitively causes a memory constraint on the platform, until then let the garbage collector do its job, and you do yours as easy as possible.
Given that your lists may contain tens of thousands of elements, you should be concerned that you simple nested-loop approach will be too slow. It will certainly perform a lot more comparisons than it needs to do.
If memory is comparatively abundant, then the fastest suitable approach would probably be to form a Map from mailId to (list of) corresponding Foo from one of your lists, somewhat as #MichaelH suggested, and to use that to match mailIds. If mailId values are not certain to be unique in one or both lists, however, then you'll need something a bit different than Michael's specific approach. Even if mailIds are sure to be unique within both lists, it will be a bit more efficient to form only one map.
For the most general case, you might do something like this:
// The initial capacity is set (more than) large enough to avoid any rehashing
Map<Long, List<Foo>> dbMap = new HashMap<>(3 * objectFromDb.size() / 2);
// Populate the map
// This could be done more effciently if the objects were ordered by mailId,
// which perhaps the DB could be enlisted to ensure.
for (Foo foo : objectsFromDb) {
Long mailId = foo.getMailId();
List<Foo> foos = dbMap.get(mailId);
if (foos == null) {
foos = new ArrayList<>();
dbMap.put(mailId, foos);
}
foos.add(foo);
}
// Use the map
for (Foo fooStatus: feedBackStatusFromCsvFiles) {
List<Foo> dbFoos = dbMap.get(fooStatus.getMailId());
if (dbFoos != null) {
String status = fooStatus.getStatus();
// Iterate over only the Foos that we already know have matching Ids
for (Foo fooDB : dbFoos) {
fooDB.setStatus(status);
}
}
}
On the other hand, if you are space-constrained, so that creating the map is not viable, yet it is acceptable to reorder your two lists, then you should still get a performance improvement by sorting both lists first. Presumably you would use Collections.sort() with an appropriate Comparator for this purpose. Then you would obtain an Iterator over each list, and use them to iterate cooperatively over the two lists. I present no code, but it would be reminiscent of the merge step of a merge sort (but the two lists are not actually merged; you only copy status information from one to the other). But this makes sense only if the mailIds from feedBackStatusFromCsvFiles are all distinct, for otherwise the expected result of the whole task is not well determined.
your problem is merging Foo's last status into Database objects.so you can do it in two steps that will make it more clearly & readable.
filtering Foos that need to merge.
merging Foos with last status.
//because the status always the last,so you needn't use groupingBy methods to create a complex Map.
Map<String, String> lastStatus = feedBackStatusFromCsvFiles.stream()
.collect(toMap(Foo::getMailId, Foo::getStatus
, (previous, current) -> current));
//find out Foos in Database that need to merge
Predicate<Foo> fooThatNeedMerge = it -> lastStatus.containsKey(it.getMailId());
//merge Foo's last status from cvs.
Consumer<Foo> mergingFoo = it -> it.setStatus(lastStatus.get(it.getMailId()));
objectsFromDB.stream().filter(fooThatNeedMerge).forEach(mergingFoo);
Sorry about the non-descriptive title, I couldn't think of a way to explain it better short of 100 words. What I would like to be able to do is sort a list of strings into "boxes" based on a string associated with the main string and an array of strings "order".
For my current setup I am using a HashMap to store the string and it's associated "place-in-the-order" string.
I am aware that my explanation is truly crap so I have made an image which I hope will explain it better:
The variables are initialised as follows:
private final String[] order = new String[] {"Severe", "Warning", "Info"};
private final Box[] boxes = new Box[] {new Box(1), new Box(2), new Box(3), new Box(4)};
private final Map<String, String> texts = new HashMap<String, String>();
texts.put("Server on fire!", "Severe");
texts.put("All is good!", "Info");
texts.put("Monkeys detected!", "Warning");
texts.put("Nuke activated!", "Severe");
This shouldn't be too hard to implement but the only way I can think of doing it is by using 3 loops which seems a bit wasteful and would be slow if there was large numbers of any of the inputs.
Here is some example code which will hopefully show what I have come up with so far and perhaps explain the problem, I have not tested it and don't have an IDE handy so have probably overlooked something.
Set<Box> usedBoxes = new HashSet<Box>();
for(String curOrder : order) {
for (String text : texts) {
if (texts.get(text).equals(order)) {
for (Box box : boxes) {
if (!usedBoxes.contains(box)) {
box.setText(text);
usedBoxes.add(box);
break;
}
}
}
}
}
I'm not sure I fully understand what you want to achieve, but I feel that there are two things that would make your design much simpler:
Don't use Strings for your severity levels. Use enums instead. Enums have a name, may have other fields and methods, and are naturally ordered using their order of definition. And there is no way to make a typo and introduce an unknown severity: they're type-safe
enum Severity {
SEVERE, WARNING, INFO
}
Don't store things in parallel arrays or associate them with maps. Define a class containing the information of your objects:
public class Box {
private String text;
private Severity severity;
}
Now that you have these, you can simply create a List<Box>, and sort it using a Comparator<Box> which sorts them by severity, for example:
List<Box> boxes = Arrays.asList(new Box("Server is on fire", Severity.SEVERE),
new Box("All is good", Severity.INFO),
...);
Collections.sort(boxes, new Comparator<Box>() {
#Override
public int compare(Box b1, Box b2) {
return b1.getSeverity().compareTo(b2.getSeverity());
}
}
or even simpler, with Java 8:
boxes.sort(Comparator.comparing(Box::getSeverity));
You should make your "statuses" (Severe, Info etc) into an Enum.
public enum StatusLevel {
Severe,
Warning,
Info;
}
You can then sort by StatusLevel natively as long as you define the in a top to bottom order.
If you want to supply your Box object directly insead of pulling out the StatusLevel or have a secondary sort by another property like time or alphabetically you should implement your own Comparator
Also you may want to look into SortedMap or other Map that keeps its order so you don't have to resort a HashMap every time as it does not guarantee order.
I have a HashSet of Strings in the format: something_something_name="value"
Set<String> name= new HashSet<String>();
Farther down in my code I want to check if a String "name" is included in the HashSet. In this little example, if I'm checking to see if "name" is a substring of any of the values in the HashSet, I'd like it to return true.
I know that .contains() won't work since that works using .equals(). Any suggestions on the best way to handle this would be great.
With your existing data structure, the only way is to iterate over all entries checking each one in turn.
If that's not good enough, you'll need a different data structure.
You can build a map (name -> strings) as follows:
Map<String, List<String>> name_2_keys = new HashMap<>();
for (String name : names) {
String[] parts = key.split("_");
List<String> keys = name_2_keys.get(parts[2]);
if (keys == null) {
keys = new ArrayList<>();
}
keys.add(name);
name_2_keys.put(parts[2], keys);
}
Then retrieve all the strings containing the name name:
List<String> keys = name_2_keys.get(name)
You can keep another map where name is the key and something_something_name is the value.
Thus, you would be able to move from name -> something_something_name -> value. If you want a single interface, you can write a wrapper class around these two maps, exposing the functionality you want.
I posted a MapFilter class here a while ago.
You could use it like:
MapFilter<String> something = new MapFilter<String>(yourMap, "something_");
MapFilter<String> something_something = new MapFilter<String>(something, "something_");
You will need to make your container into a Map first.
This would only be worthwhile doing if you look for the substrings many times.
I am trying to access a String array which i have created in my Java class.
The string array is stored in a Map with the name 'notSelected' using the same key.
I also have a single String object called 'testString' stored in the same Map which i can easily access and display using:
$testString
However how do i go about accessing the String array object (notSelected) from the Map inside the velocity template object?
I have tried:
$notSelected.get(0)
$notSelected[0]
$notSelected.[0]
${notSelected}.get(0)
The last three seem to return the reference value of the memory location of the String array object but i still can't access the values inside the array.
Any help is gladly appreciated. Thanks
Here is the java code:
public Map<String, Object> getVelocityParameters
(final Issue issue, final CustomField field, final FieldLayoutItem fieldLayoutItem) {
final Map<String, Object> map = super.getVelocityParameters(issue, field, fieldLayoutItem);
String[] notSelected = {"foo", "bar", "baz"};
map.put("notSelected", notSelected);
String[] selected = {"foo", "bar", "baz"};
map.put("selected", selected);
//this code works and i can access $testString in the velocity template
String testString = "Test Worked";
map.put("testString", testString);
return map;
}
JIRA uses an older version of Velocity that does not support array index notation for accessing arrays. Instead, use a List and .get(n) notation:
List foo = new ArrayList() {{ add("hi"); add("there"); }};
$foo.get(0)
$foo.get(1)
And remember, little tidbits of info like the environment you're operating in can make a huge difference (and when someone asks a question, there may be a reason for asking it ;)