抽象类和接口

抽象类

在继承的层次结构中,随着每个新子类的出现,类会变得越来越来明确和具体.如果从一个子类追溯到父类,类就会变得更通用,更加不明确.类的设计应该确保父类包含他的子类的共同特征.有时候,一个父类设计得非常抽象,以至于他都没有任何具体的实例.这样的类称为抽象类
GeometricObject类定义成CircleRectangle类的父类.GeometricObject类模拟了几何对象的共同特征.Circle类和Rectangle类都包含分别计算圆和矩形的面积和周长的方法getArea()getPerimeter().因为可以计算所有几何对象的面积和周长,所以最好在GeometricObject类中定义getArea()getPerimeter()方法.但是,这些方法不能在GeometricObject类中实现,因为它们的实现取决于几何对象的具体类型.这样的方法称为抽象方法(abstract method),在方法头中使用abstract修饰符表示.在GeometricObject类中定义了这些方法后,GeometricObject就成为一个抽象类.在类头使用abstract修饰符表示该类为抽象类.
这里写图片描述

GeometricObject.java代码:
package chapter14;
public abstract class GeometricObject
{
    private String color = "white";
    private boolean filled;
    private java.util.Date dateCreated;

    /*Construct a default geometric object*/
    protected GeometricObject()
    {
        dateCreated = new java.util.Date();
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public boolean isFilled() {
        return filled;
    }

    public void setFilled(boolean filled) {
        this.filled = filled;
    }

    public java.util.Date getDateCreated() {
        return dateCreated;
    }

//  public void setDateCreated(java.util.Date dateCreated) {
//      this.dateCreated = dateCreated;
//  }

    //Construct a geometric object with color and filled value
    protected GeometricObject(String color,boolean filled)
    {
        dateCreated = new java.util.Date();
        this.color = color;
        this.filled = filled;
    }

    //Return a string representation of this object
    public String toString()
    {
        return "created on " +dateCreated + "\ncolor:" + color + " and filled: " + filled;
    }

    /*Abstract method getArea*/
    public abstract double getArea();

    /*Abstract method getPerimeter*/
    public abstract double getPerimeter();
}

抽象类和常规类很像,但是不能使用new操作符创建它的实例.抽象方法只有定义而没有实现.它的实现由子类提供.一个包含抽象方法的类必须声明为抽象类
抽象类的构造方法定义为protected,因为它只被子类使用.创建一个具体子类的实例时,他的父类的构造方法被调用以初始化父类定义的数据域.
抽象类GeometricObject为几何对象定义了共同特征(数据和方法),并且提供了正确的构造方法.因为不知道如何计算几何对象的面积和周长,所以,getAreagetPerimeter定义为抽象方法.这些方法在子类中实现.

Circle.java代码:
package chapter14;
public class Circle extends GeometricObject {
    private double radius;

    public Circle(){
    }

    public Circle(double radius){
        this.radius = radius;
    }

    public Circle(double radius,String color,boolean filled){
        this.radius = radius;
        setColor(color);
        setFilled(filled);
    }

    public double getRadius(){
        return radius;
    }

    public void setRadius(double radius){
        this.radius = radius;
    }

    public double getArea(){
        return radius * radius * Math.PI;
    }

    public double getDiameter(){
        return 2 * radius;
    }

    public double getPerimeter(){
        return 2 * radius * Math.PI;
    }

    public void printCircle(){
        System.out.println("The circle is created " + getDateCreated() + " and the radius is " + radius);
    }
}
Rectangle.java代码:
package chapter14;
public class Rectangle extends GeometricObject{
    private double width;
    private double height;

    public Rectangle(){
    }

    public Rectangle(double width,double height){
        this.width = width;
        this.height = height;
    }

    public Rectangle(double width,double height,String color,boolean filled){
        this.width = width;
        this.height = height;
        setColor(color);
        setFilled(filled);
    }

    public double getWidth(){
        return width;
    }

    public void setWidth(double width){
        this.width = width;
    }

    public double getHeight(){
        return height;
    }

    public void setHeight(double height){
        this.height = height;
    }

    public double getArea(){
        return width * height;
    }

    public double getPerimeter(){
        return 2 * (width + height);
    }
}

为什么要用抽象方法

你可能会疑惑在GeometricObject类中定义方法getAreagetPerimeter为抽象的而不是在每个子类中定义它们会有什么好处.下面的例子就能看出在GeometricObject中定义它们的好处.
例子创建了两个几何对象:一个圆和一个矩形,调用equalArea方法来检查它们的面积是否相同,然后调用displayGeometricObject方法来显示它们.

TestGeometricObject.java代码:
package chapter14;
public class TestGeometricObject
{
    public static void main(String[] args)
    {
        //Create two geometric objects
        GeometricObject geoObject1 = new Circle(5);
        GeometricObject geoObject2 = new Rectangle(5,3);

        System.out.println("The two objects have the same area? " + equalArea(geoObject1,geoObject2));

        //Display circle
        displayGeometricObject(geoObject1);

        //Display rectangle
        displayGeometricObject(geoObject2);
    }

    /*A method for comparing the areas of two geometric objects*/
    public static boolean equalArea(GeometricObject object1,GeometricObject object2)
    {
        return object1.getArea() == object2.getArea();
    }

    /*A method for displaying a geometric object*/
    public static void displayGeometricObject(GeometricObject object)
    {
        System.out.println();
        System.out.println("The area is " + object.getArea());
        System.out.println("The perimeter is " + object.getPerimeter());
    }
}

Circle类和Rectangle类中覆盖了定义在GeometricObject类中的getArea()getPerimeter()方法.

GeometricObject geoObject1 = new Circle(5);
GeometricObject geoObject2 = new Rectangle(5,3);

创建了一个 新圆和一个新矩形,并把它们赋值给变量geoObject1geoObject2.这两个变量都是GeometricObject类型的
当调用equalArea(geoObject1,geoObject2)时,由于geoObject1是一个圆,所以object1.getArea()使用的是Circle类定义的getArea()方法,而geoObject2是一个矩形,所以object2.getArea()使用的是Rectangle类的getArea()方法.
类似的,当调用displayGeometricObject(geoObject1)时,使用Circle类中定义的getAreagetPerimeter方法,而当调用displayGeometricObject(geoObject2)时,使用的是在Rectangle类中定义的getAreagetPerimeter方法.JVM在运行时根据对象的类型动态的决定调用那一个方法.

关于抽象类的几个关注点

1.抽象方法不能包含在非抽象类中.如果抽象父类的子类不能实现所有的抽象方法,那么子类也必须定义为抽象的.换句话说,在抽象类扩展的非抽象子类中,必须实现所有的抽象方法.还要注意到,抽象方法是非静态的.
2.抽象类是不能使用new操作符来初始化.但是,仍然可以定义它的构造方法,这个构造方法在它的子类的构造方法中调用.例如.GeometricObject类的构造方法在Circle类和Rectangle类中调用
3.包含抽象对象的类必须是抽象的.但是,可以定义一个不包含抽象方法的抽象类.在这种情况下.不能使用new操作符创建该类的实例.这种类是用来定义新子类的基类的.
4.即使子类的父类是具体的,这个子类也可以是抽象的.例如.Object类是具体的,但是它的子类如GeometricObject可以是抽象的
5.子类可以覆盖父类的方法并将它定义为abstract.这是很少见的,但是它在当父类的方法实现在子类中变得不合法时是很有用的.在这种情况下,子类必须定义为abstract.
6.不能使用new操作符从一个抽象类创建一个实例.但是抽象类可以用作一种数据类型.因此,下面的语句是创建一个GeometricObjetc实例的数组,这个语句是正确的:

GeometricObject[] objects = new GeometricObject[10];

然后可以创建一个GeometricObject的实例,并将它的引用赋值给数组,如下所示:

objects[0] = new Circle();

举例:日历类Calendar和公历类GregorianCalender

一个java.util.Date的实例表示以毫秒为单位的特定时间段.java.util.Calendar是一个抽象的基类,可以提取出详细的日历信息,例如,年,月,日,小时,分钟和秒.Calendar类的子类可以实现特定
日历系统.例如,公历(Gregorian历),阴历和犹太历.目前,Java支持公历类java.util.GregorianCalendar.
这里写图片描述
Calendar类的域常量
这里写图片描述
下面的例子显示当前时间的日期和时间信息

TestCalendar.java代码:
package chapter14;
import java.util.*;
public class TestCalendar {
  public static void main(String[] args) {
    // Construct a Gregorian calendar for the current date and time
    Calendar calendar = new GregorianCalendar();
    System.out.println("Current time is " + new Date());
    System.out.println("YEAR:\t" + calendar.get(Calendar.YEAR));
    System.out.println("MONTH:\t" + calendar.get(Calendar.MONTH));
    System.out.println("DATE:\t" + calendar.get(Calendar.DATE));
    System.out.println("HOUR:\t" + calendar.get(Calendar.HOUR));
    System.out.println("HOUR_OF_DAY:\t" + 
      calendar.get(Calendar.HOUR_OF_DAY));
    System.out.println("MINUTE:\t" + calendar.get(Calendar.MINUTE));
    System.out.println("SECOND:\t" + calendar.get(Calendar.SECOND));
    System.out.println("DAY_OF_WEEK:\t" + 
      calendar.get(Calendar.DAY_OF_WEEK));
    System.out.println("DAY_OF_MONTH:\t" + 
      calendar.get(Calendar.DAY_OF_MONTH));
    System.out.println("DAY_OF_YEAR: " + 
      calendar.get(Calendar.DAY_OF_YEAR));
    System.out.println("WEEK_OF_MONTH: " + 
      calendar.get(Calendar.WEEK_OF_MONTH));
    System.out.println("WEEK_OF_YEAR: " + 
      calendar.get(Calendar.WEEK_OF_YEAR));
    System.out.println("AM_PM: " + calendar.get(Calendar.AM_PM));

    // Construct a calendar for September 11, 2001
    Calendar calendar1 = new GregorianCalendar(2001, 8, 11);
    System.out.println("September 11, 2001 is a " + 
      dayNameOfWeek(calendar1.get(Calendar.DAY_OF_WEEK)));
  }

  public static String dayNameOfWeek(int dayOfWeek) {
    switch (dayOfWeek) {
      case 1: return "Sunday";
      case 2: return "Monday";
      case 3: return "Tuesday";
      case 4: return "Wednesday";
      case 5: return "Thursday";
      case 6: return "Friday";
      case 7: return "Saturday";
      default: return null;
    }
  }
}

接口

接口(interface)是一种与类相似的结构,只包含常量与抽象方法.接口在许多方面都与抽象类很相似,但是它的目的是指明多个对象的共同行为.例如,使用正确的接口,可以指明这些对象是可比较的,可食用的或可克隆的.
下面是一个接口的例子:

public interface Edible
{
    /*Describe how to eat*/
    public abstract String howToEat();
}

在JAVA中,接口被看做是一种特殊的类.就像常规类一样,每个接口都被编译为独立的字节码文件.与抽象类相似,不能使用new操作符创建接口的实例,但是在大多数情况下,使用接口或多或少有点像使用抽象类.例如,可以使用接口作为引用变量的数据类型或类型转换的结果等.
现在,可以使用Edible接口来明确一个对象是否是可食用的.这需要使用implements关键字让对像的类实现这个接口来完成.类和接口之间的关系称为接口继承(interface inheritance)

TestEdible.java代码:
package chapter14;
public class TestEdible
{
    public static void main(String[] args)
    {
        Object[] objects = {new Tiger(),new Chicken(),new Apple()};
        for(int i = 0; i < objects.length; i++)
            if(objects[i] instanceof Edible)
                System.out.println(((Edible)objects[i]).howToEat());
    }
}

class Animal
{
    //Data fields,construction,and methods omitted here
}

class Chicken extends Animal implements Edible
{
    public String howToEat()
    {
        return "Chicken: Fry it";
    }
}

class Tiger extends Animal
{

}

abstract class Fruit implements Edible
{
    //Data fields,constructs,and methods omitted here
}

class Apple extends Fruit
{
    public String howToEat()
    {
        return "Apple: Make apple cider";
    }
}

class Orange extends Fruit
{
    public String howToEat()
    {
        return "Orange: Make orange juice";
    }
}

这里写图片描述
Chicken类扩展子Animal类,并实现Edible以表明小鸡是可食用的.当一个类实现接口时,该类实现了定义在接口中的所有带确切签名和返回类型的方法.Chicken类实现了howToEat方法.
Fruit类实现Edible。因为它不实现howToEat方法,所以Fruit必须表示为abstract.Fruit的具体子类必须实现howToEat方法.Apple类和Orange类实现howToEat方法.
main方法创建由Tiger,Chicken和Apple的三个对象构成的数组,如果这个元素是可食用的,调用howToEat方法.
这里写图片描述

Comparable接口

comparable接口的定义如下所示:

//Interface for comparing objects,defined in java.lang
package java.lang;
public interface Comparable
{
    public int compareTo(Object o);
}

compareTo方法判断这个对象相对于给定对象o的顺序,并且当这个对象小于,等于或大于给定对象o时,分别返回负整数,0和正整数.
Java类库中的许多类(例如,String和Date类)实现了Comparable接口以定义对象的自然顺序,如果你检查这些类的源代码,就会发现这些类中使用的关键字implements,如下所示:
这里写图片描述
从两个对象中找出最大者的通用方法max可以定义为如图a或图b所示:
这里写图片描述

ComparableRectangle.java代码:
package chapter14;
public class ComparableRectangle extends Rectangle implements Comparable<Object>
{
    /*Construct  a ComparableRectangle with specified properties*/
    public ComparableRectangle(double width,double height)
    {
        super(width,height);
    }

    /*Implement the compareTo method defined in Comparable*/
    public int compareTo(Object o)
    {
        if(getArea() > ((ComparableRectangle)o).getArea())
            return 1;
        else if(getArea() < ((ComparableRectangle)o).getArea())
            return -1;
        else
            return 0;
    }
}

ComparableRectangle类扩展自Rectangle类并实现Comparable方法,关键字implements表示ComparableRectangle类继承Comparable接口的所有常量,并实现该接口的方法.CompareTo方法比较两个矩形的面积.ComparableRectangle类的一个实例也是Rectangle,GeometricObject,ObjectComparable的实例.
这里写图片描述
现在,可以使用max方法找出两个ComparableRectangle对象中较大的一个,请看下面的例子:

ComparableRectangle rectangle1 = new ComparableRectangle(4,5);
ComparableRectangle rectangle2 = new ComparableRectangle(3,6);
System.out.println(Max.max(rectangle1,rectangle2));

ActionListener接口

按钮就是动作来源的源对象(source object).需要创建一个对象能够处理按钮上的动作事件.这个对象称为监听器(listener),
不是所有的对象都能成为动作事件的监听器.一个对象要成为源对象上动作事件的监听器,需要满足两个条件:
1.这个对象必须是ActionListener(事件监听器)接口的一个实例.该接口定义了所有动作监听器共有的动作.
2.ActionListener对象listener必须使用方法source.addActionListener(listener)注册给源对象.
这里写图片描述
ActionListener接口包含处理事件的actionPerformed方法.监听器必须覆盖该方法来响应事件.

HandleEvent.java代码:
package chapter14;
import javax.swing.*;
import java.awt.event.*;
public class HandleEvent extends JFrame
{
    private static final long serialVersionUID = 1L;
    public HandleEvent()
    {
        //Create two buttons
        JButton jbtOK = new JButton("OK");
        JButton jbtCancel = new JButton("Canel");

        //Create a panel to hold buttons
        JPanel panel = new JPanel();
        panel.add(jbtOK);
        panel.add(jbtCancel);

        add(panel);//Add panel to the frame

        //Register listeners
        OKListenerClass listener1 = new OKListenerClass();
        CancelListenerClass listener2 = new CancelListenerClass();
        jbtOK.addActionListener(listener1);
        jbtCancel.addActionListener(listener2);
    }
    public static void main(String[] args)
    {
        JFrame frame = new HandleEvent();
        frame.setTitle("Handle Event");
        frame.setSize(200,150);
        frame.setLocation(200, 100);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}

class OKListenerClass implements ActionListener
{
    public void actionPerformed(ActionEvent e)
    {
        System.out.println("OK button clicked");
    }
}

class CancelListenerClass implements ActionListener
{
    public void actionPerformed(ActionEvent e)
    {
        System.out.println("Cancel button clicked");
    }
}

Cloneable接口

接口包括常量和抽象方法,但是Cloneable接口是一个特殊情况.在java.lang包中的Cloneable接口的定义如下所示:

package java.lange;
public interface Cloneable
{
{

这个接口是空的.一个带空体的接口称为标记接口(marker interface).一个标记接口即不包括常量也不包括方法.它用来表示一个类拥有某些特定的属性.实现Cloneable接口的类标记为可克隆的,而且他的对象可以使用在Object类中定义的clone()方法克隆.
Java库中的很多类(例如,Date,CalendarArrayList)实现Cloneable.这样,这些类的实例可以被克隆.例如,下面的代码:

Calendar calendar = new GregorianCalendar(2003,2,1);
Calendar calendar1 = calendar;
Calendar calendar2 = (Calendar)calendar.clone();
System.out.println("calendar == calendar1 is " + (calendar == calendar1));
System.out.println("calendar == calendar2 is " + (calendar == calendar2));
System.out.println("calendar.equals(calendar2) is " + calendar.equals(calendar2));
//显示
calendar == calendar1 is true
calendar == calendar2 is
false
calendar.equals(calendar2) is true

可以使用clone方法克隆一个数组.例如,下面的代码

int[] list1 = {1,2};
int[] list2 = list1.clone();
list1[0] = 7;list[1] = 8;
System.out.println("list1 is " + list1[0] + ", " + list1[1]);
System.out.println("list2 is " + list2[0] + ", " + list2[1]);
//显示
list1 is 7,8
list2 is 1,2

为了定义一个自定制类来实现Cloneable接口,这个类必须覆盖Object类中的clone()方法.下面代码定义了一个实现Cloneable和Comparable的名为House的类

House.java代码:
package chapter14;

public class House implements Cloneable,Comparable<Object>
{
    private int id;
    private double area;
    private java.util.Date whenBuilt;

    public House(int id,double area)
    {
        this.id = id;
        this.area = area;
        whenBuilt = new java.util.Date();
    }

    public int getId() {
        return id;
    }

//  public void setId(int id) {
//      this.id = id;
//  }

    public double getArea() {
        return area;
    }

//  public void setArea(double area) {
//      this.area = area;
//  }

    public java.util.Date getWhenBuilt() {
        return whenBuilt;
    }

//  public void setWhenBuilt(java.util.Date whenBuilt) {
//      this.whenBuilt = whenBuilt;
//  }

    //Override the protected clone method defined in the object
    //class,and strength its accessibility
    public Object clone() throws CloneNotSupportedException
    {
        return super.clone();
    }

    //Implement the compareTo method defined in Comparable
    public int compareTo(Object o)
    {
        if(area > ((House)o).area)
            return 1;
        else if(area < ((House)o).area)
            return -1;
        else
            return 0;
    }
}

House类实现在Object类中定义的clone方法.方法头是:

protected native Object clone() throws CloneNoSupportedException;

关键字native表明这个方法不适用java写的,但它是JVM针对自身平台实现的,关键字protected限定方法只能在同一包内或其他子类中访问.由于这个原因,House类必须覆盖该方法并将它的可视性修饰符改为public,这样,该方法就可以在任何一个包中使用。因为Object类中针对自身平台实现的clone方法完成了克隆对象的任务,所以,在House类中 的clone方法只要调用super.clone()即可.在Object类中定义的clone方法会抛出CloneNotSupportedException异常
House类实现定义在Comparable接口中的compareTo方法.该方法比较两个房子的面积.
现在,可以创建一个House类的对象,然后从这个对象创建一个完全一样的拷贝,如下所示:

House house1 = new House(1,1750,50);
House house2 = (House)house1.clone();

house1和house2是两个内容相同的不同对象.Object类中的clone方法将原始对象的每个数据域复制给目标对象。如果一个数据域是基本类型,复制的就是它的值。例如area(double类型)的值从house1复制到house2.如果一个数据域是对象,复制的就是该域的引用.例如,域whenBuilt是Date类,所以,它的引用被复制给house2.因此,尽管house1==house2为假,但是house1.whenBuilt==house2.whenBuilt为真。这称为浅复制(shallow copy)而不是深复制(deep copy)这意味着如果数据域是对象类型,那么复制的是对象的引用,而不是它的内容.
这里写图片描述
这里写图片描述

抽象类与接口的比较

这里写图片描述
参考链接:
http://www.importnew.com/12399.html
http://www.cnblogs.com/dolphin0520/p/3811437.html
http://yinny.iteye.com/blog/1152430

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值