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 already safely go on an internship / internship.
These questions will help you refresh your knowledge before the interview, or learn something new for yourself. For practical skills, study on CodeGym . Original article Links to other parts: Java Core. Interview Questions Part 1 Java Core. Questions for the interview, part 3
Why should the finalize() method be avoided?
We all know the statement that a methodfinalize()
is called by the garbage collector before the memory occupied by an object is deallocated. 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 finalize
was not executed for any thread. This proves my words. I think the reason is that the finalizers are executed by a separate garbage collector thread. If the JVM terminates too early, then the garbage collector does not have enough time to create and execute the finalizers. Other reasons not to use the method finalize()
might be:
- The method
finalize()
does not work with chains like constructors do. This means that when you call a class constructor, the superclass constructors will be called unconditionally. But in the case of the methodfinalize()
, this will not follow. The superclass methodfinalize()
must be called explicitly. - Any exception thrown by the method
finalize
is ignored by the garbage collector thread and will not be propagated further, which means the event will not be logged to your logs. It's very bad, isn't it? -
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 creation and destruction of simple objects takes about 5.6 nanoseconds.
Adding a finalizer increases the time to 2400 nanoseconds. In other words, creating and deleting an object with a finalizer is about 430 times slower.”
Why shouldn't HashMap be used in a multi-threaded environment? Can this cause an infinite loop?
We know thatHashMap
is an unsynchronized collection, of which HashTable
. Thus, when you are accessing a collection in a multi-threaded environment where all threads have access to the same instance of the collection, then it is safer to use HashTable
for obvious reasons, such as avoiding dirty reading and maintaining data consistency. In the worst case, this multi-threaded environment will cause an infinite loop. Yes it's true. HashMap.get()
can 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 prey to an infinite loop in a multi-threaded runtime environment if for some reason e.next
it can point to itself. This will cause an infinite loop, but how e.next
will it point to itself (that is, to e
)? This can happen in a method void transfer(Entry[] newTable)
that is being called while HashMap
resizing.
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 happens at the same time another thread tries to resize the map instance ( HashMap
). The only way to avoid this scenario is to use synchronization in code, or better yet, use a synchronized collection.
Explain abstraction and encapsulation. How are they related?
In simple words , " The abstraction displays only those properties of the object that are significant for the current view". In object-oriented programming theory, abstraction includes the ability to define objects that represent abstract "actors" that can do 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 view of the programmer's needs by completely hiding the abstractions they are built on, such as design patterns. Generally, abstraction can be seen in two ways: Data abstractionis 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 relevant statements and exposing them as a unit of work. We usually use this feature when we create a method to do some work. The encapsulation of data and methods within classes, combined with the implementation of hiding (using access control), is often referred to as encapsulation. The result is a data type with characteristics and behavior. Encapsulation, in essence, also contains 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 data and implementation. So they can be linked like this:- Abstraction for the most part is 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.
Cat
and Dog
can be inherited from an abstract class Animal
, and this abstract base class will implement the methodvoid Breathe()
- to 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 to each of these verbs. For example, all animals can eat, so I'll create an interface IFeedable
and make it Animal
implement that interface. Only Dog
and Horse
good enough to implement the interface ILikeable
(capable of liking me), but not all. Someone said: the main difference is where you want your implementation. When creating an interface, you can move the implementation to any class that implements your interface. By creating an abstract class, you can separate the implementation of all derived classes in one place and avoid a lot of bad things like code duplication.
How does StringBuffer save memory?
The classString
is implemented as an immutable object, meaning that when you initially decide to put something into an object String
, the virtual machine allocates a fixed-length array, exactly the size of your original value. In the future, this will be treated as a constant inside the virtual machine, which provides a significant performance improvement in case the value of the string does not change. However, if you decide to change the contents of the string in any way, what the virtual machine is really doing is copying the contents of the original string into temporary space, making your changes, then saving those changes to a new memory array. Thus, making changes to the string value after initialization is an expensive operation. StringBuffer
, on the other hand, is implemented as a dynamically expandable array inside the virtual machine, which means that any change operation can take place on an existing memory location, and new memory will be allocated as needed. However, there is no way for the virtual machine to make optimizations StringBuffer
, since its contents are considered volatile in each instance.
Why are the wait and notify methods declared on the Object class instead of Thread?
wait
The , notify
, methods notifyAll
are only needed when you want your threads to have access to shared resources and the shared resource can be any java object in the heap. Thus, these methods are defined on the base class Object
so that each object has the control to allow threads to wait on its 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 Object
to be able to become a shared resource, and to provide helper methods such as wait()
, notify()
,notifyAll()
. Java is based on the idea of monitors by Charles Hoare. In Java, all objects have a monitor. Threads are waiting on monitors, so we need two parameters to execute the wait:
- a thread
- monitor (any object).
wait
). This is a good idea, because if we can make any other thread wait on a certain monitor, it will lead to "intrusion", making it difficult to design/program parallel programs. 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 Javadeadlock
, this is a situation where at least two threads hold a block on different resources, and both are waiting for another resource to be freed to complete their task. And neither is able to leave the lock on the held resource. Program example:
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 it?
In this case, it will be thrownNotSerializableException
during execution. To fix this problem, there is a very simple solution - check these boxes transient
. This means that the checked fields will not be serialized. If you also want to store the state of these fields, then you need to consider reference variables that already implement the Serializable
. You may also need to use the readResolve()
and methods writeResolve()
. Let's summarize:
- First, make your non-serializable
transient
. - First
writeObject
calldefaultWriteObject
on the thread to save all non-transient
fields, then call the rest of the methods to serialize the individual properties of your non-serializable object. - In
readObject
, first calldefaultReadObject
on the stream to read all non-transient
fields, then call other methods (corresponding to the ones you added inwriteObject
) to deserialize your non-transient
object.
Explain transient and volatile keywords in Java
"The keywordtransient
is used to designate 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 stable state of an object. For example, you can contain fields derived from other fields, and it is preferable to get them programmatically rather than restoring their state through serialization. For example, in a class, BankPayment.java
fields such as principal
(director) and rate
(rate) can be serialized, andinterest
(interest accrued) can be calculated at any time, even after deserialization. If we recall, 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 to shared memory, from where all threads can access the variable. As a rule, this is a normal thread inside the virtual machine. But the volatile modifier tells the virtual machine that a thread's access to this variable must always match its own copy of this variable with the main 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 state of internal memory and update the variable from main memory. Volatile
most useful in lock-free algorithms. You mark a variable holding shared data as volatile, then you don't use locks to access that variable, and any changes made by one thread will be visible to others. Or if you want to create an happened-after relationship to ensure that calculations are not repeated, again to provide real-time visibility into changes. Volatile should be used to securely publish immutable objects in a multi-threaded environment. The field declaration public volatile ImmutableObject
ensures that all threads always see the currently available instance reference.
Difference between Iterator and ListIterator?
We can use , orIterator
to iterate over the elements . But it can only be used for iterating over elements . Other differences are described below. You can: Set
List
Map
ListIterator
List
- iterate in reverse order.
- get index anywhere.
- add any value anywhere.
- set any value at the current position.
GO TO FULL VERSION