Java and generics. Isn't 0 a Number? - java

What is that I'm missing about this snippet of code?
public class Zero<N extends Number> {
public N zero() {
return new Integer(0);
}
}
It says:
Type mismatch: cannot convert from Integer to N
Thanks!
Update I've changed the snippet to use an integer. Same thing happens. And it happens even when creating an anonymous subclass of Number. Could it be Eclipse that is faulty about this?

While an Integer is a Number, an Integer might not be compatible to N which can be any subclass of Number.

Integer is not guaranteed to be a superclass of N, so you can't just set an Integer value to an object of type N.
Think about it this way: If someone instantiates Zero<N> as Zero<Double>, the class effectively becomes:
public class Zero {
public Double zero() {
return new Integer(0);
}
}
which is obviously not valid.
Furthermore, you can't do return 0 either, because in the same manner, there is no way for the compiler to know how to convert it into N. (The compiler can only autobox types it knows about, but by using generics you widened the available types to also include custom implementations of Number.)

The problem with your code is that Java needs to be able to confirm that the return type of the function needs to be convertible to N extends Number for any N. So, in particular, if I were to instantiate the class with a Double, as in
Zero<Double> z = new Zero<Double>();
z.zero();
You'd run into trouble, because zero says that it returns a Double but it actually returns an Integer. The type error indicates that the compiler is concerned that something like this will happen.
To the best of my knowledge, there is no good way to do this in Java because generics are implemented via erasure; it can't know what the type of the argument is.

0 is an int, but since your method returns an object, it will be autoboxed to an Integer. The problem is that returning an Integer where any subclass of Number is allowed is not allowed by the compiler. That's simply because you can instantiate your class as
new Zero<Double>()
and in this case, returning Integer would not be compatible with the expected return type: Double.

When 'N' extends 'Number', 'N' becomes a specialization of 'Number' and you cannot assign an instance of a base class to a reference variable of it's specialization (upcast issue). This holds good while returning as well. A base class instance cannot be returned using the specialization type.

Related

Why does this Number class assignment work (java)?

I looked for a duplicate of this but don't see a replica similar enough to satisfy.
You can't instantiate abstract classes in Java, and Number is abstract, so why does this line compile:
Number num = 3;
If it was Integer num, it would get autoboxed, but does autoboxing somehow work for Number too, even though it's abstract? Or is something else happening?
Integer is a subclass of Number, so 3 gets autoboxed from int to Integer, then the Integer gets stored in the Number variable.
It’s not that auto-boxing works for Number. You are fully correct, the Number class is abstract and cannot be instantiated. Also no general mechanism for auto-boxing a primitive number into a Number object exists in Java.
It’s that auto-boxing works from int to Integer. The literal 3 is an int (with no exception). And Integer is a concrete subclass of Number, so putting a reference to an Integer into a variable declared as Number is trouble-free.
It may be a bit surprising that it works, I agree with you. The basic rule of auto-boxing is you can put an int where an Integer is expected, a double where a Double is expected, and so forth. We can hardly say that an Integer was necessarily expected on the right-hand side of your initialization. It seems they have extended the rule to be applicable here anyway. And it’s no doubt in the JLS somewhere (JLS: Java Language Specification).
Just for checking we may do:
Number num = 3;
System.out.println(num.getClass());
Output:
class java.lang.Integer
You may extend the rule one step further:
Object obj = 3;
It still gives you an Integer (not just an Object even though Object is a concrete class).
Link: Similar question: Does Java autobox when assigning an int to an Object? (you will also find the references to JLS there)

Insert Dimensions to complete Expression/ReferenceType

I'm a newbie to Java.
I have provided a short snippet from my code for BFS.
public int bfs(Person p, Person q) {
private HashMap<Person, boolean> marked;
private int count;
marked = new marked<Person, boolean>();
count = new int;
}
According to Eclipse, I have an error on each of the last 4 lines.
Syntax Error: insert "Dimensions" to complete expression/referencetype.
I would appreciate any input/advice!
Cause of this error -You are trying to pass a primitive object into a generic type declaration whereas generic types always expect a Wrapper Class object. So please use 'Boolean' instead of 'boolean' in your code i.e. 'B' in caps.
You need to use the wrapper object not the primitive. Use Boolean instead of boolean.
Satyendra Sharma's answer is absolutely correct, but here's some reasoning of what exactly the error message is saying.
The error is caused by using a primitive type, which cannot be used as a generic type argument. For instance, List<boolean> is incorrect, whereas List<Boolean> is correct. Wrapper classes can be used to wrap the primitive values and yield a reference type, which can be used with generics.
Insert dimensions? What?
The message "Insert dimensions to complete expression/referenceType" is probably because in order for the expression to become valid, the only valid token here is a set of square brackets.
For instance,
HashMap<Person, boolean[]> marked;
will just compile fine. This is because, unlike a boolean, a boolean[] is an object.
Generic are resolved during compile time and during runtime their no context about the generic used in your code. The Object is than type cast into the class type provided against the generic type. Now both primitive and object are completely unrelated entities in java. Direct time-cast of Object to primitive type isn't possible in java. For this reason the use of primitive type in generic is disallowed and eclipse gives this warning.
First I would suggest you start reading a Java tutorial...
https://docs.oracle.com/javase/tutorial/java/TOC.html
For your issues specifically:
https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html
https://docs.oracle.com/javase/tutorial/java/javaOO/objectcreation.html
https://docs.oracle.com/javase/tutorial/java/javaOO/variables.html
As for your code, you can initialize your variables right when you declare them:
Map<Person, Boolean> marked = new HashMap<Person, Boolean>();
int count = 0; // or whatever initial value
It seems that this snippet is throwing around random keywords without any understanding - I would suggest a Java tutorial. First of all, generics are one of the main uses for boxing. boolean or any other primitives (you can recognise these by the fact that their identifiers are in lower-case and most IDEs will highlight them) cannot be used as a generic type, and their capitalised equivalent must be used (a simple wrapper class). Here, use HashMap<Person, Boolean>.
I'm not sure what is meant by marked = new marked... - clearly, marked is not a type and cannot be used in this context. new x(params) initialises an object of type x, passing its constructor params. new x<generics>(params) is the same but the generic type(s) of x are generics.
Finally, new int is not at all valid - see my explanation above. Primitives are not objects, which means initialising them is meaningless and therefore invalid. Also, what do you expect this expression to yield? Something of type int, but you are not specifying which int. The correct syntax is a literal: count = x; where x is some integer within the range of int.
As a side note, your method has an unclear name and variables may be initialised in the same line you declare them to simplify code.
Visit Cannot Instantiate Generic Types with Primitive Types
Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
The type parameter, V, actually also K, which is declared in HashMap<K,V>, will be replaced with Object after erasing, because they are unbounded. While primitive type can not be store as Object.

Performing operation using Generic and primitive type not working

Trying to learn some basic operations using JAVA-Genric, i tried to make a generic class, which takes a number and does some operation on it and returns the value but its throwing error
Parent class :
public class HelloWorld{
public static void main(String []args){
MathsExec<Integer> my_obj = new MathsExec<Integer>();
int rst = my_obj.doAddition(100);
System.out.println("Solution is : "+rst);
}
}
Generic Class :
class MathsExec<T>{ //didn't extend NUMBER because m strictly sending int values
T doAddition(T nmbr){
int k=100;
T rst;
rst = nmbr+k;
return rst;
}
}
Error :
MathsExec.java:6: error: bad operand types for binary operator '*'
rst = nmbr+k;
^
first type: T
second type: int
where T is a type-variable:
T extends Object declared in class MathsExec 1 error
I understand why this error is coming(incompatible types for operation) but as per generics, type T should have been converted to Integer before doing the + operation...or is there some other explanation i should know????
P.S : please go easy, JAVA is not my strong suite!!
Generic types are not known in compile time. Therefore, you get that compilation error. And it makes sense, because there is no guarantee that your * operator will work on your generic type T.
A trivial but educational solution might be adding an interface called Multipliable and adding the abstract method multiply() to it, than calling that method in your doAddition method. BTW to do that you need to change your class definition as something like
class MathsExec<T extends Multipliable>
Additional clarifications:
We have to come to an understanding that you cannot use operators like * for classes, they are for primitive types only.
If you have to use generics and you have to do some operations on generic types, you have to keep your compiler happy and assure it that that Generic object, does have that method. And that is through T extends SomeClass.
If you want to practice generics without using interfaces or abstract classes or whatever, the most common use case is custom data structures. Where it is possible that you do not actually need many operations on the data that you are storing. You just put them in your structure.
// didn't extend NUMBER because m strictly sending int values
You can do that. int primitives will be boxed to Integer wrapper. But the issue is, arithmetic operators won't work on generic types. There may be workaround, but it wouldn't be worth of your effort. I would rather provide overloaded method for handling different primitive types.
BTW, you should take two arguments in the doAddition() method, and pass the value of k, that you're currently hard-coding. This will allow you to re-use this method in any other class too.
You should do an explicit cast from T to int if you know for sure that T will be casted down to an int. The compiler has no way of knowing if that will actually happen, so it will give you that error.

Reflection on methods with a primitive numeric return type

I'm currently working on a small framework to collect metrics in an OSGi system.
The core of it is an annotation #Metric that indicates that a given method of a service can deliver a metric (e.g. numeric value) when asked.
Those methods would look like:
#Metric
public int getQueueSize() {...}
or
#Metric
public double getAvgTaskTime() {...}
I'm using reflection to inspect the service implementation class and register the methods that are annotated with #Metric. As a sanity check, I'm checking that the method indeed delivers a numeric value. I tried this and failed:
for (Method method: metricSource.getClass().getMethods()) {
if (method.isAnnotationPresent(Metric.class) && Number.class.isAssignableFrom(method.getReturnType()) {
// add method for later process
}
Then, later on the processor, I'd do:
Number value = (Number) method.invoke(target, obj[]);
It turns out that for a primitive type you would get e.g. int.class and that's not assignable to Number.class whereas the boxed type Integer.class would be. Damn. Move on.
Then I created a Map<Class<?>, Extractor> where Extractor takes a class and casts a parameter into that class:
public NumberExtractor(Class<? extends Number> clazz) {
this.clazz = clazz;
}
public double extract(Object val) {
Number nbr = clazz.cast(val);
return nbr.doubleValue();
}
}
In face of the previous observation, I added an entry like this:
extractorMap.put(int.class, new NumberExtractor(int.class));
But that didn't work either. It gave a runtime class cast exception saying that Integer.class could not be cast to int. Note also that the compiler does not complain on new NumberExtractor(int.class), letting the boundary check Class<? extends Number> pass on int.class
At the end, this combination worked:
extractorMap.put(int.class, new NumberExtractor(Integer.class));
What is going on here? Reflection says that the return type of the object (int.class) is not assignable to Number.class, but when I go on with the method invoke, I actually get a Integer.class? WhiskeyTangoFoxtrot?!
I'm left wondering whether there is another way to address this issue other than maintaining the Map of int -> Integer, Integer -> Integer, float -> Float, Float -> Float? (which is now done, so this is for the sake of learning)
The behaviour of the boxing and type information doesn't seem coherent here.
Could anybody shine some light on this?
Reflection says that the return type of the object (int.class) is not assignable to Number.class, but when I go on with the method invoke, I actually get a Integer.class?
Absolutely. The reflection API can't possibly return you an actual int, so it boxes the value in an Integer. What else could it do?
The fact that it has to box does not change the assignability from int to Number. Look at the docs for isAssignableFrom:
If this Class object represents a primitive type, this method returns true if the specified Class parameter is exactly this Class object; otherwise it returns false.
So it's behaving exactly according to the documentation.
I'm left wondering whether there is another way to address this issue other than maintaining the Map of int -> Integer, Integer -> Integer, float -> Float, Float -> Float? (which is now done, so this is for the sake of learning)
Yup, that sounds right to me. Or just have a Set<Class> for primitive numeric types rather than explicitly mapping.

If class Number is abstract why I'm I allowed to write Number n = 5?

Number n = new Number(5) is illegal, but Number n = 5 isn't. Why?
Because of autoboxing. 5 is not an object so it is wrapped into an object (Integer in this case), and Integer is a Number.
Fundamentally, it's because Number is an abstract class - there is no constructor that corresponds to Number(5), and even if there was you still would not be able to instantiate the class directly because it's abstract.
As Bombe explains, in your second case you're really creating an Integer object* - which, as a subclass of Number, can be assigned to such a variable. And as it's a concrete class you can instantiate it.
*Although in practice it's actually more equivalent to Integer.valueOf(5), which on Sun JREs won't create an additional Integer object but will use a pooled version (like the Flyweight pattern).
It's similar to how the following would work:
List bob = new ArrayList();
List is an interface, so you can't instantiate it directly. However, you can declare a variable of type List and then assign to it a concrete object that implements that interface. Along the same lines, you can declare a variable of type Number and then assign to it any value object that is a concrete instance of that type. What you have done with the functional code is, for all intents and purposes (due to autoboxing):
Number n = new Integer(5);
It shouldn't be. autoboxing is a big mistake.

Categories