JavaRush /Java 博客 /Random-ZH /Java 开发人员访谈问答分析。第12部分

Java 开发人员访谈问答分析。第12部分

已在 Random-ZH 群组中发布
你好!知识就是力量。在第一次面试之前你掌握的知识越多,你就会越有信心。 Java 开发人员访谈问答分析。 第 12 - 1 部分有了足够的知识,你就很难被迷惑,同时你也能够给面试官带来惊喜。因此,今天,闲话少说,我们将通过检查Java 开发人员的 250 多个问题来继续加强您的理论基础。 Java 开发人员访谈问答分析。 第 12 - 2 部分

103. 继承中检查异常的规则是什么?

如果我正确理解这个问题,他们是在询问继承过程中处理异常的规则,如下所示:
  • 后代/实现中的重写或实现的方法不能抛出层次结构中比超类/接口方法中的异常更高的已检查异常。
也就是说,如果我们有某个Animal接口,其方法会抛出IOException
public  interface Animal {
   void voice() throws IOException;
}
在这个接口的实现中,我们不能抛出更通用的抛出异常(例如ExceptionThrowable),但我们可以用后代异常来替换它,例如FileNotFoundException
public class Cat implements Animal {
   @Override
   public void voice() throws FileNotFoundException {
// некоторая реализация
   }
}
  • 子类构造函数必须在其throws 块中包含创建对象时调用的超类构造函数抛出的所有异常类。
假设Animal类的构造函数抛出很多异常:
public class Animal {
  public Animal() throws ArithmeticException, NullPointerException, IOException {
  }
那么类继承人也必须在构造函数中指出它们:
public class Cat extends Animal {
   public Cat() throws ArithmeticException, NullPointerException, IOException {
       super();
   }
或者,与方法的情况一样,您可以指定不同的异常,而是指定更通用的异常。在我们的例子中,指定一个更通用的异常 - Exception就足够了,因为这是所考虑的所有三个异常的共同祖先:
public class Cat extends Animal {
   public Cat() throws Exception {
       super();
   }

104. 你能写出finally块何时不被执行的代码吗?

首先,让我们记住finally是什么。之前,我们研究了捕获异常的机制:try块概述了捕获区域,而catch块是抛出特定异常时将起作用的代码。 finally是finally之后的第三个代码块,可与catch互换但不互斥。该块的本质是,无论trycatch的结果如何(无论是否抛出异常),其中的代码始终有效。其失败的案例非常罕见,而且是不正常的。最简单的失败情况是在上面的代码中调用System.exit(0)方法,该方法终止程序(消灭它):
try {
   throw new IOException();
} catch (IOException e) {
   System.exit(0);
} finally {
   System.out.println("Данное сообщение не будет выведенно в консоль");
}
还有一些其他情况finally不起作用:
  • 由于严重的系统问题导致程序异常终止,或者某些错误的发生会使应用程序“崩溃”(错误的一个例子可以是堆栈内存溢出时发生的相同的StackOwerflowError )。
  • 当守护线程通过ry...finally块时,程序将随之结束。毕竟,守护线程是用于后台操作的线程,也就是说,它不是优先级和强制的,应用程序不会等待其工作完成。
  • 最常见的无限循环,在trycatch中,一旦进入其中,流程将永远保留在那里:

    try {
       while (true) {
       }
    } finally {
       System.out.println("Данное сообщение не будет выведенно в консоль");
    }

这个问题在新手面试中很常见,因此一些特殊情况值得记住。 Java 开发人员访谈问答分析。 第 12 - 3 部分

105. 编写一个在一个catch块中处理多个异常的示例

1)也许这个问题问错了。 据我了解,这个问题意味着一个try块有多个捕获
try {
  throw new FileNotFoundException();
} catch (FileNotFoundException e) {
   System.out.print("Упс, у вас упало исключение - " + e);
} catch (IOException e) {
   System.out.print("Упс, у вас упало исключение - " + e);
} catch (Exception e) {
   System.out.print("Упс, у вас упало исключение - " + e);
}
如果try块 中发生异常,则catch块从上到下交替尝试捕获该异常,如果某个catch块成功,则它获得处理异常的权利,而下面的其余块将不再被捕获。能够尝试捕捉它并以自己的方式处理它。因此,较窄的异常被放置在catch块链的较高位置,而较宽的异常则被放置在较低位置。例如,如果在我们的第一个catch 块中捕获了Exception类的异常,那么受检查的异常将无法进入剩余的块(具有Exception后代的剩余块将绝对无用)。 2)问题问得正确, 在这种情况下,我们的处理将如下所示:
try {
  throw new NullPointerException();
} catch (Exception e) {
   if (e instanceof FileNotFoundException) {
       // некоторая обработка с сужением типа (FileNotFoundException)e
   } else if (e instanceof ArithmeticException) {
       // некоторая обработка с сужением типа (ArithmeticException)e
   } else if(e instanceof NullPointerException) {
       // некоторая обработка с сужением типа (NullPointerException)e
   }
通过catch 捕获异常后,我们尝试通过instanceof方法找出其具体类型,该方法用于检查对象是否属于某种类型,以便稍后我们可以将范围缩小到该类型,而不会产生负面后果。这两种考虑的方法都可以在相同的情况下使用,但我说这个问题是不正确的,因为我不会认为第二个选项很好,并且在我的实践中从未见过它,而同时第一个带有多重捕获的方法已经得到了广泛的应用关注.传播. Java 开发人员访谈问答分析。 第 12 - 4 部分

106. 哪个运算符允许强制抛出异常?写一个例子

我已经在上面多次使用过它,但尽管如此,我还是会重复这个关键字 - throw。用法示例(强制异常):
throw new NullPointerException();

107. main 方法可以抛出 throws 异常吗?如果是的话,会转移到哪里?

首先,我要指出的是,main只不过是一个常规方法,是的,它是由虚拟机调用来开始执行程序的,但除此之外,它还可以从任何其他代码中调用。也就是说,它也遵循在throws之后指定检查异常的通常规则:
public static void main(String[] args) throws IOException {
相应地,其中也可能出现异常。如果main没有在某个方法中调用,而是作为程序启动点启动,那么它抛出的异常将由.UncaughtExceptionHandler拦截器处理。该处理程序是每个线程一个(即每个线程中有一个处理程序)。如有必要,您可以创建自己的处理程序并使用Thread对象上调用的setDefaultUncaughtExceptionHandler方法来设置它。

多线程

Java 开发人员访谈问答分析。 第 12 - 5 部分

108. 您知道哪些用于多线程处理的工具?

Java中使用多线程的基础/基础工具:
  • 同步是一种当线程从其他线程进入方法/块时关闭(阻止)方法/块的机制。
  • Volatile是一种确保不同线程对变量的访问一致的机制,也就是说,变量上存在此修饰符后,所有赋值和读取操作都必须是原子的。换句话说,线程不会将该变量复制到本地内存并更改它,而是会更改其原始值。
在这里阅读更多关于易失性的信息。
  • Runnable是一个可以在某个类中实现的接口(特别是它的 run 方法):
public class CustomRunnable implements Runnable {
   @Override
   public void run() {
       // некоторая логика
   }
}
创建此类的对象后,您可以通过在新Thread对象的构造函数中设置该对象并调用其start()方法来启动新线程:
Runnable runnable = new CustomRunnable();
new Thread(runnable).start();
start 方法在单独的线程中运行已实现的run()方法。
  • Thread是一个类,继承自该类(同时重写run方法):
public class CustomThread extends Thread {
   @Override
   public void run() {
       // некоторая логика
   }
}
通过创建此类的对象并使用start()方法启动它,我们将启动一个新线程:
new CustomThread().start();
  • 并发是一个包含在多线程环境中工作的工具的包。
它包括:
  • 并发集合- 一组专门用于在多线程环境中工作的集合。
  • 队列- 用于多线程环境(阻塞和非阻塞)的专用队列。
  • 同步器是用于在多线程环境中工作的专用实用程序。
  • 执行器是创建线程池的机制。
  • ——线程同步机制(比标准同步机制更灵活——synchronized、wait、notify、notifyAll)。
  • 原子是针对多线程执行而优化的类;每个操作都是原子的。
在此处阅读有关并发包的更多信息。

109.谈谈线程之间的同步。wait()、notify() - notifyAll() join() 方法的用途是什么?

据我了解这个问题,线程之间的同步与关键修饰符 - synchronized有关。该修饰符可以直接放置在块旁边:
synchronized (Main.class) {
   // некоторая логика
}
或者直接在方法签名中:
public synchronized void move() {
   // некоторая логика}
正如我之前所说,同步是一种机制,允许您在一个线程已经进入某个块/方法时从其他线程关闭该块/方法。将块/方法视为一个房间。一些流到达该房间后,将进入它并锁定它,其他流到达该房间并看到它已关闭,将在它附近等待,直到它空闲。完成其任务后,第一个线程离开房间并释放密钥。我不断地谈论钥匙也不是无缘无故的,因为它确实存在。这是一个具有忙碌/空闲状态的特殊对象。该对象附加到每个 Java 对象,因此当使用同步块时,我们需要在括号中指出我们想要关闭其互斥锁的对象:
Cat cat = new Cat();
synchronized (cat) {
   // некоторая логика
}
您还可以使用类互斥体,就像我在第一个示例 ( Main.class ) 中所做的那样。当我们在方法上使用同步时,我们没有指定要关闭的对象,对吗?在这种情况下,对于非静态方法,它将关闭this对象(即该类的当前对象)的互斥锁。静态的将关闭当前类的互斥体(this.getClass();)。您可以在此处阅读有关互斥体的更多信息。好吧,请在这里阅读有关同步的内容。 Wait()是一种释放互斥体并将当前线程置于待机模式的方法,就像附加到当前监视器(类似于锚点)一样。因此,该方法只能从同步块或方法中调用(否则,它应该释放什么以及它应该期望什么)。另请注意,这是Object类的方法。更准确地说,不是一个,而是三个:
  • Wait() - 将当前线程置于等待模式,直到另一个线程调用该对象的notify()notifyAll()方法(我们稍后将讨论这些方法)。

  • wait(长超时) - 将当前线程置于等待模式,直到另一个线程调用此对象的notify()notifyAll()方法或指定的超时到期。

  • wait (long timeout, int nanos) - 与上一个类似,只有nanos允许您指定纳秒(更精确的时间设置)。

  • Notify()是一种允许您唤醒当前同步块的一个随机线程的方法。再次强调,它只能在同步块或方法中调用(毕竟在其他地方它不会有人解冻)。

  • NotifyAll()是一个唤醒当前监视器上所有等待线程的方法(也仅在同步块或方法中使用)。

110.如何止流?

首先要说的是,当run()方法完全执行完毕后,线程会自动销毁。但有时你需要在这个方法完成之前提前杀死他。那我们该怎么办呢?也许Thread对象应该有一个stop()方法?不管怎么样!这种方法被认为已经过时,并且可能导致系统崩溃。 Java 开发人员访谈问答分析。 第 12 - 6 部分嗯,然后呢?有两种方法可以做到这一点: 第一种是使用内部布尔标志。让我们看一个例子。我们有自己的线程实现,该线程应该在屏幕上显示某个短语直到完全停止:
public class CustomThread extends Thread {
private boolean isActive;

   public CustomThread() {
       this.isActive = true;
   }

   @Override
   public void run() {
       {
           while (isActive) {
               System.out.println("Поток выполняет некую логику...");
           }
           System.out.println("Поток остановлен!");
       }
   }

   public void stopRunningThread() {
       isActive = false;
   }
}
当使用stopRunning()方法时,内部标志变为 false,run方法停止运行。让我们在main中运行它:
System.out.println("Начало выполнения программы");
CustomThread thread = new CustomThread();
thread.start();
Thread.sleep(3);
// пока наш основной поток спит, вспомогательный  CustomThread работает и выводит в коноль своё сообщение
thread.stopRunningThread();
System.out.println("Конец выполнения программы");
结果,我们会在控制台中看到类似这样的内容:
程序执行开始 线程正在执行某些逻辑... 线程正在执行某些逻辑... 线程正在执行某些逻辑... 线程正在执行某些逻辑... 线程正在执行某些逻辑...线程正在执行一些逻辑...程序执行结束线程已停止!
这意味着我们的线程工作了,向控制台输出了一定数量的消息并成功停止。我注意到,每次运行时输出的消息数量都会有所不同;有时附加线程甚至没有输出任何内容。正如我注意到的,这取决于主线程的睡眠时间,时间越长,附加线程不输出任何内容的机会就越小。当睡眠时间为 1ms 时,消息几乎不会输出,但如果将其设置为 20ms,则几乎总是有效。也许,当时间很短时,线程根本没有时间启动并开始其工作,并立即停止。 第二种方法是使用Thread对象上的interrupted()方法,该方法返回内部中断标志的值(该标志默认为)和它的另一个interrupt()方法,该方法将该标志设置为true(当此标志为true时)标志为true则线程应该停止其工作)。让我们看一个例子:
public class CustomThread extends Thread {

   @Override
   public void run() {
       {
           while (!Thread.interrupted()) {
               System.out.println("Поток выполняет некую логику...");
           }
           System.out.println("Поток остановлен!");
       }
   }
}
在main 中运行:
System.out.println("Начало выполнения программы");
Thread thread = new CustomThread();
thread.start();
Thread.sleep(3);
thread.interrupt();
System.out.println("Конец выполнения программы");
执行的结果将与第一种情况相同,但我更喜欢这种方法:我们编写更少的代码并使用更多现成的标准功能。 Java 开发人员访谈问答分析。 第 12 - 7 部分这就是我们今天要停下来的地方。Java 开发人员访谈问答分析。 第 12 - 8 部分
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION