1.軟件開發概述
軟件生命周期
軟件生命周期:軟件的產生到報廢的整個過程
軟件生命周期內有:問題定義,可行性分析,總體描述,系統設計,編碼,測試和調試,驗收和運行,維護升級到報廢等階段.
- 問題定義及規劃:此階段是軟件開發方與需求方共同討論,主要確定開發目標及其可行性.
- 需求分析: 在確定軟件開發可行的情況下,對軟件需要實現的各功能進行詳細分析.需求分析階段是一個很重要的階段,這一階段做的好,將為整個軟件開發項目的成功打下良好的基礎.
- 軟件設計: 此階段主要根據需求分析的結果,把整個軟件系統劃分為大大小小的多個模塊,設計出每一個模塊的具體結構. 如系統框架設計,數據庫設計等.軟件設計一般分為總體設計和詳細設計.
- 程序編碼:此階段是將軟件設計的結果轉換為計算機可運行的程序代碼. 在程序編碼中必須要制定統一,符合標準的編寫規范,以保證程序的可讀性,易維護性,提高程序的運行效率.
- 軟件測試:在軟件設計完成后要經過嚴密的測試,以發現軟件在整個設計過程中存在的問題并加以糾正,整個測試過程分單元測試(白盒),集成測試(黑盒,功能測試,強度性能測試)以及系統測試三個階段進行.測試的方法主要有白盒測試和黑盒測試兩種. 在測試過程中需要建立詳細的測試計劃并嚴格按照測試計劃進行測試,以減少測試的隨意性.
6)運行維護:安裝部署軟件系統,修復軟件中存在的bug和升級系統.在軟件開發完成并投入使用后,由于多方面的原因,軟件不能繼續適應用戶的需求.要延續軟件的使用壽命,就必須對軟件進行維護. 軟件的維護包括糾錯性維護和改進型維護兩個方面.
軟件設計原則:
為了提高軟件的開發效率,降低軟件的開發成本,一個良好的軟件系統應該具有以下特點:
1)可重用性:遵循DRY原則,減少軟件中的重復代碼
2)可拓展性:當軟件需要升級增加新的功能,掙夠在先有的系統架構上方便地創建新的模塊,而不需要改變軟件現有的結構,也不會影響以及存在的模塊.
3)可維護性:當用戶需求發生變化時,只需要修改局部的模塊中的少量代碼即可
如何讓軟件系統達到上述特點,我們對模塊的要求:
- 結構穩定性: 在軟件設計階段,把一個模塊劃分為更小的模塊時,設計合理,是的系統結構健壯,以便適應用戶的需求變化.
- 可拓展性:當軟件必須增加新的功能時,可在現有模塊的基礎上創建出新的模塊,該模塊繼承了原有模塊的一些特性,并且還具有一些新的特性,從而實現軟件的可重用和可拓展性.
- 可組合性: 若干模塊經過組合,形成大系統,模塊的可組合性提高軟件的可重用和維護性,并且能簡化軟件開發過程.
4)高內聚性:內聚,強調一個系模塊內的功能聯系,每個模塊只完成特定的功能,不同模塊之間不會有功能的重疊,高內聚性可以提高軟件的可重用性和可維護性
5)低耦合性:耦合,強調多個模塊之間的關系,模塊之間相互獨立,修改其一個模塊,不會影響其他的模塊,低耦合性提高了軟件的可維護性.
2.軟件的開發方式:
面向過程的開發
面向對象的開發
面向過程:一中較早的編程思想,顧名思義就是站在過程的角度思考問題,強調的就是功能行為,功能的執行過程,即先干啥后干啥.而每個功能我們都可以使用函數(類似于方法)這些步驟一步一步實現,使用的時候依次調用就可以了
面向過程的設計:
最小的程序單元是函數,每個函數負責完成某一個功能,用以接受輸入數據對輸入數據進行處理,然后輸出結果數據.
整個軟件系統有一個個的函數組成,其中作為程序入口的函數稱之為主函數,主函數依次調用其它函數,普通函數之間可以相互調用,從而實現整個系統功能
面向過程的缺陷:
面向過程的設計,是采用置頂而下的設計方式,在設計階段就需要考慮每一個模塊應該分解成哪些子模塊,每一個子模塊又細分為更小的子模塊,如此類推,直到將模塊細化為一個個函數.
存在的問題:
設計不夠直觀,與人類的習慣思維不一致.
系統軟件適應性差,可拓展性差,維護性低.
面向過程的最大問題在于隨著系統的膨脹,面向過程將無法應對,最終導致系統的崩潰.未解決這一種軟件危機,我們提出面向對象思想
3.軟件設計之面向對象
面向對象:一種基于面向過程的新的編程思想,顧名思義就是站在對象的角度思考問題,我們把多個功能合理的放到不同對象里,強調的是具備某些功能的對象.具備某種功能的實體,我們稱之為對象.
面向對象最小的程序單元是類
面向對象更加符合我們常規的思維方式,穩定性好,可重用性強,易于開發大型軟件產品,有良好的可維護性.
軟件工程上,面向對象可以使工程更加模塊化,實現更低的耦合和更高的內聚
當然上述例子僅僅只是說明了面向對象的一個特征--封裝.除此以外面向對象還有兩大特征,我們再具體講解的時候再做分析.
三大特征:
(1)封裝(encapsulation)
(2)繼承(inheritance)
(3)多態(polymorphism)
封裝是指將對象的實現細節隱藏起來,然后通過公共的方法來向外暴露該對象的功能.
繼承是面向對象實現軟件服用的重要手段,當子類繼承父類后,子類是一種特殊的父類,能直接或間接獲得父類里的成員.
多態是可以直接把子類對象賦給父類變量,但是運行時依然表現出子類的特征,這意味著同一類型的對象在運行時可能表現出不同的行為特征.
抽象:從特定的角度出發,從已經存在的一些食物中抽取我們所關注的特性、行為,從而形成一個新的事物的思維過程,是一種從復雜到簡潔的思維方式。
4.對象和類的關系
類:具有相同特性(狀態)和行為(功能)的對象的抽象就是類。因此對象的抽象是類,類的具體化就是對象。也可以說類的實例是對象,類實際上就是一種數據類型
類具有特性,對象的狀態,用成員變量來描述,封裝數據
類具有功能,對象的行為,用方法來表示
類是對象的類型/模板。創建一個對象,就是使用一個類作為構建該對象的基礎。
對象是類的實例,類是對象的模板
5.類的定義
類的定義
[修飾符] class 類名
{
0~N個成員變量(字段/Field),不要叫屬性(JavaBean中特殊的一種成員)
0~N個方法
}
不加static屬于對象,加上static屬于類
定義類的注意事項:
1)如果類使用了pubic修飾符,必須保證當前文件名和當前類名相同.
2)類名使用名稱表示,類表示某一類事物,首字母大寫,如果是多個單詞組成使用駝峰表示法
3)在面對對象的過程中,定義類的時候,專門為描述對象提供一個類,該類不需要main方法。專門再定義一個測試類/演示類來運行程序
6.對象的創建和調用方法
1)根據類來創建對象的語法
類名 變量=new 類名(); 表示創建一種這種類型(類)的變量
2)給字段設置值
對象變量.字段名=該類型的值;
3)獲取字段的值
該字段類型 變量=對象變量.字段值;
4)通過對象調用方法
對象變量.方法(實參);
class Servent {
String name;
int age;
void shopping() {
System.out.println("買菜...");
}
void cook() {
System.out.println("做飯...");
}
void wash() {
System.out.println("洗碗...");
}
}
public class Demo {
public static void main(String []args) {
// 創建一個對象
Servent s1 = new Servent();
// 給對象起名和設置年齡
s1.name = "小李";
s1.age = 18;
System.out.println(s1.name + ", "+ s1.age);
s1.shopping();
s1.cook();
s1.wash();
}
}
運行結果:
小李, 18
買菜...
做飯...
洗碗...
- 對象的實例化過程內存分析
public class Demo {
public static void main(String []args) {
// 創建一個對象
Servent s1 = new Servent();
// 給對象起名和設置年齡
s1.name = "小麗";
s1.age = 18;
// 創建另一個對象
Servent s2 = new Servent();
s2.name = "Lucy";
s2.age = 20;
// 創建另一個對象
Servent s3 = new Servent();
s3.name = "Lily";
s3.age = 22;
s2 = s3;
System.out.println(s2.name); // Lily
System.out.println(s3.name); // Lily
s1 = null;
System.out.println(s1.name); // NullPointerException
}
}
7.對象實例化過程-內存分析-生命周期
(1)對象的打印操作 其實打印出來的是對象的十六進制hashcode值,如:@74a14482。
(2)對象比較操作
對于基本數據類型來說,是比較的值。
對于引用數據類型來說,是比較的是內存中的地址值。
每次new,表示在堆中開辟一塊新內存空間,不同的內存空間,地址值不同。object.equal()
(3)生命周期
對象的時候:使用new關鍵字,就會在內存開辟空間,此對象開始存在。
對象的結束:當堆中的對象,沒有被任何變量所引用,此時該對象就成了垃圾,就等著垃圾回收器(GC)來回收該垃圾,當被回收的時候,對象就被銷毀,回收垃圾的目的在于釋放更多的內存空間
(4)匿名對象:沒有名稱的對象,創建對象之后沒有賦給某一個變量。匿名對象只是在堆中開辟一塊新的內存空間但是沒有把該空間地址賦給任何變量,因為沒有名稱,匿名對象僅僅只能使用一次,使用完成之后就變成了垃圾。一般的,把匿名對象作為方法的實參傳遞:如new Servent()
就是一個匿名對象
8.構造方法/構造器/Constructor。
作用:(1)創建對象,必須和new 一起使用(2)完成對象的初始化操作。
特點:(1)構造器的名稱和當前所在類的名稱相同(2)禁止定義返回類型(3)不需要使用return語句。
其實構造器是有返回值的,返回的是當前創建對象的引用地址。
默認構造器的特點:(1)符合構造器特點。(2)無參數的(3)無方法體的(4)如果類A使用了pulic 修飾,則編譯器創建的構造器也用public修飾。若沒有使用public修飾,那么編譯器創建的構造器也沒有public修飾
構造器如果我們沒有提供構造器,則編譯器在編譯時創建一個缺省的構造器。但是,如果我們顯示定義了一個構造器,則編譯器不再創建默認構造器。因此可以得出一個結論:某一個類中至少存在一個構造器
9.自定義構造器和構造器重載
顯示寫出構造器,則編譯器就不會再產生默認的構造器
方法的重載:避免了在同一個類中,相同功能的方法名字不同的問題
構造器是一種特殊的方法,也可以存在重載
// 表示人類
class Person {
String name = null;
// 顯示寫出構造器,則編譯器就不會再產生默認的構造器
// 自定義構造器
Person() {
System.out.println("無參數");
}
Person(String a) {
System.out.println(a);
name = a;
}
}
public class PersonDemo {
public static void main(String[] args) {
// 創建對象,其實是在調用構造器
new Person();
Person p1 = new Person("haha"); // 表示調用Person類中,帶有一個String類型的參數
System.out.println(p1.name);
}
}
10.static修飾符
static修飾符表示靜態的,可修飾字段,方法,內部類,其修飾的成員屬于類,也就是說static修飾的資源屬于類級別,而不是對象級別(只屬于類,不屬于對象)。
static修飾符的特點:
(1)static修飾的成員(字段/方法),隨著所在類的加載而加載。當JVM把字節碼加載進JVM的時候,static修飾的成員已經在內存中存在了。
(2)優先于對象的存在:對象是我們手動通過new關鍵字創建出來。
(3)static修飾的成員被該類型的所有對象所共享。根據該類創建出來的任何對象,都可以訪問static成員,其本質依然使用類名詞訪問和對象沒有任何關系
(4)直接使用類名訪問static成員:因為static修飾的成員直接屬于類,不屬于對象,所以可以直接使用類名訪問static成員。
11.類成員和實例成員的訪問
類中的成員:字段,方法,內部類
類成員:使用static修飾的成員
實例成員:沒有使用static修飾的成員
類成員只能訪問類成員,實例成員只能訪問實例成員
類成員直接屬于類,可以通過類來訪問static字段和static方法
實例成員只屬于對象,通過對象來訪問非static字段和非static方法
注意:通過反編譯可以看出來對象其實可以訪問類成員,但是底層依然使用類名來訪問
在static方法中只能調用static成員
非static方法中,可以訪問靜態成員,也可以訪問實例成員
什么時候用static,什么時候不用static?
如果這個狀態/行為屬于整個事物(類),就直接使用static修飾,被所有對象所共享
在開發中,往往把工具方法使用static修飾。如果不使用static修飾,則這些方法屬于該類的對象,我們得先創建對象在調用方法。在開發中工具類只需要一分即可,可能創建N個對象,此時我們往往把該類設計成單例,但是還是有點麻煩。因此在一般的開發中,涉及到工具方法,為了調用簡單,直接使用static修飾即可
12.定義變量的語法
數據類型 變量名=值;
變量根據在類中定義位置的不同,分成兩大類;
成員變量:全局變量/字段(Field),不要稱之為屬性(錯誤).直接定義在類中,方法外面.有初始值
1):類成員變量---使用static修飾的字段.
2):實例成員變量---沒有使用static修飾的字段.
局部變量:變量除了成員變量,其他都是局部變量.沒有初始值
1);方法內部的變量.
2)方法的形參.
3):代碼塊中的變量,一對{}.
變量的初始值
變量的初始值:初始化才會在內存中開辟空間.開辟空間的目的在于存儲數據
成員變量:默認是有初始值的;
(1)整數類型(byte、short、int、long)的基本類型變量的默認值為0。
(2)單精度浮點型(float)的基本類型變量的默認值為0.0f。
(3)雙精度浮點型(double)的基本類型變量的默認值為0.0d。
(4)字符型(char)的基本類型變量的默認為 “/u0000”。
(5)布爾性的基本類型變量的默認值為 false。
(6)引用類型的變量是默認值為 null。
(7)數組引用類型的變量的默認值為 null。當數組實例化后,如果沒有沒有顯式地為每個元素賦值,Java 就會把該數組的所有元素初始化為其相應類型的默認值。
局部變量:沒有初始值,所以必須先初始化才能使用.
變量的作用域
變量根據定義的位置不同,各自的作用域也是不同的,看變量所在的那對{}.
成員變量:在整個類中都有效.
局部變量:在開始定義的位置開始,到緊跟著結束的花括號為止.
成員變量,可以先使用后定義.局部變量必須先定義而后才能使用
13.變量的生命周期
變量的作用域指的是變量的存在范圍,只有在這個范圍內,程序代碼才能訪問它。當一個變量被定義時,它的作用域就確定了
變量的作用域決定了變量的生命周期,作用域不同,生命周期就不一樣
變量的生命周期指的是一個變量被創建并分配內存空間開始,到該變量被銷毀
存在位置 | 生命周期開始 | 生命周期結束 | 在內存中的位置 | |
---|---|---|---|---|
類變量 | 字段,使用static修飾 | 當所在字節碼被加載進JVM | 當JVM停止 | 方法區 |
實例變量 | 字段,沒有使用static修飾 | 當創建所在類的對象的時候 | 當該對象被GC回收 | 堆 |
局部變量 | 方法形參,代碼塊中,方法內 | 當代碼執行到初始化變量的時候 | 所在的方法/代碼塊結束 | 當前方法的棧幀中 |
局部變量定義后,必須顯示初始化后才能使用,因為系統不會為局部變量執行初始化操作。這就意味著,定義局部變量后,系統并未為這個變量分配內存空間。直到程序為這個變量賦值時,系統才會為局部變量分配內存,并將初始化值保存到該內存中。
局部變量不屬于任何類或實例,因此他總是保存在其所在方法的棧內存中。
基本數據局部變量:直接把這個變量的值保存到該變量所對應的內存中
引用數據局部變量:這個變量內存中存的是地址,通過該地址引用到該變量的實際引用堆里的對象。
棧內存中的變量無需系統垃圾回收,其往往隨方法或代碼塊的運行結束而結束。
什么時候使用成員變量和局部變量:
(1)考慮變量的生存時間,這會影響內存開始。
(2)擴大變量作用域,不利于提高程序的高內聚。
開發中應該盡量縮小變量的作用范圍,如此在內存中停留時間越短,性能也就越高。
不要動不動就使用static修飾,一般,定義工具方法的時候,static方法需要訪問的變量,該變量屬于類,此時才使用static來修飾
也不要動不動就使用成員變量,因為存在著線程安全問題,能使用局部變量就使用局部變量
14.packag關鍵字
Java中特殊的文件夾稱之為包(package)
關鍵字:package,專門用來給當前Java文件設置包名
語法格式: package 包名.子包名.子子包:
必須把該語句作為java文件中,并且是第一行代碼(所有代碼之前).
注意:編譯命令:javac -d . PackageDemo.java 運行命令:java abc.xyz.PackageDemo
1):package規范:
(1)包名必須遵循標識符規范/全部小寫;
(2)企業開發中,包名使用公司域名倒寫;例如com._zntq;
(3)在安卓中,如果package中使用了下劃線__
,則不能部署在模擬器上;此時也可以使用一個字母來代替下劃線;比如:package com.mzntq;
(4)語法格式:package 域名倒寫.模塊名.組件名.
(5)自定義的包名,不能以java開頭,因為java的安全機制會檢查這個引起報錯
2):類的名稱
類的簡單名稱:定義類的名稱 PackageDemo;
類的全限定名稱:包名.類名 com.zntq.PackageDemo;
3):在開發中,都是先有package,再定義類;
4):Java(JDK)中的包名
包名 | 描述 |
---|---|
java.lang | 語言核心類,系統自動導入,只要用Java,都會用到這個包 |
java.util | Java工具類、集合框架、時間、日歷等都是用這個包 |
java.net | 網絡編程接口和類,用到網絡相關的應用都要用到這個包 |
java.io | 流的接口和類,用到讀寫文件或者圖片等要用到這個包 |
java.text | Java格式化相關類,以及之后用到的國際化要用到這個包 |
java.sql | jdbc相關接口和類,操作Java連接數據庫要用到這個類 |
java.awt | 抽系窗口工具集相關接口和類,做一個類似QQ一樣的軟件,界面得使用這個包 |
java.swing | 圖形用戶界面相關接口和類(可跨平臺) |
15.import關鍵字
當A類和B類不在同一個包中,若A類需要使用到B類,此時就得讓A類中去引入B類。
沒有使用import之前,操作不在同一個包中的類需要全限定名來操作。
public class ImportDemo {
public static void main(String[] args) {
int[] arr = new int[]{11,-2,3,-4,0,6};
// 打印數組
String ret = java.util.Arrays.toString(arr);
System.out.println(ret);
// 對數組進行排序
java.util.Arrays.sort(arr);
// 重新打印數組
ret = java.util.Arrays.toString(arr);
System.out.println(ret);
}
}
解決方案:使用import語句直接把某個包下的類導入到當前類中。
語法格式:import 需要導入類的全限定名;
此后,在本java文件中,只需要使用類的簡單名稱即可。
import java.util.Arrays;
public class ImportDemo {
public static void main(String[] args) {
int[] arr = new int[]{11,-2,3,-4,0,6};
// 打印數組
String ret = Arrays.toString(arr);
System.out.println(ret);
// 對數組進行排序
Arrays.sort(arr);
// 重新打印數組
ret = Arrays.toString(arr);
System.out.println(ret);
}
}
編譯器會自動去java.lang包中去尋找我們用到的類,比如String、System(這些就在java.lang的包中),所以用到這些類不需要導入。由此可以得出推論:非java.lang包的類都需要導入。
import java.util.Arrays;
import java.util.Set;
import java.util.List;
public class ImportDemo {
public static void main(String[] args) {
int[] arr = new int[]{11,-2,3,-4,0,6};
// 打印數組
System.out.println(Arrays.toString(arr));
// java.util.Set s;
// java.util.List l;
Set s;
List l;
}
}
上述代碼中表示引入了java.util包下的Arrays類、Set類、List類
import java.util.Arrays;
import java.util.Set;
import java.util.List;
但是如果要用到很多java.util包下的類,就需要不斷引入,因此可以使用通配符來簡單代替:**import java.util.* **
import 類的全限定名:只能導入某一個類。
import 包名.子包名.* :表示會引入該包下的所有的類到當前文件中使用到的類。注意:用到哪些就加載哪些,用不到的即使是通配符也不會引入
import java.util.*:此時的*表示類名。
在Eclipse工具在,即使我們使用了通配符*,在格式化代碼的時候,也會轉換為N條import語句。
上邊的代碼中有這樣一段:
Set s;
List l;
因為只定義了沒有使用,因此從反編譯的代碼中可以看到并沒有引入import java.util.Set; import java.util.List;
如果使用后,這樣在反編譯中就會引入import java.*
注意:編譯器會默認找java.lang包下的類,但是不會去找java.lang的子包下的類。比如:java.lang.reflect.Method類,當用到該類時,也得使用import java.lang.reflect.Method進行引入。
16.靜態導入(static import)
import java.util.Arrays;
public class ImportDemo {
public static void main(String[] args) {
int[] arr = new int[]{11,-2,3,-4,0,6};
// 打印數組
String ret = Arrays.toString(arr);
System.out.println(ret);
// 對數組進行排序
Arrays.sort(arr);
// 重新打印數組
ret = Arrays.toString(arr);
System.out.println(ret);
}
}
在上述的代碼中,每次使用Arrays類中的靜態方法。即使已經使用了import語句,但每次都需要使用Arrays類名去調用靜態方法,這樣使用不太方便,期望能把Arrays類中的靜態成員像當前類中的靜態成員一樣調用,如sort(arr)。這時就要使用靜態導入:
語法格式:
import static 類的全限定名.該類中static成員名
import java.util.Arrays;
import static java.util.Arrays.sort;
public class StaticImportDemo {
public static void main(String[] args) {
int[] arr = new int[]{11,-2,3,-4,0,6};
// 打印數組
String ret = Arrays.toString(arr);
System.out.println(ret);
// 對數組進行排序
sort(arr);
// 重新打印數組
ret = Arrays.toString(arr);
System.out.println(ret);
}
}
為了簡便同樣可以直接使用通配符*
import static 類的全限定名.* 此時的*表示當前類的任意使用到的靜態成員
注意:雖然這樣可以使用,但是仍然不建議這樣用,因為使用了通配符引入兩個類中的靜態成員,而這兩個類中又有重名的方法,就會導致出現問題。通過反編譯工具,所謂的靜態導入也是一個語法糖,是一個編譯器級別的新特性(在底層還是使用類方法進行調用)。在實際開發中,不使用靜態導入,因為這樣會分不清楚一個靜態方法或者字段到底來源于哪個類。Eclipse工具,當格式化代碼的時候,就自動取消了所有的靜態導入,還是變成使用類名導入。因此只需要知道有這個靜態導入即可
17.理解封裝思想
什么是封裝?封裝是面向對象的三大特征之一,另外兩個特征是:繼承和多態
(1)封裝就是將對象的狀態和行為看成一個統一的整體,將二者存放在一個獨立的模塊中(類)
(2)“信息的隱藏”,封裝能夠提供安全性,盡可能隱藏對象實現功能,想外暴露方法,保證外界安全訪問功能。把所有數據信息隱藏起來,盡可能隱藏多的功能,只向外部暴露便捷的方法,以供調用。
將所有的字段都私有化,使用private關鍵字修飾,不準外界訪問;把方法使用public修飾,允許外界訪問
加了private后字段就只能在本類中訪問,要想訪問就需要設置setter方法并把傳過來的數保存到對象之中
一般來說,所有的字段都應該隱藏起來.
封裝的好處:
(1) 讓調用者方便,正確的調用系統功能,防止調用者隨意修改系統屬性,保證安全性。
(2)提高組件的重用性
(3)達到系統之間的低耦合和高內聚。內部發生變化時,只要暴露的接口不變,就不會影響到其他模塊。
高內聚:把該模塊的內部數據,功能細節隱藏在模塊內部,不允許外部直接干預
低耦合:該模塊只需給外界暴露少量功能方法
class Human {
String name;
private int age; // 如果年齡設置成了負數,那么就會導致不安全或者不合理
// private修飾后,age只能在該類中訪問,其他類無法訪問
// setter方法,專門用于給對象設置age數據,并把傳過來的數據保存到對象中
void setAge(int a) {
if (a < 0) {
System.out.println("年齡不能為負數");
} else {
age = a; // 把參數的值,賦給age變量
}
}
}
public class HumanDemo {
public static void main(String[] args) {
// 創建一個Human對象
Human p = new Human();
p.name = "Well";
p.setAge(-17);
// System.out.println(p.name + ", " + p.age);
}
}
18.權限訪問修飾符
封裝其實就是讓有些類看不到另外一些類里邊做了什么事情。所以Java提供了訪問權限修飾符來規定在一個類里面能看到什么,能暴露什么
訪問權限控制
private 表示私有的,表示類訪問權限,只能在本類中訪問,離開本類之后就不能直接訪問;
不寫(缺省):表示包私有,表示包訪問權限,訪問者的包必須和當前定義類的包相同才能訪問(子包也不行);
protected: 表示子類訪問權限,同包中的類可以訪問,即使是不同包,但是有繼承關系,也可以訪問;
public: 表示全域的,公共訪問權限,如果某個字段/方法使用了public修飾,則可以在當前項目中任何地方訪問。
修飾符 | 類內部 | 同一個包 | 子類 | 任何地方 |
---|---|---|---|---|
private | ?? | |||
無 | ?? | ?? | ||
protected | ?? | ?? | ?? | |
public | ?? | ?? | ?? | ?? |
一般地,字段都使用private修飾,表達隱藏,為了安全性。擁有實現細節的方法,一般使用private修飾,不希望外界(調用者)看到該方法的實現細節。
一般地,方法我們用public修飾,供外界直接調用。
一般地,我們不用缺省,即使要使用,也僅僅是暴露給同包中的其他類。
protected:一般在繼承關系中,父類需要把一個方法只暴露給子類。
19.JavaBean規范
JavaBean是一種Java語言寫成的可重用組件(類)。
必須遵循一定的規范:
(1)類必須使用public修飾;
(2)必須保證公共無參數構造器,即使手動提供了帶參數的構造器,也必須提供無參數構造器;
(3)包含了屬性的操作手段(給屬性賦值,獲取屬性值)。
分類:
(1)復雜:UI.比如Button、Panel、Window類
(2)簡單:domain,service組件,封裝數據,操作數據庫,邏輯運算等。(封裝有字段,提供getter/setter)
成員:
(1)方法:Method;
(2)事件:event;
(3)屬性:property。
屬性:
(1)attribute:表示狀態,Java中沒有該概念,很多人把字段稱之為屬性(attribute),不要把成員變量叫做屬性
(2)property:表示狀態,但是不是字段,是屬性的操作方法(getter/setter)決定的,組架中使用的大多數是property。
為了能讓外界(其他類)訪問到本類中的私有字段成員,我們專門提供getter以及setter方法。
getter方法:僅僅使用于獲取某一個字段儲存的值。
//去掉get,把首字母小寫,得到name,此時name才是屬性。
setter方法:僅僅用于給某一個字段設置需要儲存的值。
每一個字段都得提供一對getter/setter,以后使用Edlipse工具之后getter/setter都是自動生成。
class Human {
private int age; // 年齡
private boolean sex; // 性別
private String name; // 姓名
public int getAge() {
return age;
}
public void setAge(int a) {
age = a;
}
public boolean isSex() {
return sex;
}
public void setSex(boolean s) {
sex = s;
}
public String getName() {
return name;
}
public void setName(String n) {
name = n;
}
}
public class Person {
public static void main(String[] args) {
Human human = new Human();
human.setAge(10);
human.setName("小明");
human.setSex(true);
System.out.println("姓名是:" + human.getName() + ", " + "年齡是:" + human.getAge()) ;
}
}
注意:
(1)如果操作字段是boolean類型的,此時不應該叫做getter方法,而是is方法,把getName變成isName.
(2)在JavaBean中有屬性這個概念,只有標準情況下字段名和屬性名才相同。
20.this關鍵字
this表示當前對象,主要存在于兩個位置中:
(1)構造器中:就表示當前創建的對象
(2)方法中:哪一個對象調用this所在的方法就表示哪一個對象。
當一個對象創建之后,JVM會分配一個引用自身的引用this
// 用戶信息類
class User {
private String name; // 名稱
private int age; // 年齡
// 構造器重載的互調
// 默認構造器 無參數構造器
User() {
// this(""); 或者不寫
// System.out.println("調用了無參數的構造器");
}
// 創建對象,初始化name 帶一個參數構造器
User(String name) {
// this(); // 這句話只能寫第一行 但是根據少參數調用多參數的原則 需要再修改一下
// System.out.println("調用了我, " + name);
// this.name = name;
this(name, 0);
}
// 創建對象,初始化name和age 帶兩個參數的構造器
User(String name, int age) {
// this.name = name;
// User(name);
/*
* 使用這種方法直接調用,編譯器會報如下的錯誤,因為用User(name)雖然能夠解決代碼的重復問題,但是
* 當成了一個User方法進行了調用,注意方法和構造器是不一樣的,這里沒有User的方法,只有User的構造器
* 所以就會報這樣的錯誤
* Error:(25, 8) Gradle: 錯誤: 找不到符號 符號:方法 User(String) 位置: 類 User
*/
// System.out.println("調用了我, " + name);
// 針對上邊的問題,我們這樣子調用
// this(name);
// 這個就是構造器重載的互調,表明在調用參數為String類型的構造器,
// 為了驗證可以在String參數類型的構造器中加一句打印的語句
// 注意單獨的使用this的時候我們叫做關鍵字,當他后面跟語句的時候我們稱為this語句
// 當我們在這個語句上邊增加一個打印輸出語句的時候System.out.println("調用了我, " + name);編譯器
// 會報錯 Error:(36, 12) Gradle: 錯誤: 對this的調用必須是構造器中的第一個語句。也就說明構造器重載
// 互相調用,this必須寫在構造方法第一行
// 根據少參數調用多參數上編代碼再一次進行修改
this.name = name;
this.age = age;
}
// 上邊的三個是構造器的重載,代碼本身沒問題,但是還是存在一些問題:(1)代碼的功能重復,比如this.name = name
// (2)代碼重復會導致代碼維護性低
// 上邊三個構造器的調用 當多個構造器重載時或者多個方法重載時,一般的是少參數調用多參數的。因為參數越多,該方法
// 考慮的未知因素也越多,也就是說功能更強大
public String getName() {
return name; // 返回name字段的值
}
public void setName(String name) {
this.name = name; // 把調用者傳遞的n參數值賦值給name
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void show() {
System.out.println(this.name + ", " + this.age);
}
public void sayHello() {
System.out.println("Hello");
this.show(); // 同類中調用實例方法,可以省略this,但是建議不要省略,會更直觀些
}
}
public class ThisDemo {
public static void main(String[] args) {
// 調用無參數的構造器
User u1 = new User();
// 給u1對象設置名為Lucy
u1.setName("Lucy"); // 間接的操作私有字段
// 設置年齡
u1.setAge(18);
// u1.show();
u1.sayHello();
User u2 = new User("LiLei", 10);
u2.sayHello();
}
}
使用this的六個場景:
- 解決成員變量和參數(局部變量)之間的二義性,必須使用
- 同類中實例方法互調
- 將當前對象作為參數傳遞給另一個方法
- 將當前對象作為方法的返回值(鏈式方法編程)
- 構造器重載的互調,this([參數])必須寫在構造方法的第一行
- static不能和this一起使用(當字節碼被加載進JVM,static成員已經存在了,但此時對象還沒有創建,而this是對象中指向自己的一個引用,因此不能一起使用,如在main方法中打印this,會報“無法從靜態上下文中引用非靜態變量this的錯誤”)
21.構造器和setter方法選用
創建對象并給對象設置初始值有兩種方式:
①先通過無參構造器創建出一個對象,再通過對象調用相應的setter方法
②直接調用帶有參數的構造器,創建出來的對象就有了初始值.
通過構造器和通過setter方法都可以完成相同的功能.
給對象設置數據:
(1)setter注入(屬性注入)
(2)構造注入.
如何選擇?
(1)如果存在帶參數的構造器,方式②是比較簡潔的
(2)如果在構建對象的時候需要初始化多個數據,如果使用方式②,那么構造器需要提供N個參數,參數過大不直觀,此時方式①就比較簡單明了了.
一般兩種方式都可以選擇,但針對于某些特殊的應用場景就需要考慮是否合理的問題了,如下:
(3)圓對象,如何畫一個圓?必須根據半徑來確定對象,就應該在構建圓對象的時候,就要確定半徑值.而不是先畫圓再確定半徑,這樣就不合理了。因此有時候需要根據數據來構建對象,此時優先選用構造器方式.
下邊是一個判斷點和圓位置關系的例子
// 點對象
class Point {
private double x; // 橫坐標
private double y; // 縱坐標
// 構造器 setter方法
Point(double x, double y) {
this.x = x;
this.y = y;
}
// getter方法
public double getX() {
return x;
}
public double getY() {
return y;
}
}
// 圓對象
class Circle {
private double r; // 半徑
// 構造器
Circle(double r) {
this.r = r;
}
// 判斷點與圓的位置關系,返回有三種結果 -1在圓內,0在圓周上,1在圓外
// 參數:需要判斷的點對象
int judge(Point p) {
double distance = p.getX() * p.getX() + p.getY() * p.getY();
double rr = this.r * this.r;
if (distance > rr) {
return 1;
} else if (distance < rr) {
return -1;
} else {
return 0;
}
}
}
public class CirclePointDemo {
public static void main(String[] args) {
// 創建一個點對象
Point p = new Point(3, 4);
// 創建一個圓對象
Circle c = new Circle(5);
// 判斷點與圓的對象
int result = c.judge(p);
System.out.println(result);
}
}