大數據技術之_31_Java 面試題_02_== 和 equals 有什麼區別 + String 相關 + 多態 + 傳值 + static 加載機制 + 線程

一、== 和 equals 有什麼區別?二、爲何須要同時覆寫 hashCode 和 equals 方法?三、爲何用 eclipse 重寫 hashCode 方法,有 31 這個數字?四、String 相關五、多態六、傳值七、static 加載機制八、談談你對 HashMap 中 put/get 方法的認識?若是瞭解再談談 HashMap 的擴容機制?默認大小是多少?什麼是負載因子?什麼是吞吐臨界值?JDK1.7 版本爲例九、請問 ArrayList/LinkedList/Vector 的區別?談談你的理解?ArrayList 底層是什麼?擴容機制?Vector 和 ArrayList 的最大區別?JDK1.7十、線程java


程序員級別:碼龍 > 碼神 > 碼農 > 碼畜
學生級別:學神 > 學霸 > 學渣 > 學弱程序員

IT/DT 是腦力密集型的高智商行業。web

反覆的強化,反覆的強化,反覆的強化。
刻意的練習,刻意的練習,刻意的練習。編程

一、== 和 equals 有什麼區別?

== 既能夠比較基本類型也能夠比較引用類型。對於基本類型就是比較值,對於引用類型就是比較內存地址(值)(本質上來講也是值)。

equals 的話,它是屬於 java.lang.Object 類裏面的方法,在源代碼的 149 行,若是該方法沒有被重寫過默認也是 ==,咱們能夠看到 String 類的 equals 方法是被重寫過的,並且 String 類在平常開發中用的比較多,長此以往,造成了 equals 是比較值的錯誤觀點(是由於覆寫了 equals 方法才比較值)。

具體要看這有沒有重寫 Object 的 hashCode 方法和 equals 方法來判斷。

-----------------------------------------------------------------

以 Person 爲例,什麼時候須要重寫 equals()?

當一個類有本身特有的「邏輯相等」概念,當改寫 equals() 的時候,老是要改寫 hashCode(),根據一個類的 equals 方法(重寫後),兩個大相徑庭的實例有可能在邏輯上是相等的,可是,根據 Object.hashCode 方法,它們僅僅是兩個對象。

所以,違反了 「相等的對象必須具備相等的散列碼」。

結論:重寫 equals 方法的時候通常都須要同時重寫 hashCode 方法。

示例代碼以下:數組

package com.atguigu.test;

import java.util.HashSet;
import java.util.Set;

import com.atguigu.entities.Person;

public class TestEquals {
    public static void main(String[] args) {
        // String s1 = new String("abc");
        // String s2 = new String("abc");
        // System.out.println(s1 == s2); // false
        // System.out.println(s1.equals(s2));
        //
        // Set<String> set01 = new HashSet<String>();
        // set01.add(s1);
        // set01.add(s2);
        // System.out.println(s1.hashCode() + "\t" + s2.hashCode());
        // System.out.println(set01.size());

        System.out.println("================================");

        // 兩個大相徑庭的實例,有可能在邏輯上是相等的
        // (假設項目須要,自定義邏輯相等的概念,好比只要 name 屬性值一致咱們就是認爲是同一個對象)
        // 即只要 name 屬性值一致,用 equals 比較獲得 true,就是同一個對象,這是開發人員本身定製的業務規則,可是JVM 不認,由於 JVM 只認識 hashCode
        Person p1 = new Person("abc");
        Person p2 = new Person("abc");
        System.out.println(p1 == p2); // false
        System.out.println(p1.equals(p2));
        Set<Person> set02 = new HashSet<Person>();
        set02.add(p1);
        set02.add(p2);
        System.out.println(p1.hashCode() + "\t" + p2.hashCode());
        System.out.println(set02.size());
    }
}

二、爲何須要同時覆寫 hashCode 和 equals 方法?

答:由於假如一個類被用在集合類中,在該集合類中判斷是否爲同一個對象,判斷的是 hashCode 的值(即地址值)。即僅僅覆寫 equals 方法是不夠的!多線程

三、爲何用 eclipse 重寫 hashCode 方法,有 31 這個數字?

  計算機的乘法涉及到移位計算。當一個數乘以 2 時,就直接拿該數左移一位便可!選擇 31 緣由是由於 31 是一個素數!所謂素數:質數又稱素數(在一個大於 1 的天然數中,除了 1 和此整數自身外,無法被其餘天然數整除的數)。app

  在存儲數據計算 hash 地址的時候,咱們但願儘可能減小有一樣的 hash 地址,所謂 「衝突」。dom

  由於任何數 n * 31 就能夠被 JVM 優化爲 (n << 5) -n,移位和減法的操做效率要比乘法的操做效率高的多,對左移虛擬機裏面都有作相關優化,而且 31 只佔用 5 bits!eclipse

四、String 相關

示例代碼以下:ide

package com.atguigu.test;

public class TestString {
    public static void main(String[] args) {
        String s1 = new String("abc"); // 生成了兩個對象:一個在字符串常量池中,一個在堆中被 s1 指向
        String s2 = "abc"// s2 指向字符串常量池的 "abc"
        String s3 = new String("abc"); // 生成了一個對象:s3 指向堆中另外一個 "abc"

        System.out.println(s1 == s2); // false
        System.out.println(s1 == s3); // false
        System.out.println(s2 == s3); // false

        System.out.println("====================");

        // String s2 = "abc"; // s2 指向字符串常量池的 "abc"
        // String s1 = new String("abc"); // 生成了一個對象:s2 指向堆中一個 "abc"
        // String s3 = new String("abc"); // 生成了一個對象:s3 指向堆中另外一個 "abc"
        //
        // System.out.println(s1 == s2); // false
        // System.out.println(s1 == s3); // false
        // System.out.println(s2 == s3); // false
        //
        // System.out.println("====================");

        /*
         * 返回字符串對象的規範化表示形式。 一個初始爲空的字符串池,它由類 String 私有地維護。 
         * 當調用 intern 方法時,若是池已經包含一個等於此 String 對象的字符串(用  equals(Object) 方法肯定),
         * 則返回池中的字符串。不然,將此 String 對象添加到池中,並返回此 String 對象的引用。 
         * 它遵循如下規則:對於任意兩個字符串  s 和  t,當且僅當 s.equals(t) 爲 true 時,s.intern() == t.intern() 才爲 true。
         * 全部字面值字符串和字符串賦值常量表達式都使用 intern 方法進行操做。
         * 字符串字面值在 Java Language Specification 的 §3.10.5 定義。 
         * 返回:一個字符串,內容與此字符串相同,但必定取自具備惟一字符串的池。
         */

        System.out.println(s1 == s1.intern()); // false
        System.out.println(s2 == s2.intern()); // true
        System.out.println(s1.intern() == s2.intern()); // true    簡言之:intern() 表示找池中常量的地址

        System.out.println("====================");

        String s4 = "java";
        String s5 = "ja";
        String s6 = "va";
        System.out.println(s4 == "java");       // true     常量找池(池對象)
        System.out.println(s4 == (s5 + s6));    // false    拼接找堆(會在堆中生成新的對象,堆對象)
        System.out.println(s4 == "ja" + s6);    // false
    }
}

五、多態

什麼是多態?
Java 裏經過方法重載和方法重寫來體現多態是否正確?答:錯誤,方法重載跟多態沒有任何關係。
多態是編譯時行爲仍是運行時行爲?答:運行時行爲。由於只有在實際調用運行時才能肯定具體的對象。

示例代碼以下:

package com.atguigu.test;

import java.util.Random;

interface Animal {
    public void eat();
}

class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("dog eat bone---111");
    }
}

class Cat implements Animal {
    @Override
    public void eat() {
        System.out.println("cat eat fish---222");
    }
}

class Sheep implements Animal {
    @Override
    public void eat() {
        System.out.println("sheep eat grass---333");
    }
}

public class TestPolymorphism {

    public static Animal getInstance(int key) {
        Animal result = null;

        switch (key) {
        case 0:
            result = new Dog();
            break;
        case 1:
            result = new Cat();
            break;
        default:
            result = new Sheep();
            break;
        }
        return result;
    }

    public static void main(String[] args) {
        Animal animal = TestPolymorphism.getInstance(new Random().nextInt(3));
        animal.eat();
    }
}

六、傳值

示例代碼以下:

package com.atguigu.test;

import com.atguigu.entities.Person;

public class TestTransferValue {

    public void changeValue1(int age) {
        age = 30;
    }

    public void changeValue2(Person person) {
        person.setPersonName("xxx");
    }

    public void changeValue3(String str) {
        str = "xxx";
    }

    public static void main(String[] args) {

        TestTransferValue test = new TestTransferValue();
        int age = 20;
        test.changeValue1(age);
        System.out.println("age---" + age); // age---20

        Person person = new Person("abc");
        test.changeValue2(person);
        System.out.println("personName---" + person.getPersonName()); // personName---xxx

        String str = "abc";
        test.changeValue3(str);
        System.out.println("String---" + str); // String---abc
    }
}

七、static 加載機制

示例代碼以下:

package com.atguigu.test;

class Father {
    public Father() {
        System.out.println("111111");
    }

    { // 非靜態代碼塊每次實例化的時候都加載
        System.out.println("222222");
    }

    static { // 靜態的東西只加載一次
        System.out.println("333333");
    }
}

class Son extends Father {
    public Son() {
        System.out.println("444444");
    }

    { // 非靜態代碼塊每次實例化的時候都加載
        System.out.println("555555");
    }

    static { // 靜態的東西只加載一次
        System.out.println("666666");
    }
}

public class TestStaticSeq {
    public static void main(String[] args) {
        // 特別注意:最開始加載靜態的內容,有無如下建立對象都會加載(由於靜態內容屬於類的東西)
        new Son();
        System.out.println("-------------------");
        new Son();
        System.out.println("-------------------");
        new Father();
    }
}

輸出結果以下以下:

333333
666666
222222
111111
555555
444444
-------------------
222222
111111
555555
444444
-------------------
222222
111111

口訣static 加載機制:由父到子,靜態先行,子方法先行,非靜態代碼塊先於構造方法,構造方法最後

八、談談你對 HashMap 中 put/get 方法的認識?若是瞭解再談談 HashMap 的擴容機制?默認大小是多少?什麼是負載因子?什麼是吞吐臨界值?JDK1.7 版本爲例

1、HashSet 底層是採用 HashMap 實現
2、集合裏面放置的永遠是對象的引用而不是對象自己
3、當你在 HashSet 裏 add 對象的時候,實際是 HashMap 裏面 put 了 key-value 鍵值對,其中 key 就是你 add 進來的對象,value 是一個固定的 Object 常量
4、HashMap 底層是個 Entry 類型的,名字叫 table 的數組
5、put:當程序試圖將一個 key-value 對放入 HashMap 中時,程序首先根據該 key 的 hashCode() 返回值決定該 Entry 的存儲位置:若是兩個 Entry 的 key 的 hashCode() 返回值相同,那它們的存儲位置相同。若是這兩個 Entry 的 key 經過 equals 比較返回 true,則新添加 Entry 的 value 將覆蓋集合中原有 Entry 的 value,但 key 不會覆蓋。若是這兩個Entry 的 key 經過 equals 比較返回 false,則新添加的 Entry 將與集合中原有 Entry 造成 Entry 鏈,並且新添加的 Entry 位於 Entry 鏈的頭部--具體說明繼續看 addEntry() 方法的說明。

順便複習下 hashCode、equals、HashSet、HashMap 之間到底有什麼樣的關係?

九、請問 ArrayList/LinkedList/Vector 的區別?談談你的理解?ArrayList 底層是什麼?擴容機制?Vector 和 ArrayList 的最大區別?JDK1.7

ArrayList 定義

ArrayList/LinkedList/Vector的區別

十、線程

java8 中的 JUC = java.util.concurrent

題目1:3 個售票員賣出 30 張票 賣票。
示例代碼以下:

package com.atguigu.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; // 可重入鎖

/**
 * 題目1:3 個售票員賣出 30 張票 賣票 
 * synchronized: 鎖提供了對共享資源的獨佔訪問,一次只能有一個線程得到鎖,對共享資源的全部訪問都須要首先得到鎖。
 * 
 * 1    線程     操做   資源類 
 * 2    高內聚+低耦合
 * 
 * java8 中的 JUC = java.util.concurrent
 */

public class ThreadDemo01 {

    public static void main(String[] args) {

        Ticket ticket = new Ticket();

        // 實際開發中使用 匿名內部類 的方式(代碼冗餘度降低)
//        new Thread(new Runnable() {
//            @Override
//            public void run() {
//                for (int i = 1; i <= 40; i++) {
//                    ticket.sale();
//                }
//            }
//        }, "AA").start();

        // java 8 中使用 lambda 表達式代替 匿名內部類
        new Thread(() -> {
            for (int i = 1; i <= 40; i++) {
                ticket.sale();
            }
        }, "AA").start();

        new Thread(() -> {
            for (int i = 1; i <= 40; i++) {
                ticket.sale();
            }
        }, "BB").start();

        new Thread(() -> {
            for (int i = 1; i <= 40; i++) {
                ticket.sale();
            } // 聯想到 Scala 的 map 操做
        }, "CC").start();
    }
}

class Ticket implements Runnable {

    private int number = 30;

    private Lock lock = new ReentrantLock(); // Lock 代替 synchronized,但 Lock 更強大

    public void sale() {
        lock.lock();
        try {
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "\t" + (number--) + "\t 還剩下:" + number);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub

    }
}

多線程的三種方式:

  • 第一種:Ticket extend Thread {},此法最搓,強烈不推薦!由於 Java 中要少用繼承,繼承是很寶貴的資源,儘可能要面向接口編程。
  • 第二種:Ticket implements Runnable {} 實現 run() 方法,面向接口編程。不夠好,使得 Ticket 和 Runnable 有關係了,沒有實現 高內聚+低耦合。
  • 第三種:使用 java.util.concurrent.locks.Lock; (JUC 中的 Lock 接口,實現使用匿名內部類的方式),很是好,實現了 高內聚+低耦合+低冗餘度。

題目2:兩個線程對一個初始值爲零的變量操做,實現一個線程加一,另外一個線程減一,來 10 輪。
示例代碼以下:

package com.atguigu.thread;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 題目2:兩個線程對一個初始值爲零的變量操做,實現一個線程加一,另外一個線程減一,來 10 輪
 * 
 * 1 線程   操做   資源類 
 * 2 高內聚+低耦合
 */

public class ThreadDemo02 {

    public static void main(String[] args) {

        // 新建資源類對象
        ShareData sd = new ShareData();

        // 線程操做資源類的方法
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    Thread.sleep(200);
                    sd.increment();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    Thread.sleep(300);
                    sd.decrement();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    Thread.sleep(400);
                    sd.increment();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    Thread.sleep(500);
                    sd.decrement();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();
    }
}

// 資源類
class ShareData {
    private int number = 0;
    private Lock lock = new ReentrantLock(); // Lock 取代了  synchronized
    private Condition condition = lock.newCondition(); // Condition 取代了 wait,notify,notifyAll

    public void increment() throws InterruptedException {
        lock.lock();
        try {
            while (number != 0) {
                condition.await(); // this.wait();
            }
            // 幹活
            ++number;
            System.out.println(Thread.currentThread().getName() + "\t" + number);
            // 通知喚醒
            condition.signalAll(); // this.notifyAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number == 0) {
                condition.await(); // this.wait();
            }
            // 幹活
            --number;
            System.out.println(Thread.currentThread().getName() + "\t" + number);
            // 通知喚醒
            condition.signalAll(); // this.notifyAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

//    public synchronized void increment() throws InterruptedException { // 判斷
//        while (number != 0) {
//            this.wait(); // A C
//        }
//        // 幹活 
//        ++number;
//        System.out.println(Thread.currentThread().getName() + "\t" + number); 
//        // 通知喚醒
//        this.notifyAll();
//    }
//
//    public synchronized void decrement() throws InterruptedException {
//        while (number == 0) {
//            this.wait();
//        }
//        --number;
//        System.out.println(Thread.currentThread().getName() + "\t" + number);
//        this.notifyAll();
//    }
}

注意:資源類中要用 where 做爲多線程的判斷,不能用 if,這樣能避免線程的虛假喚醒。

題目3:8鎖
示例代碼以下:

package com.atguigu.thread;

import java.util.concurrent.TimeUnit;

/**
 * 8鎖
 * 1    一部手機,正常訪問,先打印蘋果仍是Android?答:先蘋果再Android
 * 2    新增 TimeUnit,先打印蘋果仍是Android?答:先蘋果再Android
 * 3    新增 hello 方法,先打印蘋果仍是hello?答:先hello再蘋果
 * 
 * 4    兩部手機,先打印蘋果仍是Android?答:先Android再蘋果
 * 
 * 5    兩個靜態同步方法,一部手機,先打印蘋果仍是Android?答:先蘋果再Android
 * 6    兩個靜態同步方法,兩部手機,先打印蘋果仍是Android?答:先蘋果再Android
 * 
 * 7    1個靜態同步方法,1個普通同步方法,一部手機,先打印蘋果仍是Android?答:先Android再蘋果
 * 8    1個靜態同步方法,1個普通同步方法,兩部手機,先打印蘋果仍是Android?答:先Android再蘋果
 * 
 * 一個對象裏面若是有多個 synchronized 方法,某一個時刻內,只有一個線程去調用其中的一個 synchronized 方法了,其它的線程都只能等待,
 * 換句話說,
 * 某一個時刻內,只能有惟一一個線程去訪問這些 synchronized 方法。
 * 
 * 鎖的是當前對象 this,被鎖定後,其它的線程都不能進入到當前對象的其它的 synchronized 方法
 * 
 * 加個普通方法後發現和同步鎖無關
 * 
 * 換成兩個對象後,不是同一把鎖了,狀況馬上變化。
 * 
 * 都換成靜態同步方法後,狀況又變化
 * 
 * 全部的非靜態同步方法用的都是同一把鎖--實例對象自己,也就是說若是一個實例對象的非靜態同步方法獲取鎖後,--鎖的是當前對象 this
 * 該實例對象的其餘非靜態同步方法必須等待獲取鎖的方法釋放鎖後才能獲取鎖,
 * 但是別的實例對象的非靜態同步方法由於跟該實例對象的非靜態同步方法用的是不一樣的鎖,
 * 因此毋須等待該實例對象已獲取鎖的非靜態同步方法釋放鎖就能夠獲取他們本身的鎖。
 * 
 * 全部的靜態同步方法用的也是同一把鎖--類對象自己,這兩把鎖是兩個不一樣的對象,--鎖的是當前對象的模板 class
 * 因此靜態同步方法與非靜態同步方法之間是不會有競態條件的。
 * 可是一旦一個靜態同步方法獲取鎖後,其餘的靜態同步方法都必須等待該方法釋放鎖後才能獲取鎖,
 * 而不論是同一個實例對象的靜態同步方法之間,仍是不一樣的實例對象的靜態同步方法之間,只要它們同一個類的實例對象!

 */

public class ThreadDemo03 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
            try {
                phone.getIOS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "AA").start();

        new Thread(() -> {
            try {
                // phone.getAndroid();
                // phone.getHello();
                phone2.getAndroid();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "BB").start();
    }
}

class Phone {
    public static synchronized void getIOS() throws Exception {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("-----getIOS");
    }

    public synchronized void getAndroid() throws Exception {
        System.out.println("-----getAndroid");
    }

    public void getHello() {
        System.out.println("-----getHello");
    }
}

Linux 下查詢 java 進程的個數:top -H -p {pid} 或者 ps huH p {PID} | wc -l

相關文章
相關標籤/搜索