通過學習 極客學院wikejava提高篇,記錄一些比較重要的東西。
1. ISP(Interface Segregation Principle)
isp:使用多個專門的接口比使用單一的總接口要好。
一個類對另外一個類的依賴性應當是建立在最小的接口上的,沒有關係的接口合併在一起,形成一個臃腫的大街口,這是對角色和接口的污染。
2. 使用序列化實現對象的拷貝
public class Person implements Cloneable{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
protected Person clone() {
Person person = null;
try {
person = (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return person;
}
}
使用:
Person p1 = new Person();
Person p2 = p1.clone();
這種方式是淺拷貝,他是有選擇的拷貝,它具有如下特徵:
- 基本類型值,拷貝其值
- 對象,拷貝的是他的引用,這樣的話,如果修改p1中對象引用的屬性,那麼 p2的值也會發生改變
- 字符串,拷貝其引用,但是如果你修改的話,他會在字符串常量池中重新創建一個字符串,這樣的話,修改字符串並不會改變p2.
使用序列化即可避免這樣情況
實現原理:把對象寫入內存中的字節流,然後在從字節流中讀取,獲取的對象即爲拷貝的對象。對象需要實現Serializable接口。
public class CloneUtils {
@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T obj){
T cloneObj = null;
try {
//寫入字節流
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream obs = new ObjectOutputStream(out);
obs.writeObject(obj);
obs.close();
//分配內存,寫入原始對象,生成新對象
ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(ios);
//返回生成的新對象
cloneObj = (T) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
}
3. 內部類
3.1 爲什麼使用內部類
如果想要實現多重繼承的話,我們可以使用多個內部類來繼承多個具體的或者抽象的類。
3.2 .this 和.new
創建內部類:outer.new InnerClass();在創建內部類對象的時候,我們需要一個外部類的對象,這樣的話,內部類就與外部類產生了聯繫,在內部類中會保存這個外部類的對象,從而可以在內部類中使用外部類中屬性和方法,包括私有屬性和方法(在類內部,故可以使用)。
public class OuterClass {
private String name ;
private int age;
/**省略getter和setter方法**/
public class InnerClass{
public InnerClass(){
name = "chenssy";
age = 23;
}
public void display(){
System.out.println("name:" + getName() +" ;age:" + getAge());
}
}
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
innerClass.display();
}
}
在內部類中我們可以使用OuterClass.this獲取這個外部類的對象
4.匿名內部類
創建的格式:
new 類(包括抽象類)(參數列表)|實現接口()
{
//匿名內部類的類體部分,實現抽象方法或者接口
}
Bird:
public abstract class Bird {
String name;
public Bird(String name){
this.name = name;
}
public abstract int fly(int distance);
public String getName(){
return name;
}
}
Test:
public class Test {
public static void main(String[] args) {
Test t = new Test();
t.info(new Bird("xinwa") {
@Override
public String getName() {
// TODO Auto-generated method stub
return this.name;
}
@Override
public int fly(int distance) {
// TODO Auto-generated method stub
return distance;
}
});
}
}
public void info(Bird bird){
System.out.println(bird.getName()+"能飛多少米"+bird.fly(100));
}
}
使用匿名內部類的場景一般是對於對於類只需要使用一次
4.1使用的形參爲何要爲 final
public class OuterClass {
public void display(final String name,int age){
class InnerClass{
void display(){
System.out.println(name);
}
}
}
}
比方說上面這個例子,編譯之後如下
public class OuterClass$InnerClass {
public InnerClass(String name,String age){
this.InnerClass$name = name;
this.InnerClass$age = age;
}
public void display(){
System.out.println(this.InnerClass$name + "----" + this.InnerClass$age );
}
}
會發現:在編譯之後,內部類會對參數進行備份,這樣其實內部類使用的和外部類傳進來的參數是倆個東西。
如果不爲final的話,看下面例子,當然下面例子在jdk7是錯誤的,只是用來演示,語法是錯的。
public class OuterClass {
public void display(String name,String age){
name = "xinwa";
class InnerClass{
void display(){
System.out.println(name);
}
}
}
}
編譯之後,內部類的值時copy參數的方法,當執行name=”xinwa”;這條語句之後,這時內部類中的name值並沒有改變,依然等於參數的值,這顯然不合常理,所以爲了避免這樣情況,我們給形參加一個final,讓其值無法改變。
5.強制類型轉換
在java中強制類型轉換分爲基本數據類型和引用數據類型。
Java中由於繼承和向上轉型,子類可以非常自然的轉換爲父類,但是父類轉換爲子類則需要強制轉換。
當使用一個類型的構造器構造出一個對象時,這個對象的類型就已經確定了。
我們可以通過繼承和向上轉型的父類類型來引用他。
Father father = new Son();
Son son = (Son)father;
這種方式是可行的。
Father father = new Father();
Son son = (Son)father;
這種情況在編譯時並不會出錯,但是在運行時會報ClassCastException 異常信息
故:在繼承中,子類可以自動轉型爲父類,但是父類強制轉換爲子類只有當父類的引用類型的對象爲子類的類型時,才能轉換成功,否則會發生類型轉換異常
6.代碼塊
6.1 編譯器如何處理構造代碼塊呢?
編譯器會將 代碼塊 按照他們的順序呢插入到所有構造函數的最前端,這樣保證不管調用那個構造函數都會執行所有的構造代碼塊
6.2用途:
1.初始化實例變量:
如果一個類中存在若干個構造函數,這些構造函數都需要對實例變量進行初始化,那麼可以簡化代碼,把它們單獨放在代碼塊中
2.初始化實例環境
當構造一個對象時,需要先實現一些複雜的邏輯,這時爲了讓構造函數簡單易懂,我們可以把實現的邏輯放在代碼塊中,這樣程序就可以更加簡單易懂。
3. 靜態代碼塊、構造代碼塊、構造函數執行順序
靜態代碼塊會先執行,然後是構造代碼塊,最後是構造函數,但要注意一點是靜態代碼塊只會在加載類的時候執行一次