Java从入门到放弃09—多态/向上转型/向下转型/多态内存图/抽象类/关键字abstract不能和哪些关键字共存/接口/类与类,类与接口,接口与接口的关系/抽象类与接口的区别
01 多态
-
多态指的是某个事物,在某个时刻所表现出来的不同状态。
-
多态的三个必要条件:
1.要有继承。(存在父类和子类)
2.要有方法重写,可以没有,但是多态就没有意义了。
3.父类引用指向子类对象。如Animal为父类,Cat为猫类。父类引用指向子类对象 Animal cat=new Cat();
-
多态的优势:提高代码的复用性(通过继承来实现),提高代码的扩展性(通过面向父类型传参来实现)。
-
多态的弊端:父类引用可以访问被子类重写的方法,但不能直接访问子类的特有功能。(可以通过向下转型去访问)
-
多态其实就是向上转型。子类转为父类
程序示例1:多态形式访问成员变量,编译运行看左边(这里的左边指,new对象的语句中等号的左边)
public class MyTest {
public static void main(String[] args) {
//以多态的形式来访问成员变量 使用的还是父类的变量
//编译看左边,运行也看左边
Fu f = new Zi();//多态 父类引用指向子类空间
System.out.println(f.num);//访问的是父类变量中的num
}
}
class Fu{
int num = 100;
public void show(){
System.out.println("父类的show方法");
}
}
class Zi extends Fu{
int num=10;
@Override
public void show() {
System.out.println("子类重写过后的show方法");
}
}
运行结果:100
程序示例2:多态形式访问成员方法,编译看左边,运行看右边(这里的右边指,new对象的语句中等号的右边)
public class MultiStateTest {
public static void main(String[] args) {
Animal cat = new Cat();
MyUtils.eatTest(cat);
Animal dog = new Dog();
MyUtils.eatTest(dog);
Animal sheep = new Sheep();
MyUtils.eatTest(sheep);//使用多态传入sheep(属于animal类),先进入父类,再调用子类重写后的方法(运行看右边)
}
}
class Animal{
public void eat(){
System.out.println("吃饭");
}
}
class Cat extends Animal{
@Override//检查方法重写 快捷键ctrl+O
public void eat() {
System.out.println("猫吃鱼");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
class Sheep extends Animal{
@Override
public void eat() {
System.out.println("羊吃草");
}
}
class MyUtils{
private MyUtils() {//将工具包私有化,让外部不能使用空参构造创建对象,也不需要
}
public static void eatTest(Animal animal){//用static修饰该方法,可以不创建对象,通过类名直接调用调用该方法。
animal.eat();//根据传入的参数,执行方法重写机制
}
}
运行结果:猫吃鱼
狗吃肉
羊吃草
02 向下转型
- 父类只能访问子类的重写方法,如果想要父类能够访问子类的特有方法,需要进行向下转型。
- 向下转型可理解为父类强制转换为子类,语法为 子类名 对象名=(子类名) 父类对象。
程序示例:
public class MultiStateTest01 {
public static void main(String[] args) {
Humanbeing s= new Student1();//父类引用指向子类,其实就是向上转型,即多态
s.eat();//父类可以访问父类的方法,及子类的重写方法
s.sleep();//父类可以直接访问子类的重写方法
Student1 s1=(Student1)s;//父类引用指向子类
s1.learning();//向下转型后,父类才能调用子类的特有方法
Humanbeing t=new Techer();
t.eat();//父类可以直接访问子类的重写方法
t.sleep();//父类可以直接访问子类的重写方法
((Techer) t).tech();//省略写法,((Teacher)t)表示向下转型。向下转型后,父类才能调用子类的特有办法
}
}
class Humanbeing{
public void sleep(){
System.out.println("go to sleep");
}
public void eat(){
System.out.println("eat foods");
}
}
class Techer extends Humanbeing{
@Override
public void sleep() {
System.out.println("sleep with his wife");
}
public void tech(){
System.out.println("teaching");
}
}
class Student1 extends Humanbeing{
@Override
public void eat() {
System.out.println("eat hot-pot");
}
public void learning(){
System.out.println("study");
}
}
运行结果:eat hot-pot
go to sleep
study
eat foods
sleep with his wife
teaching
- 向下转型的注意事项:必须先new一个子类,该子类开辟出引用空间后,才能进行强制转换。否则会报错ClassCastException 类型转换异常。
程序示例:
public class MyTest {
public static void main(String[] args) {
Cat cat = new Cat();
Animal an=cat; //多态,父类引用指向子类空间,实质是向上转型
an.eat();
Cat c= (Cat) an; //向下转型
c.eat();//向下转型后,可以调用子类的个性功能
c.cacheMouse();
Dog dog = new Dog();
an=dog;
Dog d= (Dog) an; //向下转型
d.lookDoor();
an.eat();
// an=new Tiger();
Tiger t= (Tiger) an;//执行该语句前,由于没有new出tiger的子类空间并将父类引用指向子类空间,因此程序报错 ClassCastException 类型转换异常
t.goSwimming();
}
}
class Animal{//父类
public void eat(){
System.out.println("吃饭");
}
}
class Cat extends Animal{
@Override
public void eat() {//共性方法重写
System.out.println("猫吃鱼");
}
public void cacheMouse(){//个性方法
System.out.println("猫抓老鼠");
}
}
class Dog extends Animal{//子类
@Override
public void eat() {//子类重写方法
System.out.println("狗吃肉");
}
public void guardHomer(){//子类个性方法
System.out.println("狗守家");
}
}
class Tiger extends Animal{
@Override
public void eat() {//子类重写方法
System.out.println("老虎不吃素");
}
public void goSwimming(){//子类个性方法
System.out.println("老虎去游泳");
}
}
运行结果:猫吃鱼
猫吃鱼
猫抓老鼠
狗看门
狗吃骨头
Exception in thread "main" java.lang.ClassCastException//报错
03 多态内存图
- 子类在堆内存中开辟的空间内有一块由super标识的空间,父类在这块空间内进行初始化。
- 在调用方法时,父类引用会依据动态绑定机制,将引用空间指向子类的重写方法。
程序示例:
public class MyTest {
public static void main(String[] args) {
Fu fu = new Zi();
System.out.println(fu.num);
fu.show();
Zi zi= (Zi) fu;
System.out.println(zi.num);
zi.test();
}
}
class Fu{
int num=100;
public void show(){
System.out.println("fu show");
}
}
class Zi extends Fu{
int num=20;
@Override
public void show() {
System.out.println("zi show");
}
public void test(){
System.out.println("zi test");
}
}
运行结果:100
zi show
20
zi test
内存图解:
04 多态题目分析
class A {//10.A类虽然有show方法,但是C类对show方法进行了覆盖,因此执行C.show
public void show() {//3.子类B没有show方法,因此执行A.show
show2();//4./12.执行show2
}
public void show2() {//5.由于子类B重写了show2,因此不执行该方法 13.子类C重写了show2,因此执行C.show2
System.out.println("我");
}
}
class B extends A {//9.父类B没有show方法,但是继承的A类有show方法
public void show2() {//6.执行B.show2,打印爱
System.out.println("爱");
}
}
class C extends B {
public void show() {
super.show();//11.执行父类show方法,即执行父类B继承的父类A的show方法
}
public void show2() {//13.执行C.show2,打印你
System.out.println("你");
}
}
public class DuoTaiTest4 {
public static void main(String[] args) {
A a = new B();//1.父类a引用指向子类B
a.show();//2.调用父类a的show
B b = new C();//7.父类b引用执行子类C
b.show();//8.调用父类b的show
}
}
运行结果:爱
你
05 抽象类
- 场景引申:父类将所有子类的共性功能向上抽取后,并不知道每个子类对共性功能的具体实现,因此没必要在父类中给出共性功能的具体实现,给出声明即可。
- 抽象类概述:一个没有方法体的方法应该定义为为抽象方法,而类中如果有抽象方法该类必须定义为抽象类。
- 抽象类和抽象方法必须用abstract关键字修饰
- 抽象类格式:权限修饰符 abstract class 类名{}
- 抽象方法格式:权限修饰符 abstract 返回值类型 方法名();
程序示例
public class MyTest {
public static void main(String[] args) {
Animal an=new Cat();//抽象类不能直接创建对象,可以采用多态间接的去实例化抽象类
an.eat();
an.sleep();
an.show();
}
}
abstract class Animal {
public Animal() {
System.out.println("父类的构造方法执行了");
}
public abstract void eat(); //抽象方法,此方法没有具体方法实现
public abstract void sleep();
//抽象类中既可以有抽象方法,也可以非抽象方法
public void show(){
System.out.println("这是父类的一个非抽象方法");
}
}
class Cat extends Animal{
@Override
public void eat() {//抽象类的子类必须重写所有抽象方法
System.out.println("猫爱吃鱼");
}
@Override
public void sleep() {
System.out.println("猫白天睡觉");
}
}
-
抽象类特点:
1.抽象类不一定有抽象方法,有抽象方法的一定是抽象类。
- 抽象类中可以有构造方法,但是抽象类不能直接通过构造方法进行实例化,需要使用多态对抽象类进行实例化。抽象类的构造方法用于子类访问父类数据时的初始化。
- 抽象类按照多态的形式,由具体的子类实例化。
- 抽象方法必须被重写,否则会报错(要么将子类也定义为抽象类)。
-
抽象类的面试题
-
一个类如果没有抽象方法,可不可以定义为抽象类?如果可以,有什么意义?
答:抽象类中可以没有抽象方法。一个抽象类中没有抽象方法的意义在于该类不用也不能被实例化。比如Java中的有些工具类,虽然被定义为抽象类,但是类中的方法都用static修饰,可以直接通过类名调用,没必要进行实例化,用abstract修饰类名可以防止由该类生成实例。
-
abstract不能和关键字private/final/static并存,为什么?
答:被abstract修饰的方法需要被子类重写,而被private修饰的类或方法只能在本类中被访问不能被继承,因此冲突。被final修饰的类不能被继承,方法不能被重写,因此冲突。static静态方法存放在方法区的静态区,无需实例化,可用类名调用。而被abstract修饰的方法需要通过多态进行实例化,如果两者并存,通过类名调用抽象的静态方法将是无意义的。
06 接口
-
接口相关面试题:
-
Java中有多继承吗?
答:分情况,接口与接口之间可以实现多继承,但是类与类之间不可以多继承,只能单继承或者多层继承。
-
抽象类和接口的区别:抽象类,抽取所有子类的共性功能,并强制子类对共性功能进行重写。接口定义的是整个继承体系中的一些额外的扩展功能,哪些类想要具备这些扩展功能,可以单独去实现某个接口。区别:1.抽象类中有构造方法,接口中没有构造方法。2.抽象类中既可以有抽象方法,也可以由非抽象方法。接口中全是抽象方法。3.抽象类中既可以有成员变量,也可以有常量,接口中全是公共的静态常量。
-
注意:JDK1.8之后接口中可以定义默认方法(有具体的方法实现),但是要在权限修饰符后加关键字default,否则会报错。(定义默认方法,能弥补单继承的劣势,类只能单继承,但是类可以实现多个接口,通过接口实现多方法的继承)
-
接口的特点
1.接口不能直接实例化,需要通过多态的方式进行实例化。
2.接口中的子类可以是抽象类,但这样做的意义不大。接口中的子类也可以是具体类,具类要重写接口中的所有抽象方法。
3.接口用关键字interface表示。
interface 接口名{}
4.类实现接口用implements表示
class 类名 implements 接口名{}
类实现多个接口可以表示为(接口可以多继承):
class 类名 implements 接口名1,接口名2{}
-
接口成员的特点
interface interface{ //没有构造方法 public static final int NUM=100;//成员变量只能是常量,默认修饰符为public static final public abstract void test1(){}//JDK1.7之前成员方法只能是抽象方法,默认修饰符为public abstract public default void test2(){}//JDK1.8之后成员方法可以具体实现,但是该方法必须被default修饰。 }
总结:1.成员变量只能是常量,并且是静态的。
2.没有构造方法。
3.成员方法只能是抽象方法(JDK1.8之后有默认方法)
- 程序示例
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
Animal animal1=cat;//抽象父类引用指向子类,实现实例化
animal1.eat();
Cat c=(Cat)animal1;//向下转型
Calc ca1=c;//接口引用指向子类,实现实例化
ca1.calc();
Jump j=c;//接口引用指向子类,实现实例化
j.jumpFireCircle();
Dog dog = new Dog();
Animal animal2=dog;
animal2.eat();
Dog d=(Dog)animal2;//向下转型
d.guardHome();
Calc ca2=d;//接口引用指向子类,实现实例化
ca2.calc();
}
}
abstract class Animal {
//成员变量
private String name;
private int age;
//空参构造方法
public Animal() {
}
//有参构造方法
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public abstract void eat();
}
interface Calc {
public abstract void calc();
}
interface Jump {
public abstract void jumpFireCircle();
}
class Dog extends Animal implements Calc{//类实现单接口
@Override
public void eat() {
System.out.println("狗吃肉");
}
@Override
public void calc() {
System.out.println("狗学会了10以内的加减法");
}
public void guardHome(){
System.out.println("狗的个性本领是守家");
}
}
class Cat extends Animal implements Calc,Jump {//类实现多接口
@Override
public void eat() {
System.out.println("猫吃鱼");
}
@Override
public void calc() {
System.out.println("猫学会了10以内的加减法");
}
@Override
public void jumpFireCircle() {
System.out.println("猫学会了跳火圈");
}
}
运行结果:猫吃鱼
猫学会了10以内的加减法
猫学会了跳火圈
狗吃肉
狗的个性本领是守家
狗学会了10以内的加减法
07 类与类,类与接口,接口与接口的关系
- 类与类:继承关系,只能单继承,可以多层继承。
- 类与接口:实现关系,可以单实现,也可以多实现。并且还可以在继承一个类的同时实现多个接口。
- 接口与接口:继承关系,可以单继承,也可以多继承。