1. 多态
1.1 概述:
某一个事务,在不同环境下表现出来的不同状态
如:中国人可以是人的类型,中国人 p = new 中国人();同时中国人也是人类的一份,也可以把中国人称为人类,人类 d = new 中国人()
1.2 多态的前提和体现
1. 有继承关系
2. 父类的引用指向子类的对象
3. 有方法的重写
1 public class Test { 2 public static void main() { 3 Person p = new Teacher(); // 父类的引用指向子类的对象 4 } 5 } 6 class Person{} 7 class Teacher extends Person{}
如前几天学习,创建对象应该是如下
Person p = new Person(); Teacher t = new Teacher();
但是现在写成如下,便是父类的引用指向子类的对象
Person p = new Teacher(); //按以前学习此处的p的引用指向应该为new Person
1.3 两种转型
向上转型:Person p = new Teacher();
向下转型:Teacher t = (Teacher)p;
1.4 调用关系
1. 同名成员变量: 父类的
2. 同名静态方法: 父类的(所以说静态方法不能叫重写,因为调用还是会调用父类的方法,子类中定义的方法不会被调用)
3. 同名静态变量: 父类的
4. 同名成员方法: 子类的
5.子类独有: 不能调用
6.父类独有: 调用父类
案例
class Animal { int num = 10; static int age = 20; public void eat() { System.out.println("动物吃饭"); } public static void sleep() { System.out.println("动物在睡觉"); } public void run(){ System.out.println("动物在奔跑"); } } class Cat extends Animal { int num = 80; static int age = 90; String name = "tomCat"; public void eat() { System.out.println("猫吃饭"); } public static void sleep() { System.out.println("猫在睡觉"); } public void catchMouse() { System.out.println("猫在抓老鼠"); } } class AnimalTest { public static void main(String[] args) { Animal am = new Cat();//向上转型 am.eat(); // 猫吃饭 Animal.sleep();// 动物在睡觉 am.run(); //动物在跑步 System.out.println(am.num);// 10 System.out.println(Animal.age);// 20 am.catchMouse();// 报错,可知子类独有的方法不能被调用 System.out.println(am.name);// 报错,可知子类独有的变量不能被调用 Cat ct = (Cat)am; //向下转型,调用时就调用子类自己的属性或方法,若没有相关事务就去父类中找 ct.eat();// 猫吃饭 Cat.sleep(); // 猫在睡觉 ct.run(); // 动物在跑步,可见子类中没有的方法会去父类中找 ct.catchMouse(); // 猫在抓老鼠 } }
练习,如何将三个不同类型的蛋放一个篮子里(如鸡蛋,鸭蛋,鹅蛋)
创建3个蛋,分别为鸡蛋,鸭蛋,鹅蛋
1 public class EggDemo { 2 public static void main(String[] args) { 3 HenEgg egg1 = new HenEgg(); 4 DuckEgg egg2 = new DuckEgg(); 5 GooseEgg egg3 = new GooseEgg(); 6 } 7 } 8 class HenEgg{String name;} 9 class DuckEgg{String name;} 10 class GooseEgg{String name;}
比如,将3个蛋放进鸡蛋数组,发现并不能实现,如下图
所以,若没有多态,就实现不了一个篮子放三个鸡蛋,下面是利用多态来解决这个放鸡蛋的问题
public class EggDemo { public static void main(String[] args) { Egg egg1 = new HenEgg(); Egg egg2 = new DuckEgg(); Egg egg3 = new GooseEgg(); Egg[] eggs = new Egg[]{egg1,egg2,egg3}; } } class Egg{String name;} class HenEgg extends Egg{String name;} class DuckEgg extends Egg{String name;} class GooseEgg extends Egg{String name;}
结合这个例子理解什么是多态?
多态就是多种形态,有时候是蛋,有时候是鸡蛋,所以是多套
Egg egg1 = new HenEgg(); 等号左边是蛋右边是鸡蛋
什么时候是鸡蛋,什么时候是蛋呢?
在调用成员方法时,调用的是子类方法,这个时候就是鸡蛋,除了成员方法以外,调用的都是父类的事务,所以是蛋
多态的应用(同样是这个例子)
1 public class EggDemo { 2 public static void main(String[] args) { 3 Egg egg1 = new HenEgg(); 4 Egg egg2 = new DuckEgg(); 5 Egg egg3 = new GooseEgg(); 6 Egg[] eggs = new Egg[]{egg1,egg2,egg3}; 7 // egg1.born(); 8 // egg2.born(); 9 // egg3.born(); 10 test(egg1); 11 } 12 public static void test(Egg e) { // 将参数类型定义为父类,传哪个子类就调用哪个子类方法,这样更加灵活,无多态的话每次就需要创建一个对象来实现 13 e.born(); 14 } 15 } 16 class Egg{ 17 String name; 18 public void born() { 19 System.out.println("生小*"); 20 } 21 } 22 23 class HenEgg extends Egg{ 24 String name; 25 public void born() { 26 System.out.println("生小鸡"); 27 } 28 } 29 class DuckEgg extends Egg{ 30 String name; 31 public void born() { 32 System.out.println("生小鸭"); 33 } 34 } 35 class GooseEgg extends Egg{ 36 String name; 37 public void born() { 38 System.out.println("生小鹅"); 39 } 40 }
1.5 多态的访问特点
略
面试题1
子类独有的方法不能被调用
面试题2
结果是 我爱你
1..6 多态的好处和弊端
1.多态的好处 :
提高了程序的维护性(由继承保证)
提高了程序的扩展性(由多态保证)
2. 多态的弊端
不能访问子类特有的功能
2. 抽象
1.1 抽象类的概述:
回想前面我们的猫狗案例,提取出了一个动物类。并且我们在前面也创建过了动物对象,其实思想是不对的。为什么呢?因为,我说动物,你知道我说的是什么动物嘛?只有看到了具体的动物,你才知道,这是什么动物。所以说,动物本身并不是一个具体的事物,而是一个抽象的事物。只有真正的猫,狗才是具体的动物。同理,我们也可以推想,不同的动物睡觉的方式应该是不一样的,所以,我们不应该在动物类中给出具体体现,而是应该给出一个声明即可。在java中,一个没哟方法体的方法应该定义为抽象方法,而类中如果有抽象方法,该类必须定义为抽象类
1.2 细节
用abstract 修饰的类:
(1)抽象方法格式:
abstract 修饰符返回值类型方法名(参数列表);
(2)抽象类的定义格式:
abstract class 类名{}
1. 抽象类中可以没有抽象方法,有抽向方法的类一定是抽象类
2. 抽象类不能创建对象,需要使用子类向上转型
3. 抽象的子类要么实现抽象类中所有的抽象方法,要么自己是一个抽象类
4. 抽象类有构造方法(为了让子类能够调用,完成数据的初始化)
5. abstract 不能和final 共存
案例
抽象类不能创建对象,下面第三行代码会报错
1 public class AbstractDemo { 2 public static void main(String[] args) { 3 Animal a = new Animal(); 4 } 5 } 6 // 动物抽象类 7 abstract class Animal{ 8 abstract public void sleep(); 9 }
报错如下
抽象类的子类不能有抽象方法,所以需要将这个抽象方法重写
当在定义一个继承自Animal类的Cat类时,就需要重写Animal类中的抽象方法,如下
1 // 定义猫类 2 class Cat extends Animal{ 3 @Override 4 public void sleep() { //重写sleep方法 5 System.out.println("猫站着睡觉"); 6 } 7 }
(3)抽象类的成员特点
1. 成员变量:
可以是变量,也可以是常量
2. 构造方法
有构造方法,但是不能实例化(即不能创建对象),起用于子类访问父类数据的初始化
3. 成员方法
可以有抽象方法,限定子类必须完成某些动作
也可以有非抽象方法,用于提高代码的复用性(如抽象类中需要额外实现一个功能,若这个类中有很多子类,用抽象方法的话,其子类都要重写这个抽象方法,若代码量特别 大,将是毁灭性的,这个时候用非抽象方法就没这个问题)
练习
1. 定义一个抽象类形状(shape), 包含两个方法,求周长和面积
定义一个类长方形,实现抽象类中的方法
定义一个圆,实现抽象类中的方法
在测试类中测试
1 public class Exercise1 { 2 public static void main(String[] args) { 3 Shape s = new Rect(2,4); 4 Shape c = new Circle(3); 5 result(s); 6 result(c); 7 } 8 public static void result(Shape s) { 9 System.out.println("面积为:"+s.getArea()); 10 System.out.println("周长为:"+s.getPerimeter()); 11 } 12 } 13 14 abstract class Shape{ 15 public abstract double getArea(); 16 17 public abstract double getPerimeter(); 18 } 19 20 class Rect extends Shape{ // 继承关系 Is a 21 double length; 22 double wide; 23 public Rect(double length,double wide){ 24 this.length = length; 25 this.wide = wide; 26 } 27 @Override 28 public double getArea() { // 该重写方法的权限要比抽象类声明处的权限要高,声明处是public,所以此处只能是public 29 return length*wide; 30 } 31 public double getPerimeter() { 32 return 2*(length+wide); 33 } 34 } 35 class Circle extends Shape{ 36 double r; 37 public static final double PI=3.14; 38 public Circle(double r) { 39 this.r = r; 40 } 41 public double getArea() { 42 return PI*r*r; 43 } 44 public double getPerimeter() { 45 return 2*PI*r; 46 } 47 }
2. 定义一个人,张三,男,18岁 手机 其中手机的特性为苹果X ,白色,价格是8888
此处注意点是自己定义一个手机类,当做手机的数据类型 ,即Mobile mobile; 此处为组合关系 has a
public class PersonTest { public static void main(String[] args) { Mobile mobile = new Mobile("苹果X","白色",8888); Person p = new Person("张三",'男',18,mobile); System.out.println("我的名字叫"+p.name+",性别:"+p.gender+",年龄;"+p.age+"我的手机为:"+mobile.name+",颜色为"+mobile.color+",价格为"+mobile.price); } } // 定义一个Person类 class Person{ String name; char gender; int age; Mobile mobile; public Person(String name,char gender,int age,Mobile mobile) { this.name = name; this.gender = gender; this.age = age; this.mobile = mobile; } } // 定义一个手机类 class Mobile{ String name; String color; double price; public Mobile(String name,String color,double price) { this.name = name; this.color = color; this.price = price; } }
3.
3. 接口
接口不是类,但其与类是同一层次的事物。
3.0 接口的概述:
3.1 定义格式:
interface 接口名{ }
public class InterfaceDemo{} interface InterfaceA{}
3.2 注意事项
1. 接口中只能定义常量,默认public static final修饰,不能定义变量。
如
interface InterfaceA{ int a=10;//等价于public static final int a = 10 }
2. 接口中只能定义抽象方法(1.8之前) 默认是public abstract 修饰
如下面代码
interface InterfaceA{ private void test(); // 报错 void test();//正确,此外写publc和abstract二者之一都是正确的,如下 //public void test(); 正确 }
注意:这种默认情况表明子类中的方法也只能用public修饰(子类重写父类中的方法,权限不能比父类中的方法低)
3. 接口本身不能创建对象,使用子类向上转型(如练习三中)
接口的子类:实现了接口的类
格式:class 类名 implements 接口名{}
一个类可以实现多个接口:class 类名 implements 接口1,,接口2......{}
4.接口的子类要么实现接口中所有的抽象方法要么自己是一个抽象类
1 public class InterfaceDemo {} 2 interface InterfaceA{ 3 void test(); 4 } 5 class Test implements InterfaceA{ 6 public void test() { //重写test()方法,此处是类,一定要加public 7 } 8 }
5. 接口中没有构造方法
6. 接口不能实现接口,只能继承接口,并且可以多继承(类实现接口,并且一个类可以实现多个接口)
如,再定义一个接口B,让其继承自InterfaceA,里面不能重写接口InterfaceA中声明的test()方法(即不能实现InterfaceA接口),会报错,如下
class 子类名 implements 接口1,接口2{ }
public class InterfaceDemo {} interface InterfaceA{ void test(); } interface InterfaceB { void test1(); } class Test implements InterfaceA,InterfaceB{// 接口的子类一定要去实现其继承接口的所有方法,否则报错 public void test() { System.out.println("实现接口A"); } public void test1() { System.out.println("实现接口B"); } }
7.从jdk1.8以后,接口中可以定义非抽象的方法,但是必须使用static或者default(不能省略)修饰
8. 一个类可以继承一个类,并且同时实现多个接口(先继承,再实现)
class 类1 extends 类2 implents 接口()//类中可以定义实现接口的方法
3.3 面试题
1.抽象类和接口的区别
1. 一个类可以实现多个接口,但却只能继承最多一个抽象类
2. 抽象类可以包含具体的方法,接口的所有方法都是抽象的(jdk1.8之前)
3. 抽象类可以定义常量也可以定义变量,接口只能定义常量
4. 接口的方法都是public的,抽象类的方法可以是public,protected,private或者默认的package
5. 抽象类可以定义构造函数,但接口却不能
2.什么时候定义抽象类,什么时候定义接口?
接口是功能的扩展,抽象类是根源的扩展
如门,开门,关门应该是其固有的功能,但报警的话就只有防盗门有这个功能,其他门没有,所以报警就可以被定义为接口
1 // 定义抽象类,其中定义开门和关门的功能 2 abstract class Door{ 3 public abstract void openDoor(); 4 public abstract void closeDoor(); 5 } 6 //定义报警接口 7 interface Alarm{ 8 public void alarm(); 9 } 10 // 定义门的子类--防盗门, 11 class FDoor extends Door implements Alarm{ 12 public void openDoor() {} 13 public void closeDoor() {} 14 public void alarm() {} // 实现接口中报警的方法
3. 编写一个抽象类Animal,抽象类中包括属性:name(String类型),抽象方法:speak().
编写一个宠物接口Pet,接口中包括方法:eat()。
再编写一个Cat,实现该接口和抽象类中的所有方法
在main中进行测试,输出:“miao, my name is xxx”; "I want to eat some fish"
public class AnimalTest2 { public static void main(String[] args) { Cat1 c = new Cat1("小黑"); c.speak(); c.eat();
//此处也可用接口创建对象,但其只能调用接口中的方法(其他方法相当于子类中独有的方法,如speak)
//Pet p = new Cat1("小黑");此叫接口的多态,同抽象类一样
//p.eat();
} } abstract class Animal2{ String name; abstract void speak(); } interface Pet{ void eat(); } class Cat1 extends Animal2 implements Pet{ public Cat1(String name) { this.name = name; } public void speak() { System.out.print("my name is"+name); } public void eat() { System.out.println(" I want to eat some fish"); } }
4.