java基本功04--复用类

4. 复用类

当你想要升级现有的功能但是不想重新编写代码的话,你可以选择复用当前的代码。复用类有两种方式:

  • 组合:在新的类种产生现有的类的实例对象,需要时再通过这个对象调用原有的方法;
  • 继承:按照现有类的类型来创建新类,采用现有类的形式并在其中添加新代码。

4.1 组合

在新的类的类成员中防止现有类的类对象,在需要时调用它的方法。

public class Composition {
    public static void main(String[] args) {
        Old.show();
        New.show();
    }
}
class Old{
    public static void show(){
        System.out.println("Hello");
    }
}
class New{
    private static Old old = new Old();
    public static void show(){
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date = df.format(System.currentTimeMillis());
        old.show();
        System.out.println(date);
    }
}

正如前面早初始化部分提到的,你必须在使用原有的对象之前进行初始化,这里选择以单例模式的方式进行初始化以节省系统内存。当然你也可以在该对象即将被使用的时候初始化,这成为惰性初始化

4.2 继承

除非使用extends关键字显式指出从某个类中继承,否则任何类都默认继承于Object。

  • 子类会自动得到父类中的所有域与方法可以视作对父类方法的调用,但它们的访问权限不会变化,你可以选择:

    • 继续沿用父类定义的方法

    • 覆盖父类的调用逻辑

      覆盖父类的方法必须声明一个方法签名与父类方法完全一致的方法,可以使用@Override注解判断是否成功覆盖。

  • 在子类中可以使用super获得对父类对象的引用。

public class Composition {
    public void main(String[] args) {
        New n = new New();
        // 通过继承增强的方法
        n.show();

        // n.private_method();
    }
}
class Old{
    private String private_field = "private";
    public String public_field = "public";

    @Override
    public String toString() {
        String father = super.getClass().getSimpleName();
        return "Old extends from" + father;
    }

    private void private_method(){
        System.out.println("this is a private method");
    }

    public void show(){
        System.out.println("Hello");
    }
}
class New extends Old{
    private static Old old = new Old();
    // 覆盖父类定义的show方法
    public void show(){
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date = df.format(System.currentTimeMillis());
        old.show();
        System.out.println(date);
    }
    // 子类中增加的方法
    public void getName(){
        System.out.println(super.public_field);
        System.out.println(this.public_field);
    }
}

4.2.1 初始化基类

当创建一个导出类对象时,该对象会包含一个积累的子对象,基类"对象"被包含在导出类对象内部,但是对外表现为同一个类:你无法在外部访问到导出类的基类。这就意味着编译器会自动调用父类的默认构造器。对象的构建过程时向外“扩散”的——先构造基类,在构造导出类。

public class Extends {
    public static void main(String[] args) {
        Son son = new Son();
    }
}
class Father{
    public Father() {
        System.out.println("Father created");
    }
}

class Son extends Father {
    public Son() {
        System.out.println("Son created");
    }
}
//=========================================
output:
Father created
Son created

当然也可以显式调用父类的构造器:在子类构造器内使用super语句调用构造器,但需要注意的是super()语句与使用this()语句调用构造器一样必须在构造器方法体的第一句,也就是说二者只能选其一。

public class Extends {
    public static void main(String[] args) {
        Son sson = new Son(2);
    }
}
class Father{

    public Father(int i) {
        System.out.println(i);
        System.out.println("Father created");
    }
}

class Son extends Father {
    public Son(int i){
        super(i);
        System.out.println("Son created");
    }
}
//=========================================
output:
2
Father created
Son created

调用基类构造器时在导出类构造器中要做的第一件事。

4.2.2 向上转型

继承可以确保基类中所有的方法在导出类中同样有效,所以能够向基类发送的消息同样也可以想导出类发送,可以说导出类也是基类的一种类型。因此一个导出类对象可以向上转型为其基类对象,一个基类的引用可以持有其导出类的对象(多态)。多态与向上转型只是一个现象的两个角度的不同描述,对象在向上转型或者被基类的引用持有后都只能使用当前引用类型定义的成员。

image-20210101163055369
public class UpperCast {
    public static void main(String[] args) {
        Father fa = new Father();
        Son son = new Son();
        fa.fun();           // Father
        son.fun();          // Son
        People.show(son);   // Son
        People.show(fa);    // Father
    }
}

class People{
    public void fun(){
        System.out.println(this.getClass().getSimpleName());
    }
    public static void show(People people){
        System.out.println(people.getClass().getSimpleName());
    }
}

class Father extends People{ }
class Son extends People { }

例子中Son与Father类都导出自People类,因此Son与Father也可以看成是People类的一种,因此在执行People类的show()方法时,son与father对象可以作为People类型的参数被传入方法中。

4.3 组合与继承

4.3.1 结合使用组合与继承

代理模式

代理模式就是将一些功能交给其他对象来实现,为其他对象提供一种代理以控制对这个对象的访问。

静态代理

通过组合被代理对象实现代理,这个时最简单的方法。代理模式 | 菜鸟教程

public class Client {
    public static void main(String[] args) {
        Proxy.loginIn();
        Proxy.loginIn();
        Proxy.regist();
    }
}
// 被代理类:真正提供功能的类
class Real{
    private String name;
    public void loginIn(){
        System.out.println("LoginIn");
    }
    public void logOut(){
        System.out.println("logOut");
    }
    public void regist(){
        System.out.println("regist");
    }
}
// 代理类
class Proxy{
    private static Real real = new Real();
    public static void loginIn(){
        real.loginIn();
    }
    public static void logOut(){
        real.logOut();
    }
    public static void regist(){
        real.regist();
    }
}

在这里使用Proxy类代理类Real类,Real类才是真正实现功能的对象,但是Client实际请求的是Proxy类。

静态代理又一大缺点:你必须为被代理类中的所有要被代理的方法提供提供一个被代理方法。为了解决这个问题提出了动态代理,动态代理使用反射技术实现自动代理被调用的方法,这里不作展开。Java 动态代理详解 - whirlys

值得注意的是在同时使用组合与继承时,编译器会强制你构造基类对象,但是它并不会监督你初始化组合的对象,因此在使用组合对象前一定要记得对它初始化。

4.3.2 确保正确清理

习惯性地忘记清理对象不是一件好事,但有时需要在对象的声明周期内进行一些清理的操作,但是由于我们不知道垃圾回收器何时被调用,甚至无法确定它被调用了。因此我们需要显式地编写一个特殊的方法来销毁对象,并且保证使用类库的使用者知晓他们需要调用这一方法。如涉及IO操作的一些类的close()方法。如果需要清理对象,最好是编写自己的清理方法,但不要使用finalize(),可以参考IO类的close()方法实现。

public class Clean {
        public static void main(String[] args) {
            ConcreteIO io = new ConcreteIO();
            io.ioFunction();
            io.close();
        }
    }
}

class ConcreteIO extends AbstractIO{

    @Override
    public void ioFunction() {
        System.out.println("a IO function");
    }

    @Override
    public void close() {
        System.out.println("concrete closed");
    }
}

这里采用抽象类作为基类,在子类继承基类的时候编译器会强制实现基类的方法。

4.3.3 名称屏蔽

  • 重载:重载是指在同一个类中具有相同方法名但是参数列表却不相同的两个方法之间的关系
  • 重写(覆盖):重写发生在继承关系中,子类重新定义类父类的方法,要求这两个方法具有相同的方法签名(方法名 + 参数列表),重写可以通过@Override注解检测
class Father{
    public void fun(String s){
        System.out.println("s");
    }
}
class Son extends Father{

    /*
     *  Son 的两个方法的同名但是方法签名不一致构成重载
     *  Son继承自Father类并且Son的fun方法与Father的fun方法签名一致构成重写
     */
    @Override
    public void fun(String s) {
        System.out.println("override");
        super.fun(s);
    }

    public void fun(int i){
        System.out.println(i);
    }
}

Java基类中的某个方法已经被多次重载,若在导出类中再一次重载该方法,并不会影响其基类的方法(以及其重载方法)的执行。

public class OverrideTest {
    public static void main(String[] args) {
        Son son = new Son();
        son.fun("son");     // son
        son.fun(1);         // 1
        son.fun(Son.class);   // Son
    }
}

class Father {
    // 父类中的重载方法
    public void fun(String s) {
        System.out.println(s);
    }

    public void fun(int i) {
        System.out.println(i);
    }
}

class Son extends Father {
    // 再次重载父类定义的方法
    public void fun(Class aclass){
        System.out.println(aclass.getSimpleName());
    }
}

4.3.4 如何选择

组合与继承都允许在新的类中放置子对象,组合是显式地进行,继承是隐式的。

当我们使用组合的时候通常更加关注于对象已经拥有的功能,即在新类中嵌入某个对象,让其实现需要的功能,但是外界调用的只能是新类定义的接口;

而使用继承时关注点更多在基类定义好的接口上,即使用有个现有的类去开发它的特殊版本,或者部分沿用它提供的接口。通常是使用某个通用类,为了实现某种特定的需要而将其特殊化。

“继承时有毒的”

在新建类的时候如果首先考虑继承,反倒会加重设计的负担;更好的方式是组合,尤其是不能是十分确定需要使用哪一种类型时,组合可以动态选择类型;相反,继承在编译时就需要知道确切的类型。

public class Actor {
    public void act() {
    }
}

class HappyActor extends Actor{
    public void act(){
        System.out.println("HappyActor");
    }
}

class SadActor extends Actor{
    public void act(){
        System.out.println("SadActor");
    }
}

class Stage{
    private Actor actor = new HappyActor();
    public void changeActor(){
        this.actor = new SadActor();
    }
    public void performPlay(){
        actor.act();
    }
}

class Transmogrify{
    public static void main(String[] args) {
        Stage stage = new Stage();
        stage.performPlay();
        stage.changeActor();
        stage.performPlay();
    }
}
//=================================================
output:
HappyActor
SadActor

上面的例子中的performPlay()方法的行为会根据Actor的状态而改变,意味着我们在运行期间获得了灵活性。这也称作状态模式(使用类表示状态)。Stage通过组合不同的对象使自己的行为发生变化。

用继承表达行为间的差异,用字段表达状态上的变化

4.4 final关键字

final关键字通常指的是“不能被改变的”,它可以应用在变量、方法和类上。

4.4.1 final数据

使用final关键字修饰成员变量表示:

  1. 一个永不改变的编译时常量
  2. 一个运行时被初始化的值,并且不希望它被改变。

所谓恒定,对于引用类型来说是引用持有的对象不能改变,该对象内的非final成员仍是可变的;对于基本数据类型来说是指该引用的值不能改变。

public class Final {
    public static void main(String[] args) {
        final int i = 2;
        // cannot assign a value ro final variable
        // i = 3;

        final Number num = new Number();
        num.setI(2);
        System.out.println(num.i);
        // cannot assign a value ro final variable
        // num = new Number();
    }
}
class Number{
    public int i;

    public void setI(int i) {
        this.i = i;
    }
}

static final

static final 表现为全局只有一个且不可变,static final的域只占据一段不能改变的存储空间。static final 与 final 的区别表现在初始化上

public class Final {

    public static void main(String[] args){
        for (int i = 0; i < 10; i++) {
            SFNumber sf = new SFNumber();
            System.out.println("第"+i+"次 => sf\t"+sf.getINT()+"\ti\t"+sf.getI());
        }
    }
}
class SFNumber{
    private static Random RAND = new Random();
    private static final int INT = RAND.nextInt(100);
    private final int i = RAND.nextInt(100);

    public int getINT() {
        return INT;
    }

    public int getI() {
        return i;
    }
}
//=====================================================
output:0=> sf	28	i	801=> sf	28	i	692=> sf	28	i	933=> sf	28	i	654=> sf	28	i	965=> sf	28	i	656=> sf	28	i	587=> sf	28	i	228=> sf	28	i	719=> sf	28	i	21

由于INT被static final修饰,在整个内存内都只有一个并且在类第一次访问的时候就被初始化,之后不能更改,所以10次访问下来INT的值都是相等的;而i只被final修饰,在每次创建SFNumber对象的是偶都会被初始化一次。

空白final

空白final是指被声明为final但又未给定初始值的域。由于static的特殊性,static final 修饰的引用必须在声明处进行初始化。

public class Final {

      // final static Object OBJECT;

    public static void main(String[] args){
        final Object obj;
        // obj.toString(); // variable might not be intialized
        obj = new SFNumber();
        obj.toString();
    }
}

Java编译器确保所有空白final在被使用之前都被初始化。

4.4.2 final方法

使用final方法的原因有两个:

  1. 锁定方法,防止任何继承类修改它的定义,确保在继承中使方法行为保持不变,并且不会覆盖
  2. 效率,final修饰的方法会被编译器优化,优化的结果是减少了方法调用的次数。编译器直接将的方法体内嵌到了调用方法的地方

应该让编译器与JVM去处理优化的问题,因此final方法最常见就是明确该方法禁止覆盖

public class Final {

    public static void main(String[] args){
        Son son = new Son();
        son.donotOverride();
    }
}
class Father{
    public final void donotOverride(){
        System.out.println("Father");
        System.out.println("donot Override");
    }
}

class Son extends Father{
    // cannot Override
    /*
    public void donotOverride(){
        System.out.println("donot Override");
    }
    */
}
//============================================
output:
Father
donot ovrttide

final参数

方法的参数列表允许使用final修饰,这样确保无法在方法内部修改参数引用指向的对象。

public class Final {

    public static void main(String[] args) {
        no_final(new Integer(8));
        finalParam(new Integer(8));
    }

    public static void no_final(Integer integer) {
        integer = new Random().nextInt(100);
        System.out.println(integer);
    }

    public static void finalParam(final Integer integer) {
        // cannot assign a calue to final variable
        // integer = new Random().nextInt(100);
        System.out.println(integer);
    }
}

private 方法与final

类中所有private方法都隐式地指定为final。使用final修饰方法的主要目的是为了进制该方法被子类覆盖,而被final修饰的方法接口并不会暴露出去,子类也就无法覆盖该方法。

public class Final {

    public static void main(String[] args) {
        Son son  = new Son();
        son.show();
    }
}
class Father{
    private void show(){
        System.out.println("Father");
    }
    // 借助其他暴露出来的接口实现调用private修饰的方法
    public void getshow(){
        show();
    }
}
class Son extends Father{
    // @Override 子类无法访问到父类的private方法,不构成覆盖
    public void show(){
        System.out.println("son");
        getshow();
    }
}

4.4.3 final类

当将某个类指定为final时,就表明该类不允许被继承。编译器直接进制继承的发生。同时fianl内的所有方法也都会被隐式地指定为final,毕竟定义其导出类。

public class Final {
    public static void main(String[] args) {
        FinalC.show();
    }
}
final class FinalC{
    public static void show(){
        System.out.println("final class");
    }
}
// java: 无法从最终SuperTest.Test.FinalC进行继承
// class Extends extends FinalC{ }

4.5 类的初始化与清理

4.5.1 初始化

每个类的编译代码都在一个被public修饰的类命名的.class文件中,类的代码只有在初始使用的时候才加载,初次使用发生在static修饰的成员被访问以及通过new创建一个类的实例对象时,被static修饰的成员只会被初始化一次。

  1. 递归调用构造器(递归进行,直到调用根基类构造器)
  2. 按声明顺序调用成员的初始化方法
  3. 调用导出类构造器的主体

通过调用基类构造器保证当前需要的成员都已构建完毕,因此当我们进入导出类构造器时,基类中可供我们访问的成员都已经初始化。当涉及继承的时候,类的编译器会通过extends关键字得知它有一个基类,直到根基类被初始化,接着从内到外扩散地对类初始化。

public class Initialize{
    public static void main(String[] args) {
        Son son = new Son(82);
    }
}

class Grand{
    static String name = "grand";
    private int age ;
    public Grand(int age){
        this.age = age;
    }
}

class Father extends Grand{
    public Father(int age) {
        super(age);
    }
}

class Son extends Father{

    public Son(int age) {
        super(age);
    }
}

在通过new创建Son的实例对象时,编译器会找到Grand类(实际上根基类是Object)然后初始化其静态成员,然后通过构造器初始化其他成员,而在继承子Grand的各个导出类都需要在他们自己的构造器中调用各自的父类构造器,从而将参数从Son类型的构造器最终传递到Grand类型的构造器中。

4.5.2 清理

在继承关系中以及依赖关系中,销毁对象的顺序与初始化的顺序相反,即使从导出类开始一层一层往上,并且必须在导出类中覆盖dispose()方法

public void dispose(){
	super.dispose();
}

而当出现对象被一个或者多个其他对象共享的情况,就需要使用引用计数来跟踪引用访问着对象的数量了。

public class ReferenceCounting {
    public static void main(String[] args) {
        Shared shared = new Shared();
        Composing[] composing = {
                new Composing(shared),new Composing(shared),
                new Composing(shared),new Composing(shared),
                new Composing(shared),new Composing(shared)
        };
        for (Composing c : composing) {
            c.dispose();
        }
    }
}

class Shared {
    private int refConunt = 0;
    private static long counter = 0;
    private final long id = counter++;

    public Shared() {
        System.out.println("Creating " + this);
    }

    public void addref() {
        refConunt++;
    }

    protected void dispose() {
        if (--refConunt == 0) {
            System.out.println("Disposing " + this);
        }
    }

    @Override
    public String toString() {
        return "Shared{" +
                "refConunt=" + refConunt +
                ", id=" + id +
                '}';
    }
}

class Composing{
    private Shared shared;
    private static long counter = 0;
    private final long id = counter++;

    public Composing(Shared shared) {
        this.shared = shared;
        System.out.println("Creating "+ this);
        this.shared.addref();
    }

    protected void dispose(){
        System.out.println("disposing "+ this);
    }

    @Override
    public String toString() {
        return "Composing{" +
                "shared=" + shared +
                ", id=" + id +
                '}';
    }
}

5.接口

接口是一种特殊的类,它提供了一种将定义与实现分离的更结构化的方法。可以把接口理解为某种规范,只要使用了这种规范就是该类型的一员。

5.1 抽象类与抽象方法

抽象方法是指仅有声明而没有方法体的方法 ,而包含一个或者多个抽象方法的类就会被限定成为抽象类。抽象方法以及抽象类都使用abstract修饰。若类继承自一个抽象类,那么新类必须实现抽象类中的抽象方法,此外抽象类不能直接创建对象,必须通过继承实现类抽象方法并创建该导出类对象,因此abstractfinal不能同时修饰同一个类。

public abstract class Abstract {
    public abstract void show();
    public void fun(){
        System.out.println("function");
    }
}

class Concrete extends Abstract{

    @Override
    public void show() {
        System.out.println(this.getClass().getSimpleName());
    }
}

class Main{
    public static void main(String[] args) {
        Concrete concrete = new Concrete();
        concrete.show();    // Concrete
        concrete.fun();     // function

        // 通过匿名内部类的方式继承抽象类
        Abstract abs = new Abstract() {
            @Override
            public void show() {
                System.out.println(this.getClass().getName());
            }
        };
        abs.show();     // _SuperTest.Test.Main$1
        abs.fun();      // function
    }
}

使用抽象类主要两种用途:

  1. 通过继承实现对同一接口的多种实现
  2. 通过向上转型或者多态后实现使用同一对象管理一系列类的目的。

在这里插入图片描述

public abstract class Creature {
    public abstract void move();
}

class Cat extends Creature{

    @Override
    public void move() {
        System.out.println("run with legs");
    }
}
class Fish extends Creature{

    @Override
    public void move() {
        System.out.println("it can swim");
    }
}

class Main{
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.move();
        Fish fish = new Fish();
        fish.move();
    }
}

5.2 interface接口

interface关键字使得抽象的该能更上一层,interface只产生一个完全抽象的类,类里面的任何方法都不允许实现(static除外,因为被static修饰的方法属于类而非对象)。通过使用implement关键自可以让某个类遵循某个接口。由此接口的访问控制权限只能是publicdefault的。

接口内可以定义域以及方法,接口内的所有域都默认是public static final的;而接口内的方法都是默认public的,当然你可以显式地修改方法的访问权限。

interface aInterface{
    int i = 2;
    public void fun();
    static void show(){
        System.out.println("static method");
    }
}

class Impl implements aInterface{

    @Override
    public void fun() {
        // cannot assign a value to final variable
        // i = 3;
        System.out.println(i);
    }
}

class Main{
    public static void main(String[] args) {
        new Impl().fun();   // 2
        aInterface.show();  // static method
    }
}

在接口的方法通过某个类实现之后,实现类就成了个普通的类,可以按照正常的方式去扩展它(即接口的抽象在实现的那一刻终结了)。

初始化接口中的域

由于接口中的域是static final的,因此它不能是“空白final”,必须在声明的地方直接初始化;并且这些域也不会成为接口的一部分,它们的值被存储在该接口的静态存储区域内。

5.2.1 嵌套接口

接口可以嵌套在其他类与其他接口中,嵌套的接口也只能是public或者是default的,被public修饰的嵌套接口与非嵌套接口无异,值得注意的是被private修饰的接口。

private修饰的内嵌接口只能在当前类的范围内别访问,因此在类的范围外无论以什么样的方式获得该接口实现类的实例对象,都无法调用其中的成员;同时,也无法声明嵌套类的引用。

public class Interface {
    public static void main(String[] args) {
        A a = new A();
        // 创建内部类对象,只能创建public的类
        A.BImpl b = a.new BImpl();
        A.CImpl c = a.new CImpl();
        b.f();
        c.f();

        a.getPriB().f();
        // 尝试获得private 嵌套接口的实现类对象

        /* A.CImpl_pri 在 _SuperTest.Test.A 中是 private 访问控制 */
        // A.CImpl_pri priC_noCast = a.getPriC_noCast();
        
        /* A.C 在 _SuperTest.Test.A 中是 private 访问控制 */
        // A.C priC = a.getPriC();

        /* A.C中的f()是在不可访问的类或接口中定义的 */
        // a.getPriC().f();
        // a.getPriC_noCast().f();

        a.C_Method();
    }
}

class A {
    interface B {
        void f();
    }

    public class BImpl implements B {

        @Override
        public void f() {
            System.out.println(this.getClass().getSimpleName());
        }
    }

    private class BImpl_pri implements B {

        @Override
        public void f() {
            System.out.println("private implement " + this.getClass().getSimpleName());
        }
    }

    private interface C {
        void f();

        static void s_fun() {
            System.out.println("static");
        }
    }

    class CImpl implements C {

        @Override
        public void f() {
            System.out.println(this.getClass().getSimpleName());
        }
    }

    private class CImpl_pri implements C {

        @Override
        public void f() {
            System.out.println("private implement " + this.getClass().getSimpleName());
        }
    }

    public C getPriC() {
        System.out.println("get PriC");
        return new CImpl_pri();
    }

    public CImpl_pri getPriC_noCast() {
        System.out.println("get PriC");
        return new CImpl_pri();
    }

    public B getPriB() {
        return new BImpl_pri();
    }

    public void C_Method() {
        // 在类内部就能正常使用被private修饰的接口
        CImpl c = new CImpl();
        c.f();
        CImpl_pri cpri = new CImpl_pri();
        cpri.f();
    }
}

5.2.2 完全解耦

继承与接口的最大区别在于,接口的耦合程度比继承低。当你使用的引用为某个类,那么你只能调用这个类或者其子类的方法,也就是说只能通过继承进入这个调用的体系内;而使用接口作为引用的类型,任何该接口的实现类都在该引用的调用范围内。

5.3 使用接口

使用接口主要是为了以下目的:

  1. 核心目的:将方法的定义与实现分离,从而实现解耦。

  2. 实现多继承:extends关键字继承类只能继承自一个基类,但是使用implements关键字可以实现多个接口。

    这也是使用接口而不是抽象类的一大原因。

  3. 与抽象类一样,防止用户直接创建对象,只能通过实现继承类的方式创建实例对象。

5.3.1 实现多继承

通过extends你只能继承自一个基类,但是使用implement可以实现多个接口,只需要在接口之间使用逗号隔开并且放在extends关键字后。在继承接口之后,可以通过向上转型的方式转换为任意一个接口类型,这也是使用接口的核心原因,第二大原因与使用抽象类一致:防止使用者直接创建该类的对象。

public class MuiltyExtend {
    public static void main(String[] args) {
        Human human = new Human();
        Creature creature = human;
        LandLife landLife = human;

        Stand stand = human;
        creature.show();
        landLife.breath();
        stand.walk();
    }
}

abstract class Creature{
    public abstract void show();
}

interface LandLife{
    void breath();
}

interface Stand{
    void walk();
}

class Human extends Creature implements LandLife,Stand{

    @Override
    public void show() {
        System.out.println("Human");
    }

    @Override
    public void breath() {
        System.out.println("breath with lung");
    }

    @Override
    public void walk() {
        System.out.println("walk with legs");
    }
}

在这里插入图片描述

若基类与接口含有签名完全一样的方法,那么在实现接口的方法时也就覆盖了基类方法的实现。

public class MuiltyExtend {
    public static void main(String[] args) {
        Human human = new Human();
        Creature creature = human;
        LandLife landLife = human;

        human.show();   // Human
        creature.show();// Human
        landLife.show();// Human
    }
}

class Creature{
    public void show() {
        System.out.println("creature");
    }
}

interface LandLife{
    void show();
}


class Human extends Creature implements LandLife{

    @Override
    public void show() {
        System.out.println("Human");
    }
}

5.3.2 通过继承扩展接口

接口也可以通过extends关键字来为接口添加新的方法声明,需要注意的是当接口只能继承子另外一个类。当接口继承了其他接口之后,新接口会包含基类接口的方法(记住接口内定义的数据是static final的)。

public class MethodName {
    public static void main(String[] args) {
        Impl impl = new Impl();
        impl.fun();
        impl.show();
        System.out.println(EClass.i+"\t"+I1.i);
    }
}

interface EClass{
    void show();
    int i = 2;
}

interface I1 extends EClass {
    int i = 3;
    void fun();
}

class Impl implements I1{

    @Override
    public void show() {    }

    @Override
    public void fun() {    }
}

名字冲突问题

在进行接口的多重继承时,不能出现方法签名相同但是方法返回值不同的情况。但是可以出现方法名相同,方法签名不同以及签名完全相同的情况,它们都会在接口的实现类中实现重载以及被重写。

public class MethodName {
    public static void main(String[] args) {
        Impl impl = new Impl();
        impl.fun();
        impl.fun(2);
        impl.show();
    }
}

interface EClass{
    void show();
    int i = 2;
}

interface I1 extends EClass {
    int i = 3;
    void fun();
}

interface I2 extends EClass,I1 {
    void show();
    // I2中的fun()与I1中的fun()冲突,返回类型int与void不兼容
    // int fun();
    int fun(int i);
}

class Impl implements I1,I2{
	// 这里重载了I2内的show()方法
    @Override
    public void show() {

    }

	// 下面两个方法在实现后构成重载
    @Override
    public int fun(int i) {
        return i;
    }

    @Override
    public void fun() {

    }
}

5.3.2策略模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qbbdsgkI-1609837164278)(C:\Users\Leric\AppData\Roaming\Typora\typora-user-images\image-20210103200450116.png)]

例子来自于《图解设计模式》

在上面的例子中,使用定义了一个Strategy接口,它包含两个接口,用以决定下一手出什么花色;在Player类中组合尽了一个Strategy实例对象,它根据策略使用不同的Strategy实现类,若需要更换策略,只需要更换strategy持有的对象即可,这就是策略模式。

5.3.3 适配器模式

在这里插入图片描述

例子来自于《图解设计模式》

使用适配器模式的一个主要目的是复用旧有的类,产生新的符合功能的新类型。在这里使用的是继承旧类(Banner)实现接口(Print)的方式来产生PrintBanner类。

5.3.4 接口与工厂

public class Factories {
    public static void serviceConsumer(ServiceFactory serf){
        Service s = serf.getService();
        s.method1();
        s.method2();
    }

    public static void main(String[] args) {
        serviceConsumer(new ServiceFactoryImpl1());
        serviceConsumer(new ServiceFactoryImpl2());
    }
}

interface Service{
    void method1();
    void method2();
}

interface ServiceFactory{
    Service getService();
}

class Impl1 implements Service{
    // 包权限
    Impl1() {
    }

    @Override
    public void method1() {
        System.out.println("Impl1 mehtod1");
    }

    @Override
    public void method2() {
        System.out.println("Impl1 mehtod2");
    }
}
class Impl2 implements Service{
    // 包权限
    Impl2() {
    }

    @Override
    public void method1() {
        System.out.println("Impl2 mehtod1");
    }

    @Override
    public void method2() {
        System.out.println("Impl2 mehtod2");
    }
}

class ServiceFactoryImpl1 implements ServiceFactory{

    @Override
    public Service getService() {
        return new Impl1();
    }
}
class ServiceFactoryImpl2 implements ServiceFactory{

    @Override
    public Service getService() {
        return new Impl2();
    }
}

在这里插入图片描述

工厂模式是生成接口对象的典型方式,通过在工厂调用创建方法(方法内调用对应实现类的构造器),从而通过工厂生成某个实现类的实例对象,否则就应该在某处指定要创建的Service的具体类型。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值