再读《Java编程思想》(Review 《Thinking in Java 3rd》)(5-8章)

来源:互联网 发布:淘宝卖食用农产品 编辑:程序博客网 时间:2024/06/09 19:45

原创文章,转载请注明出处:http://blog.csdn.net/wind5shy/article/details/5344447

第五章隐藏具体实现

(By wind5shyhttp://blog.csdn.net/wind5shy)

访问控制

1.      使用户知道什么数据是需要知道的,什么数据是不该访问的;。

2.      程序设计者可以更改程序内部实现而不对现有程序造成影响。

 

 

同一个类

同一个包

不同包的子类

不同包的非子类

public

protected

×

package(默认)

×

×

private

×

×

×

 

n      类修饰符只有public与默认(package),不能为privateprotected。如果需要类似privateprotected的作用,可以将类的构造器设为privateprotected。每个编译单元(java文件,不是编译后的class文件)只能有一个public类,这表示每个编译单元都有一个单一的公共接口,由public类来表现。这样做仍然是为了隐藏具体实现,需要提供给用户的类就定义为public,不需要的就为默认。

n      值得注意的是,protected修饰的成员或方法在包内访问如同public,在包外则只能通过继承的方式访问。个人觉得这样做的目的是为了使继承的结构更清楚。

 

一个例子:

package test2;

//AInheritanceTest分别在不同的包里,所以A需为public InheritanceTest中才能访问A

public class A {

   protected A() {

 

   }

 

   public A(int n) {

 

   }

 

   protected int n;

 

   public static long l;

 

   protected void f() {

 

   }

   

   private void g() {

      

   }

 

   public static void v() {

 

   }

 

}

 

package test;

 

import test2.A;

 

class B extends A {

   protected int b;

 

   protected void b() {

      n = 5;//可以通过继承方式访问nf()

      super.f();

      // A a = new A();B虽然继承A,但在不同包,所以不能调用其protected构造器

      A a = new A(1);

      // a.n = 2;同上,也不能通过创建对象来访问其protected成员和方法

      // a.f();

      // super.g();g()private,用继承方式也无法访问

   }

}

 

class C extends B {

   int c = new B().b;

 

   void c() {

      new B().b();

   }

}

 

public class InheritanceTest {

   public static void main(String[] args) {

      A.l = 5;//可以访问A的静态成员和方法,即访问A

      A.v();

      // A a = new A();A的默认构造器为protected,无法用其创建A的对象

      A a = new A(1);//A带参数的构造器为public,可以用其创建A的对象a

      // a.n = 2;无法访问protected的成员和方法nf()

      // a.f();

   }

}

 

import

个人认为使用import主要是为了使用其他包的对象是可以简化输入,否则就要输入全名,如java.util.ArrayList al = new java.util.ArrayList();

 

package

一个java文件只能有一个public类且必须命名与java文件相同(main所在的类无需是public,只需mainpublic就可以运行)。在相同目录下且没有设定包的java文件默认为同一个缺省包。

 

CLASSPATH

用来告诉JDK已编译的class文件的根目录位置,可以设置多个。

 

 (By wind5shyhttp://blog.csdn.net/wind5shy)

 

第六章复用类

(By wind5shyhttp://blog.csdn.net/wind5shy)

继承初始化

子类包含父类对象,初始化子类时从最上层父类开始,所有类中的静态成员先被初始化(因为静态成员是属于类的),再从根据层次上到下一个类一个类地初始化,比如:

class A {

A() {

      System.out.println("A");

   }

 

   int a;

   {

      a = 1;

      System.out.println("a = " + a);

   }

   static int sa;

   static {

      sa = 10;

      System.out.println("sa = " + sa);

   }

}

 

class B extends A {

B() {

      System.out.println("B");

   }

 

   int b;

   {

      b = 2;

      System.out.println("b = " + b);

   }

   static int sb;

   static {

      sb = 20;

      System.out.println("sb = " + sb);

   }

}

 

public class C extends B {

C() {

      System.out.println("C");

   }

 

   int c;

   {

      c = 3;

      System.out.println("c = " + c);

   }

   static int sc;

   static {

      sc = 30;

      System.out.println("sc = " + sc);

   }

 

   public static void main(String[] args) {

      new Test();

   }

}

结果:

sa = 10

sb = 20

sc = 30

a = 1

A

b = 2

B

c = 3

C

 

组合与继承的使用

多用组合,少用继承。在需要“向上转型”的时候用继承,其他时候慎用。

 

final

修饰成员

常量(分编译期常量与运行期常量,即编译时就被能初始化与运行时才能被初始化,与是否static无关,final static只是说明是类常量),必须被初始化,一旦初始化后不能改变(即只有初始化能设定值,之后不能在方法内赋值):基本类型表示值不变,引用表示指向的对象不变(引用的值是地址,所以其实也是值不变)即不能指向新的对象,对象本身可以改变(数组是对象,无法使数组中的值为final)。

 

修饰方法

目的1、方法在子类中不变即不能被覆写。2、效率问题,final方法可以转为内嵌(inline)调用,即调用该方法时直接复制代码而不是call,不用动态绑定。

private方法隐含final(也可显示写明final),因为其在子类里不能被访问,也就不能被覆写。如果父类有private方法,而子类有同名方法,这时子类的该方法不是覆写,而是一个新方法。

 

修饰类

类不能被继承。类中的成员可以是final也可以不是,但方法隐含final,因为不会被覆写。

(By wind5shyhttp://blog.csdn.net/wind5shy)

 

第七章多态

(By wind5shyhttp://blog.csdn.net/wind5shy)

多态

n      “相同的外观不同的形式,或者说相同的动作名字不同的动作内容”,技术上说就是可以把子类的引用传给父类即所有子类都可以被当成父类来处理,但方法使用时编译器会自动调用子类的相应方法(继承自同一父类的多个对象的相同的动作用一个面向父类的方法就可以处理,这样可以避免为每个不同类对象的相同动作都要添加一种对应的方法。重要的是这样可以只需针对父类接口编程而不用考虑其子类。)

n      多态是针对方法的概念,成员无多态的概念,因为方法可以覆写而造成名字一样但方法不同,而成员继承下来都是一样的,子类中新添加的方法也不存在多态的概念。

n      只有非private方法才能被覆写,从而具有多态的概念。

n      子类覆写父类方法时访问控制只能不变或变大,不能缩小。如父类protected a(),子类只能protected/public a(),不能private,否则多态的时候就会出问题。同样,显然也不能改变方法类型。

 

抽象方法与抽象类

n      只有名字没有实际操作的方法(语法中就是没有方法体)为抽象方法。抽象类可以没有抽象方法,但有抽象方法的类一定是抽象类,没有抽象成员的概念!

n      抽象类不能被实例化,即使其中不含有抽象方法。所以抽象类无需构造器,但其有默认无参数构造器,只用于其子类继承的时候调用,不能用来创建对象;将一个类声明为abstract可以阻止这个类被实例化。

n      抽象类实现接口时不需一一实现接口里声明的方法。因为抽象类本来就是让其子类实现其抽象方法的,如同接口里的抽象方法一样。

n      抽象类中的方法必须在子类中就实现,不能留到子类的子类中实现,除非子类也是抽象类。一个类不是抽象类就是具体类,表达的是实际存在,自然要把所有的方法都实现出来。

 

继承与清除

需要手动清除时(比如用dispose()),应在子类的dispose()中用super调用父类的dispose()来进行父类的清除工作,并按照与初始化相反的顺序即父类先清除。

 

新建对象初始化顺序

1.      加载父类,同时执行父类的静态初始化。

2.      加载子类,同时执行子类的静态初始化(12说明子类会包含父类的对象,可以理解父类的代码加在子类的代码之前从而形成新的子类代码)。

3.      将分配给对象的内存初始化成二进制的0即将程序中(新的子类代码中)用到的所有的成员设为默认值。

4.      执行程序中父类的非静态初始化,成员初始化与初始化块执行的先后顺序按照出现的顺序。

5.      执行父类的构造函数。

6.      执行程序中子类的非静态初始化,成员初始化与初始化块的先后顺序按照出现的顺序。

7.      执行子类的构造函数。

 

一个例子:

class A {

   public A() {

      print();

   }

 

   public void print() {

      System.out.println("A");

   }

}

 

class B extends A {

   int i = 24;

 

   public void print() {

      System.out.println("i = " + i);

   }

}

 

public class Initialization {

   public static void main(String args[]) {

      B b = new B();

      b.print();

   }

}

结果:

i = 0

i = 24

 

为什么结果不是Ai = 24?

 

b是一个包含一个A类子对象的B类对象,编译器会给b中的成员分配内存,并置将内存0;然后调用A的构造器,但A构造器中print方法作用的对象不是A而是B,为什么这么说呢?因为A类中定义的print是非静态方法,因此这个方法是作用于某一对象的,这个对象就是print前面隐藏的this(编译过程中编译器自动加上this),而这个this指代是new出来的新对象bb中虽然包含A类子对象,但是在对象内部,this不会从b外传递到b内,即this没有指代b的同时又能根据情况指代b的子对象的这种机制。)所以A的构造器中print方法的实质是this.print()或者说b.print(),所以由于多态,会调用B中覆写的print(),而这时int i = 24还未执行,所以i还是0,显示i = 0

 

根据上面的顺序12形成的新b的代码如下:(个人猜测,方便理解,不代表实际情况)

class B {

public A() {

print();

}

 

public void super.print() {

System.out.println("A");

}

//我猜测编译器应该会使用某种机制来区分父类的print()与子类覆写的print(),这里我加上super来区分,不代表实际情况。

int i = 24;

public void print() {

System.out.println("i = " + i);

}

}

 

编写构造器规则

用尽可能简单的方法使对象进入正常状态,尽量避免调用其他方法,特别是能被覆写的方法;如需调用的话,尽量调用父类的final方法和private方法(隐含final),这些方法不能被覆写,是安全的。

(By wind5shyhttp://blog.csdn.net/wind5shy)

 

第八章接口与内部类

(By wind5shyhttp://blog.csdn.net/wind5shy)

接口

n      建立类与类之间的协议。

n      为了向上转化为不止一个的基类型。

n      可以有成员,但隐含为public final static,所以接口可以用作枚举(要获得更高的安全性可以用final类来实现,private构造器,要用的枚举static final为成员),可以被非常量表达式初始化,如Random r = new Random();int n = r.nextInt(10);方法隐含public且只能是public所以其实现类的方法也必须为public

n      一个类O继承一个类A同时实现多个接口BCD时要将继承写在前,如A extends B implements BCD;接口可以继承多个接口。

 

接口与抽象类的选择

接口只是一些方法,有成员也是常量,而抽象类则可以有普通成员。如果某事物应该成为一个根父类,首选使它成为一个接口,只有在强制需要具体方法定义与成员的时候,才选择抽象类。

 

嵌套类(nested class

分为内部类(inner class)与静态嵌套类(static nested class)。(Thinking in Java 3rd里把嵌套类当成了内部类,这是一个bugSunJava language specification 3rd 里面nested class定义如下:

Nested class is any class whose declaration occurs within the body of another class or interface. Top level class is a class that is not a nested class.

Inner class定义如下:

An inner class is a nested class that is not explicitly or implicitly declared static. Inner classes may not declare static initializers or member interfaces. Inner classes may not declare static members, unless they are compile-time constant fields.

Thinking in Java 3rd里的Inner class

It is possible to place a class definition within another class definition. This is called an inner class.

以上可以看出是嵌套类包含内部类而不是内部类包含嵌套类。

 

将类封装在类中,可以声明为privateprotected(因为定义在类的内部,可以看做是成员),利用访问控制,可以作为接口实现的同时很好地隐藏实现,比如某嵌套类A实现接口I但声明为private,使用者访问时就不能向下转型为A从而不能访问A中任何新增的非接口的方法,A对使用者来说完全和接口I一样,这样也可以阻止任何依赖类型的编码。若A为普通类且实现接口I,使用时定义一个接口Iprivate对象并指向A类对象,则一旦获得这个对象后就可以用向下转型为A来访问A中新增的方法。更重要的是嵌套类可以独立地继承一个类或实现一个接口而不管其外围类是否继承了该类或实现了该接口,所以嵌套类可以用来处理多重继承的问题,特别是需要在一个类中继承多个类

 

重载:不会被重载,有自己独立的命名空间,也会生成classclass命名规则:外围类名+$+(数字。局部内部类加数字,其他不加)+嵌套类名(匿名嵌套类相当于没有嵌套类名,即为外围类名+$+数字)。

 

内部类

n      有其外围类所有成员和方法的访问权(不管嵌套多少层,每层都可以访问),当某外围类对象创建了一内部类对象时,此内部对象会保持一个指向外围对象的引用。因为这个特性所以内部类需要在外围类实例化之后才能被实例化,静态嵌套类则不需。

n      不能有static成员、方法及初始化块,不能包含静态嵌套类。因为内部类依赖于外围对象,如果在其内部定义static成员或方法,即可以通过类名来访问而无需创建外围对象,这与内部类的使用目的不符(对于无需外围对象即可访问这样的问题java定义了静态嵌套类专门来处理),所以可以说内部类就是明确定义为不能有static成员与方法从而来专门处理需要外围类依赖的问题的概念,更专一化。

n      获得外围对象引用 =外围类名.this;创建内部类对象,需用一外围类对象来创建,如:外围类A,内部类B A a = new A()A.B b = a.new B();(A.this == a

 

一个例子:

public class Test {

   public class A {

      public class B {

      }

   }

 

   public static void main(String[] args) {

      Test t = new Test();

      Test.A a = t.new A();

      Test.A.B b = a.new B();

   }

}

要创建b,必须创建b的外围类a,而要创建a又要创建a的外围类t,而且new要写在外围类引用后,这样表示通过外围类对象来创建。

 

继承内部类时构造器语法:例子class InheritInner extends WithInner.Inner{ public InheritInner

(WithInner wi){wi.super(); }}wi.super()如何理解?

由于Inner是内部类依赖于外围类WithInner,包含自身的引用还包含指向WithInner的引用,所以InheritInner继承时Inner也要需要一个WithInner引用,同时由于继承构造器还需要调用父类的构造器,这样一来就明白了wi.super()表示的不是调用WithInner的父类的构造器而是表示一个WithInner引用和InheritInner调用其父类WithInner.Inner的构造器两个概念!注意:关于supersuper前面是不能有对象引用的,因为其本身就是个引用,而java没有指向引用的引用的概念this类似,所以super只能用在一个子类里来调用父类的成员或方法,不能在子类外通过子类的引用来调用其父类,所以想通过child.super()这样的表达式来调用child父类的构造器是行不通的。)

 

匿名内部类

n      定义语法:new interfacename(){......}; new superclassname(){......}。(可以看出匿名嵌套类总是继承自某类或者实现某接口)。

n      在使用外围类方法中的局部对象或参数对象时需将其对象引用声明为final。这是因为内部类在编译时生成的class文件中保存了该对象的一个引用,如果引用不是final则引用可以在其他地方被修改,从而造成内部类中的引用出现问题。

n      没有名字所以没有构造器,new的时候是调用的父类的构造器,new的形式就决定具体调用父类的哪个构造器。不过实例初始化的效果就是构造器,显然不能重载实例初始化,相当于只有一个构造器——所以使用匿名内部类是为了创建那些继承自某一类且确定的、只需要单一实例化不需要重载和继承的对象(相当于是final的)

 

一个例子:

public class Father {

   public Father() {

      System.out.println("Default");

   }

 

   public Father(String s) {

      System.out.println(s);

   }

}

 

public class AnonymousInnerClass {

   Father get() {

      return new Father() {

          //调用Father()构造器

      };

   }

 

   Father get(String s) {

      return new Father(s) {

          //调用Father(s)构造器

      };

   }

   

   public static void main(String[] args) {

      AnonymousInnerClass aic = new AnonymousInnerClass();

      aic.get();

      aic.get("with param");

   }

}

 

结果:

Default

with param

 

局部内部类

定义在某方法内或某代码块等局部,不是外围类的一部分,所以没有访问说明符(privatepublic等),其名字在方法外不可见,但可以有构造器(因为有名字),这点在使用上是和匿名内部类的区别。

 

静态嵌套类

n      声明为static的嵌套类,表示其和外围类实例没有联系但和外围联系紧密,即表示的是类与类之间的联系:一个类(外围类)可以控制另一类(静态嵌套类)但反过来不行;相对应的,内部类表达的是实例和实例间的联系:外围实例控制内部类实例。嵌套类实例化时不需要外围类实例,而且如同静态方法一样不能从嵌套类中访问非静态的外围类实例,基本上可以将其看做顶级类。

n      可以包含静态嵌套类,可以放在接口内部(因为是静态针对类的)。

 

一个例子:

public class Test {

   static public class A {

      public static void f() {

          System.out.println("f()");

      }

   }

 

   public static void main(String[] args) {

      Test t = new Test();

      // t.A.f();             //和外围类对象没有联系,不能通过外围类对象来访问

      A a = new A();          // Test内部这两行都正确,Test外部只能用下一行,内部类也一样。

Test.A a2 = new Test.A();//注意,new是在最前面,这样也表达了静态内部

类无需外围对象的概念

   }

}

 

静态嵌套类为什么没有匿名与局部的概念?因为匿名类和局部类,都相当于局部变量,而局部变量无法加static修饰——Java中静态 ==类(全局),动态 == 局部即声明为static的对象都是全局的,采用静态储存,是针对类的,而局部变量是不能声明为static的,可以声明为finalJava中没有C/C++中静态局部变量的概念。

 

闭包

包含自己本身的信息还还包含创建它的作用域的信息。另一种说法:包含自由(未绑定)变量的代码块;这些变量不是在这个代码块或者任何全局上下文中定义的,而是在定义代码块的环境中定义。“闭包”一词来源于以下两者的结合:要执行的代码块(由于自由变量的存在,相关变量引用没有释放)和为自由变量提供绑定的计算环境(作用域),对于Java来说,前者就是嵌套类,后者则是嵌套类的外围类。

(By wind5shyhttp://blog.csdn.net/wind5shy)

原创粉丝点击