海树

我心有猛虎 细嗅蔷薇香

Owen Lee's avatar Owen Lee

Java 笔试面试(2)面向对象

1. 面向对象与面向过程的区别

面向对象是把数据及对数据的操作方法放在一起,作为一个相互依存的整体。面向对象是用计算机来模拟客观世界中的物理存在,更加符合人的思维方式。

面向过程是自顶向下顺序执行,逐步求精的过程。

2. 面向对象的特征

  1. 抽象
  2. 继承:是一种联结类的层次模型,提供了一种明确的表述共性的方法。一个新类可以从现有的类派生,这个过程叫做继承。
  3. 封装:是指将客观事物抽象成类。
  4. 多态:是指允许不同类的对象对同一消息做出不同响应。

3. 继承

继承是面向对象中的一个重要特性,通过继承,子类可以使用父类中的一些变量和方法,从而能够提高代码的可复用性。

继承特性:

  1. Java不支持多重继承。子类只能有一个父类,但是可以通过实现多个接口来达到多重继承的目的。(接口是可以多重继承的
  2. 子类只能继承父类的非私有的变量和方法。
  3. 当子类中定义的成员变量与父类中定义的成员变量同名时,会覆盖,不会继承。
  4. 当子类中的函数与父类中的函数完全相同(相同的函数名、相同的参数、相同的返回类型)时,会覆盖父类的方法,不会继承。
  5. 当子类的方法覆盖父类中的方法时,子类方法的权限修饰符的范围必须大于等于父类方法的权限范围。

4. 组合&继承

组合是在新类中创建原有类的实例。
继承是根据其他类的实现来定义一个新的类。
组合是has-a的关系。继承是is-a的关系。

组合和继承都能够实现代码的重用。
选择原则:

  • 除非两个类之间是is-a的关系,否则不要轻易使用继承。
  • 不要仅仅为了多态而使用继承,如果类之间没有is-a的关系,可以通过实现接口和组合的方式达到相同目的。

能使用组合就尽量不要用继承。

5. 多态的实现机制

多态表示当同一个操作作用在不同的对象时,会有不同的语义,从而产生不同的结果。

两种实现多态的方式:

  1. 重载(水平的) 编译时多态
  2. 覆盖(垂直的) 运行时多态

子类可以覆盖父类的方法,因此相同的方法在子类和父类中有着不同的表现形式。Java中,基类的引用变量不仅可以指向基类的实例对象,还可以指向其子类的实例对象。同样接口的引用变量也可以指向其实现类的实例对象。而程序调用的方法是运行时才动态绑定的,即内存中那个实例对象的方法,而不是引用变量的类型所定义的方法。

class Base{
    public Base(){
        g();
    }
    public void f(){
        System.out.println("Base f()");
    }
    public void g(){
        System.out.println("Base g()");
    }
}
class Sub extends Base{
    public void f(){
        System.out.println("Sub f()");
    }
    public void g(){
        System.out.println("Sub g()");
    }
    public static void main(String [] args){
        Base a = new Sub();
        a.f();
        a.g();
    }
}

程序运行的结果为:
Sub g()
Sub f()
Sub g()
子类的f()和g()方法与父类中同名,会覆盖父类中的方法。在实例化Sub的对象时,会调用父类的构造函数,在构造函数中执行g()方法,由于多态,会调用子类的g()方法,而不是父类的g()方法。

只有类中的方法有多态,成员变量是没有多态的概念的。

class Base{
    public int i = 1;
}
class Sub extends Base{
    public int i = 2;
    public static void main(String [] args){
        Base a = new Sub();
        System.out.println("i = "+a.i);
    }
}

程序运行的结果为:
i = 1
成员变量是无法实现多态的,成员变量的值取父类还是子类并不取决于创建对象的类型,而是取决于所定义引用变量的类型,这是在编译期间确定的。

6. 重载&覆盖

重载:
重载是指在一个类中定义了多个同名的方法,它们有不同的参数个数或不同的参数类型。

  1. 重载是通过不同的参数来区分的(不同的参数个数,不同的参数类型,不同的参数顺序)
  2. 不能通过方法的访问权限、返回值类型和抛出的异常类型来进行重载。
  3. 对继承来说,如果基类方法是private,则不能再派生类中对其进行重载。如果在派生类中也定义了一个同名的方法,这个方法是一个新方法,不能达到重载的效果。

覆盖:
覆盖是指子类函数覆盖父类函数,以达到不同的作用。

  1. 必须有相同的函数名和参数。
  2. 返回值类型必须相同。
  3. 所抛出的异常要一致。
  4. 基类中被覆盖方法不能为private,否则子类只是定义了一个新的方法,没有对其进行覆盖。

7. 抽象类&接口

抽象类:
如果一个类中包含抽象方法(用abstract修饰方法),这个类就是抽象类(用abstract修饰这个类)。

接口:
接口是指一些方法的集合,接口中的方法都没有方法体(用interface表示接口)。

相同点:

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

不同点:

抽象类 接口
抽象类中可以有非抽象方法 接口中只有抽象方法
抽象类只能被继承(extends) 接口需要被实现(implements)
抽象类强调所属关系,是is-a的关系 接口强调特定功能的实现,是has-a的关系
抽象类可以有成员变量,权限默认为default,也可以定义成其他权限;这些成员变量可以在子类中被重新定义,也可以重新赋值 接口中的属性默认都为public static final,即静态不可变的,且必须要赋初值
抽象类中的抽象方法不能用private、static、synchronized、native等修饰 接口中方法默认为public abstract修饰
更多充当公共类,不适用日后重新对里面的代码进行修改 接口用于实现比较常用的功能,便于日后维护或者添加删除方法

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

8. 内部类

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

内部类主要有四种:

  1. 静态内部类
  2. 成员内部类
  3. 局部内部类
  4. 匿名内部类

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

class OutterClass{
    static class InnerClass{}
}

成员内部类可以自由使用外部类的成员变量和方法,无论是静态的还是非静态的。但是它与一个外部类的实例绑定在一起,不可以定义静态的变量和方法。只有在外部类被实例化后,该内部类才能被实例化。内部类的权限修饰符可以任意,甚至可以定义多个public的内部类。

class OutterClass{
    class InnerClass{}
}
class Test{
    public static void main(String [] main){
        OutterClass oc = new OutterClass();
        OutterClass.InnerClass ic = oc.new InnerClass();
    }
}

局部内部类时值定义在一个代码块中的内部类,它的作用范围是其所在的代码块。局部内部类像局部变量一样,不能被public、protected、private以及static所修饰,只能访问方法中定义为final的局部变量。

class OutterClass{
    public void f(){
        class InnerClass{}
    } 
}

匿名内部类是一种没有类名的内部类,不使用关键字class、extends、implements,没有构造函数,它必须继承其他类或实现其他接口。匿名内部类的好处是代码更加简洁紧凑,但带来的问题是代码易读性下降。它一般应用于GUI编程中实现事件处理等。

使用原则:

  1. 不能有构造函数
  2. 不能定义静态成员、方法和类。
  3. 不是public、protected、private、static
  4. 只能创建一个实例
  5. 一定是在new的后面,必须继承一个父类或实现一个接口
  6. 因为匿名内部类是局部内部类,所以局部内部类的所有限制都对其生效。
btn.setOnClickListener(new OnClickListener(){
    @Override
    public void onClick(View v){
        //按钮的点击事件
    }
});

9. this&super

this:指向当前类的实例对象
作用:

  1. 普通的直接引用,指向当前对象本身。
  2. 区别成员变量和方法的形参
class Person{
    String name;
    public Person(String name){
        this.name = name;
    }
}
  1. 在一个构造函数中调用另一个构造函数(注意:调用代码一定要写在构造函数的第一行)
    class Person{
     String name;
     public Person(){
         this("Tom");
         ...
     }
     public Person(String name){
         this.name = name;
     }
    }

super:是指向当前对象的父类的一个指针,而这个父类是离自己最近的一个父类。

用法:

  1. 普通的直接引用
    与this类似,super指向当前对象的父类,可以用super.XXX来引用父类的成员。

  2. 子类中的成员变量或方法与父类中的成员变量或方法同名,使用super调用父类中的方法。

  3. 调用父类的某一个构造函数(注意:调用语句要为构造函数中的第一条语句)

class Person{
    String name;
    public Person(){
        System.out.println("父类无参构造函数");
    }
    public Person(String name){
        this.name = name;
        System.out.println("父类有参构造函数");
    }
}
class Chinese extends Person{
    public Chinese(){
        //调用父类无参构造函数
        super();
        System.out.println("子类无参构造函数");
    }
    public Chinese(String name){
        //调用父类有参构造函数
        super(name);
        System.out.println("子类有参构造函数");
    }
}

this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。
从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。