一 抽象类(abstract)
抽象类的概念
只抽取了很多类的方法的声明,方法声明用abstract修饰。
一个类如果有抽象方法,那么这个类必须是抽象类。
抽象类里边可以没有抽象方法,如果这么做只有一个目的:不让你创建这个类的对象。
抽象类不能被实例化,不能创建对象。
如果一个类继承抽象类,那么,它要么重写抽象类中的所有抽象方法,要么本身也是抽象类。
抽象类的成员
成员变量:可以是常量,也可以是变量。子类可以直接继承抽象类中的成员变量。
成员方法:可以是抽象的,也可以是非抽象的。抽象方法在子类中必须要被实现。普通方法可以被子类直接继承使用。
构造方法:抽象类不能被实例化创建对象,但抽象类是class,那么它就有构造方法,可以给子类实例化使用。
抽象关键字abstract不可以和哪些关键字共存?
private:私有的,外部直接无法访问。
static:static修饰后抽象方法就可以通过类名调用,但是这样是没有意义的。
final:final修饰的方法不能被重写,所以它和abstract冲突。
二 接口(interface)
接口的概念
接口是功能的集合,可看做是一种数据类型,一种只包含了功能声明的特殊类,是比抽象类更为抽象的”类”。
接口只描述所应该具备的方法,没有具体实现,具体实现由接口的实现类(相当于接口的子类)来完成。
当一个类中所有的方法都是抽象的时候,没必要定义为抽象类,定义为接口即可。
接口的作用
1 扩展了功能,将功能的定义与实现分离,优化程序设计,降低了耦合性,即设备与设备之间实现了解耦。
2 解决了java中只能单继承的问题。接口与接口可以是单继承,也可以是多继承。 extends
Java多继承会出现的问题
多继承时,当多个父类中有相同功能时,子类调用会产生不确定性,不确定运行父类中哪个功能主体。
接口多实现时,接口中的功能都没有方法体,由子类来明确,避免了多继承的问题。
接口的成员
接口只有成员变量和成员方法。
成员变量:必须是常量, 默认修饰符 public static final
成员方法:必须是公共访问的抽象方法,默认修饰符 public abstract
为便于阅读,建议手动加上修饰符。
定义格式
public interface 接口名 {
抽象方法1;
抽象方法2;
抽象方法3;
}
接口的实现类
在类实现接口后,该类就会将接口中的抽象方法继承过来,此时该类需要重写该抽象方法,完成具体的逻辑。
子类必须覆盖掉接口中所有的抽象方法后,子类才可以实例化。否则子类是一个抽象类。
类实现接口的格式
class 类 implements 接口 {
重写接口中方法
}
继承+实现接口
父类中定义的事物的基本功能,接口中定义的事物的扩展功能。子类通过继承父类来扩展功能,如果想继续扩展其他类中的功能,需要通过实现接口来完成。
接口和抽象类异同
相同点:
都位于继承的顶端,用于被其他类实现或继承;
都不能直接实例化对象;
都包含抽象方法,其子类都必须覆写这些抽象方法;
区别:
抽象类只能单继承;接口可以多实现。
抽象类为部分方法提供实现,避免子类重复实现这些方法,提高代码重用性;接口只能包含抽象方法;
抽象类是这个事物体系结构中的共性内容, 继承体系是is..a关系
接口是这个事物中的扩展功能,继承体系是like..a关系
包含成员区别
二者的选用:
优先选用接口,尽量少用抽象类;
需要定义子类的行为,又要为子类提供共性功能时才选用抽象类;
三 多态
多态的概念
Java作为面向对象的语言,可以描述一个事物的多种形态。如Student类继承了Person类,一个Student的对象便既是Student,又是Person。
Java中多态的代码体现在一个子类对象(实现类对象)既可以给这个子类引用变量赋值,又可以给这个子类的父类(接口)变量赋值。如一个Student对象既可以赋值给一个Student类型的引用,也可以赋值给一个Person类型的引用。
最终多态体现为父类引用变量可以指向子类对象。
使用多态后的父类引用变量调用方法时,会调用子类重写后的方法。
方法重载(静态多态)
方法重写(动态多态,对象多态)
多态的前提
A:类与类(或接口)要有继承(或实现)关系。
B:一定要有方法的重写。
C:一定要有父类或者接口的引用指向子类的对象。
多态思想:可以指挥同一类型的一批对象做事情,多态的出现让我们复杂的问题简单化了。
类、抽象类、接口的多态调用
类的多态定义格式:
父类的引用变量指向子类对象
父类类型 变量名 = new 子类类型();
变量名.方法名();
class Fu {}
class Zi extends Fu {}
//类的多态使用
Fu f = new Zi();
抽象类多态定义格式
抽象类 变量名 = new 抽象类子类();
abstract class Fu {
public abstract void method();
}
class Zi extends Fu {
public void method(){
System.out.println(“重写父类抽象方法”);
}
}
//类的多态使用
Fu fu= new Zi();
接口多态定义的格式
接口 变量名 = new 接口实现类();
interface Fu {
public abstract void method();
}
class Zi implements Fu {
public void method(){
System.out.println(“重写接口抽象方法”);
}
}
//接口的多态使用
Fu fu = new Zi();
多态成员方法的变化
多态出现后会导致子父类中的成员变量有微弱的变化
class Fu { int num = 4; } class Zi extends Fu { int num = 5; } class Demo { public static void main(String[] args) { Fu f = new Zi(); System.out.println(f.num); Zi z = new Zi(); System.out.println(z.num); } }
>>>
4
5
多态成员变量
如果子父类中出现同名成员变量,多态调用该变量时:
编译时期:参考的是引用型变量所属的类中是否有被调用的成员变量。没有,编译失败。
运行时期:也是调用引用型变量所属的类中的成员变量。
简单记: 编译运行看左边。
class Fu { int num = 4; void show() { System.out.println("Fu show num..."); } } class Zi extends Fu { int num = 5; void show() { System.out.println("Zi show num..."); } } class Demo { public static void main(String[] args) { Fu f = new Zi(); f.show(); System.out.println(f.num); Zi z = new Zi(); z.show(); System.out.println(z.num); } }
>>>
Zi show num...
4
Zi show num...
5
多态成员方法
编译时期:参考引用变量所属的类,如果没有类中调用的方法,编译失败。
运行时期:参考引用变量所指的对象所属的类,并运行对象所属类中的成员方法。
简而言之:编译看左边,运行看右边。
Fu f = new Zi();
成员变量: 编译和运行都看Fu。
非静态方法:编译看Fu,运行看Zi。
静态方法: 编译和运行都看Fu。
举例:超人的例子:
person :走路();
SuperMan:走路();fly();
Person p = new SuperMan();//超人没变身之前就是普通人一个,只能调用Person里的方法
//在运行的时候发现有SuperMan这个子类继承了他,会去看里面是否有和你调用Person里相同的方法
//如果有运行就执行子类重写的方法(成员函数的特性,覆盖)
p.走路();
SuperMan sm= (SuperMan)p;//变身超人
sm.走路();
sm.fly();
总结:无论是向上转型还是向下转型,变化的都是子类对象,绝对不能把父类对象强转为子类类型
instanceof关键字
instanceof关键字用来判断对象是否属于某种数据类型。如学生的对象属于学生类,学生的对象也属于人类
格式:boolean b = 对象 instanceof 数据类型;
Person p1 = new Student();
boolean flag = p1 instanceof Student; //flag结果为true
boolean flag2 = p2 instanceof Teacher; //flag结果为false
多态转型
向上转型:当有子类对象赋值给一个父类引用时,便是向上转型,多态本身就是向上转型的过程。
使用格式:
父类类型 变量名 = new 子类类型();
Person p = new Student();
向下转型:一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用转为子类引用,这个过程是向下转型。如果是直接创建父类对象,是无法向下转型的!
使用格式:
子类类型 变量名 = (子类类型) 父类类型的变量;
Student stu = (Student) p; //变量p实际上指向Student对象
多态的好处和弊端
当父类的引用指向子类对象时,就发生了向上转型,即把子类类型对象转成了父类类型。
向上转型的好处是隐藏了子类类型,提高了代码的扩展性。
弊端是只能使用父类共性的内容,而无法使用子类特有功能,功能有限制。
看如下代码
//描述动物类,并抽取共性eat方法 abstract class Animal { abstract void eat(); } // 描述狗类,继承动物类,重写eat方法,增加lookHome方法 class Dog extends Animal { void eat() { System.out.println("啃骨头"); } void lookHome() { System.out.println("看家"); } } // 描述猫类,继承动物类,重写eat方法,增加catchMouse方法 class Cat extends Animal { void eat() { System.out.println("吃鱼"); } void catchMouse() { System.out.println("抓老鼠"); } } public class Test { public static void main(String[] args) { Animal a = new Dog(); //多态形式,创建一个狗对象 a.eat(); // 调用对象中的方法,会执行狗类中的eat方法 // a.lookHome();//使用Dog类特有的方法,需要向下转型,不能直接使用 // 为了使用狗类的lookHome方法,需要向下转型 // 向下转型过程中,可能会发生类型转换的错误,即ClassCastException异常 // 那么,在转之前需要做健壮性判断 if( !a instanceof Dog){ // 判断当前对象是否是Dog类型 System.out.println("类型不匹配,不能转换"); return; } Dog d = (Dog) a; //向下转型 d.lookHome();//调用狗类的lookHome方法 } }
什么时候使用向上转型:
当不需要面对子类类型时,通过提高扩展性,或者使用父类的功能就能完成相应的操作,这时就可以使用向上转型。
Animal a = new Dog();
a.eat();
什么时候使用向下转型:
当要使用子类特有功能时,就需要使用向下转型。
Dog d = (Dog) a; //向下转型
d.lookHome();//调用狗类的lookHome方法
弊端:需要面对具体的子类对象;向下转型时容易发生ClassCastException类型转换异常,转换前必须用instanceof做类型判断。
多态举例:毕老师和毕姥爷的故事
class 毕姥爷 { void 讲课() { System.out.println("政治"); } void 钓鱼() { System.out.println("钓鱼"); } } // 毕老师继承了毕姥爷,就有拥有了毕姥爷的讲课和钓鱼的功能, // 但毕老师和毕姥爷的讲课内容不一样,因此毕老师要覆盖毕姥爷的讲课功能 class 毕老师 extends 毕姥爷 { void 讲课() { System.out.println("Java"); } void 看电影() { System.out.println("看电影"); } } public class Test { public static void main(String[] args) { // 多态形式 毕姥爷 a = new 毕老师(); // 向上转型 a.讲课(); //这里表象是毕姥爷,其实真正讲课的仍然是毕老师,因此调用的也是毕老师的讲课功能 a.钓鱼();//这里表象是毕姥爷,但对象其实是毕老师,而毕老师继承了毕姥爷,即毕老师也具有钓鱼功能 // 当要调用毕老师特有的看电影功能时,就必须进行类型转换 毕老师 b = (毕老师) a; // 向下转型 b.看电影(); } }
原文地址:https://www.cnblogs.com/createtable/p/10589454.html