1. 程式人生 > 實用技巧 >Java基礎知識入門

Java基礎知識入門

本文內容來自本人在學習Java過程中整理的筆記,供自己日後翻閱,也可以作為Java入門基礎的讀者一部分學習資料,有問題歡迎留言,知無不言。

1.當一個類有多個構造器時,一個構造器呼叫另外一個構造器,可以使用this。

  1. class Post {
  2. private String title;
  3. private String content;
  4. public Post(String title) // 第一個構造器
  5. {
  6. this.title = title;
  7. }
  8. public Post(String title, String content)// 第二個構造器
  9. {
  10. this(title);
  11. this.content = content;
  12. }
  13. }

在第二個構造器中,this(title);這一行程式碼表示呼叫第一個構造器。 在構造器比較複雜時,這種方式可以讓程式碼更加簡潔。

2.我們也可以定義有引數的構造器,即構造器也是可以過載的。比如:

  1. class Post {
  2. private String title;
  3. private String content;
  4. public Post(String title, String content)
  5. {
  6. this.title = title;
  7. this.content = content;
  8. }
  9. }

這裡建構函式具有兩個引數。this表示的是當前物件,表示將引數title和content賦給自己對應的成員變數。如果自行定義了構造器,則編譯器就不再會為我們生成預設構造器,針對以上Post程式碼,通過Post post = new Post();來建立Post物件將會產生編譯錯誤。

3.構造器類似於普通方法,但是有兩個特殊的地方:方法名稱必須和類名相同,不允許定義返回型別。如果你沒有定義任何構造器,則編譯器會自動幫你生成一個構造器,稱之預設構造器。預設構造器不包含任何引數,會自動呼叫其父類的無引數構造器。回顧此前Post類的定義:

  1. class Post {
  2. private String title;
  3. private String content;
  4. }

因為沒有顯式定義構造器,所以編譯器會幫我們生成預設構造器。因此我們可以使用new操作符來呼叫預設構造器。例如: Post post = new Post();構造器也可以使用public和priavate 修飾。如果使用private修飾,則外部不能通過new操作建立該類的例項。

4.如果一個方法定義了返回型別,在方法體內必須有return語句返回該型別的資料。return後面可以跟字面量、變數或者表示式。return也可以單獨使用,不跟任何表示式,表示立即結束當前方法的執行。.return也表示無條件分支,它包含兩種用途:(1)return後面跟一個值或者變數,指定一個方法的返回值(2)單獨的return;語句,後面不跟任何值或者變數,表示退出當前方法比如我們可以定義一個方法來計算從1到100之和,然後將其返回:

  1. public static int sumOfNumbers() {
  2. int sum = 0;
  3. for(int i = 0; i <= 100; i++)
  4. {
  5. sum += i;
  6. }
  7. return sum;
  8. }

5.private:表示任何其他類不能直接訪問該成員變數,只有該類自身可以訪問
protected:表示只有該類自身及其子類可以訪問該成員變數
public:表示任何類都可以直接訪問該成員變數。
沒有修飾:表示同一個包的類可以訪問該成員變數

6.關於類和Java檔案的關係:一般情況下,都是一個類一個.java檔案如果一個.java檔案裡有多個類,只可能有一個public的類。而且檔名必須和public類同名如果檔案裡所有類都不是public的,那麼檔名和任意一個類同名即可。

7.continue用於在迴圈控制結構中,讓程式立刻跳轉到下一次迴圈。在for迴圈中,continue語句使程式立即跳轉到控制變數更新語句。在while或者do/while迴圈中,程式立即跳轉到布林表示式的判斷語句。比如,我們計算從1到100的所有偶數之和,我們也可以通過加入continue語句來實現:

  1. int sum = 0;
  2. for(int i = 0; i <= 100; i++)
  3. {
  4. if (i % 2 == 1)
  5. {
  6. continue; // 如果是奇數,則立刻跳轉到下一次迴圈,不做累加
  7. }
  8. sum += i;
  9. }

8.Java利用for語句引入了更為簡單的方式來進行Java資料及容器的操作。通過這種方式,我們可以不用引入額外的控制變數。以遍歷陣列為例: 一般的for迴圈:

  1. String[] sentences = {"hello", "thank u", "thank u very much"};
  2. for (int index = 0; index < sentences.length; index++)
  3. {
  4. System.out.println(sentences[index]);
  5. }
  6. //可以採用如下方式進行簡化:
  7. String[] sentences = {"hello", "thank u", "thank u very much"};
  8. for (String sentence : sentences)
  9. {
  10. System.out.println(sentence);
  11. }

9.'=='和'!='作為關係運算符只用來比較物件的引用。如果想比較兩個物件實際內容是否相同,需要呼叫物件的equals()方法。比如判斷一個字串str的內容是否為"abcd",應該這樣比較:

  1. if (str.equals("abcd")) {
  2. }
  3. //下面這種方式是一種錯誤的方式:
  4. if (str == "abcd") {
  5. }

10.命名衝突即類同名的情況,想想一下同一個資料夾下是不可以有兩個同名的檔案的,不同資料夾下就不存在這個問題,包可以看做是存放Java類的不同資料夾。Java平臺提供了很多方便我們程式設計的類,通常稱之為類庫(lib),也稱之為應用程式設計介面(Application Programming Interface, API)。不僅僅是使用Java平臺提供的類需要import,任何一個package中的類,如果需要訪問另外一個package中的類,就需要import.

11.引用型別和基本型別的區別

  1. int color = 0;
  2. int speed = 100;
  3. Car myCar = new Car(color, speed);

則記憶體狀態如下:

與引用型別myCar不同,基本型別變數的值就是儲存在棧中,作用域結束(比如main方法執行結束)則這些變數佔據的棧記憶體會自動釋放。

12.在外部(即其它類中)也可以訪問一個類的非private屬性,通過物件名.屬性名的方式進行訪問。例如將Car的color屬性設定為public:

  1. public class Car {
  2. public int color;
  3. // ...
  4. }

如果我們定義一個Driver類,可以這樣訪問color屬性:

  1. public class Driver {
  2. public static void main(String[] args) {
  3. Car car = new Car();
  4. car.color = 0xffffff; // 修改color屬性的值
  5. int color = car.color; // 訪問color屬性的值,將其賦給其他變數
  6. System.out.println(car.color); // 將color作為引數,列印
  7. }
  8. }

13.如果自行定義了構造器,則編譯器就不再會為我們生成預設構造器,針對以上Post程式碼,通過Post post = new Post();來建立Post物件將會產生編譯錯誤,除非你顯式地增加預設構造器:

  1. class Post {
  2. private String title;
  3. private String content;
  4. public Post() {
  5. }
  6. public Post(String title, String content) {
  7. this.title = title;
  8. this.content = content;
  9. }
  10. }

14.equals()與==的區別:如果兩個字串變數指向的字元序列內容完全一樣,equals()返回true;如果兩個字串變數指向同一個物件,==返回true。

  1. String str1 = new String("abc");
  2. String str2 = new String("abc");
  3. System.out.println(str1 == str2);
  4. System.out.println(str1.equals(str2));

以上程式碼,第一個輸出為false,第二個輸出為true。

15.一個類的成員變數,一個函式中的引數,都具有一種資料型別,可以為基本資料型別(如int型別)或者引用型別(如Car型別)。假設我們想描述平面座標的一個點,那麼我們建立一個Point類,這個類要包含表示X座標和Y座標的成員變數:

  1. public class IntPoint{
  2. private int x;
  3. private int y;
  4. public int getX() {
  5. return x;
  6. }
  7. public void setX(int x) {
  8. this.x = x;
  9. }
  10. public int getY() {
  11. return y;
  12. }
  13. public void setY(int y) {
  14. this.y = y;
  15. }
  16. }

如果我們發現int型別描述平面座標上的點精度不夠,希望換做double型別,那麼程式碼應該大部分是類似的,可是上面的程式碼我們卻無法複用。我們只能再定義一個非常類似的DoublePoint重新定義為:

  1. public class DoublePoint{
  2. private double x;
  3. private double y;
  4. public double getX() {
  5. return x;
  6. }
  7. public void setX(double x) {
  8. this.x = x;
  9. }
  10. public double getY() {
  11. return y;
  12. }
  13. public void setY(double y) {
  14. this.y = y;
  15. }
  16. }

能否只定義一個類就能滿足座標既可能為int型別,也可能為double型別的情況呢?如果可以的話將可以讓程式碼更加通用,減少大量的重複程式碼。答案是肯定的,這個時候你需要泛型。

在使用泛型時,我們可以把型別作為引數傳入到泛型類中。類似於把引數傳入到方法中一樣。我們來實現一個通用的泛型Point類

  1. public class Point<T> {
  2. private T x;
  3. private T y;
  4. public T getX() {
  5. return x;
  6. }
  7. public void setX(T x) {
  8. this.x = x;
  9. }
  10. public T getY() {
  11. return y;
  12. }
  13. public void setY(T y) {
  14. this.y = y;
  15. }
  16. }

此時Point成為了一個泛型類,T是則是型別引數,T具體是什麼型別那要看程式執行的時候我們傳入什麼型別給他。使用泛型類時,注意實際傳入的型別引數不能是原生型別,必須是引用型別,因此如果希望傳入int型別的話,那麼需要傳入int對應的包裝類Interger。對應地,double型別則要傳入包裝類Double。

  1. public class Test{
  2. public static void main(String[] args){
  3. // 座標為int型別,把int型別的包裝類Integer作為引數傳入泛型類中
  4. Point<Integer> point1 = new Point<Integer>();
  5. point1.setX(1);
  6. point1.setY(1);
  7. // 座標為double型別,把double型別的包裝類Double作為引數傳入泛型類中
  8. Point<Double> point2 = new Point<Double>();
  9. point2.setX(3.456);
  10. point2.setY(4.789);
  11. }
  12. }

Point<Integer>等價於IntPoint類,而Point<Double>則等價於DoublePoint類。
再舉一個例子,我們定義一個容器類Container,這個容器中可以存放各種型別的物件,可以使用泛型類實現這一特性。

  1. public class Container<T> {
  2. private T variable;
  3. public Container () {
  4. variable = null;
  5. }
  6. public Container (T variable) {
  7. this.variable = variable;
  8. }
  9. public T get() {
  10. return variable;
  11. }
  12. public void set(T variable) {
  13. this.variable = variable;
  14. }
  15. public static void main(String[] args) {
  16. Container<String> stringContainer = new Container<String>();
  17. stringContainer.set("this is a string");
  18. }
  19. }

我們例項化Container物件時,只需設定它使用的型別,如:

  1. Container<String> stringContainer = new Container<String>();
  2. stringContainer.set("this is a string");

16.static的作用:大家都知道,我們可以基於一個類建立多個該類的物件,每個物件都擁有自己的成員,互相獨立。然而在某些時候,我們更希望該類所有的物件共享同一個成員。此時就是 static 大顯身手的時候了!Java 中被 static 修飾的成員稱為靜態成員或類成員。它屬於整個類所有,而不是某個物件所有,即被類的所有物件所共享。靜態成員可以使用類名直接訪問,也可以使用物件名進行訪問。當然,鑑於他作用的特殊性更推薦用類名訪問。使用 static 可以修飾變數、方法和程式碼塊。例如,我們在類中定義了一個 靜態變數 hobby ,操作程式碼如下所示:

  1. public class HelloWorld {
  2. static String hobby="kat";
  3. public static void main(String[] args) {
  4. System.out.println("通過類名訪問hobby:"+HelloWorld.hobby);
  5. HelloWorld hello=new HelloWorld();
  6. System.out.println("通過物件名訪問hobby:"+hello.hobby);
  7. hello.hobby="melo";
  8. System.out.println("再次通過類名訪問hobby:"+HelloWorld.hobby);//列印結果:melo
  9. }
  10. }

靜態成員屬於整個類,當系統第一次使用該類時,就會為其分配記憶體空間直到該類被解除安裝才會進行資源回收!

關於static,有一道筆試題,不妨一看。

  1. class Test{
  2. private int m;
  3. public static void fun(){
  4. //some code...
  5. }
  6. }
  7. 如何使成員變數m被函式fun()直接訪問
  8. A.將private int m改為protected int m
  9. B.將private int m改為public int m
  10. C.將private int m改為static int m
  11. D.將private int m改為int m

本題選C

17.Java集合:集合類存放於java.util包中,java集合主要可以劃分為四個部分,List、Set、Map、工具類(Iterator迭代器、Enumeration列舉類、Arrays和VCollections)。java的集合類主要由兩個介面派生而來,Collection和Map。在匯入Set和List類時,方法是import java.util.Set和import java.util.List。但是注意Map和Collection都是藉口,不能像Set類和List一樣類匯入。該怎麼匯入呢?例如我們要匯入下面Colletion介面圖中的TreeSet這個類,或者Map介面HashMap類,可以這樣匯入

  1. Map map=new HashMap();//import java.util.HashMap;和import java.util.Map;
  2. Collection col=new TreeSet();//import java.util.Collection;和import java.util.TreeSet;

分別來看一下這兩個介面的體系

Colletion介面

Map介面

這個是總的結構圖

(1)Collection介面是集合類的根介面,Java中沒有提供這個介面的直接的實現類。但是卻讓其被繼承產生了兩個介面,就是Set和List。Set中不能包含重複的元素。List是一個有序的集合,可以包含重複的元素,提供了按索引訪問的方式。

(2)Map是Java.util包中的另一個介面,它和Collection介面沒有關係,是相互獨立的,但是都屬於集合類的一部分。Map包含了key-value對。Map不能包含重複的key,但是可以包含相同的value。

(3)Iterator,所有的集合類,都實現了Iterator介面,這是一個用於遍歷集合中元素的介面,主要包含以下三種方法:
1.hasNext()是否還有下一個元素。
2.next()返回下一個元素。
3.remove()刪除當前元素。

(4)ArrayList和LinkedList在用法上沒有區別,但是在功能上還是有區別的。LinkedList經常用在增刪操作較多而查詢操作很少的情況下,ArrayList則相反。

(5)Map集合,實現類:HashMap、Hashtable、LinkedHashMap和TreeMap

HashMap是最常用的Map,它根據鍵的HashCode值儲存資料,根據鍵可以直接獲取它的值,具有很快的訪問速度,遍歷時,取得資料的順序是完全隨機的。因為鍵物件不可以重複,所以HashMap最多隻允許一條記錄的鍵為Null,允許多條記錄的值為Null,是非同步的。

Hashtable,Hashtable與HashMap類似,是HashMap的執行緒安全版,它支援執行緒的同步,即任一時刻只有一個執行緒能寫Hashtable,因此也導致了Hashtale在寫入時會比較慢,它繼承自Dictionary類,不同的是它不允許記錄的鍵或者值為null,同時效率較低。

LinkedHashMap儲存了記錄的插入順序,在用Iteraor遍歷LinkedHashMap時,先得到的記錄肯定是先插入的,在遍歷的時候會比HashMap慢,有HashMap的全部特性。

TreeMap實現SortMap介面,能夠把它儲存的記錄根據鍵排序,預設是按鍵值的升序排序(自然順序),也可以指定排序的比較器,當用Iterator遍歷TreeMap時,得到的記錄是排過序的。不允許key值為空,非同步的。

有關集合,有一個例子特別好

  1. package knowledgeAboutColletion;
  2. import java.util.ArrayList;
  3. import java.util.Collection;
  4. import java.util.Iterator;
  5. public class HashSetTest {
  6. public static void main(String args[]){
  7. /*1.Collection<String>:泛型介面,這裡的泛型String,所以新增的元素只能是String型別
  8. *2.ArrayList屬於繼承Collection介面的類,這個屬於多型的向上轉型
  9. *3.list屬於介面,它有五種方法,add、remove、isEmpty、iterator、size*/
  10. Collection<String> list=new HashSet<>();//列印結果a,abc。Set集合不允許重複。
  11. list.add("abc");//新增字串
  12. list.add("a");
  13. list.add("abc");
  14. /*Iterator迭代器,有三種方法
  15. *next():獲得序列的下一個元素
  16. *hasNext():序列中是否還有元素
  17. *remove():將迭代器新返回的元素刪除*/
  18. Iterator<String> it=list.iterator();
  19. while(it.hasNext()){
  20. String str=it.next();
  21. System.out.println(str);
  22. }
  23. }
  24. }

如果是List集合,則不一樣,可以重複。

  1. package knowledgeAboutColletion;
  2. import java.util.ArrayList;
  3. import java.util.Collection;
  4. import java.util.Iterator;
  5. public class HashSetTest {
  6. public static void main(String args[]){
  7. Collection<String> list=new LinkedList<>();//列印結果abc,a,abc。List集合可以重複。
  8. list.add("abc");
  9. list.add("a");
  10. list.add("abc");
  11. Iterator<String> it=list.iterator();
  12. while(it.hasNext()){
  13. String str=it.next();
  14. System.out.println(str);
  15. }
  16. }
  17. }

這裡把ArrayList改成LinkedList效果差不多,兩者的區別百度一下,會有很多文章,同理上面的TreeSet和HashSet。暫時不細分同一介面下不同繼承類的差別。
如果上面的Collection沒有泛型呢?

  1. package knowledgeAboutColletion;
  2. import java.util.Collection;
  3. import java.util.Iterator;
  4. import java.util.TreeSet;
  5. public class HashSetTest {
  6. public static void main(String args[]){
  7. HashSetTest t=new HashSetTest();
  8. Collection list=new HashSet<>();//列印結果a,abc,Set集合不允許重複
  9. list.add("abc");
  10. list.add("a");
  11. list.add("abc");
  12. list.add(t);//新增物件,由於之前Collection泛型String,所以不能新增物件t,但是可以新增t.toString
  13. Iterator<String> it=list.iterator();
  14. while(it.hasNext()){
  15. String str=it.next();
  16. System.out.println(str);
  17. }
  18. }
  19. }

列印結果

  1. a
  2. abc
  3. Exception in thread "main" java.lang.ClassCastException: knowledgeAboutColletion.HashSetTest cannot be cast to java.lang.String
  4. at knowledgeAboutColletion.HashSetTest.main(HashSetTest.java:24)

物件t可以增加到集合中,但是無法打印出來。
Map集合的例項

  1. import java.util.Collection;
  2. import java.util.HashMap;
  3. import java.util.Iterator;
  4. import java.util.Map;
  5. import java.util.Set;
  6. public class HashSetTest {
  7. public static void main(String args[]){
  8. /*1.建立Map介面例項,這裡泛型,並且用了兩個泛型,這樣泛型,後面put方法中填入的key-value只能是String型別
  9. * 如果沒有這個泛型,可以是任何型別的
  10. 2.Map介面的六大方法
  11. (1)put(K key,V value):向集合中新增指定的key與value的對映關係
  12. (2)containsKey
  13. (3)containsValue
  14. (4)get
  15. (5)keySet:返回所有key物件形成的Set集合
  16. (6)values:返回所有key物件形成的Collection集合*/
  17. Map<String,String> map=new HashMap<>();
  18. map.put("01", "jason");//put方法需要兩個
  19. map.put("02", "shu");
  20. Set<String> set=map.keySet();
  21. //iterator方法,Collection集合的方法,返回在此Collection的元素上進行迭代的迭代器
  22. Iterator<String> it1=set.iterator();
  23. System.out.println("key集合中的元素");
  24. //整個過程,從Map中提取key→將key轉換為Set集合→為Set集合建立迭代器(因為要遍歷)
  25. while(it1.hasNext()){
  26. System.out.println(it1.next());
  27. }
  28. Collection<String> col=map.values();//解釋如上
  29. Iterator<String> it2=col.iterator();
  30. System.out.println("values集合中的元素");
  31. while(it2.hasNext()){
  32. System.out.println(it2.next());
  33. }
  34. }
  35. }

18.類的封裝:封裝是一種隱藏資訊的技術,是將一個系統中的結構和行為通過類來劃分的過程。即通過定義一組類,將特定的資料組合到某一個類中,形成一個整體,將該隱藏的資料進行保護,只對外暴露這些資料的訪問的方法。封裝程式碼有兩個好處:(1)程式碼使用者無需考慮實現細節就能直接使用它,同時不用擔心不可預料的副作用,別人不能隨便修改內部結構(2)在外部介面保持不變的情況下,自己可以修改內部的實現。Java是通過訪問控制關鍵字來實現的資訊隱藏的,一共有三個關鍵字:public、protected和private。如果要隱藏一個類的成員變數,只要在該成員變數的前面加上private,這樣外部就無法直接通過類的例項來訪問這些成員變量了。

  1. package com.tianmaying.domain;
  2. public class Post {
  3. int id;
  4. int title;
  5. public long getId() {
  6. return id;
  7. }
  8. public void setId(long id) {
  9. this.id = id;
  10. }
  11. public String getTitle() {
  12. return title;
  13. }
  14. public void setTitle(String title) {
  15. this.title = title;
  16. }
  17. public String getContent() {
  18. return content;
  19. }
  20. public void setContent(String content) {
  21. this.content = content;
  22. }
  23. }

使用get和set方法是為了程式的封裝,為了其它的類可以使用(設定和獲取)該類的私有方法。為什麼要用get和set方法而不是用直接用public呢?譬如寫個基類Base,然後有人繼承了Base:

  1. public class Base {
  2. public String[] data;
  3. }
  4. class Sub extends Base{
  5. public void show(){
  6. System.out.println(Arrays.toString(this.getData()));
  7. }
  8. }

可是有一天你發覺用ArrayList更好,你就把Base改成:

  1. public class Base {
  2. //public String[] data;
  3. public ArrayList data;
  4. }

然後所有Base的子類都無法編譯,直接呼叫到子類data域的類也無法編譯。回過頭來想,如果當初Base的data域是private,由get/set來訪問,那麼你可以輕鬆的修改Base, 子類無需改動:

  1. public class Base {
  2. //private String[] data;
  3. private ArrayList<String> data;
  4. public String[] getData() {
  5. return (String[]) data.toArray();
  6. }
  7. public void setData(String[] data) {
  8. this.data = new ArrayList<String>(Arrays.asList(data));
  9. }
  10. }

順便說一下,Eclipse有自動生成標準get/set函式的功能,所以生成get/set是很容易的事情,不要為了方便把成員都變成public。

19.類的繼承:繼承使用extends關鍵字,Java允許一個類僅能繼承一個其它類,即一個類只能有一個父類,這個限制被稱做單繼承性。後面將會學到介面(interface)的概念,介面允許多繼承。

  1. class People{
  2. String name;
  3. int age;
  4. int height;
  5. void say(){
  6. System.out.println("我的名字是 " + name + ",年齡是 " + age + ",身高是 " + height);
  7. }
  8. }

定義一個Teacher類來繼承People類

  1. class Teacher extends People{
  2. String school; // 所在學校
  3. String subject; // 學科
  4. int seniority; // 教齡
  5. void say(){// 覆蓋 People 類中的 say() 方法
  6. System.out.println("我叫" + name + ",在" + school + "教" + subject + ",有" + seniority + "年教齡");
  7. }
  8. void lecturing(){
  9. System.out.println("我已經" + age + "歲了,依然站在講臺上講課");
  10. }
  11. }

(1)name和age變數雖然沒有在 Teacher 中定義,但是已在People中定義,可以直接拿來用。
(2)Teacher是People的子類,People是Teacher類的父類。
(3)子類可以覆蓋父類的方法。
(4)子類可以繼承父類除private以外的所有的成員,不能繼承private的方法和變數,這也是封裝的原理。
(5)構造方法不能被繼承。

20.super關鍵字:super 關鍵字與 this 類似,this 用來表示當前類的例項,super 用來表示父類。super 關鍵字的功能:
(1)呼叫父類中宣告為private的變數。
(2)點取已經覆蓋了的方法。
(3)作為方法名錶示父類構造方法。

  1. public class Demo{
  2. public static void main(String[] args) {
  3. Dog dog = new Dog();
  4. dog.move();
  5. }
  6. }
  7. class Animal{
  8. private String desc = "Animals are human's good friends";
  9. // 必須要宣告一個 getter 方法
  10. public String getDesc() {
  11. return desc;
  12. }
  13. public void move(){
  14. System.out.println("Animals can move");
  15. }
  16. }
  17. class Dog extends Animal{
  18. public void move(){
  19. super.move(); // 呼叫父類的方法,super可以理解為父類物件
  20. System.out.println("Dogs can walk and run");
  21. // 通過 getter 方法呼叫父類隱藏變數
  22. System.out.println("Please remember: " + super.getDesc());
  23. }
  24. }

列印結果:

  1. Animals can move
  2. Dogs can walk and run
  3. Please remember: Animals are human's good friends


21.final關鍵字:一個變數可以宣告為final,這樣做的目的是阻止它的內容被修改。這意味著在宣告final變數的時候,必須初始化它,通常情況下,我們會使用final來定義一些常量。

22.在面向物件的領域一切都是物件,所有的物件都是通過類來描述的。如果我們要定義的一個類沒有足夠的資訊來描述一個具體的物件,還需要其他的具體類來支援,這個時候我們可以考慮使用抽象類。在類定義的前面增加abstract關鍵字,就表明一個類是抽象類。抽象類除了不能例項化物件之外,類的其它功能依然存在,成員變數、成員方法和構造方法的訪問方式和普通類一樣。由於抽象類不能例項化物件,所以抽象類必須被繼承,才能被使用。

abstract關鍵字同樣可以用來宣告抽象方法,抽象方法只包含一個方法名,而沒有方法體。抽象方法沒有定義,方法名後面直接跟一個分號,而不是花括號。宣告抽象方法會帶來以下兩個結果:(1)如果一個類包含抽象方法,那麼該類必須是抽象類。(2)任何子類必須重寫父類的抽象方法,否則就必須宣告自身為抽象類。

一般情況下,我們將一個類宣告為abstract的,是因為它包含了沒有具體實現的抽象方法。比如說我們給Graph類增加一個求面積的方法area(),因為我們不知道圖形的形狀,我們是無法給出實現的,只能交給特定的子類去實現,這時我們只能將area()宣告為abstract的,程式碼如下:

  1. abstract class Graph {
  2. String name;
  3. public Graph(){}
  4. public Graph(String name) {
  5. this.name = name;
  6. }
  7. public void show() {
  8. System.out.println("I'm a graph");
  9. }
  10. public abstract double area();
  11. }

這時Rectangle類就必須給出area()方法的實現,否則它自己也必須用abstract修飾。

  1. class Rectangle extends Graph{
  2. public double area() {
  3. return width * height;
  4. }
  5. }


23.介面:與抽象類類似的一個重要概念是介面。介面(Interface)是一組抽象方法的集合。介面中定義的方法沒有方法體,它們以分號結束。介面也和抽象類一樣,無法被例項化,但是可以被實現。一個實現介面的類,必須實現介面內所描述的所有方法,否則就必須宣告為抽象類。編寫介面和編寫類的方式是大體上是類似的,一個介面可以有多個方法,程式碼儲存在以介面命名且以.java結尾的檔案中。介面使用interface關鍵字進行定義。比如我們定義一個Animal介面:

  1. interface Animal {
  2. void eat();
  3. void sleep();
  4. }

這個介面包含了兩個抽象方法:eat()和sleep()。介面中的方法都是外部可訪問的,因此我們可以不需要用public修飾。介面中也可以宣告變數,一般是final和static型別的,要以常量來初始化,實現介面的類不能改變介面中的變數。比如我們可以在Animal介面增加一個成員變數TIMES_OF_EATING,表示動物每天吃飯的次數。介面訪問許可權有兩種:public許可權和預設許可權,如果介面的訪問許可權是public的話,所有的方法和變數都是public。預設許可權則同一個包內的類可以訪問。一個介面能繼承另一個介面,和類之間的繼承方式比較相似。介面的繼承使用extends關鍵字,子介面繼承父介面的方法。比如我們可以定義TerrestrialAnimal介面,表示陸棲動物,它繼承自Animal介面,同時還具有run()方法。

  1. interface TerrestrialAnimal extends Animal {
  2. void run();
  3. }

介面實現:類使用implements關鍵字實現介面。在類宣告中,implements關鍵字放在class聲明後面。介面支援多重繼承,即一個類可以同時實現多個介面。

  1. class Cat implements Animal {
  2. public void eat() {
  3. System.out.println("eat");
  4. }
  5. public void sleep() {
  6. System.out.println("sleep");
  7. }
  8. }

類需要對介面中的每一個方法都給出實現。我們可以使用介面型別來宣告一個變數,那麼這個變數可以引用到一個實現該介面的物件。比如:

Animal cat = new Cat()

通過介面來宣告變數,可以讓程式更具有擴充套件性,因為將來我們更方便替換介面的實現。

24.抽象類和介面的比較:

相同點:
都不能被例項化
都包含抽象方法,這些抽象方法用於描述系統能提供哪些服務,而這些服務是由子類來提供實現的
在系統設計上,兩者都代表系統的抽象層,當一個系統使用一棵繼承樹上的類時,應該儘量把引用變數宣告為繼承樹的上層抽象型別,這樣可以提高兩個系統之間的鬆耦合

不同點:
在抽象類中可以為部分方法提供預設的實現,從而避免在子類中重複實現它們;但是抽象類不支援多繼承。介面不能提供任何方法的實現,但是支援多繼承。
介面代表了介面定義者和介面實現者的一種契約;而抽象類和具體類一般而言是一種is-a的關係,即兩者在概念本質上是不同的。

25.Java異常:異常定義了程式中遇到的非致命的錯誤,比如如程式要開啟一個不存的檔案、網路連線中斷、除零操作、運算元越界、裝載一個不存在的類等情況。我們將可能出現異常的程式碼通過try/catch程式碼進行了處理,當異常發生時,系統能夠繼續執行,而沒有意外終止。當try程式碼塊中的語句發生了異常,程式就會跳轉到catch程式碼塊中執行,執行完catch程式碼塊中的程式程式碼後,系統會繼續執行catch程式碼塊之後的程式碼,try程式碼塊中發生異常語句後的程式碼則不會再執行。

26.throws關鍵字:下面是一個程式碼例項。

  1. package com.tianmaying;
  2. public class HelloWorld {
  3. private static void fun() {
  4. int x = 5 / 0;
  5. System.out.println(x);
  6. }
  7. public static void main(String[] args) {
  8. try {
  9. fun();
  10. } catch (Exception e) {
  11. e.printStackTrace();
  12. }
  13. System.out.println("program is still running here!");
  14. }
  15. }

面對的是一個fun()函式,如何知道這個函式是否需要呼叫新增try/catch處理呢?在Java中,這個問題是交給被呼叫的方法的實現者來解決的。在這個例子中,定義fun()方法的時候,我們在方法引數列表後面增加一個throws關鍵字,然後增加這個方法可能丟擲的異常,這種情況下呼叫者就必須使用try/catch進行處理了,否則編譯將無法通過。因此將程式碼改為:

  1. public class HelloWorld {
  2. private static void foo() throws Exception {
  3. int x = 5 / 0;
  4. System.out.println(x);
  5. }
  6. public static void main(String[] args) {
  7. try {
  8. foo();
  9. } catch (Exception e) {
  10. e.printStackTrace();
  11. }
  12. System.out.println("program is still running here!");
  13. }
  14. }

如果一個方法中的語句執行時可能生成某種異常,但是並不能確定如何處理,則此方法應宣告丟擲異常,表明該方法將不對這些異常進行處理,而由該方法的呼叫者負責處理。也就是如果程式中的異常沒有用try/catch捕捉異常以及處理異常的程式碼,我們可以在程式程式碼所在的函式(方法)聲明後用throws宣告該函式要丟擲異常,將該異常丟擲到該函式的呼叫函式中。

Exception類是java.lang.Throwable類的子類。在實際應用中,我們一般是使用Exception的子類來描述特定的異常的。Exception類是所有異常類的父類,Java語言為我們提供了許多Exception類的子類,分別對應不同的異常型別,例如:
ArithmeticException(在算術運算中發生的異常,如除以零)
NullPointerException(變數還沒有指向一個物件,就引用這個物件的成員)
ArrayIndexOutOfBoundsException(訪問陣列物件中不存在的元素)
使用Java內建的異常類可以描述在程式設計時出現的大部分異常情況。除此之外,使用者還可以自定義異常。使用者自定義異常類,只需繼承Exception類即可。在程式中使用自定義異常類,大體可分為以下幾個步驟:(1)建立自定義異常類(2)在方法中通過throw關鍵字丟擲異常物件(3)如果在當前丟擲異常的方法中處理異常,可以使用try-catch語句捕獲並處理;否則在方法的宣告處通過throws關鍵字指明要丟擲給方法呼叫者的異常,繼續進行下一步操作(4)在出現異常方法的呼叫者中捕獲並處理異常。

  1. public class BlogAppException extends Exception {//繼承Exception異常類
  2. private static final long serialVersionUID = 1L;
  3. private String command;// 可以給自定義異常增加成員變數,用以儲存額外的異常資訊
  4. public BlogAppException(String command) {
  5. this.command = command;
  6. }
  7. public String toString(){
  8. return "Exception happened when executing command " + command;
  9. }
  10. }

我們來增加一個可以丟擲這個異常的方法,並且在main方法中進行呼叫:

  1. public class HelloWorld {
  2. private static void bar() throws BlogAppException {
  3. System.out.println("let's assume BlogAppException happened when executing `create` command");
  4. // 為了演示,這裡我們假設執行create命令時,丟擲了異常
  5. throw new BlogAppException("create");
  6. }
  7. private static void foo() throws ArithmeticException {
  8. int x = 5 / 0;
  9. System.out.println(x);
  10. }
  11. public static void main(String[] args) {
  12. try {
  13. foo();
  14. bar();
  15. } catch (Exception e) {
  16. e.printStackTrace();
  17. }
  18. System.out.println("program is still running here!");
  19. }
  20. }

在catch多個異常時,如果使用catch(Exception e)語句,那麼它不能放在其他catch語句的前面,否則後面的catch永遠得不到執行,因為Exception是所有異常的父類。另外關於異常,在繼承時需要注意兩點:(1)一個方法被覆蓋時,覆蓋它的方法必須扔出相同的異常或異常的子類。(2)如果父類丟擲多個異常,那麼重寫(覆蓋)方法必須扔出那些異常的一個子集,也就是說,不能扔出新的異常。
在try/catch語句後,我們還可以有個finally語句,finally語句中的程式碼塊不管異常是否被捕獲總是要被執行的。finally還有一個特殊之處在於,即使try程式碼塊和catch程式碼塊中使用了return語句退出當前方法或break跳出某個迴圈 ,相關的finally程式碼塊都要執行。finally中的程式碼塊不能被執行的唯一情況是:在被保護程式碼塊中執行了System.exit(0)。

27.Java IO:下面的程式碼通過FileInoutStream和FileOutputStream來完成檔案內容的拷貝:

  1. import java.io.*;
  2. public class CopyFileByte {
  3. public static void main(String args[]) throws IOException
  4. {
  5. FileInputStream in = null;
  6. FileOutputStream out = null;
  7. try {
  8. in = new FileInputStream("input.txt");
  9. out = new FileOutputStream("output.txt");
  10. int c;
  11. while ((c = in.read()) != -1) { //返回-1 表示達到檔案結尾,首先判斷是否到檔案結尾,沒有的話賦值給c
  12. out.write(c);
  13. }
  14. }finally {
  15. if (in != null) {
  16. in.close();
  17. }
  18. if (out != null) {
  19. out.close();
  20. }
  21. }
  22. }
  23. }

InputStream和OutputStream是位元組流處理,最小單位是一個位元組8bits,而Java IO中還有一種字元流,它們處理的單位是16bits。

  1. import java.io.*;
  2. class ConsoleInOut {
  3. public static void main(String args[])throws IOException{
  4. InputStreamReader isr = null;
  5. try {
  6. isr = new InputStreamReader(System.in);//控制檯輸入,而不是標準輸入類似於上面的"input.txt"
  7. System.out.println("Enter characters, 'q' to quit.");
  8. char c;
  9. do {
  10. c = (char) isr.read();//第一次鍵盤鍵入q的時候,還是會輸出q
  11. System.out.println(c);
  12. } while(c != 'q');
  13. }finally {
  14. if (isr != null) {
  15. isr.close();
  16. }
  17. }
  18. }
  19. }

在這兩個語句當中,我們可以看到對異常的處理都是用try...finally,而不是try...catch...finally的形式,那什麼時候使用try-finally,什麼時候使用try-catch-finally呢?很顯然這取決於方法本身是否能夠處理try中出現的異常。如果自己可以處理,那麼直接catch住,不用拋給方法的呼叫者;如果自己不知道怎麼處理,就應該將異常向外拋,能夠讓呼叫者知道發生了異常。即在方法的簽名中宣告throws可能出現而自己又無法處理的異常,但是在方法內部做自己應該的事情。

28.陣列和集合的區別:(1)陣列宣告容納元素的型別,集合不宣告 (2)陣列是靜態的,大小固定,集合不固定 (3)陣列存放的型別只能是一種,集合可以有多種,不加泛型時型別為Object (4)陣列由於是java內建的資料型別,執行效率快 (5)ArrayList就是基於陣列建立的容器類

29.List是一個介面,而ArrayList是List介面的一個實現類。ArrayList類繼承並實現了List介面。因此,List介面不能被構造,也就是我們說的不能建立例項物件,但是我們可以像下面那樣為List介面建立一個指向自己的物件引用,而ArrayList實現類的例項物件就在這充當了這個指向List介面的物件引用。

30.多型:理解多型前先了解一下過載和覆蓋這兩個概念。

一個方法名,引數不同,這叫方法過載。(Overload)

  1. void foo(String str);
  2. void foo(int number);

父類與子類有同樣的方法名和引數,這叫方法覆蓋。(Override)

  1. class Parent {
  2. void foo() {
  3. System.out.println("Parent foo()");
  4. }
  5. }
  6. class Child extends Parent {
  7. void foo() {
  8. System.out.println("Child foo()");
  9. }
  10. }

父類引用指向子類物件,呼叫方法時會呼叫子類的實現,而不是父類的實現,這叫多型。

  1. Parent instance = new Child();
  2. instance.foo(); //==> Child foo()

31.Object類:Object類在Java中被定義為一個頂級父類,它是任何類的父類,我們可以顯示的繼承它,也可以隱式繼承,如以下例項

  1. public class Dog extends Object{
  2. }

  1. public class Dog{
  2. }

二者完全相同,Object類包含如下方法,1.equals(Object obj) 2.finalize() 3.getClass 4. hashCode() 5. notify 6. notifyAll() 7. wait() 8. Clone() 9. toString()由於Object類是任何類的父類,也就是說對Java中任意一個物件,都是可以用這九種方法的。

32.IO流:主要搞清楚InputStream,Reader,InputStreamReader,FileInputStream,BufferedReader,FileReader,BufferedInputStream.DataInputStream.

所有輸入流都是InputStream(位元組輸入流)和Reader(字元輸入流)的子類,InputStreamReader是位元組流和字元流之間的橋樑,能將位元組流輸出為字元流。熟悉了這兩點其他的就很好說了,其他的輸入流都是作為它們的子類。

InputStream:位元組輸入流,在搞清楚位元組輸入流之前,我們需要搞清楚位元組,所謂位元組(Byte),指的是一種計量單位,表示資料量多少。例如“11數3A+、”所佔的位元組數為9,怎麼來的呢?阿拉伯數字和英文字元佔的位元組數為1,中文字元“數”和中文符號“、”佔的位元組數為2,所以位元組數為9。

Reader:字元是指計算機中使用的文字和符號,比如“1、2、3、A、B、C、~!·#¥%……—*()——+、”等等。