Konstantin
Level 36
Odesa

Exploring questions and answers from a job interview for a Java developer position. Part 12

Published in the Random group
Hi! Knowledge is power. The more knowledge you have in your first interview, the more confident you will feel. Exploring questions and answers from a job interview for a Java developer position. Part 12 - 1If you bring in a big brain full of knowledge, your interviewer will find it difficult to confuse you and is likely to be pleasantly surprised. So without further ado, today we'll continue to strengthen your theoretical base by reviewing questions for a Java developer.

103. What rules apply to exception checking during inheritance?

If I understand the question correctly, they are asking about rules for working with exceptions during inheritance. The relevant rules are as follows:
  • An overridden or implemented method in a descendant/implementation cannot throw checked exceptions that are higher in the hierarchy than exceptions in a superclass/interface method.
For example, suppose we have some Animal interface with a method that throws an IOException:

public interface Animal {
   void speak() throws IOException;
}
When implementing this interface, we cannot expose a more general throwable exception (e.g. Exception, Throwable), but we can replace the existing exception with subclass, such as FileNotFoundException:

public class Cat implements Animal {
   @Override
   public void speak() throws FileNotFoundException {
// Some implementation
   }
}
  • The throws clause of the subclass constructor must include all exception classes thrown by the superclass constructor called to create the object.
Suppose the constructor of the Animal class throws a lot of exceptions:

 public class Animal {
   public Animal() throws ArithmeticException, NullPointerException, IOException {
   }
Then a subclass constructor must also throw them:

public class Cat extends Animal {
   public Cat() throws ArithmeticException, NullPointerException, IOException {
       super();
   }
Or, as with methods, you can specify different, more general exceptions. In our case, we can indicate Exception, because it is more general and is a common ancestor of all three exceptions indicated in the superclass:

public class Cat extends Animal {
   public Cat() throws Exception {
       super();
   }

104. Can you write some code where the finally block is not executed?

First, let's remember what finally is. Earlier, we examined the exception catching mechanism: a try block designates where exceptions will be caught, and catch block(s) are the code that will be invoked when a corresponding exception is caught. A third block of code marked by the finally keyword can replace or come after the catch blocks. The idea behind this block is that its code is always executed regardless of what happens in a try or catch block (regardless of whether there is an exception or not). Instances, where this block is not executed are infrequent and they are abnormal. The simplest example is when System.exit(0) is called before the finally block, thereby terminating the program:

try {
   throw new IOException();
} catch (IOException e) {
   System.exit(0);
} finally {
   System.out.println("This message will not be printed on the console");
}
There are also some other situations where the finally block won't run:
  • For example, an abnormal program termination caused by critical system errors, or some error that causes the application to crash (for example, the StackOverflowError, which occurs when the application stack is overflowed).

  • Another situation is when a daemon thread enters a try-finally block, but then the program's main thread terminates. After all, daemon threads are for background work that is not high-priority or mandatory, so the application won't wait for them to finish.

  • The most mindless example is an endless loop inside a try or catch block — once inside, a thread will be stuck there forever:

    
    try {
       while (true) {
       }
    } finally {
       System.out.println("This message will not be printed on the console");
    }
    
This question is very popular in junior developer interviews, so it's a good idea to remember a couple of these exceptional situations. Exploring questions and answers from a job interview for a Java developer position. Part 12 - 2

105. Write an example where you handle multiple exceptions in a single catch block.

1) I'm not sure this question was asked correctly. As far as I understand, this question is referring to several catch blocks and a single try:

try {
  throw new FileNotFoundException();
} catch (FileNotFoundException e) {
   System.out.print("Oops! There was an exception: " + e);
} catch (IOException e) {
   System.out.print("Oops! There was an exception: " + e);
} catch (Exception e) {
   System.out.print("Oops! There was an exception: " + e);
}
If an exception is thrown in a try block, then the associated catch blocks attempt to catch it, sequentially from top to bottom. Once the exception matches one of the catch blocks, any remaining blocks will no longer be able to catch and handle it. This all means that narrower exceptions are arranged above more general ones in the set of catch blocks. For example, if our first catch block catches the Exception class, then any subsequent blocks will not catch checked exceptions (that is, remaining blocks with subclasses of Exception will be completely useless). 2) Or perhaps the question was asked correctly. In that case, we could handle exceptions as follows:

try {
  throw new NullPointerException();
} catch (Exception e) {
   if (e instanceof FileNotFoundException) {
       // Some handling that involves a narrowing type conversion: (FileNotFoundException)e
   } else if (e instanceof ArithmeticException) {
       // Some handling that involves a narrowing type conversion: (ArithmeticException)e
   } else if(e instanceof NullPointerException) {
       // Some handling that involves a narrowing type conversion: (NullPointerException)e
   }
After using catch to catch an exception, we then try to discover its specific type by using the instanceof operator, which checks whether an object belongs to a certain type. This allows us to confidently perform a narrowing type conversion without fear of negative consequences. We could apply either approach in the same situation. I expressed doubt about the question only because I would not call the second option a good approach. In my experience, I have never encountered it, and the first approach involving multiple catch blocks is widespread.

106. Which operator lets you force an exception to be thrown? Write an example

I have already used it several times in the examples above, but I will repeat it once again: the throw keyword. Example of manually throwing an exception:

throw new NullPointerException();

107. Can the main method throw an exception? If so, then where does it go?

First of all, I want to note that the main method is nothing more than an ordinary method. Yes, it is called by the virtual machine to start the execution of a program, but beyond that, it can be called from any other code. That means it is also subject to the usual rules about indicating checked exceptions after the throws keyword:

public static void main(String[] args) throws IOException {
Accordingly, it can throw exceptions. When main is called as the program's starting point (rather than by some other method), then any exception it throws will be handled by UncaughtExceptionHandler. Each thread has one such handler (that is, there is one such handler in each thread). If necessary, you can create your own handler and set it by calling the public static void main(String[] args) throws IOException {setDefaultUncaughtExceptionHandler method on a public static void main(String[] args) throws IOException {Thread object.

Multithreading

Exploring questions and answers from a job interview for a Java developer position. Part 12 - 3

108. What mechanisms for working in a multithreaded environment do you know?

The basic mechanisms for multithreading in Java are:
  • The synchronized keyword, which is a way for a thread to lock a method/block when it enters, preventing other threads from entering.

  • The volatile keyword ensures consistent access to a variable accessed by different threads. That is, when this modifier is applied to a variable, all operations to assign and read that variable become atomic. In other words, the threads won't copy the variable to their local memory and change it. They will change its original value.

  • Runnable — We can implement this interface (which consists of a single run() method) in some class:

    
    public class CustomRunnable implements Runnable {
       @Override
       public void run() {
           // Some logic
       }
    }
    

    And once we create an object of that class, we can start a new thread by passing our object to the Thread constructor and then calling the start() method:

    
    Runnable runnable = new CustomRunnable();
    new Thread(runnable).start();
    

    The start method runs the implemented run() method on a separate thread.

  • Thread — We can inherit this class and overrides its run method:

    
    public class CustomThread extends Thread {
       @Override
       public void run() {
           // Some logic
       }
    }
    

    We can start a new thread by creating an object of this class and then calling the start() method:

    
    new CustomThread().start();
    

  • Concurrency — This is a package of tools for working in a multithreaded environment.

    It consists of:

    • Concurrent Collections — This is a collection of collections explicitly created for working in a multithreaded environment.

    • Queues — Specialized queues for a multithreaded environment (blocking and non-blocking).

    • Synchronizers — These are specialized utilities for working in a multithreaded environment.

    • Executors — Mechanisms for creating thread pools.

    • Locks — Thread synchronization mechanisms that more flexible than the standard ones (synchronized, wait, notify, notifyAll).

    • Atomics — Classes optimized for multithreading. Each of their operations is atomic.

109. Tell us about synchronization between threads. What are the wait(), notify(), notifyAll(), and join() methods for?

Synchronization between threads is about the synchronized keyword. This modifier can be placed either directly on the block:

synchronized (Main.class) {
   // Some logic
}
Or directly in the method signature:

public synchronized void move() {
   // Some logic }
As I said earlier, synchronized is a mechanism for locking a block/method to other threads once one thread enters. Let's think of a code block/method as a room. Some thread approaches the room, enters it, and locks the door with its key. When other threads approach the room, they see that the door is locked and wait nearby until the room becomes available. Once the first thread is done with its business in the room, it unlocks the door, leaves the room, and releases the key. I've mentioned a key a couple of times for a reason — because something analogous really exists. This is a special object that has a busy/free state. Every object in Java has such an object, so when we use the synchronized block, we need to use parentheses to indicate the object whose mutex will be locked:

Cat cat = new Cat();
synchronized (cat) {
   // Some logic
}
We can also use a mutex associated with a class, as I did in the first example (Main.class). After all, when we use synchronized on a method, we don't specifying the object we want to lock, right? In this case, for non-static methods, the mutex that will be locked is the this object, i.e. the current object of the class. For static methods, the mutex associated with the current class (this.getClass();) is locked. wait() is a method that frees the mutex and puts the current thread into the waiting state, as if attaching to the current monitor (something like an anchor). Because of this, this method can only be called from a synchronized block or method. Otherwise, what would it be waiting for and what would be released?). Also note that this is a method of the Object class. Well, not one, but three:
  • wait() puts the current thread into the waiting state until another thread calls the notify() or notifyAll() method on this object (we will talk about these methods later).

  • wait(long timeout) puts the current thread into the waiting state until another thread calls the notify() or notifyAll() method on this object or the time interval specified by timeout expires.

  • wait(long timeout, int nanos) is like the previous method, but here nanos lets you specify nanoseconds (a more precise timeout).

  • notify() lets you wake up one random thread waiting on the current synchronization block. Again, this method can only be called in a synchronized block or method (after all, in other places there would be nobody to wake up).

  • notifyAll() awakens all threads waiting on the current monitor (also only used in a synchronized block or method).

110. How do we stop a thread?

The first thing to say here is that when the run() runs to completion, the thread terminates automatically. But sometimes we want to kill a thread ahead of schedule, before the method is done. So what do we do? Perhaps we can use the stop() method on the Thread object? Nope! That method is deprecated and may cause system crashes. Exploring questions and answers from a job interview for a Java developer position. Part 12 - 4Well, what then? There are two ways to do this: First, use its internal boolean flag. Let's look at an example. We have our implementation of a thread that should display a certain phrase on the screen until the thread stops completely:

public class CustomThread extends Thread {
private boolean isActive;
 
   public CustomThread() {
       this.isActive = true;
   }
 
   @Override
   public void run() {
       {
           while (isActive) {
               System.out.println("The thread is executing some logic...");
           }
           System.out.println("The thread stopped!");
       }
   }
 
   public void stopRunningThread() {
       isActive = false;
   }
}
Calling the stopRunningThread() method sets the internal flag to false, causing the run() method to terminate. Let's call it in main:

System.out.println("Program starting...");
CustomThread thread = new CustomThread();
thread.start();
Thread.sleep(3);
// As long as our main thread is asleep, our CustomThread runs and prints its message on the console
thread.stopRunningThread();
System.out.println("Program stopping...");
As a result, we'll see something like this in the console:
Program starting... The thread is executing some logic... The thread is executing some logic... The thread is executing some logic... The thread is executing some logic... The thread is executing some logic... The thread is executing some logic... Program stopping... The thread stopped!
That means that our thread started, printed several messages on the console, and then successfully stopped. Note that the number of messages displayed will vary from launch to launch. And sometimes the auxiliary thread may not display anything at all. The specific behavior depends on how long the main thread sleeps. The longer it sleeps, the less likely it is that the auxiliary thread won't manage to display anything. With a sleep time of 1 ms, you will almost never see the messages. But if you set it to 20 ms, then the messages almost always get displayed. When the sleep time is short, the thread simply does not have time to start and do its work. Instead, it gets stopped immediately. A second way is to use the interrupted() method on the Thread object. It returns the value of the internal interrupted flag, which is false by default. Or its interrupt() method, which sets this flag to true (when the flag is true, the thread should stop running). Let's look at an example:

public class CustomThread extends Thread {
 
   @Override
   public void run() {
       {
           while (!Thread.interrupted()) {
               System.out.println("The thread is executing some logic...");
           }
           System.out.println("The thread stopped!");
       }
   }
}
Running in main:

System.out.println("Program starting...");
Thread thread = new CustomThread();
thread.start();
Thread.sleep(3);
thread.interrupt();
System.out.println("Program stopping...");
The result of running this is that same as in the first case, but I like this approach better: we wrote less code and used more ready-made, standard functionality. Well, that’s it for today!
Read more:
Exploring questions and answers from a job interview for a Java developer position. Part 12 - 5
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION