zoukankan      html  css  js  c++  java
  • ThinkInJava4读书笔记之第六章类继承

        创建一个类时肯定会进行继承,因为没有显示继承,会从Java 的标准根类Object 中继承。继承需要在类后面加extends关键字和父类名称。

        程序开发是一个不断递增或者累积的过程,就象人们学习知识一样。当然可根据要求进行尽可能多的分析,但在一个项目的设计之初,谁都不可能提前获知所有的答案。如果能将自己的项目看作一个有机的、能不断进步的生物,从而不断地发展和改进它,就有望获得更大的成功以及更直接的反馈。

      abstract class A{
      	int  i = 1;
      	public void pirntI(){
      		System.out.println("i=" + i);
      	}
      }
      
      public class B extends A{
      	int i = 2;
      //	public void pirntI(){
      //		System.out.println("i=" + i);
      //	}
      	public static void main(String[] args){
      		B b = new B();
      		b.pirntI();
      	}
      }
    

      

    final关键字

    final数据

         常数主要应用于下述两个方面:

         (1) 编译期常数,它永远不会改变

         (2) 在运行期初始化的一个值,我们不希望它发生变化

         对于编译期的常数,编译器(程序)可将常数值“封装”到需要的计算过程里。也就是说,计算可在编译期间提前执行,从而节省运行时的一些开销。在Java 中,这些形式的常数必须属于基本数据类型(Primitives),而且要用final 关键字进行表达。在对这样的一个常数进行定义的时候,必须给出一个值。

        若随同对象句柄使用final,进行声明时,必须将句柄初始化到一个具体的对象。而且永远不能将句柄变成指向另一个对象。

     class Value {
    
    int i = 1;
    
    }
    
    public class FinalData {
    
    final int i1 = 9;
    
    static final int I2 = 99;
    
    public static final int I3 = 39;
    
    final int i4 = (int)(Math.random()*20);
    
    static final int i5 = (int)(Math.random()*20);
    
    Value v1 = new Value();
    
    final Value v2 = new Value();
    
    static final Value v3 = new Value();
    
    final int[] a = { 1, 2, 3, 4, 5, 6 };
    
    public void print(String id) {
    
    System.out.println(
    
    id + ": " + "i4 = " + i4 +
    
    ", i5 = " + i5);
    
    }
    
    public static void main(String[] args) {
    
    FinalData fd1 = new FinalData();
    
    fd1.v2.i++; // Object isn't constant!
    
    fd1.v1 = new Value(); // OK -- not final
    
    for(int i = 0; i < fd1.a.length; i++)
    
    fd1.a[i]++; // Object isn't constant!
    
    fd1.print("fd1");
    
    System.out.println("Creating new FinalData");
    
    FinalData fd2 = new FinalData();
    
    fd1.print("fd1");
    
    fd2.print("fd2");
    
    }
    
    } 
    

      由于i1 和I2 都是具有final 属性的基本数据类型,并含有编译期的值,所以它们除了能作为编译期的常数使用外,在任何导入方式中也不会出现任何不同。I3 是我们体验此类常数定义时更典型的一种方式:public表示它们可在包外使用;Static 强调它们只有一个;而final 表明它是一个常数。注意对于含有固定初始化值(即编译期常数)的final static 基本数据类型,它们的名字根据规则要全部采用大写。也要注意i5 在编译期间是未知的,所以它没有大写。

        不能由于某样东西的属性是final,就认定它的值能在编译时期知道。i4 和i5 向大家证明了这一点。它们在运行期间使用随机生成的数字。例子的这一部分也向大家揭示出将final 值设为static 和非static 之间的差异。只有当值在运行期间初始化的前提下,这种差异才会揭示出来。因为编译期间的值被编译器认为是相同的。这种差异可从输出结果中看出:

    fd1: i4 = 15, i5 = 9
    
    Creating new FinalData
    
    fd1: i4 = 15, i5 = 9
    
    fd2: i4 = 10, i5 = 9
    

      注意对于fd1 和fd2 来说,i4 的值是唯一的,但i5 的值不会由于创建了另一个FinalData 对象而发生改变。那是因为它的属性是static,而且在载入时初始化,而非每创建一个对象时初始化。

    空白final

         Java 1.1 允许我们创建“空白final”,它们属于一些特殊的字段。尽管被声明成final,但却未得到一个初始值。无论在哪种情况下,空白final 都必须在实际使用前得到正确的初始化。对于final 关键字的各种应用,空白final 具有最大的灵活性。要么在定义字段时使用一个表达 式,要么在每个构建器中。

    final 自变量

         Java 1.1 允许我们将自变量设成final 属性,方法是在自变量列表中对它们进行适当的声明。这意味着在一个方法的内部,我们不能改变自变量句柄指向的东西。

    class Gizmo {
    
    int  i = 0;
    
    public Gizmo(){
    
    i = 7;
    
    }
    
    public void spin() {
    
    System.out.println("i = " + i);
    
    }
    
    }
    
     
    
    public class FinalArguments {
    
    void with(final Gizmo g) {
    
    // ! g = new Gizmo(); // Illegal -- g is final
    
    g.spin();
    
    g.i++;
    
    g.spin();
    
    }
    
     
    
    void without(Gizmo g) {
    
    g = new Gizmo(); // OK -- g not final
    
    g.spin();
    
    }
    
     
    
    int g(final int i) {
    
    return i + 1;
    
    }
    
     
    
    public static void main(String[] args) {
    
    FinalArguments bf = new FinalArguments();
    
    //bf.without(null);
    
    //bf.with(null);
    
    Gizmo gz = new Gizmo();
    
    bf.without(gz);
    
    bf.with(gz);
    
    }
    
    }
    

    final方法

        之所以要使用final 方法,可能是出于对两方面理由的考虑。第一个是为方法“上锁”,防止任何继承类改变它的本来含义。设计程序时,若希望一个方法的行为在继承期间保持不变,而且不可被覆盖或改写,就可以采取这种做法。

        采用final 方法的第二个理由是程序执行的效率。将一个方法设成final 后,编译器就可以把对那个方法的所有调用都置入“嵌入”调用里。只要编译器发现一个final 方法调用,就会(根据它自己的判断)忽略为执行方法调用机制而采取的常规代码插入方法(将自变量压入堆栈;跳至方法代码并执行它;跳回来;清除堆栈自变量;最后对返回值进行处理)。相反,它会用方法主体内实际代码的一个副本来替换方法调用。这样做可避免方法调用时的系统开销。当然,若方法体积太大,那么程序也会变得雍肿,可能受不到嵌入代码所带来的任何性能提升。因为任何提升都被花在方法内部的时间抵消了。Java 编译器能自动侦测这些情况,并颇为“明智”地决定是否嵌入一个final 方法。然而,最好还是不要完全相信编译器能正确地作出所有判断。通常,只有在方法的代码量非常少,或者想明确禁止方法被覆盖的时候,才应考虑将一个方法设为final。

         类内所有private 方法都自动成为final。由于我们不能访问一个private 方法,所以它绝对不会被其他方法覆盖(若强行这样做,编译器会给出错误提示)。可为一个private 方法添加final 指示符,但却不能为那个方法提供任何额外的含义。

    final类

         final类不能被继承,因此final类的成员方法没有机会被覆盖,默认都是final的。在设计类时候,如果这个类不需要有子类,类的实现细节不允许改变,并且确信这个类不会载被扩展,那么就设计为final类。由于类禁止了继承,所以一个final类中的所有方法都默认为final。因为此时再也无法覆盖它们。所以与我们将一个方法明确声明为final一样,编译器此时有相同的效率选择。可为final 类内的一个方法添加final 指示符,但这样做没有任何意义。

    final的注意事项

         设计一个类时,往往需要考虑是否将一个方法设为final。可能会觉得使用自己的类时执行效率非常重要,没有人想覆盖自己的方法。这种想法在某些时候是正确的。但要慎重作出自己的假定。通常,我们很难预测一个类以后会以什么样的形式再生或重复利用。常规用途的类尤其如此。若将一个方法定义成final,就可能杜绝了在其他程序员的项目中对自己的类进行继承的途径,因为我们根本没有想到它会象那样使用。

         标准Java 库是阐述这一观点的最好例子。其中特别常用的一个类是Vector。如果我们考虑代码的执行效率,就会发现只有不把任何方法设为final,才能使其发挥更大的作用。我们很容易就会想到自己应继承和覆盖如此有用的一个类,但它的设计者却否定了我们的想法。但我们至少可以用两个理由来反驳他们。首先,Stack(堆栈)是从Vector 继承来的,亦即Stack“是”一个Vector,这种说法是不确切的。其次,对于Vector 许多重要的方法,如addElement()以及elementAt()等,它们都变成了synchronized(同步的)。这会造成显著的性能开销,可能会把final 提供的性能改善抵销得一干二净。因此,程序员不得不猜测到底应该在哪里进行优化。在标准库里居然采用了如此笨拙的设计,真不敢想象会在程序员里引发什么样的情绪。

         另一个值得注意的是Hashtable(散列表),它是另一个重要的标准类。该类没有采用任何final 方法。正如我们在本书其他地方提到的那样,显然一些类的设计人员与其他设计人员有着全然不同的素质(注意比较Hashtable极短的方法名与Vector的方法名)。对类库的用户来说,这显然是不应该如此轻易就能看出的。一个产品的设计变得不一致后,会加大用户的工作量。这也从另一个侧面强调了代码设计与检查时需要很强的责任心。

  • 相关阅读:
    Java 常用正则表达式
    MySql数据库备份的几种方式
    分布式系统一致性算法(Paxos)
    桥接模式
    vnc mirror driver
    VC Mirror Driver显示虚拟驱动经典开发
    基于MirrorDriver的录屏技术
    基于GDI和D3D的抓屏技术
    Various methods for capturing the screen
    IDE Goland DEBUG报错(could not launch process: decoding dwarf section info at offset 0x0: too short)
  • 原文地址:https://www.cnblogs.com/waimai/p/2953027.html
Copyright © 2011-2022 走看看