Java - Generics with lists - java

I wrote a function for my cache to retrieve a specific object. This way I don't need to cast it .
#SuppressWarnings("unchecked")
public static <T> T inCache(Class<T> obj, String token) {
Object cacheObj = Cache.get(token);
if (cacheObj != null) {
if (obj.isAssignableFrom(cacheObj.getClass())) {
return (T) cacheObj;
}
}
return null;
}
I am using it like this
String s = inCache(String.class, title);
But now I have a list of Strings in my cache and I can't use it like this
List<String> ipList = Util.inCache(List<String>.class, title);
The problem is the List<String>.class . I am very new to java, how do I have to write it?

There is a concept in java called type erasure. Due to legacy reasons, something like List is just a list. It doesn't remember that it is a list of string at run time. You should just write List.class.
You can then specify the type of object in the List when iterating through it.

You can't get class of List<String>, in your case the only way is:
List<String> ipList = (List<String>)Util.inCache(List.class, title);

You can try :
List<String> ipList = Util.inCache(List.class, title);

Try this-
List<String> inList = (List<String>)Test.inCache(List.class, title);
And you can do also -
List<String> inList = Test.inCache((Class<? extends List<String>>)List.class, token);

Just to clarify Joe's answer ( I don't have enough reputation to comment), at runtime there is no difference between a List <String> and List<Integer> or any other type of List, generics aren't kept at runtime.
Meaning, List<String>.class is completely identical to List<Integer>.class and is actually List.class. This is a weakness of the Java type system. I'm not familiar with a simple way to implement what you wish for.
A code proof for the heck of it :
// It is true that
List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();
System.out.println( stringList.getClass() == integerList.getClass() );
// And that ...
List objectList = new ArrayList();
System.out.println( stringList.getClass() == objectList.getClass() );
//However, the following is false because a different implementation is used ( I wanted a false case)
List objectLinkedList = new LinkedList();
System.out.println( objectLinkedList.getClass() == objectList.getClass() );

Related

Lazy reinit a list to modifiable after declaring with Collections.emptyList()

Want to declare a list as List<String> info = Collections.emptyList()
but when user calls add(String msg) then re-init to a modifiable list.
Is this the correct way:
private List<String> info = Collections.emptyList();
public void addInfo(String s){
final List<String> e = Collections.emptyList();
if(info == e){
info = new ArrayList<>();
}
info.add(s);
}
Or
if(info.equals(e)){
If I have 3 of these can I have this common code :
public void addInfo(String s) {
info = addTo(s, info);
}
public void addWarn(String s) {
warn = addTo(s, warn);
}
public void addErr(String s) {
errs = addTo(s, errs);
}
private List<String> addTo(String s, #org.jetbrains.annotations.NotNull List<String> t){
final List<String> e = Collections.emptyList();
if(t.equals(e)){
t = new ArrayList<>();
}
t.add(s);
return t;
}
I guess the following wont work due to the new list being created?
private void addTo(String s, #org.jetbrains.annotations.NotNull List<String> t){
final List<String> e = Collections.emptyList();
if(t.equals(e)){
t = new ArrayList<>();
}
t.add(s);
}
Note that even if Collections.emptyList() always returns the one instance held in Collections.EMPTY_LIST, a reference comparison does not detect when a caller used JDK 9+ List.of() to initialize the field. On the other hand, being non-empty does not guaranty mutability either.
The entire logic is suitable only for a private method were all callers and their usage are known.
But you should consider the alternative of dropping these special cases altogether. Since Java 8, the default constructor new ArrayList<>() will not create a backing array. It is deferred until the first addition of an element.
So you can initialize all fields with a plain new ArrayList<>() and implement the addInfo, addWarn, and addErr with a plain add call, getting rid of the addTo method, the conditionals, and the repeated assignments. Even declaring the fields final is possible. While still not requiring a significant amount of memory for the unused lists.
Using .equals is the only correct solution -- but equivalent to the much simpler info.isEmpty().
Considering your code:
final List<String> e = Collections.emptyList();
if(info == e){
info = new ArrayList<>();
}
info.add(s);
I don't believe there's any guarantee in the Java API that the same reference will always be returned from emptyList() (the javadoc states "Implementations of this method need not create a separate List object for each call").
Given you may modify the list it'd make more sense to initialise with new ArrayList<>() rather than emptyList(). Really doesn't make much sense to use an unmodifiable list that you may want to modify.
However if you really need to use emptyList() for some reason, then perhaps:
if (info.isEmpty())
info = new ArrayList<>();
Given you are about to add an item to it this test will only pass once anyway.

can't join String[] because it's stored in an object

I'm very new to Java and I'm running into difficulty with something simple. I create a vector like the below, where the third element is an array of Strings, not a String itself.
this.myvec = new Vector();
myvec.add("a");
myvec.add("b");
myvec.add(new String[]{
"c",
"d",
"e",
"f");
Later I want to iterate over this, but because some elements are Strings and some are String arrays, I have to do it like this. But I also want to log it, so I have a condition based on the type to send to different log statements.
However in the case of the array, I want to join it as you can see below.
for (Object myobj : myvec) {
if (myobj.getClass().equals(String.class)) {
log.info("My object is " + myobj);
}
else {
log.info("My object is " + String.join(",", myobj));
}
The second log statement doesn't compile because you can't have an Object be the second argument to String.join. How can I get this to work?
If you really want to have mixed types in your Vector you can do it this way, using the instanceof keyword:
Vector<Object> myvec = new Vector<>();
myvec.add("a");
myvec.add("b");
myvec.add(new String[] {"c", "d", "e", "f"});
myvec.add(1); // Added to test the Unknow type object
for (Object myobj : myvec) {
String myobjAsString = "Unknown"; // default
if (myobj instanceof String) {
myobjAsString = (String)myobj;
} else if (myobj instanceof String[]) {
String[] myarray = (String[])myobj;
myobjAsString = String.join(",", myarray);
}
System.out.println("My object is " + myobjAsString);
}
Prints out:
My object is a
My object is b
My object is c,d,e,f
My object is Unknown
Side notes:
Declare your Vector as a Vector of Objects: Vector<Object>. It is a Generic class. See Vector Api and Generic Types
Vector is only preferred in very specific cases (so specific that I can't think of one), I don't think you should be bothered with that at your level, you might prefer to use ArrayLists<T> in most if not all cases. See Vector Vs ArrayList
To use an ArrayList instead of a Vector, simply declare:
List<Object> myvec = new ArrayList<>();
The rest of the code is the same.
Try this
log.info("My object is " + String.join(",", (String[])myobj));

TreeMap using a string key and an arraylist

I am brand new to using collections, so I am confused on how to do this. I am trying to use a TreeMap to hold a word as the key and then an ArrayList to hold one or more definitions for the word.
public class Dict {
Map<String, ArrayList<String>> dic = new TreeMap<String, ArrayList<String>>();
public void AddCmd(String word, String def) {
System.out.println("Add Cmd " + word);
if(dic.get(word)==null){
dic.put(word, new ArrayList.add(def));
}
}
}
I am getting an error on "new ArrayList.add(def)". I thought this was the correct way to do this, but I am obviously wrong. Does anyone have any ideas as to what I am doing wrong?
Calling ArrayList#add returns a boolean which is not the desired value for your Map, thus getting the compiler error.
You need to insert the ArrayList and then add the element. Your code should look like this:
ArrayList<String> definitions = dic.get(word);
if (definitions == null) {
definitions = new ArrayList<String>();
dic.put(word, definitions);
}
definitions.add(def);
dic.put(word, new ArrayList.add(def)); is the culprit.
since you have declared map to take Arraylist of string as a value. the value to pass for map must be Arraylist of string.
but this line is adding a value as new ArrayList.add(def) since you are trying to create a list and adding element , add method returns boolean -> true if it can add false if it fails.
so it means value to the map is going as a boolean not as arraylist which is against the map declaration.
so use code as below
ArrayList<String> listOfString = dic.get(word);
if (listOfString == null) {
listOfString = new ArrayList<String>();
listOfString .add(def);
}
dic.put(word, listOfString );
You have to break it up, because add does not return the original ArrayList:
ArrayList<String>> NewList = new ArrayList<String>();
NewList.add(def);
dic.put(word, NewList);
You are not actually creating a new ArrayList. Try this:
ArrayList<String> newDef = new ArrayList<String();
newDef.add(def);
dic.put(word, newDef);

How to print content in Collection in Java

Good day All,
Normally, I will print all the content in List by look the list.size() and assign it to an object and print the object value. The following is my example code:
List ccUserList = new ArrayList(); // Here I declare a List
Collection ccGroupCol = new ArrayList(); // Here I declare a collection
CCuserBO bo = null;
ccUserList = getSummaryList();
for(int i = 0, i < ccUserList.size() , i++){
bo = ( CCUserBO ) ccUserList.get(i);
System.out.println(bo.userName);
}
I would like to ask about the way to print content in Collection.
Since Collection no have .get() function.
The following in the code that I try in Collection:
CCuserBO newBo = null;
ccGroupCol = getSummaryList();
Iterator iterator = ccGroupCol.iterator();
while ( iterator.hasNext()){
newBo = iterator.next(); //error here, Type mismatch: cannot convert from Object to //Object[]
System.out.println("....");
}
If you simply want to print all elements of a Collection just sysout Collection directly it will provide you the following form in output: [element1, element2, ....] because toString() method is overrided and implemented to provide such output for all Collection classses.
By using Iterator you can get the element one by one:
Iterator iterator = ccGroupCol.iterator();
while ( iterator.hasNext()){
newBo = (**type cast here to particular newBo object type**)iterator.next();
System.out.println(newBo);//here whatever you implemented in toString() method
// in newBo type class(if you did so), you will get that type of output, if you do not override
//toString() to provide your implementation,you will get default implementation in
//which it will show <the object class>#<its hash code>
}
Note: the return type of iterator.next() is Object type, so you must type cast it to avoid incompatible type exception. Or use Generics.
I found the solution. Here is the example code:
CCGroupBO newBo;
for(int i = 0 ; i < ccGroupCol.size() ; i++){
newBo = ( CCGroupBO ) ccGroupCol.toArray()[i];
System.out.println(newBo.getGroupName());
}
Thanks for all your help.
You can use the for loop to iterate the collection.
Collection collection= new ArrayList();
for (Object obj : collection) {
//Here you can type cast the obj then you can print.
}
As statet in comment, a faster solution for your own answer:
Collection<CCGroupBO> ccGroupCol = new ArrayList<CCGroupBO>()
…
CCGroupBO[] boArray = ccGroupCol.toArray();
for(CCGroupBO newBo : boArray){
System.out.println(newBo.getGroupName());
}
or even more direct:
Collection<CCGroupBO> ccGroupCol = new ArrayList<CCGroupBO>()
…
for(CCGroupBO newBo : ccGroupCol){
System.out.println(newBo.getGroupName());
}
depending on other circumstances there is even a nicer method:
class CCGroupBO {
…
public String toString() {
return getGroupName();
}
}
…
Collection<CCGroupBO> ccGroupCol = new ArrayList<CCGroupBO>()
…
System.out.println(ccGroupCol);

Is it possible to avoid loop which will cast one object type to another in split() or Arrays.asList()?

I'm trying to simplify my code and I have a question: is it's possible to convert string of IDs separated by coma to specific collection type?
So, my code now is:
String [] condition_list_id_tmp = rule.getContractRuleConditions().split(",");
List<String> condition_list_id = Arrays.asList(condition_list_id_tmp);
List<Long> condition_ids = new ArrayList<Long>();
for (String str_id : condition_list_id){
condition_ids.add(Long.parseLong(str_id));
}
Can I simplify this code by using for example Type collectionType = new TypeToken<List<Long>>() {}.getType(); like in gson?
Have you considered LambdaJ?
class StringToLong implements Converter<String, Long> {
public Long convert(String str) {
return Long.parseLong(str);
}
}
String [] condition_list_id_tmp = rule.getContractRuleConditions().split(",");
List<String> condition_list_id = Arrays.asList(condition_list_id_tmp);
List<Long> condition_ids = convert(condition_list_id, new StringToLong());
or using some libraries, like guava? so that there is no looping in your codes (but in theirs) ?
I saw that your mentioned your goal is "to simplify my code"
e.g.
final List<String> strList = Arrays.asList("1,2,3,4,5".split(","));
final List<Long> l = Lists.transform(strList, new Function<String, Long>() {
#Override
public Long apply(final String input) {
return Long.parseLong(input);
}
});
I don't think so with out looping you can do this. How come your collection's content type be changed with out casting explicitly .
There is no method available without looping. Even if a method is available it will look like to you as a single operation but obviously it has to
loop internally.
For eg: Arrays.fill(arrayname, intval);
This is a single method to fill the array with any integer value. But internally it will also run a loop on the array.
java 8 can do this :
String [] condition_list_id_tmp = rule.getContractRuleConditions().split(",");
List<String> condition_list_id = Arrays.asList(condition_list_id_tmp);
List<Long> condition_ids = condition_list_id.map(c -> Long.parseLong(c))
The solution for your problem until Java 8 pop to the market could be project Guava, with their support for Functional Idioms.
Then you could perform that operation in different way, but as i wrote in the comment. At the end you will have same operation.
public static List<Long> splitToLong(String list, String token) {
StringTokenizer tokenizer= new StringTokenizer(list, token);
List<Long> result = new ArrayList<Long>();
while(tokenizer.hasMoreTokens() {
result.add(Long.parseLong(tokenizer.nextToken()));
}
}
If you put this method in some Util class, then you can enjoy clean code
//....
for(Long mLong : StringHelper.splitToLong(message,",")) {
//Do something with mLong
}
//....

Categories