rspec/rules/S6320/java/rule.adoc

137 lines
4.0 KiB
Plaintext

This issue indicates that a cast operation will fail and throw a `ClassCastException`.
To fix it, make sure only references to objects with a compatible type can be cast.
== Why is this an issue?
A cast operation allows an object to be "converted" from one type to another.
The compiler raises an error if it can determine that the target type is incompatible with the declared type of the object, otherwise it accepts the cast.
However, depending on the actual runtime type of the object, a cast operation may fail at runtime.
When a cast operation fails, a `ClassCastException` is thrown.
=== What is the potential impact?
This type of exception is usually unexpected.
It causes the program to crash or puts it into an inconsistent state.
Therefore, this issue might impact the availability and reliability of your application, or even result in data loss.
== How to fix it
When an object is cast, the code makes assumptions about the type of the object.
A `ClassCastException` indicates that this assumption has been broken.
If the assumption is reasonable, then some prior logic should ensure that only objects of a compatible type can be cast.
You should try to identify the code responsible for these checks and fix it.
=== Code examples
==== Noncompliant code example
[source,java,diff-id=1,diff-type=noncompliant]
----
private String hexString(Object o) {
return Integer.toHexString((Integer) o); // Noncompliant if hexString is called with a String for example
}
----
==== Compliant solution
One possible solution is to change `hexString` to only accept integers and adapt call sites.
[source,java,diff-id=1,diff-type=compliant]
----
private String hexString(Integer i) {
return Integer.toHexString(i);
}
----
Another one is to return a default value for types that are not `Integer`.
Here, the `if` statement with the condition relying on `instanceof` prevents references to objects with an incompatible type from making it to the cast operation.
[source,java,diff-id=1,diff-type=compliant]
----
private String hexString(Object o) {
if (o instanceof Integer) {
return Integer.toHexString((Integer) o);
}
return "0x0";
}
----
The solution to adopt depends on which part of the code should be responsible to handle non-Integer types.
=== Going the extra mile
Casting is considered an anti-pattern in object-oriented programming.
It is sometimes necessary, but there often is a better alternative.
Consider:
* Polymorphism
* The visitor design pattern
* Pattern matching
They come with some guarantees from the compiler, making your code more reliable.
==== Noncompliant code example
[source,java,diff-id=2,diff-type=noncompliant]
----
int foo(Shape shape) {
if (shape instanceof Circle) {
Circle circle = (Circle) shape;
// Code for objects of type Circle
} else if (shape instanceof Square) {
Square square = (Square) shape;
// Code for objects of type Square
}
// Default
}
----
==== Compliant solution
[source,java,diff-id=2,diff-type=compliant]
----
int foo(Shape shape) {
return shape.fooValue(...); // Code was moved into subclasses
}
----
or
[source,java,diff-id=2,diff-type=compliant]
----
int foo(Shape shape) {
return shape.accept(new FooVisitor()); // Code was moved into FooVisitor
}
----
or
[source,java,diff-id=2,diff-type=compliant]
----
// Java 14+ required
int foo(Shape shape) {
if (shape instanceof Circle c) {
// Code for objects of type Circle
} else if (shape instanceof Square s) {
// Code for objects of type Square
}
}
----
== Resources
=== Documentation
* https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/ClassCastException.html[ClassCastException]
=== Articles & blog posts
* https://refactoring.guru/design-patterns/visitor[Refactoring Guru: Visitor]
* https://openjdk.org/jeps/394[JEP 394: Pattern Matching for `instanceof`]
=== Standards
* https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.16[JLS: Cast Expressions]
* https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.20.2[JLS: Type Comparison Operator `instanceof`]