问题及答案来源自《Java程序员面试笔试宝典》第四章 Java基础知识 4.2面向对象技术
1、面向对象与面向过程有什么区别?
看下面一个实例即可:
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候依次调用;
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为
可以拿生活中的实例来理解面向过程与面向对象,例如五子棋,面向过程的设计思路就是首先分析问题的步骤:
1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,
6、绘制画面,7、判断输赢,8、返回步骤2,9、输出最后结果。把上面每个步骤用不同的方法来实现。
如果是面向对象的设计思想来解决问题。面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为1、黑白双方,
这两方的行为是一模一样的,2、棋盘系统,负责绘制画面,3、规则系统,负责判定诸如犯规、输赢等。
第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收
到了棋子的变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。
可以明显地看出,面向对象是以功能来划分问题,而不是步骤。同样是绘制棋局,这样的行为在面向过程的设计中
分散在了多个步骤中,很可能出现不同的绘制版本,因为通常设计人员会考虑到实际情况进行各种各样的简化。
而面向对象的设计中,绘图只可能在棋盘对象中出现,从而保证了绘图的统一。
2、面向对象有哪些特征?
面向对象的主要特征:抽象、继承、封装、多态,详细解释如下:
- 抽象:忽略主题中与当前目标无关的那些方面,只注意与当前目标有关的方面
- 继承:子类继承父类,子类从父类那里继承成员方法和成员变量并且子类可以修改(重写)或增加新的方法
- 封装:将客观事物抽象成类时每个类可以对自身的数据和方法实行保护,可以进行信息隐藏
- 多态:指不同类的对象对同一消息作出响应,也就是同一个行为具有多个不同表现形式或形态的能力
3、面向对象的开发方式有什么优点?
面向对象开发的优点:
- 开发效率高
- 保证软件的鲁棒性
- 保证软件的高可维护性
4、什么是继承?
继承是面向对象中的一个非常重要的特性。通过继承,子类可以使用父类中的一些成员变量和成员方法,
从而提高代码复用性,提高开发效率。在Java中,被继承的类叫基类(superclasss)或父类,继承基类或父类的
类叫派生类或子类(subclass)。
使用如下:
1 public class 父类{} 2 public class 子类 extends 父类{}
继承的特性如下:
- Java不支持多重继承,也就是说子类最多只能有一个父类,但是可以通过多个接口达到多重继承的目的
- 子类只能继承父类的非私有(public和protected)成员变量和方法
- 当子类的成员变量或方法和父类中的成员变量或方法相同时,子类的成员变量或方法将覆盖父类
5、组合和继承有什么区别?
组合和继承都是面向对象中代码复用的方式:
组合是指在新类里面创建原有类的对象,重复利用原有类的功能
继承是利用其他类的实现来定义一个类的实现,子类重用父类的方法或成员变量
组合和继承的区别:
组合和继承都允许在新的类中设置子对象,只是组合是显式的设置,而继承是隐式的设置
组合和继承存在着对应关系:组合中的整体类对应继承中的父类,组合中的部分类对应继承中的子类
组合和继承两者都可以实现代码复用,那么在实际使用时又该如何选择?一般情况应该遵循以下原则:
- 除非两个类是is-a的关系,否则不要轻易使用继承(过多使用继承会破坏代码的可维护性)
- 不要仅仅为了实现多态而使用继承,如果类之间没有is-a的关系可以通过实现接口与组合的方式来达到目的(策略模式)
为什么说在Java中能使用组合就不要使用继承?
由于Java只支持单继承,如果想要同时继承两个类或多个类,在Java中无法直接实现。
同时在Java中如果继承使用得太多,也会让class中的内容臃肿不堪,所有才会有在
Java中能使用组合就不要使用继承的说法
6、多态的实现机制是什么?
多态是什么?
多态是面向对象程序设计中代码重用的一个重要机制,它表示当同一个操作作用在不同对象时,
会有不同的语义,会产生不同的结果。例如同样是执行"+"操作,3+4是实现整数相加,
而"3" + "4"是实现字符串的连接
在Java中多态的实现方式:
方法的重载(overload):重载是指同一个类中有多个同名的方法,但这些方法有着不同的参数(个数和类型),
在编译时可以确定到底使用哪个方法,这是一种编译时多态,重载可以被看做一个类中的方法多态性
方法的覆盖(override):子类可以覆盖父类的方法并重写,因此同样的方法在会在父类和子类中有着
不同的表现形式。程序的调用方法在运行期才动态绑定,通过这种动态绑定的方法实现了多态。
由于只有在运行时才能确定调用哪个方法,因此通过方法覆盖实现类的多态也可以被称为运行时多态
什么是绑定:
在Java中,基类的引用变量不仅可以指向基类的实例对象,也可以指向其子类的实例对象。同样接口的
引用变量也可以指向其实现类的实例对象。程序的调用方法在运行期才动态绑定,绑定是指将一个方法调用
和方法主体连接到一起。
常见面试题 - Java中提供了哪两种用于多态的机制?
- 编译时多态:通过方法的重载实现
- 运行时多态:通过方法的覆盖(子类覆盖父类方法)实现
7、重载和覆盖有什么区别?
重载的定义:一个类定义了多个同名的方法,它们有不同的参数个数或不同的参数类型
覆盖的定义: 覆盖是指子类方法覆盖父类方法,覆盖一个方法并对其进行重写以达到不同作用
重载和覆盖的区别:
- 覆盖是子类和父类之间的关系(垂直关系);而重载是同一个类中不同方法之间的关系(水平关系)
- 覆盖只能对一个方法或只能由一对方法产生关系;而重载是多个方法之间的关系
- 覆盖要求参数列表相同;而重载要求参数列表不同
- 覆盖中调用方法是根据对象的类型来决定;而重载是根据调用时实参表和形参表选择方法
8、抽象类和接口有什么区别?
抽象类:如果一个类中包含抽象方法,那这个类就是抽象类。在Java中,可以通过把类
或类中的某些方法声明为abstract(只能修饰类或方法)表示一个类是抽象类
接口:接口是指方法的集合,接口中的所有方法都没有方法体,在Java中接口用interface实现
抽象类和接口的相同点:
- 代表系统的抽象层,都不能实例化,都能包含抽象方法
- 接口的实现类或抽象类的子类只有实现了所有的接口或所有的抽象方法后才能实例化
抽象类和接口的不同点:
构造方法:抽象类可以有构造方法,接口中不能有构造方法
变量:
- 普通变量:抽象类可以有普通成员变量,接口中没有普通成员变量,默认类型public static final
- 静态变量:抽象类中静态变量访问类型可以是任意的,接口中静态变量必须是public static final类型
方法:
- 静态方法:抽象类中可以包含静态方法,接口不能包含静态方法
- 抽象方法:抽象类中抽象方法访问类型可以是public,protected,default,接口抽象方法默认只能是public abstract
- 普通方法:抽象类中可以包含普通方法,接口中所有方法必须都是抽象的
继承性:一个类只能继承一个直接父类,但可以实现多个接口,使用接口可以间接地达到多重继承地目的
接口和抽象类的不同之处:
- 接口只有定义,其方法不能在接口中实现,而抽象类可以有定义和实现
- 接口需要实现(implements),而抽象类需要继承(extends),一个类可以实现多个接口,但一个类只能继承一个抽象类
- 接口强调特定功能的实现,设计理念是has-a关系,而抽象类强调所属关系,其设计理论为is-a关系
- 接口被运用于实现比较常用的功能,便于日后维护或添加删除方法,而抽象类更倾向于充当公共类的角色,不适合日后对其中的代码进行修改
9、内部类有哪些?
内部类与外部类:
在Java中可以把一个类定义到另一个类地内部,在类里面的这个类就叫做内部类,外面的类叫外部类
在这种情况下,这个内部类可以看作是外部类的一个成员(与类的属性和方法类似)
顶层类:
还有一种类被称为顶层类,指的是类定义代码不嵌套在其他类定义中的类
内部类分为以下四种:
- 静态内部类
- 成员内部类
- 局部内部类
- 匿名内部类
如下所示:
1 class outerClass{ 2 static class staticInnerClass{ 3 // 静态内部类 4 } 5 class innerClass{ 6 // 成员内部类 7 } 8 9 public void func(){ 10 class innerClassInFunc{ 11 // 局部内部类 12 } 13 } 14 } 15 16 public class MyFrame extends Frame{ 17 public MyFrame(){ 18 addWindowListener(new WindowAdapter(){ 19 // 匿名内部类 20 public void windowClosing(WindowEvent e){ 21 dispose; 22 System.exit(0); 23 } 24 }); 25 } 26 }
静态内部类:
静态内部类是指被声明为static的内部类,它可以不依赖于外部类实例而被实例化(通常的内部类都需要在外部类
实例化后才能实例化),静态内部类不能不能与外部类有相同的名字,不能访问外部类的普通成员变量,只能
访问外部类的普通成员变量,只能访问外部类中的静态成员和静态方法(包括私有类型)
成员内部类:
静态内部类去掉static关键字就是成员内部类,成员内部类可以自由引用外部类的属性和方法(静态和非静态都可以)
但是成员内部类和外部类的实例绑定在一起,只有在外部类被实例化后,这个内部类才可以被实例化
另外成员内部类是非静态内部类,是不能有静态成员的(不可以定义静态的属性和方法)
局部内部类:
局部内部类是定义在一个代码块中的类,作用范围为这个代码块中,局部内部类和局部变量一样
不能用public、protected、private及static修饰,只能访问方法中定义为final类型的局部变量
匿名内部类:
匿名内部类是一种没有类名的内部类,不使用关键字class、extends、implements,没有构造函数,
它必须继承其他类或实现其他接口。匿名类的好处就是代码更加简介,但使易读性下降
匿名内部类使用原则如下:
- 不能有构造函数
- 不能定义静态成员、方法和类
- 只能创建匿名内部类的一个实例
- 一个匿名内部类一定是在new的后面,必须继承一个父类或实现一个接口
- 匿名内部类是局部内部类的一种,局部内部类的所有限制都对其生效
对匿名内部类进一步认识:
1 /* 2 * 匿名内部类: 3 * 是一种特殊的语法,用来快速创建抽象类的子类对象以及快速创建接口的实现类对象 4 * 5 * 不用匿名内部类: 6 * 给你一个抽象类,要求创建该类的子类对象: 7 * 1.创建子类,继承抽象类 8 * 2.重写抽象类中的所有抽象方法 9 * 3.new 子类对象 10 * 给你一个接口,要求创建该接口的实现类对象: 11 * 1.创建实现类 implments 接口 12 * 2.重写接口中所有的抽象方法 13 * 3.new 实现类对象 14 * 15 * 使用匿名内部类: 16 * // 1 17 * new ClassName(){ // ClassName可以是接口名也可以是抽象类名 18 * @Override 19 * public void xxx(){ 20 * // 实现代码 21 * xxx 22 * } 23 * }; 24 * // 2 25 * new ClassName(){ // ClassName可以是接口名也可以是抽象类名 26 * @Override 27 * public void xxx(){ 28 * // 实现代码 29 * xxx 30 * } 31 * }.xxx(); 32 * // 3 33 * ClassName n = new ClassName(){ // ClassName可以是接口名也可以是抽象类名 34 * @Override 35 * public void xxx(){ 36 * // 实现代码 37 * xxx 38 * } 39 * }; 40 * n.xxx() 41 */
10、如何获取父类的类名?
Java提供了获取类名的方法:getClass().getName(),可以用这种方法来获取类名,实例如下:
1 public class test{ 2 public void test(){ 3 System.out.println(this.getClass().getName()); // test 4 } 5 public static void main(String[] args) { 6 new test().test(); 7 } 8 }
那么可不可以用这种方法获取父类的类名呢?尝试把this改成super:
1 class A{} 2 3 public class test extends A{ 4 public void test(){ 5 System.out.println(super.getClass().getName()); // test 6 } 7 public static void main(String[] args) { 8 new test().test(); 9 } 10 }
结果还是输出test,结果不是A的主要原因是Java任何类都继承自Object类,getClass方法在Object类中
被定义为final和native,子类不能覆盖该方法,因此this.getClass()和super.getClass()最终调用的都是
Object中的getClass方法,而Object终的getClass方法是返回此Object的运行时类,故返回的是test
那么如何在子类中得到父类的名字呢?可以通过Java的反射机制(this.getClass().getSuperclass().getName()):
1 class A{} 2 3 public class test extends A{ 4 public void test(){ 5 System.out.println(this.getClass().getSuperclass().getName()); // A 6 } 7 public static void main(String[] args) { 8 new test().test(); 9 } 10 }
11、this和super有什么区别?
this:指向当前实例对象,它的一个重要作用就是用来区分对象的成员变量与方法的形参(当一个方法的形参
和成员变量的名字一样时就会覆盖成员变量)
super:用来访问父类的方法和成员变量,当子类的方法或成员变量与父类有相同名字时也会覆盖父类的方法
或成员变量,要想访问父类的方法或成员变量只能通过super关键字,另外在子类构造函数中写super()是调用
父类构造函数,但是注意super()必须为构造函数中第一条语句
this和super的用法如下:
1 // this的用法 2 class People{ 3 String name; 4 public People(String name){ 5 // 准确的写法 6 this.name = name; 7 } 8 public People(String name){ 9 // 错误的写法 10 name = name; 11 } 12 } 13 14 // super的用法 15 class Base{ 16 public void f(){ 17 System.out.println("Base:f()"); 18 } 19 } 20 21 class Sub extends Base{ 22 public void f(){ 23 System.out.println("Sub:f()"); 24 } 25 public void subf(){ 26 f(); 27 } 28 public void basef(){ 29 super.f(); 30 } 31 } 32 33 public class test{ 34 public static void main(String[] args) { 35 Sub s = new Sub(); 36 s.subf(); // Sub:f() 37 s.basef(); // Base:f() 38 } 39 }