Preguntas de la entrevista de Complete Works of Java (6)

Preguntas de la entrevista de Complete Works of Java (6)

Baiyu es jaja

51. La clase ExampleA hereda Exception y la clase ExampleB hereda ExampleA.

Existen los siguientes fragmentos de código:


try {
    throw new ExampleB("b")
} catch(ExampleA e){
    System.out.println("ExampleA");
} catch(Exception e){    System.out.println("Exception");
}

¿Cuál es el resultado de ejecutar este código?

Respuesta: Salida: Ejemplo A. (De acuerdo con el principio de sustitución de Richter [donde se puede usar el tipo principal, se puede usar el subtipo], el bloque catch que captura la excepción de tipo ExampleA puede capturar la excepción de tipo ExampleB lanzada en el bloque try)

Pregunta de entrevista: indique el resultado del siguiente código. (La fuente de esta pregunta es el libro "Pensamientos de programación de Java")


class Annoyance extends Exception {}
class Sneeze extends Annoyance {}
class Human {
    public static void main(String[] args) 
        throws Exception {
        try {
            try {
                throw new Sneeze();
            } 
            catch ( Annoyance a ) {
                System.out.println("Caught Annoyance");
                throw a;
            }
        } 
        catch ( Sneeze s ) {
            System.out.println("Caught Sneeze");
            return ;
        }
        finally {
            System.out.println("Hello World!");
        }
    }
}

52. ¿La lista, el conjunto y el mapa se heredan de la interfaz de colección?

Respuesta: Lista y Conjunto son sí, pero Mapa no. Map es un contenedor de mapeo de clave-valor, que obviamente es diferente de List y Set. Set almacena elementos dispersos y no permite elementos duplicados (lo mismo es cierto para conjuntos en matemáticas). List es un contenedor con una estructura lineal y es adecuado para valores numéricos. Índice de acceso al elemento.

53. Explique el rendimiento y las características del almacenamiento de ArrayList, Vector, LinkedList.

Respuesta: ArrayList y Vector usan matrices para almacenar datos. La cantidad de elementos en esta matriz es mayor que los datos reales almacenados para agregar e insertar elementos. Ambos permiten la indexación directa de elementos por número de secuencia, pero la inserción de elementos implica operaciones de memoria como el movimiento de elementos de matriz, por lo que La indexación de datos es rápida y la inserción de datos lenta. El método en Vector es un contenedor seguro para subprocesos debido a la modificación sincronizada, pero su rendimiento es peor que el de ArrayList, por lo que ya es un contenedor heredado en Java. LinkedList utiliza una lista doblemente vinculada para lograr el almacenamiento (asocia las celdas de memoria dispersas en la memoria a través de referencias adicionales para formar una estructura lineal que se puede indexar por número de secuencia. En comparación con el almacenamiento continuo de matrices, este método de almacenamiento encadenado tiene una tasa de utilización de memoria. Más alto), la indexación de datos por número de secuencia requiere un recorrido hacia adelante o hacia atrás, pero al insertar datos, solo necesita registrar los elementos anteriores y siguientes de este elemento, por lo que la velocidad de inserción es más rápida. Vector pertenece a contenedores heredados (contenedores provistos en las primeras versiones de Java. Además, Hashtable, Dictionary, BitSet, Stack y Properties son todos contenedores heredados) y ya no se recomienda, pero debido a que ArrayList y LinkedListed no son seguros para subprocesos, Si encuentra un escenario en el que varios subprocesos operan el mismo contenedor, puede usar el método synchronizedList en las colecciones de herramientas para convertirlo en un contenedor seguro para subprocesos antes de usarlo (esta es la aplicación del modo de decoración y los objetos existentes se pasan a otro Cree nuevos objetos en el constructor de una clase para mejorar la implementación).

Suplemento: La clase Properties y la clase Stack en el contenedor heredado tienen serios problemas de diseño. Properties es un mapeo especial de pares clave-valor cuya clave y valor son ambas cadenas. El diseño debe ser asociar una Hashtable y dos El parámetro genérico se establece en el tipo String, pero las Propiedades en la API de Java heredan directamente Hashtable, lo que obviamente es un abuso de herencia. La forma de reutilizar el código aquí debería ser la relación Has-A en lugar de la relación Is-A. Por otro lado, el contenedor pertenece a la clase de herramienta. Heredar la clase de herramienta en sí es un enfoque incorrecto. La mejor manera de usar la clase de herramienta es la relación Has-A. (Asociación) o Relación de Uso-A (dependencia). Por la misma razón, es incorrecto que la clase Stack herede Vector. Los ingenieros de Sun también cometen errores de bajo nivel, lo que avergüenza a la gente.

54. ¿Cuál es la diferencia entre colección y colecciones?

Respuesta: Collection es una interfaz, que es la interfaz principal de Set, List y otros contenedores; Collections es una clase de herramienta que proporciona una serie de métodos estáticos para ayudar en las operaciones del contenedor. Estos métodos incluyen búsqueda de contenedores, clasificación, seguridad de subprocesos, etc. Espere.

55. ¿Cuáles son las características de cada una de las tres interfaces de Lista, Mapa y Conjunto al acceder a elementos?

Respuesta: List accede a elementos con un índice específico y puede haber elementos repetidos. El conjunto no puede almacenar elementos repetidos (utilice el método equals () del objeto para distinguir si los elementos se repiten). El mapa almacena el mapeo de pares clave-valor, y la relación de mapeo puede ser uno a uno o varios a uno. Los contenedores Set y Map tienen dos versiones de implementación basadas en el almacenamiento de hash y el árbol de clasificación. La complejidad del tiempo de acceso de la teoría de la versión basada en el almacenamiento de hash es O (1), mientras que la implementación basada en la versión del árbol de clasificación es al insertar o eliminar elementos. El árbol de clasificación se construirá según el elemento o la clave del elemento para lograr el efecto de clasificación y deduplicación.

56. ¿Cómo comparan los elementos TreeMap y TreeSet al ordenar? ¿Cómo compara los elementos el método sort () de la clase de utilidad Colecciones?

Respuesta: TreeSet requiere que la clase a la que pertenece el objeto almacenado debe implementar la interfaz Comparable. Esta interfaz proporciona un método compareTo () para comparar elementos. Cuando se inserta un elemento, este método se volverá a llamar para comparar el tamaño del elemento. TreeMap requiere que las claves almacenadas en el mapeo del par clave-valor deben implementar la interfaz Comparable para ordenar los elementos según la clave. El método de clasificación de la clase de herramientas Colecciones tiene dos formas sobrecargadas. La primera requiere que los objetos almacenados en el contenedor entrante para ser ordenados implementen la interfaz Comparable para lograr la comparación de elementos; la segunda no es obligatorio para requerir los elementos en el contenedor Debe ser comparable, pero se requiere pasar el segundo parámetro. El parámetro es un subtipo de la interfaz Comparator (el método de comparación debe reescribirse para lograr la comparación de elementos), que es equivalente a una regla de clasificación definida temporalmente, de hecho, el tamaño del elemento de comparación se inyecta a través de la interfaz El algoritmo también es la aplicación del modo de devolución de llamada (el soporte de la programación funcional en Java).
Ejemplo 1:


public class Student implements Comparable<Student> {
    private String name;        // 姓名
    private int age;            // 年龄
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
    }
    @Override
    public int compareTo(Student o) {
        return this.age - o.age; // 比较年龄(年龄的升序)
    }
}

import java.util.Set;
import java.util.TreeSet;
class Test01 {
    public static void main(String[] args) {
   // Java 7的钻石语法(构造器后面的尖括号中不需要写类型)
        Set<Student> set = new TreeSet<>();  
        set.add(new Student("Hao LUO", 33));
        set.add(new Student("XJ WANG", 32));
        set.add(new Student("Bruce LEE", 60));
        set.add(new Student("Bob YANG", 22));
        for(Student stu : set) {
            System.out.println(stu);
        }
//      输出结果: 
//      Student [name=Bob YANG, age=22]
//      Student [name=XJ WANG, age=32]
//      Student [name=Hao LUO, age=33]
//      Student [name=Bruce LEE, age=60]
    }
}

Ejemplo 2:


public class Student {
    private String name;    // 姓名
    private int age;        // 年龄
    public Student(String name, int age) {
        this.name = name;
        this.age = age;    }    /**     * 获取学生姓名     */
    public String getName() {
        return name;
    }
    /**     * 获取学生年龄     */
    public int getAge() {
        return age;
    }
    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
    }
}

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class Test02 {
    public static void main(String[] args) {
        List<Student> list = new ArrayList<>();     // Java 7的钻石语法(构造器后面的尖括号中不需要写类型)
        list.add(new Student("Hao LUO", 33));
        list.add(new Student("XJ WANG", 32));
        list.add(new Student("Bruce LEE", 60));
        list.add(new Student("Bob YANG", 22));
        // 通过sort方法的第二个参数传入一个Comparator接口对象
        // 相当于是传入一个比较对象大小的算法到sort方法中
        // 由于Java中没有函数指针、仿函数、委托这样的概念
        // 因此要将一个算法传入一个方法中唯一的选择就是通过接口回调
        Collections.sort(list, new Comparator<Student> () {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.getName().compareTo(o2.getName());    // 比较学生姓名
            }
        });
        for(Student stu : list) {
            System.out.println(stu);
        }
//      输出结果: 
//      Student [name=Bob YANG, age=22]
//      Student [name=Bruce LEE, age=60]
//      Student [name=Hao LUO, age=33]
//      Student [name=XJ WANG, age=32]
    }
}

57. Tanto el método sleep () de la clase Thread como el método wait () del objeto pueden hacer que el hilo suspenda la ejecución ¿Cuál es la diferencia entre ellos?

Respuesta: El método sleep () (sleep) es un método estático de la clase de subproceso (Thread). Si se llama a este método, el subproceso actual suspenderá la ejecución durante un tiempo específico y dará la oportunidad de ejecución (CPU) a otros subprocesos, pero el bloqueo del objeto aún se mantiene. Por lo tanto, se reanudará automáticamente después de que expire el tiempo de reposo (el hilo vuelve al estado listo, consulte el diagrama de transición del estado del hilo en la pregunta 66). wait () es un método de la clase Object. Llamar al método wait () del objeto hace que el subproceso actual abandone el bloqueo del objeto (el subproceso suspende la ejecución) y entra en el grupo de espera del objeto. Solo se llama al método notificar () (o notificar a todos) del objeto () método) puede despertar los hilos en el grupo de espera para ingresar al grupo de bloqueo (grupo de bloqueo), si el hilo vuelve a adquirir el bloqueo del objeto, puede ingresar al estado listo.

Suplemento: es posible que muchas personas todavía sean vagas sobre qué es un proceso y qué es un hilo, y no comprenden particularmente por qué se necesita la programación de varios hilos. En pocas palabras: un proceso es un programa con una determinada función independiente sobre una actividad en ejecución en un determinado conjunto de datos, una unidad independiente del sistema operativo para la asignación y programación de recursos; un hilo es una entidad del proceso, que es la base de la programación y el envío de la CPU. Una unidad es una unidad básica que es más pequeña que un proceso y puede ejecutarse de forma independiente. La escala de división de los subprocesos es más pequeña que la de los procesos, lo que hace que los programas multiproceso tengan una alta concurrencia; los procesos generalmente tienen unidades de memoria independientes durante la ejecución y los subprocesos pueden compartir memoria. La programación de subprocesos múltiples generalmente puede brindar un mejor rendimiento y experiencia de usuario, pero los programas de subprocesos múltiples no son compatibles con otros programas porque pueden consumir más recursos de la CPU. Por supuesto, no es que cuantos más subprocesos, mejor será el rendimiento del programa, porque la programación y el cambio entre subprocesos también desperdiciarán tiempo de CPU. Hoy en día, el muy moderno Node.js adopta el modo de trabajo de E / S asíncrona de un solo subproceso.

58. ¿Cuál es la diferencia entre el método thread sleep () y el método yield ()?

Respuesta:
① El método sleep () no considera la prioridad del hilo cuando se le da a otros hilos la oportunidad de ejecutarse, por lo que le dará al hilo de baja prioridad la oportunidad de ejecutarse; el método yield () solo le dará al hilo con la misma prioridad o mayor prioridad Aproveche la oportunidad de ejecutar;
② El hilo pasa al estado bloqueado después de ejecutar el método sleep (), y pasa al estado listo después de ejecutar el método yield ();
③ La declaración del método sleep () arroja InterruptedException y yield () El método no declara ninguna excepción;
④ El método sleep () tiene una mejor portabilidad que el método yield () (relacionado con la programación de la CPU del sistema operativo).

59. Después de que un subproceso ingresa al método sincronizado A de un objeto, ¿pueden otros subprocesos ingresar al método sincronizado B de este objeto?

Respuesta: No. Otros subprocesos solo pueden acceder al método no sincronizado del objeto y el método sincronizado no puede ingresar. Debido a que el modificador sincronizado en el método no estático requiere que se obtenga el bloqueo del objeto cuando se ejecuta el método, si ha ingresado el método A para indicar que se ha eliminado el bloqueo del objeto, entonces el hilo que intenta ingresar al método B solo puede esperar al grupo de bloqueo (tenga en cuenta que no está esperando La cerradura del objeto que espera en la piscina.

60. Cuénteme los métodos relacionados con la sincronización y la programación de subprocesos.

responder:

  • wait (): pone un hilo en estado de espera (bloqueo) y suelta el bloqueo del objeto que contiene;
  • sleep (): hace que un hilo en ejecución se suspenda, es un método estático, que llama a este método para tratar con InterruptedException;
  • notificar (): Despierta un hilo en un estado de espera. Por supuesto, al llamar a este método, no puede exactamente despertar un hilo en un estado de espera, pero la JVM determina qué hilo despertar y no tiene nada que ver con la prioridad;
  • notityAll (): despierta todos los subprocesos en el estado de espera. Este método no otorga el bloqueo del objeto a todos los subprocesos, pero les permite competir. Sólo el subproceso que obtiene el bloqueo puede entrar en el estado listo;

Sugerencia: Con respecto a la programación simultánea y multiproceso de Java, le sugiero que lea mi otro artículo "Resumen y pensamiento sobre la programación simultánea de Java".
Suplemento: Java 5 proporciona un mecanismo de bloqueo explícito (bloqueo explícito) a través de la interfaz de bloqueo, que mejora la flexibilidad y coordinación de subprocesos. La interfaz Lock define métodos para bloquear (lock ()) y desbloquear (unlock ()). También proporciona un método newCondition () para generar un objeto Condition para la comunicación entre subprocesos; además, Java 5 también proporciona señales Semáforo, el semáforo se puede utilizar para limitar el número de subprocesos que acceden a un recurso compartido. Antes de acceder al recurso, el hilo debe obtener el permiso del semáforo (llamar al método adquirir () del objeto Semaphore); después de completar el acceso al recurso, el hilo debe devolver el permiso al semáforo (llamar al método release () del objeto Semaphore) .

El siguiente ejemplo demuestra la ejecución de 100 hilos depositando 1 yuan en una cuenta bancaria al mismo tiempo, sin usar el mecanismo de sincronización y usando el mecanismo de sincronización.
Categoría de cuenta bancaria:


/** * 银行账户 * @author 骆昊 * */
public class Account {
    private double balance;     // 账户余额
    /**     * 存款     * @param money 存入金额     */
    public void deposit(double money) {
        double newBalance = balance + money;
        try {
            Thread.sleep(10);   // 模拟此业务需要一段处理时间
        }
        catch(InterruptedException ex) {
            ex.printStackTrace();
        }
        balance = newBalance;
    }

    /**     * 获得账户余额     */
    public double getBalance() {
        return balance;
    }
}

Guardar clase de hilo:


/** * 存钱线程 * @author 骆昊 * */
public class AddMoneyThread implements Runnable {
    private Account account;    // 存入账户
    private double money;       // 存入金额
    public AddMoneyThread(Account account, double money) {
        this.account = account;
        this.money = money;
    }
    @Override
    public void run() {
        account.deposit(money);
    }
}

Categoría de prueba:


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test01 {
    public static void main(String[] args) {
        Account account = new Account();
        ExecutorService service = Executors.newFixedThreadPool(100);
        for(int i = 1; i <= 100; i++) {
            service.execute(new AddMoneyThread(account, 1));
        }
        service.shutdown();
        while(!service.isTerminated()) {}
        System.out.println("账户余额: " + account.getBalance());
    }
}

En ausencia de sincronización, el resultado de la ejecución generalmente muestra que el saldo de la cuenta es inferior a 10 yuanes. La razón de esta situación es que cuando un hilo A intenta depositar 1 yuan, otro hilo B también puede ingresar al método de depósito. , El saldo de la cuenta leído por el hilo B sigue siendo el saldo de la cuenta antes de que el hilo A deposite 1 yuan, por lo que también agrega 1 yuan al saldo original 0. De manera similar, el hilo C hará cosas similares. Entonces, cuando finaliza la ejecución de los últimos 100 hilos, se espera que el saldo de la cuenta sea de 100 yuanes, pero el resultado real suele ser inferior a 10 yuanes (lo más probable es que 1 yuan). La solución a este problema es la sincronización. Cuando un hilo deposita dinero en una cuenta bancaria, necesita bloquear la cuenta y solo permitir que otros hilos operen después de que se complete su operación. El código tiene los siguientes esquemas de ajuste:

  • Palabras clave sincronizadas en el método de depósito de la cuenta bancaria

/** * 银行账户 * @author 骆昊 * */
public class Account {
    private double balance;     // 账户余额
    /**     * 存款     * @param money 存入金额     */
    public synchronized void deposit(double money) {
        double newBalance = balance + money;
        try {
            Thread.sleep(10);   // 模拟此业务需要一段处理时间
        }
        catch(InterruptedException ex) {
            ex.printStackTrace();
        }
        balance = newBalance;
    }
    /**     * 获得账户余额     */
    public double getBalance() {
        return balance;
    }
}

Sincronizar cuentas bancarias cuando el hilo llama al método de depósito


/** * 存钱线程 * @author 骆昊 * */
public class AddMoneyThread implements Runnable {
    private Account account;    // 存入账户
    private double money;       // 存入金额
    public AddMoneyThread(Account account, double money) {
        this.account = account;
        this.money = money;
    }
    @Override
    public void run() {
        synchronized (account) {
            account.deposit(money); 
        }
    }
}

A través del mecanismo de bloqueo que se muestra en Java 5, se crea un objeto de bloqueo para cada cuenta bancaria, y las operaciones de bloqueo y desbloqueo se realizan durante la operación de depósito.


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/** * 银行账户 * * @author 骆昊 * */
public class Account {
    private Lock accountLock = new ReentrantLock();
    private double balance; // 账户余额
    /**     * 存款     *     * @param money     *            存入金额     */
    public void deposit(double money) {
        accountLock.lock();
        try {
            double newBalance = balance + money;
            try {
                Thread.sleep(10); // 模拟此业务需要一段处理时间
            }
            catch (InterruptedException ex) {
                ex.printStackTrace();
            }
            balance = newBalance;
        }
        finally {
            accountLock.unlock();
        }
    }
    /**     * 获得账户余额     */
    public double getBalance() {
        return balance;
    }
}

Después de modificar el código de las tres formas anteriores, vuelva a escribir y ejecute el código de prueba Test01, y verá que el saldo final de la cuenta es de 100 yuanes. Por supuesto, también puede utilizar Semaphore o CountdownLatch para lograr la sincronización.

Supongo que te gusta

Origin blog.51cto.com/15061944/2593369
Recomendado
Clasificación