Is there an easy way to decide the resulting Java Class of a Java mathematical expression? For example, I can calculate the resulting class of something like String + double but is there something out there give the resulting class of any valid expression of mixed classes at runtime?
Background: I need this as I am using Java reflection to call methods with arguments at runtime. The arguments have been parsed from text into Expression objects where Expression is my class for handling expressions. An Expression can be made up of other Expressions. So for instance I may be parsing code like:
double c = 2.2;
double d = 3.3;
long e = 4;
int f = 1000;
double a = 0;
double b = 0;
MyObject object = new MyObject();
object.getSomething(a, b, (c+d+e)/f);
I know the Classes of a and b because they are defined variables but the Class of third argument needs to be calculated before I can do something like:
Method method = MyObject.class.getMethod("getSomething", double.class, double.class, ????);
In first place, you can't know the classes of a and b because a and b are not objects. They are local variables of the primitive type double. In java you have two basic kind of types: primitive types (boolean, byte, char, short, int, long, float, double, and maybe void) and class types. However, there is also a mechanism called autoboxing (and autounboxing) that automatically converts when necessary between primitive types and their corresponding "primitive wrapper classes" (Boolean, Byte, Character, Short, Integer, Long, Float, and Double). You can use this automatic conversion to determine the type of an expression. Example:
byte a= 10 ;
float b= 16.3 ;
int c= 34 ;
Object o= a * b + c ;
o.getClass(); // Should be Double or one of the primitive wrapper classes in the general case
// You can easily convert this to double.class through an if/elseif sequence
// or a Map.
A note of attention: When one mixes variables (and/or also parameters and/or return values) of primitive types and wrappers classes, it's very easy to lose track of types. In particular, the Java reflection API works mostly if not exclusively with wrapper classes. This means, for example, that method invoke supports parameters of class types only and thanks to autoboxing Java will convert any primitive type into a wrapper object, even if the target method really expects the primitive (in which case there will be an internal conversion back to the primitive type immediately before the reflective call). As a good experiment, try turning autoboxing/unboxing off if your compiler or IDE supports it. If not this, configure them to produce warnings on autoboxing/unboxing.
Now, if your numeric values are represented in a polymorphic way (as String, Object, or similar), then you will need to define your own rules for the resulting type of the operations (or model them after the Java language itself) and implement them accordingly. This is because Java autoboxing/unboxing is a static mechanism.
I agree with Mario Rossi's answer.
In addition, even if you were using reference expressions, you cannot in general deduce the types of the formal arguments from the types of the actual arguments on a sample call. Consider this program:
import java.lang.reflect.Method;
class Test {
public static void main(String[] args) throws NoSuchMethodException, SecurityException {
double a = 3.0;
new Test().myMethod(a);
Method method = Test.class.getMethod("myMethod", Double.class);
System.out.println(method);
}
// public void myMethod(Double d) {
// System.out.println("Double version");
// }
public void myMethod(Number d){
System.out.println("Number version");
}
}
As presented, it fails with a NoSuchMethodException, because I'm looking for a Double argument and the only method expects a Number. If I uncomment the first myMethod declaration, the getMethod call succeeds.
This answer is based on the assumption that the objective is close to full Java code interpretation.
The information about expression types is provided, directly or by reference, in the Java Language Specification, Chapter 15. Expressions. The actual number of rules that need to be implemented is smaller than it might appear, because many groups of expression forms share the same conversion rules. For example, as already mentioned in a comment on the question, binary operators with numeric operands, including operands that can be unboxed to numeric, generally use binary numeric promotion.
I have looked, unsuccessfully, for short cuts for finding the right Method object for a given call. You may need to actually go through the steps described in 15.12. Method Invocation Expressions. If so, you can use the getMethods methods instead of getMethod and apply the rules to the array of Method references.
Related
This question already has answers here:
Generic type extending Number, calculations
(3 answers)
Closed 5 years ago.
The following code snippet throw me the error as shown in the header, I didn't figure out why it does not work as T is of type Number, I expected operator '+' to be fine.
class MathOperationV1<T extends Number> {
public T add(T a, T b) {
return a + b; // error: Operator '+' cannot be applied to 'T', 'T'
}
}
Would be appreciate if anyone can provide some clues, thx !
There is a fundamental problem with the implementation of this idea of generic arithmetic. The problem is not in your reasoning of how, mathematically speaking, this ought to work, but in the implications of how it should be compiled to bytecodes by the Java compiler.
In your example you have this:
class MathOperationV1<T extends Number> {
public T add(T a, T b) {
return a + b; // error: Operator '+' cannot be applied to 'T', 'T'
}
}
Leaving boxing and unboxing aside, the problem is that the compiler does not know how it should compile your + operator. Which of the multiple overloaded versions of + should the compiler use? The JVM has different arithmetic operators (i.e. opcodes) for different primitive types; hence the sum operator for integers is an entirely different opcode than the one for doubles (see, for example, iadd vs dadd) If you think about it, that totally makes sense, because, after all, integer arithmetic and floating-point arithmetic are totally different. Also different types have different sizes, etc (see, for example ladd). Also think about BigInteger and BigDecimal which extend Number as well, but those do not have support for autoboxing and therefore there is no opcode to deal with them directly. There are probably dozens of other Number implementations like those in other libraries out there. How could the compiler know how to deal with them?.
So, when the compiler infers that T is a Number, that is not sufficient to determine which opcodes are valid for the operation (i.e. boxing, unboxing and arithmetic).
Later you suggest to change the code a bit to:
class MathOperationV1<T extends Integer> {
public T add(T a, T b) {
return a + b;
}
}
And now the + operator can be implemented with an integer sum opcode, but the result of the sum would be an Integer, not a T, which would still make this code invalid, since from the compiler standpoint T can be something else other than Integer.
I believe there is no way to make your code generic enough that you can forget about these underlying implementation details.
--Edit--
To answer your question in the comment section consider the following scenario based on the last definition of MathOperationV1<T extends Integer> above.
You're correct when you say that the compiler will do type erasure on the class definition, and it will be compiled as if it was
class MathOperationV1 {
public Integer add(Integer a, Integer b) {
return a + b;
}
}
Given this type erasure it would seem as if using a subclass of Integer ought to work here, but that's not true since it would make the type system unsound. Let me try to demonstrate that.
The compiler cannot only worry for the declaration site, it also has to consider what happens in the multiple call sites, possibly using a different type argument for T.
For example, imagine (for the sake of my argument) that there is a subclass of Integer that we'll call SmallInt. And assume our code above compiled fine (this is actually you question: why it doesn't compile?).
What would happen then if we did the following?
MathOperationV1<SmallInt> op = new MathOperationV1<>();
SmallInt res = op.add(SmallInt.of(1), SmallInt.of(2));
And as you can see the result of the op.add() method is expected to be a SmallInt, not an Integer. However, the result of our a + b above, from our erased class definition, would always return an Integer not a SmallInt (because the + uses the JVM integer arithmetic opcodes), and therefore this result would be unsound, right?.
You may now wonder, but if the type erasure of MathOperationV1 always returns an Integer, how in the world in the call site it might expect something else (like SmallInt) anyways?
Well, the compiler adds some extra magic here by casting the result of add to a SmallInt, but only because it has already ensured that the operation can't return anything else other than the expected type (this is why you see a compiler error).
In other words, your call site would look like this after erasure:
MathOperationV1 op = new MathOperationV1<>(); //with Integer type erasure
SmallInt res = (SmallInt) op.add(SmallInt.of(1), SmallInt.of(2));
But that would only work if you could ensure that add returns always a SmallInt (which we cannot due to the operator problems described in my original answer).
So, as you can see, your type erasure just ensures that, as per the rules of subtyping, you can return anything that extends an Integer, but once your call site declares a type argument for T, you're supposed to always assume that same type wherever T appeared in the original code in order to keep the type system sound.
You can actually prove these points by using the Java decompiler (a tool in your JDK bin directory called javap). I could provide finer examples if you think you need them, but you would do well to try it yourself and see what's happening under the hood :-)
Auto(un)boxing only works for types that can be converted to their primitive equivalents. Addition is only defined for numeric primitive types plus String. i.e: int, long, short, char, double, float, byte . Number does not have a primitive equivalent, so it can't be unboxed, that's why you can't add them.
+ isn't defined for Number. You can see this by writing (with no generics):
Number a = 1;
Number b = 2;
System.out.println(a + b);
This simply won't compile.
You can't do addition generically directly: you need a BiFunction, a BinaryOperator, or similar, which is able to apply the operation to the inputs:
class MathOperationV1<T extends Number> {
private final BinaryOperator<T> combiner;
// Initialize combiner in constructor.
public T add(T a, T b) {
return combiner.apply(a, b);
}
}
But then again, you may as well just use the BinaryOperator<T> directly: MathOperationV1 adds nothing over and above that standard class (actually, it provides less).
I read this post: Is int an object in Java?.
In the post it is argued that int is not inherited from Object. If so is the case, then why does the code below compile without any error? Given that int is not Object and the signature of format() method is public static String format(String format, Object... args) as shown in documentation: javadoc for String!
public class Testing {
public static void main(String[] args) {
int integer = 7;
String str = String.format("%03d", integer);
System.out.println(str);
}
}
I have also read about "Autoboxing". What does this exactly mean? Are all the primitives replaced by appropriate Object's before compilation? If so, then is there any advantage of memory usage while using large array of int's (int[]) over Integer's (Integer[])? Similar arguments follow for double's etc.
Any insights are welcome.
It is caused by Autoboxing.
Here is a small snippet from the linked Java documentation that explains it better than I could:
Autoboxing is the automatic conversion that the Java compiler makes
between the primitive types and their corresponding object wrapper
classes. For example, converting an int to an Integer, a double to a
Double, and so on. If the conversion goes the other way, this is
called unboxing.
Here is the simplest example of autoboxing:
Character ch = 'a';
The rest of the examples in this section use generics. If you are not
yet familiar with the syntax of generics, see the Generics (Updated)
lesson.
Consider the following code:
List<Integer> li = new ArrayList<>();
for (int i = 1; i < 50; i += 2)
li.add(i);
Although you add the int values as primitive types, rather than
Integer objects, to li, the code compiles. Because li is a list of
Integer objects, not a list of int values, you may wonder why the Java
compiler does not issue a compile-time error. The compiler does not
generate an error because it creates an Integer object from i and adds
the object to li. Thus, the compiler converts the previous code to the
following at runtime:
List<Integer> li = new ArrayList<>();
for (int i = 1; i < 50; i += 2)
li.add(Integer.valueOf(i));
When calling String.format("%d",myInt), myInt is automatically (and implicitly) wrapped in an Integer instance, which extends Object, therefore it compiles.
Concerning the arrays, the conversion from primitiveType[] to WrapperClass[] is not automatic for some reason. If you try to use an array of a primitive type where an array of the wrapper class is expected, it will result in a compile error.
Using Integer creates an overhead compared to using int because you need to assign and store references. However, this overhead is limited when using Integer values between -128 and 127 because these values are pooled (which means that all instances of Integer wrapping a value in this in interval point to a unic reference).
Autoboxing is a help from the compiler, which automatically compiles something like
foo(i);
into
foo(Integer.valueOf(i));
when foo() takes an argument of type Object and you pass it a primitive type (int, in this case). It just makes the code easier to type and read.
And that's what happens here. The String.format() method expects objects as argument. You're passing it a primitive type, so the compiler autoboxes it to an Integer for you.
int i = 10;
i++; // it ok primitive value can use ++.
Integer integer = 10;
integer++; // how it can use ++
MyClass myClass = new MyClass();
myClass++; // then why myclass can't use ++.
C++ has the ability to overload operators. The Java language considers this to be open to too much abuse (overloaded operators can be obfuscating) so it was never incorporated into Java.
Therefore, you can't write myClass++ as the syntax is not available to specify that operation.
But ++ does work on a selection of non-primitives. The mechanism exploited is called autoboxing. (Essentially the underlying plain-old-data type is extracted from the boxed type, incremented then re-boxed to the original reference type).
Somewhat related to this is the ability to apply += and + to java.lang.String instances. Simply put, this is a special case. Although fully aware of the risk of downvotes I regard this as one of the worst kludges in Java, particularly += which will create a new instance of a string (as strings themselves are immutable), and many Java programmers will be unaware of the effect this has on memory.
It is because of Java's autoboxing feature which is added in Java 1.5
The compiler will convert the statment as follow
Integer integer = 10;
integer.iniValue++;
You can try to add the compiler flag "javac -source 1.4" and it will return an error
From the Link provided in a comment by Konstantin V. Salikhov,
Integer has a defined method to return an int, which then has the ++ operator defined.
MyClass has no ++ operator, hence myClass++; is invalid
The method in question goes like:
Integer myInteger = 10;
myInteger.intValue++;
Autoboxing is the automatic conversion that the Java compiler makes between the primitive types and their corresponding object wrapper classes.
Operator overloading in Java has a description (as to it being not allowed) at Operator overloading in Java
I have been looking for a transparent way to cast an object to a primitive type, something like:
class Example {
double number;
public Example(double number) {
this.number = number;
}
// this or something similar to this
public toDouble() {
return number;
}
...
}
Example ex = new Example(18.0);
double number = ex;
After a few searches i am almost sure that it does not exists, but if there would be a big help.
Update 1
Excuse me if the questions was not very clear.
I am developing a code editor for an internal tool: the idea is write a program with a reduced set of java instructions, translate to java and compile.
I want avoid that the editor have to make a lot of times the same casts or call same calls, and the exprexions sometimes will be very complex to translate.
A better example could be:
the simplified code
Example ex = new Example(18.0);
Example ex2 = new Example(23.0);
Example mean = Mean(ex + 1.0, ex2, 3.0);
(i would prefer avoid Example mean = Mean(ex.toDouble() + 1.0, ex2.toDouble(), 3.0);)
and the function in java
double Mean(double... numbers)
All the primitive types in java have their wrappers: Integer, Double etc.
According to Java tutorials:
Converting an object of a wrapper type (Integer) to its corresponding primitive (int) value is called unboxing.
And this is done pretty automatically, so I am not sure which object else would you like to "cast" to primitive? You can use a method like your toDouble(), which will return primitive, but it won't be casting at all.
You can use the wrapper of double, Double.
Double d = new Double(10.3);
double d1 = d; // This will be auto(un)boxing and not casting
transparent way to cast an object to a primitive type
double number = ex; this is illegal as ex is of type Example and number is of the primitive type double. This really doesn't make sense.
You either need to use toDouble(), or completely remove the Example class and use the wrapper class Double as mentioned above.
There is no analogy to C#`s implicit modifier. In general there are less syntax sugar in Java then in .NET. I dont this this is a problem. You can look inside sources of Integer, Double, Float etc. All they have methods like floatValue(), longValue() and so on. Making casts explicit improves code readability. While implicit casts, like available in C#, are very hard to understand. You can make your own method yourTypeValue(). This is a sort of Java convention.
You cannot cast, and little confusing what you are trying to do.
If you need double then , call toDouble()
double number = ex.toDouble();
or even, as some one commented, use the Double class constructor Double(double value)
Double wrapper = new Double(18.0); //class
double primitiveDouble =wrapper; // primitive
in wrapper classes we have two types of methods parseXxx() and valueOf() in every wrapper class for interconversion between primitive and wrapper objects.recently java 1.5 introduced auto boxing and boxing.so why they didn't deprecate those methods.
Because Autoboxing and Auto Unboxing are just compile time features. Try writing something like this in your source file and then have a look at the decompiled code:
Integer i = 10;
Decompiled code:
Integer i = Integer.valueOf(10);
Similarly,
int i = new Integer(100);
will give you the below when decompiled:
int i = (new Integer(100)).intValue();
Thus, the JVM still heavily relies on these methods at runtime, though it's masked when you write the code.
Well, parseXxx() is entirely unlike boxing; it turns a String into a primitive object. valueOf(), on the other hand, is actually used in boxing -- it either constructs a new wrapper object, or it fetches an existing one from a cache, depending on the value. The Java compiler generates a call to valueOf(), and that's precisely what boxing means.
1. There can be value sometimes in explicitly stating some conversion (for the clarity of e.g. some unobvious/obscure case).
2. Wouldn't that deprecation result in old programs becoming excessively littered with deprecation warnings?
As the command line arguments are treated as String Array, but given the condition when you are expecting command line argument other than String datatype(that may be primitives) i.e. boolean, int, byte, short, long, float, double, char than you need to parse the argument into the one what your program expects and here you use parseXXX() methods, to be precise parseXXX method take String argument and return the appropriate data type which you are trying to parse into.