JavaRush /Java Blog /Random EN /Java Core. Interview questions, part 2
Andrey
Level 26

Java Core. Interview questions, part 2

Published in the Random EN group
For those who hear the word Java Core for the first time, these are the fundamental foundations of the language. With this knowledge, you can safely go for an internship/internship.
Java Core.  Questions for an interview, part 2 - 1
These questions will help you refresh your knowledge before the interview, or learn something new for yourself. To gain practical skills, study at JavaRush . Original article Links to other parts: Java Core. Interview questions, part 1 Java Core. Questions for an interview, part 3

Why should finalize() method be avoided?

We all know the statement that a method finalize()is called by the garbage collector before freeing the memory occupied by an object. Here is an example program that proves that a method call finalize()is not guaranteed:
public class TryCatchFinallyTest implements Runnable {

	private void testMethod() throws InterruptedException
	{
		try
		{
			System.out.println("In try block");
			throw new NullPointerException();
		}
		catch(NullPointerException npe)
		{
			System.out.println("In catch block");
		}
		finally
		{
			System.out.println("In finally block");
		}
	}

	@Override
	protected void finalize() throws Throwable {
		System.out.println("In finalize block");
		super.finalize();
	}

	@Override
	public void run() {
		try {
			testMethod();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
public class TestMain
{
	@SuppressWarnings("deprecation")
	public static void main(String[] args) {
	for(int i=1;i< =3;i++)
	{
		new Thread(new TryCatchFinallyTest()).start();
	}
	}
}
Output: In try block In catch block In finally block In try block In catch block In finally block In try block In catch block In finally block Surprisingly, the method finalizewas not executed for any thread. This proves my words. I think the reason is that finalizers are executed by a separate garbage collector thread. If the Java Virtual Machine terminates too early, then the garbage collector does not have enough time to create and execute finalizers. Other reasons not to use the method finalize()may be:
  1. The method finalize()does not work with chains like constructors. This means that when you call a class constructor, the superclass constructors will be called unconditionally. But in the case of the method finalize(), this will not happen. The superclass method finalize()must be called explicitly.
  2. Any exception thrown by the method finalizeis ignored by the garbage collector thread and will not be propagated further, which means that the event will not be recorded in your logs. This is very bad, isn't it?
  3. You also get a significant performance penalty if the method finalize()is present in your class. In Effective Programming (2nd ed.), Joshua Bloch said:
    “Yes, and one more thing: there is a big performance penalty when using finalizers. On my machine, the time to create and destroy simple objects is approximately 5.6 nanoseconds.
    Adding a finalizer increases the time to 2400 nanoseconds. In other words, it is approximately 430 times slower to create and delete an object with a finalizer.”

Why shouldn't HashMap be used in a multi-threaded environment? Could this cause an infinite loop?

We know that HashMapthis is a non-synchronized collection, the synchronized counterpart of which is HashTable. So, when you are accessing a collection and in a multi-threaded environment where all threads have access to a single instance of the collection, then it is safer to use HashTablefor obvious reasons, such as avoiding dirty reads and ensuring data consistency. In the worst case, this multi-threaded environment will cause an infinite loop. Yes it's true. HashMap.get()may cause an infinite loop. Let's see how? If you look at the method's source code HashMap.get(Object key), it looks like this:
public Object get(Object key) {
    Object k = maskNull(key);
    int hash = hash(k);
    int i = indexFor(hash, table.length);
    Entry e = table[i];
    while (true) {
        if (e == null)
            return e;
        if (e.hash == hash && eq(k, e.key))
            return e.value;
        e = e.next;
    }
}
while(true)can always fall victim to an infinite loop in a multi-threaded runtime environment if for some reason e.nextit can point to itself. This will cause an endless loop, but how e.nextwill it point to itself (that is, to e)? This can happen in a method void transfer(Entry[] newTable)that is called while it HashMapis being resized.
do {
    Entry next = e.next;
    int i = indexFor(e.hash, newCapacity);
    e.next = newTable[i];
    newTable[i] = e;
    e = next;
} while (e != null);
This piece of code is prone to creating an infinite loop if the resize occurs at the same time that another thread is trying to change the map instance ( HashMap). The only way to avoid this scenario is to use synchronization in your code, or better yet, use a synchronized collection.

Explain abstraction and encapsulation. How are they connected?

In simple words , “ Abstraction displays only those properties of an object that are significant for the current view . ” In object-oriented programming theory, abstraction involves the ability to define objects that represent abstract "actors" that can perform work, change and report changes in their state, and "interact" with other objects in the system. Abstraction in any programming language works in many ways. This can be seen from the creation of routines to define interfaces for low-level language commands. Some abstractions try to limit the breadth of the overall representation of a programmer's needs by completely hiding the abstractions on which they are built, such as design patterns. Typically, abstraction can be seen in two ways: Data abstraction is a way of creating complex data types and exposing only meaningful operations to interact with the data model, while at the same time hiding all implementation details from the outside world. Execution abstraction is the process of identifying all significant statements and exposing them as a work unit. We usually use this feature when we create a method to do some work. Confining data and methods within classes in combination with performing hiding (using access control) is often called encapsulation. The result is a data type with characteristics and behavior. Encapsulation essentially also involves data hiding and implementation hiding. "Encapsulate everything that can change" . This quote is a well-known design principle. For that matter, in any class, data changes can happen at runtime and implementation changes can happen in future versions. Thus, encapsulation applies to both the data and the implementation. So they can be connected like this:
  • Abstraction is mostly What a class can do [Idea]
  • Encapsulation is more How to achieve this functionality [Implementation]

Differences between interface and abstract class?

The main differences can be listed as follows:
  • An interface cannot implement any methods, but an abstract class can.
  • A class can implement many interfaces, but can only have one superclass (abstract or non-abstract)
  • An interface is not part of a class hierarchy. Unrelated classes can implement the same interface.
What you have to remember is this: “When you can fully describe a concept in terms of “what it does” without having to specify “how it does it”, then you should use an interface. If you need to include some implementation details, then you need to represent your concept in an abstract class." Also, to put it another way: Are there many classes that can be "grouped together" and described by a single noun? If so, create an abstract class with the name of this noun, and inherit classes from it. For example, Catand Dogcan inherit from the abstract class Animal, and this abstract base class will implement the method void Breathe()- breathe, which all animals will thus perform in the same way. What verbs can be applied to my class and can be applied to others? Create an interface for each of these verbs. For example, all animals can eat, so I will create an interface IFeedableand make it Animalimplement that interface. Only good enough to implement an interface Dog( capable of liking me), but not all. Someone said: the main difference is where you want your implementation. When you create an interface, you can move the implementation to any class that implements your interface. By creating an abstract class, you can share the implementation of all derived classes in one place and avoid a lot of bad things like duplicating code. HorseILikeable

How does StringBuffer save memory?

The class Stringis implemented as an immutable object, meaning that when you initially decide to put something into the object String, the virtual machine allocates a fixed-length array exactly the size of your original value. This will then be treated as a constant inside the virtual machine, which provides a significant performance improvement if the value of the string does not change. However, if you decide to change the contents of a string in any way, what the virtual machine actually does is copy the contents of the original string into temporary space, make your changes, then save those changes to a new memory array. Thus, making changes to the value of a string after initialization is an expensive operation. StringBuffer, on the other hand, is implemented as a dynamically expanding array inside the virtual machine, which means that any modification operation can occur on an existing memory cell and new memory will be allocated as needed. However, there is no way for the virtual machine to do the optimization StringBufferbecause its contents are considered inconsistent across each instance.

Why are the wait and notify methods declared in the Object class instead of Thread?

waitThe , notify, methods notifyAllare only needed when you want your threads to have access to shared resources and the shared resource could be any java object in the heap. Thus, these methods are defined on the base class Objectso that each object has a control that allows threads to wait on their monitor. Java does not have any special object that is used to share a shared resource. No such data structure is defined. Therefore, it is the responsibility of the class Objectto be able to become a shared resource, and provide helper methods such as wait(), notify(), notifyAll(). Java is based on Charles Hoare's idea of ​​monitors. In Java, all objects have a monitor. Threads wait on monitors, so to perform the wait we need two parameters:
  • a thread
  • monitor (any object).
In Java design, a thread cannot be precisely defined; it is always the current thread executing the code. However, we can define a monitor (which is an object on which we can call a method wait). This is a good design because if we can force any other thread to wait on a specific monitor, it will result in "invasion", making designing/programming parallel programs difficult. Remember that in Java, all operations that interfere with other threads are deprecated (for example, stop()).

Write a program to create a deadlock in Java and fix it

In Java deadlock, this is a situation where at least two threads hold a block on different resources, and both are waiting for the other resource to become available to complete their task. And none of them is able to leave a lock on the resource being held. Java Core.  Questions for an interview, part 2 - 2 Example program:
package thread;

public class ResolveDeadLockTest {

	public static void main(String[] args) {
		ResolveDeadLockTest test = new ResolveDeadLockTest();

		final A a = test.new A();
		final B b = test.new B();

		// Thread-1
		Runnable block1 = new Runnable() {
			public void run() {
				synchronized (a) {
					try {
					// Добавляем задержку, чтобы обе нити могли начать попытки
					// блокирования ресурсов
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					// Thread-1 заняла A но также нуждается в B
					synchronized (b) {
						System.out.println("In block 1");
					}
				}
			}
		};

		// Thread-2
		Runnable block2 = new Runnable() {
			public void run() {
				synchronized (b) {
					// Thread-2 заняла B но также нуждается в A
					synchronized (a) {
						System.out.println("In block 2");
					}
				}
			}
		};

		new Thread(block1).start();
		new Thread(block2).start();
	}

	// Resource A
	private class A {
		private int i = 10;

		public int getI() {
			return i;
		}

		public void setI(int i) {
			this.i = i;
		}
	}

	// Resource B
	private class B {
		private int i = 20;

		public int getI() {
			return i;
		}

		public void setI(int i) {
			this.i = i;
		}
	}
}
Running the above code will result in a deadlock for very obvious reasons (explained above). Now we need to solve this problem. I believe that the solution to any problem lies at the root of the problem itself. In our case, the access model to A and B is the main problem. Therefore, to solve it, we simply change the order of access operators to shared resources. After the change it will look like this:
// Thread-1
Runnable block1 = new Runnable() {
	public void run() {
		synchronized (b) {
			try {
				// Добавляем задержку, чтобы обе нити могли начать попытки
				// блокирования ресурсов
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			// Thread-1 заняла B но также нуждается в А
			synchronized (a) {
				System.out.println("In block 1");
			}
		}
	}
};

// Thread-2
Runnable block2 = new Runnable() {
	public void run() {
		synchronized (b) {
			// Thread-2 заняла B но также нуждается в А
			synchronized (a) {
				System.out.println("In block 2");
			}
		}
	}
};
Run this class again and now you won't see the deadlock. I hope this helps you avoid deadlocks and get rid of them if you encounter them.

What happens if your class that implements the Serializable interface contains a non-serializable component? How to fix this?

In this case, it will be thrown NotSerializableExceptionduring execution. To fix this problem, there is a very simple solution - check these boxes transient. This means that checked fields will not be serialized. If you also want to store the state of these fields, then you need to consider reference variables, which already implement the Serializable. You may also need to use the readResolve()and methods writeResolve(). Let's summarize:
  • First, make your field non-serializable transient.
  • First writeObject, call defaultWriteObjecton the thread to save all non- transientfields, then call the remaining methods to serialize the individual properties of your non-serializable object.
  • In readObject, first call defaultReadObjecton the stream to read all non transientfields, then call other methods (corresponding to the ones you added in writeObject) to deserialize your non transientobject.

Explain transient and volatile keywords in Java

"The keyword transientis used to indicate fields that will not be serialized." According to the Java Language Specification: Variables can be marked with the transient indicator to indicate that they are not part of the object's persistent state. For example, you may contain fields derived from other fields, and it is preferable to obtain them programmatically rather than restore their state through serialization. For example, in a class, BankPayment.javafields such as principal(director) and rate(rate) can be serialized, and interest(accrued interest) can be calculated at any time, even after deserialization. If we remember, each thread in Java has its own local memory and performs read/write operations on this local memory. When all operations are done, it writes the modified state of the variable into shared memory, from where all threads access the variable. Typically, this is a normal thread inside a virtual machine. But the volatile modifier tells the virtual machine that a thread's access to that variable must always match its own copy of that variable with the master copy of the variable in memory. This means that every time a thread wants to read the state of a variable, it must clear the internal memory state and update the variable from main memory. Volatilemost useful in lock-free algorithms. You mark a variable storing shared data as volatile, then you don't use locks to access that variable, and all changes made by one thread will be visible to others. Or if you want to create a "happened-after" relationship to ensure that calculations are not repeated, again to ensure changes are visible in real time. Volatile should be used to safely publish immutable objects in a multi-threaded environment. The field declaration public volatile ImmutableObjectensures that all threads always see the currently available reference to the instance.

Difference between Iterator and ListIterator?

We can use , or Iteratorto iterate over elements . But it can only be used to iterate over elements . Other differences are described below. You can: SetListMapListIteratorList
  1. iterate in reverse order.
  2. get index anywhere.
  3. add any value anywhere.
  4. set any value at the current position.
Good luck with your studies!! Author of the article Lokesh Gupta Original article Java Core. Interview questions, part 1 Java Core. Questions for an interview, part 3
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION