接口
Java中类只能单继承,但是可以实现多个接口。接口相当于一个纯的抽象类,里面规定的方法只能声明原型,不能实现。接口中可以包含数据成员,但是都默认为static+final,所以基本数据类型的成员需要初始化,对象成员需要立刻调用构造方法。
接口的机制可以将不同的功能方法放入不同的接口中,从而条理清晰地实现各种组合的功能;接口可以实现不同类之间的常量共享。
声明:
[接口修饰符] interface 接口名称 [extends 父接口名称]{
\quad方法原型声明或者静态数据;
}
接口中的数据默认是不能修改的,所以可以省略final关键字;同时,接口中的方法默认是抽象的,且是公共可以访问的,所以省略public和abstract。由于接口方法声明时隐含了public,所以在实现接口的时候必须将方法声明为public类型。举例如下:
interface Shape2D{
double pi = Math.PI; // 这边省略了final static
double area(); // 这边省略了public abstract
}
class Circle implements Shape2D{
double radius;
public Circle(double radius){this.radius = radius;}
@Override
public double area() {
return pi*radius*radius;
}
}
class Rectangle implements Shape2D{
int width, height;
public Rectangle(int width, int height){this.width = width; this.height = height;}
@Override
public double area() {
return width*height;
}
}
public class Exp1 {
public static void main(String[] args){
Rectangle r = new Rectangle(6, 3);
Circle c = new Circle(2.0);
System.out.println("Area of rectangle: "+r.area()); //18.0
System.out.println("Area of circle: "+c.area()); // 12.566370614359172
}
}
可以用实现接口的类的对象初始化接口的引用,但是此时接口的引用相当于对象的引用,中间隐含实现了类型转换。举例如下:
Shape2D s1, s2;
s1 = new Rectangle(5, 6);
s2 = new Circle(3.0);
System.out.println("Area of s1: "+s1.area()); // 30.0
System.out.println("Area of s2: "+s2.area()); // 28.274333882308138
类可以实现多个接口,声明如下
[类修饰符] class 类名 implements 接口1, 接口2,…{
\quad 内容;
}
接口之间可以继承,且可以多继承
[接口修饰符] interface 接口名 extends 接口1,接口2, …{
\quad 接口定义内容;
}
子接口如果定义了和父接口重名的属性,当类实现子接口的时候,父接口的同名属性就被覆盖了,类看不到父类的同名属性。
一个综合的例子如下:
class Point{
int x, y;
public Point(int x, int y){this.x = x; this.y = y;}
public int getX(){return x;}
public int getY(){return y;}
}
interface Color{
void setColor(String color);
}
interface Shape{
double pi = 3.14;
}
interface Shape2D extends Shape{
Point p = new Point(1, 2);
double area();
}
class Circle implements Shape2D, Color{
double radius;
String color;
public Circle(double radius){this.radius = radius;}
@Override
public double area() {
System.out.println(pi);
// System.out.println(super.pi);
return pi*radius*radius;
}
@Override
public void setColor(String color) {
this.color = color;
}
}
class Rectangle implements Shape2D, Color{
int width, height;
String color;
public Rectangle(int width, int height){this.width = width; this.height = height;}
@Override
public double area() {
return width*height;
}
@Override
public void setColor(String color) {
this.color = color;
}
}
该例子中可以如之前定义Shape2D引用指向Circle类和Rectangle一样,用Circle或者Rectangle对象初始化Shape2D接口,但是此时该引用不能调用Color接口中的方法,即使Circle和Rectangle中已经实现了。所以接口引用指向类的对象的时候,只能使用改接口中有的属性和方法。
类型转换
这里讨论Java中的引用类型之间的转换,基本数据类型类型的转换包括隐含转换和强制类型的转换。
隐含转换
隐含转换指向上转换,即将某个引用指向更一般的应用。对应于基本数据类型中的隐含转换。包括将子类对象赋值给超类的引用,将子类引用赋值给超类引用;也可以是将实现某个接口的类的引用赋值给这个接口的引用;或者是子接口赋值给超接口。
这种转换可以自动完成,应为更特殊的引用一定包含更一般的引用所需要的所有内容。而当引用的赋值完成之后,显然,超类只能调用超类中实现的方法,而对超类中没有的内容,即使子类中拥有,且将引用传给了超类,也不能使用。同样地,接口也不能调用实现接口的类特有的方法或者访问特有的属性。
如上例中将Circle对象赋值给Shape2D接口的引用。
显式转换
显示转换即向下转换。这种情况对应于基本数据类型中的强制类型转换。基本数据类型的强制类型转换可能会丢失数据精度,而在引用之间的显示转换中却非常危险。这是一种将可能较一般的引用赋值给较为特殊的引用的方式。这种转换只有一种情况正确,即这个可能较为一般的引用实际是一个很特殊的引用隐含转换过来的。举例如下
Employee A = new Manager(); // Manager 是 Employee 的子类
Manager B = (Manager) A;
上面那种情况就能正确将一般的类型A赋值给特殊类型B。
类型转换的主要应用场合
- 赋值转换:将右边类型转化为左边类型
- 方法调用转换:将实参类型转化为形参类型
- 算术表达式转换:一个表达式中包含不同类型的操作数,将它们都转化为相同的类型再运算
- 字符串转换:字符串连接运算的时候,如果一个为字符串,另一个为其它类型的操作数,则将其自动转化为字符串。
方法的查找
子类的对象可以初始化父类的引用,当父类中没有子类的方法时,该引用不能使用子类的方法。
但是当子类覆盖超类的方法时,将子类对象赋给超类引用时,超类却可以调用子类的同名实例方法,但是还是调用的自己的类方法。因为当查找方法的时候,都是从子类往父类查找方法。所以子类实例是从子类的方法开始查找。而类方法直接从该类的静态方法向父类查找,所以会有不同。
举例如下:
class People{
String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class Employee extends People{
public void computePay(){
System.out.println("Low Payment");
}
public static void payType(){
System.out.println("Employee pay type");
}
}
class Manager extends Employee{
public void computePay(){
System.out.println("High Payment");
}
public static void payType(){
System.out.println("Manager pay type");
}
// 子类没有这个方法的时候,即使使用类型转换,也用不了这个方法
public void teem(){
System.out.println("I have a teem");
}
// 静态方法子类用不了
public static void teemType(){
System.out.println("Manager teem type");
}
}
public class Exp2 {
public static void main(String[] args){
Manager m = new Manager();
Employee e = new Employee();
Employee s = m;
m.computePay();
e.computePay();
s.computePay();
m.payType();
e.payType();
s.payType(); // 静态方法会直接从自己的类里面调用。
}
}
结果如下:
High Payment
Low Payment
High Payment
Manager pay type
Employee pay type
Employee pay type
而且如果父类引用有静态方法,子类实例有同名实例方法,也是先调用子类实例方法。
多态
所谓多态,就是通过超类引用指向子类对象,或者通过接口引用指向实现接口的类对象,实现不同的执行。不同子类或者实现接口的类通过覆盖或者实现方法,可以生成对同意方法的不同执行方式,这样就可以以一个超类或者一个接口获得不同的实现,这种运行时的绑定(晚绑定)就是多态。
多态应用举例——二次分发
abstract class Vechicle{
String type;
public Vechicle(){}
public Vechicle(String t){type = t;}
public abstract void driveByMale();
public abstract void driveByFemale();
}
class Car extends Vechicle{
public Car(){type="Car";}
public void driveByMale(){
System.out.println("Car drived by male");
}
public void driveByFemale(){
System.out.println("Car drived by female");
}
}
class Bus extends Vechicle{
public Bus(){type="Bus";}
public void driveByMale(){
System.out.println("Bus drived by male");
}
public void driveByFemale(){
System.out.println("Bus drived by female");
}
}
abstract class Driver{
public abstract void drive(Vechicle v);
}
class Male extends Driver{
public void drive(Vechicle v){
v.driveByMale();
}
}
class Female extends Driver{
public void drive(Vechicle v){
v.driveByFemale();
}
}
public class Exp3 {
public static void main(String[] args){
Driver a = new Male();
Driver b = new Female();
Vechicle x = new Car();
Vechicle y = new Bus();
a.drive(x);
b.drive(y);
}
}
结果如下:
Car drived by male
Bus drived by female
构造方法与多态
在构造方法中调用多态方法会产生意想不到的隐患,所以不要在构造方法中调用多态方法,并且最好不要调用除了父类构造方法以外的方法。如果实在要调用别的方法,最好调用final方法或者private方法,因为这两个方法不能覆盖,也就不具有多态性了。