抽象类和接口学习笔记
已经学习了如何编写简单的程序创建和现实GUI组件,你能编写代码以响应像点击一个按钮一样的用户动作吗?如下图所示,当点击一个按钮时,控制台上就会显示一条消息。
为了编写这样的代码,必修要很了解接口。借口就是定义多个类(特别是不相关的类)的共同行为。在学习接口之前,我来介绍一下相关主题:抽象类。
抽象类
和C++一样,抽象类就是包含抽象方法的类。为什么需要抽象方法呢,举一个例子来说。Circle和矩Rectangle都是Shape类的子类,Circle和Rectangle都有getArea()和getPerimeter()方法,但是所有的Shape都有这两种方法,所以最好的方式就是在Shape类中定义getArea()和getPerimeter()方法,但是这两个方法不能再Shape类中实现,因为它们的具体实现方式都取决于具体的形状,所以这样的方法成为抽象方法。在方法头中使用abstract修饰符即可,如:
public abstract class GeometricObject
{
public abstract double getArea();
public abstract double getPerimeter();
}
抽象类的构造方法一般被定义为protected,因为它只被子类使用。
关于抽象类的几个注意点:
1. 抽象方法不能包含在非抽象类中。如果抽象父类的子类不能实现所有的抽象方法,那子类也必须定义为抽象的。还要注意到,抽象方法是非静态的。
2. 抽象类是不能使用new操作符来初始化的,但是仍然可以定义它的构造方法,这个构造方法在它的子类的构造方法中调用。
3. 包含抽象对象的类必须是抽象的,但是也可以定义一个不包含抽象方法的抽象类。
4. 即使子类的父类的方法是具体的,子类也可以是抽象的。
5. 子类可以覆盖父类的方法并将它定义为abstract。
6. 不能使用new操作符从一个抽象类创建一个实例,但是抽象类可以作为一种数据类型
为什么要使用抽象类
举一个例子来说,人,兔子,老虎都属于动物,动物都有一系列的相同属性,比如都有眼睛鼻子耳朵嘴巴还有四肢,但是人,兔子,老虎吃的方式不一样,人要煮熟了吃,兔子慢慢啃,老虎用咬的。于是,我们定义一个动物类(定义为抽象类),人,兔子,老虎为其派生类,其他的属性因为都一样,我们只要为派生类”定制“它们的特征方法就行了,这样书写代码简单而又高效。
接口
接口是一种与类相似的结构, 只包含常量和抽象方法。接口在很多地方都与抽象类相似,但是它的目的是指明多个对象的共同行为。一个类要继承一个接口的时候需要用关键字implement。
Comparable接口
接口定义为:
public interface Comparable
{
Public int compareTo(Object o);
}
compareTo 方法判断这个对象对于给定对象o的顺序,并且当这个对象小于等于或大于给定对象o,分别返回负整数,0,或正整数。Java类库中的许多类如String和Date,都实现了Comparable接口以定义对象的自然顺序。
自定义继承Comparable 类 comparableRectangle
代码如下:
public class comparableRectangle extends Rectangle implements Comparable {
public comparableRectangle(double width, double height) {
super(width, height);
}
public int compareTo(Object o) {
if (getArea() > ((comparableRectangle) o).getArea())
return 1;
else if (getArea() < ((comparableRectangle) o).getArea())
return -1;
else
return 0;
}
}
定义了comparableRectangle类之后就可以方便比较原来不方便比较的对象,从而可以实现方法Max以得到对象的最大值。
ActionListener接口
现在你已经准备好了编写一个小程序来解决文章开头的提出的问题。为了响应一个按钮的点击,需要编写代码来处理点击动作。按钮是动作的源对象。需要创建一个对象能够处理按钮上的动作事件。这个对象称为监听器(listener)。
不是所有的对象都能成为动作事件的监听器。一个对象要成为源对象上的动作事件的监听器,需要满足两个条件:
1. 这个对象必须是ActionListener(事件监听器)接口的一个实例。该接口定义了所有动作监听器的共有动作。
2. ActionListener对象listener必须使用方法source.addActionListener(listene)注册给源对象。
ActionListener接口包含处理事件的actionPerformed方法。监听器必须覆盖该方法来响应事件。下面的代码实现了点击OK,就会显示“OK button clicked”,点击Cancel显示“Cancel button clicked”。
HandleEvent 类
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class HandleEvent extends JFrame{
public HandleEvent()
{
JButton jbtOK = new JButton("OK");
JButton jbtCancel = new JButton("Cancel");
JPanel p = new JPanel();
p.add(jbtOK);
p.add(jbtCancel);
add(p);
OKListenerClass listener1 = new OKListenerClass();
CancelListenerClass listener2 = new CancelListenerClass();
jbtOK.addActionListener(listener1);
jbtCancel.addActionListener(listener2);
}
}
class OKListenerClass implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
JOptionPane.showMessageDialog(null, "OK!");
}
}
class CancelListenerClass implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
JOptionPane.showMessageDialog(null, "Cancel!");
}
}
Main函数
import java.util.*;
import javax.swing.*;
import java.io.*;
public class Main {
public static void main(String[] args) {
JFrame frame = new HandleEvent();
frame.setTitle("Handle Event");
frame.setSize(200,150);
frame.setLocation(200, 100);
frame.setDefaultCloseOperation(HandleEvent.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Cloneable接口
接口包括常量和抽象方法,但是Cloneable接口是一个特殊情况,它的定义如下:
package java.lang;
public interface Cloneable
{
}
这个接口是空的,它用来标记某些特定的属性,接下来看下面一段代码:
public class Main {
public static void main(String[] args) {
Calendar date = new GregorianCalendar(2003,2,1);
Calendar date1 = date;
Calendar date2 = (Calendar)date.clone();
System.out.println("date == date1 is "+(date==date1)); //true
System.out.println("date == date2 is "+(date==date2)); //false
System.out.println("date.equals(date2) is "+date.equals(date2)); //true
}
}
代码中,将date的引用复制给date1,所以date和date1都指向相同的date对象,接下来创建了一个新对象,它是date的克隆,然后将这个新对象的引用赋值给date2.date2和date是内容相同的不同对象。这个克隆的意义和人类社会的克隆意义相同。
当然,也可以用clone方法克隆一个数组。
接口与抽象类
用Comparable接口实现对一个对象数组的排序
import java.util.*;
import javax.swing.*;
import java.io.*;
public class Main {
public static void sort(Comparable[] list)
{
Comparable currentMin;
int currentMinIndex;
for(int i=0;i<list.length;++i)
{
currentMin = list[i];
currentMinIndex = i;
for(int j = i+1;j<list.length-1;++i)
{
if(currentMin.compareTo(list[j])>0)
{
currentMin = list[j];
currentMinIndex = j;
}
}
if(currentMinIndex != i)
{
list[currentMinIndex] = list[i];
list[i]=currentMin;
}
}
}
public static void printList(Object[] list)
{
for(int i=0;i<list.length;++i)
{
System.out.print(list[i]+" ");
}
System.out.println();
}
public static void main(String[] args) {
Integer[] intArray = {new Integer(2),new Integer(4),new Integer(3)};
Double[] doubleArray={new Double(3.4),new Double(1.3),new Double(-22.1)};
Character[] charArray={new Character('a'),new Character('J'),new Character('r')};
String[] stringArray = {"Tom","John","Fred"};
sort(intArray);
sort(doubleArray);
sort(charArray);
sort(stringArray);
printList(intArray);
printList(doubleArray);
printList(charArray);
printList(stringArray);
}
}
Biginteger和BigDecimal类
有时候会碰到计算特别大的数,比如111111111111111111111111*12233333333333,这样的计算需要显然不能用之前的方式来进行。我们不能用任何的数据类型来装下这么大的数,它已经操作了int、float、double的数据类型的范围。那么如何解决这样的计算需求呢?这时候,就需要进行大数操作。
在java.math这个包中有两个进行大数操作的类:java.math.BigInteger和java.math.BigDecimal。从名字上可以知道这两个类的作用了吧。很明显,前者是进行整数的大数操作的,后者是进行小数的大数操作的。下面来看一下实例。
实例1:
import java.math.BigInteger;
public classBigIntegerDemo01 {
public static voidmain(String[] args){
String num1="88379348888888478403839479";
String num2="838777777333333333337";
BigInteger big1=newBigInteger(num1);
BigInteger big2=newBigInteger(num2);
System.out.println(big1.add(big2));//加法操作
System.out.println(big1.subtract(big2));//加法操作
System.out.println(big1.multiply(big2));//乘法操作
System.out.println(big1.divide(big2));//除法操作
BigInteger[] result=big1.divideAndRemainder(big2);
System.out.println(big1.toString()+"/"+big2.toString()+"的商:"+result[0]);
System.out.println(big1.toString()+"/"+big2.toString()+"的余数:"+result[1]);
}
}
构造方法publicBigInteger(String val)是 将 BigInteger 的十进制字符串表示形式转换为BigInteger。大整数操作可以像其它类型的数据一样进行加法、减法、乘法、除法等操作。需要特别说明的是除法操作。publicBigInteger divide(BigInteger val)这个方法只能得到一个“商“,要想的到余数需要用public BigInteger[] divideAndRemainder(BigInteger val)这个方法。divideAndRemainder()这个方法返回的是存储有”商“和”余数“的BigInteger数组。下面看看BigDecimal如何使用。
实例2:
package cn.tty.math;
import java.math.BigDecimal;
public classBigDecimalDemo02 {
public static voidmain(String[] args) {
String num1="84995.333333333323";
String num2="894.99";
//保留5位小数
System.out.println(BigDecimalDemo02.round(BigDecimalDemo02.add(num1, num2), 5));
//保留4位小数
System.out.println(BigDecimalDemo02.round(BigDecimalDemo02.subtract(num1, num2), 4));
//保留3位小数
System.out.println(BigDecimalDemo02.round(BigDecimalDemo02.multiply(num1, num2), 3));
//保留2位小数
System.out.println(BigDecimalDemo02.divide(num1, num2,2));
}
public static doubleadd(String num1,String num2){
//将 BigDecimal 的字符串表示形式转换为BigDecimal
BigDecimal b1=newBigDecimal(num1);
BigDecimal b2=newBigDecimal(num2);
returnb1.add(b2).doubleValue();
}
public static doublesubtract(String num1,String num2){
//将 BigDecimal 的字符串表示形式转换为BigDecimal
BigDecimal b1=newBigDecimal(num1);
BigDecimal b2=newBigDecimal(num2);
returnb1.subtract(b2).doubleValue();
}
public static doublemultiply(String num1,String num2){
//将 BigDecimal 的字符串表示形式转换为BigDecimal
BigDecimal b1=newBigDecimal(num1);
BigDecimal b2=newBigDecimal(num2);
returnb1.multiply(b2).doubleValue();
}
public static doubledivide(String num1,String num2,int scale){
BigDecimal b1=newBigDecimal(num1);
BigDecimal b2=newBigDecimal(num2);
//下面的“2”表示需要保留的小数位数,“BigDecimal.ROUND_HALF_UP”常量表示的是四舍五入的模式
returnb1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
}
public static double round(doublenum1,int scale){
BigDecimal big1=newBigDecimal(num1);
BigDecimal big2=newBigDecimal(1);
returnbig1.divide(big2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();//任何数除于1都等于本身
}
}
上面的例子重新包装了BigDecimal的加减乘除操作,是这些方法的使用更符合本例的需要。加减乘的操作就不用多说了,很直接,很简单,需要说明的还是除法操作。
BigDecimal的除法重载了很多。其中有一种是publicBigDecimal divide(BigDecimal divisor,int scale,RoundingMode roundingMode)。这种方法指定了保留的小数位数(scale)和四舍五入的模式(roundingMode)。比如,“ROUND_HALF_DOWN“的模式表示向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为上舍入的舍入模式。
//下面的操作不涉及大数操作的内容,但涉及到保留小数位数
double x=874.748;
double y=893.32;
double z=x*y;
System.out.println("x * Y = "+z);
System.out.println("x * Y = "+(int)(z*10)/10.0);//保留1为小数
System.out.println("x * Y = "+(int)(z*100)/100.0);//保留2为小数
System.out.println("x * Y = "+(int)(z*1000)/1000.0);//保留3为小数
这种方式并不能完全指定四舍五入的小数位数,可以称之为“伪四舍五入“,因为这某种巧合的情况下,它并不能很好的实现指定小数位数的功能。比如x=874.738,y=893.32那么 z= 781420.9501600001。(int)(z*1000)/1000.0的输出结果仍为”781420.95“,并没有预想的保留3位小数。原因很简单,z*1000=781420950. 1600001,(int)(z*1000)=781420950,那么(int)(z*1000)/1000.0=781420.95。因为末位是0,因此被舍掉了。
虽然这种方式的保留小数位数的方式不保险,但这种方式简单便捷,在要求并不严苛的情况下可以使用。
还有个四舍五入的方法,在java.lang.Math类中:
public staticlong round(double a)
publicstatic int round(float a)
显然,这两个方法返回的数将是整型数据,并不会保留任何小数。
package cn.tty.format;
import java.text.DecimalFormat;
public classDecimalFormatDemo02 {
public static String round(String pattern,double value){
DecimalFormat formatter=new DecimalFormat(pattern);
String rv=formatter.format(value);
return rv;
}
public static void main(String[] args){
//指定模式:保留2为小数
String roundedValue=DecimalFormatDemo02.round("####.00",838.666);//保留两位小数
System.out.println(roundedValue);
}
}
本章小结
1.接口是一种与类很类似的结构,只是包含常量和抽象方法。接口在许多方面与抽象类很接近,但是抽象类除了包含常量和抽象方法之外,还可以包含变量和具体方法。
2.在Java中接口被认为是一种特殊的类。(用implement实现)就像常规类一样,每一个接口都被编译为独立的字节码文件。
3.接口Cloneable是一个标记接口(空接口)。实现Cloneable接口的对象是可克隆的。
4.一个类仅能继承一个父类,但是一个类可以实现一个或多个接口。
5.一个类如果实现了一个借口,则要实现该接口的所有方法。
6.一个接口可以扩展为多个接口。(interface a extends b,c,d)
7.许多Java方法要求使用对象作为参数,Java提供了一个便捷的办法,将基本数据类型合并或包装到一个对象中(如,包装int值到Integer类中,包装double到Double类中)。对应的类称作包装类。使用包装对象而不是基本数据类型的变量,将有助于通用程序设计
8.Java可以根据上下文自动将基本类型值转换为包装对象,反之亦然
9.BigInteger类在计算和处理任意大小的正整数方面是很有用的。BigDecimal类可以用于计算和处理待人以精度的浮点数。
10.因为接口的方法默认是public类型的,所以在实现的时候一定要用public来修饰(否则默认为protected类型,缩小了方法的使用范围)。