《Think In Java》閱讀筆記 ·第一卷
過載
基本型別的過載
當傳入較小精確度的數值時,會呼叫擁有最接近該精確度形參的方法。
當傳入較大精確度的數值時,需要至少窄化轉換成所有過載方法中最大精確度的數值,否則編譯器將報錯。
不能通過方法的返回值來區分過載方法
構造器的過載
在構造器中可以呼叫其它過載構造器,但只能呼叫一個,且必須放在該構造器的首行。
初始化
靜態成員的初始化
public class Initialization {
public static final void main(String[] args) {
new First();
}
}
class First{
Second second=new Second();
static Staticer1 staticer=new Staticer1();
public First() {
System.out.println("First");
}
}
class Second{
public Second() {
System.out.println("Second");
}
static Staticer2 staticer2=new Staticer2();
}
class Staticer1{
public Staticer1() {
System.out .println("Staticer1");
}
}
class Staticer2{
public Staticer2() {
System.out.println("Staticer2");
}
}
Staticer1
Staticer2
Second
First
靜態優先,當靜態成員所在類沒有用到則不會初始化該靜態成員
構造器外優先,構造器外面的成員初始化完才開始初始化構造器中的
成員變數會自動初始化,如定義陣列如果沒有初始化會預設全部至為0,若物件引用沒有初始化會預設至為null
但區域性變數不會,需要自己初始化
清理
引用計數:引用連線至物件時,引用計數加一,當離開作用域或被至為null時,則減一。
看到P161後的補充:
public class OOer{
public static final void main(String[] args) {
OOers2 os2=new OOers2();
OOers1[] os1= {new OOers1(os2),new OOers1(os2),new OOers1(os2),new OOers1(os2),new OOers1(os2)};
for(OOers1 o1:os1) {
o1.dispose();
}
}
protected void dispose() {
System.out.println("OOers dispose");
}
}
class OOers1{
private static int count=0;
private final int id=count++;
private OOers2 os2;
public OOers1(OOers2 os2) {
System.out.println("OOers1 Creating:"+this);
this.os2=os2;
os2.add();
}
protected void dispose() {
System.out.println("OOers1 dispose");
os2.dispose();
}
public String toString() {
return "OOers2 id:"+id;
}
}
class OOers2{
private int flag=0;
public void add() {
++flag;
}
protected void dispose() {
if(--flag==0) {
System.out.println("OOers2 dispose");
}
}
}
OOers1 Creating:OOers2 id:0
OOers1 Creating:OOers2 id:1
OOers1 Creating:OOers2 id:2
OOers1 Creating:OOers2 id:3
OOers1 Creating:OOers2 id:4
OOers1 dispose
OOers1 dispose
OOers1 dispose
OOers1 dispose
OOers1 dispose
OOers2 dispose
迴圈引用:
public class Circular{
private First first=new First();
private Second second=new Second();
private void test(){
first.s=second;
second.f=first;
}
}
first擁有Second的引用,而second也擁有First的引用。
當該類中的test方法執行完後,gc理應回收first和second物件,但要回收First得先回收second所擁有的First引用,而要回收second所擁有的First引用得先回收first擁有的Second的引用。
故迴圈引用問題讓gc不能通過引用計數方法清理。
解決“互動自引用的物件組”問題的方法:對於“活”的物件一定能最終追溯到位於堆疊或靜態儲存區的引用。
找到”活“的物件後:
停止-複製:將程式停下,然後將”活“的物件複製到另一個堆中,沒被轉移過來的就是待清理的物件。
標記-清掃:類似於停止-複製遍歷所有引用,找到”活“的物件,但不復制只作標記,當所有標記都作完後就清理。
自適應:即結合停止-複製和標記-清掃來使用,當垃圾較多時運用第一種方法,當垃圾較少時用第二種方法。
即時編譯器(JIT):將程式全部或部分程式碼編譯成機器碼(本應是JVM的工作)。
解構函式 finalize
在垃圾回收器執行時呼叫被回收物件的finalize()方法,可以覆蓋此方法來實現對一些JVM無法回收的資源的回收。一旦垃圾回收器準備釋放物件佔用的記憶體,將首先呼叫該物件的finalize()方法,而JVM在下一次垃圾回收動作發生時,才真正回收物件佔用的記憶體空間。
public class Finalizesr {
public static final void main(String[] args) {
//Finalizesr1 f1=new Finalizesr1();
new Finalizesr1();
System.gc();
}
}
class Finalizesr1{
@Override
protected void finalize() throws Throwable {
System.out.println("I am killed");
super.finalize();
}
}
若程式碼中用註釋掉的語句替換掉下一條語句,則finalize不會被呼叫,因為該記憶體依然有引用指向它
可變引數
public class ChangeParam{
//此處引數已相當於String[] args
public static final void main(String...args){
for(String str:args){
System.out.println(str);
}
}
}
向main函式傳參的方法為:
命令列下:
java xxx a b c
在eclipse下:
run as->run configurations->Argument
然後在program arguments中輸入要傳入的引數,一行一個
列舉型別
enum People{
Dog,Cat,Bird;
}
public class Enumer {
public static final void main(String[] args) {
EnumerSwitch enumerSwitch=new EnumerSwitch(EnumerSwitch.Animal.Cat);
enumerSwitch.judge();
}
}
class EnumerSwitch{
Animal animal = null;
protected static enum Animal{
Dog,Cat,Bird;
}
public EnumerSwitch(Animal animal) {
this.animal=animal;
}
protected void judge() {
switch (animal) {
case Dog:
System.out.println("Dog");
break;
default:
break;
}
}
}
編譯時常量與執行時常量
public class TestFinal1 {
public static final void main(String...args) {
System.out.println(TestFinal2.i);
System.out.println(TestFinal2.j);
}
}
class TestFinal2{
static {
System.out.println("static");
}
final static int i=5;
final static int j="test".length();
}
5
static
4
i為編譯時常量,而j為執行時常量。
編譯時常量:當類還沒載入時就載入
執行時常量:當類載入完後才載入,而類載入完後首先執行static程式碼塊,所以j會在static後面
空白final
空白final指的是在定義常量時可以先不賦初值,但必須在域或構造方法中賦值。
多型
多型的侷限
- 無法覆蓋私有方法
- 域和靜態方法的呼叫多型無效
eg:
public class MainClass{
public String s="String_MainClass";
private void f() {
System.out.println("MainClass");
}
public static void sf(){
System.out.println("static_MainClass");
}
public static final void main(String...args) {
MainClass m=new ChildClass();
m.f();
System.out.println(s);
m.sf();
}
}
public class ChildClass extends MainClass{
public String s="String_ChildClass";
public static void sf(){
System.out.println("static_ChildClass");
}
public void f() {
System.out.println("ChildClass");
}
}
MainClass
String_MainClass
static_MainClass
public class MainClass{
protected void f() {
System.out.println("MainClass");
}
public static final void main(String...args) {
MainClass m=new ChildClass();
m.f();
}
}
public class ChildClass extends MainClass{
public void f() {
System.out.println("ChildClass ");
}
}
ChildClass
構造器內部多型方法導致的問題
public class Conster extends Conster1{
private int r=1;
protected void f() {
System.out.println("Conster_f:"+r);
}
public static final void main(String...args) {
Conster c=new Conster();
c.f();
}
}
class Conster1{
public Conster1() {
System.out.println("run before f");
f();
System.out.println("run after f");
}
protected void f() {
System.out.println("Conster1_f");
}
}
run before f
Conster_f:0
run after f
Conster_f:1
該程式碼初始化的實際過程為:
- 在其他任何事情發生前,將分配給物件的儲存空間初始化為二進位制的零。
- 然後呼叫其基類的構造器,然後會呼叫在被覆蓋的方法f,由步驟1可知此時的r並不是1而是0。
- 按照宣告的順序呼叫成員的初始化方法。
- 呼叫匯出類的構造器主體。
總結:應該儘量避免在構造方法中呼叫其它方法,唯一安全的是呼叫final方法(private方法)。
協變返回型別
匯出類覆蓋基類的方法返回型別可以不同(在兩個不同返回型別是繼承關係的基礎上)。
public class Xiebianer{
public static final void main(String...args) {
Xiebianer2 xb2=new Xiebianer1();
System.out.println(xb2.xb());
}
}
class Xiebianer1 extends Xiebianer2{
Xiebianer3 xb() {
return new Xiebianer3();
}
}
class Xiebianer2{
Xiebianer4 xb() {
return new Xiebianer4();
}
}
class Xiebianer3 extends Xiebianer4{
public String toString() {
return "Xiebianer3";
}
}
class Xiebianer4{
public String toString() {
return "Xiebianer4";
}
}
Xiebianer3
需要注意的是:如果覆蓋方法的返回型別不同,那麼匯出類方法的返回型別與基類方法的返回型別的關係必須分別是匯出類和基類的關係。
例如如果用Xiebianer4型別的方法去覆蓋Xiebianer3型別的方法就會出錯,因為:Xiebianer4是Xiebianer3的基類。
//...
class Xiebianer1 extends Xiebianer2{
Xiebianer4 xb() {
return new Xiebianer4();
}
}
class Xiebianer2{
Xiebianer3 xb() {
return new Xiebianer3();
}
}
//...
出錯
“多重繼承”
菱形問題:當有兩個類a,b都繼承了同一個類c,而類d又都繼承了a,b,那類d就有兩個祖類c,此時就會出現一些衝突內容,如a重寫了c的f方法,而b也重寫了f方法。該問題存在於支援多重繼承語言中,如c++,python。
用多介面防止菱形問題
public interface IDoubler extends IDoubler1,IDoubler2{
public void f();
}
public interface IDoubler1 extends IDoubler3{
public void f1();
}
public interface IDoubler2 extends IDoubler3{
public void f2();
}
public interface IDoubler3 {
public void f3();
}
public class DoInterfacer implements IDoubler{
@Override
public void f1() {
// TODO Auto-generated method stub
System.out.println("f1");
}
@Override
public void f3() {
// TODO Auto-generated method stub
System.out.println("f3");
}
@Override
public void f2() {
// TODO Auto-generated method stub
System.out.println("f2");
}
@Override
public void f() {
// TODO Auto-generated method stub
System.out.println("f");
}
}
public class ApplyIter{
IDoubler1 id1=new DoInterfacer();
public static final void main(String...args) {
ApplyIter ai=new ApplyIter();
ai.id1.f1();
}
}
f1
組合介面時名字衝突現象
public class Typer extends TypExtender implements I2{
}
public interface I2 {
public void f();
}
public class TypExtender {
public int f() {
return 1;
}
}
public interface I1 extends I2,I3{
}
public interface I2 {
public void f();
}
public interface I3 {
public int f();
}
The return types are incompatible for the inherited methods I2.f(), TypExtender.f()
The return types are incompatible for the inherited methods I2.f(), I3.f()
Readable與Scanner的用法
import java.io.IOException;
import java.nio.CharBuffer;
import java.util.Random;
import java.util.Scanner;
public class Readabler implements Readable{
private int count=0;
public Readabler(int count) {
this.count=count;
}
private Random r=new Random(55);
public static final char[] str1="abcdddddddssssdefghijddddddddddddklmn".toCharArray();
public static final char[] str2="opqrstuvwxdddsssssssssssdddddyzqqqq".toCharArray();
@Override
public int read(CharBuffer cb) throws IOException {
if(count--==0) {
return -1;
}
for(int i=0;i<5;i++) {
cb.append(str1[r.nextInt(str1.length)]);
cb.append(str2[r.nextInt(str2.length)]);
}
cb.append(" ");
return count;
}
public static final void main(String[] args) {
Scanner s=new Scanner(new Readabler(5));
while(s.hasNext()) {
System.out.println(s.next());
}
}
}
介面的任何域都自動是static和final的,介面修飾符只能是public和default
工廠設計模式
直接get獲取物件,而不需要new
interface IGraphic{
public void drawer();
}
interface IGraphicFactory{
public IGraphic getIGraphic();
}
class Circle implements IGraphic{
@Override
public void drawer() {
System.out.println("Circle");
}
}
class Bicycle implements IGraphic{
@Override
public void drawer() {
System.out.println("Bicycle");
}
}
class CircleFactory implements IGraphicFactory{
@Override
public IGraphic getIGraphic() {
return new Circle();
}
}
class BicycleFactory implements IGraphicFactory{
@Override
public IGraphic getIGraphic() {
return new Bicycle();
}
}
public class GraphicFactorer {
public void draw(IGraphicFactory igf ) {
IGraphic ig=igf.getIGraphic();
ig.drawer();
}
public static final void main(String[] args) {
GraphicFactorer gf=new GraphicFactorer();
gf.draw(new CircleFactory());
gf.draw(new BicycleFactory());
}
}
Circle
Bicycle