1. 程式人生 > >"Java基礎"-Java,Android面試必問部分

"Java基礎"-Java,Android面試必問部分

關於文章內容:

大家好,今天我打算整理並總結關於JAVA,Android的相關方面的技能點,主要分為:

  • 1.java基礎板塊;
  • 3.andoroid基礎板塊;
  • 4.android高階板塊.

如果大家認真掌握好,那麼你就相當於有了兩年以上的開發經驗,拿到12k以上的薪水是沒什麼問題的,廢話少說,現在開始我們的第一部分java基礎面試點部分.

1.基礎部分

一.面向物件的思想

特徵:

1.繼承
    從父類(超類,基類)繼承得到之類(派生類),讓軟體有了延續性,是封裝程式中可變因素的重要手段.
2.封裝
    1.把資料和操作資料的方法封裝起來,對資料的訪問只提供介面
    2.面向物件本質:將現實世界描繪成完全自治,封閉的物件
    3.我們在類中寫方法就是對實現細節的一種封裝,編寫類就是對資料和資料操作的封裝
    結論:封裝一切可以封裝的東西,只對外提供最簡單的介面

3.多型
    1.不同的子類物件對同一個訊息做出不同的響應,而同樣的物件(分類)呼叫同樣的方法,能夠做不同的事情:爸爸喊一聲,開始工作:打兒子就開拖拉機耕地,二兒子開飛機撒農藥.
    2.多型性的分類:
        1.編譯時(前繫結)
            方法過載
        2.執行時(後繫結),是面向物件的精髓
            方法重寫覆蓋
            1.A訪問B提供的服務,B有多鍾提供服務的方式,對A是透明的.
    3.實現多型要做的兩件事:
        1.方法重寫,覆蓋
        2.物件的造型
            父類引用指向子類物件,同樣的引用呼叫同樣的方法就會根據子類物件的不同表現出不同的行為
    4.抽象
        1.是將一類物件的共同特徵而而總結出來的構造類,包括:資料抽象,行為抽象.
        2.抽象只關注屬性和行為,不關注行為細節(無方法體)

開心小案例
程式碼:

    interface Fu {
        void doTask();
    }

     class Zi implements Fu {
        @Override
        public void doTask() {
            System.out.println("我是大兒子,拖拉機播種");
        }
    }
     class Zi1 implements Fu{
        @Override
        public void doTask() {
            System.out.println("我是二哥,開飛機噴農藥"
); } } class Zi2 implements Fu{ @Override public void doTask() { System.out.println("我是三弟,遠端控制蔬菜棚溫度"); } } class Test{ public static void main(String[] args) { Fu z = new Zi(); Fu z1 = new Zi1(); Fu z2 = new Zi2(); TallTool.addClild(z); TallTool.addClild(z1); TallTool.addClild(z2); TallTool.tall(); } }

其他包的工具類:

import java.util.ArrayList;

class TallTool {
    static ArrayList<Fu> l = new ArrayList();

    public static void addClild(Fu f) {
        l.add(f);
    }

    public static void tall() {
        for (Fu f : l) {
            //多型指向子類物件
            f.doTask();
        }
    }
}

列印結果:

我是大兒子,拖拉機播種
我是二哥,開飛機噴農藥
我是三弟,遠端控制蔬菜棚溫度

二.多型

1.實現多型的機制

1.靠的是父類或介面定義的引用變數指向子類或者具體實現類物件
2.程式呼叫的方法在執行期才動態繫結,引用的是:指向的具體事例物件的方法(記憶體中執行的物件的方法),而不是引用變數型別中定義的方法.

2.多型的案例:

1.口訣的解析:“成員變數,靜態方法看左邊;非靜態方法:編譯看左邊,執行看右邊。”
2.父類變數引用子類物件時:Fu f = new Zi();
    1.在這個引用變數f指向的物件中,他的成員變數和靜態方法與父類是一致的;
    2.他的非靜態方法,在編譯時是與父類一致的,執行時卻與子類一致(發生了複寫)。

程式碼例子:

    class Fu {

        int num = 5;//成員變數

        static void method_static() {

            System.out.println("fu static");

        }

        void method() {

            System.out.println("fu method");

        }

    }

    class Zi extends Fu {

        int num = 8;

        static void method_static() {

            System.out.println("zi method_static");

        }

        void method() {

            System.out.println("zi method");

        }

    }

    class DuoTaiDemo {  

        public static void main(String[] args) {    

            Fu f = new Zi();    

            System.out.println(f.num);//與父類一致   

            f.method_static();//與父類一致   

            f.method();//編譯時與父類一致,執行時與子類一致  

            Zi  z = new Zi();   

            System.out.println(z.num);  

            z.method_static();  

            z.method3();    

        }   

    }

輸出結果:

5
fu method_static
zi method
8
zi method_static
zi method 

分析:

Fu f = new Zi();

瞭解變數f到底是什麼,把這句子分2段:
1.Fu f;這是宣告一個變數f為Fu這個類,那麼知道了f肯定是Fu類。
2.f=new Zi();中建立一個子類物件賦值給了f,結果是什麼?即:擁有了"被Zi類函式覆蓋"後的Fu類物件"f",也就是說f不可能變成其他比如Zi這個類.

對於成員變數:

f所代表的是函式被複寫後(多型的意義)的一個Fu類,而Fu類原來有的成員變數(不是成員函式不可能被複寫)沒有任何變化.
結論1:成員變數:編譯和執行都看Fu。

對於成員方法:

f的Fu類函式被複寫了.
獲得結論2:非靜態方法:編譯看Fu,執行看Zi

對於靜態方法:編譯和執行都看Fu

很簡單,首先我們要理解靜態情況下發生了什麼?

靜態時,Fu類的所有函式跟隨Fu類載入而載入了。

Fu類的靜態函式(是先於物件建立之前就存在了,無法被後出現的Zi類物件所複寫的,所以沒發生複寫.
結論3:靜態方法:編譯和執行都看Fu。

三.異常的處理

1.種類

按異常需要處理的"時機"分類:
1.編譯時異常,也叫檢測異常:CheckedException.
    1.只有java提供檢測異常,認為檢測異常都是可以被處理的異常,所以必須"顯式"處理"檢測異常"
    2.如果不處理編譯就會出錯,體現了java的設計哲學:沒有完善掉錯誤的程式碼就沒有執行的機會
    3.檢測異常處理的兩種方法:
        1.知道怎麼處理:用try...catch處理
        2.不知道怎麼處理:方法丟擲處理
2.執行時異常,RuntimeException.
    1.程式碼執行的時候才發現的異常,通常不需要try...catch.
    2.如:除數為0,陣列下標越界等.
    3.因為產生的頻繁,處理麻煩,如果都處理對可讀性和執行效率影響太大,當然如果有需求也可以捕獲.

2.catch和finally的返回值

public int result(){ 
    try { 
        int a = 1/0; 
        return 1; 
    } catch (Exception e) { 
        return 2; 
    }finally{ //int b = 1/0; 
        //int j = 3/0;//再加上這個就輸出異常了
        return 3; 
    }

列印結果是 3:

解析:

普通理解catch再到finally:第3行除數是0,return 1就執行不到,來到catch 然後return 2.結束

實際的情況是:異常機制有一個原則:如果catch裡面有return或者諸如break等讓函式終止的,而且finally裡面也有return,那麼必須先執行fianlly裡面的程式碼塊後才確認

所以程式碼讀到catch的return的時候就跳到了finally的return 3,catch裡不會返回.
    //普通情況1:
    public static int getResult2() {
        int num = 0;
        try {
            int i = 2 / 0;
            num = 1;
            return num--;
        } catch (Exception e) {
             num = num+1;   //先得1
        }
        finally {
             num = num-2;   //再變成1-2=-1;
        }
        return num;         //返回-1;
    }
    //普通情況2:
    public static int getResult2() {
        int num = 0;
        try {
            int i = 2 / 0;
            num = 1;
            return num--;
        } catch (Exception e) {
            return num+1;   //已經返回了1
        }
        finally {
             num = num-2;   //然後再變:1-2 = -1;但上面已經返回了1;
        }

    }

四.資料型別

1.佔的位元組

型別 位元組
byte 1
short 2
int 4
long 8
float 4
double 8
char 2
boolean 1

2.String屬於嗎?能繼承嗎?

1.不屬於基本資料型別,是引用型別,底層是char陣列實現.

2.是被final修飾的類,所以不能被繼承;

五.IO

1.幾種型別

1.位元組流
    繼承於InputStream和OutputStream基類.
    具體實現(用於檔案):FileInputStream,FileOutputStream

2.字元流
    繼承於Reader和Writer基類
    具體實現:FileReader,FileWriter

2.位元組流怎麼轉成字元流

位元組輸入流轉字元輸入流通過InputStreamReader實現,建構函式傳入(inputStream)
位元組輸出流轉字元輸出流通過OutputStreamWriter實現,建構函式傳入(OutputStream)

3.如何將java物件序列化到檔案
1.如Person類,Student類

    public class Demo {

        public static void main(String[] args)throws Exception{
            //放入
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:/obj"));
            oos.writeObject(new User("Hello JAVA", 99));
            oos.close();
            //讀取
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/obj"));
            User user = (User) ois.readObject();
            System.out.println(user);
            ois.close();
        }
    }
    //要實現Serializable,否則執行出現異常
    class User implements Serializable {
        private String name;

        private int score;

        User(String name, int score) {
            this.name = name;
            this.score = score;
        }

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", score=" + score +
                    '}';
        }
    }

拓展:
Serializable和Parcelable的區別

1.作用:
    Serializable:儲存到本地檔案,資料庫,網路流,或者程式間.
    Parcelable:因Serializable效率過慢而設計,為在不同安卓程式AIDL高效傳輸而設計,僅在"記憶體中"存在,是通過IBinder通訊的訊息的載體.
2.效率和選擇:
    1.記憶體間傳輸資料優先Parcelable:效能更好,記憶體開銷少.
    2.需要保持或者網路傳輸優先Serializable:因為能持久化資料,而Parcelable不推薦持久化

六.集合

1.HashMap排序上機題

1.User類有name,age屬性
2.有一個HashMap<Integer,User>集合存放多個使用者
3.寫一個方法實現對HashMap裡面內容進行排序,方法接收HashMap<Integer,User>為形參,返回值也是這個
4.要求進行age倒序,並且key,value鍵值保持原來的對應.

方法1(萬能).

    public class Demo {

        public static void main(String[] args)throws Exception {
            //HashMap如果鍵是用整形的數字(1,2,3)就可以按鍵的順序排,如果按字元型的數字("1","2","3")就不規則排,TreeMap技能排序整,也能排序字元型的鍵
            HashMap<Integer,User> hm = new HashMap<>();
            hm.put(1, new User("Andy", 24));
            hm.put(5, new User("Josh", 22));
            hm.put(3, new User("Lucy", 23));
            hm.put(8, new User("Lucy", 26));
            hm.put(4, new User("Lucy", 25));
            hm.put(2, new User("Lucy", 20));
            System.out.println("--------------------排序前----------------------");
            System.out.println(hm);
            HashMap<Integer, User> hmsort = getSortHashMap(hm);
            System.out.println("--------------------排序後----------------------");
            System.out.println(hmsort);
        }

        /**
         * 處理邏輯:
         *      1.對比User裡的age,然後用方法進行排序,
         *      2.然後放入LinkedHashMap
         *      3.然後放入LinkedHashMap因為是其之類,直接返回即可
         */
        private static HashMap<Integer, User> getSortHashMap(HashMap<Integer, User> hm) {
            //LinkedHashMap是HashMap的之類,根據儲存的方式排列
            LinkedHashMap<Integer,User> lh = new LinkedHashMap();
            //定義一個集合放置單個物件類,等下要根據年齡排序
            ArrayList<User> list = new ArrayList<>();
            Set<Map.Entry<Integer, User>> entries = hm.entrySet();
            Iterator<Map.Entry<Integer, User>> iterator = entries.iterator();
            while (iterator.hasNext()) {
                Map.Entry<Integer, User> next = iterator.next();
                //獲取值,放入集合
                User value = next.getValue();
                list.add(value);
            }
            //集合工具類根據物件的年齡重新排序
            Collections.sort(list, new Comparator<User>() {
                @Override
                public int compare(User o1, User o2) {
                    return o1.age - o2.age;
                }
            });
            //遍歷集合,獲取匹配的鍵,然後存入按存入順序的LinkedList
            for (int i = 0; i < list.size(); i++) {
                //逐個對比
                User user = list.get(i);
                Iterator<Map.Entry<Integer, User>> iterator2 = entries.iterator();
                while (iterator2.hasNext()) {
                    Map.Entry<Integer, User> next = iterator2.next();
                    Integer key = next.getKey();
                    User value = next.getValue();
                    //相同就放入
                    if (value.equals(user)) {
                        //放入
                        lh.put(key, value);
                    }
                }
            }
            return lh;
        }
    }

方法2(技巧轉換).

    /**
     * 處理邏輯:
     *      1.獲取"鍵值對"集
     *      2.鍵值對集變成 集合,是鍵值對的"集"
     *      3.通過集合工具(對比年齡)類排列
     *      3.然後放入LinkedHashMap因為是其之類,直接返回即可
     */
    private static HashMap<Integer, User> getSortHashMap(HashMap<Integer, User> hm) {
        //獲取鍵值對
        Set<Map.Entry<Integer, User>> entries = hm.entrySet();
        //鍵值對轉換成集合list,然後對比
        ArrayList<Map.Entry<Integer,User>> list = new ArrayList<Map.Entry<Integer,User>>(entries);//直接構造放入
        //集合工具類根據物件的年齡重新排序
        Collections.sort(list, new Comparator<Map.Entry<Integer, User>>() {
            @Override
            public int compare(Map.Entry<Integer, User> o1, Map.Entry<Integer, User> o2) {
                return o1.getValue().age-o2.getValue().age;
            }
        });
        //LinkedHashMap是HashMap的之類,根據儲存的方式排列
        LinkedHashMap<Integer,User> lh = new LinkedHashMap();
        for (Map.Entry<Integer,User> map :
                list) {
            lh.put(map.getKey(), map.getValue());
        }
        return lh;
    }

2.集合安全性問題

1.ArrayList,HashSet,HashMap執行緒安全嗎?如何變成安全的?
    1.看原始碼,沒加鎖,所以不安全,vector和hashable才是安全,因為核心方法加了鎖synchronized.
    2.變成安全的方法,集合工具類:
        1.Collections.synchronizedCollection(c)
        2.Collections.synchronizedList(list)
        3.Collections.synchronizedSet(set)
        4.Collections.synchronizedMap(map)
        工具類.鎖對應的型別(物件),原理就是核心方法加了關鍵字.

3.ArrayList內部的實現

1.建構函式
    1.空構造:
    使用了一個new Object[0]陣列
    2.帶引數構造
        1.小於0時,丟擲執行時異常
        2.大於0時,建立一個長度為該值的新陣列
        3.傳入Collection的子類,判斷是否為空,是則空指標異常,不是就轉成陣列a,賦值為成員變數array,他的size就是陣列的長度
2.內部如何實現陣列的增加和刪除和清空
3.陣列長度是固定的,而不斷往其中新增物件,是如何管理陣列?

4.併發集合和普通集合區別

1.普通集合
    效能最高,但不保證多執行緒的安全性和併發的可靠性.
2.同步集合
    僅僅是添加了synchronized同步鎖,犧牲了新能
3.併發集合
    效能更低,通過複雜的策略不僅保證了多執行緒的安全,有提高併發時的效率.

七.多執行緒

1.兩種建立方式

1.繼承Threa類
2.實現Runnable介面

最後都通過重寫run方法實現執行緒

2.wait和sleep區別

1.wait釋放鎖,sleep不釋放
2.wait用於執行緒間互動,sleep用於暫停
3.wait等待CPU,sleep攢著CPU睡覺

3.synchronized和volatile關鍵字作用

volatile :易變,不穩定之意,一個成員變數被修飾後:
1.保證了不同執行緒對這個變數操作時的可見性:一個執行緒修改了某變數值,該值對其他執行緒立即可見.
2.禁止進行指令重排序
    volatile告訴jvm,當前變數在暫存器(工作記憶體)中值是不確定的,需要從主存中讀取
    synchronized鎖定當前變數,只有當前執行緒可訪問,其他的阻塞.
    1.volatile只能在變數級別
        synchronized可在變數,方法,類級別
    2.volatile緊能實現變數的修改可見性,不能保證原子性
        synchronized能實現變數的修改可見性並能保證原子性
    3.volatile不會造成執行緒的阻塞
        synchronized可能會造成執行緒的阻塞。
    4.volatile 標記的變數不會被編譯器優化;
        synchronized 標記的變數可以被編譯器優化;

4.執行緒併發訪問程式碼

    public class Demo3 {
        public static void main(String[] args) {
            final Counter counter = new Counter();
            for (int i = 0; i < 1000; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        counter.inc();
                    }
                }).start();
            }
            System.out.println(counter);
        }
    }

    class Counter {
        private volatitle int count = 0;

        public void inc() {
            try {
                Thread.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            }
            //增加
            count++;
        }

        @Override
        public String toString() {
            return "[count = " + count + "]";
        }
    }

問,是否列印1000?

不是,結果不可能等於1000,肯定是小於1000的,執行緒安全問題,所以小於,不管有無都小於.

執行緒池的重點部分

1.執行緒池是什麼?對執行緒池的理解?怎麼使用?
2.使用方法
3.舉例對比
3.啟動的策略
4.如何控制”併發執行緒”的”併發執行緒數”

因為這一塊是重要的部分,並且講解比較詳細,所以我為大家特意寫了一遍,請大家點選連結.
請點選點選我”秒殺執行緒池”

AsyncTask和Handler的區別介紹:

請介紹兩者的特點,引數,以及優缺點.
同樣的我特意寫了一篇詳細的”訊息非同步機制”的文章
點我征服非同步訊息機制