第三章 Java基礎 (一)

文章目錄

(一)==、equals與hashCode

1、==

(1.1)介紹
java中的數據類型,可分爲兩類:
1.基本數據類型,也稱原始數據類型(byte,short,char,int,long,float,double,boolean)
基本數據類型用= =比較的是數據的值。
2.引用類型(類、接口、數組)
引用類型用= =進行比較的是他們在內存中的存放地址。
所以,除非是同一個new出來的對象,他們的比較後的結果爲true,否則比較後結果爲false。
對象是放在堆中的,棧中存放的是對象的引用(地址)。由此可見’=='是對棧中的值進行比較的。如果要比較堆中對象的內容是否相同,那麼就要重寫equals方法了。
(1.2)實例

public static void main(String[] args) {
 
		int int1 = 12;
		int int2 = 12;
		Integer Integer1 = new Integer(12);
		Integer Integer2 = new Integer(12);
		Integer Integer3 = new Integer(127);
 
		Integer a1 = 127;
		Integer b1 = 127;
 
		Integer a = 128;
		Integer b = 128;
 
		String s1 = "str";
		String s2 = "str";
		String str1 = new String("str");
		String str2 = new String("str");
 
		System.out.println("int1==int2:" + (int1 == int2));//true
		System.out.println("int1==Integer1:" + (int1 == Integer1));//true,Integer會自動拆箱爲int,所以爲true
		System.out.println("Integer1==Integer2:" + (Integer1 == Integer2));//false,不同對象,在內存中存放位置不同
		System.out.println("Integer3==b1:" + (Integer3 == b1));//false,Integer3指向new的對象地址,b1指向緩存中127的地址,地址不同,故爲false
		System.out.println("a1==b1:" + (a1 == b1));//true,a1,b1均指向緩存中127的地址
		System.out.println("a==b:" + (a == b));//false,根據源碼,a1,b1在127與-127範圍之外時會指向new的對象地址
		
		System.out.println("s1==s2:" + (s1 == s2));//true
		System.out.println("s1==str1:" + (s1 == str1));//false
		System.out.println("str1==str2:" + (str1 == str2));//fa;se

	}

2、equals

(2.1)默認情況(沒有覆蓋equals方法)
Object的equals方法主要用於判斷對象內存地址引用是不是同一個 地址(是不是同一個對象)
定義的equals與==等效

public boolean equals(Object obj) {
    return (this == obj);
    }

(2.2)覆蓋equals方法
根據具體的代碼確定equlas方法,覆蓋後一般都是通過對象的內容是否相等來判斷對象是否相等。下面是String類對equals方法進行重寫

public boolean equals(Object anObject) {
    if (this == anObject) {//參數是否爲這個對象的引用
        return true;
    }
    if (anObject instanceof String) {//參數是否爲正確的類型
        String anotherString = (String)anObject;
	//獲取關鍵域,判斷關鍵域是否匹配
        int n = count;
        if (n == anotherString.count) {
        char v1[] = value;
        char v2[] = anotherString.value;
        int i = offset;
        int j = anotherString.offset;
        while (n-- != 0) {
            if (v1[i++] != v2[j++])
            return false;
        }
        return true;
        }
    }
    return false;
    }

判斷步驟:
1.若A==B 即是同一個String對象 返回true
2.若對比對象是String類型則繼續,否則返回false
3.判斷A、B長度是否一樣,不一樣的話返回false
4.逐個字符比較,若有不相等字符,返回false

3、hashCode

(3.1)定義

public native int hashCode();

hashCode()方法返回的就是一個hash碼(int類型)。
hash碼的主要用途就是在對對象進行散列的時候作爲key輸入,我們需要每個對象的hash碼儘可能不同,這樣才能保證散列的存取性能(將數據按特定算法指定到一個地址上)。事實上,Object類提供的默認實現確實保證每個對象的hash碼不同(在對象的內存地址基礎上經過特定算法返回一個hash碼)。
(3.2)作用
hashCode只有在集合中用到,相當於集合的key,利用下標訪問可以提高查找效率
Java中的集合(Collection)有兩類,一類是List,再有一類是Set。前者集合內的元素是有序的,元素可以重複;後者元素無序,但元素不可重複。要想保證元素不重複,可兩個元素是否重複應該依據什麼來判斷呢?
Java採用了哈希表的原理。當集合要添加新的元素時,先調用這個元素的hashCode方法,就一下子能定位到它應該放置的物理位置上。
如果這個位置上沒有元素,它就可以直接存儲在這個位置上,不用再進行任何比較了;(放入對象的hashcode與集合中任一元素的hashcode不相等)
如果這個位置上已經有元素了(hashcode相等),就調用它的equals方法與新元素進行比較,相同的話就不存,不相同就散列其它的地址。
所以這裏存在一個衝突解決的問題。這樣一來實際調用equals方法的次數就大大降低了,幾乎只需要一兩次。
(3.3)equals與hashCode
1.equals與hashCode關係

  • 同一對象上多次調用hashCode()方法,總是返回相同的整型值。
  • 如果a.equals(b),則一定有a.hashCode() 一定等於 b.hashCode()。
  • 如果!a.equals(b),則a.hashCode() 不一定等於 b.hashCode()。此時如果a.hashCode() 總是不等於 b.hashCode(),會提高hashtables的性能。
  • a.hashCode()==b.hashCode() 則 a.equals(b)可真可假
  • a.hashCode()!= b.hashCode() 則 a.equals(b)爲假。
    2.實例
    同時覆蓋hashcode與equals方法
//學生類
public class Student {
	private int age;
	private String name;
	public Student() {
	}
	public Student(int age, String name) {
		super();
		this.age = age;
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public String getName() {
		return name;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		System.out.println("hashCode : "+ result);
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Student other = (Student) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
	
}

運行結果:
stu1 == stu2 : false
stu1.equals(stu2) :true
list size:2
hashCode :775943
hashCode :775943
set size:1
結果分析:
重寫equals保證姓名、年齡相等爲同一對象
重寫hashCode保證相同的對象不重複存入集合
stu1和stu2通過equals方法比較相等,而且返回的hashCode值一樣,所以放入set集合中時只放入了一個對象。
3.equals與hashCode重寫規範
(1)如果兩個對象相同,那麼他們的hashcode應該相等
若重寫equals(Object obj)方法,有必要重寫hashcode()方法,確保通過equals(Object obj)方法判斷結果爲true的兩個對象具備相等的hashcode()返回值。
(2)如果兩個對象不相同,他們的hashcode可能相同
如果equals(Object obj)返回false,即兩個對象“不相同”,並不要求對這兩個對象調用hashcode()方法得到兩個不相同的數。

爲了滿足上述規範,覆蓋equals方法時總要覆蓋hashCode,這樣該類才能結合所有基於散列的集合(如HashMap、HashSet、HashTable)一起正常運作

(二)序列化

1、什麼是序列化?

對象序列化是一個用於將對象狀態轉換爲字節流的過程,可以將其保存到磁盤文件中或通過網絡發送到任何其他程序;從字節流創建對象的相反的過程稱爲反序列化。
序列化以特定的方式對類實例的瞬時狀態進行編碼保存的一種操作.序列化作用的對象是類的實例.對實例進行序列化,就是保存實例當前在內存中的狀態.包括實例的每一個屬性的值和引用等.反序列化的作用便是將序列化後的編碼解碼成類實例的瞬時狀態.申請等同的內存保存該實例.
序列化的作用就是爲了保存java的類對象的狀態,並將對象轉換成可存儲或者可傳輸的狀態,用於不同jvm之間進行類實例間的共享。

2、爲什麼JAVA對象需要實現序列化?

序列化就是一種用來處理對象流的機制,所謂對象流也就是將對象的內容進行流化。我們可以對流化後的對象進行讀寫操作,也可將流化後的對象傳輸於網絡之間(注:要想將對象傳輸於網絡必須進行流化)因爲在對對象流進行讀寫操作時會引發一些問題,而序列化機制正是用來解決這些問題的。
(若對象不進行序列化,則無法跨平臺傳輸,安全性也無法得到保證)

3、序列化的方式?

方式1:要傳遞的類實現Serializable接口傳遞對象(Java自帶)

(1)介紹
1.1)Serializable接口
Serializable是序列化的意思,表示將一個對象轉換成可存儲或可傳輸的狀態。用於保存在內存中各種對象的狀態,並且可以把保存的對象狀態再讀出來。
序列化後的對象可以在網絡上進行傳輸,也可以存儲到本地。對於任何需要被序列化的對象,都必須要實現接口Serializable,它只是一個標識接口,本身沒有任何成員,只是用來標識說明當前的實現類的對象可以被序列化.
1.2)Java序列化應用場景
a.把內存中的對象狀態保存到一個文件中或者數據庫中時候;
b.用套接字在網絡上傳送對象的時候;
c.通過RMI傳輸對象的時候;…
(2)實例
使用Java序列化把對象存儲到文件中,再從文件中讀出來
創建序列化對象

public class Box implements Serializable{

    private int width;
    private int height;

    public Box(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }
    
    @Override
    public String toString() {
        return "Child{" +
                "width=" + width +
                ", height=" + height +
                '}';
    }
}

讀寫測試
使用ObjectOutputStream對象的writeObject()方法來進行對象的寫入。
使用ObjectInputStream對象的readObject()方法來讀取對象。

public class SerializableTest {

    public static void main(String args[]) throws Exception{
        
        File file = new File("box.out");  
        
        FileOutputStream fos = new FileOutputStream(file);
        ObjectOutputStream out = new ObjectOutputStream(fos);
        Box oldBox = new Box(10,20);
        out.writeObject(oldBox);
        out.close();
            
        FileInputStream fis = new FileInputStream(file);
        ObjectInputStream in = new ObjectInputStream(fis);
        Box newBox = (Box)in.readObject();
        in.close();
        System.out.println(newBox.toString());
        
    }
}

輸出結果

Child{width=10, height=20}

方式2:要傳遞的類實現Parcelable接口傳遞對象(android專用)

(1)介紹
1.1)Parcelable接口
進行Android開發的時候,無法將對象的引用傳給Activities或者Fragments,我們需要將這些對象放到一個Intent或者Bundle裏面,然後再傳遞
不過不同於將對象進行序列化,Parcelable方式的實現原理是將一個完整的對象進行分解,而分解後的每一部分都是Intent所支持的數據類型,這樣也就實現傳遞對象的功能了。
Parcelable作用:
1)永久性保存對象,保存對象的字節序列到本地文件中;
2)通過序列化對象在網絡中傳遞對象;
3)通過序列化在進程間傳遞對象。
1.2)Android序列化應用場景
需要在多個部件(Activity或Service)之間通過Intent傳遞一些數據,簡單類型(如:數字、字符串)的可以直接放入Intent。複雜類型必須實現Parcelable接口。
1)在使用內存的時候,Parcelable比Serializable性能高,所以推薦使用Parcelable。
2)Serializable在序列化的時候會產生大量的臨時變量,從而引起頻繁的GC。
3)Parcelable不能使用在要將數據存儲在磁盤上的情況,因爲Parcelable不能很好的保證數據的持續性在外界有變化的情況下。儘管Serializable效率低點,但此時還是建議使用Serializable 。
(2)實現
2.1)寫實體類,實現Parcelable接口
1、複寫describeContents方法和writeToParcel方法
2、實例化靜態內部對象CREATOR,實現接口Parcelable.Creator
3、自定義構造方法,私有變量的set與get

public class Pen implements Parcelable{
    private String color;
    private int size;
    
    // 系統自動添加,給createFromParcel裏面用
    protected Pen(Parcel in) {
        color = in.readString();
        size = in.readInt();
    }
    public static final Creator<Pen> CREATOR = new Creator<Pen>() {
        /**
         *
         * @param in
         * @return
         * createFromParcel()方法中我們要去讀取剛纔寫出的name和age字段,
         * 並創建一個Person對象進行返回,其中color和size都是調用Parcel的readXxx()方法讀取到的,
         * 注意這裏讀取的順序一定要和剛纔寫出的順序完全相同。
         * 讀取的工作我們利用一個構造函數幫我們完成了
         */
        @Override
        public Pen createFromParcel(Parcel in) {
            return new Pen(in); // 在構造函數裏面完成了 讀取 的工作
        }
        //供反序列化本類數組時調用的
        @Override
        public Pen[] newArray(int size) {
            return new Pen[size];
        }
    };
    
    @Override
    public int describeContents() {
        return 0;  // 內容接口描述,默認返回0即可。
    }
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(color);  // 寫出 color
        dest.writeInt(size);  // 寫出 size
    }
    // ======分割線,寫寫get和set
    //個人自己添加
    public Pen() {
    }
    //個人自己添加
    public Pen(String color, int size) {
        this.color = color;
        this.size = size;
    }
    
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
    public int getSize() {
        return size;
    }
    public void setSize(int size) {
        this.size = size;
    }
}

2.2)MainActivity與SecondActivity之間傳遞實體Bean
MainActivity

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.mTvOpenNew).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent open = new Intent(MainActivity.this, SecondActivity.class);
                Person person = new Person();
                person.setName("一去二三裏");
                person.setAge(18);
                // 傳輸方式一,intent直接調用putExtra
                // public Intent putExtra(String name, Serializable value)
                open.putExtra("put_ser_test", person);
                // 傳輸方式二,intent利用putExtras(注意s)傳入bundle
                /**
                 Bundle bundle = new Bundle();
                 bundle.putSerializable("bundle_ser",person);
                 open.putExtras(bundle);
                 */
                startActivity(open);
            }
        });
        // 採用Parcelable的方式
        findViewById(R.id.mTvOpenThird).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent mTvOpenThird = new Intent(MainActivity.this,ThirdActivity.class);
                Pen tranPen = new Pen();
                tranPen.setColor("big red");
                tranPen.setSize(98);
                // public Intent putExtra(String name, Parcelable value)
                mTvOpenThird.putExtra("parcel_test",tranPen);
                startActivity(mTvOpenThird);
            }
        });
    }
}

SecondActivity

public class SecondActivity extends Activity{
    private TextView mTvSecondDate;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        mTvSecondDate = (TextView) findViewById(R.id.mTvSecondDate);
//        Intent intent = getIntent();
//        Pen pen = (Pen)intent.getParcelableExtra("parcel_test");
        Pen pen = (Pen)getIntent().getParcelableExtra("parcel_test");
        mTvSecondDate = (TextView) findViewById(R.id.mTvSecondDate);
        mTvSecondDate.setText("顏色:"+pen.getColor()+"\\n"
                            +"大小:"+pen.getSize());
    }
}

Serializable 和Parcelable的對比

android上應該儘量採用Parcelable,效率至上
(1)編碼上:
Serializable代碼量少,寫起來方便;Parcelable代碼多一些
(2)效率上:
Parcelable的速度比高十倍以上;serializable的迷人之處在於你只需要對某個類以及它的屬性實現Serializable 接口即可。Serializable 接口是一種標識接口(marker interface),這意味着無需實現方法,Java便會對這個對象進行高效的序列化操作。
這種方法的缺點是使用了反射,序列化的過程較慢。這種機制會在序列化的時候創建許多的臨時對象,容易觸發垃圾回收。
Parcelable方式的實現原理是將一個完整的對象進行分解,而分解後的每一部分都是Intent所支持的數據類型,這樣也就實現傳遞對象的功能了

(三)內部類

1、什麼是內部類

定義在類內部的類就被稱爲內部類。外部類按常規的類訪問方式使用內部類,唯一的差別是內部類可以訪問外部類的所有方法與屬性,包括私有方法與屬性。
內部類是一個編譯時的概念。外部類outer.java內定義了一個內部類inner,一旦編譯成功,就會生成兩個完全不同的.class文件,分別是outer.class和outer$inner.class。

2、爲什麼要設計內部類

  • 內部類是爲了更好的封裝,把內部類封裝在外部類裏,不允許同包其他類訪問
  • 內部類中的屬性和方法即使是外部類也不能直接訪問,相反內部類可以直接訪問外部類的屬性和方法,即使private
  • 實現多繼承:每個內部類都能獨立地繼承一個(接口的)實現,所以無論外圍類是否已經繼承了某個(接口的)實現,對於內部類都沒有影響。
  • 匿名內部類用於實現回調

3、內部類分類

(3.1)靜態內部類

定義在類內部的靜態類
3.1.1)代碼
定義

public class Out {
    private static int a;
    private int b;

    public static class Inner {
        public void print() {
            System.out.println(a);
        }
    }
}

Inner是靜態內部類。靜態內部類可以訪問外部類所有靜態變量和方法。靜態內部類和一般類一致,可以定義靜態變量、方法,構造方法等。
使用

Out.Inner inner = new Out.Inner();
inner.print();

外部類.靜態內部類
3.1.2)實現原理

public class Out$Inner {
    public Out$Inner() {
    }

    public void print() {
        System.out.println(Out.access$000());
    }
}

Out.java編譯後會生成兩個class文件,分別是Out.class和Out$Inner.class。因爲這兩個類處於同一個包下,所以靜態內部類自然可以訪問外部類的非私有成員。對外部類私有變量的訪問則通過外部類的access$000()方法。
3.1.3)應用場景
與外部類關係密切且不依賴外部類實例。
Java集合類HashMap內部就有一個靜態內部類Entry。Entry是HashMap存放元素的抽象,HashMap內部維護Entry數組用了存放元素,但是Entry對使用者是透明的。

(3.2)成員內部類

定義在類內部的非靜態類稱爲成員內部類。
3.2.1)代碼
定義

public class Out {
    private static int a;
    private int b;

    public class Inner {
        public void print() {
            System.out.println(a);
            System.out.println(b);
        }
    }
}

成員內部類可以訪問外部類所有的變量和方法,包括靜態和實例,私有和非私有。和靜態內部類不同的是,每一個成員內部類的實例都依賴一個外部類的實例(成員內部類是依附外部類而存在的)。其它類使用內部類必須要先創建一個外部類的實例。
使用

Out out = new Out();
Out.Inner inner = out.new Inner();
inner.print();

注:
(1)成員內部類不能定義靜態方法和變量(final修飾的除外)。這是因爲成員內部類是非靜態的,類初始化的時候先初始化靜態成員,如果允許成員內部類定義靜態變量,那麼成員內部類的靜態變量初始化順序是有歧義的。
(2)成員內部類是依附於外圍類的,所以只有先創建了外圍類才能夠創建內部類。
(3)成員內部類與外部類可以擁有同名的成員變量或方法,默認情況下訪問的是成員內部類的成員。如果要外部類的同名成員,需用下面的形式訪問:

OutterClass(外部類).this.成員

3.2.2)實現原理
Out.java編譯後會生成兩個class文件,分別是Out.class和Out$Inner.class。成員內部類的代碼如下:

public class Out$Inner {

   public Out$Inner(Out var1) {
        this.this$0 = var1;
    }

    public void print() {
        System.out.println(Out.access$000());
    }
}

成員內部類訪問外部類的私有變量和方法也是通過編譯時生成的代碼訪問的。區別是,成員內部類的構造方法會添加一個外部類的參數。
解釋:爲什麼Java中成員內部類可以訪問外部類成員?
總結爲2點:1、內部類對象的創建依賴於外部類對象;2、內部類對象持有指向外部類對象的引用。
1 編譯器自動爲內部類添加一個類型爲Outer,名字爲this$0的成員變量,這個成員變量就是指向外部類對象的引用;
2 編譯器自動爲內部類的構造方法添加一個類型爲Outer的參數,在構造方法內部使用這個參數爲內部類中添加的成員變量賦值;
3 在調用內部類的構造函數初始化內部類對象時, 會默認傳入外部類的引用。
3.2.3)應用場景
靜態內部類&成員內部類對比
在這裏插入圖片描述

(3.3)局部內部類&閉包

1、局部內部類
定義在外部類方法中的類,叫局部類
3.3.1)代碼
定義

public class Out {
    private static int a;
    private int b;

    public void test(final int c) {
        final int d = 1;
        class Inner {
            public void print() {
                System.out.println(a);
                System.out.println(b);
                System.out.println(c);
                System.out.println(d);
            }
        }
    }

    public static void testStatic(final int c) {
        final int d = 1;
        class Inner {
            public void print() {
                System.out.println(a);
                //定義在靜態方法中的局部類不可以訪問外部類的實例變量
                //System.out.println(b);
                System.out.println(c);
                System.out.println(d);
            }
        }
    }
}

局部類只能在定義該局部類的方法中使用。定義在實例方法中的局部類可以訪問外部類的所有變量和方法,定義在靜態方法中的局部類只能訪問外部類的靜態變量和方法。同時局部類還可以訪問方法的參數和方法中的局部變量,這些參數和變量必須要聲明爲final的。否則會報錯
Cannot refer to a non-final variable x inside an inner class defined in a different method
3.3.2)實現原理
Out.java編譯後局部類會生成相應的class文件。

class Out$1Inner {
    Out$1Inner(Out var1, int var2) {
        this.this$0 = var1;
        this.val$c = var2;
    }

    public void print() {
        System.out.println(Out.access$000());
        System.out.println(Out.access$100(this.this$0));
        System.out.println(this.val$c);
        System.out.println(1);
    }
}

和成員內部類類似,生成的局部類的構造方法包含了外部類的參數,並且還包含了定義局部類方法的參數。
解釋了爲什麼局部類訪問的變量需要final修飾?
基本知識:內部類和外部類是處於同一個級別的,方法內部的類不是在調用方法時纔會創建的,它們一樣也被事先編譯了,內部類也不會因爲定義在方法中就會隨着方法的執行完畢就被銷燬。(內部類只有沒有引用指向該對象時,纔回被GC回收)
問題1:外部類方法結束時候,局部變量就會銷燬,但內部類對象可能還存在,並指向一個不存在的局部變量。
問題2:局部變量複製爲內部類的成員變量時,必須保證兩個變量一致。在內部類修改成員變量,方法中局部變量也會跟着改變。
解決:將局部變量設置爲final,
這樣編譯器會將final局部變量"複製"作爲局部內部類中的數據成員(且此時爲常量)
使內部類無法去修改這個變量。保證複製的數據成員與原始變量一致。
彷彿局部變量的"生命週期"延長了
3.3.3)應用場景
局部內部類是嵌套在方法和作用域內的,對於這個類的使用主要是應用與解決比較複雜的問題,想創建一個類來輔助我們的解決方案,但又不希望這個類是公共可用的,所以就產生了局部內部類,局部內部類和成員內部類一樣被編譯,只是它的作用域發生了改變,它只能在該方法和屬性中被使用,出了該方法和屬性就會失效。
2、閉包
2.1)定義
閉包就是把函數以及變量包起來,使得變量的生存週期延長。閉包跟面向對象是一棵樹上的兩條枝,實現的功能是等價的。
閉包(Closure)是一種能被調用的對象,它保存了創建它的作用域的信息。JAVA並不能顯式地支持閉包,但是在JAVA中,閉包可以通過“接口+內部類”(接口實例化)來實現。
2.2)使用
閉包是實現回調的一種很靈活的方式

public class OutClass {
    
    private void readBook(){
        System.out.println("read book");
    }

    public InnerClass getInnerClass(){
        return new InnerClass();
    }

    public class InnerClass{
        public void read(){
            readBook();
        }
    }

}

在該例種InnerClass即是一個閉包,它包含了OutClass的對象;調用InnerClass的read()方法會回調OutClass的readBook()方法。
2.3)應用
一個接口程序員和一個基類作家都有一個相同的方法work,相同的方法名,但是其含義完全不同,這時候就需要閉包。

interface Writer {//作家基類
    void work(){};
}
interface programmer{//程序員接口
    void work();
}

閉包實現

public class WriterProgrammer extends Writer {
    @Override
    public void work(){
        //寫作
    }
    public void code(){
        //寫代碼
    }
    public ProgrammerInner getProgrammerInner(){
    	return new ProgrammerInner();
	}
    class ProgrammerInner implements programmer{
        @Override
        public void work(){
            code();
        }
    }
}

使用

public static void main(String[] args) {
        WriterProgrammer writerProgrammer = new WriterProgrammer();
        writerProgrammer.work();
        writerProgrammer.getProgrammerInner().work();
}

問題
匿名函數裏的變量引用,也叫做變量引用泄露,會導致線程安全問題,因此如果在匿名類內部引用函數局部變量,必須將其聲明爲final,即不可變對象。

(3.4)匿名內部類

3.4.1)代碼
匿名內部類需要提前定義(必須存在)

public class Out {
    private static int a;
    private int b;

    private Object obj = new Object() {
        private String name = "匿名內部類";
        @Override
        public String toString() {
            return name;
        }
    };

    public void test() {
        Object obj = new Object() {
            @Override
            public String toString() {
                System.out.println(b);
                return String.valueOf(a);
            }
        };
        System.out.println(obj.toString());
    }
}

3.4.2)實現原理
Out.java編譯後匿名內部類會生成相應的class文件。

class Out$1 {
    private String name;

    Out$1(Out var1) {
        this.this$0 = var1;
        this.name = "匿名內部類";
    }

    public String toString() {
        return this.name;
    }
}

匿名內部類可以訪問外部類所有的變量和方法。
3.4.3)應用場景
匿名內部類常用於回調函數,比如我們常用的綁定監聽的時候。

    public interface OnClickListener {
        /**
         * Called when a view has been clicked.
         *
         * @param v The view that was clicked.
         */
        void onClick(View v);
    }

view.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    Toast.makeText(v.getContext(),"click",Toast.LENGTH_SHORT).show();    }
});

4、內部類總結

(4.1)內部類特點
1.非靜態內部類對象不僅指向該內部類,還指向實例化該內部類的外部類對象的內存。
2.內部類和普通類一樣可以重寫Object類的方法,如toString方法;並且有構造函數,執行順序依舊是先初始化屬性,再執行構造函數
3.在編譯完之後,會出現(外部類.class)和(外部類﹩內部類.class)兩個類文件名。
4.內部類可以被修飾爲private,只能被外部類所訪問。事實上一般也都是如此書寫。
5.內部類可以被寫在外部類的任意位置,如成員位置,方法內。
(4.2)內部類訪問外部類

  • 靜態時,靜態內部類只能訪問外部類靜態成員;非靜態內部類都可以直接訪問。(原因是:內部類有一個外部類名.this的指引)當訪問外部類靜態成員出現重名時,通過(外部類名.靜態成員變量名)訪問。如,Out.show();
  • 重名情況下,非靜態時,內部類訪問自己內部類通過this.變量名。訪問外部類通過(外部類名.this.變量名)訪問 。如Out.this.show();
  • 在沒有重名的情況下,無論靜態非靜態,內部類直接通過變量名訪問外部成員變量。
    (4.3)外部類訪問內部類
  • 內部類爲非靜態時,外部類訪問內部類,必須建立內部類對象。建立對象方法,如前所述。
  • 內部類爲靜態時,外部類訪問非靜態成員,通過(外部類對象名.內部類名.方法名)訪問,如new Out().In.function();
  • 內部類爲靜態時,外部類訪問靜態成員時,直接通過(外部類名.內部類名.方法名),如 Out.In.funchtion();
  • 當內部類中定義了靜態成員時,內部類必須是靜態的;當外部靜態方法訪問內部類時,內部類也必須是靜態的才能訪問。

(四)靜態屬性與靜態方法的繼承問題

1、靜態屬性與靜態方法是否能被繼承

(1.1)實例

Parent.java

public class Parent {//父類
    public String normalStr = "Normal member of parent.";
    public static String staticStr = "Static member of parent.";

    public void normalMethod(){
        System.out.println("Normal method of parent.");
    }

    public static void staticMethod(){
        System.out.println("Static method of parent.");
    }
}

ChildA.java

public class ChildA extends Parent {
//子類A繼承父類所有屬性與方法
}

Main.java

public class Main {
    public static void main(String[] args) {
        Parent child = new ChildA();
        System.out.println(child.normalStr);
        System.out.println(child.staticStr);
        child.normalMethod();
        child.staticMethod();
    }
}

結果

Normal member of parent.
Static member of parent.
Normal method of parent.
Static method of parent.

(1.2)結論

子類可以繼承父類的非靜態方法與非靜態屬性。
子類可以繼承父類的靜態方法與靜態屬性。

2、靜態屬性與靜態方法是否能被重寫

(2.1)實例

Parent.java

public class Parent {//父類
    public String normalStr = "Normal member of parent.";
    public static String staticStr = "Static member of parent.";

    public void normalMethod(){
        System.out.println("Normal method of parent.");
    }

    public static void staticMethod(){
        System.out.println("Static method of parent.");
    }
}

ChildA.java

public class ChildB extends Parent {
//子類B繼承父類所有屬性與方法
    public String normalStr = "Normal member of child.";
    public static String staticStr = "Static member of child.";

    public void normalMethod(){
        System.out.println("Normal method of child.");
    }

    public static void staticMethod(){
        System.out.println("Static method of child.");
    }
}

Main.java

public class Main {
    public static void main(String[] args) {
        Parent child = new ChildB();
        System.out.println(child.normalStr);
        System.out.println(Parent.staticStr);
        child.normalMethod();
        Parent.staticMethod();
    }
}

結果

Normal member of parent.
Static member of parent.
Normal method of child.
Static method of parent.

(2.2)結論

子類不可以重寫父類的靜態/非靜態屬性。父類的同名屬性會被隱藏。
子類可以重寫覆蓋父類的非靜態方法但不可重寫覆蓋父類靜態方法。

3、結論

對於非靜態的屬性和方法

  • 對於非靜態屬性,子類可以繼承父類的非靜態屬性。但是當子類和父類有相同的非靜態屬性時,並沒有重寫並覆蓋父類的非靜態屬性,只是隱藏了父類的非靜態屬性。
  • 對於非靜態的方法,子類可以繼承父類的非靜態方法並可以重寫覆蓋父類的非靜態屬性方法。

對於靜態的屬性和方法

  • 對於靜態的屬性,子類可以繼承父類的靜態屬性。但是和非靜態的屬性一樣,會被隱藏。
  • 對於靜態的方法,子類可以繼承父類的靜態方法。但是子類不可重寫覆蓋父類的靜態方法,子類的同名靜態方法會隱藏父類的靜態方法。

4、原因

其實,java中靜態屬性(靜態變量)和靜態方法沒有繼承這一說的,當然更沒有被重寫(overwrite),而是叫隱藏.

  • 靜態方法和屬性是屬於類的,調用的時候直接通過類名.方法名即可,不需要繼承機制也可以調用。如果子類裏面定義了同樣的靜態方法和屬性,那麼這時候父類的靜態方法或屬性稱之爲"隱藏"(hide)。如果你想要調用父類的靜態方法和屬性,直接通過父類名.方法或變量名完成。至於是否繼承一說,子類是有“繼承”靜態方法和靜態屬性,但是跟實例方法和屬性(實例變量)的繼承是不一樣的意思(這裏要切記),靜態的"繼承",通過子類能調用相關的靜態變量與靜態方法。
  • 多態之所以能夠實現依賴於繼承、接口和重寫、重載(繼承和重寫最爲關鍵)。有了繼承和重寫就可以實現父類的引用指向不同子類的對象。重寫的功能是:"重寫"後子類的優先級要高於父類的優先級,但是靜態變量與靜態方法的“隱藏”是沒有這個優先級之分的。
  • 靜態屬性(靜態變量)、靜態方法無繼承,但有隱藏,靜態方法又不能被重寫,因此不能實現多態。非靜態方法(實例發方法)可以被繼承和重寫,因此可以實現多態。

(五)Java編碼方式

一、字節與字符

字節(Byte)=8bit 默認數據的最小單位
字符(Character)=2byte=16bit(Java默認UTF-16編碼)

char: 2 byte
boolean: 1 byte
short: 2 byte
int: 4 byte
float: 4 byte
long: 8 byte
double: 8 byte

二、爲何要編碼——如何讓計算機表示人類能夠理解的符號

1、計算機中存儲信息的最小單元是1byte即8bit,所以能表示的字符範圍是 0~255 個
2、人類要表示的符號太多,無法用一個字節來完全表示
3、需要一個新的數據結構char來表示這些字符。char與byte之間轉換需要編碼與解碼
編碼:字節與字符的轉換方式

三、各類編碼規範

3.1 ASCII

我們知道,在計算機內部,所有的信息最終都表示爲一個二進制的字符串。每一個二進制位(bit)有0和1兩種狀態,因此八個二進制位就可以組合出256種狀態,這被稱爲一個字節(byte)。也就是說,一個字節一共可以用來表示256種不同的狀態,每一個狀態對應一個符號,就是256個符號,從0000000到11111111。
上個世紀60年代,美國製定了一套字符編碼,對英語字符與二進制位之間的關係,做了統一規定。這被稱爲ASCII碼,一直沿用至今。
ASCII碼一共規定了128個字符的編碼,比如空格"SPACE"是32(二進制00100000),大寫的字母A是65(二進制01000001)。這128個符號(包括32個不能打印出來的控制符號),只佔用了一個字節的後面7位,最前面的1位統一規定爲0。

3.2 ISO-8858-1

但西方世界不光只有英語一門語言。包括德語,法語,西班牙語都有自己的特殊字母。每個國家都可以定義屬於自己語言的特殊編碼標準,而且大小照樣不超過256。因爲ASCII碼中本身就有很多空碼位沒有使用。
128 個字符顯然是不夠用的,於是 ISO 組織在 ASCII 碼基礎上又制定了一些列標準用來擴展 ASCII 編碼,它們是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵蓋了大多數西歐語言字符,所有應用的最廣泛。ISO-8859-1 仍然是單字節編碼,它總共能表示 256 個字符。

3.3 GB2312

信息交換用漢字編碼字符集 基本集
雙字節編碼,總的編碼範圍是 A1-F7,其中從 A1-A9 是符號區,總共包含 682 個符號,從 B0-F7 是漢字區,包含 6763 個漢字。

3.4 GBK

漢字內碼擴展規範
擴展 GB2312,加入更多的漢字,它的編碼範圍是 8140~FEFE(去掉 XX7F)總共有 23940 個碼位,它能表示 21003 個漢字,它的編碼是和 GB2312 兼容的,也就是說用 GB2312 編碼的漢字可以用 GBK 來解碼,並且不會有亂碼。
這也是很多文件默認使用的編碼,有時候打開文件中文變亂碼了,這時候就需要規定編碼方式爲GBK。

3.5 Unicode

Unicode只是一個符號集,它只規定了符號的二進制代碼,卻沒有規定這個二進制代碼應該如何存儲。
在出現Unicode之前,幾乎每一種文字都有一套自己的編碼方式。同一段“字節流”,在美帝可能是"hello world",到我們天朝就變成“錕斤拷” ,“燙燙燙”了。
ISO 試圖想創建一個全新的超語言字典,世界上所有的語言都可以通過這本字典來相互翻譯。故這本字典很複雜。最初,每個字符佔用2個字節,總共65535個字符空間。從第四版開始加入的“擴展字符集”開始使用4個字節(32 bit)編碼。目前Unicode收錄的字符規模大概在12萬左右。
Unicode只是一套符號的編碼,計算機對Unicode字符具體存取方式有:UTF-16(字符用2字節表示)、UTF-32(字符用4字節表示)、UTF-8(字符不定長)

3.5.1 UTF-16

UTF-16 具體定義了 Unicode 字符在計算機中存取方法。UTF-16 用兩個字節來表示 Unicode 轉化格式,這個是定長的表示方法,不論什麼字符都可以用兩個字節表示,兩個字節是 16 個 bit,所以叫 UTF-16。
UTF-16 表示字符非常方便,每兩個字節表示一個字符,這個在字符串操作時就大大簡化了操作,這也是 Java 以 UTF-16 作爲內存的字符存儲格式的一個很重要的原因。
用 UTF-16 編碼將 char 數組放大了一倍,單字節範圍內的字符,在高位補 0 變成兩個字節,中文字符也變成兩個字節。從 UTF-16 編碼規則來看,僅僅將字符的高位和地位進行拆分變成兩個字節。特點是編碼效率非常高,規則很簡單,但是這對於存儲來說是極大的浪費,可以看到補了很多的0,文本文件的大小會因此大出二三倍,這是無法接受的。

3.5.2 UTF-8

互聯網的普及,強烈要求出現一種統一的編碼方式。UTF-8就是在互聯網上使用最廣的一種Unicode的實現方式。
UTF-8最大的一個特點,就是它是一種變長的編碼方式。它可以使用1~4個字節表示一個符號,根據不同的符號而變化字節長度。
UTF-8的編碼規則很簡單,只有二條:
1、對於單字節的符號,字節的第一位設爲0,後面7位爲這個符號的unicode碼。因此對於英語字母,UTF-8編碼和ASCII碼是相同的。
2、對於n字節的符號(n>1),第一個字節的前n位都設爲1,第n+1位設爲0,後面字節的前兩位一律設爲10。剩下的沒有提及的二進制位,全部爲這個符號的unicode碼。

四、幾種編碼格式的比較

1、GB2312 與 GBK 編碼規則類似,但是 GBK 範圍更大,它能處理所有漢字字符,所以 GB2312 與 GBK 比較應該選擇 GBK。
2、UTF-16 與 UTF-8 都是處理 Unicode 編碼,它們的編碼規則不太相同,相對來說 UTF-16 編碼效率最高,字符到字節相互轉換更簡單,進行字符串操作也更好。它適合在本地磁盤和內存之間使用,可以進行字符和字節之間快速切換,如 Java 的內存編碼就是採用 UTF-16 編碼。但是它不適合在網絡之間傳輸,因爲網絡傳輸容易損壞字節流,一旦字節流損壞將很難恢復,想比較而言 UTF-8 更適合網絡傳輸,對 ASCII 字符采用單字節存儲,另外單個字符損壞也不會影響後面其它字符,在編碼效率上介於 GBK 和 UTF-16 之間,所以 UTF-8 在編碼效率上和編碼安全性上做了平衡,是理想的中文編碼方式。

五、總結

總而言之,一切都是字節流,其實沒有字符流這個東西。字符只是根據編碼集對字節流翻譯之後的產物。而編碼集是人爲規定的產物。

六、Java對字符的處理

(1)IO操作中存在的編碼
我們知道涉及到編碼的地方一般都在字符到字節或者字節到字符的轉換上,而需要這種轉換的場景主要是在 I/O 的時候,這個 I/O 包括磁盤 I/O 和網絡 I/O,關於網絡 I/O 部分在後面將主要以 Web 應用爲例介紹。下圖是 Java 中處理 I/O 問題的接口:
在這裏插入圖片描述
Reader 類是 Java 的 I/O 中讀字符的父類,而 InputStream 類是讀字節的父類,InputStreamReader 類就是關聯字節到字符的橋樑,它負責在 I/O 過程中處理讀取字節到字符的轉換,而具體字節到字符的解碼實現它由 StreamDecoder 去實現,在 StreamDecoder 解碼過程中必須由用戶指定 Charset 編碼格式。值得注意的是如果你沒有指定 Charset,將使用本地環境中的默認字符集,例如在中文環境中將使用 GBK 編碼。
寫的情況也是類似,字符的父類是 Writer,字節的父類是 OutputStream,通過 OutputStreamWriter 轉換字符到字節。如下圖所示:
在這裏插入圖片描述
同樣 StreamEncoder 類負責將字符編碼成字節,編碼格式和默認編碼規則與解碼是一致的。
如下面一段代碼,實現了文件的讀寫功能:

String file = "c:/stream.txt";    
String charset = "UTF-8";    
// 寫字符換轉成字節流   
FileOutputStream outputStream = new FileOutputStream(file);    
OutputStreamWriter writer = new OutputStreamWriter(    
outputStream, charset);    
try {    
   writer.write("這是要保存的中文字符");    
} finally {    
   writer.close();    
}    
// 讀取字節轉換成字符   
FileInputStream inputStream = new FileInputStream(file);    
InputStreamReader reader = new InputStreamReader(    
inputStream, charset);    
StringBuffer buffer = new StringBuffer();    
char[] buf = new char[64];    
int count = 0;    
try {    
   while ((count = reader.read(buf)) != -1) {    
       buffer.append(buffer, 0, count);    
   }    
} finally {    
   reader.close();    
}   

(2)內存中操作中的編碼
在 Java 開發中除了 I/O 涉及到編碼外,最常用的應該就是在內存中進行字符到字節的數據類型的轉換,Java 中用 String 表示字符串,所以 String 類就提供轉換到字節的方法,也支持將字節轉換爲字符串的構造函數。如下代碼示例:

  1. getBytes(charset)
    這是java字符串處理的一個標準函數,其作用是將字符串所表示的字符按照charset編碼,並以字節方式表示。注意字符串在java內存中總是按unicode編碼存儲的。比如"中文",正常情況下(即沒有錯誤的時候)存儲爲"4e2d 6587",如果charset爲"gbk",則被編碼爲"d6d0 cec4",然後返回字節"d6 d0 ce c4".如果charset爲"utf8"則最後是"e4 b8 ad e6 96 87".如果是"iso8859-1",則由於無法編碼,最後返回 “3f 3f”(兩個問號)。

java.class類的編碼爲:unicode;
windows默認的編碼爲:中文:gb2312; 英文:iso8859;

String str = "張三" ;
byte[] jiema= str.getBytes("gb2312") ; //解碼
String bianma = new String(jiema,"UTF-8");//編碼 如果上面的解碼不對 可能出現問題
  1. new String(charset)
    這是java字符串處理的另一個標準函數,和上一個函數的作用相反,將字節數組按照charset編碼進行組合識別,最後轉換爲unicode存儲。參考上述getBytes的例子,“gbk” 和"utf8"都可以得出正確的結果"4e2d 6587",但iso8859-1最後變成了"003f 003f"(兩個問號)。
    因爲utf8可以用來表示/編碼所有字符,所以new String( str.getBytes( “utf8” ), “utf8” ) === str,即完全可逆。
  2. setCharacterEncoding()
    該函數用來設置http請求或者相應的編碼。
    對於request,是指提交內容的編碼,指定後可以通過getParameter()則直接獲得正確的字符串,如果不指定,則默認使用iso8859-1編碼,需要進一步處理。參見下述"表單輸入".值得注意的是在執行setCharacterEncoding()之前,不能執行任何getParameter()。java doc上說明:This method must be called prior to reading request parameters or reading input using getReader()。而且,該指定只對POST方法有效,對GET方法無效。分析原因,應該是在執行第一個getParameter()的時候,java將會按照編碼分析所有的提交內容,而後續的getParameter()不再進行分析,所以setCharacterEncoding()無效。而對於GET方法提交表單是,提交的內容在URL中,一開始就已經按照編碼分析所有的提交內容,setCharacterEncoding()自然就無效。
    對於response,則是指定輸出內容的編碼,同時,該設置會傳遞給瀏覽器,告訴瀏覽器輸出內容所採用的編碼。

(六)Java的異常體系

1、Java異常的基礎知識

異常知識體系樹如下圖:
在這裏插入圖片描述
Java異常以Throwable開始,擴展出Error和Exception。
Error是程序代碼無法處理的錯誤,比如OutOfMemoryError、ThreadDeath等。這些異常發生時,Java虛擬機(JVM)一般會選擇線程終止退出,其表示程序在運行期間出現了十分嚴重、不可恢復的錯誤,應用程序只能中止運行。
Exception分運行時異常和非運行時異常。運行時異常都是RuntimeException類及其子類異常,如NullPointerException、IndexOutOfBoundsException等,這些異常也是不檢查異常,程序代碼中自行選擇捕獲處理,也可以不處理。這些異常一般是由程序邏輯錯誤引起的,程序代碼應該從邏輯角度儘可能避免這類異常的發生。所有繼承Exception且不是RuntimeException的異常都是非運行時異常,也稱檢查異常,如上圖中的IOException和ClassNotFoundException,編譯器會對其作檢查,故程序中一定會對該異常進行處理,處理方法要麼在方法體中聲明拋出checked Exception,要麼使用catch語句捕獲checked Exception進行處理,不然不能通過編譯。

2、業務異常的通常處理機制(可選,局部異常處理)

(2.1)不想處理或未知的異常一直往外拋直至到最上層集中處理,注意集中處理,處理時必須輸出對應的日誌。

如:利用Sring mvc支持異常集中處理特性
不想處理或未知的異常從dao->service->controller往上拋,然後在controller統一集中處理,當然可按需集中處理,如不處理統一交給全局異常處理。

注意:異常集中處理時不能丟掉或吃掉異常,一定要把異常捕獲並後臺輸出錯誤日誌,但頁面上不能輸出錯誤日誌,且響應狀態碼不能設置爲200,要按需設置爲40x或50x。

在這裏插入圖片描述

(2.2)Jsp頁面處理異常

Jsp代碼拋出異常並結合errorPage搭配組合使用。如:新建一個異常接收頁面error.jsp,在該頁面指定<%@ page isErrorPage=“true”%>,然後其他jsp頁面的異常處理指向它,<%@ page errorPage=“error.jsp”%>當然可按需處理,如不處理統一給全局異常處理。

注意:error.jsp不能丟掉或吃掉異常,一定要把異常捕獲並後臺輸出錯誤日誌,但頁面上不能輸出錯誤日誌,且error.jsp的響應狀態碼不能設置爲200,要按需設置爲40x或50x。

3、全局異常處理(必須,上面第二步沒處理的異常最後統一處理)

(3.1)通過web.xml配置接收異常的頁面

在這裏插入圖片描述
其他http響應狀態碼按需配置,如400、502、503、504等

(3.2)按指定異常配置接收異常頁面

在這裏插入圖片描述

注意:此異常接收處理頁面不能用靜態頁必須是動態頁,且不能丟掉或吃掉異常,一定要把異常捕獲並後臺輸出錯誤日誌,但頁面上不能輸出錯誤日誌,且異常接收頁面的響應狀態碼不能設置爲200,要按需設置爲40x或50x。

以上第二和第三部分互爲一體,有些異常需要局部處理的按需處理。有些異常可以進行全局處理。

4、一些必須及時捕獲處理異常的場景

  • 用多線程實現的定時任務在循環處理數據時出現異常必須及時處理,否則執行時會退出。
  • 頁面豆腐塊接口或供外接口必須處理異常,如出現異常返回空字符串或其他指定格式的信息提示返回。
  • ajax異步調用的接口必須處理異常,如出現異常返回空字符串或其他指定格式的信息提示返回。

5、一些關於處理異常的重要原則

  • 捕獲異常是爲了處理它,捕獲異常後吃掉不作任何處理是毫無節操無人品的耍流氓,至少要輸出簡單的錯誤日誌提示,如果不想處理它,請將該異常拋給它的調用者。捕獲異常後不處理的代碼示例:
	try{
		Do something;
	}catch(Exeception e){
		//此處無任何代碼處理異常,挖坑作死的節奏!
	}
  • 異常不要用來做流程或條件控制,因爲異常的處理效率比較低。
  • 防止出現空指針異常是程序員的基本修養,注意該異常產生的場景。
  • 當方法判斷出錯該返回時應該拋出異常,該拋異常就得拋,而不是返回一些錯誤值,如返回-1 或者 -2 之類的錯誤值。
  • 如需處理處理異常,其處理的粒度不能太粗,如幾百行代碼放到一個try-catch 塊中處理,應該一個一個異常放在各自的try-catch 塊中處理。
  • 對於一個應用來說,應該要有自己的一套整體的異常處理機制,當各種異常發生時能得到相應統一的處理風格,將友好的異常信息反饋給用戶。

(七)final、finally、finalize區別與使用

1、final 修飾符(關鍵字)

final用於控制成員、方法或者是一個類是否可以被重寫或者繼承等功能。
(1)如果類被聲明爲final,意味着它不能再派生出新的子類,不能作爲父類被繼承。
(2)將變量或者方法聲明爲final,可以保證他們在使用中不被改變。其初始化可以在兩個地方:一是其定義處,也就是說,在final變量定義時直接給其賦值;二是構造函數中。這2個地方只能選其一,要麼在定義處直接給其賦值,要麼在構造函數中給值,並且在以後的引用中,只能讀取,不可修改。被聲明爲final的方法也同樣只能使用,不能重寫。

2、finally(用於異常處理)

一般是用於異常處理中,提供finally塊來執行任何的清楚操作,try{} catch(){} finally{}。finally關鍵字是對java異常處理模型的最佳補充。**finally結構使代碼總會執行,不關有無異常發生。**使得finally可以維護對象的內部狀態,並可以清理非內存資源。
finally在try,catch中可以有,可以沒有。如果trycatch中有finally則必須執行finally塊中的操作。一般情況下,用於關閉文件的讀寫操作,或者是關閉數據庫的連接等等。

3、finalize(用於垃圾回收)

finalize這個是方法名。在java中,允許使用finalize()方法在垃圾收集器將對象從內存中清理出去之前做必要的清理工作。這個方法是Object類中定義的,因此,所有的類都繼承了它。finalize()方法是在垃圾收集器刪除對象之前對這個對象調用的。
一旦垃圾回收器準備釋放對象所佔的內存空間,如果對象覆蓋了Object的finalize()並且函數體內不爲空,就會首先調用對象的finalize(),然後在下一次垃圾回收動作發生的時候真正回收對象所佔的空間。
儘量避免使用finalize():
1、finalize()不一定會被調用, 因爲java的垃圾回收器的特性就決定了它不一定會被調用.
2、就算finalize()函數被調用, 它被調用的時間充滿了不確定性, 因爲程序中其他線程的優先級遠遠高於執行finalize()函數線程的優先級。也許等到finalize()被調用,數據庫的連接池或者文件句柄早就耗盡了.
3、如果一種未被捕獲的異常在使用finalize方法時被拋出,這個異常不會被捕獲,finalize方法的終結過程也會終止,造成對象出於破壞的狀態。被破壞的對象又很可能導致部分資源無法被回收, 造成浪費.
4、finalize()和垃圾回收器的運行本身就要耗費資源, 也許會導致程序的暫時停止.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章