阿里面经之解答by cmershen(1)——Java的基本特性,面向对象的六大特征等

在牛客网上看到一份阿里面经,真假先不谈,就其中总结概括的知识点和常见问题还是不错的,在面试中也是经常会被问到的。
美中不足的是,里面的解答有的太过冗杂,有的则太过简略。本学妹按自己的理解,将这些问题重新整理,给出一份自己的理解,与读者共同学习共同进步。

1.自我介绍(略)
2.做过的项目(就说dts好啦~~)


(以下是重点的开始)


3.Java的四个基本特性:抽象,封装,继承,多态,对多态的理解以及项目中哪些地方用到多态
原po的解释太过繁杂,根本记不住
抽象:将一类对象的共同特征抽象成类。包括数据抽象(即成员变量)和行为抽象(即方法)。
继承:将已知类和继承信息合并起来创建新类的过程。
封装:隐藏内部的实现逻辑,只向外界提供最简单的接口的过程。
多态:允许不同子类型的对象,对同一消息做出不同的响应。

 3.1 多态的理解
 方法重载和重写:重载是同一个类中,同名方法有不同的参数列表 例如:

f(arg1,arg2) {
    ...
}
f(arg1,arg2,arg3) {
    ...
}

方法重写:
是在子类中重写父类已有的方法或抽象方法,例如:

class ClassA {
    public void f() {
        System.out.println("ClassA f()");
    }
}
class ClassB extends ClassA {
    public void f() {
        System.out.println("ClassB f()");
    }
}

要实现多态需要做的两件事:方法重写,对象造型(ClassA b = new ClassB();)
项目中多态的应用:
在dts中的抽象语法树部分使用过多态,类SimpleNode代表抽象语法树的节点,但抽象语法树有许多种类型的节点,所以SimpleNode类中有一些抽象方法,在不同类型的节点中分别使用不同的实现类实现不同的处理逻辑。
(其实根本就不是这样的,编一个吧,DTS的设计模式无力吐槽。。。)
4.面向对象和面向过程的区别?用面向过程可以实现面向对象吗?
面向过程以事件为中心,分析出解决问题的步骤,然后用函数把这些步骤实现。
面向对象以类和对象为中心。
举例:”汽车发动“和”汽车停止“
对于面向对象来说是这样的:

Class Car {
    ...
    void start();
    void stop();
}
Car c=new Car();
c.start();
c.stop();

对于面向过程来说是这样的:

void start(Car* c)
void stop(Car* c)

5.重载和重写如何确定调用哪个函数?
重载:发生在同一个类中,同名方法具有不同参数,根据调用函数时的入口参数,选择与之对应的重载函数。
重写:发生在父类与子类之间,根据不同的子类对象确定调用哪个方法。

6.面向对象开发的六个基本原则:
(1)单一职责(SRP):There should never be more than one reason for a class to change.
即一个类只有一个引起变化的原因。
举例:一个调制解调器有四个功能,分别为拨号,挂断,接受消息,发送消息。

public class Modem {
    public void dial();
    public void hangup();
    public void receive();
    public void send();
}

实际上Dial(拨通电话)和Hangup(挂电话)是属于连接的范畴,而Receive(收到信息)和Send(发送信息)是属于数据传送的范畴。这里类包括两个职责,显然违反了SRP。
因此要重构Modem类,从中抽象出两个接口,一个专门负责连接,另一个专门负责数据传送。依赖Modem类的元素要做相应的细化,根据职责的不同分别依赖不同的接口。如下:

public interface IConnection {
    public void dial();
    public void hangup();
}

public interface IDataTransfer {
    public void receive();
    public void send();
}

public class Modem implements IConnection,IDataTransfer
{

}

这样无论单独修改连接部分还是单独修改数据传送部分,都彼此互不影响。
(2)里氏替换原则:“Subtypes must be substitutable for their base types”
也就是说子类必须能够替换成他们的基类。
举例:一个士兵用枪(手枪、步枪、机枪)射击杀人可描述为:

public class Soldier {
    public void killEnemy(AbstractGun gun) {
        gun.shoot();
    }
}
public abstract class Gun {
    public void shoot();
}
public class HandGun extends Gun{
    public void shoot() {
        System.out.println("HandGun shoot!");
    }
}
public class Rifle extends Gun{
    public void shoot() {
        System.out.println("Rifle shoot!");
    }
}
public class MachineGun extends Gun{
    public void shoot() {
        System.out.println("MachineGun shoot!");
    }
}

如果public void killEnemy(AbstractGun gun)中不能调用父类AbstractGun,则违反了里氏代换原则。
(3)依赖倒置原则(Dependence Inversion Principle DIP )
所谓依赖倒置原则就是要依赖于抽象,不要依赖于具体。简单的说就是对抽象进行编程,不要对实现进行编程。面向对象的开发很好的解决了这个问题,一般的情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变化,只要抽象不变,客户程序就不需要变化。
例如一个自动驾驶系统可以在福特车和本田车上使用,提供了三个方法:开车,停车,转弯。

如果不考虑依赖倒置原则来实现,应该这样写:

public class HondaCar {
    public void Run() { System.out.println("本田车启动了!"); }
    public void Turn() { System.out.println("本田车拐弯了!"); }
    public void Stop() { System.out.println("本田车停止了!"); }
}

public class FordCar {
    public void Run() { System.out.println("福特车启动了!"); }
    public void Turn() { System.out.println("福特车拐弯了!"); }
    public void Stop() { System.out.println("福特车停止了!"); }
}

public class AutoSystem {
    public enum CarType{ Ford,Honda}
    private HondaCar hondacar=new HondaCar();
    private FordCar fordcar=new FordCar();
    private CarType type;

    public AutoSystem(CarType carType) {
        this.type = carType;
    }

    public void RunCar() {
        if (this.type == CarType.Honda)
            hondacar.Run();
        else if (this.type == CarType.Ford)
            fordcar.Run();
    }
    public void StopCar() {
        if (this.type == CarType.Honda)
            hondacar.Stop();
        else if (this.type == CarType.Ford)
            fordcar.Stop();
    }
    public void TurnCar() {
        if (this.type == CarType.Honda)
            hondacar.Turn();
        else if (this.type == CarType.Ford)
            fordcar.Turn();
    }
}

若该自动驾驶系统增加了对宝马车的支持,则需要增加BMWCar类,并在AutoSystem里按规律增加支持BMWCar的语句。如果又增加了10种,20种车的支持呢?如果不只是Run,Stop,Turn三种功能,而是100种,10000种功能呢?改动量将会变得极大。
这样我们需要抽象出来,使得Run,Stop,Turn三个函数只与车接口耦合,而不直接依赖具体车型,应该重构以上功能如下:

public interface ICar {
    public void Run();
    public void Turn();
    public void Stop();
}

public class HondaCar implements ICar{
    public void Run() { System.out.println("本田车启动了!"); }
    public void Turn() { System.out.println("本田车拐弯了!"); }
    public void Stop() { System.out.println("本田车停止了!"); }
}

public class FordCar implements ICar{
    public void Run() { System.out.println("福特车启动了!"); }
    public void Turn() { System.out.println("福特车拐弯了!"); }
    public void Stop() { System.out.println("福特车停止了!"); }
}

public class AutoSystem {
    public ICar car;
    public AutoSystem(ICar car) {
        this.car=car;
    }
    public void runCar() {
        this.car.run();
    }
    public void stopCar() {
        this.car.stop();
    }
    public void turnCar() {
        this.car.turn();
    }
}

这样AutoSystem只与ICar耦合,无论增加多少种车,也不需改变AutoSystem类,只需新增加ICar接口的实现类即可。
4合成聚合复用:子类是超类的一个特殊种类,而不是超类的一个角色。区分“Has-A”和“Is-A”。只有“Is-A”关系才符合继承关系,“Has-A”关系应当用聚合来描述。
尽量使用聚合而不是继承,只有严格满足“Is-A”关系才使用继承。
5.开放封闭原则:Software entities(classes,modules,functions etc) should open for extension ,but close for modification.
软件实体应该对扩展开放,而对修改封闭。
对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
对修改封闭,意味着类一旦设计完成,就可以独立其工作,而不要对类做任何修改。
举例:
银行业务系统有3种功能,存款、取款、转账。若不考虑OCP,则这样设计:

public class BankProcess
{
    public void Deposite();
    public void Withdraw();
    public void Transfer();
}

public class BankStaff
{
    private BankProcess bankpro = new BankProcess();
    public void BankHandle(Client client) {
        switch (client.Type) {
            case "deposite":
                bankpro.Deposite();
                break;
            case "withdraw":
                bankpro.Withdraw();
                break;
            case "transfer":
                bankpro.Transfer();
                break;
        }
    }
}

如果该系统升级,增加了第四种功能,那么BankProcess和BankStaff类都要修改。正确的做法是把三种业务抽象成接口,当业务员依赖于固定的抽象时,对修改就是封闭的,而通过继承和多态继承,从抽象体中扩展出新的实现,就是对扩展的开放。
6接口隔离原则(ISP)

接口隔离原则 认为:”使用多个专门的接口比使用单一的总接口要好”。因为接口如果能够保持粒度够小,就能保证它足够稳定,正如单一职责原则所标榜的那样。多个专门的接口就好比采用活字制版,可以随时拼版拆版,既利于修改,又利于文字的重用。而单一的总接口就是雕版印刷,显得笨重,实现殊为不易;一旦发现错字别字,就很难修改,往往需要整块雕版重新雕刻。

迪米特法则:一个类应该对其他类有尽可能少的了解。简称为“不要和陌生人说话”。
(DTS中的应用:遍历控制流图时使用观察者模式。)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值