本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內容請到個人倉庫裏查看html
喜歡的話麻煩點下Star哈android
文章首發於個人我的博客:git
www.how2playlife.com程序員
本文是微信公衆號【Java技術江湖】的《夯實Java基礎系列博文》其中一篇,本文部份內容來源於網絡,爲了把本文主題講得清晰透徹,也整合了不少我認爲不錯的技術博客內容,引用其中了一些比較好的博客文章,若有侵權,請聯繫做者。 該系列博文會告訴你如何從入門到進階,一步步地學習Java基礎知識,並上手進行實戰,接着瞭解每一個Java知識點背後的實現原理,更完整地瞭解整個Java技術體系,造成本身的知識框架。爲了更好地總結和檢驗你的學習成果,本系列文章也會提供每一個知識點對應的面試題以及參考答案。github
若是對本系列文章有什麼建議,或者是有什麼疑問的話,也能夠關注公衆號【Java技術江湖】聯繫做者,歡迎你參與本系列博文的創做和修訂。面試
面向對象簡稱 OO(Object Oriented),20 世紀 80 年代之後,有了面向對象分析(OOA)、 面向對象設計(OOD)、面向對象程序設計(OOP)等新的系統開發方式模型的研究。算法
對語言來講,一切皆是對象。把現實世界中的對象抽象地體如今編程世界中,一個對象表明了某個具體的操做。一個個對象最終組成了完整的程序設計,這些對象能夠是獨立存在的,也能夠是從別的對象繼承過來的。對象之間經過相互做用傳遞信息,實現程序開發。編程
Java 是面向對象的編程語言,對象就是面向對象程序設計的核心。所謂對象就是真實世界中的實體,對象與實體是一一對應的,也就是說現實世界中每個實體都是一個對象,它是一種具體的概念。對象有如下特色:segmentfault
面向過程: 一種較早的編程思想,顧名思義就是該思想是站着過程的角度思考問題,強調的就是功能行爲,功能的執行過程,即前後順序,而每個功能咱們都使用函數(相似於方法)把這些步驟一步一步實現。使用的時候依次調用函數就能夠了。
面向過程的設計: 最小的程序單元是函數,每一個函數負責完成某一個功能,用於接受輸入數據,函數對輸入數據進行處理,而後輸出結果數據,整個軟件系統由一個個的函數組成,其中做爲程序入口的函數稱之爲主函數,主函數依次調用其餘函數,普通函數之間能夠相互調用,從而實現整個系統功能。 面向過程最大的問題在於隨着系統的膨脹,面向過程將沒法應付,最終致使系統的崩潰。爲了解決這一種軟件危機,咱們提出面向對象思想。
面向過程的缺陷: 是採用指定而下的設計模式,在設計階段就須要考慮每個模塊應該分解成哪些子模塊,每個子模塊又細分爲更小的子模塊,如此類推,直到將模塊細化爲一個個函數。
存在的問題
設計不夠直觀,與人類的思惟習慣不一致 系統軟件適應新差,可拓展性差,維護性低
面向對象:
一種基於面向過程的新編程思想,顧名思義就是該思想是站在對象的角度思考問題,咱們把多個功能合理放到不一樣對象裏,強調的是具有某些功能的對象。
具有某種功能的實體,稱爲對象。面向對象最小的程序單元是:類。面向對象更加符合常規的思惟方式,穩定性好,可重用性強,易於開發大型軟件產品,有良好的可維護性。
在軟件工程上,面向對象可使工程更加模塊化,實現更低的耦合和更高的內聚。
面向對象開發模式更有利於人們開拓思惟,在具體的開發過程當中便於程序的劃分,方便程序員分工合做,提升開發效率。
該開發模式之因此使程序設計更加完善和強大,主要是由於面向對象具備繼承、封裝和多態 3 個核心特性。
繼承是java面向對象編程技術的一塊基石,由於它容許建立分等級層次的類。
繼承就是子類繼承父類的特徵和行爲,使得子類對象(實例)具備父類的實例域和方法,或子類從父類繼承方法,使得子類具備父類相同的行爲。
兔子和羊屬於食草動物類,獅子和豹屬於食肉動物類。
食草動物和食肉動物又是屬於動物類。
因此繼承須要符合的關係是:is-a,父類更通用,子類更具體。
雖然食草動物和食肉動物都是屬於動物,可是二者的屬性和行爲上有差異,因此子類會具備父類的通常特性也會具備自身的特性。
多態是同一個行爲具備多個不一樣表現形式或形態的能力。
多態就是同一個接口,使用不一樣的實例而執行不一樣操做,如圖所示:
多態性是對象多種表現形式的體現。
現實中,好比咱們按下 F1 鍵這個動做:
- 若是當前在 Flash 界面下彈出的就是 AS 3 的幫助文檔;
- 若是當前在 Word 下彈出的就是 Word 幫助;
- 在 Windows 下彈出的就是 Windows 幫助和支持。
同一個事件發生在不一樣的對象上會產生不一樣的結果。
在面向對象程式設計方法中,封裝(英語:Encapsulation)是指一種將抽象性函式接口的實現細節部份包裝、隱藏起來的方法。
封裝能夠被認爲是一個保護屏障,防止該類的代碼和數據被外部類定義的代碼隨機訪問。
要訪問該類的代碼和數據,必須經過嚴格的接口控制。
封裝最主要的功能在於咱們能修改本身的實現代碼,而不用修改那些調用咱們代碼的程序片斷。
適當的封裝可讓程式碼更容易理解與維護,也增強了程式碼的安全性。
面向對象編程是利用 類和對象編程的一種思想。萬物可歸類,類是對於世界事物的高度抽象 ,不一樣的事物之間有不一樣的關係 ,一個類自身與外界的封裝關係,一個父類和子類的繼承關係, 一個類和多個類的多態關係。萬物皆對象,對象是具體的世界事物,面向對象的三大特徵封裝,繼承,多態,封裝,封裝說明一個類行爲和屬性與其餘類的關係,低耦合,高內聚;繼承是父類和子類的關係,多態說的是類與類的關係。
如同生活中的子女繼承父母擁有的全部財產,程序中的繼承性是指子類擁有父類數據結構的方法和機制,這是類之間的一種關係;繼承只能是單繼承。
例如定義一個語文老師類和數學老師類,若是不採用繼承方式,那麼兩個類中須要定義的屬性和方法如圖 1 所示。
從圖 1 可以看出,語文老師類和數學老師類中的許多屬性和方法相同,這些相同的屬性和方法能夠提取出來放在一個父類中,這個父類用於被語文老師類和數學老師類繼承。固然父類還能夠繼承別的類,如圖 2 所示。
總結圖 2 的繼承關係,能夠用歸納的樹形關係來表示,如圖 3 所示。
從圖 3 中能夠看出,學校主要人員是一個大的類別,老師和學生是學校主要人員的兩個子類,而老師又能夠分爲語文老師和數學老師兩個子類,學生也能夠分爲班長和組長兩個子類。
使用這種層次形的分類方式,是爲了將多個類的通用屬性和方法提取出來,放在它們的父類中,而後只須要在子類中各自定義本身獨有的屬性和方法,並以繼承的形式在父類中獲取它們的通用屬性和方法便可。
繼承是類與類的一種關係,是一種「is a」的關係。好比「狗」繼承「動物」,這裏動物類是狗類的父類或者基類,狗類是動物類的子類或者派生類。以下圖所示:
注:java中的繼承是單繼承,即一個類只有一個父類。
補充:Java中的繼承只能單繼承,可是能夠經過內部類繼承其餘類來實現多繼承。
public class Son extends Father{
public void go () {
System.out.println("son go");
}
public void eat () {
System.out.println("son eat");
}
public void sleep() {
System.out.println("zzzzzz");
}
public void cook() {
//匿名內部類實現的多繼承
new Mother().cook();
//內部類繼承第二個父類來實現多繼承
Mom mom = new Mom();
mom.cook();
}
private class Mom extends Mother {
@Override
public void cook() {
System.out.println("mom cook");
}
}
}
複製代碼
子類擁有父類的全部屬性和方法(除了private修飾的屬性不能擁有)從而實現了實現代碼的複用;
子類若是對繼承的父類的方法不滿意(不適合),能夠本身編寫繼承的方法,這種方式就稱爲方法的重寫。當調用方法時會優先調用子類的方法。
重寫要注意:
a、返回值類型
b、方法名
c、參數類型及個數
都要與父類繼承的方法相同,才叫方法的重寫。
重載和重寫的區別:
方法重載:在同一個類中處理不一樣數據的多個相同方法名的多態手段。
方法重寫:相對繼承而言,子類中對父類已經存在的方法進行區別化的修改。
一、初始化父類再初始化子類
二、先執行初始化對象中屬性,再執行構造方法中的初始化。
基於上面兩點,咱們就知道實例化一個子類,java程序的執行順序是:
父類對象屬性初始化---->父類對象構造方法---->子類對象屬性初始化--->子類對象構造方法
下面有個形象的圖:
使用final關鍵字作標識有「最終的」含義。
在對象的內部使用,能夠表明父類對象。
一、訪問父類的屬性:super.age
二、訪問父類的方法:super.eat()
super的應用:
首先咱們知道子類的構造的過程中必須調用父類的構造方法。其實這個過程已經隱式地使用了咱們的super關鍵字。
這是由於若是子類的構造方法中沒有顯示調用父類的構造方法,則系統默認調用父類無參的構造方法。
那麼若是本身用super關鍵字在子類裏調用父類的構造方法,則必須在子類的構造方法中的第一行。
要注意的是:若是子類構造方法中既沒有顯示調用父類的構造方法,而父類沒有無參的構造方法,則編譯出錯。
(補充說明,雖然沒有顯示聲明父類的無參的構造方法,系統會自動默認生成一個無參構造方法,可是,若是你聲明瞭一個有參的構造方法,而沒有聲明無參的構造方法,這時系統不會動默認生成一個無參構造方法,此時稱爲父類有沒有無參的構造方法。)
封裝是將代碼及其處理的數據綁定在一塊兒的一種編程機制,該機制保證了程序和數據都不受外部干擾且不被誤用。封裝的目的在於保護信息,使用它的主要優勢以下。
Java 語言的基本封裝單位是類。因爲類的用途是封裝複雜性,因此類的內部有隱藏實現複雜性的機制。Java 提供了私有和公有的訪問模式,類的公有接口表明外部的用戶應該知道或能夠知道的每件東西,私有的方法數據只能經過該類的成員代碼來訪問,這就能夠確保不會發生不但願的事情。
在面向對象程式設計方法中,封裝(英語:Encapsulation)是指一種將抽象性函式接口的實現細節部份包裝、隱藏起來的方法。
封裝能夠被認爲是一個保護屏障,防止該類的代碼和數據被外部類定義的代碼隨機訪問。
要訪問該類的代碼和數據,必須經過嚴格的接口控制。
封裝最主要的功能在於咱們能修改本身的實現代碼,而不用修改那些調用咱們代碼的程序片斷。
適當的封裝可讓程式碼更容易理解與維護,也增強了程式碼的安全性。
封裝的優勢
Java 封裝,說白了就是將一大坨公共通用的實現邏輯玩意,裝到一個盒子裏(class),出入口都在這個盒子上。你要用就將這個盒子拿來用,鏈接出入口,就能用了,不用就能夠直接扔,對你代碼沒什麼影響。
對程序員來講,使用封裝的目的:
須要注意:對封裝的屬性不必定要經過get/set方法,其餘方法也能夠對封裝的屬性進行操做。固然最好使用get/set方法,比較標準。
從表格能夠看出從上到下封裝性愈來愈差。
1.this關鍵字表明當前對象
this.屬性 操做當前對象的屬性
this.方法 調用當前對象的方法。
2.封裝對象的屬性的時候,常常會使用this關鍵字。
3.當getter和setter函數參數名和成員函數名重合的時候,可使用this****區別。如:
內部類( Inner Class )就是定義在另一個類裏面的類。與之對應,包含內部類的類被稱爲外部類。
那麼問題來了:那爲何要將一個類定義在另外一個類裏面呢?清清爽爽的獨立的一個類多好啊!!
答:內部類的主要做用以下:
內部類可分爲如下幾種:
面向對象的多態性,即「一個接口,多個方法」。多態性體如今父類中定義的屬性和方法被子類繼承後,能夠具備不一樣的屬性或表現方式。多態性容許一個接口被多個同類使用,彌補了單繼承的不足。多態概念能夠用樹形關係來表示,如圖 4 所示。
圖4 多態示例圖
從圖 4 中能夠看出,老師類中的許多屬性和方法能夠被語文老師類和數學老師類同時使用,這樣也不易出錯。
可替換性(substitutability)。多態對已存在代碼具備可替換性。例如,多態對圓Circle類工做,對其餘任何圓形幾何體,如圓環,也一樣工做。
可擴充性(extensibility)。多態對代碼具備可擴充性。增長新的子類不影響已存在類的多態性、繼承性,以及其餘特性的運行和操做。實際上新加子類更容易得到多態功能。例如,在實現了圓錐、半圓錐以及半球體的多態基礎上,很容易增添球體類的多態性。
接口性(interface-ability)。多態是超類經過方法簽名,向子類提供了一個共同接口,由子類來完善或者覆蓋它而實現的。
靈活性(flexibility)。它在應用中體現了靈活多樣的操做,提升了使用效率。
簡化性(simplicity)。多態簡化對應用軟件的代碼編寫和修改過程,尤爲在處理大量對象的運算和操做時,這個特色尤其突出和重要。
子代父類實例化,而後就至關於一個父親有不少兒子,送快遞的給這個父親的兒子送東西,他只須要送到父親的家就好了,至於具體是那個兒子的,父親還會分不清本身的兒子麼,因此你就不用操心了。
使用多態是一種好習慣 多態方式聲明是一種好的習慣。當咱們建立的類,使用時,只用到它的超類或接口定義的方法時,咱們能夠將其索引聲明爲它的超類或接口類型。
它的好處是,若是某天咱們對這個接口方法的實現方式變了,對這個接口又有一個新的實現類,咱們的程序也須要使用最新的實現方式,此時只要將對象實現修改一下,索引無需變化。
好比Map< String,String> map = new HashMap < String,String>();
想換成HashTable實現,能夠Map< String,String> map = new HashTable < String,String>();
好比寫一個方法,參數要求傳遞List類型,你就能夠用List list = new ArrayList()中的list傳遞,可是你寫成ArrayList list = new ArrayList()是傳遞不進去的。儘管方法處理時都同樣。另外,方法還能夠根據你傳遞的不一樣list(ArrayList或者LinkList)進行不一樣處理。
java裏的多態主要表如今兩個方面:
父類的引用能夠指向本類的對象;
父類的引用能夠指向子類的對象;
這兩句話是什麼意思呢,讓咱們用代碼來體驗一下,首先咱們建立一個父類Animal和一個子類Dog,在主函數裏以下所示:
注意:咱們不能使用一個子類的引用來指向父類的對象,如:
這裏咱們必須深入理解引用多態的意義,才能更好記憶這種多態的特性。爲何子類的引用不能用來指向父類的對象呢?我在這裏通俗給你們講解一下:就以上面的例子來講,咱們能說「狗是一種動物」,可是不能說「動物是一種狗」,狗和動物是父類和子類的繼承關係,它們的從屬是不能顛倒的。當父類的引用指向子類的對象時,該對象將只是當作一種特殊的父類(裏面有重寫的方法和屬性),反之,一個子類的引用來指向父類的對象是不可行的!!
根據上述建立的兩個對象:本類對象和子類對象,一樣都是父類的引用,當咱們指向不一樣的對象時,它們調用的方法也是多態的。
建立本類對象時,調用的方法爲本類方法;
建立子類對象時,調用的方法爲子類重寫的方法或者繼承的方法;
使用多態的時候要注意:若是咱們在子類中編寫一個獨有的方法(沒有繼承父類的方法),此時就不能經過父類的引用建立的子類對象來調用該方法!!!
注意: 繼承是多態的基礎。
瞭解了多態的含義後,咱們在平常使用多態的特性時常常須要進行引用類型轉換。
引用類型轉換:
1.向上類型轉換(隱式/自動類型轉換),是小類型轉換到大類型
就以上述的父類Animal和一個子類Dog來講明,當父類的引用能夠指向子類的對象時,就是向上類型轉換。如:
2. 向下類型轉換(強制類型轉換),是大類型轉換到小類型(有風險,可能出現數據溢出)。
將上述代碼再加上一行,咱們再次將父類轉換爲子類引用,那麼會出現錯誤,編譯器不容許咱們直接這麼作**,雖然咱們知道這個父類引用指向的就是子類對象,可是編譯器認爲這種轉換是存在風險的。**如:
那麼咱們該怎麼解決這個問題呢,咱們能夠在animal前加上(Dog)來強制類型轉換。如:
可是若是父類引用沒有指向該子類的對象,則不能向下類型轉換,雖然編譯器不會報錯,可是運行的時候程序會出錯,如:
其實這就是上面所說的子類的引用指向父類的對象,而強制轉換類型也不能轉換!!
還有一種狀況是父類的引用指向其餘子類的對象,則不能經過強制轉爲該子類的對象。如:
這是由於咱們在編譯的時候進行了強制類型轉換,編譯時的類型是咱們強制轉換的類型,因此編譯器不會報錯,而當咱們運行的時候,程序給animal開闢的是Dog類型的內存空間,這與Cat類型內存空間不匹配,因此沒法正常轉換。這兩種狀況出錯的本質是同樣的,因此咱們在使用強制類型轉換的時候要特別注意這兩種錯誤!!下面有個更安全的方式來實現向下類型轉換。。。。
3. instanceof運算符,來解決引用對象的類型,避免類型轉換的安全性問題。
instanceof是Java的一個二元操做符,和==,>,<是同一類東東。因爲它是由字母組成的,因此也是Java的保留關鍵字。它的做用是測試它左邊的對象是不是它右邊的類的實例,返回boolean類型的數據。
咱們來使用instanceof運算符來規避上面的錯誤,代碼修改以下:
利用if語句和instanceof運算符來判斷兩個對象的類型是否一致。
補充說明:在比較一個對象是否和另外一個對象屬於同一個類實例的時候,咱們一般能夠採用instanceof和getClass兩種方法經過二者是否相等來判斷,可是二者在判斷上面是有差異的。Instanceof進行類型檢查規則是:你屬於該類嗎?或者你屬於該類的派生類嗎?而經過getClass得到類型信息採用==來進行檢查是否相等的操做是嚴格的判斷,不會存在繼承方面的考慮;
總結:在寫程序的時候,若是要進行類型轉換,咱們最好使用instanceof運算符來判斷它左邊的對象是不是它右邊的類的實例,再進行強制轉換。
D、重寫和重載
多態通常能夠分爲兩種,一個是重寫override,一個是重載overload。
重寫是因爲繼承關係中的子類有一個和父類同名同參數的方法,會覆蓋掉父類的方法。重載是由於一個同名方法能夠傳入多個參數組合。
注意,同名方法若是參數相同,即便返回值不一樣也是不能同時存在的,編譯會出錯。
從jvm實現的角度來看,重寫又叫運行時多態,編譯時看不出子類調用的是哪一個方法,可是運行時操做數棧會先根據子類的引用去子類的類信息中查找方法,找不到的話再到父類的類信息中查找方法。
而重載則是編譯時多態,由於編譯期就能夠肯定傳入的參數組合,決定調用的具體方法是哪個了。
複製代碼
public static void main(String[] args) {
Son son = new Son();
//首先先明確一點,轉型指的是左側引用的改變。
//father引用類型是Father,指向Son實例,就是向上轉型,既可使用子類的方法,也可使用父類的方法。
//向上轉型,此時運行father的方法
Father father = son;
father.smoke();
//不能使用子類獨有的方法。
// father.play();編譯會報錯
father.drive();
//Son類型的引用指向Father的實例,因此是向下轉型,不能使用子類非重寫的方法,可使用父類的方法。
//向下轉型,此時運行了son的方法
Son son1 = (Son) father;
//轉型後就是一個正常的Son實例
son1.play();
son1.drive();
son1.smoke();
//由於向下轉型以前必須先經歷向上轉型。
//在向下轉型過程當中,分爲兩種狀況:
//狀況一:若是父類引用的對象若是引用的是指向的子類對象,
//那麼在向下轉型的過程當中是安全的。也就是編譯是不會出錯誤的。
//由於運行期Son實例確實有這些方法
Father f1 = new Son();
Son s1 = (Son) f1;
s1.smoke();
s1.drive();
s1.play();
//狀況二:若是父類引用的對象是父類自己,那麼在向下轉型的過程當中是不安全的,編譯不會出錯,
//可是運行時會出現java.lang.ClassCastException錯誤。它可使用instanceof來避免出錯此類錯誤。
//由於運行期Father實例並無這些方法。
Father f2 = new Father();
Son s2 = (Son) f2;
s2.drive();
s2.smoke();
s2.play();
//向下轉型和向上轉型的應用,有些人以爲這個操做沒意義,何須先向上轉型再向下轉型呢,不是畫蛇添足麼。其實能夠用於方法參數中的類型聚合,而後具體操做再進行分解。
//好比add方法用List引用類型做爲參數傳入,傳入具體類時經歷了向下轉型
add(new LinkedList());
add(new ArrayList());
//總結
//向上轉型和向下轉型都是針對引用的轉型,是編譯期進行的轉型,根據引用類型來判斷使用哪一個方法
//而且在傳入方法時會自動進行轉型(有須要的話)。運行期將引用指向實例,若是是不安全的轉型則會報錯。
//若安全則繼續執行方法。
}
public static void add(List list) {
System.out.println(list);
//在操做具體集合時又經歷了向上轉型
// ArrayList arr = (ArrayList) list;
// LinkedList link = (LinkedList) list;
}
複製代碼
總結: 向上轉型和向下轉型都是針對引用的轉型,是編譯期進行的轉型,根據引用類型來判斷使用哪一個方法。而且在傳入方法時會自動進行轉型(有須要的話)。運行期將引用指向實例,若是是不安全的轉型則會報錯,若安全則繼續執行方法。
其實就是根據引用類型來調用對應方法。
public static void main(String[] args) {
Father father = new Son();
靜態分派 a= new 靜態分派();
//編譯期肯定引用類型爲Father。
//因此調用的是第一個方法。
a.play(father);
//向下轉型後,引用類型爲Son,此時調用第二個方法。
//因此,編譯期只肯定了引用,運行期再進行實例化。
a.play((Son)father);
//當沒有Son引用類型的方法時,會自動向上轉型調用第一個方法。
a.smoke(father);
//
複製代碼
}
public void smoke(Father father) {
System.out.println("father smoke");
}
public void play (Father father) {
System.out.println("father");
//father.drive();
}
public void play (Son son) {
System.out.println("son");
//son.drive();
}
複製代碼
public static void main(String[] args) {
方法重載優先級匹配 a = new 方法重載優先級匹配();
//普通的重載通常就是同名方法不一樣參數。
//這裏咱們來討論當同名方法只有一個參數時的狀況。
//此時會調用char參數的方法。
//當沒有char參數的方法。會調用int類型的方法,若是沒有int就調用long
//即存在一個調用順序char -> int -> long ->double -> ..。
//當沒有基本類型對應的方法時,先自動裝箱,調用包裝類方法。
//若是沒有包裝類方法,則調用包裝類實現的接口的方法。
//最後再調用持有多個參數的char...方法。
a.eat('a');
a.eat('a','c','b');
}
public void eat(short i) {
System.out.println("short");
}
public void eat(int i) {
System.out.println("int");
}
public void eat(double i) {
System.out.println("double");
}
public void eat(long i) {
System.out.println("long");
}
public void eat(Character c) {
System.out.println("Character");
}
public void eat(Comparable c) {
System.out.println("Comparable");
}
public void eat(char ... c) {
System.out.println(Arrays.toString(c));
System.out.println("...");
}
// public void eat(char i) {
// System.out.println("char");
// }
複製代碼
若是你們想要實時關注我更新的文章以及分享的乾貨的話,能夠關注個人公衆號【Java技術江湖】一位阿里 Java 工程師的技術小站,做者黃小斜,專一 Java 相關技術:SSM、SpringBoot、MySQL、分佈式、中間件、集羣、Linux、網絡、多線程,偶爾講點Docker、ELK,同時也分享技術乾貨和學習經驗,致力於Java全棧開發!
Java工程師必備學習資源: 一些Java工程師經常使用學習資源,關注公衆號後,後臺回覆關鍵字 「Java」 便可免費無套路獲取。
做者是 985 碩士,螞蟻金服 JAVA 工程師,專一於 JAVA 後端技術棧:SpringBoot、MySQL、分佈式、中間件、微服務,同時也懂點投資理財,偶爾講點算法和計算機理論基礎,堅持學習和寫做,相信終身學習的力量!
程序員3T技術學習資源: 一些程序員學習技術的資源大禮包,關注公衆號後,後臺回覆關鍵字 「資料」 便可免費無套路獲取。