Java編程思想第4版學習筆記(一)
第二章 一切都是對象(Hello World)
這個筆記本主要記錄了我在學習Java編程思想(第4版,中文版)的過程中遇到的重難點及其分析。主要參考了C++11版本的C++語言,對比了它們不同的部分。
知識點0:第一章概括
在探討第二章的內容之前,先簡要地概括一下第一章說了什么,第一章的標題叫做“對象導論”,第二章叫做“一切都是對象”,這種令人混淆的說法或許讓人乍一看很難區分第一二章講的內容有什么區別。
不過顯然,這兩章主要講述的內容是不同的——第一章主要講了面向對象編程中一些重要的概念,這些概念在任何一門適合配合面向對象編程的思想進行開發的語言上我們都能看到——就是 對象的概念、接口(類方法)的概念、用對象組合程序的概念、訪問控制的概念、繼承和派生的概念、多態的概念、容器的概念 和 泛型的概念。
第一章里提到了一些Java和這些概念的關聯和幾個細節,比如設計類的內聚性和耦合性、派生類對基類方法的覆蓋、編譯時的前期綁定和后期綁定、Java的一切基於Object的單根繼承結構、內存分配、並發 和 網絡編程等概念。第一章涵蓋了這些也許要把整本書閱讀完再回過頭看才能明白一些的基礎和復雜的概念,它們並不完整,有很多點都需要在實踐中不斷的補充完善。因此也許初學者不需要一開始糾結於弄明白第一章,而是要在學會C/C++之類的語言的一些基礎后,起碼編過程序,再來學習這本書,粗讀第一章,然后從第二章開始。
第二章主要講了Java中對象的表現形式——引用、基本類型和其包裝器類型、簡單的對象生命周期/作用域、創建類類型、類字段(數據成員)和方法、初始化、類內函數、包、靜態成員、HelloWorld、編譯運行、注釋和Javadoc。(這里面提到的概念比第一章少多了而且更具體),總之,第二章就是一個HelloWorld式的章節,淺顯的講了一下為了你能夠不那么迷惑的寫出Java語言的HelloWorld而需要知道的Java最重要的一些知識,雖然這些概念也仍然沒有得到充分的講解,不過第二章使你對Java中對象是怎么存在的有了一個基本的認識
。
下面就梳理一下第二章中比較重要和難懂的知識點。
知識點1:P21,2.1,創建和操縱對象
創建一個可以操控的對象
Java創建對象的語句形如這樣:new 類型(構建對象的參數-可選);,其中new為創建對象的關鍵字,類型可以是基本類型,包裝器類型以及各種類類型,()里是參數列表,代表調用了構造函數,分號代表語句的結尾。這個語句創建了指定類型的對象。要想使用這個對象,必須要聲明一個對象類型的引用,引用就是一個別名,Java里引用的概念和C++中引用的概念類似。比如String s = new String("1234");或者是String s = "1234";
初始化
第一句話定義並初始化了一個String類型的引用s,s被賦值為一個String類型的對象,這個對象被初始化為字符串字面值“1234”。
這里發生了兩次初始化,引用s的初始化,以及一個不具名的String類型對象的初始化。
一個引用必須在使用(除了賦值操作的其他操作)之前初始化,一個對象創建的時候就一定被構造函數初始化過了,一個未經初始化的引用,其初值為null,使用值為null的引用會導致報錯
。
內存布局
引用和基本類型的對象都被存儲到堆棧中。
其他
對象被存儲到堆中。
常量儲存在代碼內部。
作用域和生命周期
基本類型 和 對象引用 的作用域在它所在的語句塊內,生命周期從它被定義到它所在的語句塊結束。
Java對象的作用域在它
所在的語句塊內,生命周期
從它被定義到它被Java的自動垃圾回收機制回收。
知識點2:P23,2.2.2,基本類型及其包裝器類型
基本類型有哪些
boolean(布爾值),char(字符),byte(很小的整數),short(比較小的整數),int(整形),long(長整形),float(浮點數),double(高精度浮點數),void(空類型)。
什么是包裝器
基本類型沒有一個函數,不方便按照面向對象的思想向其傳遞信息,因此Java給每一個基本類型都對應了一個包裝器類型,它們是一些類類型,在堆中被創建,可以執行各種方法,更方便使用。
以上
類型對應的包裝器類型有哪些
Boolean(布爾值),Character(字符),Byte
(很小的整數),Short(比較小的整數),Integer(整形),Long(長整形),Float(浮點數),Double(高精度浮點數),Void(空類型)。
基本類型的特點
大小固定,存在堆棧中,過作用域就被釋放,不是對象,不需要用引用操控,沒有可以調用的函數,按值傳遞。
包裝器
類型的特點
大小固定,存在堆中,由GC釋放,本身是對象,需要依賴引用操控,有函數可以調用,按引用傳遞。
初始化基本類型的方法
可以一開始就用字面值初始化,比如char c = 'c';
也可以用另一個基本類型的值初始化,比如char ch = c;
還可以用這個基本類型的包裝器類型初始化基本類型的值,比如char ch2 = new Character('c');
如果不在創建之初就初始化一個作為類成員的基本類型,則這個基本類型的變量會得到一個默認值,比如0。
初始化 基本類型的包裝器類型的引用 的方法
可以一開始就用字面值初始化,比如Character c = 'c';
也可以 先用字面值初始化這個包裝器類型的對象,再用這個對象初始化這個包裝器類型的引用,比如
Character ch = new Character('c');
還可以
先用基本類型的值初始化這個包裝器類型的對象,再用這個對象初始化這個包裝器類型的引用,比如
Character ch = new Character(c);
知識點3:P25,2.4,類類型
什么是類類型
類類型是一種區別於基本類型的類型分類,它允許Java使用者自己創造一些新的類型和這些類型的細節。
創建和使用類類型
創建類類型的代碼看起來如下:class 類型名{ 類體 },其中,class是創建新類型的關鍵字,類型名是一個大寫字母開頭的名字,類體包括數據成員(屬性)和函數成員(方法)。
要想創建這個自定義類型的對象,需要使用下述語句:類型名 對象的引用名 = new 類型名(構造對象的參數-可選);
可見自定義的類類型和Java已經存在的類類型的用法基本一致。
什么是類屬性/字段/數據成員/域(在特定語境下)
字段(或者屬性,數據成員,域)基本都是一個意思,是組成類的兩個元素之一,它可以由一個或幾個,可以是基本類型或引用類型,是用來存儲類實例數據的類成員(或稱“實例域”)。
什么是類方法/類函數/函數成員
類方法(method)
是組成類的兩個元素之一,可以有一個或多個,是用來操控修改數據成員的子程序(可復用程序片段),在Java中,沒有全局的函數,一個函數(方法)必定屬於某個類。
知識點4:P27,2.5,類方法
定義和使用類方法
類定義一個類方法的語句形如 返回值類型 類方法名(參數列表){ 方法體 },必須定義在class后面的大括號里,其中,參數列表形如(類型1 參數1,類型2 參數2,etc)。如果返回類型不為void/Void,方法體中需顯式地寫出return語句來結束這個函數,形如return 返回值; 其中返回值可以為對象的引用或者基本類型的變量,也可以是一個字面值,一個常量。這個值的類型需和方法定義中的返回值類型一致或能非窄化地轉換為返回值類型。比如double func(){return 1;},其中返回的1是int類型字面值,它轉換到定義的double類型精度無損失(非窄化轉換)。因此這樣做可行。
使用一個類方法,需要用點運算符。形如 對象引用.類方法名(想傳入的參數);
靜態方法有特殊的調用方式,本章稍后就能看到。
知識點5:P28,2.6.1,import是什么,怎么用
包(Java類庫)和名字沖突
包就是一個Java類庫,是程序員寫好、打包好的一些類、方法的集合。
我們在寫Java程序中往往要根據一些已經實現好的功能做我們的新功能,而不是什么都自己開發。所以可以在你的Java編程IDE中進行設置來導入一個別人打包好的功能,通過在代碼中使用import 這個包的名.具體的類; 來使用你導入的包中一個具體的類,或者使用
import 這個包的名.*; 把這個語句放在一個Java文件的開頭,來導入這個包中所有的類。大部分編譯器都有編譯優化,會只編譯你用到的,不過仍然要說明這么一句,讓編譯器知道你可能會用到的內容。
Java自己就提供了豐富的類庫,其中java.lang類庫是基礎類庫,每次都會被自動導入,而java.uitl非常常用,類似於C++中的SL,常用的寫法是import java.util.*; 來導入java.util中的所有類。
我們可能導入了多個包,不同的包中可能聲明了同名的類,不過同一個文件中不能用同名的類,甚至在同一個方法下的不同作用域里,變量名都不能相同,所以顯然無論你導入了幾個包,它們中有幾個你想用的同名的類,你都只能使用其中的一個。
用
import 這個包的名.具體的類; 就可以明確的告訴編譯器,你想要使用哪個包里的這個名字的類。
很多包名好像一個網站的網址倒過來寫,比如com.google.xxxx(我隨便舉的例子)。這是因為網址是不重復的,不同的類庫的開發者很難擁有同一個網址,所以Java希望你給包起名時用自己的網站
倒過來的
網址。試想你和另一個人都使用了com.baidu.useless作為包名,里面都有一個叫做Date的類,這兩個類其實是不同的(但導入都要寫成import com.baidu.useless.Date;),別的開發者拿到你們兩個人的包想使用Date類時就會頭疼了。
知識點6:P29,2.6.3,static關鍵字
靜態成員的定義、性質和使用
static關鍵字用於放在定義語句的類型前面,說明正在定義的字段/方法是靜態的,被static修飾的類方法和類字段統稱為靜態成員。
比起同一個類型的其他成員,所有這個類型的實例中的靜態成員都是同一個對象,占用一個存儲空間,它不依賴某個具體實例化的對象而存在,即使沒有這種類型的對象,這種類型的靜態成員也能被訪問。
舉個例子,class StaticTest{ static int i = 47; } 在這個類中,i就是靜態成員,而且被預初始化為47,直接在類體中寫類似賦值的形式就行了,即使創建了兩個StaticTest類的實例,比如StaticTest st1 = new StaticTest();
StaticTest st2 = new StaticTest();,st1和st2中的i仍是同一個變量。
你可以試着把st2中的i賦值成88,寫下這個語句 st2.i = 88; ,之后你再檢查st1.i的值,就會發現它也變成了88。
訪問StaticTest的靜態數據成員有兩種方法,一種是上述例子中的st1.i,通過點運算符,鏈接對象引用和對象內成員,這個常見的訪問成員的方式對靜態成員同樣適用。還有一種訪問方法是靜態成員獨有的,就是 類名.靜態成員名 ,通過這種簡單的方法你也可以訪問到它。在StaticTest例子中想訪問靜態成員i還可以這么寫:StaticTest.i,效果和 st1.i 或者 st2.i 是沒有區別的。
靜態方法也可以這么訪問,比如
class StaticTest{ static void Func(){} },這個新的StaticTest類里定義了什么都不做的靜態方法(靜態函數成員)Func,可以使用StaticTest.Func();,在不創建StaticTest對象的情況下訪問這個類內靜態的函數。
知識點7:P30,2.6.3,主函數
主函數和HelloWorld
在目前的很多IDE下,Java可以有多個主函數,不過你要選擇其中的一個進行執行。主函數的寫法很簡單,不過因為Java的所有函數必須以類方法的形式存在,因此你需要先隨便創建一個單獨的類:
public class MainTest{}
class前面的public代表一種公開狀態的訪問權限,具體使用方法不在這章講,總之要寫成這樣。MainTest可以起成任何別的你喜歡的名字,不過一個Java文件里只能有一個public修飾的類,而且這個Java文件的文件名必須和public修飾的類名保持一致。
然后,在類中寫一個特殊的方法:
public class MainTest{
public static void main(String[] s){
//主函數第一行,程序執行從這里開始
}
}
就像這樣,類必須是publc的,方法必須是public static的(如你所見,它們並列修飾一個函數時,public關鍵字要在static前面),返回值為void,參數類型是一個String數組s,用來存儲命令行參數,就和其他的語言差不多。
這樣,一個主函數就寫好了,書上給出了一個很簡單的HelloWorld:
import java.util.*;
public class MainTest{
public static void main(String[] s){
System.out.println("Hello World!");
System.out.println(new Date());
}
}
讓我們稍微分析一下這個HelloWorld,首先是import java.util.*;,導入java.util包中的所有類,我們下面用到了Date類,因此需要引入這個包。
主函數里,System是一個類,out是System類的靜態對象,println是out對象的靜態方法,因此即使不創建System類,也能用點運算符訪問它的對象,再用點運算符訪問其對象的方法。
println是一種很常見的方法,println可以向控制台輸出它接收到的信息,比如一個字符串字面值"HelloWorld!"或者new Date(); 創建出的一個對象。
知識點8:P32,2.8,三種注釋
C風格注釋
你可以把注釋內容寫在/* */里,可跨多行,一旦標記了/*,它遇到下一個*/就會結束注釋。
單行注釋
你可以把注釋內容寫在//標記之后,知道本行結束,不可跨多行。
用於提取文檔的注釋(注釋文檔)
Java提供了Javadoc機制從源碼的注釋中提取和生成html文檔,不過這個注釋要符合特定格式,
用於提取文檔的注釋以/**開頭,以*/結束。這樣的注釋也可跨多行,注釋間每行開始如果有一個*或任意空白都可被忽略,
為了更方便的提取文檔,可以在這種注釋里直接加入html代碼,比如:
/**
*具體類信息可以到這里查找<br>
*<a href = "https://www.baidu.com"></a>
*/
把這段注釋文檔放在某個你想注釋的類,字段 或者 方法前面一行,再用javadoc提取文檔你就能看到你對這個類/字段或者方法的注釋,后面我們會提到,只能對標記為public或protect的類/域/方法做注釋。
也可以通過@符號加一些標記讓javadoc快速自動幫你生成符合情境的html文檔,比如:
/**
*@author TiriSane
*/
提取文檔后,這個注釋對應的
類/域/方法介紹上會有作者這一項。除了author還有很多標記可以用:
@see 類名
@see 類名#類成員名
@see可以在你介紹一個類成員時,鏈接到其他類/類成員的文檔。
{@link 類名#類成員名
你想要顯示的標簽名}
@link作用類似域@see,不過文檔中超鏈接處會顯示標簽名而不是類名。
@version
用於說明版本信息。
@since
用於說明代碼使用JDK的最早版本,比如@since JDK 1.8
@author
用於說明作者信息。
@param 參數名 描述
帶@param的注釋需放在一個類方法之前,用於說明這個方法的其中一個參數的意義。
@return 描述
帶@return的注釋需放在一個類方法之前,用於說明這個方法的返回值。
@throws 異常類 異常說明
目前還沒講到異常,簡言之,就是用來 放在方法前 描述這個方法在何種情況下可能會產生這種異常。
@deprecated
用於指出注釋文檔所注釋的
類/域/方法是過時的,下個版本可能就刪掉了,建議使用你代碼的人不要使用這個標記的
類/域/方法。
{@docRoot}
這個標簽往往配合一些注釋文檔里的html代碼使用,指出了文檔的根目錄,是一種相對路徑標記。
{@inheritDoc}
這個標記所在的注釋文檔要放在類前面,生成javadoc時會把它的直接繼承類的注釋復制到這個類里。
練習題
:P37
練習1:創建一個類,它包含一個int域(字段)和一個char域(字段),他們都沒有被(顯示地)初始化,將它們的值打印出來,以驗證
Java(對類內字段)執行了默認初始化。

1 class Test 2 { 3 int i; 4 char c; 5 } 6 7 public class MainTest { 8 public static void main(String[] args){ 9 Test t = new Test(); 10 System.out.println(t.i); 11 System.out.println(t.c); 12 } 13 }
練習2:
創建一個輸入"Hello, World"的程序,編譯和運行它。

1 public class MainTest { 2 public static void main(String[] args){ 3 System.out.println("Hello, World"); 4 } 5 }
練習3:
找出本章含有ATypeName的代碼段,將其改成完整的程序。

1 class ATypeName{} 2 3 public class MainTest { 4 public static void main(String[] args){ 5 ATypeName a = new ATypeName(); 6 } 7 }
練習4、5:
找出本章含有DataOnly的代碼段,將其改寫成一個程序,並把DataOnly的數據打印出來。

1 class DataOnly{ 2 int i; 3 double d; 4 boolean b; 5 } 6 7 public class MainTest { 8 public static void main(String[] args){ 9 DataOnly data = new DataOnly(); 10 data.i = 47; 11 data.d = 1.1; 12 data.b = false; 13 } 14 }
練習6:
編寫一個程序,使它含有本章定義的storage方法的代碼段,並調用之。

1 class Storage{ 2 int Storage(String s){ 3 return s.length()*2; 4 } 5 } 6 7 public class MainTest { 8 public static void main(String[] args){ 9 Storage st = new Storage(); 10 st.Storage("1234"); 11 } 12 }
練習7:
將和Incrementable相關的代碼段改寫成一個完整的可編譯源代碼。

1 class StaticTest { 2 static int i = 47; 3 } 4 5 class Incrementable{ 6 static void increment(){ StaticTest.i++; } 7 } 8 9 public class MainTest { 10 public static void main(String[] args){ 11 Incrementable icmt = new Incrementable(); 12 icmt.increment(); 13 14 Incrementable.increment(); 15 } 16 }
練習8:
編寫一個程序,展示無論你創建了某個特定類型的多少個對象,這個類中的某個特定的static域只有一個實例。

1 class StaticTest { 2 static int i; 3 } 4 5 public class MainTest { 6 public static void main(String[] args){ 7 StaticTest st0 = new StaticTest(); 8 StaticTest st1 = new StaticTest(); 9 StaticTest st2 = new StaticTest(); 10 11 st0.i = 1; 12 st1.i = 2; 13 st2.i = 3; 14 15 System.out.println(st0.i); 16 System.out.println(st1.i); 17 System.out.println(st2.i); 18 } 19 }
練習9:
編寫一個程序,展示自動包裝功能對所有的基本類型和包裝器類型都起作用。

1 public class MainTest { 2 public static void main(String[] args){ 3 boolean b = true; 4 char c = '0'; 5 byte by = 1; 6 short s = 2; 7 int i = 3; 8 long l = 4; 9 float f = 3.14F; 10 double d = 2.17; 11 12 Boolean B = true; 13 Character C = '0'; 14 Byte By = 1; 15 Short S = 2; 16 Integer I = 3; 17 Long L = 4L; 18 Float F = 3.14F; 19 Double D = 2.17; 20 Void V = null; 21 } 22 }
練習10:
編寫一個程序,打印出從命令行獲得的三個參數。為此,需要確定命令行數組中String的下標。

1 public class MainTest { 2 public static void main(String[] args) { 3 System.out.println(args[0]); 4 System.out.println(args[1]); 5 System.out.println(args[2]); 6 } 7 }
練習11:
將AllTheColorsOfTheRainbow這個示例改寫成一個程序,然后編譯、運行。

1 class AllTheColorsOfTheRainbow { 2 int anIntegerRepresentingColors; 3 void changeTheHueOfTheColor(int newHue) { } 4 } 5 6 public class MainTest{ 7 public static void main(String[] args){ 8 AllTheColorsOfTheRainbow acr = new AllTheColorsOfTheRainbow(); 9 acr.changeTheHueOfTheColor(1); 10 } 11 }
練習12、13、14:略,javadoc操作相關。
練習15:
使用練習2的程序,加入注釋文檔,並用Javadoc提取出html文件查看效果。

1 /** 2 * @author TiriSane 3 * @version 1.0 4 * @since JDK 1.8 5 */ 6 public class MainTest { 7 /** 8 * @author TiriSane 9 * @version 1.0 10 * @since JDK 1.8 11 * @param args 用於接收命令行參數 12 * @return 沒有返回值 13 */ 14 public static void main(String[] args){ 15 System.out.println("Hello, World"); 16 } 17 }
練習16:找到第5章的Overloading.java示例,並為它加入javadoc文檔,之后用Javadoc提取出html文件查看效果。

1 /** 2 * @author Bruce Eckel 3 * @version 1.0 4 * @since JDK 1.5 5 */ 6 class Tree{ 7 int height; 8 Tree(){ 9 System.out.println("Planting a seeding"); 10 height = 0; 11 } 12 13 Tree(int initialHeight){ 14 height = initialHeight; 15 System.out.println("Creating new Tree that is " + height + " feet tall"); 16 } 17 18 void info(){ 19 System.out.println(" Tree is " + height + " feet tall"); 20 } 21 22 void info(String s){ 23 System.out.println(s + ": Tree is " + height + " feet tall"); 24 } 25 } 26 27 public class Overloading { 28 public static void main(String[] args){ 29 for(int i = 0;i<5;i++){ 30 Tree t = new Tree(i); 31 t.info(); 32 t.info("overloaded method"); 33 } 34 new Tree(); 35 } 36 }