抽象类
在普通类中,一个类必须实现自身写的所有方法,每个方法必须含有自己的方法体。即便先创建一个父类,再由后续的类去继承该类,父类所有方法也必须如此。倘若创建该父类只是为了构造一个后续类的模板,那么可以考虑抽象类,仅仅说明要怎么做并且把具体步骤放到实现该抽象类的类中:含有抽象方法的类叫做抽象类,同时只有抽象类才能含有抽象方法,抽象类也可含有普通方法。例如:
abstract class animal{
int age;
String name;
animal(String name, int age){
this.age = age;
this.name = name;
}
abstract void canMove(int stepNum);
public void eat(){
}
}
可见,一个抽象类可以含有成员变量、构造方法、抽象方法和普通方法。abstract只能修饰类(将其变为抽象类)、普通方法(将其变为抽象方法),而不能修饰成员变量和构造方法。同时抽象方法不能含有方法体,即{},参数列表则有无皆可。尝试一下直接用抽象类new对象:
//!animal theAnimal = new animal("puppy", 5);
animal dog = new animal("puppy", 5) {
@Override
void canMove(int stepNum) {
System.out.println(this.name + " walks " + stepNum + " steps.");
}
};
dog.canMove(5);
//!animal theNewAnimal = new animal("kitty", 6);
animal cat = new animal("kitty", 6){
@Override
void canMove(int stepNum){
System.out.println(this.name + " jumps " + stepNum + " steps");
}
};
cat.canMove(5);
显然,抽象类不可以直接生成对象,而可以在生成对象时同时采用内部类的方法重写抽象类的抽象函数达到实现该抽象类的方法,使得生成的当前一个对象可以使用新生成的没有名字但的确实现了抽象类的类,但因为其作用域只在生成对象的语句内,所以一次只能作用于当前的一个对象,后续的对象无法使用前面依照如此改动的类。也能凭此创造出仅对应该对象的类,且该类定义的对象只在该语句中才出现。
并且通过以上的例子可以看出:抽象类想要生成对象,就必须用其他类通过方法重写(需依照重写规则)实现其抽象方法。同时子类在自己的构造方法中可使用super(参数列表)调用被继承的抽象类的构造方法,不可仅有抽象类含有构造方法而子类没有构造方法。若抽象类和子类都没有构造方法,自动使用无参构造方法。上面的例子凑巧用似乎是内部类的方式写出了隐式实现抽象类的方式,显式继承抽象类的方式见下:
class dog extends animal{
public dog(String name, int age){
super(name, age);
}
@Override
void canMove(int stepNum) {
System.out.println(this.name + " runs " + stepNum + " steps. ");
}
}
接口
用interface声明的更加抽象的类,其成员变量、包含域默认由static、final修饰,归类所有且不可更改;其方法都是抽象方法,没有任何实现,且不能被private或static修饰,默认为public类型,也可在接口中显式声明出来。类想要实现借口,需使用implements关键字,方法需为public。创建接口和接口实例化例子如下:
interface eatAble{
void canBeEaten();
}
class apple implements eatAble{
public void canBeEaten(){
System.out.println(this + " can be eaten");
}
public String toString(){
return "apple";
}
}
class banana implements eatAble{
public void canBeEaten(){
System.out.println(this + " can be eaten");
}
public String toString(){
return "banana";
}
}
接口为每个实现该接口的类提供相同的方法,保证实现了该接口的类的一定程度的统一,类似继承中子类对父类方法的重写。
接口多重实现
在Java中继承关系是不允许多继承的,即一个子类含有多个父类,因为若多个父类中含有相同的成员变量、域或方法,子类若继承多个父类时若要调用相同域会出现歧义(菱形继承),而在Java中一个类可以实现多个接口,因为接口没有任何具体的实现。在例子中写父类Fruit被grape类继承,同时grape类实现eatAble、colorful两个接口。
class Fruit{
public void ripe(){
System.out.println("The Fruit is ripe.");
}
}
interface colorful{
int trick = 1;
void hasColor();
}
interface colourful{
int trick = 2;
void hasColor();
}
class grape extends Fruit implements eatAble, colorful, colourful{
int trick = colorful.trick;
@Override
public void hasColor() {
System.out.println(this + " is purple.");
}
@Override
public void canBeEaten() {
System.out.println(this + " can be eaten.");
}
@Override
public String toString() {
return "grape";
}
@Override
public void ripe() {
System.out.println(this + "is ripe");
}
}
尝试了以下接口多实现,且colorful、colourful接口含有相同的待实现方法hasColor()(同void类型),且含有相同名称的已赋值的变量trick。若grape同时implements两个接口且重写该方法,则两个接口同时被实现,毕竟存在于接口中的方法为没有方法体、待实现的抽象方法,所以即使接口内方法重名,也需要实现了该接口的类去实现该方法。但当grape想创建成员变量trick时,就必须声明该trick具体来自于哪个接口,应注意此处的trick不过是grape类中的一个普通成员变量,此时未被任何修饰符修饰,仅仅将colorful接口中的trick值赋给了它而已,在其他地方仍可对其进行修改。
若类要实现的两个接口含有相同名称的成员变量和方法,但成员变量数据类型以及方法返回值不同,再与继承相混用,成员变量的变化不难判断,如上文,需要声明变量具体来自哪个接口。为观察方法的代码见下(摘自《Java编程思想》):
interface I1{
void f();
}
interface I2{
int f(int i);
}
interface I3{
int f();
}
class C1{
int f(){
return 1;
}
}
class poorGuy1 implements I1, I2{
@Override
public void f() {
}
@Override
public int f(int i) {//also overload method "public void f()"
return 0;
}
}
class poorGuy2 extends C1 implements I2{
@Override
public int f(int i) {
return 0;
}
}
class poorGuy3 extends C1 implements I3{
@Override
public int f() {
return 0;
}
}
// ! class poorGuy4 extends C1 implements I1{ public void f() {}}
// ! interface I4 extends I1,I3{void f();}
在类poorGuy1中,poorGuy1实现了接口I1、I2,两者拥有相同名称的方法f(),虽然返回类型不同,但因参数不同可以将两个方法区分开。poorGuy2继承了C1且实现了I2接口,也可通过参数不同确定重写的方法是来自于继承父类还是实现接口。poorGuy3继承C1实现I3接口,父类方法与接口内含有的抽象方法重名、返回类型相同且参数列表相同(都为空),写出无参方法f(),即可完成对要实现接口抽象类的抽象方法的重写。然而要实现抽象方法的类或接口其抽象方法不可冲突,即返回类型不同且参数列表相同,因为如此类或对象便不能根据传入参数决定调用的方法。所以为避免如此情况的发生,应让方法的命名多样化。
接口继承接口
通过接口继承接口,位置位于子类的接口便可获得“父类”接口的方法及成员变量,便实现了扩展接口。
interface delicious extends eatAble{
// void canBeEaten();
void isDelicious();
}
写出一个接口delicious继承eatAble,同时接口中重写方法canBeEaten()、添加方法isDelicious(),然而在接口中重写“父类”接口方法并没有具体用处,因为在父类和子类接口中都没有方法的具体实现,实现子接口的类必然要实现父类和子类接口的方法。将canBeEaten()方法注释化,写出meat类实现delicious接口:
class meat implements delicious{
@Override
public void canBeEaten() {
System.out.println(this + " can be eaten.");
}
@Override
public void isDelicious() {
this.canBeEaten();
System.out.println(this + " is also very delicious");
}
@Override
public String toString() {
return "meat";
}
}
嵌套接口
接口可以嵌套在类或其他接口中,即在类或其他接口中定义接口。如此,尝试:接口内的接口、类中的接口、接口中的类。代码如下:
interface theI1{ interface innerI1{void f();} void g();}
class theC2{interface innerI2{void f();}}
interface theI2{abstract class innerC1{void f(){System.out.println("theI2.innerC1.f()");}}}
class test implements theI1.innerI1{public void f(){}}
class test2 implements theI1{public void g() { }}
然而,由于接口内域都为public,所以可以用接口名称加“.”表示所属关系且extends类或implements接口。也可用此方法implements上面代码中theC2类的innerI2。但倘若将类中接口设置为private则只可在该类作用域中实现该接口,因而只能用该类的内部类来实现。
工厂模式
不通过new对象而实例化对象的方式。在其他地方创建返回值为该类的方法,通过return方法中new出的对象实现对象的实例化。
class fruit{
public void show(){}
}
class apple extends fruit{
public void show(){
System.out.println("this is an apple.");
}
}
class pear extends fruit{
public void show(){
System.out.println("this is a pear");
}
}
class fruitFactory{
public fruit createFruit(String choice){
if(choice.equals("apple")){
return new apple();
}
else if(choice.equals("pear")){
return new pear();
}
else{
System.out.println("null");
return null;
}
}
}
调用:
fruitFactory fruitFactory = new fruitFactory();
fruit f1 = fruitFactory.createFruit("apple");
f1.show();
结果为:this is an apple.
此处可以看到:工厂要生产出来的产品都是继承自同一父类,这样在调用工厂中父类返回值方法时才能返回包含了父类所有域的子类,在由工厂创建实例时,再由父类引用引用子类返回实现类似向上造型的操作。可以看到,f1引用的为apple类的对象。