摘要:核心技術卷第章對象與類面向對象程序設計創建標準類庫中的類對象如何編寫自己的類傳統的結構化程序設計首先確定如何操作數據,再決定如何組織數據。當使用構造器時,無法改變所構造的對象類型。
《Java核心技術 卷Ⅰ》 第4章 對象與類
面向對象程序設計
創建標準Java類庫中的類對象
如何編寫自己的類
OOP傳統的結構化程序設計:首先確定如何操作數據,再決定如何組織數據。
面向對象程序設計:將數據放在第一位,再考慮操作數據的算法。
類類(class)是構造對象的模板或藍圖,
由類構造(construct)對象的過程稱為創建類的實例(instance)。
封裝(encapsulation),也稱數據隱藏,封裝將數據和行為組合在一個包中,并對對象使用者隱藏數據實現方式,對象中的數據域稱為實例域(instance field),操作數據的過程稱為方法(method)。
對于每個特定的類實例(對象)都有一組特定的實例域值,這些值的集合就是這個對象的當前狀態(state),只要向對象發送一個消息,它的狀態就有可能發生改變。
實現封裝的關鍵:絕對不能讓類中的方法直接地訪問其他類的實例域。
OOP的另一個原則:可以通過擴展一個類來建立另外一個新的類。在Java中,所有類都源于一個超類——Object。
在擴展一個已有類時,新類具有這個類的全部屬性和方法,在新類中,只需要提供適用于這個新類的新方法和數據域就可以了,這個過程稱為繼承(inheritance)。
對象對象的三個主要特性:
行為:可以讓對象做什么?
狀態:被使用時,如何響應對應的行為?
標示:如何辨別具有相同行為與狀態的不同對象?
識別類識別類的簡單規則:
使用的名詞:類
使用的動詞:類的方法
類之間的關系常見的關系有:
依賴(use-a):如果一個類的方法操縱另一個類的對象,我們說一個類依賴另一個類(即沒有這個類就無法完成指定的方法),比如消費者想要支付,TA需要操作手機去完成具體的支付方式,即Customer "use-a" MobilePhone。
聚合(has-a):聚合關系意味著類A的對象包含B的對象,比如程序員要喝咖啡,TA有一個杯咖啡,即Programmer "has-a" Coffee。
繼承(is-a):類A擴展類B,要做學生,先要做人,即Student "is-a" Person.。
使用預定義類 對象與對象變量想要使用對象,就必須首先構造對象,并指定其初始狀態,
然后對對象應用方法。
在Java中,使用構造器(constructor)構造新實例,它是一種特殊的方法,用于構造并初始化對象。
Date birthday = new Date(); String s = birthday.toString();
對象變量并沒有實際包含一個對象,而僅僅是一種引用,在Java中,任何對象變量的值都是對存儲在另外一個地方的一個對象的引用,new操作符的返回值也是一個引用。
當一個對象變量只是聲明但是沒有具體的引用對象時,調用其方法會在編譯時產生變量未初始化錯誤。
// Error test P1 Date deadline; deadline.toString();
當一個對象變量只是聲明但是沒有具體的引用對象時,調用其方法會產生運行時錯誤(通常為java.lang.NullPointerException)。
// Error test P2 Date deadline = null; deadline.toString();
上面兩個例子說明,Java中的局部變量并不會自動地初始化為null,而必須通過調用new或將他們設置為null進行初始化。
LocalDate類Date類的實例有一個狀態,即特定的時間點。
時間是距離紀元(epoch)的毫秒數(可正可負),紀元是UTC(Coordinated Universal Time)時間1970年1月1日 00:00:00。
類庫設計者把保存時間與給時間點命名分開,所以標準Java類庫分別包含了兩個類:
表示時間點的Date類
日歷表示法的LocalDate類
不要使用構造器來構造LocalDate類的對象,應用靜態工廠方法(factory method)代表調用構造器。
// 當前時間的對象 LocalDate.now(); // 指定時間的對象 LocalDate.of(1996, 6, 30); // 保存對象 LocalDate birthday = LocalDate.of(1996, 6, 30);
有了對象就可以使用方法獲得年、月、日。
int year = birthday.getYear(); // 1996 int month = birthday.getMonthValue(); // 6 int day = birthday.getDayOfMonth(); // 30 int dayOfWeek = birthday.getDayOfWeek().getValue(); // 7
需要計算某個日期時:
LocalDate someday = birthday.plusDays(708); int year = someday.getYear(); // 1998 int month = someday.getMonthValue(); // 6 int day = someday.getDayOfMonth(); // 8 // 當然還有minusDays方法更改器方法與訪問器方法
更改器方法(mutator method):調用后,對象的狀態會改變。
訪問器方法(accessor method):只訪問對象而不修改對象狀態的方法。
用戶自定義類 簡單類定義Java簡單類的形式:
class ClassName { filed1 field2 ... constructor1 constructor2 ... method1 method2 ... }
一個使用簡單類的程序例子:
// File EmployeeTest.java public class EmployeeTest { public static void main(String[] args) { Employee[] staff = new Employee[3]; staff[0] = new Employee("Bob Hacker", 75000, 1996, 6, 30); ... } } class Employee { // instance fields private String name; private double salary; private LocalDate hireDay; // constructor public Employee(String n, double s, int year, int month, int day) { name = n; salary = s; hireDay = LocalDate.of(year, month, day); } // methods public String getName() { return name; } ... }
注意,這個程序中包含兩個類:
Employee類
帶有public訪問修飾符的EmployeeTest類
源文件名是EmployeeTest.java,這是因為文件名必須與public類的名字相匹配,在一個源文件中,只能有一個公有類,但可以有任意個非公有類。
當編譯這段源碼時,編譯器會在目錄下生成兩個類文件:EmployeeTest.class 和 Employee.class。
將程序中包含main方法的類名提供給字節碼解釋器,啟動程序:
java EmployeeTest
字節碼解釋器開始運行其中的main方法的代碼。
構造器剛才所使用類中的構造器:
class Employee { ... public Employee(String n, double s, int year, int month, int day) { name = n; salary = s; hireDay = LocalDate.of(year, month, day); } ... }
構造器與類同名,在構造Employee類對象時,對應的構造器會運行,執行代碼將實例域初始化所指定的狀態。
構造器與方法的其中一個不同是,構造器總是伴隨new操作符的執行被調用,并且不能對已經存在的對象調用構造器來重新設置實例域。
Employee bob = new Employee("Bob", 47000, ...); bob.Employee("Bob", 47500, ...); // Compiler Error: Can"t find symbol of method Person(String, int, ...)
構造器基礎的簡單總結:
構造器與類同名
構造器可以有任意個參數,甚至沒有參數
構造器沒有返回值
構造器伴隨new操作一起調用
每個類可以有一個以上的構造器
多個構造器時,根據調用new的參數類型來進行選擇
隱式參數與顯式參數方法用于操作對象以及存取他們的示例域。
public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; }
當對象調用方法時
bob.raiseSalary(5);
raiseSalary方法有兩個參數。
隱式(implicit)參數,這里指的是現在方法名前的Employee類的對象。
顯示(explicit)參數,這里指的是位于方法名后括號中的數據。
在每一個方法中,關鍵字this表示隱式參數,上面的方法也可以寫為:
public void raiseSalary(double byPercent) { // double raise = salary * byPercent / 100; double raise = this.salary * byPercent / 100; // salary += raise; this.salary += raise; }
有些人偏愛這樣寫(包括我),雖然費事點,但是可以將實例域與局部變量明顯的區分開來。
封裝的優點封裝對于直接簡單的公有數據而言,提供了更多對公有數據保護的途徑。
對于訪問器來說,它們只返回實例域的值,并且在處理可引用的返回對象時,要通過clone來創建新的對象來作為返回值的載體,如果將可引用對象直接返回,并且該對象恰有一個可修改值的方法時,任何外部對這個返回值的處理都將會直接影響到這個對象內部的對象(Java引用在這部分的情況類似與C中的指針)。
對于更改器來說,它們在被調用時可以主動的執行數據合法性的檢查,從而避免破壞數據的合法性。
基于類的訪問權限方法可以訪問所調用對象的私有數據。
但是Java其實還要更進一步:一個方法可以訪問所屬類的所有對象的私有數據。
// class class Employee { public boolean equals(Employee other) { return name.equals(other.name); } } ... // main if(harry.equals(boss)) ...
這個方法訪問harry的私有域,同時它還訪問了boss的私有域,這是合法的,boss也是Employee類對象,Employee類的方法可以訪問Employee類的任何一個對象的私有域。
私有方法有時候為了完成任務需要寫一些輔助方法,這些輔助方法不應該稱為公有接口的一部分,這是由于它們與當前的實現機制非常緊密,最好將這樣的方法設計為private。
簡單來說,為了更好地封裝性,不在公有接口范圍內的方法都應該設計為private。
final實例域類中可以定義實例域為final,但是必須確保在每一個構造器執行之后,這個域的值會被設置,并在后面的操作中不能再對其進行修改。
但是這里的不能修改大都應用于基本(primitive)類型和不可變(immutable)類型的域(如果類中每個方法都不會改變對象狀態,則類就是不可變的類,例如String類)。
對于可變的類(比如之前的StringBuilder類),使用final修飾符只是表示該變量的對象引用不會再指示其他的對象,但其對象本身是可以更改的(比如StringBuilder類的對象執行append方法)。
靜態域與靜態方法 靜態域如果將一個域定義為static,每個類中只有這樣的一個域。
通俗來講,如果一個域被定義為static,那么這個域屬于這個類,而不屬于任何這個類的對象,這些對象同時共享這個域(有點像類的一個全局變量域)。
一個簡單的靜態域用法:
// class Employee ... // 可以在類定義中直接對靜態域賦予一個初值。 private static int nextId = 1; private int id; ... public void setId() { id = nextId; nextId++; }靜態常量
靜態常量相比于靜態變量使用的要多一些。
例如Math類中的PI:
public class Math { ... publuc static final double PI = 3.14159265358979323846; ... }
程序通過Math.PI的形式獲得這個常量。
靜態方法靜態方法是一種不能向對象實施操作的方法。
靜態方法在調用時,不使用任何實例對象,換句話說就是沒有隱式參數。
需要使用靜態方法的情況:
一個方法不需要訪問對象狀態,參數都是顯示參數提供
一個方法只需要訪問類的靜態域
工廠方法比如之前LocalDate類使用的靜態工廠方法(factory method)來構造對象。
不利用構造器完成這個操作的兩個原因:
無法命名構造器。構造器的名字必須與類名相同。
當使用構造器時,無法改變所構造的對象類型。
main方法main方法不對任何對象進行操作,因為事實上在啟動程序時還沒有任何一個對象,靜態的main方法將隨著執行創建程序所需要的對象。
同時,每一個類都可以有一個main方法,常用于進行類的單元測試。
方法參數在程序設計語言中有關參數傳遞給方法(函數)的一些專業術語:
按值調用(call by value):表示方法接收的是調用者提供的值。
按引用調用(call by reference):標識方法接收的是調用者提供的變量地址。
Java程序設計語言總是采用按值調用,即方法得到的只是參數值的一個拷貝,不能修改傳遞給它的任何參數變量的內容。
但是當對象引用作為參數時,情況就不同了,方法獲得的是對象引用的拷貝,對象引用和其他拷貝同時引用同一個對象。
但是這并不是引用調用。
public static void swap(Obejct a, Obejct b) { Object tmp = a; a = b; b = tmp; }
如果Java在對象參數時采用的是按引用調用,上述方法就能實現交換數據的效果。
但是這里的swap方法并沒有改變存儲在調用參數中的對象引用,swap方法的參數a和b被初始化為兩個對象引用的拷貝,這個方法交換的是這兩個拷貝的引用。
Java中方法參數總結:
方法不能修改基本數據類型的參數
方法可以改變對象參數的狀態
方法不能讓對象參數引用一個新的對象
對象構造 重載有些類有多個多個構造器。
這種特征叫做重載(overloading),如果多個方法有相同的名字、不同的參數,便產生了重載。
編譯器通過用各個方法給出的參數類型與特定方法調用所使用的值類型進行匹配來挑選出相應的方法,如果編譯器找不到匹配的參數,就會產生編譯時錯誤。
Java允許重載任何方法,并不只是構造器,因此要完整地描述一個方法,需要指出方法名以及參數類型,這叫方法的簽名(signature)。
// 方法重載的簽名舉例 indexOf(int) indexOf(int int) indexOf(String)
可以看出,返回類型并不是方法簽名的一部分,即不能有兩個名字相同、參數類型相同但是卻返回不同類型值的方法。
默認域初始化如果域沒有在構造器中被賦予初值,則會被自動地賦予默認值:
數值:0
布爾:false
對象引用:null
這與局部變量的聲明不同,局部變量必須明確的進行初始化。
構造器中如果不明確地進行初始化,會影響代碼的可讀性。
無參數的構造器如果在編寫一個類時沒有編寫構造器,那么系統會提供一個無參數構造器,這個構造器將所有的實例域設置為默認值。
如果類中提供了至少一個構造器,但是沒有提供無參數構造器,則構造對象時如果沒有提供參數就會被視為不合法。
顯式域初始化通過重載類的構造器方法,可以采用多種形式設置類的實例域的初始狀態。
可以在類定義中,直接講一個值賦予給任何域。
class Employee { private String name = ""; ... }
在構造器執行之前,先執行賦值操作。
初始值也可以不用是常量。
class Employee { private static int nextId; private int id = assignId(); ... private static int assignId() { int r = nextId; nextId++; return r; } ... }
上面的例子中,可以調用方法對域進行初始化。
參數名在編寫很小的構造器時,通常用單個字符命名:
public Employee(String n, double s) { name = n; salary = s; }
這樣的缺陷是失去了代碼可讀性,也可以采用加前綴的方法:
public Employee(String aName, double aSalary) { name = aName; salary = aSalary; }
當然還有一種技巧,參數變量用同樣的名字將實例域屏蔽起來:
public Employee(String name, double salary) { this.name = name; this.salary = salary; }調用另一個構造器
如果構造器的第一個語句形如this(...),這個構造器將調用同一個類的另一個構造器。
public Employee(double s) { // calls Employee(String, double) this("Employee #" + nextId, s); nextId++; }初始化塊
除了前面提到的兩種初始化數據域的方法:
在構造器中設置值
在聲明中賦值
還有第三種,稱為初始化塊(initialization block),在類定義中可以包含多個代碼塊,只要構造類的對象,這些塊就會被執行。
class Employee { private static int nextId = 0; private int id; { id = nextId; nextId++; } ... }
無論哪個構造器構造對象,初始化塊都會執行,首先運行初始化塊,然后才運行構造器的主體部分。
調用構造器的具體處理步驟:
所有數據域被初始化為默認值
按照出現次序執行初始化語句和初始化塊
如果構造器調用了第二個構造器,則執行第二個構造器主體
執行這個構造器主體
初始化塊比較常用于代碼比較復雜的靜態域初始化:
static { Random generator = new Random(); nextId = generator.nextInt(10000); }包
Java允許使用包(package)將類組織起來。
借助于包可以方便地組織自己的代碼,并將自己的代碼與別人提供的代碼庫分開管理。
標準的Java類庫分布在多個包中,包括java.lang、java.util和java.net等。
使用包的主要原因是確保類名的唯一性。
假如兩個程序員都建立了Employee類,只要將類放置在不同的包中,就不會產生沖突。
從編譯器角度來說,嵌套的包之間沒有任何關系。例如java.util包與java.util.jar包毫無關系。
類的導入一個類可以使用所屬包的所有類,以及其他包中的公有類(public class)。
可以使用兩種方式訪問另一個包中的公有類。
在類名前添加完整地包名
使用import語句,可以導入一個特定的類或者整個包。
// 添加包名 java.time.LocalDate today = java.time.LocalDate.now(); // import import java.util.*; // or import java.time.LocalDate 引入特定類 LocalDate today = LocalDate.now();
大多數情況下導入包即可,但是在發生命名沖突的時候,就要注意了,
import java.util.*; import java.sql.*; Date today; // Error
因為這兩個包都有Date類,編譯器無法確定是哪一個包的Date類,所以這個時候可以增加一個指定特定類的import語句。
如果兩個類都要使用時,就在每個類名前加上完整地包名。
靜態導入import語句還增加了導入靜態方法和靜態域的功能。
import static java.lang.System.*; // 然后可以使用System類的靜態方法和靜態域而不必加前綴 out.println("Hohoho!"); // System.out.println()
另外,還可以導入特定的方法或域:
import stattic java.lang.System.out; out.println("Hohoho!");將類放入包中
想將一個類放入包中,就必須將包的名字放在源文件的開頭。
package com.horstmann.corejava; public clas Employee { ... }
如果沒有在源文件中放置package語句,源文件中的類被放置在默認包(default package)中,默認包是一個沒有名字的包。
一般需要把包中的文件放到與完整的包名匹配的子目錄中。
例如package com.horstmann.corejava包中的所有源文件,應該被放置在子目錄com/horstmann/corejava中。
包作用域標記為public的類、方法、變量可以被任意的類使用
標記為private的類、方法、變量只能被定義他們的類使用
如果沒有指定,則他們可以被同一個包中的所有方法訪問
文檔注釋JDK包含一個很用有的工具——javadoc,它可以由源文件生成一個HTML文檔。
在源代碼中添加以專用的定界符/**開始的注釋,則可以容易地生成形式上專業的文檔,相比于把文檔和代碼多帶帶存放,修改代碼的同時修改文檔注釋再重新運行javadoc,就不會出現不一致的問題。
注釋的插入javadoc從下面幾個特性中抽取信息:
包
公有類與接口
公有的和受保護的構造器及方法
公有的和受保護的域
應該為這幾部分編寫注釋,注釋應該放在所描述特性的前面。
注釋以/**開始,以*/結束。
每個/**...*/文檔注釋中使用自由格式文本(free-form text),標記由@開始。
類注釋類注釋必須放在import語句之后,類定義之前。
/** * Just some comment words here * another comment line * what is this class for? */ public class Card { ... }方法注釋
方法注釋放在描述的方法前,除了通用標記,還可以使用下面的標記:
@param 變量 描述:用于標記當前方法的參數部分的一個條目
@return 描述:用于標記方法的返回部分
@throws類 描述:表示方法有可能拋出異常
/** * Buy one coffee. * @param money the cost of coffee * @param coffeeTpye which coffee * @return coffee one hot coffee * @throws NoMoreCoffee */ public buyCoffee(double money, CoffeeType coffeeTpye) { ... }域注釋
只需要對公有域(通常是靜態常量)建議文檔。
/** * The ratio of a circle"s circumference to its diameter */ public static final double PI = 3.1415926...;通用注釋
可用在類文檔的注釋的標記:
@author 姓名:可以使用多個
@version 文本:版本條目
@since 文本:始于...條目,這里的文本可以是對版本的描述
@deprecated 文本:標記對類、方法或變量不再使用,例如:
@deprecated Use setVisible(true)
instead
@see 引用:增加一個超鏈接,可以用于類、方法中,引用有以下情況:
package.class#feature label
// 建立一個連接到com.horstmann.corejava.Employee類的raiseSalary(double)方法的超鏈接 @see com.horstmann.corejava.Employee#raiseSalary(double) // 可以省略包名,甚至把包名和類名省去 @see Employee#raiseSalary(double) // 此時鏈接定位于當前包 @see raiseSalary(double) // 此時連接定位于當前類
@see The Core ]ava home page // 此處可以使用label標簽屬性來添加用戶看到的錨名稱
"text"
@see "Core Java 2 volume 2n"
如果愿意的話,還可以在注釋的任何位置放置指向其他類和方法的超鏈接:
{ @link package.class#feature label } // 這里的描述規則與@see標記規則一樣包與概述注釋
如果想要包的注釋,就要在每一個包的目錄中添加一個多帶帶的文件。
提供一個以package.html命名的文件,在...之間的所有文本會被抽取。
提供一個以package-info.java命名的文件,這個文件包含一個初始的以/**和*/界定的Javadoc注釋,跟隨在一個包語句之后。
還可以為所有的源文件提供一個概述性的注釋,這個注釋將被放置在一個名為overview.html的文件中,這個文件位于包含所有源文件的父目錄中,標記...之間的所有文本會被抽取。當用戶選擇overview時,就會查看到這些注釋內容。
類設計技巧應用這些技巧可以設計出更具有OOP專業水準的類。一定要保證數據私有
絕對不要破壞封裝性,這是最重要的。
數據的表示形式很可能會改變,但是它們的使用方式卻不會經常發生變化,當數據保持私有時,它們的表示形式的變化不會對類的使用者產生影響,即使出現bug也易于檢測。
一定要對數據初始化Java不對局部變量進行初始化,但是會對對象的實例域進行初始化。
但是也最好不要依賴系統的默認值,應該用構造器或者是提供默認值的方式來顯式地初始化所有的數據。
不要在類中使用過多的基本類型用其他的類代替多個相關的基本類型的使用。
這樣會使類更加易于理解和修改。
比如用一個Address的類來代替下面的實例域:
private String street; private String city; private String state; private int zip;
這樣更容易理解和處理表示地址的域,而使用這些域的類并不用去關心這些域是怎么具體變化的。
不是所有的域都需要獨立的域訪問和更改器有些域在對象被構造出來后,在類的設計上,可能不再允許被修改
在對象中有時候包含一些不希望別人獲得或設置的實例域
將職責過多的類進行分解雖然這里的“過多”對于個人來說是一個含糊的概念,但是如果明顯地可以將一個復雜的類分解成兩個更為簡單的類,則應該進行分解。
類名和方法名要能體現職責命名類名的良好習慣是采用:
名詞 Order
前面有形容詞修飾的名詞 RushOrder
動名詞修飾的名詞 BillingAddress
對于方法來說:
訪問器用小寫的get開頭
更改起用小寫的set開頭
優先使用不可變的類更改對象的問題在于:如果多個線程視圖同時更新一個對象,就會發生并發更改,其結果是不可預料的。如果類時不可變的,就可以安全地在多個線程間共享其對象。
Java對象與類總結OOP的簡要概念
類與對象
類之間的關系
對象與對象變量
更改器與訪問器
自定義類
構造器
隱式參數與顯式參數
封裝
基于類的訪問權限
私有方法
final實例域
靜態域與靜態方法
按值調用
重載
默認域的初始化
無參數構造器
顯式域初始化
初始化塊
包
文檔注釋
類設計技巧
個人靜態博客:
氣泡的前端日記: https://rheabubbles.github.io
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/72917.html
摘要:學習總結學習整理的一些筆記,很簡單。大部分認為和只是不同的叫法而已。依賴注入的兩種方式和注解使用注釋驅動的功能源碼剖析 Spring IoC學習總結 學習spring Ioc整理的一些筆記,很簡單。分享給大家。 IoC 基本概念 在這之前,我們先記住一句話。好萊塢原則:Dont call us, we will call you.其實這句話很恰當地形容了反轉的意味;Ioc, Inve...
摘要:它屬于類創建型模式。基于繼承,將復雜的放置在函數中,簡單的共同的放置到一個構造函數中。代碼與繼承類似,但是核心就是將簡單的共有的放置到構造函數中,與類的思想類似。單例模式實現代碼庫,產生命名空間,一次只能實例化一個。 JavaScript設計模式閱讀 更多文章查看本專欄 設計模式第一篇:創建型設計模式 1、簡單工廠模式 簡單工廠模式:又叫靜態工廠方法,有一個工廠對象決定創建某一種產品...
摘要:抽象數據類型的多個不同表示可以共存于同一個程序中,作為實現接口的不同類。封裝和信息隱藏信息隱藏將精心設計的模塊與不好的模塊區分開來的唯一最重要的因素是其隱藏內部數據和其他模塊的其他實施細節的程度。 大綱 面向對象的標準基本概念:對象,類,屬性,方法和接口OOP的獨特功能 封裝和信息隱藏 繼承和重寫 多態性,子類型和重載 靜態與動態分派 Java中一些重要的Object方法設計好的類面向...
摘要:如果需要收集參數化類型對象,只有使用警告這節討論,向參數可變的方法傳遞一個泛型類型的實例。異常不能拋出或捕獲泛型類的實例實際上,泛型類擴展也是不合法的。 Object:所有類的超類 java中每個類都是由它擴展而來,但是并不需要這樣寫:class Employee extends Object.如果沒有明確指出超類,Object類就被認為是這個的超類。可以使用Object類型的變量引用...
閱讀 3799·2021-09-23 11:32
閱讀 2466·2021-09-06 15:01
閱讀 1625·2021-08-18 10:24
閱讀 3462·2019-12-27 11:44
閱讀 3611·2019-08-30 15:52
閱讀 2519·2019-08-30 11:11
閱讀 691·2019-08-29 17:27
閱讀 606·2019-08-29 16:22