本篇是面向对象的又一大精华,面向对象的过程,就是在不断的抽象和总结,把关注的事物从不同的方面,根据不同的角度和需求总结出自己的看法和基本规律,笔者最近在看java设计模式之禅,刚看完组合模式就觉得那些玩软件架构的真是非常厉害,把平常所见事物和组织可以归纳出各个不同的部件,进而描述客观世界。有句话叫做大道至简,正好符合咱们这个行业。好了,来看本篇的内容吧。
一、多态
在这里先学一个单词:Polymrophism:
N:多态性,多形式,化学上的同态多晶型现象,同素异构现象。
而在更专业或者更正确的表述中应该是“多胎”;也就是父类被继承生成子类,子类可以有多个,这些子类分别包含有父类的特性和自己独特的特性,进而表现出多态的特征。
在实际应用中我们可以这样理解:使用指向父类的指针或者引用,能够调用子类的对象。
这里有一句英文:Polymorphismmeans the ability to assign a different meaning or usage to something indifferent contexts-----specifically,to allow an object to have more than oneform.
释义:多态性意味着在不同的上下文中对某种对象赋予不同的意义或用法的能力,-----具体而言,就是允许对象有多种形式。
1.1从引用变量的类型角度说明多态
Java引用变量有两个类型,一个是编译时类型,一个是运行时类型。
说明:编译时类型是由声明该变量时使用的类型决定。
运行时类型由实际赋值给该变量的对象决定。
若果程序中的某个引用对像有这两种类型,并且二者不同,则可以出现多态的现象。
实例代码一:
package cn.com.basicThree;
public class Subclass extends Baseclass{
public String book = "《岛》";
public void test(){
System.out.println("子类重写父类的方法。。。。");
}
public void sub(){
System.out.println("子类的普通方法。。。。。。");
}
public static void main(String[] args) {
//下面编译时类型和运行时类型完全一样,因此不存在多态。
Baseclass bc = new Baseclass();
System.out.println(bc.book);
bc.base(); //这两个方法是Baseclass的
bc.test();
//下面编译时类型和运行时类型完全一样,因此不存在多态。
Subclass sub = new Subclass();
System.out.println(sub.book);
sub.test();sub.base(); //这两个方法是Subclass的
//上转型对象:编译时类型和运行时类型不同,多态发生
Baseclass ploymophicBc = new Subclass();
System.out.println(ploymophicBc.book); // 访问父类的变量
ploymophicBc.base(); //调用父类的方法
ploymophicBc.test(); //调用自己的重写父类的方法
//该对象不能调用自己的普通方法 sub()
//与父类的对象产生多态性。同时与子类对象产生多态性
}
}
代码说明:其中声明了三个对象,父类,子类,上转型对象,
在上转型对象中体现了多态的性质,由于上转型对象的特征使得,上转型对象不同于前两者,因此表现出的形式自然有多态的影子。
在引用变量的运行类型的定义来说,子类确实没有体现出多态的性质,但是从广泛的定义来说,子类有多态的变化,因为子类兼具父类的某些性质和特征。
这里可以总结三点
1.子类与父类在广泛的定义下,存在多态。
2.上转型对象与子类对象和父类对象存在多态的性质。
3.上转型对象是一个引用类型的对象。
因此说明了,多态不止一个类,或者不止一个对象有某些类似的行为。
注意:
1.引用变量在编译阶段只能调用其编译时类型所具有的方法(只能调用父类的方法,变量),但运行时则执行它运行时类型所具有的方法(只能调用重写父类的方法,继承自父类的变量)。
因此编写java代码时,引用变量只能调用声明该变量时所用类里包含的方法。
例如通过Objectp = new Person();这个p只能调用Object类的方法,而不能调用Person类里定义的方法。
从这里可以知道上转型对象兼具运行时类型和编译时类型的特征。
2.通过引用变量来访问其所包含的实例、Field时,系统总是试图访问它编译时类型所定义的Field,而不是他运行时所定义的Filed。
由此我们可以得出一个结论:
在上转型对象中,编译时类型指的是父类对象,运行时类型指的是子类对象。
1.2扩展:引用类型的强制转换,这个在多态中表现的是数据对象之间的变化。
类型转换运算符是小括号,用法:(type)variable,这种类型是强制转换,也叫显示转换。
作用:可以将一个数据对象转换成另一个数据对象。
进行强制类型转换的时候应该注意:
1.基本类型之间的转换只能在数值类型之间进行,这里所说的数值类型包括,整数类型,字符型和浮点型,数值类型和布尔类型之间不能进行转换。
2.引用类型之间的转换只能在具有集成关系的两个类型之间进行,否则编译出错。
3.可以先用instanceof运算符判断一下两个对象之间是否可以进行类型转换,保证程序的健壮性。
实例代码2:
package cn.com.basicThree;
public class ConversionTest {
public static void main(String[] args) {
double d = 13.4;
long l = (long) d;
System.out.println(l); //由浮点型到整形转换,小数点后面的数字全部舍弃,只留整数部分,损失一部分精度
int i = 5;
//boolean b = (boolean )i;编译出错。
long k = i;
long m = 232049202439204924l;
int h = (int)m; //这里需要注意一下,当数据类型( byte char int short float double long ,)由高到低转换的时候,
//不需要强制转换(是隐士转换,可能损失精度),由低到高需要显示转换。
System.out.println(h+"---------"+m);
int s = 242;
double e = s; //这里隐士转换
System.out.println("e = "+e); //由整形到浮点型的转换默认会有小数点和后面的零,如果有指定规则的话,可以指定小数点后多少位
Object obj = "hello";
String objStr = (String)obj; //需要进行显示转换,obj的编译时类型是Object,运行时的真正类型是String
Object objInt = new Integer(23);
//String intStr = (String)objInt;//编译时可以通过,运行时不能通过,请读者思考。
String intStr = null;
//使用instanceof对上面的危险代码优化
if(objInt instanceof String){//如果objInt类型是String则进行转换
intStr = (String)objInt;
}else{
System.out.println("objInt不是String类型的。。。。。。");
}
}
}
1.3子类之间的多态
说明: 当一个父类存在多个派生类的时候,并且每个派生类都重写了父类中的某个方法,那么父类的该方法在不同的子类中就出现了不同的行为,这就是多态的一种表现形式。
实例代码3:
package cn.com.basicThree;
/**
*
* @author fcs
* 2014年8月20日
* Animal
* 说明:演示多态的三种方式
* 1.父类的abstract方法必须被子类实现。
* 2.子类重写父类的非私有方法。
* 3.子类扩展的新增方法与其他子类显示出不同的行为。
*
*/
public abstract class Animal {
public String name;
public String age;
private String type;
public String addr;
public Animal(){}
public Animal(String name, String age, String addr) {
this.name = name;
this.age = age;
this.addr = type;
}
public abstract void run();
public abstract void eat();
public void life(){
System.out.println("我们在地球上,快灭绝了。。。。。");
}
private void info(){
System.out.println("地球上的动物进化缓慢。。。。");
}
public void getTrue(){
System.out.println("等待物种大爆发。。。。。。");
}
public static void main(String[] args) {
Bird bird = new Bird("沙燕","5","亚洲");
bird.eat(); //调用自己的方法
bird.run();
bird.life(); //调用父类的方法
bird.home();
Fish fish = new Fish("虎鲸","15","太平洋等海域");
fish.eat();
fish.run();
fish.life();
fish.home();
}
}
/**
*
* @author fcs
* 2014年8月19日
* Bird
* 说明:鸟类实现类
*/
class Bird extends Animal{
public Bird(String name, String age, String addr) {
super(name, age, addr);
}
@Override
public void run() {
System.out.println("我有翅膀,天使的翅膀,飞的快。。。。。");
}
@Override
public void eat() {
System.out.println("我用喙啄食。。。。。。");
}
public void getAge(){
System.out.println("我的年龄 : "+age);
}
public void getTrue(){
System.out.println("我们的生活日益艰难。。。。。");
}
public void home(){
System.out.println("我们用树枝,唾液,草根等筑巢。。。。");
}
}
/**
*
* @author fcs
* 2014年8月19日
* Fish
* 说明:鱼类实现类
*/
class Fish extends Animal{
public Fish(String name, String age, String addr) {
super(name, age, addr);
}
@Override
public void run() {
System.out.println("我用鳍游动,很优雅。。。。。");
}
@Override
public void eat() {
System.out.println("我用鲸吞的方式,速度很快,。。。。。");
}
public void getTrue(){
System.out.println("我们什么时候能进化。。。。。。");
}
public void home(){
System.out.println("我们 四海为家。。。");
}
}
说明:1.父类是一个抽象类,定义的抽象方法必须被子类实现,因此是显示多态的一种方式
2.当子类重写父类的方法,显示不同的行为,和特征。
3.子类新增的方法与其他子类在同一个场景下显示不同的行为和特征。
4.继承体现了某种程度的多态特性。多态通过继承实现不同的实例对象的行为,二者互补。
5.可以通过多重继承实现更丰富的多态,比如Yanzi 可以继承Bird类,实现自己的特性。
这里有两个知识点:
1.abstract关键字的用法
abstract关键字可以用来修饰类和方法。
1.abstract修饰的类叫做抽象类,该类不能被实例化,也就是不能使用关键字new来创建该类对象。
2.abstract修饰的方法叫做抽象方法,抽象方法只有声明部分,而没有具体的方法体。
注意: 1abstract不能用于修饰变量,任何变量都不可以。
2abstract不能用于修饰构造方法,构造方法是一种特殊的方法。
3.当修饰方法的时候,该方法必须被子类继承才能有方法体,因此该方法的访问权限不能是private,至少是public,protected或者默认友好的,因此abstract和private不能同时修饰一个方法,
4.当修饰类的时候,该类就是所谓的抽象类,而Final修饰的类不能被继承,方法不能被重写,因此Final与abstract永远不能同时修饰类或者方法。
5.抽象类里可以包含变量,方法,构造函数,初始化块,内部类,枚举
6.抽象类的构造方法主要用于被子类调用。
抽象类的得失:“得:”可以有抽象方法,“失:“失去了创建实例的能力。
使用规则:
1.如果一个类是一个abstract类的子类,它必须具体实现父类的所有abstract方法。
2.如果一个类中含有abstract方法,那么这个类必须用abstract来修饰(abstract类也可以没有abstract方法,但abstract方法必须出现在abstract类中)。
3.一个abstract类只关心它的子类是否具有某种功能,并不关心功能的具体行为,功能的具体行为由子类负责实现。
2.抽象类的特征,作用,与多态的关系
在类的修饰符上加abstract关键字即可成为抽象类,具有抽象类的特征,因此我们可以知道抽象类不能创建实例,只能当成父类来被继承。
从语义上说,抽象类是从多个具体的类中抽象出来的父类,以这个抽象类作为其子类的模板,从而避免了子类设计的随意性。
抽象类因此体现了一种模板模式的设计,是一种较高层次的抽象。
在比较流行的23种设计模式中抽象类的作用是非常明显的,可以很快速而且准确的描述设计模式所表达的思想。
上面是总体来说是通过继承体现多态行为的,下面可以用实现接口的方式展现java语言面向对象的多态。这是一种比抽象类更加彻底的抽象方式。因此接口(interface)也被称为一种特殊的抽象类,因此接口里的方法必须默认是抽象的方法,即使没有abstract修饰。
二、接口
1。概念: 接口是从多个相似类中抽象出来的规范,接口不提供任何实现,接口体现的是规范和实现分离的设计哲学。
说明: 接口定义的是共同的公共行为规范,这些行为规范是与外部交流的通道,这就意味着接口里定义的通常是一组公共的方法和静态Final变量。接口里没有构造方法,和静态初始化块。接口里定义的内部类,接口,枚举,默认采用public static两个修饰符,如果不指定的话,系统默认会加上。
创建接口的语法格式如下:
<修饰符>interface <接口名> {}
接口的修饰符只能有两种,public或者默认的包级访问权限。
命名规范:接口和类名采用相同的命名规范,遵循java可读性规范。
规则:
1.接口可以继承接口,但是接口不能继承类。
2.接口可以被多重实现,但是类只能单重继承。
3.抽象类可以继承抽象类。
4.如果一个类实现了多个接口的时候,用“,”分割多个接口的名称
语法:class<类名> implements <接口名1>,<接口名2>{}
5.在类中实现接口的方法时,方法的名字、返回类型、参数个数及参数类型必须与接口中的完全一致。
6.如果一个类实现了一个接口,但没有实现接口中的所有方法,那么这个类必须 是abstract类。也就是说抽象类可以实现接口。
说明: 一个类实现类某个接口时,该类将会获得接口中定义的变量,抽象方法等,因此可以把实现接口理解为一种特殊的继承,(实际上已经有人这样称呼实现为继承了),相当于实现类继承了一个接口。
下面用一个实例演示接口怎么实现多态的。
实例代码4:
package cn.com.basicThree;
/**
*
* @author fcs
* 2014年8月20日
* Car
* 说明:汽车接口
* 定义了四个方法和一个常量
*/
public interface Car {
public static final int MAX_SPEED = 500;
public abstract void band(String band);
public int wheelNum();
public String info();
}
package cn.com.basicThree;
//卡车实现类
public class KaChe implements Car{
public int speed = 300;
public String name;
public String band;
public double price ;
public KaChe( String name, String band,double price) {
this.name = name;
this.band = band;
this.price = price;
}
@Override
public void band(String band) {
System.out.println("我的品牌是: "+band);
}
@Override
public int wheelNum() {
return 6;
}
@Override
public String info() {
return toString() ;
}
@Override
public String toString() {
return "KaChe [speed=" + speed + ", name=" + name + ", band=" + band
+ ", price=" + price + "]";
}
public int getMaxSpeed(){
return Car.MAX_SPEED;
}
}
package cn.com.basicThree;
//自行车实现类
public class ZiXingChe implements Car {
public int speed = 80;
public String name;
public String band;
public double price ;
public ZiXingChe( String name, String band, double price) {
this.name = name;
this.band = band;
this.price = price;
}
public void band(String band) {
System.out.println("我的品牌是: "+band);
}
@Override
public int wheelNum() {
return 2;
}
@Override
public String info() {
return toString();
}
@Override
public String toString() {
return "ZiXingChe [speed=" + speed + ", name=" + name + ", band="
+ band + ", price=" + price + "]";
}
}
package cn.com.basicThree;
//场景类
public class Client {
public static void main(String[] args) {
KaChe ka = new KaChe("卡车","江淮",232424.0);
ka.band("江淮一号。。。。");
System.out.println("最大速度: "+ka.getMaxSpeed());
System.out.println(ka.info());
System.out.println("轮子数目: "+ka.wheelNum());
System.out.println("--------------");
ZiXingChe zx = new ZiXingChe("自行车", "飞轮", 345.0);
zx.band("飞轮海一号");
System.out.println(zx.info());
System.out.println("轮子数目: "+zx.wheelNum());
}
}
上面代码不用多说,显示了汽车的几个属性和行为,实现类有了不同的特征,正是多态的表现,
在擦做上还有另外一个名词叫:接口回调。
定义:接口声明,类实例化的对象叫做接口回调。
概念:可以把实现某一接口的类创建的对象赋给该接口声明的接口变量中。那么该接口变量就可以调用被类实现的接口中的方法。实际上,当接口变量调用被类实现的接口中的方法时,就是通知相应的对象调用接口的方法。
实例代码5:
package cn.com.basicThree;
//接口回调演示
public class Client2 {
public static void main(String[] args) {
Car ka = new KaChe("擎天柱", "外星球品牌", 2320492); //接口声明,类实例化
System.out.println(ka.wheelNum());
System.out.println(ka.info());
Car zix = new ZiXingChe("大架子","解放牌",450);
System.out.println(zix.info());
}
}
java中实现多态的代码模式有很多,以后会逐一介绍
三、两个比较
1.接口回调和上转型对象的比较
说明:实现了某接口的对象,并得到对此接口的引用与上转型得到父类的引用的实质效果是一样的。
对于接口回调就是调用实现类实现接口中的方法,对于上转型对象来说就是调用子类重写父类的方法,对于父类来说就是调用父类的方法,
2.抽象类和接口的比较
相同点:
1.接口和抽象类都不能被实例化,都位于继承树的顶端,用于被其他类继承和实现。
2. 接口和抽象类都可以有抽象方法。
3.接口和抽象类是不同形式但本质是相同的多态。
不同点:
1.在类对二者的选择上,只能继承一个抽象类,但是可以实现多个接口。
2.设计目的不同,接口体现的是一种规范,强调了一组特定的服务。抽象类体现的是一种模板式设计,作为多个子类的抽象父类,是功能上的体现。
3.抽象类可以实现接口,但接口不能继承抽象类。
4.接口里只能包含抽象方法,不包含已经提供实现的方法,抽象类可以有这两种方法,。
5.接口里不能定义静态方法,抽象类里可以定义静态方法。
6.接口里没有构造方法,抽象类里可以有。
7.接口里不能有初始化块,抽象类可以有。
从这里我们可以看出接口是比抽象类更加抽象的表现形式。