2.1 面向对象与面向过程有什么区别?
区别:
- 出发点不同;
- 层次逻辑关系不同;
- 数据处理方式与控制程序方式不同;
- 分析设计与编码转换方式不同。
2.2 面向对象有哪些特性?
抽象、继承、封装和多态。
2.3 面向对象的开发方式有什么优点?
- 开发效率高。可以对现实的实物进行抽象;
- 保证软件鲁棒性。重用在相关领域经过长期测试的代码;
- 保证软件的高可维护性。成熟的设计模式。
2.4 什么是继承?
提高代码的复用性、提高开发效率。
class subclass extends superclass
特性:
- 子类最多有一个父类(单继承),但可通过实现多个接口达到多重继承。
- 子类只能继承父类的非私有成员变量和方法。
- 子类中定义的成员变量和方法(方法名和参数列表相同)和父类的成员变量和方法相同时,子类会覆盖父类的成员变量和方法。
package org.base;
class Father {
int i = 10;
public void setI(int i) {
this.i = i;
}
}
class Son extends Father {
int i = 10;
public void show() {
System.out.println("super.i : " + super.i + ";this.i : " + this.i);
}
}
public class TestExtends {
public static void main(String[] args) {
Son son = new Son();
System.out.println(son.i);
son.setI(100);
son.show();
System.out.println(son.i);
Father parent = son; // 基类指向派生类的对象
System.out.println(parent.i);
}
}
输出结果:10
super.i : 100;this.i : 10
10
100
2.5 组合和继承有什么区别?
组合和继承是代码复用的两种方式。
组合:在新类里面创建原有类的对象,重复利用已有类功能。
继承:根据其他类的实现来定义一个类的实现。
对应关系:组合中的整体类和继承中的子类对应,组合中的局部类和继承中的父类对应。
Car是Vehicle的一种,(是一种“is - a”关系);而Car包含多个Tire,是一种组合关系(“ has - a”关系)
// 继承
class Verhicle{
}
class Car extends Verhicle{
}
组合
class Tire{
}
class Verhicle{
}
class Car extends Verhicle{
}
注:
- 除非两个类之间是“is - a”的关系,否则不要轻易使用继承,不要为了实现代码重用性而使用继承,过多使用继承会破坏代码的可维护性,当父类被修改时,会影响到所有继承的子类,增加维护成本;
- 不要为了实现多态而使用继承,如果类之间没有“is - a”关系,可通过实现接口与组合方式。(策略模式很好的说明了这一点),接口和组合比继承具有更好的可扩展性。
2.6 多态的实现机制是什么?
多态标识同一个操作作用在不同对象时,会有不同的语义。例如:+ 整数相加或字符串连接。
- 方法的重载(Overload)。同一个类中多个同名的方法,但参数列表不同。因此在编译时就可以确定调用哪个方法,是一种编译时多态。
- 方法的覆盖(Override)。基类的引用变量不仅可以指向基类的实例对象,也可以指向其子类的实例对象。接口的引用变量也可以指向实现类的实例对象。而程序调用的方法在运行期才动态绑定(将一个方法调用和一个方法主体连接),也就是内存中正在运行的那个对象的方法。通过这种动态绑定的方法实现了多态。只有在运行时才能确定调用哪个方法,被称为运行时多态。
类中的方法才有多态,类中的成员变量没有多态。
Java中提供了哪两种用于多态的机制?
编译时多态和运行时多态。编译时多态通过方法的重载实现的,运行时多态是通过方法的覆盖(子类实现父类方法)实现的。
2.7 重载和覆盖有什么区别?
Overdide和Overload是Java多态性的不同表现。
重载:
- 重载通过不同的方法的参数来区分的,例如:不同的参数个数、不同的参数类型 、不同的参数顺序。
- 不能通过方法的访问权限、返回值类型和抛出的异常类型来进行重载。
- 对于继承来说,如果基类方法的访问权限为private,那么就不能再派生类中重载;如果派生类也定义了一个同名函数,这只是 一个新的方法,不会达到重载效果。相反,派生类中定义private 访问权限覆盖父类的方法,会导致 编译异常。
覆盖:
- 派生类中的覆盖方法必须要和基类中被覆盖的方法被覆盖的方法有相同的函数名和参数。
- 派生类中的覆盖方法的返回值必须和基类中被覆盖的方法返回值相同。
- 派生类中的覆盖方法所抛出的异常不能比基类中被覆盖方法所抛出的异常大。(派生类的异常 <= 基类异常)
- 基类中被覆盖的方法不能为private,否则只是定义了一个方法,未实现覆盖,派生类访问权限 >= 基类的访问权限。
第3点解释:
A a = new B();
a.test();
编译时调用 类A 中test(),抛出异常,
实际调用的是 类B 中的 test(),
如果类B中的 方法抛出了 更大的异常,这样就无法捕获了。
覆盖中 ,调用的方法体是根据对象的类型(对象对应存储空间类型)来决定;而重载是根据调用时的实参列表与形参列表来选择方法的。
public class polymorphic {
public static void main(String[] args) {
Super s = new SubClass();
System.out.println(s.f());
}
}
class Super{
public int f() {
return 0;
}
}
class SubClass extends Super{
public float f() {// 返回类型必须一致
return 2f;
}
}
运行结果:编译错误。
2.8 抽象类和接口有什么异同?
如果一个类中包含抽象方法,那么这个类就是抽象类。Java中,abstract用来修饰类或者方法,不能用来修饰属性。 接口就是指一个方法的集合,接口中所有方法都没有方法体。
抽象类和接口都支持抽象类定义的两种机制(前后两个抽象类的意义不一样,前者表实体,后者表概念)。
- 只要包含一个抽象方法的类就必须被声明为抽象类, 抽象类可以声明方法的存在而不去实现它(无方法体)。
- 在实现时,必须包含相同的或者更低的访问级别(public → protected → private)。
- 抽象类在使用过程中不能被实例化,但可以创建一个对象使其指向子类的一个实例。
- 子类继承抽象的父类中的所有抽象方法要提供具体的实现,否则他们也是抽象类。
- 接口(默认abstract修饰)可以看做抽象类的变体。接口中的所有方法都是抽象的(默认abstract修饰的),可以通过接口来间接地实现多继承。
- 接口中成员变量都是static final类型。由于 抽象类可以包含部分方法的实现,因此,在一些场合下抽象类比接口存在更多优势。
- Java8接口提供了default方法和静态方法。
package org.base;
public class polymorphic {
public static void main(String[] args) {
Super class1 = new SubClass();// 抽象类不可实例化,但可以实例化子类让父类指向子类
class1.f();
}
}
abstract class Super{
public abstract void f();
public abstract void m();
}
class SubClass extends Super{// 实现了父类中的所有抽象方法,否则声明此类为abstract
int a;// abstract不可修饰
public void f(){
}
@Override
public void m() {
}
}
接口与抽象类的相同点:
- 都不能被实例化。
- 接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能被实例化。
接口和抽象类的不同点:
- Java 8之前,接口只有定义,其方法不能在接口中实现,只有实现接口的类才能实现接口中定义的方法,而抽象类可以有定义与实现,即其方法可以在抽象类中被实现。
- 接口需要实现,但抽象类只能继承。一个类可以实现多个接口,但一个类只能继承一个抽象类,因此使用接口可以间接地达到多重继承的目的。
- 接口强调特定的功能实现,其设计理念是“ has - a ”关系;而抽象类强调所属关系,其设计理念为“ is - a ” 关系。
- 接口中成员变量默认public static final并要赋初值,其所有成员方法只能被这两个关键字修饰(public、abstract),而抽象类可以有自己的数据成员变量,也可以有非抽象成员方法,而且抽象类中的成员变量默认为default(本包可见)。抽象类中的抽象方法不能用private、static、synchronized、native修饰,方法必须以分号结尾,并且不带花括号。所以,当功能需要累积时,用抽象类;不需要累积时,用接口。
- 接口被运用于实现比较常用的功能,便于维护、添加和删除;而抽象类更倾向于充当公共类的角色,不适用于维护、修改。
关于接口的定义:
void m(); // 正确,默认abstract修饰
public double m(); // 正确
public final double m();// 接口中的方法只能用public 和abstract修饰
static void m(double d1); // 同上
protected void m(double d1);// 同上
int a; // 未初始化
int b = 1;// 正确 接口常量默认public static final类型
abstract interface Super{// 默认也是abstract接口
public static final int a = 0;// 默认也是public static final修饰,必须初始化
default void f(){// Java8新增
}
static void g(){// Java8新增
}
public abstract void m();// 默认也是abstract方法
}
class SubClass implements Super{
int a;
public void f(){
}
@Override
public void m() {
}
}
2.9 内部类有哪些?
Java中,可以把一个类定义到另外一个类的内部,在类里面的类叫内部类,外面的类叫外部内。嵌套类(C++的说法)与内部类(Java的说法)类似。
内部类分一下4种:
class outerClass{
static class innerClass{} // 静态内部类
}
静态内部类,可以不依赖于外部类实例而被实例化,而通常的内部类需要在外部类实例化后才能实例化。静态内部类不能与外部类有相同的名字,不能访问外部类的普通成员变量,只能访问外部类中的静态成员和静态方法。
class outerClass{
class innerClass{} // 成员内部类
}
成员内部类为非静态内部类,可以引用外部类的属性和方法,无论这些属性和方法时静态的还是非静态的。但他与一个实例绑定在了一起,不可以定义静态的属性和方法。只有在外部类被实例化后,内部类才能被实例化。
注意:非静态内部类中不能有静态成员。
class outerClass{
public void menberFunction(){ // 局部内部类
class innerClass{}
}
}
局部内部类指的是定义在一个代码块内的类,作用范围在代码块,内部类用的最少的一种类型。局部内部类像局部变量一样,不能被public、protected、private和static修饰,只能访问方法中定义为final类型的局部变量。
public class MyFrame extends Frame{ // 匿名内部类
public MyFrame(){
addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
dispose();
System.exit(0);
}
});
}
}
匿名内部类是一种没有类名的内部类,不能使用关键字class、extends、implements,没有构造函数,他必须继承其他类或实现其他接口。匿名内部类好处是代码简洁,但易读性差。一般应用于GUI。使用匿名内部类,原则如下:1.不能有构造函数;2.不能定义静态成员、方法和类;3.不能是public、protected、private、static;4.只能创建匿名内部类的一个实例;5.一个匿名内部类一定是在new的后面,这个匿名类必须继承一个父类或实现一个接口。6.因为匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。
public class OuterClass{
private int d1 = 1;
// 请填写内部类
}
A
class InnerClass{//非静态类内部中不能定义静态成员
public static int methoda(){return d1};
}
B
public class InnerClass{//非静态类内部中不能定义静态成员
static int methoda(){return d1};
}
C
private class InnerClass{
int methoda(){return d1};
}
D
static class InnerClass{// 静态内部类不能访问外部类的非静态成员
protected int methoda(){return d1};
}
E
abstract class InnerClass{
public abstract int methoda();
}
2.10 如何获取父类的类名
获取类名的方法:getClass().getName()
package org.base;
public class Test {
public void test() {
System.out.println(this.getClass().getName());
}
public static void main(String[] args) {
new Test().test();
}
}
运行结果:org.base.Test
通过调用父类的getClass().getName()能否获取到父类的类名?
不对。
package org.base;
public class Test extends Z{
public void test() {
System.out.println(super.getClass().getName());
}
public static void main(String[] args) {
new Test().test();
}
}
class Z{
}
运行结果:org.base.Test
为什么运行结果不是A呢?
因为Java语言中任何类都继承自Object类,getClass()方法在Object类中被定义为final与native,子类不能覆盖该方法。 因此this.getClass()和super.Class()最终都调用的是Object中的getClass()方法。而Object的getClass()方法的释义是:返回此Object的运行时类。
Object中的getClass()方法:
public final native Class<?> getClass();
怎么才能在子类中得到父类的名字?
通过Java的反射机制,使用getClass().getSuperclass().getName()
package org.base;
public class Test extends Z{
public void test() {
System.out.println(this.getClass().getSuperclass().getName());
}
public static void main(String[] args) {
new Test().test();
}
}
class Z{
}
运行结果:org.base.Z
2.11 this与super有什么区别 ?
Java中,this用来指向当前实例对象,区分对象的成员变量与方法的形参(当一个方法的形参与成员变量的名字相同时,就会覆盖成员变量)。super可以用来访问父类的方法或成员变量。 当子类的方法或成员变量与父类有相同名字时会覆盖父类的方法或成员变量,要向访问父类的方法或成员变量只能通过super关键字来访问。
package org.base;
public class Test extends O {
public static void main(String[] args) {
S s = new S();
s.subf();
s.basef();
}
}
class S extends O{
public void f(){
System.out.println("Sub:f()");
}
public void subf(){
f();
}
public void basef(){
super.f();
}
}
class O{
public void f(){
System.out.println("Base:f()");
}
}
运行结果:Sub:f()
Base:f()
package org.base;
public class Test extends O {
public static void main(String[] args) {
new S();
}
}
class S extends O{
public S(){
// super(); // 默认调用父类的构造器
System.out.println("Sub");
}
}
class O{
public O(){
System.out.println("Base");
}
}
运行结果:Base
Sub