4

Why does this not compile (tried with java 8 and java 10)? It yields a missing return statement error.

 public class CompilerIssue {
   public boolean run() {
     throwIAE();
     // Missing return statement
   }

   public void throwIAE() {
     throw new IllegalStateException("error");
   }
 }
Csa77
  • 649
  • 13
  • 19
gkumar7
  • 43
  • 4

8 Answers8

18

The java compiler does not know that the throwIAE will always throw an exception so it assumes that you will eventually reach the end of the run method and, when that happens, a return value is required.

DwB
  • 37,124
  • 11
  • 56
  • 82
  • Well, it should be accepted answer nice explanation +1 – Emre Savcı Aug 27 '18 at 17:41
  • just curious, how to make modification so that the java compiler know that he throwIAE will always throw an exception? – The Scientific Method Aug 27 '18 at 17:43
  • 1
    You can't change the code such that the compiler knows that throwIAE will always throw an exception. If the "real" code should always throw the exception, then inline the throw new exception() into the `run` method. – DwB Aug 27 '18 at 17:59
  • In the given example, the compiler really can't know, as a subclass might override throwIAE() with a different implementation, e.g. not throwing at all. Let's check if a final declaration makes a difference (will stille challenge the intelligence of the compiler...). – Ralf Kleberhoff Aug 27 '18 at 19:48
  • Declaring `throwIAE()` final doesn't help (maybe some future compiler will be clever enough). – Ralf Kleberhoff Aug 27 '18 at 19:54
7

Your method has a return type so it should return a boolean result.

   public boolean run() {
     throwIAE();
     // Missing return statement
     return false;
   }

Or you should throw your exception directly in method :

   public boolean run() {
     throw new IllegalStateException("error"); // it will compile
   }
Emre Savcı
  • 3,034
  • 2
  • 16
  • 25
3

Even if compilers are smart nowadays, they cannot 'see' that your throwIAE() method always returns an Exception because this will happen during runtime and not compile time. What if you handle the exception? Then you must add a return value eventually.

tomky71
  • 56
  • 3
2

I didn't really expect this to work, but interestingly you CAN do this, which makes the code a little more readable:

    public static <T> T unsupported()
    {
        throw new UnsupportedOperationException();
    }

     ...
    
    return unsupported();
Jonathan Locke
  • 243
  • 2
  • 6
1

The exception thrown terminates the throwIAE method, but not the method which calls this one. Understand this as a method returning an output (empty return; since void).

public void throwIAE() {
     return;                // is not responsible for terminating run()
}

Do you see? The return terminates the throwIAE method and the program continues. Both the return and throw is related to the scope of the method itself, not the calling method. You need:

public boolean run() {
     throwIAE();            // throws exception and terminates throwIAR()
     return false;          // terminates run()
}
Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
1

Good answers so far. I wonder how nobody mentioned AOP(Aspect Oriented Programming). With AOP you can easily intercept your method throwIAE() during RUNTIME and catch the exception. The calling method run() cannot know this.

Some example:

package yourPackage;

public class YourClass {

    public boolean run() {
        throwIAE();
        System.out.println("Hello world!");// prints Hello World! even if method throws exception
        return false;// does not compile without return statement
    }

    public void throwIAE() {
        throw new IllegalStateException("error");
    }

}

And another Aspect class which intercepts throwIAE() method during runtime:

package yourPackage;

@Aspect
public class ExceptionHandlingAspect {
    @Around("execution(* yourPackage.YourClass.throwIAE())")
    public Object handleException(ProceedingJoinPoint pjp) throws Throwable {
        try {
            return pjp.proceed();
        } catch (Exception e) {
            System.out.println("exception caught");
            return null;
        }
    }
}  
Csa77
  • 649
  • 13
  • 19
  • you have a nice example there :) I didn't think about this, but as you say, everything can happen during runtime and the compiler cannot know this in advance – tomky71 Aug 28 '18 at 10:01
  • The example, while useful to show possible runtime behaviors, is not Java and hence will not be considered by a Java compiler. I understood the question to be about Java, though. – Stephan Herrmann Sep 10 '18 at 21:12
0

While other answers correctly explain the situation from a user's perspective, it is worth noting, that the "cleverness" of analyzing such flows is not at the discretion of a compiler but precisely defined by the rules of JLS. In particular, §14.21 contains this:

An expression statement can complete normally iff it is reachable.

A method invocation like throwIAE() is an expression statement, and because it is reachable in the example, it can also "complete normally". Because of that, the method body can "complete normally", which according to §8.4.7 is illegal for a method with a non-void return type (which is to say, such methods must return a value on all possible paths).

I other words, JLS defines that all method invocations are treated equally during flow analysis, and there is no such concept as "method-that-always-throws".

Community
  • 1
  • 1
Stephan Herrmann
  • 7,963
  • 2
  • 27
  • 38
0

Imagine this:

 public class NotReallyACompilerIssue extends CompilerIssue {

   @Override    
   public void throwIAE() {
     // doesn't really throw the IAE!
   }
 }

If your original class compiled without the return statement in run(), the run() method in NotReallyACompilerIssue whould've completed successfully and had no return. Which wouldn't be good, would it?

It is be possible to detect such situation if the class isfinal, or method throwIAE isfinal. It isn't the case though, the compiler (Oracle J8_131 at least) does not make such checks. I imagine this is a very specific case and it wasn't worth the effort.

Dariusz
  • 21,561
  • 9
  • 74
  • 114