对于那些第一次听到 Java Core 这个词的人来说,这些是该语言的基础。有了这些知识,您就可以安全地去实习/实习了。
这些问题将帮助您在面试前刷新知识,或者为自己学习新的东西。要获得实用技能,请在JavaRush学习。 原创文章 其他部分链接: Java Core。面试问题,第 1 部分 Java 核心。面试问题,第 3 部分
为什么要避免使用finalize()方法?
finalize()
我们都知道垃圾收集器在释放对象占用的内存之前调用方法的说法。finalize()
下面是一个示例程序,证明不能保证 方法调用:
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();
}
}
}
输出: 在try块中 在catch块中 在finally块中 在try块中 在catch块中 在finally块中 在try块中 在catch块中 在finally块中 令人惊讶的是,该方法finalize
没有为任何线程执行。这证明了我的话。我认为原因是终结器是由单独的垃圾收集器线程执行的。如果 Java 虚拟机终止得太早,那么垃圾收集器就没有足够的时间来创建和执行终结器。不使用该方法的其他原因finalize()
可能是:
- 该方法
finalize()
不适用于构造函数等链。这意味着当您调用类构造函数时,将无条件调用超类构造函数。但在该方法的情况下finalize()
,这种情况不会发生。finalize()
必须显式调用超类方法。 - 该方法抛出的任何异常
finalize
都会被垃圾收集器线程忽略,并且不会进一步传播,这意味着该事件不会记录在您的日志中。这很糟糕,不是吗? -
finalize()
如果您的类中存在该方法,您还会受到显着的性能损失。在《有效编程》(第二版)中,Joshua Bloch 说:
“是的,还有一件事:使用终结器时会产生很大的性能损失。在我的机器上,创建和销毁简单对象的时间大约为 5.6 纳秒。
添加终结器会将时间增加到 2400 纳秒。换句话说,使用终结器创建和删除对象大约要慢 430 倍。”
为什么HashMap不能用在多线程环境中?这会导致无限循环吗?
我们知道HashMap
这是一个非同步集合,其同步对应项是HashTable
。HashTable
因此,当您访问集合并且在所有线程都可以访问集合的单个实例的多线程环境中时,出于显而易见的原因,例如避免脏读和确保数据一致性,使用它会更安全。在最坏的情况下,这种多线程环境将导致无限循环。对,是真的。HashMap.get()
可能会导致无限循环。让我们看看如何?如果你查看该方法的源代码HashMap.get(Object key)
,它看起来像这样:
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)
e.next
如果由于某种原因它可以指向自身,则它总是可能成为多线程运行时环境中无限循环的受害者。这会导致死循环,但是e.next
它如何指向自身(即 to e
)呢?void transfer(Entry[] newTable)
这可能发生在调整大小时调用的方法中HashMap
。
do {
Entry next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
如果在另一个线程尝试更改地图实例 ( ) 的同时发生调整大小,则这段代码很容易产生无限循环HashMap
。避免这种情况的唯一方法是在代码中使用同步,或者更好的是使用同步集合。
解释抽象和封装。它们是如何连接的?
简而言之,“抽象仅显示对象中对当前视图重要的那些属性。 ” 在面向对象编程理论中,抽象涉及定义代表抽象“参与者”的对象的能力,这些“参与者”可以执行工作、更改和报告其状态的变化,以及与系统中的其他对象“交互”。任何编程语言中的抽象都有多种方式。这可以从为低级语言命令定义接口的例程的创建中看出。一些抽象试图通过完全隐藏构建它们的抽象(例如设计模式)来限制程序员需求的整体表示的广度。通常,抽象可以通过两种方式来看待: 数据抽象是一种创建复杂数据类型并仅公开有意义的操作以与数据模型交互的方法,同时向外界隐藏所有实现细节。 执行抽象是识别所有重要语句并将它们作为工作单元公开的过程。当我们创建方法来完成某些工作时,我们通常会使用此功能。 将数据和方法限制在类内并结合执行隐藏(使用访问控制)通常称为封装。结果是具有特征和行为的数据类型。封装本质上还涉及数据隐藏和实现隐藏。 “封装一切可以改变的东西”。这句话是众所周知的设计原则。就此而言,在任何类中,数据更改都可能在运行时发生,并且实现更改可能在未来版本中发生。因此,封装适用于数据和实现。所以它们可以这样连接:- 抽象主要是类可以做什么[想法]
- 封装比较多如何实现这个功能【实现】
接口和抽象类的区别?
主要区别如下:- 接口不能实现任何方法,但抽象类可以。
- 一个类可以实现多个接口,但只能有一个超类(抽象或非抽象)
- 接口不是类层次结构的一部分。不相关的类可以实现相同的接口。
Cat
和Dog
可以继承抽象类Animal
,并且这个抽象基类将实现方法void Breathe()
- 呼吸,因此所有动物都会以相同的方式执行该方法。哪些动词可以应用于我的班级并且可以应用于其他班级?为每个动词创建一个接口。例如,所有动物都可以吃东西,所以我将创建一个接口IFeedable
并使其Animal
实现该接口。仅足以实现一个界面Dog
(能够喜欢我),但不是全部。有人说:主要区别在于你想要在哪里实施。创建接口时,可以将实现移至实现接口的任何类。通过创建抽象类,您可以在一处共享所有派生类的实现,并避免许多不好的事情,例如重复代码。 Horse
ILikeable
StringBuffer如何节省内存?
该类String
被实现为不可变对象,这意味着当您最初决定将某些内容放入该对象时String
,虚拟机会分配一个固定长度的数组,该数组的大小与原始值的大小完全相同。然后,这将被视为虚拟机内的常量,如果字符串的值不发生变化,这将提供显着的性能改进。但是,如果您决定以任何方式更改字符串的内容,虚拟机实际上所做的是将原始字符串的内容复制到临时空间中,进行更改,然后将这些更改保存到新的内存阵列中。因此,在初始化后更改字符串的值是一项昂贵的操作。 StringBuffer
另一方面,它是作为虚拟机内部动态扩展的数组实现的,这意味着任何修改操作都可以发生在现有的内存单元上,并且将根据需要分配新的内存。但是,虚拟机无法进行优化,StringBuffer
因为其内容在每个实例之间被认为是不一致的。
为什么wait和notify方法声明在Object类中而不是Thread类中?
仅当您希望线程能够访问共享资源并且共享资源可以是堆中的任何 java 对象时,才需要 , ,wait
方法。因此,这些方法是在基类上定义的,以便每个对象都有一个允许线程在其监视器上等待的控件。Java 没有任何用于共享共享资源的特殊对象。没有定义这样的数据结构。因此,类有责任能够成为共享资源,并提供、、 等辅助方法。Java 基于 Charles Hoare 的监视器思想。在Java中,所有对象都有一个监视器。线程在监视器上等待,因此要执行等待,我们需要两个参数: notify
notifyAll
Object
Object
wait()
notify()
notifyAll()
- 一个线程
- 监视器(任何对象)。
wait
)。这是一个很好的设计,因为如果我们可以强制任何其他线程在特定监视器上等待,就会导致“入侵”,从而使设计/编程并行程序变得困难。请记住,在 Java 中,不推荐使用所有干扰其他线程的操作(例如,stop()
)。
编写一个程序,在 Java 中创建死锁并修复它
在 Java 中deadlock
,这是一种至少两个线程在不同资源上持有一个块,并且都在等待另一个资源可用以完成其任务的情况。他们都无法对所持有的资源留下锁定。 示例程序:
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;
}
}
}
由于非常明显的原因(如上所述),运行上述代码将导致死锁。现在我们需要解决这个问题。我相信任何问题的解决都在于问题本身。在我们的例子中,A 和 B 的访问模型是主要问题。因此,为了解决这个问题,我们只需改变共享资源的访问运算符的顺序即可。更改后将如下所示:
// 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");
}
}
}
};
再次运行这个类,现在您将不会看到死锁。我希望这可以帮助您避免僵局并在遇到僵局时摆脱僵局。
如果实现 Serialized 接口的类包含不可序列化的组件,会发生什么情况?如何解决这个问题?
在这种情况下,它会在执行过程中抛出NotSerializableException
。要解决此问题,有一个非常简单的解决方案 - 选中这些框transient
。这意味着选中的字段不会被序列化。如果您还想存储这些字段的状态,那么您需要考虑引用变量,它已经实现了Serializable
. 您可能还需要使用readResolve()
和方法writeResolve()
。我们总结一下:
- 首先,使您的字段不可序列化
transient
。 - 首先
writeObject
,调用defaultWriteObject
线程来保存所有非transient
字段,然后调用其余方法来序列化不可序列化对象的各个属性。 - 在 中
readObject
,首先调用defaultReadObject
流来读取所有非transient
字段,然后调用其他方法(对应于您在 中添加的方法writeObject
)来反序列化您的非transient
对象。
解释Java中的transient和volatile关键字
“该关键字transient
用于指示不会被序列化的字段。” 根据 Java 语言规范: 变量可以用瞬态指示符来标记,以表明它们不是对象持久状态的一部分。例如,您可能包含从其他字段派生的字段,最好以编程方式获取它们,而不是通过序列化恢复其状态。例如,在一个类中,(director)和(rate)BankPayment.java
等字段可以被序列化,并且(应计利息)可以随时计算,甚至在反序列化之后也是如此。如果我们还记得的话,Java 中的每个线程都有自己的本地内存,并在该本地内存上执行读/写操作。当所有操作完成后,它将变量的修改状态写入共享内存,所有线程都从这里访问该变量。通常,这是虚拟机内的普通线程。但 volatile 修饰符告诉虚拟机,线程对该变量的访问必须始终与该变量自己的副本与内存中该变量的主副本相匹配。这意味着每次线程想要读取变量的状态时,它必须清除内部存储器状态并从主存储器更新变量。 在无锁算法中最有用。您将存储共享数据的变量标记为易失性,然后您不使用锁来访问该变量,并且一个线程所做的所有更改将对其他线程可见。或者,如果您想创建“发生后”关系以确保不重复计算,再次确保更改实时可见。应该使用 Volatile 在多线程环境中安全地发布不可变对象。字段声明确保所有线程始终看到对该实例的当前可用引用。 principal
rate
interest
Volatile
public volatile ImmutableObject
迭代器和列表迭代器的区别?
我们可以使用,或Iterator
来迭代元素。但它只能用于迭代元素。其他差异如下所述。你可以: Set
List
Map
ListIterator
List
- 以相反的顺序迭代。
- 在任何地方获取索引。
- 在任何地方添加任何价值。
- 在当前位置设置任意值。
GO TO FULL VERSION