疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

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方法的使用上不再考虑效率问题。第二个原因是为了防止在继承中对方法进行覆盖。此外,定义为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
*/
总结:
 
  在开始一个设计时,一般优先选择使用组合,或者可能是代理,只有确实必要时才使用继承,因为组合更具灵活性。
 
  在设计一个系统时,目标应该是创建某些类,其中每个类都有具体的用途,而且既不会太大,也不会太小。太大则复杂难以复用,太小则可能不添加其他功能就无法使用。所以,太大的情况下,就适当的细分。在系统的设计阶段,必须意识到这是一种增量过程,是不断累积的过程,并不是一蹴而就的。