• Thinking in Java第七章学习笔记----复用类


    复用代码,即使用已经开发并调试好的类。组合和继承是两种实现方法。

    组合语法:

      在新类中创建现有类的对象。该方法只是复用了现有代码的功能,而非它的形式。

      组合的例子随处可见,这里不举例说明。但书中特意强调了toString方法。

      每一个非基本类型的对象都有一个toString方法,因为每一个类都是继承Object类而来的,而Object类中包含这个方法。具体需要注意的是,当你要将一个对象和字符串连接的时候,编译器会自动调用toString方法,当Object类中的toString方法不能满足要求时,则需要重写这个方法。

    继承语法:

      继承是所有OOP语言不可缺少的组成部分。当创建一个类时,总是在继承,因为若没有明确指出要从其他类中继承,就默认从Java的标准根类Object进行继承。

      为了继承,一般的规则是将所有的数据成员指定为private,所有的方法指定为public。虽然在特殊的情况下必须做出调整,但是这的确是一个很有用的规则。

      当继承类中有对基类中定义的方法修改时,欲调用基类的方法,必须加上super关键字,否则程序将产生递归。当然,继承类同样可以定义属于自己的方法。

     初始化基类:

      当创建了一个导出类的对象时,该对象包含了一个基类的子对象。这个子对象与直接创建的基类对象时一样的。二者的却别在于后者来自于外部,而前者来自于导出类对象的内部。

      1)无参构造器的初始化

    class Art {
    	Art() {
    		System.out.println("Art");
    	}
    }
    class Drawing extends Art () {
    	Drawing() {
    		System.out.println("Drawing");
    	}
    }
    public class Cartoon extends Drawing {
    	public Cartoon() {
    		System.out.println("Cartoon");
    	}
    }
    /*
    Output:
    	Art
    	Drawing
    	Cartoon
    */
    

       2)有参构造器的初始化

        对于无参构造器,当然默认的构造器也是无参的,编译器可以轻松地调用而不需要考虑传递参数的问题。但是想调用带参数的基类构造器,就必须用super显示地编写调用基类构造器的语句,而且调用基类构造器必须是你在导出类构造器中要做的第一件事,否则编译器将报错。

    class Game {
        Game(int i) {
            System.out.println("Game Constructor");
        }
    }
    class BoardGame extends Game {
        BoardGame(int i) {
            super(i);
            System.out.println("BoardGame Constructor");
        }
    }
    public class Chess extends BoardGame {
        public Chess() {
            super(11);
            System.out.println("Chess Constructor");
    //        super(11); // 报错
        }
        public static void main(String[] args) {
            new Chess();
        }
    }
    /*
    output:
    	Game Constructor
    	BoardGame Constructor
    	Chess Constructor
    */

    代理:

      代理是继承与组合之间的中庸之道,但是Java并没有提供对它的直接支持。代理可控制需要哪些被代理类中的方法,而组合和继承则拥有了所有方法。

      代理的具体过程,先创建被代理类的对象引用,然后创建与被代理类中方法同名的方法,并通过这个对象引用来调用被代理类的方法,这里有点像重写(注意重写是建立在继承的基础上的)。

    class SpaceShipControls {
    	void up(int velocity) {}
    	void down(int velocity) {}
    } 
    public class SpaceShipDelegation {
    	private String name;
    	private SpaceShipControls controls = new SpaceShipControls();//创建被代理类的对象
    	public SpaceShipDelegation(String name){
    		this.name = name;
    	}
    	public void up(int velocity){//选择需要代理的方法,注意名字需一样
    		controls.up(velocity);//通过对象引用,调用被代理类的方法。实现代理
    	}
    	public static void main (String[] args) {
    		SpaceShipDelegation protector = new SpaceShipDelegation("lalala");
    		protector.up(100);
    	}
    }

    确保正确清理:

      Java中没有析构函数的概念。虽然Java中有垃圾回收机制,但是你永远不知道它什么时候才会被调用。因此,如果想要某个类清理一些东西,就必须显示地编写一个特殊方法来实现。清理的首要任务是,将这一清理动作置于finally子句之中,以防异常的出现。finally子句表示无论发生什么事,一定要执行这个动作。清理动作的顺序和生成顺序相反,因为可能存在子对象依赖于另一个子对象的情况。

    名称屏蔽:

      如果导出类中有对基类的方法进行重载,不会对名称进行屏蔽,即所有重载方法都是可用的。@override注解重写基类方法,避免方法名称写错。

    向上转型:

      导出类转型为基类,在继承图上是向上的,因此得名。由于向上转型是从一个较专用类型向较通用类型转换,所以总是安全的,而且在向上转型的过程中,类接口中唯一发生的事情是丢失方法,而不是获取他们,所以在安全的考虑上是可以接受的,编译器也是允许的。

    组合与继承之间选择:

      尽管面向对象的过程中,一直强调继承的概念,但并不是尽可能的使用它。相反,应当慎用这一技术。一个清晰的方法是问问自己到底是否需要从新类向基类向上转型。如果需要,那就用继承吧,如果不需要,那应当好好考虑下了。所以向上转型是判断组合和继承选择的重要依据。

    final数据:

      对于基本类型,final使数值恒定不变,而用于对象引用,final引用恒定不变。一旦引用被初始化指向一个对象,就无法再把它指向另一个对象。但是对象本身是可以修改的。既是static又是final的域将用大写表示,并使用下划线分隔各个单词。

      有一点需要注意的是,空白final是指指定了final但又没有赋初值的域。无论什么情况,编译器都要确保空白final在被使用前都必须要被初始化。一般情况下,空白final是在其他类的构造器中初始化,即某个类的final域可以根据创建不同的对象具有不同的初始值,而且还能保持其恒定不变的特性。空白final大大提高了灵活性。

    class Poppet {
    	private int i;
    	Poppet(int ii) {
    		i = ii;
    	}
    }
    
    public class BlankFinal {
    	private final int i = 0;
    	private final int j;
    	private final Poppet p;
    	public BlankFinal() {
    		j = 1;
    		p = new Poppet(1);
    	}
    	public BlankFinal(int x) {
    		j = x;
    		p = new Poppet(x);
    	}
    	public static void main(String[] args) {
    		/*通过调用不同的构造器,创建不同的对象,使空白final域P有不同的初始值*/
    		new BlankFinal();
    		new BlankFinal(1);
    	}
    }

    final参数:

      Java允许在参数列表中以声明的方式将参数指明为final,这意味着你不能在方法中更改参数引用所指向的对象。  

    class Gizmo {
    	public void spin() {
    	}
    }
    
    public class FinalArguments {
    	void with(final Gizmo g) {
    		//g = new Gizmo();  不能更改
    	}
    	void without(Gizmo g) {
    		g = new Gizmo();
    		g.spin();
    	}
    	//void f(final int i){i++} 不能更改i的值
    	int f(final int i){ return i + 1; } //这里并没有更改i的值
    	public static void main (String[] args){
    		FinalArguments bf = new FinalArguments();
    		bf.with(null);
    		bf.without(null);
    	}
    } 

    final方法:

      使用final方法的原因有两个,第一个原因是效率,这是使用初衷。但是这仅仅是在代码块不是很大的情况下才能显示出其作用,后来Java找到了其他的方式进行提高效率,所以在final方法的使用上不再考虑效率问题。第二个原因是为了防止在继承中对方法进行覆盖。(注意:final方法是可以被重载的!重载和覆盖不一样!)此外,定义为private访问权限的方法,其隐式指定为final。

      特别注意,覆盖只有在某方法是基类接口中的一部分时才会出现,基类中的private方法不是接口的一部分,所以如果在基类的导出类中定义一个和基类中private方法同名的public或者protect方法,不是覆盖,而是定义了一个新的方法,切记!

    final类:

      当定义某个类为final时,就表示你不想继承这个类,而且也决不允许别人继承这个类,或者说这个类完全没有被继承的必要,又或者出于安全的考虑。总之,它被限制了。由于final类无法被继承,所以类下的所有方法都是final的,无论是否指定为final。

    对于使用final的忠告:

      将一个方法或者一个类指定为final,大部分可能是明智的。但是你必须注意到,这些所谓的不能重载、不能被继承,都是你自己的想象,总有你意想不到的运用它的情况。所以,使用final请慎重!

    初始化及类的加载:

      类只有在创建类的第一个对象或者访问static域和static方法时才会发生加载。其实创建类的对象也是在访问static方法,因为创建时调用的构造器是隐式的static。所以,类的加载之处也是static初始化之处,而所有的static只会被初始化一次,按照定义的顺序初始化。

    class Insect {  
      private int i = 9;  
      protected int j;  
      Insect() {  
        print("i = " + i + ", j = " + j);  
        j = 39;  
      }  
      private static int x1 =  
        printInit("static Insect.x1 initialized");  
      static int printInit(String s) {  
        print(s);  
        return 47;  
      }  
    }  
      
    public class Beetle extends Insect {  
      private int k = printInit("Beetle.k initialized");  
      public Beetle() {  
        print("k = " + k);  
        print("j = " + j);  
      }  
      private static int x2 =  
        printInit("static Beetle.x2 initialized");  
      public static void main(String[] args) {  
        print("Beetle constructor");  
        Beetle b = new Beetle();  
      }  
    }  
    /** 
      * static Insect.x1 initialized 
      * static Beetle.x2 initialized 
      * Beetle constructor 
      * i = 9, j = 0 
      * Beetle.k initialized 
      * k = 47 
      * j = 39 
    */  

    总结:

      在开始一个设计时,一般优先选择使用组合,或者可能是代理,只有确实必要时才使用继承,因为组合更具灵活性。

      在设计一个系统时,目标应该是创建某些类,其中每个类都有具体的用途,而且既不会太大,也不会太小。太大则复杂难以复用,太小则可能不添加其他功能就无法使用。所以,太大的情况下,就适当的细分。在系统的设计阶段,必须意识到这是一种增量过程,是不断累积的过程,并不是一蹴而就的。

     

  • 相关阅读:
    python使用matplotlib:subplot绘制多个子图 不规则画图
    Python_matplotlib画图时图例说明(legend)放到图像外侧
    python ndarray与pandas series相互转换,ndarray与dataframe相互转换
    用Python实现excel 14个常用操作
    Python中保留两位小数的几种方法
    画图显示中文
    python命名规范
    Alpha阶段小结
    敏捷冲刺每日报告——Day5
    敏捷冲刺每日报告——Day4
  • 原文地址:https://www.cnblogs.com/promiseslc/p/8727301.html
Copyright © 2020-2023  润新知