面试问题:超负荷和超负荷的区别。重载的方法可以根据返回类型来区分吗?
面试官考察点猜想#这个问题纯粹是对基础理论知识的考查,对实际开发工作的指导意义不大。毕竟编辑器都有语法提示功能,写的不对就会有错误提示。
背景知识详解#实际开发中经常用到重载和重写,涉及的背景知识并不难。
重写#重写子类是重写父类的可访问方法的实现过程,返回值和参数不可更改。就是外壳不变,内核重写!
重写发生在类的继承关系或者类的实现关系中。重写后的方法和原方法需要保持相同的返回值类型、方法名、参数个数和参数类型。简而言之,子类重写的方法必须与父类完全一致
类的继承关系#让我们看下面这个基于继承关系的例子。
class Animal { public void move(){ system . out . println(& # 34;动物可以移动& # 34;); } } 类鸟扩展动物{ public void move(){ system . out . println(& # 34;鸟儿会飞& # 34;); } } 类狗扩展动物{ public void move(){ system . out . println(& # 34;狗会跑& # 34;) } } 公共类TestMain{ 公共静态void main(String args[]){ Animal a = new Animal();//Animal object Animal b = new Bird();//Bird对象 Animal c = new Dog();// Dog对象 a . move();//执行Animal类 b.move()的方法;//执行Bird类 c.move()的方法;//执行Dog类的方法 } } 上述程序的结果。
动物可以移动 鸟可以飞 狗可以跑 在这种情况下,Animal是属于动物的抽象类,它定义了一个方法move()。表示拥有动物的行为。
动物只是一个泛范畴,具体到一个动物,行为是不一样的。所以定义了Bird和Doc,分别继承了animal类,并重写了move()方法来实现这两种动物的行为。
重写的好处是子类可以根据需要定义自己的行为。也就是说,子类可以根据需要实现父类的方法。
在类继承关系中,不需要重写父类和子类的非抽象方法。在实际应用中,如果父类的方法被重写,实例对象的引用指向子类,JVM会自动调用子类重写的方法,此时父类的方法被完全屏蔽。就像之前测试的代码一样。
父类引用指向子类实现Dog()。此时调用c.move()方法只会调用Dog类中的move()方法。如果Dog子类没有覆盖move()方法,将调用父类Animal的move()方法。
动物c =新狗();// Dog对象 c . move();//执行Dog类的方法 在某些情况下,子类会重写父类的方法。我们希望在调用子类来覆盖方法的同时,我们仍然可以调用父类被覆盖的方法。我们如何做到这一点?
Super关键字#当需要调用子类中父类的重写方法时,使用super关键字。
class Animal { public void move(){ system . out . println(& # 34;动物可以移动& # 34;); } } 类鸟延伸动物{ public void move(){ super . move();//添加超级调用 system . out . println(& # 34;鸟儿会飞& # 34;); } } } 公共类TestMain{ 公共静态void main(String args[]){ Animal b = new Bird();//Bird object b . move();//执行Bird类 } } 的方法。运行结果如下:
动物会动 鸟会飞 重写方法的规则#总结一下,在Java中,方法重写的规则。
基于接口的重写在实际应用中经常使用。以线程实现为例,如图,展示了Thread和Runnable的类图。
Runnable是一个定义线程执行方法的接口。代码如下:
@ functional interface public interface Runnable { /* * *当对象实现接口& lt代码& gtRunnable & lt/code & gt;用于 *创建一个线程,启动线程会导致对象& # 39;s * & lt;代码& gt运行& lt/code & gt;要在单独执行的 *线程中调用的方法。 * & lt;p & gt *方法的一般合同& lt代码& gt运行& lt/code & gt;它可以 *采取任何行动。 * * @ see Java . lang . thread # run() */ 公共抽象void run(); } 在实际应用中,我们可以直接继承这个接口来声明一个线程。
Thread是一个普通的线程类,实现了Runnable接口,并覆盖了Runnable接口的run方法。这种设计的目的是为了避免Java中的一个类只能实现一个接口的规则所导致的问题,如果一个类继承了其他接口但想要实现线程的话。
public 类线程实现Runnable { @ Override public void run(){ if(target!= null){ target . run(); } } } 因为接口只是用于规范设计来描述一个对象的行为,它没有具体的实现,所以如果需要声明一个线程,就需要实现接口,重写其中的抽象方法(接口中没有实现的方法是抽象的,子类必须重写)。
Thread类覆盖Runnable中的run方法,该方法调用target.run()。这个目标是thread业务的真实实现,Thread只是一个委托设计模式。
所以如果要通过继承Thread来实现Thread,就需要按照下面的代码来实现,其中target是代表子类的App的对象实例。
public class app extensions thread { @ override public void run(){ /doing something } } 因为接口只是一个行为规范,本身并不提供实现,所以实现了接口的一个子类。
重载#重载在具有相同方法名和不同参数的类中。返回类型可以相同,也可以不同。
每个重载方法(或构造函数)必须有一个唯一的参数类型列表。
最常见的地方就是构造函数的重载。例如,在ThreadPoolExecutor线程池的实现类中,可以看到以下重载方法。
public thread pool executor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, blocking queue & lt;Runnable & gtwork queue){ } public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, blocking queue & lt;Runnable & gtworkQueue, thread factory thread factory){ } public thread pool executor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, blocking queue & lt;Runnable & gtWorkqueue, ThreadFactory, RejectedexecutionHandler Handler){ } 重载方法的好处是类可以统一处理不同类型的手段。调用方法时,传递给它们不同数量和类型的参数来决定使用哪个方法,这就是多态性。
其特点是:重载发生在这个类中,方法名相同,参数列表不同,与返回值无关,只与方法名和参数类型有关。
方法重载时,方法之间是有一定联系的,因为这样可以提高程序的可读性,我们通常只重载功能相似的方法。
重载规则#了解以上知识点后,我们再来看这道面试题。
面试问题:超负荷和超负荷的区别。重载的方法可以根据返回类型来区分吗?
差异:
可以根据返回类型来区分重载方法吗
重载方法不能按类型区分,只能按参数类型和个数区分。但是对于重载的方法,允许修改返回值类型、异常类型和访问级别,但是不能只根据这些类型的类重载。
为什么不能只通过返回类型来区分重载?
原因是在调用目标方法时,不能指定返回值类型信息。这个时候编译器不知道你要调用哪个函数。
比如下面这段代码,调用max(1,2)时;无法确定哪个叫什么。仅从这个角度来看,不应该允许具有不同返回值类型的重载。
float max(int a,int b); int max(int a,int b); 有的同学可能会问,如果编译器可以根据上下文判断呢?比如下面的代码。
float x=max(1,2); int y=max(2,3); 实际开发中,经常会有一个方法调用max(1,2),不声明返回值。因为这种情况,这个理论无法实现。
函数的返回值只是函数运行后的一个“状态”。保持方法的调用方与被调用方通信是关键。它不能用作方法的“标识”。
问题总结#这个问题其实属于那种问题。如果你不问我,我肯定会认为我知道,可以用在工作开展上,没有任何问题。但如果你问我,我会很困惑,不是因为我真的不懂,而是因为我不知道如何组织语言来描述这两个概念。
建议你参考费曼的学习方法,就是通过一次演讲,把本文学到的理论表达出来,和同事或者自己进行问答。