You need to cast when the new type is not wider than or the same as the old type.
short is wider than byte
int is wider than byte, short, and char
long is wider than int
float is wider than long
double is wider than float
Note that "not wider than" isn't the same as "narrower than". Neither char nor short is wider than the other. Nor not contrariwise neither, as they say in Javaranch country: neither char nor short is narrower tan the other.
Pitfall alert: "wider" and "narrower" don't refer to bit width. 32-bit float is wider than 64-bit long. Width refers to the range of possible values the type can have. The range of byte completely fits with the range of float, so no cast is required: every possible byte can be reasonably represented by a float. But when you go from a float to a byte, there are possible values for the float that can't be represented by the byte. So the language requires you to explicitly tell the compiler that you really don't mind the risk, by using a cast.
Back when I taught
Java, I told people that casting is like "The Odd Couple". Remember that show? Felix was fussy and full of rules ... just like the Java compiler. Oscar wanted to do whatever he wanted to do, regardless of the consequences ... just like us programmers. When they got into conflict, Oscar would say, "Come on, Felix, pleeeeeease!" Felix would wag his finger and say "Very well, Oscar, but let this be on your head."
Look at line 3 below:
When this fails to compile, it's Felix saying that Oscar can't smoke cigars in the apartment when the Pigeon sisters come over for their dinner date. When we change line 3 to
, we're Oscar saying, "Oh come on, pleeeease!". When line 3 now compiles, Felix is saying "Very well, but let this be on your head!"
It's on Oscar's head because at runtime, Felix the compiler isn't around. If the value in d is larger than the max int, Oscar (that's us) will just have to live with the consequences.