Java面试 4.2 面向对象(7-11)

本文介绍了Java中面向对象的重载和覆盖的概念及区别,重载关注方法签名的差异,而覆盖涉及继承中的方法替换。接着讨论了抽象类和接口的异同,包括它们的实现方式、使用场景和设计哲学。最后提到了内部类的四种类型以及如何获取父类的类名和this与super的关键字用法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

4.2.7 重载和覆盖有什么区别

重载(overload)和覆盖(override)是 Java 多态性的不同表现方式。其中,重载是在一个类中多态性的一种表现,是指在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型。在使用重载时,需要注意以下几点:

1)重载是通过不同的方法参数来区分的,例如不同的参数个数、不同的参数类型或不同的参数顺序。

2)不能通过方法的访问权限、返回值类型和抛出的异常类型来进行重载。

3)对于继承来说,如果基类方法的访问权限为 private,那么就不能在派生类对其重载;如果派生类也定义了一个同名的函数,这只是一个新的方法,不会达到重载的效果。

覆盖是指派生类函数覆盖基类函数。覆盖一个方法并对其重写,以达到不同的作用。在使用覆盖时需要注意以下几点:

1)派生类中的覆盖方法必须要和基类中被覆盖的方法有相同的函数名和参数。

2)派生类中的覆盖方法的返回值必须和基类中被覆盖白方法的返回值相同。

3)派生类中的覆盖方法所抛出的异常必须和基类(或是其子类)中被覆盖的方法所抛出的异常一致。

4)基类中被覆盖的方法不能为 private,否则其子类只是定义了一个方法,并没有对其覆盖。

 

重载与覆盖的区别主要有以下几个方面:

1)覆盖是子类和父类之间的关系,是垂直关系;重载是同一个类中方法之间的关系,是水平关系。

2)覆盖只能由一个方法或只能由一对方法产生关系;重载是多个方法之间的关系。

3)覆盖要求参数列表相同;重载要求参数列表不同。

4)覆盖关系中,调用方法体是根据对象的类型(对象对应存储空间类型)来决定;而重载关系是根据调用时的实参表与形参表来选择方法体的。

4.2.8 抽象类(abstract class)与接口(interface)有什么异同

只要包含一个抽象方法的类就必须被声明为抽象类,抽象类可以声明方法的存在而不去实现它,被声明为抽象的方法不能包含方法体。在实现时,必须包含相同的或者更低的访问级别(public→protected→private)。抽象类在使用的过程中不能被实例化,但是可以创建一个对象使其指向具体子类的一个实例。抽象类的子类为父类中的所有抽象方法提供具体的实现,否则它们也是抽象类。接口可以被看作抽象类的变体。接口中的所有方法都是抽象的,可以通过接口来间接地实现多重继承。接口中的成员变量都是 static final 类型。由于抽象类可以包含部分方法的实现,因此,在一些场合下抽象类比接口存在更多的优势。

接口与抽象类的相同点如下:

1)都不能被实例化。

2)接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能被实例化。

接口与抽象类的不同点如下:

1)接口只有定义,其方法不能在接口中实现,只有实现接口的类才能实现接口中定义的方法,而抽象类可以有定义与实现,即其方法可以在抽象类中被实现。

2)接口需要实现(用 implements),但抽象类只能被继承(用 extends)。一个类可以实现多个接口,但一个类只能继承一个抽象类,因此使用接口可以间接地达到多重继承的目的。

3)接口强调特定功能的实现,其设计理念是「has-a」关系;而抽象类强调所属关系,其设计理念为「is-a」关系。

4)接口中定义的成员变量默认为 public static final,只能够有静态的不能被修改的数据成员,而且,必须给其赋初值,其所有成员方法都是 public、abstract 的,而且只能被这两个关键字修饰。而抽象类可以有自己的数据成员变量,也可以有非抽象的成员方法,而且,抽象类中的成员变量默认为 default(本包可见),当然也可以被定义为 private、protected 和 public,这些成员变量可以在子类中被重新定义,也可以被重新赋值,抽象类中的抽象方法(其前有 abstract 修饰)不能用 private、static、synchronized、native 等访问修饰符修饰,同时方法必须以分号结尾,并且不带花括号。所以,当功能需要累积时,用抽象类;不需要累积时,用接口。

5)接口被运用于实现比较常用的功能,便于日后维护或者添加删除方法;而抽象类更倾向于充当公共类的角色,不适用于日后重新对里面的代码进行修改。

简单点说,接口是一种特殊形式的抽象类,使用接口完全有可能实现与抽象类相同的操作,但一般而言,抽象类多用于在同类事物中有无法具体描述的方法的场景,所以当子类和父类之间存在有逻辑上的层次结构时,推荐使用抽象类;而接口多用于不同类之间,定义不同类之间的通信规则,所以当希望支持差别较大的两个或者更多对象之间的特定交互行为时,应该使用接口。

此外,接口可以继承接口,抽象类可以实现接口,抽象类也可以继承具体类。抽象类也可以有静态的 main 方法。

常见笔试题:

1.下列关于接口的定义中,正确的是( )。

A.void methoda();B.public double methoda();

C.public final double methoda();D.static void methoda(double d1);

E.protected void methoda(double d1);F.int a;

G.int b=1;

答案:A、B、G。从上面的分析可知,接口中的方法只能用关键字 public 和 abstract 来修饰,因此选项 C、D、E 都是错误的。接口中的属性默认都为 public static final,由于属性被 fi-nal 修饰,因此它是常量,常量在定义时就必须初始化,因此 F 是错误的。

2.下列说法中,正确的是( )。

A.声明抽象方法大括号可有可无 B.声明抽象方法不可写出大括号

C.抽象方法有方法体 D.abstract 可修饰属性、方法和类

答案:B。抽象方法不能有方法体,同理也就不能有大括号。abstract 只能用来修饰类与方法,不能用来修饰属性。


4.2.9 内部类有哪些

在 Java 语言中,可以把一个类定义到另外一个类的内部,在类里面的这个类就叫做内部类,外面的类叫做外部类。

内部类可以分为很多种,主要有以下 4 种:静态内部类(static inner class)、成员内部类(member inner class)、局部内部类(local inner class)和匿名内部类(anonymous inner class)。它们的定义方法如下。

静态内部类是指被声明为 static 的内部类,它可以不依赖于外部类实例而被实例化,而通常的内部类需要在外部类实例化后才能实例化。静态内部类不能与外部类有相同的名字,不能访问外部类的普通成员变量,只能访问外部类中的静态成员和静态方法(包括私有类型)。

一个静态内部类,如果去掉「static」关键字,就成为成员内部类。成员内部类为非静态内部类,它可以自由地引用外部类的属性和方法,无论这些属性和方法是静态的还是非静态的。但是它与一个实例绑定在了一起,不可以定义静态的属性和方法。只有在外部的类被实例化后,这个内部类才能被实例化。需要注意的是,非静态内部类中不能有静态成员。

局部内部类指的是定义在一个代码块内的类,它的作用范围为其所在的代码块,是内部类中最少使用到的一种类型。局部内部类像局部变量一样,不能被 public、protected、private 以及 static 修饰,只能访问方法中定义为 final 类型的局部变量。对一个静态内部类,去掉其声明中的「static」关键字,将其定义移入其外部类的静态方法或静态初始化代码段中就成为了局部静态内部类。对一个成员类,将其定义移入其外部类的实例方法或实例初始化代码中就成为了局部内部类。局部静态内部类与静态内部类的基本特性相同。局部内部类与内部类的基本特性相同。

匿名内部类是一种没有类名的内部类,不使用关键字 class、extends、implements,没有构造函数,它必须继承(extends)其他类或实现其他接口。匿名内部类的好处是代码更加简洁、紧凑,但带来的问题是易读性下降。它一般应用于 GUI(Graphical User Interface,图形用户界面)编程中实现事件处理等。在使用匿名内部类时,需要牢记以下几个原则:

1)匿名内部类不能有构造函数。

2)匿名内部类不能定义静态成员、方法和类。

3)匿名内部类不能是 public、protected、private、static。

4)只能创建匿名内部类的一个实例。

5)一个匿名内部类一定是在 new 的后面,这个匿名类必须继承一个父类或实现一个接口。

6)因为匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。

在非静态内部类中不能定义静态成员,因此 A 和 B 是错误的。由于静态内部类不能访问外部类的非静态成员,因此 D 是错误的。 

4.2.10 如何获取父类的类名

Java 语言提供了获取类名的方法:getClass().getName(),开发人员可以调用这个方法来获取类名。

代码如下(示例 1)所示:

通过以上这个例子的运行结果是否可以得出一个结论:通过调用父类的 getClass().getName()方法来获取父类的类名是可行的呢?为了解答这个问题,首先来做一个实验,给出下面的程序(示例 2)。

为什么输出的结果不是「A」而是「Test」呢?主要原因在于 Java 语言中任何类都继承自 Object 类,getClass()方法在 Object 类中被定义为 final 与 native,子类不能覆盖该方法。因此 this.getClass()和 super.getClass()最终都调用的是 Object 中的 getClass()方法。而 Ob-ject 的 getClass()方法的释义是:返回此 Object 的运行时类。由于在示例 2 中实际运行的类是 Test 而不是 A,因此程序输出结果为 Test。

 

那么如何才能在子类中得到父类的名字呢?可以通过 Java 的反射机制,使用 getClass().getSuperclass().getName(),代码如下(示例 3)所示:


4.2.11 this 与 super 有什么区别

在 Java 语言中,this 用来指向当前实例对象,它的一个非常重要的作用就是用来区分对象的成员变量与方法的形参(当一个方法的形参与成员变量的名字相同时,就会覆盖成员变量)。

为了能够对 this 有一个更好的认识,首先创建一个类 People,示例如下:

super 可以用来访问父类的方法或成员变量。当子类的方法或成员变量与父类有相同名字时也会覆盖父类的方法或成员变量,要想访问父类的方法或成员变量只能通过 super 关键字来访问。

程序运行结果为:

Sub:f()

Base:f()

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

康冕峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值