写在前面:
该学习笔记基于《Head First Java》一书,仅供个人参考备忘使用,可能会存在诸多问题,也会随着学习的推进不断修改,因此请酌情将其当作参考。
目录
抽象类
Java中的类有两种,一种是具体类,也就是常用的类class,另一种是抽象类abstract class。
/*抽象类abstract class*/
abstract class Animal {
private String name;
public void setName(String newName) {
name = newName;
}
}
/*具体类*/
class Dog extends Animal {
String sexual;
public void beFriendly() {
System.out.println("be friendly");
}
public void play() {
System.out.println("play");
}
}
抽象类的特点——不能被实例化,即不能通过new关键字来创建对象,只能被其他的类继承。继承抽象类的可以是抽象类,也可以是具体类:
抽象类中的代码,一般是需要在子类中被复用的。
抽象类的设置,主要是为了适应多态的需求:
Animal creature = new Dog();
可以使用抽象的父类作为引用类型,然后为其赋予子类对象的引用;也可以在方法定义中指定抽象父类作为参数,而在调用时使用子类。
public void copy(Animal a, Animal b) {
b = a;
}
Dog creature1 = new Dog();
Dog creature2 = null;
copy(creature1, creature2)
抽象方法
abstract关键字同样也可以用于方法。用abstract标记过的方法称为抽象方法,与抽象类需要被继承(extends)相对应,抽象方法也一定要被覆盖才能使用。
抽象方法是没有内容的:
public abstract type method();
因为没有必要。
另外,抽象方法只能存在于抽象类中。但抽象类既可以包含抽象方法,也可以包含具体方法。
abstract class Animal {
private String name;
public abstract void play();
public void setName(String newName) {
name = newName;
}
}
class Dog extends Animal {
String sexual;
public void beFriendly() {
System.out.println("be friendly");
}
public void play() {
System.out.println("play");
}
}
抽象方法的意义在于,对于不同类型的子类,可能无法抽取出一个形式统一的父类方法,但又要能够在声明时使用抽象父类进行声明,使用时再代入具体的子类对象,因此可以用一个抽象方法先将参数和返回值统一,继承抽象类的子类可以按照此抽象方法为模板进行具体方法的实现。
由于在具体类中不能含有抽象方法,因此所有抽象方法在继承树下的第一个具体类中都需要被具体化,但在抽象类到具体类之间的抽象类中,也可以对抽象方法进行实现:
多态的误区
为了提高方法的适用范围,可以使用最高级别的父类Object类作为引用类型,但这样会陷入一个误区。当方法以Object作为引用类型来接收参数时,在方法中这个被接收的对象就只能当作Object来使用了!
public static void main(String[] args) {
ArrayList<Object> pets = new ArrayList<Object>();
Dog dog1 = new Dog();
pets.add(dog1);
Dog dog2 = pets.get(0); //发生错误,不能将Object对象(父类对象)赋给Dog引用类型(子类引用)
}
从上面这个例子可以看出,如果仅仅为了提高对象适用性而选用在继承树上处于更高位置(较高级别的父类)的类型作为引用类型时,就可能为之后对象的方法调用带来风险,而Java编译器会预防这种情况,从而使程序无法正常通过编译。
如何理解这件事:
引用类型的本质是遥控器:
引用类型即限定了所声明的变量能够执行的操作只能是引用类型中所定义的操作,就像你不能用电视的遥控器来遥控玩具车一样。因此使用较高级别的父类进行声明时,就意味着被声明的对象所能调用的方法会相应减少。
如何解决?
可以使用强制类型转换来将能够确定类型的对象转换成相应的更加具体的类型:
public static void main(String[] args) {
ArrayList<Object> pets = new ArrayList<Object>();
Dog dog1 = new Dog();
pets.add(dog1);
Object obj = pets.get(0);
if (obj instanceof Dog) {
Dog dog2 = (Dog) obj;
}
}
在上面的例子中,对于我们明确知道是Dog类型的Object类声明对象obj,可以使用(ClassName)形式的类型转换来将对象转换成想要的类型。但这样是有一定风险的,当不能完全确定所要转换对象的类型时,最好使用instanceof运算符,来判断对象是否是某个类的实例:
if (obj instanceof Dog)
这样的方式更加安全保险。
接口
接口是Java面向对象的又一大特性。当想让抽象类的一部分子类实现某些方法时,可以使用接口来对这些方法进行声明,这样做可以在保证这一部分子类的个性的前提下,避免其他无关子类同样需要对这些对它们来说毫无意义的方法进行实现所造成的麻烦。接口的形式如下:
interface Pet {
public abstract void beFriendly();
public abstract void play();
}
接口使用interface关键字进行定义,且内部只包含抽象方法。因此,实现接口的类必须实现接口中所有的抽象方法:
class Dog extends Animal implements Pet {
String sexual;
public void beFriendly() {
System.out.println("be friendly");
}
public void play() {
System.out.println("play");
}
}
类使用impements关键字来实现接口。当实现多个接口时,可以用逗号将其分隔:
class Dog extends Animal implements Pet, Mammal {
}
接口就像是为遥控器新增一些按钮。当然,接口存在的意义,很大程度上是为了解决多重继承可能存在的冲突:
在某些其他语言中可能会存在如下的多重继承现象:
但Java通过接口这一设计规避掉了多重继承,使得想要某些类实现某些特定方法变得更加容易,也更不容易出现冲突。
另外,接口也可以作为方法的参数或返回值类型,作为参数类型时,只需要传入任意一个实现了该接口的类作为参数即可;作为返回值类型时(注意接口可以看作是一种特殊的类型,可以用public InterfaceName method() 的形式来定义方法),接收返回值的对象可以调用接口中的所有方法。
interface Pet {
public abstract void beFriendly();
public abstract void play();
}
class Dog implements Pet {
String sexual;
public void beFriendly() {
System.out.println("be friendly");
}
public void play() {
System.out.println("woof!!");
}
}
public class learning {
public static void main(String[] args) {
learning test = new learning();
Dog a = new Dog();
Pet b = test.copy(a);
b.play();
}
public Pet copy(Pet a) {
a.play();
return a;
}
}
To be continued……