# Java封裝
[TOC]
## 導學
在之前的課程中,我們已經對Java的面向對象開發有了一些了解。那么本章節,我們就來看看面向對象三大特性之一的封裝。
所謂封裝,就是將類的某些信息隱藏在類的內部,不允許外部程序直接訪問
,只能通過該類提供的方法來實現對隱藏信息的操作和訪問。
簡單來說,既要隱藏對象的信息,也要留出訪問的接口。
封裝的特點在于:
1. 只能通過規定的方法訪問數據
2. 隱藏類的實例細節,方便修改和實現
## 封裝的實現
對于如何實現封裝,我們可以通過以下三個步驟:

在之前的課程中,我們使用了寵物貓這個例子,那么這個寵物貓的年齡可以由我們自由的設置,那么如果我們給寵物貓的年齡設置為負值,則就不符合現實的邏輯了。
### 修改屬性的可見性
~~~
public class Cat {
//將屬性的訪問控制修飾符修改為private-限定只能在當前類內被訪問
private String name;
private int month;
private double weight;
private String species;
}
~~~
`private`修飾的屬性只能在當前類中進行操作和訪問,在本類之外,是不允許被直接訪問的。
### 創建公有的get/set方法
~~~
public class Cat {
//1.將屬性的訪問控制修飾符修改為private
private String name;
private int month;
private double weight;
private String species;
//2.創建公有的get/set方法
public String getName() {
return name;
}
public void setName(String name) {//傳入相同的參數名與屬性形成對照
this.name = name;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
public String getSpecies() {
return species;
}
public void setSpecies(String species) {
this.species = species;
}
}
~~~
相關方法名的定義:
* get + 首字母大寫的屬性名() :get 方法一般都是具有和屬性數據類型一致的返回值,并且是沒有形參的。
* set + 首字母大寫的屬性名(參數):set 方法一般都是具有和屬性數據類型一致的方法參數,返回值一般是 void。
如果一個屬性只有`get`方法以供獲取的話,我們說該屬性就是一個只讀屬性。如果只有`set`方法以供操作的話,那么該屬性就是一個只寫屬性。
>[success] 在 Eclipse 中,可以通過快捷方式生成相關屬性的 getter 和 setter
> 在 「source」-> 「generate getters and setters」
### 在get/set方法中加入屬性控制
這一步并不是必須的,而是為了針對屬性值進行合理的判斷,防止對屬性值的胡亂操作。
~~~
/**
* 針對年齡范圍作出合理限制
* @param month
*/
public void setMonth(int month) {
if(month < 0 || month >= 240) {
System.out.println("年齡不合理,暫定為1");
this.month = 1;
} else {
this.month = month;
}
}
~~~
### 訪問和操作屬性
~~~
import com.dodoke.obj.animal.*;
public class CatTest {
public static void main(String[] args) {
Cat one = new Cat();
one.setName("凡凡");
one.setMonth(3);
one.setWeight(0.5);
one.setSpecies("英短");
System.out.println("昵稱:" + one.getName());
System.out.println("月份:" + one.getMonth());
System.out.println("重量:" + one.getWeight());
System.out.println("品種:" + one.getSpecies());
}
}
~~~
對于已經封裝好的類,我們想要操作和訪問其屬性,只有利用其對外暴露的接口`set/get`方法,才能實現。
### 帶參構造器中的屬性控制
在之前的課程中,我們提到可以使用帶參構造器實現對象實例化時候的屬性設定。
~~~
//保證類中無論何時都有一個無參構造器
public Cat() {
}
public Cat(int month) {
this.month = month;
}
===============================
//測試
public static void main(String[] args) {
Cat two = new Cat(-3);
System.out.println(two.getMonth());
}
~~~
我們發現上述代碼執行之后,并沒有完成對屬性值的限制,這是因為在構造器中,我們直接對屬性值進行了操作。所以,我們可以利用`set`方法中的屬性限制,完成代碼邏輯。
~~~
public Cat(int month) {
//this.month = month;
this.setMonth(month);
}
~~~
## static關鍵字

針對于主方法,我們之前也已經介紹過其中的一些知識點,本章節,我們來學習`static`關鍵字的作用。`static`代表著靜態信息。
首先我們通過一段代碼來看一下static的作用
~~~
public class Cat {
//1.將屬性的訪問控制修飾符修改為private
private String name;
private int month;
private double weight;
private String species;
public static int price;//價格
//2.創建公有的get/set方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMonth() {
return month;
}
/**
* 針對年齡范圍作出合理限制
* @param month
*/
public void setMonth(int month) {
if(month < 0 || month >= 240) {
this.month = 1;
} else {
this.month = month;
}
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
public String getSpecies() {
return species;
}
public void setSpecies(String species) {
this.species = species;
String str = "男";
if(str.equals("男")) {
System.out.println("性別為男");
} else {
System.out.println("性別為女");
}
}
public Cat() {
}
public Cat(int month) {
this.setMonth(month);
}
}
~~~
~~~
public class CatTest {
public static void main(String[] args) {
Cat one = new Cat();
one.setName("花花");
one.setMonth(3);
one.setWeight(0.5);
one.setSpecies("英短");
one.price = 2000;
Cat two = new Cat();
two.setName("凡凡");
two.setMonth(1);
two.setWeight(0.4);
two.setSpecies("中華田園貓");
two.price = 150;
System.out.println("我叫" + one.getName() + ",我的售價是" + one.price);
System.out.println("我叫" + two.getName() + ",我的售價是" + two.price);
}
}
~~~
上述代碼運行之后,我們會發現兩只貓的售價都是150。這里就是`static`的一個作用了。
`static`表示靜態的,`static`修飾的屬性稱之為【類變量,靜態變量】,方法稱之為【類方法,靜態方法】。
在Java程序中,`static`修飾的成員具有這樣一個特征,無論該類最終實例化出來多少對象,靜態成員都會共用一塊靜態空間。也就是說:

無論有多少寵物貓,對于價格而言,它們是共用同一塊靜態空間的。這也就是花花的價格起先是兩千,而在凡凡的價格修改為150之后,兩者的價格都被修改為150了。因為兩者都在針對同一塊內存空間進行操作。
>[warning]對普通成員而言:當這個類的對象產生的時候,它的相關成員會產生,而當這個對象銷毀的時候,這些成員就會進行資源釋放。
>對靜態成員而言:從類第一次加載的時候,靜態成員就會產生,一直到這個類不在有任何對象被使用。也就是它徹底銷毀的時候,靜態資源才會進行資源的釋放。所以靜態成員的生命周期比較長壽。
所以,靜態資源有著如下的特性:
* 類對象共享
* 類加載時產生,銷毀時釋放,生命周期長
靜態資源的訪問方式:
* 對象.靜態成員
* 類.靜態成員
只是對于使用`對象.靜態成員`的方式調用靜態成員,會出現警告。這是因為本質上,用`static`修飾的成員變量和方法,是屬于**類**的,而不是屬于該**類的實例(對象)**。
| 成員屬性/方法 | 類屬性/方法 |
| --- | --- |
| 屬于類的成員(對象)的屬性和方法 | 屬于對象共有(類)的屬性和方法 |
### static的修飾內容
`static`用來修飾方法和屬性
~~~
public static class Cat {}//不能用在類的修飾上
public void eat() {
static int a = 5;//注意不能用來修飾局部變量
}
~~~
### 靜態內容與非靜態內容的訪問限制
1. 在成員方法中,可以直接訪問類中靜態成員
~~~
public static void eat() {
System.out.println("小貓吃魚");
}
public void run() {
eat();
this.name = "妞妞";
//注意這邊this表示的是正在調用該屬性的對象
//其實也就是使用了==>對象.靜態資源的調用方式
this.price = 20;
System.out.println("售價是" + Cat.price + "的" + this.name + "快跑");
}
~~~
測試:
~~~
public class CatTest {
public static void main(String[] args) {
Cat one = new Cat();
one.setName("花花");
one.setMonth(3);
one.setWeight(0.5);
one.setSpecies("英短");
one.price = 2000;
Cat.price = 3000;
one.run();
}
}
~~~
2. 靜態方法中不能直接訪問同一個類的非靜態成員,只能直接調用同一個類中的靜態成員。
~~~
public static void eat() {
run();//不能調用
this.name = "胖虎";//靜態方法中不能使用this
name = "胖虎";
price = 1500;//可以調用
Cat cat = new Cat();
cat.run();//這樣才可以調用
System.out.println("小貓吃魚");
}
~~~
如果想要訪問非靜態成員,只能通過對象實例化后,`對象.成員方法/屬性`的方式訪問
### 代碼塊
在Java中,如果在語句當中出現`{}`,這樣的大括號對這就叫代碼塊。
* 普通代碼塊——順序執行
~~~
public void run(String name) {
{
System.out.println("我是普通代碼塊1");
}//出現在普通方法中
System.out.println(name + "快跑");
{
System.out.println("我是普通代碼塊2");
}
}
public static void main(String[] args) {
Cat one = new Cat();
one.run("花花");
}
~~~
* 構造代碼塊——創建對象時調用,優先于構造方法執行;多個構造代碼塊順序執行;
~~~
public class Cat {
//1.將屬性的訪問控制修飾符修改為private
private String name;
private int month;
private double weight;
private String species;
public static int price;//價格
{
System.out.println("我是構造代碼塊1");
}//直接出現在類中
//2.創建公有的get/set方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMonth() {
return month;
}
/**
* 針對年齡范圍作出合理限制
* @param month
*/
public void setMonth(int month) {
if(month < 0 || month >= 240) {
this.month = 1;
} else {
this.month = month;
}
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
public String getSpecies() {
return species;
}
public void setSpecies(String species) {
this.species = species;
}
public Cat() {
System.out.println("我是寵物貓~");
}
{
System.out.println("我是構造代碼塊2");
}//直接出現在類中
public Cat(int month) {
this.setMonth(month);
}
public static void eat() {
System.out.println("小貓吃魚");
}
public void run(String name) {
{
System.out.println("我是普通代碼塊1");
}//出現在普通方法中
System.out.println(name + "快跑");
{
System.out.println("我是普通代碼塊2");
}
}
}
~~~
測試:
~~~
public static void main(String[] args) {
Cat one = new Cat();
one.run("花花");
}
========================
運行結果:
我是構造代碼塊1
我是構造代碼塊2
我是寵物貓~
我是普通代碼塊1
花花快跑
我是普通代碼塊2
~~~
* 靜態代碼塊——static修飾的代碼塊,優于構造代碼塊執行,多個靜態代碼塊順序執行;無論產生多少個實例,只調用一次。
~~~
public class Cat {
//1.將屬性的訪問控制修飾符修改為private
private String name;
private int month;
private double weight;
private String species;
public static int price;//價格
{
System.out.println("我是構造代碼塊1");
}//直接出現在類中
//2.創建公有的get/set方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMonth() {
return month;
}
/**
* 針對年齡范圍作出合理限制
* @param month
*/
public void setMonth(int month) {
if(month < 0 && month >= 240) {
this.month = 1;
} else {
this.month = month;
}
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
public String getSpecies() {
return species;
}
public void setSpecies(String species) {
this.species = species;
}
public Cat() {
System.out.println("我是寵物貓~");
}
static{
System.out.println("我是靜態代碼塊");
}
public Cat(int month) {
this.setMonth(month);
}
public static void eat() {
System.out.println("小貓吃魚");
}
public void run(String name) {
{
System.out.println("我是普通代碼塊1");
}//出現在普通方法中
System.out.println(name + "快跑");
{
System.out.println("我是普通代碼塊2");
}
}
}
~~~
僅希望執行一次的代碼就可以放到靜態代碼塊中,這樣可以提高代碼的執行效率。
在普通代碼塊中可以操作類成員,但是在靜態代碼塊中只能操作靜態成員,如果想用需要先實例化對象,通過對象.成員調用。
### 代碼塊中的變量
~~~
public void run(String name) {
{
System.out.println("我是普通代碼塊1");
}//出現在普通方法中
System.out.println(name + "快跑");
{
System.out.println("我是普通代碼塊2");
}
}
~~~
這樣一段代碼,實際上形成了三個作用空間。

在之前的局部變量的課程中,我們也講過,一個作用空間中是不允許出現兩個同名變量的。那么在代碼塊中,是否可以出現同名變量呢?
~~~
public void run(String name) {
{
int temp = 12;
System.out.println("我是普通代碼塊1,temp=" + temp);
}//出現在普通方法中
System.out.println(name + "快跑,temp=" +temp);//出錯,temp的作用范圍只在大括號內
{
int temp = 13;
System.out.println("我是普通代碼塊2,temp=" +temp);
}
}
~~~
在一個代碼塊運行結束的時候,代碼塊中的局部變量就會被垃圾回收機制自動回收。
~~~
public void run(String name) {
int temp = 14;//以下的temp定義都會出錯。
{
int temp = 12;
System.out.println("我是普通代碼塊1,temp=" + temp);
}//出現在普通方法中
System.out.println(name + "快跑,temp=" +temp);//出錯,temp的作用范圍只在大括號內
{
int temp = 13;
System.out.println("我是普通代碼塊2,temp=" +temp);
}
}
~~~
## 練習
一、選擇
1. 當類中的一個成員方法被下面哪個修飾符修飾后,該方法只能在本類中被訪問
~~~
A. public
B. private
C. final
D. default
~~~
2. 運行以下Java代碼,說法正確的是

~~~
A. 15
B. 程序運行正常,但無輸出
C. 編譯報錯
D. 運行報錯
~~~
3. java封裝的意義(多選)
~~~
A. 防止使用者錯誤修改系統的屬性
B. 提高系統的獨立性
C. 提高軟件的可重用性
D. 提高構建大型系統的風險
~~~
4. 下面代碼的運行結果是

~~~
A. 我是一名學生。
B. 我是一個即將大學畢業的學生。
C. 我是一名學生。
我是一個即將大學畢業的學生。
D. 編譯錯誤
~~~
5. 關于package+包名;的說法不正確的是
~~~
A. 一個java源文件可以有多個package語句
B. 建議包名應該全部英文小寫
C. 建議包名命名方式:域名倒敘+模塊+功能
D. "package+包名;"必須放在java源文件中的第一行
~~~
6. 下面關于import,class和package的聲明順序哪個是正確的
~~~
A. package,import,class
B. class,import,package
C. import,package,class
D. package,class,import
~~~
7. 以下代碼運行結果為:

~~~
A. 編譯出錯
B. 輸出:c
C. 編譯正常,運行時報錯
D. 編譯正常,運行時無輸出結果
~~~
8. 下列關于static的說法不正確的是
~~~
A. 可以定義靜態方法,但不能定義靜態變量
B. class前不可以是用static作為修飾符
C. static可以把普通的成員方法變為一個靜態方法
D. static可以把一個普通方法變為一個類方法
~~~
9. 關于靜態方法和非靜態方法,以下描述正確的是
~~~
A. 非靜態方法只能訪問非靜態變量
B. 靜態方法既可以直接訪問本類的靜態變量,也可以直接訪問本類的非靜態變量
C. 靜態方法在類外只能通過類名來調用
D. 非靜態方法在類外只能通過對象來調用
~~~
10. 下面這段代碼中,報錯的地方原因是(多選)

~~~
A. 沒有傳入參數
B. 沒有使用類方法來調用
C. 不能使用靜態引用去訪問非靜態的方法
D. 只要實例化對象就不會報錯了
~~~
二、編程
1. 編寫自定義類實現圖書信息設置
任務:
屬性:書名、作者、出版社、價格
方法:信息介紹
要求:
* 設計構造函數實現對屬性賦值
* 設置私有屬性,get/set方法實現對屬性的訪問
* 限定圖書價格必須大于10,如果無效需進行提示,并強制賦值為10
* 限定作者、書名均為只讀屬性
* 信息介紹方法描述圖書所有信息
~~~
public class Book {
//私有屬性:書名、作者、出版社、價格
//通過構造方法實現屬性賦值
/*通過公有的get/set方法實現屬性的訪問,其中:
1、限定圖書價格必須大于10,如果無效需進行提示,并強制賦值為10
2、限定作者、書名均為只讀屬性
*/
//信息介紹方法,描述圖書所有信息
}
~~~
2. 編寫自定義類實現用戶信息類。
**任務**:
* 用戶類:
屬性:用戶名、密碼
* 用戶管理類:
方法:用戶信息驗證
**要求**:
??????1. 設計構造函數實現對屬性賦值
??????2. 設置私有屬性,get/set方法實現對屬性的訪問
????? 3. 用戶信息驗證判斷兩個用戶信息是否一致。當用戶名和密碼都一致的時候返回:用戶名和密碼一致,否則返回:用戶名或密碼不一致
**PS**:
字符串的判斷不可以使用`==`進行判斷,可以通過equals()方法進行字符串內容的判斷,如果內容相等返回值為true,反之為false,如當str代表用戶性別時,可以通過如下代碼,判斷性別為“男”還是“女”

**代碼運行結果參考**:
