【java基础】第四章 类的复用和抽象

#java设计思想:封装(类),继承,多态,抽象。

#类的复用:

①合成/聚合:在新的类中直接创建旧类的对象,复用的只是代码的功能而不是形式;

聚合可以看作整体和部分的关系,表示“has a”拥有,但这个部分还可以被其他整体包含。聚合频繁出现会增加设计的耦合度,应该减少聚合的使用。

合成是一种更强的“has a”拥有,成员对象和整体对象的生命周期相同,整体对象拥有队成员对象从初始化到终止的完全支配权。一个合成关系的成员对象不能被另一个对象拥有。

换句话说,合成是值的聚合,一般说的聚合是引用的聚合。合成:军队和士兵,聚合:计算机和外部设备。

若继承超过三层,考虑合成和聚合。

②继承java只有单重继承(只有一个爹),这点不足可以用接口弥补,因为Java可以允许一个类实现多个接口。

声明继承形式:

class 子类名 extends 超类名{}

子类包括超类的所有成员,除非被声明成private。

继承的初始化:①构造方法按照先超类后子类的顺序执行,

②使用super()方法传参数调用父类含参数的构造方法。且必须在子类构造方法的第一条语句使用,只有这样才能保证在实际使用类之前完成构造方法的执行。

继承实现复用的优点:超类大部分功能可以通过继承关系自动进入子类,新类的实现、修改和扩展较为容易。

缺点:一、将超类的实现细节暴露给子类,破环类的包装。二、从超类继承的复用是静态的,不能在运行时间内发生改变,没有足够的灵活性,只能在有限的环境使用。

#重写:子类修改超类已有的方法,重写后的方法与超类原方法有相同的返回值类型、方法名、参数个数和参数类型。

重写后子类对象调用的是重写后的方法,使用super.f();可以让子类访问超类方法。

注:①被子类重写的方法不能拥有比超类更加严格的访问权限。

       ②继承过程中如果超类方法抛出异常,那么子类重写该方法时,也会出现异常,且出现的异常不会多与超类中的异常。

      

#如果子类创建一个和超类变量名字相同的成员变量,称为变量重写。意义不大。

#方法重载:在一个类中,方法名相同,参数列表不同(参数个数,参数类型,参数顺序)

注:每个重载方法可以有多个不同的返回类型,返回类型不足以区分使用的是哪个方法,不能作为重载的依据。

  

  方法重写和重载的区别  
内容 重写(覆写) 重载
英文 override overload
定义 方法名,参数类型,返回类型均相同 方法名相同,参数类型、个数、或顺序不同
方法 不能拥有更严格的权限 对权限无要求
范围 发生在继承类中 发生在同一个类中

注:在面向对象程序设计的思想中,类的继承和多态性主要体现在子类重写和重载超类方法。可以通过重载构造方法来表达对象的多种初始化行为。灵活地运用重写和重载不仅能减少编码工作量,也能大大提高程序的可维护性和可拓展性。用好重写和重载可以设计一个结构清晰而简洁的类。

#abstract关键字:被该关键字修饰的成分没有完全实现,不能实例化,如果在类的方法声明中使用abstract修饰,表明该方法是抽象方法,需要在子类中实现。如果一个类包含抽象方法,则这个类也是抽象类,必须使用abstract修饰,且这个类不能实例化为对象,必须被子类继承。

注:构造方法,静态方法,final修饰方法不能使用abstract修饰; 接口隐含为abstract限定。

#final关键字:在类中定义变量时,在其前面加上final关键字,就意味着这个变量一旦被初始化便不可改变。(对基本类型是其值不可变,对引用变量来说是其引用不可变。

final类:final类不能被继承,因此类的成员方法没有机会被覆盖,默认都是final的。设计类时,如果这个类不需要子类,类的实现细节不需要改变,并且确信该类不会再被扩展,那么就设计为final类。如果一个类是完全实现的,并且不再需要子类,则他可声明为final类。(类不能被同时声明为final和abstract。)

final方法:该方法不能被子类覆盖。理由①给方法上锁,防止继承类改变其原本含义②提高程序执行效率,将一个方法设为final后,编译器就可以把对那个方法的所有调用都置入静态绑定中。

final变量(常量)

#this、super变量(使用前不需要声明):this变量出现在一个方法成员的内部,指向当前对象。当前对象是的时调用当前正在执行方法的那个对象。super变量直接指向超类的引用,用来引用超类中的变量和方法。

this作用:①使类的成员变量和方法的参数或方法自己定义的局部变量同名(this.name=name;)②用在构造方法第一个语句时,这个构造方法会调用该类另一个构造方法。形式为:this(其他构造方法参数表)。

super作用:使用超类中的成员变量和方法。

#向上转型:Animal a=new Cat(); 即子类转型为超类,超类类型引用指向子类对象。既可以利用子类的功能,又可以抽取超类的共性。

注:超类类型的引用可以调用超类定义的所有变量和方法,对子类中定义超类中没有的方法不能调用。若子类重写了方法,则超类引用调用的是重写过的方法(动态连接)。

动态连接只针对方法,对成员变量调用的仍是超类中的变量。

#多态性:发送某个消息给某个对象,让该对象自行决定响应何种行为。

java多态实现机制:①通过接口以及实现接口并覆盖接口中同一方法的几个不同的类来实现。

②由超类和继承超类并重写超类中同一方法的几个不同子类实现。

注:每一个实例对象都自带一个虚拟方法表,这个表存储的是指向虚方法的指针,实例对象通过这个表调用虚方法,实现多态。

#向上转型是自动的向下转型要强制转换。

#java多态性实现方法:

继承实现:通过重写,重载。

抽像类实现

接口实现。

#接口;一系列常量定义和抽象方法声明的集合。java接口不能有构造方法,成员需变量定义为public,static和final。

一、接口定义:[public] interface 接口名 [extends 父接口名]{

                                       [public] [static] [final] 常量;

                                       [public][abstrat] 方法;}

二、接口无public修饰是为包访问权限。

一个接口可以继承多个父接口,用逗号隔开。

类使用implement关键字实现某个接口。

若类未定义接口中所有方法,则为抽象类。

一个类可以实现多个接口。

接口影响该类的子类,不影响该类父类。

不允许创建接口的实例,但允许定义接口类型的引用的变量,该引用变量引用实现了这个接口的类的实例。

public class B implements A{}

A a=new B();

#结合java接口和抽象类各自优势的设计模式(缺省适配模式):声明类型的工作仍由java接口承担,但同时给出一个抽象类,且实现了该接口,而其他同属于这个抽象类型的具体类可以选择实现这个Java接口,也可以选择继承这个抽象类。也就是说再继承层次结构中,java接口在最上层,然后紧跟着抽象类。

#引用接口中的常量: 接口名.常量名;

#内部类:在一个类的内部定义另一个类,这种类称为嵌套类,它由两种类型,即静态嵌套类和非静态嵌套类。静态嵌套类使用很少,通常直接称为嵌套类。最重要的是非静态嵌套类,称为内部类。内部类可以不是public,也不必强制和所处文件有相同名字,而外部类的类名必须和所处源程序文件同名,而且必须是public或default。因此内部类可以被修饰为public,default,protect,和private,也可以像方法一样是静态static的,这就成了静态嵌套类。

内部类在一个类中所处位置没有限制,可以定义在方法体或语句块中,甚至可以是一个表达式的一部分。对一个名为outer的外部类,和其内部定义的名为inner的内部类,一旦编译成功,就会生成完全不同的两类,outer.class和outer$inner.class。所以内部类的成员变量和成员方法名可以和外部类的成员变量和成员方法名相同,不存在名字冲突。

#内部类好处:①隐藏类设计者不想让用户知道的内部操作,即封装性。

②内部类对象可以访问创建它的外部类对象的内容,甚至包括私有变量

注:如果内部类里的一个成员变量与外部类的一个成员变量同名,那么外部类的同名成员变量被屏蔽,可以用outerClass.this来表示外部类对象的引用。

#得到内部类对象方法:①利用外部类的方法创建并返回内部类对象。

②new关键字创建对象

outerObject=new OuterClass(参数);

outerObject.InnerClass innerObject=outerObject.new InnerClass (参数);

注:在创建非静态内部类对象时,一定要先创建相应的外部类对象。因为非静态内部类对象有着指向其外部类对象的引用。

#其他类型的内部类

除了普通的非静态内部类外,还包括静态嵌套类,局部内部类,和匿名内部类。

一、静态嵌套类:静态的内部类没有了指向外部的引用。如果不需要内部类对象与其外部类对象有联系,可以将内部类修饰为static。

任何非静态内部类中都不能有静态数据,静态方法和其他静态内部类。静态嵌套类可以用public,protect,private修饰,可以定义静态或非静态的成员,只能访问外部类中的静态成员。

外部类访问内部类的静态成员格式为:内部类.静态成员。生成一个静态嵌套类不需要外部类成员,这是静态嵌套类和普通内部类的区别。静态嵌套类的对象可以用如下形式直接生成:Outer.Inner in=new Outer.Inner(); 不需要生成外部类对象。

注:可将嵌套类置于接口中。(接口中的任何内部类自动变为public和static)

二、局部内部类:可以定义在一个方法甚至一个代码块内。与局部变量类似,不能有访问控制符,因为不是外部类一部分。但可以访问当前语句块的常量和外部类所有成员。

package ex4_17;
public class Outer {
    private int s=100;
    private out_i=1;
    public void f(final int k){
        final int s=200;
        int i=1;
        final int j=10;
        class Inner{
            int s=300;
            Inner(int k){
                inner_f(k);
        }
            int inner_i=100;
            void inner_f(int k){
                System.out.println(out_i);   //1
                System.out.println(j);        //10
                System.out.println(s);        //300
                System.out.println(this.s);    //300
                System.out.println(Outer.this.s);     //100
            }
}
        new Inner(k);
}
    public static void main(String[] args){
        Outer out=new Outer();
        out.f(3);
}
}

注:访问局部内部类必须先有外部类对象。

局部内部类不能定义静态变量。

局部内部类可以访问同一局部变量,但变量必须是final的。

如果内部类有与外部类同名的变量,直接用变量名访问的是内部类的变量,this.变量名访问的也是内部类变量,外部类名.this.变量名访问的是外部类变量。

三、匿名内部类:只需要创建一个类的对象而不需要用到它的名字时,使用匿名内部类使代码更简洁

匿名内部类定义形式(生成接口和超类的子类实例对象,重写其中抽象方法):new 接口名(){}或new超类名(){};

注:匿名内部类没有名字,而构造方法必须和类名同名,所以没有构造方法。

初始化匿名内部类的成员变量:

①直接调用超类的构造方法实现初始化,并在匿名内部类实现的过程中使用super关键字调用相应的内容。如果该匿名内部类继承了一个只含有带参数构造方法的超类,创建它的时候必须带上这些参数。

public class Main{
    public static void main(String [] args){
        InnerTest inner=new InnerTest();
        Test t=inner.get(3);
        System.out.println(t.getI());  //30
}
}
class Test { //超类
    private int i;
    public Test(int i){
        this.i=i;
        }
    public int getI(){
        return i;
}
}
class InnerTest{
    public Test get(int x){
        return new Test(x){
            public int getI() {
                return super.getI()*10;
            }
};
}
}

②final初始化匿名内部类。如果是在一个方法的匿名内部类中,可以利用这个方法传进你想要的参数,这些参数必须被声明为final。

interface Destination{
    String readLabel();
}
    class Outer{
        public Destination dest(final String dest){     //方法参数为final
            return new Destination(){
                        private String label=dest;
                        publci String readLabel(){return label;}
                        };
                }
            public static void main(String [] args){
                               Outer p=new Outer();
                               Destination d=p.dest("Tanzania");
}
}

三、在匿名内部类中使用初始化代码块。

#内部类存在的作用:一般的非内部类,是不允许有private或protected权限的,但内部类可以。内部类的作用是更小层次的封装,把一个类隐藏在另一个类的内部,只让它的外部类看得到他,能更方便地在内部类中访问外部类的私有成员。当设计一个实现某个接口的类时,若类中的一个方法与接口中方法声明相同,可以考虑用内部类实现接口,这样一来1,内部类实现了对主类方法的封装,不影响接口的扩展,避免修改接口而实现同一个类中两种同名方法的调用。内部类和接口还可以更好地实现多重继承。

#内部类生成的class文件

内部类和外部类在编译时,将各自生成class文件。外部类的class文件的名称与普通类的生成规则相同。而内部类的class文件名:OutClassName$InnerClassName.class.匿名内部类的类名:OutClassnName$#.class.(#为从1开始每生成一个内部类数字递增一。)

#内部类重载不起作用

如果你创建了一个内部类,然后继承其外围类并重新定义此内部类时,会发生什么呢?也就是说,内部类可以被重载吗?这看起来似乎是个很有用的点子,但是“重载”内部类就好像它是外部类的一个方法,其实并不起什么作用:

class Egg {
private Yolk y;

protected class Yolk {
       public Yolk() {
              System.out.println("Egg.Yolk()");
       }
}

public Egg() {
       System.out.println("New Egg()");
       y = new Yolk();
}
}

public class BigEgg extends Egg {
public class Yolk {
       public Yolk() {
              System.out.println("BigEgg.Yolk()");
       }
}

public static void main(String[] args) {
       new BigEgg();
}
}

输出结果为:
New Egg()
Egg.Yolk()

缺省的构造器是编译器自动生成的,这里是调用基类的缺省构造器。你可能认为既然创建了BigEgg的对象,那么所使用的应该是被“重载”过的Yolk,但你可以从输出中看到实际情况并不是这样的。
这个例子说明,当你继承了某个外部类的时候,内部类并没有发生什么特别神奇的变化。这两个内部类是完全独立的两个实体,各自在自己的命名空间内。当然,明确地继承某个内部类也是可以的:

/**
 *程序输出结果:
 *Egg2.Yolk()
 *New Egg2()
 *Egg2.Yolk()
 *BigEgg2.Yolk()
 *BigEgg2.Yolk.f()
 */
class Egg2 {

protected class Yolk {
       public Yolk() {
              System.out.println("Egg2.Yolk()");
       }

       public void f() {
              System.out.println("Egg2.Yolk.f()");
       }
}

//Egg2.Yolk() 成员变量先于构造方法执行
private Yolk y = new Yolk();

//New Egg2() 父类构造方法先于子类构造方法执行
public Egg2() {
    System.out.println("New Egg2()");
}

public void insertYolk(Yolk yy) {
       y = yy;
}

public void g() {
       y.f();
}
}

public class BigEgg2 extends Egg2 {

public class Yolk extends Egg2.Yolk {
        
       
       public Yolk() {
              System.out.println("BigEgg2.Yolk()");
       }
       
      //BigEgg2.Yolk.f() 子类重载父类的构造方法
       public void f() {
              System.out.println("BigEgg2.Yolk.f()");
       }
}

//Egg2.Yolk() 父类构造方法先于子类构造方法执行
//BigEgg2.Yolk() 自己的构造方法后执行
public BigEgg2() {
    insertYolk(new Yolk());
}

public static void main(String[] args) {
       Egg2 e2 = new BigEgg2();
       e2.g();
}
}

现在BigEgg2.Yolk 通过extends Egg2.Yolk 明确地继承了此内部类,并且重载了其中的方法。Egg2 的insertYolk()方法使得BigEgg2 将它自己的Yolk 对象向上转型,然后传递给引用y。所以当g()调用y.f()时,重载后的新版的f()被执行。第二次调用Egg2.Yolk()是BigEgg2.Yolk 的构造器调用了其基类的构造器。可以看到在调用g()的时候,新版的f()被调用了。

#继承内部类的类

因为内部类构造方法必须同外部类对象的一个引用联系到一起,所以从一个内部类继续时,内部类的构造方法要用到其外部类对象的引用。问题在于,那个“隐藏的“外部类对象的引用必须被初始化。要解决这个问题,需使用专门的语法来表明它们之间的关系。

package ex4_21
class Outer{
    private String name;
    public Outer(){
        System.out.println("It is in Outer");
        name="Outer";
        System.out.print("My name is ");
        System.out.println(name);
}
    public class Inner{
        private String name;
        public Inner(String name){
            System.out.println("It is in InnerConstructor ");
            this.name=name;
            System.out.print("My name is ");
            System.out.println(name);
            System.out.print("My OuterClass's name is");
            System.out.pringln(Outer.this.name);
}
}
   public void display(){
                System.out.println(name);
}
}
class ExtendsInner extends Outer.Inner{
            public ExtendsInner(Outer ot,String s){
            ot.super(s);
            System.out.println("It is in ExtendsInner");
}
            publci static void main(String[] args){
                Outer ot=new Outer();
                ExtendsInner ei=new ExtendsInner(ot,"Inner");
}
}
//输出结果:It is in Outer
          My name is Outer
          It is in InnerConstructor
          My name is Inner
          My OuterClass's name is Outer
          It is in ExtendsInner

程序说明:ExtendsInner只继承自内部类,不是外部类。当要生成一个构造方法时,缺省的ExtendsInner()构造方法只会产生错误,必须要带有外部类引用参数的构造方法;同时,不能只传递一个指向外部类对象的引用,必须在构造方法内使用:外部类对象引用.super();这样才提供了必要的引用,程序才能编译通过。

猜你喜欢

转载自blog.csdn.net/liugf2115/article/details/85260077