Java—继承与多态
一。继承
1.继承概念:
继承主要解决的问题是:共性的抽取,实现代码复用。
例如:
public class Dog{
String name;
int age;
float weight;
public void eat(){
System.out.println(name+"正在吃饭");
}
public void sleep(){
System.out.println(name+"正在睡觉");
}
void bark(){
System.out.println(name+"旺旺旺");
}
}
public class Cat{
String name;
int age;
public void eat(){
System.out.println(name+"正在吃饭");
}
public void sleep(){
System.out.println(name+"正在睡觉");
}
void mew(){
System.out.println(name+"喵喵喵");
}
}
从上述代码中就可以看到Cat和Dog类的代码中有很多地方都是重复的,因此我们引出了一种新的概念,叫作继承,就是把重复的部分变成父类,把特有点叫作子类
在面向对象思想中提到了继承的概念,专门用来进行共性抽取,实现代码的复用
2.继承语法:
继承在英语中是inheritance;在Java中如果要表示类之间的继承关系,需要借助extends关键字,具体如下:
修饰符 class 子类 extends 父类{
//.....
}
那么现在对上述代码进行优化:
public class Animal{
String name;
int age;
public void eat(){
System.out.println(name+"正在吃饭");
}
public void sleep(){
System.out,println(name+"正在睡觉");
}
}
public class Dog extends Animal{
void bark(){
System.out.println(name+"旺旺旺");
}
public class Cat extends Animal{
void mew(){
System.out.println(name+"喵喵喵");
}
}
}
public class Text1{
public static void main(String[] args){
Dog dog=new Dog();
System.out.println(dog.name);
System.out.println(dog.age);
dog.eat();
dog.sleep();
dog.bark();
}
}
3.父类成员访问:
在继承体系中,子类将父类中的方法和字段继承下来了,那在子类中能否直接访问父类中继承下来的成员呢?
(1)子类中访问父类的成员变量
类型1:子类和父类不存在同名成员变量:
public class Base(){
int a;
int b;
}
public class Ded extends Base{
int C;
public void method(){
a=10;//访问从父类继承下来的a
b=20;//访问从父类继承下来的b
c=30;//访问从子类自己的c
}
}
类型2:子类和父类成员变量同名
public class Base{
int a;
int c;
int c;
public class Ded extends Base{
int a;
char b;
}
public void method(){
a=100;
b=101;
c=102;
}
}
在子类方法中 或者 通过子类对象访问成员时:
(1)如果访问的成员变量子类中有,优先访问自己的成员变量。
(2)如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
(3)如果访问的成员变量与父类中成员变量同名,则优先访问自己的。
成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。
(2)子类中访问父类的成员方法:
类型1:成员方法名字不同:
总结:成员方法没有同名时,在子类方法中或者通过子类对象访问方法时,则优先访问自己的,自己没有时
再到父类中找,如果父类中也没有则报错。
类型2:成员方法名字相同:
总结:通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到
则访问,否则编译报错。
通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用
方法适传递的参数选择合适的方法访问,如果没有则报错;
4.super关键字:
super关键字的作用就是在子类方法中访问父类成员
public class Base{
int a;
int b;
public void methodA(){
System.out.println("Base中的methodA");
}
public void methodB(){
System.out.println("Base中的methodB");
}
}
public class Ded extends Base{
int a;
int b;
public void methodA(int a){
System.out.println("Ded中的methodA");
}
public void methodB(){//其实就是与父类的methodB()构成重写(在多态的部分会进行详细的解释)
System.out.println("Ded中的methodB");
}
public void methodC(){
a=100;
b=101;
super.a=200;//访问父类的对象要使用super来访问
super.b=201;
methodA();//这里访问的是父类中的methodA(),因为此处没有传参
methodA(20);//这里访问的就是子类中的methodA(int a)
methodB();//这里访问的是子类的
super.methodB()//使用super来使其访问父类的
}
}
super使用的注意事项:
只能在非静态方法中使用
在子类方法中调用父类的方法或对象
5.子类构造方法:
子类对象构造时,需要先调用父类构造方法,然后才能执行子类的构造方法。可以理解成有父才有子。
public class Base{
public Base(){
System.out.println("Base()");
}
}
public class Dad extends Base{
public Dad(){
System.out.println("Dad()");
}
}
注意:子类的构造方法中,会默认调用父类无参的构造方法:super()。当用户没有写时,编译器会自动添加一个super(),而且super必须是子类构造方法的第一句
也就是说:子类进行构造完,一定要先将父类初始化
public class Animal{
String name;
int age;
public Animal(String name,int age){
this.name=name;
this.age=age;
}
}
public class Dog extends Animal{
public Dog(){
super("圆圆",10);//父类初始化
}
}
这个例子就能很好的展现出,在子类构造方法时,一定要先给父类进行初始化
这时要注意一个问题,如果父类写了带参数的构造方法(就如上所示),那么在子类的构造方法中就必须写super(…),对括号里的内容进行赋值
例子:
class Animal{
String name;
int age;
public Animal(String name,int age){
this.name=name;
this.age=age;
}
}
class Dog extends Animal{
public Dog(String name,int age){
super(name,age);
}
public void methodA(){
System.out.println(name+"旺旺旺");
}
}
class Cat extends Animal{
public Cat(String name,int age){
super(name,age);
}
public void methodB(){
System.out.println(name+"喵喵喵");
}
}
public static void main(String[] args){
Dog dog=new Dog("花花",10);
dog.methodA();
Cat cat=new Cat("yuanyuna",12);
cat.methodB();
}
6.final关键字
(1)修饰变量和字段,表示常量(不能被修改)
final int a=10;
a=20;//报错
用finally修饰的变量和字段只能初始化一次,不可以被修改
(2)修饰类:表示此类不能被继承
final public class Animal{
//.....
}
public class Dog extends Animal{
//......
}
此时会报错,因为使用final修饰以后,就会使当前类不会被继承
二。多态
1.概念:
通俗的来讲,就是多种形态。具体点来讲,就是不同的对象去完成同一件事务,产生不同的状态。也可以理解成同一个人对待不同的人的态度是不同的
2.多态的实现条件
在Java中实现多态,要满足一下几点要求:
(1)必须在继承的体系下
(2)子类必须要对父类中的方法进行重写
(3)通过父类的引用调用重写的方法
完成以上三个条件就会发生动态绑定,动态绑定是多态的基础
3.向上转型
向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。
语法格式:父类类型 对象名 = new 子类类型()
class Animal{
public String name;
public int age;
public Animal(String name,int age){
this.name=name;
this.age=age;
}
public void eat(){
System.out.println(this.name+"正在吃饭");
}
}
class Dog extends Animal{
public Dog(String name,int age){
super(name,age);
}
public void bark(){
System.out.println(this.name+"旺旺旺");
}
}
public class Nhui {
public static void main(String[] args) {
Dog dog=new Dog("yuanyuan",10);
dog.eat();
dog.bark();
System.out.println("===============");
Animal animal=new Animal("panpan",12);
animal.eat();
animal.bark()//报错,因为父类不能继承子类
}
}
所以通过父类引用只能调用父类自己的方法和变量,不能调用子类特有的方法和变量
那就没办法了吗?其实不是的,解决这个问题就要引出一个新的概念----向上转型
改变上面代码的main函数部分,其余不变:
public class Nhui{
public static void main(String[] args){
Animal animal=new Dog("圆圆",12);//向上转型
}
}
这里就实现了向上转型,animal这个引用指向了dog这个引用所指向的对象
但是此时animal这个父类还是不可以访问Dog这个子类的bark()
可以发生向上转型的3个时机:
//1.直接赋值
Animal animal=new Dog("圆圆",10);
//2.方法的参数,传参的时候进行向上转型
public static void func1(Animal aniaml){
//.....
}
public static void main(String[] args){
Dog dog=new Dog("圆圆",10);
func1(dog);
}
//3.返回值向上转型
public static Animal func2(){
Dog dog=new Dog("圆圆",10);
return dog;
}
4.重写
实现重写:
(1)最基本的返回值,参数列表,方法名必须一样
(2)被重写的方法的访问修饰限定符在子类中要大于父类的(private 默认的 protected public)
(3)被private修饰的方法是不可以重写的
(4)被static修饰的方法是不可以重写的
(5)被final修饰的方法是不可以重写的
(6)可以使用@Override来注解,形成检查的作用
(7)构造方法不可以被重写
例子:
在Animal中:
public void eat(){
System.out.println(this.name+"正在吃饭");
}
在Dog类中:
public void eat(){
System.out.println(this.name+"正在吃狗粮");
}
那么这时两个方法的关系就叫作重写
main函数中:
Animal Animal=new Dog("圆圆",10);
animal.eat()
此时调用的eat是Dog类中的eat()
我们把这个过程叫作动态绑定
重写的设计原则:
对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,
并且添加或者改动新的内容。
5.多态的实现
class Animal{
public String name;
public int age;
public Animal(String name,int age){
this.name=name;
this.age=age;
}
public void eat(){
System.out.println(this.name+"正在吃饭");
}
}
class Dog extends Animal{
public Dog(String name,int age){
super(name,age);
}
public void bark(){
System.out.println(this.name+"旺旺旺");
}
public void eat(){
System.out.println(this.name+"在吃狗粮")//重写
}
}
class Cat extends Animal{
public Cat(String name,int age){
super(name,age);
}
public void caw(){
System.out.println(this.name+"喵喵喵");
}
public void eat(){
System.out.println(this.name+"在吃猫粮")//重写
}
}
public class Nhui {
public static void eatfunc(Animal animal){//向上转型
animal.eat();//同一个方法
}
public static void main(String[] args) {
Dog dog=new Dog("盼盼",12);
eatfunc(dog);
Cat cat=new Cat("圆圆",12);
eatfunc(cat);
}
}
这里主函数还有另一种写法:
public class Nhui {
public static void main(String[] args) {
Animal animal=new Dog("盼盼",12);
animal.eat()
Animal animal2=new Cat("圆圆",10);
animal2.eat()
}
}
当父类引用引用的子类对象不一样的时候,调用这个重写方法,所表现出来的行为也是不一样的,我们把这种思想叫作多态
6.多态的优点和缺点
例子:打印{“cycle”,“rect”,“cycle”,“rect”,“triangle”};
class shape{
public void draw(){
System.out.println("画图形");
}
}
class Rect extends shape{
public void draw(){
System.out.println("矩形");
}
}
class Cycle extends shape{
public void draw(){
System.out.println("圆形");
}
}
class Triangle extends shape{
public void draw(){
System.out.println("三角形");
}
}
public class Text1 {
public static void main(String[] args) {
Rect rect =new Rect();
Cycle cycle=new Cycle();
Triangle triangle=new Triangle();
String[] shapes={"cycle","rect","cycle","rect","triangle"};
for(String x:shapes){
if(x.equals("cycle")){
cycle.draw();
}else if(x.equals("rect")){
rect.draw();
}else if(x.equals("triangle")){
triangle.draw();
}
}
}
}
使用多态的好处:
能够降低代码的 “圈复杂度”, 避免使用大量的 if - else
什么叫 “圈复杂度” ?
圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解. 而如
果有很多的条件分支或者循环语句, 就认为理解起来更复杂.
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 “圈复杂度”.
如果一个方法的圈复杂度太高, 就需要考虑重构.
不同公司对于代码的圈复杂度的规范不一样. 一般不会超过 10
把以上代码写成多态的形式:(只对main里的部分修改,前面还是一样的)
public class Text1 {
public static void DrawMap(Shape shape){
shape.draw();
}
public static void main(String[] args) {
//Shape shape=new Cycle();
//Shape shape=new Rect();
//Shape shape=new Triangle();
Shape[] shapes={new Cycle(),new Rect(),new Cycle(),new Rect(),new Triangle()};
for(Shape shape:shapes){
shape.draw();
}
}
}
多态缺陷:代码的运行效率降低。
(1)属性没有多态性: 当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性
(2)构造方法没有多态性