黑马程序员-- 多态、接口、内部类 大总结

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流!---------

上一篇博客总结了复用类的基本知识,这一篇就把剩余关于类和接口的知识梳理完。

在面向对象的程序语言设计中,多态是继数据抽象继承之后的第三种基本特征。多态通过分离做什么和怎么做,从另一个角度将接口和实现分离。多态不仅能够改善代码的组织结构和可读性,还能够创建可扩展程序。我们知道对象既可以作为它自己本身的类型使用,也可以作为它的基类型使用。也就是所基类更加通用,甚至可以忘记子对象的类型。

下面首先看一个简单的例子:

package java_class_thinkink_in_java;

enum Note {
	MIDDLE_C, C_SHARP, B_FLAT; // Etc.
} 

class Instrument {
	public void play(Note n) {
		System.out.println("Instrument.play()");
	}
}

class Wind extends Instrument {
	// Redefine interface method:
	public void play(Note n) {
		System.out.println("Wind.play() " + n);
	}
}

public class Music {
	public static void tune(Instrument i) {
		// ...
		i.play(Note.MIDDLE_C);
	}
	public static void main(String[] args) {
		Instrument inst=new Instrument();
		tune(inst);
		System.out.println("inst引用对象是:"+inst);
		Wind wind = new Wind();
		tune(wind); 
		System.out.println("wind引用对象是:"+wind);
		inst=wind;
		System.out.println("inst引用对象是:"+inst);
		tune(inst);
	}

}
输出的结果是:

Instrument.play()
inst引用对象是:java_class_thinkink_in_java.Instrument@525483cd
Wind.play() MIDDLE_C
wind引用对象是:java_class_thinkink_in_java.Wind@2a9931f5
inst引用对象是:java_class_thinkink_in_java.Wind@2a9931f5
Wind.play() MIDDLE_C

每一个引用变量都携带者对象的类型信息和地址。Wind@2a9931f5   类名@地址其实我很好奇向上转型时JVM做了些什么?所以我把主函数改成了:

public class Music {
	public static void main(String[] args) {
		Instrument inst=new Wind();
		inst.play(Note.MIDDLE_C);
	}
}

用jad反汇编了这段代码得到的结果是:

public class Music
{

    public Music()
    {
    //    0    0:aload_0        //执行 Music的默认构造函数
    //    1    1:invokespecial   #8   
    //    2    4:return          
    }

    public static void main(String args[])
    {
        Instrument inst = new Wind();
    //    0    0:new             #16   //创建一个对象,并且其引用进栈
    //    1    3:dup             //复制栈顶数值,并且复制值进栈
    //    2    4:invokespecial   #18    //调用超类构造方法、实例初始化方法、私有方法
    //    3    7:astore_1        
        inst.play(Note.MIDDLE_C);
    //    4    8:aload_1         //加载对象
    //    5    9:getstatic       #19    //获取指定类的静态域,并将其值压入栈顶
    //    6   12:invokevirtual   #25    //调用实例方法
    //    7   15:return          
    }
}

根据便以结果,执行的是 Method void Instrument.play(Note)的方法,可能因为被覆盖了,所以才执行Wind的方法。这时就根据inst 存储引用对象的信息了。这也就是“后期绑定,动态绑定,运行时绑定”,在对象中安置某种类型信息。多态时期更具有通用性或者说兼容性,我们所做的代码修改,不会对程序中其他的不应受到影响的部分产生破坏,换句话说,多态让一个程序员“将改变的事物与未变的事物分离开来”的重要技术。

接口和内部类为我们提供了一种将接口与现实分离的更加结构化的方法。首先我们学习抽象类,它是普通类与接口之间的一种中庸之道。

包含抽象方法(abstract void f())的类叫做抽象类。如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的。如果从一个抽象类继承,并想创建新类的对象,那么就必须为积累中的所有抽象方法提供方法定义。如果不这样做,那么导出类也是抽象类。且必须加上abstract

interface 关键字使抽象的概念更向前迈进了一步。abstract 关键字允许人们在类中创建一个或多个没有任何定义的方法--提供接口的部分,但是没有提供任何相应的具体实现。interface 关键字产生的一个完全抽象的类,他根本没有提供任何具体的实现。========接口被用来建立类与类之间的协议。我的理解就是接口就是规范或标准,是类与类之间沟通的基础。

接口的完全解耦性:

       只要一个方法操作的是类而非接口,那么你就只能使用这个类及其子类。如果你想要将这个方法用于不在此继承结构的某各类,那么你就会触霉头。接口可以很大程度上放宽这种限制,因此,它使得我们可以编写可复用性更好的代码。   

package 接口;

import java.util.*;

class Processor {
  public String name() {
    return getClass().getSimpleName();
  }
  Object process(Object input) { return input; }
}	

class Upcase extends Processor {
  String process(Object input) { 
    return ((String)input).toUpperCase();
  }
}

class Downcase extends Processor {
  String process(Object input) {
    return ((String)input).toLowerCase();
  }
}

class Splitter extends Processor {
  String process(Object input) {
    // The split() argument divides a String into pieces:
    return Arrays.toString(((String)input).split(" "));
  }
}	

public class Apply {
  public static void process(Processor p, Object s) {
    System.out.println("Using Processor " + p.name());
    System.out.println(p.process(s));
  }
  public static String s =
    "Disagreement with beliefs is by definition incorrect";
  public static void main(String[] args) {
    process(new Upcase(), s);
    process(new Downcase(), s);
    process(new Splitter(), s);
  }
}
关系图如下:      

黑马程序员-- 多态、接口、内部类 大总结_第1张图片 

       Apply.process()方法可以接受任何类型的Processor,并将其应用到一个Object对象上,然后打印结果。               

输出的结果如下:

Using Processor Upcase
DISAGREEMENT WITH BELIEFS IS BY DEFINITION INCORRECT
Using Processor Downcase
disagreement with beliefs is by definition incorrect
Using Processor Splitter
[Disagreement, with, beliefs, is, by, definition, incorrect]

但是假设我们发现一组电子滤波,他们看起来好像适用于:Apply.process()方法

public class Waveform {
  private static long counter;
  private final long id = counter++;
  public String toString() { return "Waveform " + id; }
} 
public class Filter {
  public String name() {
    return getClass().getSimpleName();
  }
  public Waveform process(Waveform input) { return input; }
}
public class LowPass extends Filter {
  double cutoff;
  public LowPass(double cutoff) { this.cutoff = cutoff; }
  public Waveform process(Waveform input) {
    return input; // Dummy processing
  }
}
public class HighPass extends Filter {
  double cutoff;
  public HighPass(double cutoff) { this.cutoff = cutoff; }
  public Waveform process(Waveform input) { return input; }
} 
public class BandPass extends Filter {
  double lowCutoff, highCutoff;
  public BandPass(double lowCut, double highCut) {
    lowCutoff = lowCut;
    highCutoff = highCut;
  }
  public Waveform process(Waveform input) { return input; }
}

关系图如下:

黑马程序员-- 多态、接口、内部类 大总结_第2张图片


虽然它们具有相同的借口元素,但不能将Filter用于Apply.process()方法,即便这样做可以正常运行,这里主要是因为Apply.process()方法和Process之间耦合过紧,已经超出需要的程度,这就使得复用Apply.process()方法时,复用被禁止了。


但是如果Processor是一个接口,那么这些限制就会变得松动,使得你可以复用结构该接口的Apply.process()方法。下面将给出其修改版本:

public interface Processor {
  String name();
  Object process(Object input);
}
public class Apply {
  public static void process(Processor p, Object s) {
    print("Using Processor " + p.name());
    print(p.process(s));
  }
}

复用代码的第一种方式就是 客户端程序员遵守该接口来编写他们自己的类,就像下面:

public abstract class StringProcessor implements Processor{
  public String name() {
    return getClass().getSimpleName();
  }
  public abstract String process(Object input);
  public static String s =
    "If she weighs the same as a duck, she's made of wood";
  public static void main(String[] args) {
    Apply.process(new Upcase(), s);
    Apply.process(new Downcase(), s);
    Apply.process(new Splitter(), s);
  }
}	

class Upcase extends StringProcessor {
  public String process(Object input) { // Covariant return
    return ((String)input).toUpperCase();
  }
}

class Downcase extends StringProcessor {
  public String process(Object input) {
    return ((String)input).toLowerCase();
  }
}

class Splitter extends StringProcessor {
  public String process(Object input) {
    return Arrays.toString(((String)input).split(" "));
  }	
}

关系图如下:


黑马程序员-- 多态、接口、内部类 大总结_第3张图片



但是我们经常碰到的情况是你无法修改你想要使用的类。例如在电子滤波器中,类库是不能被修改的,在这种情况下,你就可以使用 适配器模式,适配器中的代码将接受你所拥有的接口,并产生你需要的接口,就像下面:

class FilterAdapter implements Processor {
  Filter filter;
  public FilterAdapter(Filter filter) {
    this.filter = filter;
  }
  public String name() { return filter.name(); }
  public Waveform process(Object input) {
    return filter.process((Waveform)input);
  }
}	

public class FilterProcessor {
  public static void main(String[] args) {
    Waveform w = new Waveform();
    Apply.process(new FilterAdapter(new LowPass(1.0)), w);
    Apply.process(new FilterAdapter(new HighPass(2.0)), w);
    Apply.process(
      new FilterAdapter(new BandPass(3.0, 4.0)), w);
  }
}

关系图如下:

黑马程序员-- 多态、接口、内部类 大总结_第4张图片


适配器可以看成对于接口的方法有选择性的 实现与abstract,公用的就实现,特殊化的就放到具体的子类中去特定的实现,有点像类中的静态变量与普通变量。

在这种适配器中的方式中,FilterAdapter的构造器接受你多拥有的接口Filter,然后生成具有你需要的Processor接口的对象,也用到了代理。将接口从具体的实现中解耦使得接口可以应用于多种不同的实现,因此代码也就更具有可复用性。

前面也讲到了接口和内部类为我们提供了一种将接口与现实分离的更加结构化的方法。下面将总结一下内部类,首先回答的问题是为什么需要内部类? 一般来说,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象,所以可以认为内部类提供了某种进入其外围类的窗口。内部类必须回答一个问题是:如果只是需要一个接口的引用,为什么不通过外围类实现那个接口呢?答案是:如果这能满足需求,那么就应该这样做。那么内部类的实现一个接口与外围类实现这个接口有什么区别呢?答案是:后者不是总能享用到就扣带来的方便。有时需要用到接口的实现,所以内部类最吸引人的地方是:

每个内部类都能独立地继承自一个接口的实现,所以无论外围类是否已经继承了某个接口的实现,对于内部类都没有影响。

如果没有内部类提供的、可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。从这个角度看,内部类使得多重继承更加完整。接口解决了部分问题,为内部类有效的实现多重继承,也就是说,内部类继承多个非接口类型(类或抽象类)

interface A {}
interface B {}

class X implements A, B {}

class Y implements A {
  B makeB() {
    // 内部类
    return new B() {};
  }
}

public class MultiInterfaces {
  static void takesA(A a) {}
  static void takesB(B b) {}
  public static void main(String[] args) {
    X x = new X();
    Y y = new Y();
    takesA(x);
    takesA(y);
    takesB(x);
    takesB(y.makeB());
  }
如果拥有的是抽象类或具体的类,而不是接口,那就只能使用内部类才能实现多重继承。

class D {}
abstract class E {}

class Z extends D {
  E makeE() { return new E() {}; }
}

public class MultiImplementation {
  static void takesD(D d) {}
  static void takesE(E e) {}
  public static void main(String[] args) {
    Z z = new Z();
    takesD(z);
    takesE(z.makeE());
  }
}


你可能感兴趣的:(培训基础课)