文章目录
前言
什么是继承呢?咱看电视剧的时候经常能看到 xxx 继承了xxx 的遗产,其实在Java里和这个差不多哦,本文就简单写点关于继承的玩意儿!
我想要两个类,分别是,狗、猫,它们要有名字年龄,还要会吃饭睡觉,汪汪叫、喵喵叫,我们是不是可以用以下代码:
class Dog {
public String name; // 狗的名字
public int age; // 狗的年龄
public void eat() {
System.out.println("正在吃饭"); // 狗正在吃饭
}
public void sleep() {
System.out.println("正在睡觉"); // 狗正在睡觉
}
public void bark() {
System.out.println("汪汪汪"); // 狗在叫
}
}
class Cat {
public String name; // 猫的名字
public int age; // 猫的年龄
public void eat() {
System.out.println("正在吃饭"); // 猫正在吃饭
}
public void sleep() {
System.out.println("正在睡觉"); // 猫正在睡觉
}
public void mew() {
System.out.println("喵喵喵"); // 猫在叫
}
}
但是有没有感觉,好多代码都是重复的?
方框部分代码重复了,假如我还要加更多的动物,难道还得再写那么多的重复代码?
当然不是(
为了解决这个问题,Java就有了继承这个玩意儿
一、继承?
1.继承概念
继承本质上是对共同(共性)的地方进行抽取,实现代码复用
狗和猫 本质上是啥?是动物!
所以我们可以先创建一个动物类,让狗和猫继承它
被继承的类称为:父类、基类、超类
继承的类称为:子类、派生类
一般用 “父子” 比较多
1.1.继承方法
如图所示,取自 菜鸟教程 Java 继承
1.2.继承语法
在Java中 某个类 要 继承 某个类的话
可以用 extends 关键字
例:
class Animal {
public String name; // 动物的名字
public int age; // 动物的年龄
public void eat() {
System.out.println(name + "正在吃饭"); // 动物正在吃饭
}
public void sleep() {
System.out.println(name + "正在睡觉"); // 动物正在睡觉
}
}
class Dog extends Animal {
public Dog(String name, int age) {
//super();
this.name = name; // 设置狗的名字
this.age = age; // 设置狗的年龄
}
public void bark() {
System.out.println("汪汪汪"); // 狗在叫
}
}
class Cat extends Animal {
public Cat(String name, int age) {
//super();
this.name = name; // 设置猫的名字
this.age = age; // 设置猫的年龄
}
public void mew() {
System.out.println("喵喵喵"); // 猫在叫
}
}
这里使用继承后,Dog和Cat类有了他们的 父类 的属性和方法
public class Test {
public static void main(String[] args) {
Dog dog = new Dog("小黄", 3);
Cat cat = new Cat("小白",6);
dog.eat();
dog.sleep();
dog.bark();
System.out.println("==========");
cat.eat();
cat.sleep();
cat.mew();
}
}
运行结果:
继承的类也可以直接用构造方法,在他的第一行有个隐式的 “super()”
super是啥,往下看~
2.super关键字
super就是用来调用,父类方法滴~
假如像上面那个代码 我想访问父类的A方法
那我就可以用super关键字来访问
注:只能在非静态方法中使用
class Base{
public void A(){
System.out.println("父类中的方法A");
}
public void B(int b){
System.out.println("父类中的方法B");
}
}
class RepeatEx extends Repeat{
public void A(){
super.A(); //通过super. 调用父类属性、变量、方法
/*System.out.println("子类中的方法A");*/
}
public void B(){
System.out.println("子类中的方法B");
}
}
2.1.子类访问父类
假如 父类和子类有同名变量的话,按就近原则访问,调用子类对象的话,先访问子类自身的方法~
例:
class Base{
public void A(){
System.out.println("父类中的方法A");
}
public void B(int b){
System.out.println("父类中的方法B");
}
}
class Derived extends Base{
public void A(){
System.out.println("子类中的方法A");
}
public void B(){
System.out.println("子类中的方法B");
}
}
public class Extends {
public static void main(String[] args) {
RepeatEx ex = new RepeatEx();
ex.A(); //就近原则,调用子类 无参 A
ex.B(1); //调用 有参B 调用父类 有参 B
}
}
运行结果:
2.2.子类构造方法
结合上面的super关键字,我们就可以进行这一步啦
我们知道,类可以有构造方法,构造方法就和一个模板一样,在你创建对象的时候它就按这个模板形状创造咯,比如一只猫吧,他是动物,但是你想让所有猫都叫“小白”,那你总不能一个一个全赋值吧~
所以咱就写一下构造方法!
class Animal{
public String name;
public int age;
public String color;
public void show(){ //显示属性
System.out.println(name + age + color);
}
/*public Animal(){
//默认无参构造方法
}*/
}
class Cat extends Animal{
public Cat(){
//super(); //编译器隐式提供一个(可以自己写上去)
}
public Cat(String name,int age,String color){
//super(); //编译器提供
this.name = name;
this.age = age;
this.color = color;
}
}
public class Test {
public static void main(String[] args) {
Cat cat1 = new Cat();
Cat cat2 = new Cat("小白",8,"白色");
cat1.show();
cat2.show();
}
}
运行结果:
编译器默认提供一个无参构造方法,所以
一旦你手动创建了任意构造方法,就必须得写全
在构造方法第一行(只能是第一行),编译器默认提供一个隐式的“super()”,用来先帮父类构造,咱也可以手写上去
父子父子,有父才有子~
咱子类继承自父类,父类还没构造,咱这子类哪来的(充话费送的X
我们这段代码里,父类没写构造方法,也就是编译器默认提供了一个无参构造方法
在上述代码的子类构造方法中,我们就是调用那个,“看不见”的父类无参构造方法,帮父类构造,然后继承下来,并构造子类~
还有一种方法是
class Animal{
public String name;
public int age;
public String color;
public void show(){
//显示属性
System.out.println(name + age + color);
}
public Animal(){
//无参构造方法
}
public Animal(String name,int age,String color){
//带3个参数的 有参构造方法
this.name = name;
this.age = age;
this.color = color;
}
}
class Cat extends Animal{
public Cat(){
super();
}
public Cat(String name,int age,String color){
super(name,age,color);
//通过super调用父类有参构造方法,助其初始化,并继承下来
}
}
public class Test {
public static void main(String[] args) {
Cat cat1 = new Cat();
Cat cat2 = new Cat("小白",8,"白色");
cat1.show();
cat2.show();
}
}
运行结果:
注意!
子类构造方法在用super调用父类构造方法时候
必须是在第一条!!!
还是那句话,父子父子,有父才有子!
3.final关键字
简单来说final关键字其实就是为了封装,让这个类不能被重写(变量)、继承(类)
如图所示,被final修饰的变量和方法都不能重写或者继承
4.组合
组合简单来说就是,在一个类里面,包含了其他类的实例化,就是类之间的组合~
class Power{ //控制开关机
public void powerOn(){
System.out.println("开机");
}
public void powerOff(){
System.out.println("关机");
}
}
class Mouse{ //控制鼠标
public void clickMouse(){
System.out.println("点击鼠标");
}
}
class KeyBoard{ //控制键盘
public void inputKeyBoard(){
System.out.println("键盘输入");
}
}
class Computer{ //电脑类
public Power power = new Power();
public Mouse mouse = new Mouse();
public KeyBoard keyBoard = new KeyBoard();
}
public class Test {
public static void main(String[] args) {
Computer computer = new Computer();
computer.power.powerOn();
computer.mouse.clickMouse();
computer.keyBoard.inputKeyBoard();
computer.power.powerOff();
}
}
感觉应该不用解释了吧(擦汗
就是简单的“套娃”
二、多态?
1.多态概念
简单来说就是,对象不同,方法不同,结果不同~
通过一个大类,来处理不同的小类对象
方法的重写,重载,也算是多态的一种表现
1.1.重写
class Animal{
public String name;
public Animal(String name){
this.name = name;
}
public void eat(){
System.out.println(name + "正在干饭");
}
}
class Cat extends Animal{
public Cat(String name){
super(name);
}
@Override //重写了父类eat
public void eat(){
System.out.println(name + "正在吃小鱼");
}
}
class Dog extends Animal{
public Dog(String name) {
super(name);
}
@Override //重写了父类eat
public void eat() {
System.out.println(name + "正在吃大骨头");
}
}
public class Test {
static void eat(Animal animal){
animal.eat();
}
public static void main(String[] args) {
Cat cat = new Cat("小白");
Dog dog = new Dog("大黄");
eat(cat);
eat(dog);
}
}
运行结果:
这里的父类“eat”方法被子类重写
也是多态的一种
cat调用它就是吃鱼
dog调用它就是吃骨头
实现多态有几个必须满足的要求:
- 必须在继承体系下
- 子类必须要对父类中方法进行重写
- 通过父类的引用调用重写的方法
代码里的Override
是编译器自动给出的注释(
注意:
- 父类被static、private修饰的方法,构造方法不能被重写
- 子类访问权限必须比父类高(例如:父类public,子类不能是protected,private之类的)
- 避免在构造方法中调用重写方法
第三条注意项示例:
class A{
public A(){
method();
}
public void method(){
System.out.println("A");
}
}
class B extends A{
private String b = "b";
@Override
public void method(){
System.out.println("B" + b);
}
}
public class Test {
public static void main(String[] args) {
B b = new B();
}
}
运行结果:
如图,在帮助父类初始化构造时候,又调用了method方法,此时编译器也不知道我们调用的是哪一个方法
看结果的话,它调用了B的方法
但是和想象中的结果 “Bb” 不一样
这是因为父类的初始化构造都没完成,我们就直接跳到了B的method
由于父类没有完成构造,子类也不能进行构造,所以String b = “b”;
自然也没有进行
1.2.重载
上面有提到过这个东西,大概和重写差不多,就是可以修改的地方变多了
举个例子:
class Animal{
public void eat(){
System.out.println("正在吃饭");
}
}
class Cat extends Animal{
@Override
public void eat(){
System.out.println("正在吃小鱼");
}
}
class Dog extends Animal{
public void eat(String food){
System.out.println("正在吃" + food);
}
public void eat(String name,String food){
System.out.println(name + "正在吃" + food);
}
}
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
Dog dog = new Dog();
cat.eat();
dog.eat("大骨头");
}
}
这里的
“public”是访问权定符
“void”是返回类型
“eat”是方法名
( 括号里面 ) 的是参数列表
1.3.重写和重载的区别
区别:
重写是子类对父类中已有方法的重新实现,方法名、参数列表和返回类型都必须相同
重载是在同一个类中定义多个具有相同方法名但参数列表不同的方法
重写发生在继承关系中,子类重写父类的方法
重载发生在同一个类中,对同一个方法进行不同的参数列表定义
重写要求返回类型必须与父类方法相同或为其子类型
重载对返回类型没有特定要求
简单来说:
方法重写是子类与父类的一种多态性表现
方法重载是一个类的多态性表现
2.静态绑定和动态绑定
2.1.静态绑定
- 在编译时进行绑定
- 也称为早期绑定或静态分派
- 发生在编译时期,根据变量的声明类型进行方法调用
- 在静态绑定中,方法的调用是根据变量的静态类型(声明类型)来决定的
2.2.动态绑定
- 在运行时进行绑定
- 也称为晚期绑定或动态分派
- 发生在运行时期,根据对象的实际类型进行方法调用
- 在动态绑定中,方法的调用是根据变量的实际类型(运行时类型)来决定的
例:
class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("猫发出喵喵的声音");
}
}
public class BindingExample {
public static void main(String[] args) {
Animal animal = new Animal(); // 静态类型为Animal,实际类型也是Animal
Animal cat = new Cat(); // 静态类型为Animal,实际类型为Cat
animal.makeSound(); // 输出:动物发出声音
cat.makeSound(); // 输出:猫发出喵喵的声音
}
}
运行结果:
简单来说就是
静态绑定是 静态绑定就是调用父类方法
动态绑定是 动态绑定就是调用子类方法
3.向上转型和向下转型
3.1.向上转型
简单来说,向上转型就是,子类如果有和父类同名的方法、属性,也就是重写
然后可以通过父类来调用同名方法、属性
一个大类,调用一堆小类(方便
class Shape{
public void draw(){
System.out.println("画画");
}
}
class Triangle extends Shape{
@Override
public void draw() {
System.out.println("三角形");
}
public void show(){
System.out.println("这是个三角形");
}
}
class Circle extends Shape{
@Override
public void draw() {
System.out.println("圆圈");
}
public void show(){
System.out.println("这是个圆圈");
}
}
class Rectangle extends Shape{/*
@Override
public void draw() {
System.out.println("矩形");
}
*/}
public class Test {
public static void main(String[] args) {
Shape[] shapes = new Shape[3];
shapes[0] = new Triangle();
shapes[1] = new Circle();
shapes[2] = new Rectangle();
for(Shape shape : shapes){
shape.draw();
}
/*for(Shape shape : shapes){
shape.show();
//不能访问子类特有方法
}*/
}
}
运行结果:
注意:
第二个for屏蔽原因是因为,向上转型后,只能访问原父类范围内的方法
向上转型后,我们每次调用shape.draw()约等于,我们在访问,原父类被子类向上转型重写后的方法
父类没有子类特有的“show”方法,更不存在重写这种东西了,所以通过“shape.”他是访问不到的,所以这里就出现了报错!
3.2.向下转型
向下转型的话,不安全!!!先声明这一点
class Animal{
public String name;
public void eat(){
System.out.println(name + "正在吃东西");
}
public Animal(String name) {
this.name = name;
}
}
class Cat extends Animal{
public Cat(String name){
super(name);
}
@Override
public void eat(){
System.out.println(name + "正在吃小鱼");
}
public void play1(){
System.out.println(name + "在玩毛球");
}
}
class Dog extends Animal{
public Dog(String name){
super(name);
}
@Override
public void eat(){
System.out.println(name + "正在吃大骨头");
}
public void play2(){
System.out.println(name + "在玩皮球");
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Cat("小白");
animal.eat();
((Cat) animal).play1();
//此处为简写,编译器不让我分开写两行(我哭死),可以看下面单独写的,是一个东西
// ↓ 强制类型转换错误 ↓
//((Dog) animal).play2();
}
}
运行结果:
Cat 可以是 Animal 向上转型:
Animal animal = new Cat();
此时 animal 里是 猫的属性和方法(子类重写父类同名方法属性) 约等于 此时的 animal 是 Cat 向下转型:
animal = (Cat)animal;
所以是可以调用从子类重写上来的eat(吃鱼)
如果想要访问子类特有的构造方法,可以通过向下转型:
animal = (Cat)animal;
animal.play1();
也可以成功调用Cat类里的play1(毛球)
但是 Cat 可以是 Dog 吗? 向下转型:
animal = (Dog)animal;
当然不可以!!!
猫怎么能成狗呢???
所以这里会报强制类型转换异常!
更别说调用Dog类里的方法了
animal.play2();
←(不可以的,白写)
编译器也蒙了
小转大,它支持
大转小,不支持!
3.2.1.instanceof关键字
向下转型的危险,想必是见识到了
虽然用的地方很少,但是万一用了,还出问题,就报错了,又白跑一次程序
为了解决这个,Java为了提高向下转型安全性,引入了“instanceof”关键字
以下一段引用自菜鸟教程:
instanceof 是 Java 的一个二元操作符,类似于 ==,>,< 等操作符。
instanceof 是 Java 的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型。
所以他的用法其实和“ == ”差不多
可以把上面的main部分换成:
public class Test {
public static void main(String[] args) {
Animal animal = new Cat("小白");
animal.eat();
if(animal instanceof Cat) {
((Cat) animal).play1();
}
if(animal instanceof Dog) {
((Dog) animal).play2();
}
}
}
这样他就会检测我现在调用是否合法了,提高了安全性,也不用像之前那样用注释屏蔽代码了(
4.多态的优缺点
咱用多态,一定是有他的好处~
4.1.能降低代码的"圈复杂度"
避免大量的循环、判断语句的出现导致圈复杂度的提高
关于圈复杂度可以看下前辈写的文章:
详解圈复杂度
简单来说就是,代码中出现越多的循环、判断语句,就会导致代码的复杂度提高,越复杂越不好维护,所以平常写代码还是要注意一下代码的全复杂度滴~
4.2.可扩展能力强
class Animal{
public String name;
public void eat(){
System.out.println(name + "正在吃饭");
}
}
class Cat extends Animal{
public Cat(String name){
this.name = name;
}
@Override
public void eat(){
System.out.println(name + "正在吃鱼");
}
}
class Dog extends Animal{
public Dog(String name){
this.name = name;
}
@Override
public void eat() {
System.out.println(name + "正在吃骨头");
}
}
/*class Bird extends Animal{
public Brid(String name){
this.name = name;
}
@Override
public void eat() {
System.out.println(name + "正在吃虫子");
}
}*/
使用多态:
public class Test {
public static void main(String[] args) {
Animal[] animals = {
new Cat("小白"),
new Dog("大黄")
/*new bird("小蓝")*/
};
for(Animal animal : animals){
animal.eat();
}
}
}
运行结果:
使用 if - else:
Cat cat = new Cat("小白");
Dog dog = new Dog("大黄");
/*Bird bird = new Bird("小蓝")*/
String[] animals = {
"cat",
"dog"
/*"bird"*/
};
for(String animal : animals){
if(animal.equals("cat")){
cat.eat();
}else if(animal.equals("dog")){
dog.eat();
}/*else if(bird.equals("bird")){
bird.eat();
}*/
}
运行结果:
看得出来两种写法,结果都一样,但是如果你要添加其他的动物,两种写法差距就出来咯
总结
简单写了点关于Java的继承和多态相关知识,写了两天,中间有点断片了,有点错误请多指正!!!
(该说不说的,这块知识点是真的多~