封装通过合并特征和行为来创建新的数据类型,“实现隐藏”则通过细节“私有化”把接口和实现分离。
继承可以允许向上转型,将对象视为它自己本身的类型或其基类来加以处理,但是这必须满足“is a”关系,而多态则消除类型之间的耦合关系,允许一种类型表现出与其他相似类型之间的区别,只要他们都是从同一个基类导出而来。多态通过分离做什么和怎么做,从另一个角度讲接口和实现分离开。
但是继承的向上转型会缩小接口,使得派生类扩展的方法向上转型之后不能使用。
package jin.feng1;
class Person{
void print(){
System.out.println();
}
//
}
public class Student extends Person
{
public void sst(){}
public static void main(String[] args)
{
Person p1=new Student();
p1.sst(); //编译错误,不能访问
p1.print();
}
}
后期绑定(多态机制):派生类和基类都含有相同的方法(覆盖),调用时不知道该调用哪个方法,是调用基类的还是派生类的方法
动态绑定是对于向上转型的对象而言,如果不存在向上转型,则该调用哪个方法调用哪个方法。对于向上转型的对象调用方法,它调用的方法肯定是向上转型这个对象类型所提供的方法,即Person类型仅仅提供print()方法,所以它可选的方法是基类非private方法,它如果调用的方法是基类仅有(子类没有覆盖),则调用基类方法,如果调用的方法子类覆盖了则调用相应子类方法。
将一个方法调用同一个方法主体关联起来被称作绑定。若在程序执行之前进行绑定,叫做前期绑定。
后期绑定,是在运行时根据对象的类型进行绑定。后期绑定也叫做动态绑定或运行是绑定。以便在运行是能判断对象的类型,从而调用恰当的方法。也就是说编译器不知道到具体对象类型,但是方法调用机制能找到正确的方法体,并加以调用。
Java中除了static方法和final方法之外,其他所有的方法都是后期绑定的。它会自动发生。
class Person{
void print(){System.out.println(" person");}
void sst(){}
}
class Person1 extends Person{
void print(){System.out.println(" person1");}
}
class Person2 extends Person{
void print(){System.out.println(" person2");}
}
public class Student
{
static void tune(Person p1){
p1.print();//子类覆盖,则根据具体类型调用具体方法
}
public static void main(String[] args)
{
Person p1=new Person1();//p1是基类Person类型,它可选的调用的子类没有覆盖方法sst()和子类覆盖的方法print()
p1.sst();//子类没有覆盖,则直接调用基类
Person p2=new Person2();
tune(p1); tune(p2);//
}
}
//output
person1
person2
虽然p1,p2是Person类型,但是它是由向上转型获得的,因此在运行期间会动态绑定到相应类型。并调用相应方法。
缺陷1:“私有方法的覆盖”
覆盖是针对非私有方法而言的,而程序中如果基类的方法是私有的,但是子类也有相应的方法名,此时的方法并不是覆盖,而是子类的新方法。
public class Student{
private void print(){System.out.println(" Student");}
public static void main(String[] args)
{
Student p1=new Student1();
p1.print();//貌似是动态绑定,其实调用的是自己类中的private方法。
}
}
class Student1 extends Student
{
void print(){System.out.println(" Student1");}
}
//output
Student
缺陷2:成员变量和静态方法
package jin.feng1;
class Super{
int field=0;
int getField(){return field;}
}
class Sub extends Super{
int field=1;
int getField(){return field;}
int getSuperField(){return super.field;}
}
public class Student{
public static void main(String[] args)
{
Super super1=new Sub();
System.out.println("成员变量访问:"+super1.field+"方法访问:"+super1.getField());
Sub sub1=new Sub();
System.out.println(sub1.getField()+" "+sub1.getSuperField());
}
}
//output
成员变量访问:0方法访问:1
1 0
补充:如果基类和派生类都定义一个相同的成员变量名,且不是私有的,则子类同时含有这两个变量,如果需要访问基类的同名变量则需要super.field,如果访问基类的同名方法则需要super.getField()
向上转型: Super super1=new Sub();成员变量访问的时候是基类的field,而通过方法访问则动态绑定访问派生类的getField()方法,则调用派生类的field.
发现成员变量访问操作不会动态绑定,原因是什么呢?
任何成员变量访问操作都将由编译器解析,因此不是多态的。
这在平时是不会发生的,因为所有的成员变量一般都是private,不能直接访问他们。
缺陷3、静态方法
因为静态方法属于整个类,不会跟着具体的对象而进行变换类型。可以说它跟对象没有关系,所以没有动态绑定这一说。
package jin.feng1;
class Static1{
static void print(){System.out.println("Static1 method");}
void ss(){System.out.println("normal1 method");}
}
class Static2 extends Static1{
static void print(){System.out.println("Static2 method");}
void ss(){System.out.println("normal2 method");}
}
public class StaticDynmic
{
public static void main(String args[]){
Static1 static1=new Static2();
static1.print();
static1.ss();
}
}
//output
Static1 method
normal2 method
构造器与多态
Java中构造器不同于其他的方法,构造器不具有多态性,因为它是static方法,不过是static声明是隐式的,如Construct c1=new Construct();就会调用构造方法,它不需对象名对其进行调用,所以它在对象创建之前就要进行调用,是类的方法。其他方法都是先创建对象,再通过对象对其进行调用。
package jin.feng1;
class Constructor11{
void draw(){System.out.println(" Constructor11 draw()");}
public Constructor11(){
System.out.println("Constructor11:before draw()");
draw();
System.out.println("Constructor11:after draw()");
}
}
class Constructor12 extends Constructor11{
private int radius=2;
public Constructor12(){
System.out.println("Constructor12:before draw()");
draw();
System.out.println("Constructor12:after draw()");
}
void draw()
{
System.out.println("Constructor12+draw "+radius);
}
}
public class Constuctor22
{
public static void main(String[] args)
{
new Constructor12();
}
}
//output
Constructor11:before draw()
Constructor12+draw 0
Constructor11:after draw()
Constructor12:before draw()
Constructor12+draw 2
Constructor12:after draw()
调用基类构造方法时:第一次调用draw时,radius是0,还未被初始化。
调用子类构造方法时:第二次调用draw时,radius是2,即类内初始化
首先类加载。分配给对象的存储空间初始化为0.接着初始化基类成员变量,调用基类构造器,初始化子类成员变量,调用子类构造器。