很久沒有更新博客了,是由於最近在集中精力學習java, Java 的基礎知識確實是比 js 多太多了。 學習java 斷斷續續的差很少有一年左右的時間, 這一年來,感受懂了一點,過一段時間又忘記了,老是不得要領。如今感受能夠了,可以向深一點的方向學習了。java
學習java 仍是很是痛苦的,尤爲是剛開始的時候,由於js 和java 是兩種不一樣的思想,我習慣了js的函數式編程,而java 則是面向對象的語言,思想的轉換是最困難的。還有一個重大的不一樣是java 是編譯型語言, 代碼寫好了,要先編譯,而後運行。而js 是解釋性語言,寫好了,直接運行就能夠了,我確實習慣了直接運行,這也對個人學習形成了不少誤解。學習方法也是比較大衆化,對於一無所知的小白來講,入門教學視頻是最好的開始,我從網上找了 畢向東的三十五天的java 學習,如今尚未學完,但懂了一點了,從我學習js 的經驗看,最終仍是要落實到書本上,由於只有書籍是系統化的,必定要系統化的學習。百度了一下,推薦最多的java core 和java 編程思想,我先看到java core, 可是看了就忘記了,來回了兩三遍,仍是不行,有點失望。java 編程思想也是同樣,我想算了,要找別的書籍看了,這和我學習js 是同樣的,你們入門的時候,通常都會推薦,JavaScript DOM 編程藝術。可是我看了,並無什麼感受,不會寫,仍是不會, 直到我看到了 beginning JavaScript 這本書,纔算入門了,我給同事推薦的js 的入門書籍都是beginning JavaScript 這本書。java 呢,我找到的兩本書或三本入門書籍是,編程
我看到是英文版,應該沒有中文版,對java 的總體有了一個完整的認識, 但沒有所有看完,由於書的後半部分都是API 的使用,也不是很詳細,就沒有看了,但整體瀏覽了一遍,收穫很是大,尤爲是右側的書籍,(我懷疑這兩本書差很少),我看來Advanced Features , 固然從網上下載的pdf,格式也不是很正確,看起來也有點費勁。總體思想了解了,那就再從細節入手,此次是看 on java 8. on java 8 是java 編程思想的一書的重寫,能夠稱之爲第五版,我沒有買,從網上找的pdf, 確實是沒有尊重知識產權,沒有尊重原書做者, 可是兩三百塊錢,也買不起。pdf 的格式有點差,我就嘗試着抄書,把原書抄一遍,順便把書上的代碼都敲一遍,抄書的過程當中,忽然發現,原來不明白的,有時候抄寫一遍就明白了,固然,抄書還在進行中,不過有些知識點也確實理解起來很困難,那就先放一放,用到的時候,纔看一看。編程語言
Java 是面向對象的編程語言,這也就意味着全部的操做都是以對象進行展開,那麼對象又怎麼理解呢?什麼是對象呢?爲何要以對象進行展開操做呢?那回到咱們的現實世界來看什麼是對象? 當別人問你,」你有對象嗎?」, 在這裏,對象指的是一我的。爲何能夠把人稱之爲對象呢?由於,他有着鮮活的特徵,最簡單的就是姓名和年齡,其次他還有着不同凡響的功能,會唱歌,跳舞,寫代碼等。抽象一下,只要有着特性和功能的東西咱們均可以稱之爲對象,洗衣機了,電冰箱了。明白了什麼是對象,那爲何要以對象展開操做呢?就是爲何要找對象來操做?這就更簡單了? 你爲何找男友或女友呢?由於,他們確定知足了某一方面的需求,找他們就可以解決。那回到java的世界裏,對象也應該有着特性和功能,而且咱們找到它,就能夠幫咱們解決問題,用稍微專業一點的術語,就是,每個對象都有着各自的屬性和方法,咱們找到這個對象,就能夠調用它特有的功能,來實現某個需求。那還有一個問題, 怎麼找到這個對象呢,從哪裏找到這個對象呢?在咱們現實生活中,電冰箱或洗衣機等,都是別人給咱們建立好的,咱們直接使用,這是建立對象的過程,但在java 中,就沒人給咱們建立對象了,只有咱們建立對象了。因此,面向對象的編程,就是建立對象,調用對象的方法的過程。ide
那這又引出了另一個問題,怎麼建立對象呢?在js 中,咱們能夠寫一個對象字面量,直接定義對象全部的屬性和方法,但在java 中不行,建立對象以前,要先描述一下對象,它有哪些屬性和方法,怎麼描述對象呢?java 定義了一種數據類型:類,那也就意味着建立對象以前要先建立一個類。類是用來描述對象的,它也僅僅是用來描述對象有哪些屬性和方法,就像咱們工做中的圖紙。房屋建造圖紙,它是一張紙,僅僅來標明房屋是由哪些部分組成(屬性)和各個部分是怎麼鏈接的(稱之爲方法吧)。有了圖紙(類)以後,咱們並不能作什麼,必須建立對象,就是把房子建起來,才能作點什麼,好比裝修,居住。那咱們寫Java代碼的順序,就變成了書寫類(描述對象),建立對象,而後調用對象的方法.函數式編程
那就先建立一個類,這時又會碰到幾個問題。首先,這個類寫到什麼地方?放到什麼文件中?再廣一點,就是咱們寫的java 代碼放到什麼文件中?全部的java 代碼都放到.java文件中. 固然,文件名也是有講究的,這裏先暫且不說,直接命名一個Person 好了,那咱們就要建立一個Person.java 的文件來存放類代碼。先建一個java 文件夾,而後再在該文件夾下建Person.java 文件。如今終於能夠寫java 代碼了,那用什麼代碼編輯軟件寫呢?有idea, eclipis 集成開發工具,也有Notepad++, vscode 等編輯軟件, 簡單起見,我這裏面就使用vscode了(最好安裝一個Java插件)。用vscode 打開Person.java 文件,開始寫類,那怎麼寫類? 類的語法又是怎麼樣的?首先以class 關鍵字開始,表示它是一個類,其次是類名,類名就是一個變量,不過要以大寫字母開始, 如:Person,而後就是一大括號{}, 大括號裏面就是屬性和方法。Person 類有哪些屬性?姓名和年齡,有哪些方法?吃飯,睡覺,說話等。那如今就要把這些屬性和方法定義到person 中,怎麼定義呢?屬性的定義和變量的定義同樣,方法的定義和函數的定義同樣,不過java 是強類型,聲明變量和函數,都要求註明類型。姓名是字符串類型,用String, 年齡是數字類型,用int . String name; int age; 就是定義了屬性。方法(函數)定義:返回的類型 函數名(接受的參數列表){ 函數體}, 若是沒有沒有返回類型,則使用void, 定義一下吃飯 void eat() {}, 那一個完整的Person 類,以下函數
class Person { String name; int age; void eat() { System.out.println("吃飯"); // 簡單打印一下 } void sleep(int time) { System.out.println("睡覺" + time + "個小時"); } void speak() { System.out.println("姓名"+name+ " 年齡 " + age ); // 方法調用屬性。 } }
類定義好了(固然這是最簡單的),那接下來就是建立對象,調用對象方法,完成需求了。這其實涉用到了代碼執行的問題,就是當程序執行的時候,執行哪一段代碼。在java 中,每個類均可以定義一個main方法,java 程序執行的時候,就會執行main 方法,它的格式也是固定的。public static void main(String[] args) {方法體}, 方法體就是要執行的代碼,咱們就能夠在裏面建立對象,調用方法。建立對象用的是new 類名(new Person()), 建立成功後,要把它賦值一個變量,這樣咱們好使用這個對象,調用方法。這時怎麼聲明一個變量來接受建立的對象呢?上面說了,聲明變量的語法是類型 變量名。 變量名很簡單,命名爲person1 好了。那這個變量的類型呢, 它屬於什麼類型? 由於咱們是使用Person 類來建立的對象,那麼這個對象就屬於Person, 聲明的變量也應該是Person 類型。Person person1 就聲明變量成功了。完整的代碼以下工具
class Person { String name; int age; void eat() { System.out.println("吃飯"); // 簡單打印一下 } void sleep(int time) { System.out.println("睡覺" + time + "個小時"); } void speak() { System.out.println("姓名" +name + " 年齡 " + age ); // 方法調用屬性。 } public static void main(String[] args) { Person person1 = new Person(); person1.eat(); } }
代碼寫完了,就要開始運行了。但對於java來講,它是一門編譯型語言,要先編譯,再運行。這就要求咱們先安裝java的運行環境和編譯命令等等,就是安裝jdk, 配置環境變量( 配置稍後再說),配置完成後, 就可使用javac 命令來編譯程序,java 命令來運行程序。找到Person.Java 文件所在的文件夾,而後在該文件夾下,打開cmd 命令窗口,執行javac Person.java 命令,報錯了。因爲個人vs code 全部的文件默認是utf-8 編碼,因此編譯的時候要提供文件編碼, javac -encoding UTF-8 Person.java, 這樣就OK了。編譯成功後, 你會看到文件夾裏多了一個Person.class 文件,這就編譯好的文件,咱們再調用java 命令執行這個文件,java Person, 你會發現輸出了吃飯。這就是整個java 程序的執行過程。學習
對於大型的java 程序來講,它不可能只有一個類,而是由無數個類組成的,這時類的命名就會是個問題,每寫一個類的時候,還要想想,之前是否是有過這個類名,總不可能衝突了吧?但誰會記得之前的類都是什麼名字呢?爲了避免形成命名衝突,你可能會把類名寫得很長,很個性,其實java 提供了一個更好解決類名衝突的方法,那就是包。咱們能夠把類放到不一樣的包下面,這樣,這個類就屬於這個包了,減小了命名衝突。包,其實就是文件夾,咱們能夠把不一樣的類文件放到不一樣的文件夾下。如今Person 類放到了java文件夾下,當咱們再寫一個類的時候,咱們不在java文件夾下建類文件了,能夠新建一個文件夾,如world, 而後,再在world 裏面建一個類文件,如Person.java。這時候Person 就是屬於world 了,和外面的Person 就沒有衝突了。咱們在java代碼中,是怎麼告訴Person 類是屬於world的。那就用package 關鍵字。在world 文件夾下Person.java 中,寫一個package world, 而後把speak 改爲以下開發工具
package world; class Person { void speak() { System.out.println("在world 文件夾下" ); // 方法調用屬性。 } }
這裏要注意,package和 文件夾的名稱是一一對應的,若是咱們寫package world.hello; 那麼它對應的文件夾應是world 文件夾下面再有一個文件夾hello, 而後在hello 文件夾下有一個Person 類。package 語法中的點號,就表明下一級文件夾。當一個類有所屬的時候, 就不能直接使用類名了,而是要把它的所屬也帶上,Person 類的真正名稱是 world.person. 建立對象時 world.Person person2 = new world.Person()。這時,能夠在外面的Person.java 中main 方法中使用。this
public static void main(String[] args) { Person person1 = new Person(); world.Person person2 = new world.Person(); // 使用world 包下在的Person 類
person2.speak(); person1.eat(); }
這時 javac -encoding UTF-8 Person.java,又報錯了。
Person 類在world 中不是公有的,這又涉及到java 的權限問題,當一個類有了包所屬後,它能夠被它所在的包中的全部java類引用,但不能被外面的包引用。若是一個類要讓外界訪問,那必須加一個修飾符public , 在class 關鍵字前面加一個public, 這時類也分爲了兩種,一種是是public修飾的,一個是默認的(沒有public 修飾),當這個類前面有public時,這個類能夠被其它包中的類進行引用。如:public class Person {}. 可是若是這個類前面什麼都沒有,它只能在本包中進行使用,不能被其它包所引用。修改一下world 文件夾下的Person類。
package world; public class Person { void speak() { System.out.println("在world 文件夾下" ); // 方法調用屬性。 } }
當一個類前面有public 的時候,這個類所在的文件的名稱,必須和類名一致,這也就是文件名爲Person.class的緣由。若是你修改文件名爲其它(如Student.java),則會報錯。同時一個文件中只能有一個public class,和文件名保持一致。好了,如今再編譯一下, 你會發現又一個錯誤
speak() 方法,不是公共的, 這又是權限問題,不過是類的內部的權限。類中的屬性和方法也有訪問權 限問題,它的更爲複雜一點,由於方法前面能夠加的修飾符比較多,private, public, protected修飾符
類中的成員或方法:
若是前面是public, 這個方法或屬性,能夠在任何地方被使用。
若是前面是private, 這個屬性或方法,只能在本類中使用。
若是前面是默認,什麼都沒有,則能夠在包中被引用。
若是前面是protected, 它能夠在本包中的其它類中進行使用,而且能夠被其它包中子類使用。只有子類才能使用。
那在這裏,咱們要使用public修飾符,改一下。
package world; public class Person { public void speak() { System.out.println("在world 文件夾下" ); // 方法調用屬性。 } }
再改一下
當咱們使用一個類的時候,咱們應該先找到這個類,這個類前面的屬性要麼是public, 要麼什麼都沒有,若是沒有,其它包中的類就不能使用這個類。肯定使用這個類之後,就要肯定可否使用這個類的屬性或方法,它前面又有四個修飾符。
這時,你會發現當類有所屬的包以後,再使用這個類就不是很方便了,要把包名和類名 連在一塊兒使用。這時java 提供了導包(import)機制, 咱們先把包導入到要使用包的java文件中,而後,使用包中的類時,就和咱們日常的使用方式同樣了 直接使用類名。 可是若是導入的包中和使用包的java 文件中,有重名的類名時,像咱們這裏,world 包中有Person, Person 類中 也有Person, 就不能直接使用類名了,由於若是使用類名,java 並不知道 要使用哪一個包中的Person類,仍是要和如今的使用方式一致,包和類名要寫全了。咱們在world文件夾中再聲明一個類,如Student, 體驗一下導包。
package world; public class Student { public void speak() { System.out.println("在world 文件夾下Student" ); } }
而後在java 文件夾中Person 類,import world.*; 導入world 包, 而後在main 方法中,直接使用Student。
import world.*; // 導入world 包中的全部方法 class Person { String name; int age; public static void main(String[] args) { Student s = new Student(); s.speak(); } }
其實當咱們寫了大量的類之後,當你再寫一個類的時候,你會發現,這個類怎麼和之前的一個類這麼類似呢,或者,某個類能夠實現這個類的部分功能?這就是java 中的繼承 和組合。好比,咱們寫一個汽車類,忽然發現之前有一個輪胎類,這時咱們就能夠直接把輪胎類對象放到汽車類做爲屬性直接使用了,這就是組合,組合和現實中的組合沒有區別,組合一個類,和搭積木同樣,它體現了一種has- a 的關係。繼承,則是體現一種is-a 的關係,它是它的關係。當咱們建立一個工人worker 類的時候,你發現了Person 類 ,工人也是人啊,絕對體現is-a 的關係,這時就能夠繼承了,使用extends 關鍵字。class Worker extends Person {} , 這時 Worker 類就徹底擁有了Person 類的屬性和方法(private 屬性除外)。當使用Worker 建立一個對象的時候,它能夠調用person 對象上的方法。把根目錄下面的Person 類改成以下內容(增長Worker)
class Person { private String name; private int age; void eat() { System.out.println("吃飯"); // 簡單打印一下 } void sleep(int time) { System.out.println("睡覺" + time + "個小時"); } } class Worker extends Person { public static void main(String[] args) { Worker w = new Worker(); w.eat(); } }
編譯運行一下
沒有問題。那Worker類能不能增長新的方法和屬性呢?固然能夠,Worker自己也是一個單獨的類,你想加什麼方法就加什麼方法, 那能不能增長和父類同名的方法好比eat, 也能夠,這叫覆寫,由於子類徹底能夠擁有本身的eat 方法。
class Worker extends Person { void eat() { System.out.println("吃饅頭"); // } void work() { System.out.println("工做"); // 工人特有的方法 } public static void main(String[] args) { Worker w = new Worker(); w.eat(); } }
這時,你會發現,每個子類都至少擁有父類的方法,子類只會比父類越來具體,父類就至關於愈來愈抽象,若是父類不停的抽象,你會出現,它只剩下描述了,方法根本沒有具體的實現方式,太抽象了,也沒有辦法實現,只能有子類進行實現,這就是接口interface。interface 裏面的方法只用描述,沒有實現。
interface Man { void eat(); }
interface 的語法和class 類似,只不過方法沒有實現。interface 確定不能直接用,要有一個類實現它,而後建立實現類的對象, 實現一個接口用的是implements 關鍵字
class Student implements Man { public void eat() { System.out.println("喝牛奶"); // 簡單打印一下 } }
這時候出現了一個神奇的事情,咱們能夠用interface 聲明一個引用變量 Man m, 而後指向實體類對象 m = new Student(), 這就造成了多態。 由於一個接口能夠有無數個實現類,m 就能夠指向無數個實現類對象, 也就擁有了多個形態。賦值成功之後,student 對象(類型)就轉化成了Man 類型,發生了向上轉型。轉化成Man 類型後,它就不記得它指向的具體類型,因此只能獲取到Man類型 定義的屬性和方法,在這裏就是eat() 方法。m.eat().
class Student implements Man { public void eat() { System.out.println("喝牛奶"); // 簡單打印一下 } public static void main(String[] args) { Man m = new Student(); m.eat(); } }
當調用eat 方法以後,你發現調用的居然是Student中的方法,不是已經發生向上轉型了,怎麼仍是調用實現類(子類)的方法, 這裏要注意的是eat 方法是對接口(父類)的覆寫。這又涉及到方法的動態綁定, 方法只有真正運行的時候,才能肯定調用哪一個方法,這是java 虛擬機自動選擇的。
It is important to understand that it is the type of the reference variable(the Man) --- not the type of the object(Student) -------that determines what members can be accessed. That is, when a reference to a subclass object( new Student()) is assigned to a superclass reference variable(Man), you will have access only to those parts of the object defined by the superclass. If you think about it, this makes sense, because the superclass has no knowledge of what a subclass adds to it. 聲明的引用變量m 決定了你能夠調用哪些屬性和方法
Method overriding forms the basis for one of java’s most powerful concepts: dynamic method dispatch. Dynamic method dispatch is the mechanism by which a call to an overridden method is resolved at run time rather than compile time. Dynamic method dispatch is important because this is how java implements run-time polymorphism. 方法覆寫組成了java 中的動態方法綁定。只有在運行的時候才能肯定執行哪一個覆寫的方法
Let begin by restating an important principle: a superclass reference variable can refer to a subclass object. Java uses this fact to resolve calls to overridden methods at run time. When an overridden method is called through a superclass reference, java determines which version of that method to execute based on the type of the object being referred to at the time the call occurs. Thus this determination is made at run time. When different types of objects are referred to ,different versions of an overridden method will be called. In other words, it is the type of the object being referred to (not the type of the reference variable) that determines which version of an overridden method will be executed.
Polymorphism is essential to object-oriented programming for one reason: it allows a general class to specify methods that will be common to all of its derivatives, while allowing subclasses to define the specific implementation of some or all of those methods.
若是真的要用m 變量引用具體的類型(Student中)的方法呢?只能強制類型轉化,把m 從Man 類型轉化成Student 類型,Student s = (Student) m. m.speak(). 假設有speak 方法。
如今就能夠真正的寫java 程序了,可是你也不用每個類都本身寫,若是第三方提供了,拿過來直接用就能夠了,何須造輪子呢?因此不光要學java的語法,還要第三方的API.
總結一下:
Java 程序是類組成的,類又是由屬性和方法構成。在程序開發的過程當中,咱們能夠書寫每個類,可是絕大多數的程序開發者都會使用第三方提供的類庫。所以在學習java 的時候,不光要學習怎麼書寫java 代碼來建立本身的類,還要學習其它類庫提供了API.
Java 程序呢?每個java 文件都要以.java 結尾,文件名必須與文件中的public class的類名相同,若是java 文件中沒有public class 呢? 這時你能夠命名任何名稱,不過,仍是建議與文件中的類保持一致。一個java 文件只能有一個public class, 可是能夠任意多個no-public class. 若是一個文件中有多個class, 編譯的時候,全部的類都會編譯到當前文件名,文件名就是類名,後綴則以.class 文件結尾。具體到每個文件都有什麼內容呢?
文檔註釋:寫過代碼的人都很熟悉,就是代碼是何時建立的,建立人是誰。
package 語句:一個java 文件中最多隻能有一條包語句,表示文件的命名空間。若是有包語句的話,它必須放到文件的最前面,也就是文件的第一句。固然文檔註釋不算在內,文檔註釋能夠放到package 語句的前面。
import 語句: 一個文件中,能夠有多條import 語句,引入文件開發所須要的包。它要放到package 語句的後面,任何自定義類或接口的前面。
接口(interface)或類(class)類定義: 一個文件中,能夠有多個接口,也能夠沒有接口。能夠有多個class, 但至少得存在一個class, 要否則程序沒有辦法運行。 之因此存在 一個類,是要有一個main 方法,java 程序運行的時候,就是找main 方法。一個最簡單的java 程序可能就是這一個類,全部的代碼都放到main 方法中運行。