目录:
1. 继承
2. 多态:
转型
重写
正文:
1. 继承:
观察以下代码:
我们发现Cat类和Dog类中有许多相同的属性,那不妨思考一下是否能有一种办法能把它们的相同点都归结到一块儿呢?
当然有,它就是继承!!!
Cat类:
public class Cat {
public String name;
public int age;
public void eat() {
System.out.println(this.name + "正在吃饭...");
}
public void mimi() {
System.out.println( this.name + "正在咪咪叫...");
}
}
Dog类:
public class Dog {
public String name;
public int age;
public void eat() {
System.out.println(this.name + "正在吃饭...");
}
public void bark() {
System.out.println(this.name + "正在汪汪叫...");
}
}
继承的定义:
继承允许一个类(称为子类或派生类)基于另一个类(称为父类、基类或超类)来定义自己的特性和行为。通过继承,子类可以获得父类的属性和方法,并可以在此基础上进行扩展或修改,从而实现代码的重用和扩展。
简单来说就是对每个类的共性进行抽取,实现代码的复用。
那我们就可以对Dog和Cat的共性进行抽取了:
public class Animal {
public String name = "小黑";
public int age;
public void eat() {
System.out.println(this.name + "正在吃饭...");
}
}
在我们抽取完成后应该借用关键字extends来让子类继承Animal:
Cat类:
public class Cat extends Animal{
public String name;
public void mimi() {
System.out.println( this.name + "正在咪咪叫...");
}
}
Dog类:
public class Dog extends Animal{
public String name = "小白";
public void bark() {
System.out.println(this.name + "正在汪汪叫...");
}
}
Test类:
public class Test {
public static void main(String[] args) {
Dog dog1 = new Dog();
dog1.bark();
}
}
运行结果:
观察运行结果可以看出调用的方法在哪个类里面,name就用谁的。
如果我们就是想要调用父类的成员变量怎么办?
这就需要用到关键字super。
修改后的Dog类:
public class Dog extends Animal{
public String name = "小白";
public void bark() {
System.out.println(super.name + "正在汪汪叫...");
}
}
运行结果:
Creature类:
public class Creature {
public String name = "大黄";
}
Animal类:
public class Animal extends Creature{
public String name = "小黑";
public int age;
public void eat() {
System.out.println(this.name + "正在吃饭...");
}
}
Dog类:
public class Dog extends Animal{
public String name = "小白";
public void bark() {
System.out.println(super.name + "正在汪汪叫...");
}
}
super只能指代当前类的父类,不能指代父类的父类,甚至继续往上指代。
注:super就是一个关键字,来提高代码的可读性!!!
在Java中没有多继承:(但我们可以通过接口的形式支持多继承)
在写继承的时候如果有太多层的话会导致整个代码比较杂乱,我们一般最多写2~3层。
如果我们想要一个类不能再被继承,这里我们可以使用final关键字来修饰这个类:
如果一个类被final给修饰了,当这个类被继承的时候编译器就会报错。(final就是起到一个标记的作用)
当子类继承父类之后,如果父类的构造方法是有参数的,子类需要显示父类的构造方法,要先帮助父类的成员进行初始化。
还需要注意的一点是super()必须在第一行,由此推出super()和this()是无法共存的:
观察下面的代码,试着猜一下运行结果是什么?
Dog类:
public class Dog extends Animal{
{
System.out.println("Dog类的实例代码块被执行了...");
}
public Dog(String name, int age) {
super(name, age);
System.out.println("Dog类的构造方法被执行了...");
}
static {
System.out.println("Dog类的静态代码块被执行了...");
}
public void bark() {
System.out.println(super.name + "正在汪汪叫...");
}
}
Animal类:
public class Animal extends Creature{
public String name;
public int age;
{
System.out.println("Animal类的实例代码块被执行了...");
}
static{
System.out.println("Animal类的静态代码块被执行了...");
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Animal类的构造方法被执行了...");
}
public void eat() {
System.out.println(this.name + "正在吃饭...");
}
}
Test类:
public class Test {
public static void main(String[] args) {
Dog dog1 = new Dog("小黑", 5);
}
}
结论:
先执行父类和子类的静态代码块;
再执行父类的实例代码块和构造方法;
最后执行子类的实例代码块和构造方法。
注:关于代码块,在《类和对象详解》的博客中有讲解。
运行结果:
2. 多态:
先给一个现象让我们看一下它到底是什么:
Shape类:
public class Shape {
public double area;
public double cir;
public Shape(double area, double cir) {
this.area = area;
this.cir = cir;
}
public void draw() {
System.out.println("画一个图形...");
}
}
Tri类:
public class Tri extends Shape{
public Tri(double area, double cir) {
super(area, cir);
}
public void draw() {
System.out.println("画一个面积为 " + this.area +" 周长为 "+ this.cir+ "的三角形...");
}
}
Rot类:
public class Rot extends Shape{
public Rot(double area, double cir) {
super(area, cir);
}
public void draw() {
System.out.println("画一个面积为 " + this.area +" 周长为 "+ this.cir+ "的圆...");
}
}
Test类:
public class Test {
public static void todraw(Shape shape) {
shape.draw();
}
public static void main(String[] args) {
Shape shape1 = new Rot(5, 10);
todraw(shape1);
Shape shape2 = new Tri(2,5);
todraw(shape2);
}
}
运行结果:
此时就是发生了多态,通俗地来讲,就是多种形态,具体点就是去完成某些行为,当不同的对象去完成的时候会产生不同的结果。
程序在编译的时候确实调用的是父类的draw() 方法,当代码运行时,通过父类引用,调用了父类和子类重写的那个方法,结果实际调用了子类的方法,此时我们把这种情况叫做动态绑定。(大前提:一定是在继承的情况下)
当然,这里也涉及到了向上转型和重写的问题,接下来我就详细讲一下什么是转型和重写?
转型:
举个例子:
比如说父类为Animal,Dog和Cat都是它的子类,这时他们之间就可以发生转型。
Animal animal = new Dog();
上面这段代码就是发生了向上转型,一共有三种方式发生向上转型:
1. 直接赋值:
Animal animal = new Dog();
2. 方法的传参:
public static void func(Animal animal) {}
public static void main(String[] args){
Dog dog = new Dog();
func(dog);
}
3. 返回值:
public static Animal func(){
Dog dog = new Dog();
return dog;
}
public static void main(String[] args){
Animal animal = func();
}
向上转型的缺陷:
通过父类的引用调用子类特有的方法是无法直接实现的,这里只能调用父类自己的。
当然,有向上转型就会有向下转型。
向下转型,例:
Animal animal = new bird();
Bird bird = (bird)animal;
bird.fly();
但是并非所有的向下转型都能成功,例:
Animal animal = new Dog();
Bird bird = (bird)animal;
bird.fly();
重写:
观察下面代码:
Shape类:
public class Shape {
public double area;
public double cir;
public Shape(double area, double cir) {
this.area = area;
this.cir = cir;
}
public void draw() {
System.out.println("画一个图形...");
}
}
Rot类:
public class Rot extends Shape{
public Rot(double area, double cir) {
super(area, cir);
}
@Override
public void draw() {
System.out.println("画一个面积为 " + this.area +" 周长为 "+ this.cir+ "的圆...");
}
}
Tri类:
public class Tri extends Shape{
public Tri(double area, double cir) {
super(area, cir);
}
@Override
public void draw() {
System.out.println("画一个面积为 " + this.area +" 周长为 "+ this.cir+ "的三角形...");
}
}
Test类:
public class Test {
public static void todraw(Shape shape) {
shape.draw();
}
public static void main(String[] args) {
Shape shape1 = new Rot(5, 10);
todraw(shape1);
Shape shape2 = new Tri(2,5);
todraw(shape2);
}
}
运行结果:
程序在编译的时候确实调用的是父类的draw() 方法,当代码运行时,通过父类引用,调用了父类和子类重写的那个方法,结果实际调用了子类的方法,此时我们把这种情况叫做动态绑定。(大前提:一定是在继承的情况下)
注:动态绑定是理解多态的基础!!!
构成重写时父类和子类的两个方法应该满足的条件:
1. 方法名相同;
2. 参数列表相同(个数,类型,顺序);
3. 返回值类型相同。
仔细观察可以发现在上面的代码有一段这样的代码:
@Override
这个代码叫做注解,注解有很多种,@Override只是其中之一,它是用来修饰被重写的方法,起到一个提示作用,如果在它下面的方法不满足重写的条件,编译器就会报错...
重写的注意事项:
1. 不能是一个静态方法;
(有人可能会觉得是子类中的方法没有加static,这里我把加了的截图也放在下面,编译器一样是报错的。)
2. 被final修饰的方法不能被重写(被final修饰的方法也叫作密封方法);
3. 如果子类重写父类的方法,子类的权限要大于要大于等于父类的权限;
4. 被private修饰的方法是不能被重写的;
5. 要构成父子关系。
面试考点:
重载与重写的区别