您的位置:首页 > 职场人生

抽象类接口和对象继承面试要点

2016-09-19 20:38 218 查看


抽象类

 含有抽象(abstract关键字)方法的类即为抽象类。(抽象类可以不含抽象方法,但是这样抽象类本身意义就很小了)

抽象类和普通类的区别:
 
1. 抽象方法必须为public,protected(private不能被继承)

  2. 抽象类不能创建对象

  3. 如果一个类为抽象类的子类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则子类也必须为抽象类。
 

 

接口
Java设计的接口(使用interface关键字)初衷:对行为的抽象。

  

   接口中可以含有方法和变量

   a. 接口中的变量:缺省且必须为public static final

   b. 接口中的方法:缺省且必须为public abstract

抽象类和接口理解和应用

定义一个Person抽象类

1. 子类Boy

2. 子类Girl

public abstract class Person {
private boolean sex;  		//性别
abstract public void run();     //跑方法
}

class Boy extends Person{
@Override
public void run() {
System.out.println("Boy run");
}

public boolean getSex(){
return true;
}
}

class Girl extends Person{
@Override
public void run() {
System.out.println("Girl run");
}

public boolean getSex(){
return false;
}
}


定义一个Dog抽象类

1. 子类Bomex

2. 子类Shiba

public abstract class Dog {
public String type;		//狗的种类
abstract public void run();     //跑方法
}

class Bomex extends Dog{
public void run() {
System.out.println("Bomex run");
}
public String getType(){
return "Bomex";
}
}

class Shiba extends Dog{
public void run() {
System.out.println("Shiba run");
}
public String getType(){
return "Shiba";
}
}

我们注意到:

在属性上,我们把男孩类和女孩类做了一个再抽象形成了Person抽象类。
                                            把博美类和柴犬类做了一个再抽象形成了Dog抽象类。
                                   
                在方法上,这几个类都拥有run()这个属性。即无论是是人还是小狗都会跑,这样就会存在大量的代码复写。

               我们可以这样理解: 抽象类是对类的再抽象,决定一个对象“是不是”。
                                                                1.  即小明是不是男孩,男孩是不是人。
                                                                2.  我家旺旺是不是博美,博美是不是狗。
                                                    接口是对行为,对能力的抽象,决定一个对象"会不会"。
1.  即小明会跑
2.  旺旺也会跑
这样的话,可以建立一个run接口,无论是Person类、还是Dog类都实现该接口,从而实现其中run方法。

将代码重写:

新建一个Run接口,建立抽象方法run()

public interface Run {
abstract public void run();
}


Person改写为:
public abstract class Person implements Run{
private boolean sex;  			//性别
}

class Boy extends Person{
@Override
public void run() {
System.out.println("Boy run");
}

public boolean getSex(){
return true;
}
}

class Girl extends Person{
@Override
public void run() {
System.out.println("Girl run");
}

public boolean getSex(){
return false;
}
}

Dog类改写为:

public abstract class Dog implements Run{
public String type;				//狗的种类
}

class Bomex extends Dog{
@Override
public void run() {
System.out.println("Bomex run");
}

public String getType(){
return "Bomex";
}
}

class Shiba extends Dog{
public void run() {
System.out.println("Shiba run");
}
public String getType(){
return "Shiba";
}
}


可以看出来,这样的改写,方便了对行为的管理。

接口和抽象类相同和区别

相同:

1.  接口和抽象类都不能被实例化

2.  接口和抽象类都可以包含抽象方法

区别:

在设计目的上:  接口作为系统与外界交互的窗口,体现的是一种规范。

对于设计实现者,接口规定了实现者必须向外提供哪些服务。
对于接口的调用者,接口规定了调用者可以调用那些服务。
对于多个程序而且,接口是多个程序之间的通信标准。
 
    抽象类为多个子类的父类,体现的是一种模板式设计。
对于抽象类中的方法,是系统实现过程中的中间产品,这个中间产品需要经过子类的再完善,才能成为最终产品。

在使用上: 

接口只能有抽象方法和静态变量
抽象类可以作为普通类使用,只是不能生成对象

类和对象

1. 如果用户没有自定义构造器,则系统会自动创建一个static属性的无参构造器。

2. 在实例化一个对象的过程中

a.  首先Java执行引擎会寻找是否已加载这个类,如果未加载,则先加载再生成对象。已加载,则直接生成对象

b.  在类加载的过程中,如果有static变量和static静态代码块,会先加载static属性。(按上下顺序)

c.  在生成类对象过程中,会先初始化类对象的所有成员变量(在程序的任何位置),再执行构造器

构造器完全负责对象的创建?

   在实例化一个对象的过程中:

1. 首先系统为该对象分配内存空间,并为对象执行默认的初始化操作。此时对象已经产生了,只是外部还不能使用

2. 再调用构造器方法(注意我们常在构造器内使用this关键字,说明此时对象已经建立),调用完构造器后return出该类的实例化对象

   3. 对于this关键字可以简单的理解和super关键字用法类似,this总是指向到调用该方法的对象,而super指向this指向对象的父对象。(java在创建一个子类对象,会隐式的创建该类的父类的对象,this指向子类对象,super指向父类对象,注意在子类构造器中调用父类构造器,使用super关键字需要放在第一句,同理,在一个子类构造器中调用另一个构造器使用this关键字也需要放在第一句

继承

1.  一个子类只能继承一个父类,一个父类可以有多个子类

2.  子类能够访问父类的非pirvate变量和方法

3.  子类和父类之间变量和方法存在隐藏和覆盖的关系

                                a. 如果子类中出现和父类同名的成员变量,则子类成员变量会屏蔽父类的成员变量(隐藏现象), 如果要调用父类的成员变量,则需要使用super关键字。

   b. 如果子类中出现和父类同名的成员方法,则子类成员方法会覆盖父类的成员方法的,使用super调用

   c. 如果子类和父类有同名的静态方法,则会产生隐藏现象

(子类和父类同名现象会在多态上体现的更明显)

实际代码应用

对于子类和父类构造器等static等调用顺序

1.  我们假设系统没有载入子类和父类,需要实例化一个子类对象

2.   理论上过程应该如下:

  a.在加载子类前,会先加载父类,故先考虑父类

1)  先初始化父类里的static变量和方法

2)  初始化父类里的成员变量

3)  调用父类的构造方法

    b. 父类加载完毕后,会加载子类,考虑子类

1)  先初始化子类里的static变量和方法

2)  初始化子类里的成员变量

3)  调用子类的构造方法

示例代码

package org.delphi.fan;
public class test {

public static void main(String[] args) {
new Student();
}
}

class Eat{
public Eat(String person){
System.out.println(person+" eat constructer.");
}
}

class Student extends Person{
Eat  eat = new Eat("student");
public Student(){
System.out.println("student constructer.");
}
static{
System.out.println("student static code.");
}
}

class Person{
Eat  eat = new Eat("person");
public Person(){
System.out.println("person constructer.");
}
static{
System.out.println("person static code.");
}
}

理论输出为:

person static code.
person eat constructer.
person constructer.
student static code.
student eat constructer.
student constructer.


实际输出为:

person static code.
student static code.
person eat constructer.
person constructer.
student eat constructer.
student constructer.


为什么会出现这个结果,是因为JVM的加载过程中,先考虑静态变量(代码块),再考虑继承关系.

     对于有static属性的继承关系内,Java创建对象的完整过程为

1. 分配父类静态变量(静态代码块)

2. 分配子类静态变量(静态代码块)

3. 加载父类成员变量  -->  调用父类构造器 (父类对象创建完毕)

4. 加载子类成员变量  -->  调用子类构造器 (子类对象创建完毕)

对于子类和父类隐藏和覆盖的解释

1.  我们假设系统使用子类构造方法实例化一个父类对象,并调用父类和子类中同名的静态方法、成员方法和变量

2.   理论上过程应该如下:

1)  调用父类的构造方法

2)  调用子类的构造方法

3)  调用父类对象同名成员属性和静态成员方法时:存在隐藏现象(使用父类属性)

4) 调用父类对象同名方法时:存在覆盖现象(使用父类属性)

示例代码

package org.delphi.fan;

public class test {
public static void main(String[] args) {
Person person = new Student();
System.out.println("name:"+person.name);
person.eat();
person.fight();
}
}

class Student extends Person{
public String name="STUDENT";
public Student(){
System.out.println("student constructer.");
}

public void eat(){
System.out.println("student eat.");
}
public static void fight(){
System.out.println("student  static fight.");
}
}

class Person{
public String name="PERSON";
public Person(){
System.out.println("person constructer.");
}
public void eat(){
System.out.println("person eat.");
}
public static void fight(){
System.out.println("person static fight.");
}

}


理论输出为:

person constructer.
student constructer.
name:PERSON
student eat.
person static fight.


实际输出为:

person constructer.
student constructer.
name:PERSON
student eat.
person static fight.


多态

Java引用变量有两个类型:一个是编译时的类型(声明该变量时使用的类型),一个是运行时的类型(实例化赋给该变量的对象类型)。

对于向上转型:

       1.  引用变量只能调用声明该变量时所用类里包含的方法。

2.  引用方法会被子类(实例声明)覆盖,即方法行为总是子类方法行为

3.  而对象的属性则不具备多态性,即保持父类(编译声明)的变量属性

对于向下转型:

保证程序不会报错,使用

if(对象 instanceof  类名){//对象必须为子类或者父类的对象 否则返回false

子类名  子对象 = (子类名)父对象 ;          

}

对于多态的作用,可以这么理解:

问题:现在我们需要喝水,对此实现方案,是编写一个水类,含有喝水drink方法。

class Water{
public void drink();
}


因为水有很多种,举个例子: 农夫山泉, 并且其有一个特殊的要求,就是喝前需要摇一摇。

 实现代码可以为:
class NongFuSQ extends Water{
public void drink(){
yaoyiyao();  		//NongFuSQ需要摇一摇
}
public void yaoyiyao(){
System.out.println("yaoyiyao!");
}
}

此时我们要喝水可以用这样代码实现:
NongFuSQ water = new NongFuSQ();
water.drink();



现在如果换了一种水,例如:哇哈哈和康师傅,他们的喝前需要的操作各自不同
class WaHaHa extends Water{
public void drink(){
addSuger();		     //WaHaHa需要加糖操作
}
public void addSuger(){
System.out.println("addSuger!");
}
}

calss KangShiFu extends Water{
public void drink(){
addNode();	//KangShiFu需要和面条一起
}
public void addNode(){
System.out.println("addNode!");
}
}



此时如果我要喝其他的水,则喝水代码要改成:
//如果是哇哈哈
WaHaHa water = new WaHaHa();
water.drink();
</pre><pre name="code" class="java" style="font-size: 14px; font-weight: bold;">//如果是康师傅
KangShiFu water = new KangShiFu();
water.drink();

只要我一换水, 就需要改写代码,如果我在多处使用water对象,且使用了大量water包含的方法(方法中可能还包括一些子类特有的方法)。

随着代码量的增大,则改动的代码也会更大。不利于代码维护。

而从问题的实际角度来想,我只是想喝水,我不管你给我的是什么水,你的水是什么牌子,是怎么弄过来的。

此时可以用多态这个概念来实现:以后我需要换水的时候,只需要改变water实例化的对象。
Water water = new WaHaHa();
water.drink();  <span>		</span>//喝水只需要调用一个water就可以


在这种情况下,water调用的方法都是由Water类管理。[b]water对象只可以调用[b]Water类定义好的方法。[/b][/b]
在这样的情况下,如果我向换一种水喝,只需要实现新的子类,然后更改一下  Water water = new XXXX();  不用考虑后面的代码是否有异常,也不需要代码功能是否完善(因为只要不是父类有的方法,后面压根就不能调用)。

面向对象三大特征:

封装

1.  隐藏类的实现细节,不允许外部直接访问,便于修改和增加控制逻辑,限制对属性不合理的访问。
 2.  把方法暴露出来,让方法来操作或访问这些属性。

 继承

提高了代码复用,有利于代码的维护。

 多态

增加代码的健壮性。

==和equals比较运算符

==在比较普通类型  只要值相同即可,  在比较两个引用变量时候,两个引用变量必须指向同一对象
      equals是Object类中的一个方法,在String类中,可以理解为判断两个引用变量对象的字符串序列是否相同,在其他类中也是判断是否指向同一对象,如果需要做特殊判定,可以重写该方法。

常用的比较可靠的equals代码复写(使用反射机制)
public  class Person {
private String id;

public String getID(){
return id;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
//
if (obj!=null && obj.getClass()==Person.class) {
Person per = (Person)obj;
if (this.getID().equals(per.getID())) {  //判断对象ID属性是否相同
return true;
}
}

return false;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐