您的位置:首页 > 编程语言 > Java开发

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

2016-06-27 16:49 627 查看
在牛客网上看到一份阿里面经,真假先不谈,就其中总结概括的知识点和常见问题还是不错的,在面试中也是经常会被问到的。

美中不足的是,里面的解答有的太过冗杂,有的则太过简略。本学妹按自己的理解,将这些问题重新整理,给出一份自己的理解,与读者共同学习共同进步。

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中的应用:遍历控制流图时使用观察者模式。)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  阿里 面试