Javaインタビューの質問の全集(6)

Javaインタビューの質問の全集(6)

BaiyuITハハ

51.クラスExampleAはExceptionを継承し、クラスExampleBはExampleAを継承します。

次のコードスニペットがあります。


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

このコードを実行した結果はどうなりますか?

回答:出力:ExampleA。(Richter置換の原則[親タイプを使用できる場合はサブタイプを使用できます]によると、ExampleAタイプの例外を取得するcatchブロックは、tryブロックでスローされたExampleBタイプの例外をキャッチできます)

インタビューの質問-以下のコードの結果を教えてください。(この質問の出典は「JavaProgrammingThoughts」という本です)


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.リスト、セット、およびマップはコレクションインターフェイスから継承しますか?

回答:リストとセットは「はい」ですが、マップはそうではありません。Mapはキー値マッピングコンテナであり、ListやSetとは明らかに異なります。Setは分散要素を格納し、重複要素を許可しません(数学のセットにも同じことが言えます)。Listは線形構造のコンテナであり、数値に適しています。要素へのインデックスアクセス。

53. ArrayList、Vector、LinkedListのストレージパフォーマンスと特性を説明します。

回答:ArrayListとVectorはどちらも配列を使用してデータを保存します。この配列の要素数は、要素を追加および挿入するために保存される実際のデータよりも多くなります。どちらもシーケンス番号による要素の直接インデックス付けが可能ですが、要素の挿入には配列要素の移動などのメモリ操作が含まれるためです。データのインデックス作成は高速で、データの挿入は低速です。Vectorのメソッドは、同期された変更によりスレッドセーフなコンテナですが、パフォーマンスはArrayListのパフォーマンスよりも劣るため、Javaのレガシーコンテナになっています。LinkedListは、二重にリンクされたリストを使用してストレージを実現します(追加の参照を介してメモリ内の分散メモリセルを関連付け、シーケンス番号でインデックス付けできる線形構造を形成します。アレイの連続ストレージと比較して、このチェーンストレージ方式はメモリ使用率があります。より高い)、シーケンス番号でデータにインデックスを付けるには、順方向または逆方向のトラバースが必要ですが、データを挿入する場合は、このアイテムの前後のアイテムを記録するだけでよいため、挿入速度が速くなります。Vectorはレガシーコンテナ(Javaの初期バージョンで提供されていたコンテナ。さらに、Hashtable、Dictionary、BitSet、Stack、およびPropertiesはすべてレガシーコンテナです)に属しており、推奨されなくなりましたが、ArrayListとLinkedListedはどちらもスレッドセーフではないため、複数のスレッドが同じコンテナを操作するシナリオが発生した場合は、ツールコレクションのsynchronizedListメソッドを使用して、使用する前にスレッドセーフコンテナに変換できます(これは装飾モードのアプリケーションであり、既存のオブジェクトは別のオブジェクトに渡されます)クラスのコンストラクターに新しいオブジェクトを作成して、実装を強化します)。

補足:レガシーコンテナのPropertiesクラスとStackクラスには、重大な設計上の問題があります。Propertiesは、キーと値が両方とも文字列である特別なキーと値のペアのマッピングです。設計では、ハッシュテーブルと2つを関連付ける必要があります。汎用パラメータは文字列タイプに設定されていますが、Java APIのプロパティはHashtableを直接継承します。これは、明らかに継承の乱用です。ここでコードを再利用する方法は、Is-A関係ではなくHas-A関係である必要があります。一方、コンテナはツールクラスに属します。ツールクラス自体を継承することは間違ったアプローチです。ツールクラスを使用する最良の方法は、Has-A関係です。 (関連付け)または使用-関係(依存関係)。同じ理由で、StackクラスがVectorを継承することは正しくありません。サンのエンジニアもそのような低レベルのミスを犯し、人々を当惑させます。

54.コレクションとコレクションの違いは何ですか?

回答:コレクションは、Set、List、およびその他のコンテナの親インターフェイスであるインターフェイスです。コレクションは、コンテナの操作を支援する一連の静的メソッドを提供するツールクラスです。これらのメソッドには、コンテナの検索、並べ替え、スレッドの安全性などが含まれます。待つ。

55.要素にアクセスするときのList、Map、およびSetの3つのインターフェイスのそれぞれの特徴は何ですか?

回答:リストは特定のインデックスを持つ要素にアクセスし、要素が繰り返される可能性があります。セットは繰り返される要素を格納できません(要素が繰り返されるかどうかを区別するには、オブジェクトのequals()メソッドを使用します)。マップにはキーと値のペアのマッピングが格納され、マッピングの関係は1対1または多対1にすることができます。セットコンテナとマップコンテナには、ハッシュストレージとソートツリーに基づく2つの実装バージョンがあります。ハッシュストレージに基づくバージョン理論のアクセス時間の複雑さはO(1)ですが、ソートツリーバージョンに基づく実装は要素を挿入または削除する場合です。ソートツリーは、要素または要素のキーに従って構築され、ソートと重複排除の効果を実現します。

56. TreeMapとTreeSetは、並べ替え時に要素をどのように比較しますか?Collectionsユーティリティクラスのsort()メソッドはどのように要素を比較しますか?

回答:TreeSetでは、保存されたオブジェクトが属するクラスにComparableインターフェイスを実装する必要があります。このインターフェイスは、要素を比較するためのcompareTo()メソッドを提供します。要素が挿入されると、このメソッドが呼び出されて要素のサイズが比較されます。TreeMapでは、キーと値のペアのマッピングに格納されているキーに、キーに従って要素を並べ替えるためのComparableインターフェイスを実装する必要があります。Collectionsツールクラスのsortメソッドには、2つのオーバーロードされた形式があります。1つ目は、並べ替える受信コンテナに格納されているオブジェクトが要素比較を実現するためにComparableインターフェイスを実装する必要があり、2つ目はコンテナ内の要素を必要とする必須ではありません。比較可能である必要がありますが、2番目のパラメーターを渡す必要があります。パラメーターはComparatorインターフェースのサブタイプです(要素の比較を行うにはcompareメソッドを書き直す必要があります)。これは一時的に定義された並べ替えルールと同等です。実際、比較要素のサイズはインターフェースを介して注入されますアルゴリズムは、コールバックモード(Javaでの機能プログラミングのサポート)のアプリケーションでもあります。
例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]
    }
}

例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. Threadクラスのsleep()メソッドとオブジェクトのwait()メソッドの両方で、スレッドの実行を一時停止できます。これらの違いは何ですか。

回答:sleep()メソッド(sleep)は、スレッドクラス(Thread)の静的メソッドです。このメソッドを呼び出すと、現在のスレッドが指定された時間実行を一時停止し、他のスレッドに実行機会(CPU)を与えますが、オブジェクトロックは維持されます。したがって、スリープ時間が経過すると自動的に再開します(スレッドは準備完了状態に戻ります。質問66のスレッド状態遷移図を参照してください)。wait()はObjectクラスのメソッドです。オブジェクトのwait()メソッドを呼び出すと、現在のスレッドはオブジェクトのロックを放棄し(スレッドは実行を一時停止し)、オブジェクトの待機プールに入ります。オブジェクトのnotify()メソッド(またはnotifyAll)のみが呼び出されます。 ()メソッド)は、待機プール内のスレッドをウェイクアップしてロックプール(ロックプール)に入ることができます。スレッドがオブジェクトのロックを再取得すると、準備完了状態に入ることができます。

補足:多くの人々は、プロセスとは何か、スレッドとは何かについてまだ漠然としていて、マルチスレッドプログラミングが必要な理由を特に理解していません。簡単に言えば、プロセスは、特定のデータセットで実行中のアクティビティに関する特定の独立した機能を備えたプログラムであり、リソースの割り当てとスケジューリングのためのオペレーティングシステムの独立したユニットです。スレッドは、CPUのスケジューリングとディスパッチの基本であるプロセスのエンティティです。ユニットは、プロセスよりも小さく、独立して実行できる基本ユニットです。スレッドの分割スケールはプロセスの分割スケールよりも小さいため、マルチスレッドプログラムの同時実行性は高くなります。通常、プロセスは実行中に独立したメモリユニットを持ち、スレッドはメモリを共有できます。マルチスレッドプログラミングは通常、パフォーマンスとユーザーエクスペリエンスを向上させることができますが、マルチスレッドプログラムは、より多くのCPUリソースを消費する可能性があるため、他のプログラムには適していません。もちろん、スレッドの数が多いほどプログラムのパフォーマンスが向上するわけではありません。スレッド間のスケジューリングと切り替えによってCPU時間が無駄になるためです。現在、非常にファッショナブルなNode.jsは、シングルスレッドの非同期I / O作業モードを採用しています。

58.スレッドsleep()メソッドとyield()メソッドの違いは何ですか?

回答:
①sleep()メソッドは、他のスレッドに実行の機会を与えるときにスレッドの優先度を考慮しないため、優先度の低いスレッドに実行の機会を与えます。yield()メソッドは、同じ優先度以上のスレッドにのみ与えます。実行する機会を利用してください;
②sleep()メソッドを実行するとスレッドはブロック状態になり、yield()メソッドを実行すると準備完了状態になります;
③sleep()メソッドステートメントはInterruptedExceptionをスローし、yield()このメソッドは例外を宣言しません。④sleep
()メソッドは、yield()メソッド(オペレーティングシステムのCPUスケジューリングに関連)よりも移植性に優れています。

59.スレッドがオブジェクトの同期メソッドAに入った後、他のスレッドはこのオブジェクトの同期メソッドBに入ることができますか?

回答:いいえ。他のスレッドはオブジェクトの非同期メソッドにのみアクセスでき、同期メソッドは入力できません。非静的メソッドの同期モディファイアでは、メソッドの実行時にオブジェクトのロックを取得する必要があるため、オブジェクトロックが解除されたことを示すためにAメソッドに入った場合、Bメソッドに入ろうとしているスレッドは、ロックプールのみを待機できます(待機していないことに注意してください)。プール内の待機中のオブジェクトのロック。

60.スレッドの同期とスレッドのスケジューリングに関連する方法を教えてください。

回答:

  • wait():スレッドを待機(ブロック)状態にし、保持しているオブジェクトのロックを解除します。
  • sleep():実行中のスレッドをスリープ状態にします。これは静的メソッドであり、InterruptedExceptionを処理するためにこのメソッドを呼び出します。
  • notify():待機状態のスレッドをウェイクアップします。もちろん、このメソッドを呼び出すと、待機状態のスレッドを正確にウェイクアップすることはできませんが、JVMはウェイクアップするスレッドを決定し、優先度とは関係ありません。
  • notityAll():待機状態のすべてのスレッドをウェイクアップします。このメソッドは、オブジェクトのロックをすべてのスレッドに与えるわけではありませんが、競合を許可します。ロックを取得したスレッドのみが準備完了状態に入ることができます。

ヒント:Javaマルチスレッドとコンカレントプログラミングについては、他の記事「Javaコンカレントプログラミングの概要と考え方」を読むことをお勧めします。
補足:Java 5は、ロックインターフェイスを介して明示的なロックメカニズム(明示的なロック)を提供します。これにより、スレッドの柔軟性と調整が強化されます。Lockインターフェイスは、ロック(lock())およびロック解除(unlock())のメソッドを定義します。また、スレッド間の通信用のConditionオブジェクトを生成するnewCondition()メソッドも提供します。さらに、Java5はシグナルも提供します。セマフォ、セマフォは、共有リソースにアクセスするスレッドの数を制限するために使用できます。リソースにアクセスする前に、スレッドはセマフォの権限を取得する必要があります(Semaphoreオブジェクトのacquire()メソッドを呼び出します)。リソースへのアクセスが完了した後、スレッドはセマフォに権限を返す必要があります(Semaphoreオブジェクトのrelease()メソッドを呼び出します)。 。

次の例は、同期メカニズムを使用せず、同期メカニズムを使用せずに、1元を銀行口座に同時に預ける100スレッドの実行を示しています。
銀行口座カテゴリ:


/** * 银行账户 * @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;
    }
}

スレッドクラスの保存:


/** * 存钱线程 * @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);
    }
}

テストカテゴリ:


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());
    }
}

同期がない場合、実行結果は通常、口座残高が10元未満であることを示します。この状況の理由は、スレッドAが1元をデポジットしようとすると、別のスレッドBもデポジット方法を入力できるためです。 、スレッドBによって読み取られたアカウント残高は、スレッドAが1元をデポジットする前のアカウント残高であるため、元の残高0に1元も追加されます。同様に、スレッドCも同様の処理を行います。したがって、最後の100スレッドの実行が終了すると、アカウントの残高は100元になると予想されますが、実際の結果は通常10元(おそらく1元)未満です。この問題の解決策は同期です。スレッドが銀行口座にお金を預けるとき、口座をロックし、操作が完了した後にのみ他のスレッドの操作を許可する必要があります。コードには次の調整スキームがあります。

  • 銀行口座の預け入れ方法の同期キーワード

/** * 银行账户 * @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;
    }
}

スレッドがdepositメソッドを呼び出すときに銀行口座を同期する


/** * 存钱线程 * @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); 
        }
    }
}

Java 5に示されているロックメカニズムにより、銀行口座ごとにロックオブジェクトが作成され、デポジット操作中にロックおよびロック解除操作が実行されます。


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;
    }
}

上記の3つの方法でコードを変更した後、テストコードTest01を書き直して実行すると、最終的なアカウント残高が100元であることがわかります。もちろん、SemaphoreまたはCountdownLatchを使用して同期を実現することもできます。

おすすめ

転載: blog.51cto.com/15061944/2593369
おすすめ