一、继承
1.1 什么是继承
生活中继承是指:
-
继承财产=>延续财产
-
继承/遗传 父母的长相,基因 => 延续上一代的基因
-
继承技能、专业、职位 =>延续
-
继承中华民族的传统文化 => 延续
-
青出于蓝而胜于蓝 或 长江后浪推前浪,前浪被拍在沙滩上 => 下一代比上一代更厉害
Java中继承是指:
-
代码的复用:子类可以继续使用父类已经声明过的代码
-
代码的扩展:子类比父类对事物的描述更具体,更丰富,(属性或功能更多)
-
is-a 的关系:子类是父类事物的一个分支
示例代码
父类Person
package com.atguigu.inherited;public class Person {//父类//属性public String name;public int age;//方法public void eat(){System.out.println(name+"现在是" + age +"岁,在吃东西");}
}
子类Student
package com.atguigu.inherited;//Student是子类
//Person是父类
//子类继承了父类的成员,代码的复用
//子类和父类不同,子类的成员比父类多,子类更具体
public class Student extends Person{//属性public int score;//成绩//方法public void exam(){System.out.println(name+"现在是" + age +"岁,在在考试");}
}
测试类TestStudent
package com.atguigu.inherited;public class TestStudent {public static void main(String[] args) {//Person对象只能访问name,age属性,调用eat()方法Person p = new Person();p.name = "李四";p.age = 24;p.eat();System.out.println("=========================");//Student对象可以访问name,age,score属性,调用eat()和exam()方法Student s = new Student();s.name = "张三";s.age = 23;s.score = 99;//父类已经声明过了,从父类继承的s.eat();//父类已经声明过了,从父类继承的s.exam();}
}
1.2 如何继承?
语法格式:
【修饰符】 class 父类名{}
【修饰符】 class 子类名 extends 父类名{ //extends是关键字}
父类:SuperClass,又称为超类,基类。
子类:SubClass,又称为派生类。
extends:扩展
1.3 继承有什么特点或要求
1、Java中只支持单继承
比喻:每一个人只有一个亲生父亲。
【修饰符】 class 子类名 extends 父类名1, 父类名2{ //错误}
2、Java中支持多层继承
比喻:代代相传。
解释:父类也可以有父类,父类的父类对于这个子类来说也是父类。
public class A{}
public class B extends A{}
public class C extends B{}
//B是C的父类,它是C的直接父类。
//A也是C的父类,它是C的间接父类。
//B会继承A的成员,然后C会继续继承B的成员。C的成员最多。
3、同一个父类可以同时有多个子类
比喻:支持多胎。
public class A{}
public class B extends A{}
public class C extends A{}
//B和C同时都是A的子类,而且是并列关系。B和C是兄弟类关系。
4、父类的所有成员变量、成员方法会继承到子类吗?
父类的所有成员变量、成员方法,都会继承到子类中。但是,父类中私有的成员变量,成员方法,子类不能直接使用,如果子类需要使用,直接通过间接的方式使用,例如:通过get/set方式。
Father类
package com.atguigu.inherited;public class Father {//父类//这四个属性的权限修饰符是不同的public int a;int b;protected int c;private int d;public int getD(){return d;}public void setD(int d) {this.d = d;}
}
Son类
package com.atguigu.inherited;//Son是子类,Father是父类
public class Son extends Father {private int e;public void setE(int e) {this.e = e;}public String getInfo() {
// return "a = " + a + ",b = " + b + ",c= " + c + ",d = " + d + ",e = " + e;//d因为在父类中是private修饰,所以在子类中无法直接使用return "a = " + a + ",b = " + b + ",c= " + c + ",d = " + getD() + ",e = " + e;}
}
测试类TestSon
package com.atguigu.inherited;public class TestSon {public static void main(String[] args) {Son s = new Son();s.a = 1;s.b = 1;s.c = 1;
// s.d = 1;
// s.e = 1;s.setD(1);s.setE(1);System.out.println(s.getInfo());}
}
5、父类的构造器会继承到子类吗?
父类的构造器不会继承到子类中,但是,子类的构造器中必须调用父类的构造器。
-
不会继承的原因:父类的构造器是用来创建父类的对象的
-
必须调用的原因:父类的构造器中 编写了 为父类中声明的这些属性初始化的代码,那么子类会继承这些属性,就需要“复用”父类构造器的这些代码为它们初始化。
说明:
-
子类的构造器==默认==去找父类的无参构造。
-
当然,子类构造器也可以通过 super(); 或 super(实参列表); 来明确子类找父类的哪个构造器
-
super(); 明确调用父类的无参构造
-
super(实参列表); 明确调用父类的有参构造
-
父类Animal
package com.atguigu.inherited;public class Animal {//父类,Animal:动物private String name;private int age;public Animal() {System.out.println("父类Animal的无参构造");}public Animal(String name, int age) {System.out.println("父类Animal的有参构造");this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}
子类Dog
package com.atguigu.inherited;//Dog是子类
//Animal是父类
public class Dog extends Animal{//Dog:狗private double weight;//重量public Dog() {super();//这句话可以省略,表示默认调用父类的无参构造//super("小黑",1);System.out.println("子类Dog的无参构造");}public Dog(String name, int age, double weight) {super(name, age);//明确说明调用父类的有参构造,为name和age属性初始化//super();System.out.println("子类Dog的有参构造");this.weight = weight;}
}
测试类TestDog
package com.atguigu.inherited;public class TestDog {public static void main(String[] args) {Dog d1 = new Dog();Dog d2 = new Dog("小白",3,5.6);}
}
思考题1
问:子类的无参构造一定找父类的无参构造,子类的有参构造一定找父类的有参构造?没有
思考题2
问:父类没有无参构造,会怎么样?
6、子类可以重写父类的方法
请看$1.4小节。
1.4 方法的重写
如果父类的某个成员方法,继承到子类后,子类认为该方法的方法体功能实现不适合子类,子类可以选择对其进行重写(Override)。
1.4.1 重载与重写的区别
方法的重载(Overload) | 方法的重写(Override) | |
---|---|---|
2个或多个方法的位置 | 在同一个类中 或 父子类中 | 父子类中 |
修饰符 | 不看 | 必须满足> 或 =的关系 |
返回值类型 | 不看 | void:必须相同 基本数据类型:必须相同 引用数据类型:必须满足 < 或 = 的关系 |
方法名 | 必须相同 | 必须相同 |
(形参列表) | 必须不同。个数、类型、顺序不同,和形参名无关。 | 必须相同。个数、类型、顺序相同,和形参名无关。 |
{方法体} | 不看 | 必须重写实现 |
示例代码1
父类Fu1
package com.atguigu.override;public class Fu1 {//父类public void m1(){System.out.println("Fu.m1方法");}protected void m2(){System.out.println("Fu.m2方法");}void m3(){System.out.println("Fu.m3方法");}private void m4(){System.out.println("Fu.m4方法");}
}
子类Zi1
package com.atguigu.override;//Zi是子类,Fu是父类
public class Zi1 extends Fu1{//因为父类Fu1的m1方法的权限修饰符是public//子类Zi1的m1方法,权限修饰符必须是public@Overridepublic void m1(){System.out.println("Zi.m1方法");}//因为父类Fu1的m2方法的权限修饰符是protected//子类Zi1的m2方法,权限修饰符可以是public 或 protected@Overridepublic void m2(){System.out.println("Zi.m2方法");}//因为父类Fu1的m3方法的权限修饰符是缺省//子类Zi1的m3方法,权限修饰符可以是public 或 protected 或缺省@Overridevoid m3(){System.out.println("Zi.m3方法");}//不是重写,因为父类Fu1的m4方法,在子类中不可见//@Override //加它会报错,因为现在的m4方法不是重写,它只是子类Zi1自己定义的一个方法private void m4(){System.out.println("Zi.m4方法");}
}
父类Fu2
package com.atguigu.override;public class Fu2 {public void m1(){System.out.println("Fu.m1的方法");}public int m2(){System.out.println("Fu.m1的方法");return 0;}public Object m3(){System.out.println("Fu.m3的方法");return new Object();}public String m4(){System.out.println("Fu.m4的方法");return new String();}
}
子类Zi2
package com.atguigu.override;public class Zi2 extends Fu2{//因为Fu2类的m1方法的返回值类型是void//Zi2的m1方法的返回值类型只能是voidpublic void m1(){System.out.println("Zi.m1的方法");}//因为Fu2类的m2方法的返回值类型是int//Zi2的m2方法的返回值类型只能是intpublic int m2(){System.out.println("Zi.m2的方法");return 1;}//因为Fu2类的m3方法的返回值类型是Object//Zi2的m3方法的返回值类型可以是Object,或 Object的子类//String < Objectpublic String m3(){System.out.println("Zi.m3的方法");return new String();}//因为Fu2类的m4方法的返回值类型是String//Zi2的m4方法的返回值类型可以是String,或String的子类//很遗憾,String没有子类,它是太监类public String m4(){System.out.println("Zi.m4的方法");return new String();}
}
父类Fu3
package com.atguigu.override;public class Fu3 {public void m1(){System.out.println("Fu.m1");}public void m2(int a, int b){System.out.println("Fu.m2");}public void m3(int a, int b){System.out.println("Fu.m3");}
}
子类Zi3
package com.atguigu.override;public class Zi3 extends Fu3{//因为Fu3的m1方法的形参列表是(),//那么Zi3的m1方法如果是()就是重写,如果是(非空)就是重载public void m1(int a){System.out.println("Zi.m1");}//因为Fu3的m2方法的形参列表是(int a, int b),//那么Zi3的m2方法的形参列表(int x, int y)//它们是重写关系,不看形参的名字,//只看类型,个数,顺序public void m2(int x, int y){System.out.println("Zi.m2");}//因为Fu3的m3方法的形参列表是(int a, int b),//那么Zi3的m3方法的形参列表(int x)//它们是重载关系,不看形参的名字,//只看类型,个数,顺序public void m3(int x){System.out.println("Zi.m3");}
}
1.4.2 @Override有什么用
@Override是一个注解。是对代码进行注释。这个注释是可以给编译器看的,
编译器看到某个方法上面加了@Override,就会对这个方法按照重写的要求,
进行格式检查,看他是否满足重写的要求,不满足就编译报错,如果满足就
不报编译错。
但是,如果一个重写的方法,没有违反重写的要求,那么加@Override
和不加它没有任何区别。
总结:@Override只是对重写方法起到一个格式检查的作用,不影响重写的本质。
建议:重写的方法都加上@Override。
1.5 super关键字
super可以调用父类声明的xxx成员。
1、super() 或 super(实参列表)
它们会出现在子类构造器的首行。表示调用父类的构造器。它只能找直接父类的。
-
super():找父类无参构造。
-
super(实参列表):找父类的有参构造。
示例代码
GrandFather爷爷类
package com.atguigu.keyword;public class GrandFather {//爷爷类private int a;public GrandFather() {System.out.println("GrandFather的构造器");}public GrandFather(int a) {this.a = a;}
}
Father父类
package com.atguigu.keyword;public class Father extends GrandFather{//父类public Father(){System.out.println("Father的构造");}
}
GrandSon孙子类
public class GrandSon extends Father{public GrandSon() {super();System.out.println("GrandSon的构造器");}public GrandSon(int a){
// super(a);//报错,因为Father类没有有参构造}
}
测试类TestGrandSon
package com.atguigu.keyword;public class TestGrandSon {public static void main(String[] args) {GrandSon g = new GrandSon();/*从构造器的角度来说,孙子要先找爸爸,爸爸再找爷爷。但是不能孙子直接找爷爷的构造器。*/}
}
2、super.方法
当子类重写了父类的某个方法,但是在子类中又要调用父类被重写的方法,就必须用“super.被重写的方法”,否则就会自己调用自己的。
如果子类没有重写父类的某个方法,在子类中需要调用这个父类的方法,加不加super.都可以。
3、super.成员变量
super.成员变量是表示使用父类的成员变量。
当父类的成员变量,与子类的成员变量重名了,可以用"super.成员变量"表示使用父类的成员变量。
如果父类的成员变量,与子类的成员变量没有重名问题,可以直接使用父类的成员变量。
虽然我们讲了这种使用方式,但是开发中一定要避免父子类的成员变量重名。
示例代码
父类Fu
package com.atguigu.keyword;public class Fu {public int a = 1;public int b = 1;
}
子类Zi
package com.atguigu.keyword;public class Zi extends Fu{public int a = 2;public void test(int a){System.out.println("a = " + a);//有重名问题,就近原则,使用局部变量System.out.println("this.a = " + this.a);//有重名问题,明确说明使用本类的System.out.println("super.a = " + super.a);//有重名问题,明确说明使用父类的System.out.println("b = " + b);//没有重名问题,直接使用父类的}
}
测试类TestZi
package com.atguigu.keyword;public class TestZi {public static void main(String[] args) {Zi z = new Zi();z.test(3);}
}
4、注意
无论在子类中调用父类的构造器,还是父类的方法,还是父类的成员变量,都要求被调用的父类成员不能是private。就算加super也不能调用。
1.6 根父类Object类
1.6.1 根父类的概念
java.lang.Object类是所有Java类的父类,它是老祖宗。所有Java类都是它的子类。
如果一个类没有明确说明它继承(extends)哪个类,那么它的父类就是Object。
1.6.2 toString方法
Object类中声明了public String toString()方法,所有Java类(或者说所有引用数据类型)都会从Object继承这个方法。
如果子类不重写toString方法,默认返回的是 对象的实际类型(new它的类型) @ 对象的hashCode值的十六进制形式。
官方API文档说明了,建议所有子类都重写toString方法,用于返回对象的详细信息,一般都是返回对象的属性值拼接。等价于我们之前做练习用的getInfo方法。
package com.atguigu.api;//Person的直接父类就是Object
public class Person {
}
package com.atguigu.api;public class TestPerson {public static void main(String[] args) {Person p = new Person();System.out.println(p.toString());//这里toString方法,就是从Object类继承的//默认打印的是 com.atguigu.api.Person@4eec7777System.out.println(p.hashCode());//1324119927//1324119927是p对象的hash值的十进制形式//4eec7777是 hash值的十六进制形式//关于hash值是什么,干什么用的,今天先不讨论,等后面讲哈希表等数据结构的时候再说。}
}
toString()非常特殊,当我们用System.out.println() 或 System.out.print()方法打印对象时,默认就会调用这个对象的toString,不用程序员手动调用。或者当我们把一个Java对象与字符串进行拼接时,也会自动调用这个对象的toString。
toString()方法的重写有两个快捷键:
-
Alt + Insert:属性拼接的模板
-
@Override public String toString() {return "Rectangle{" +"length=" + length +", width=" + width +'}'; }
-
Ctrl + O:重写父类的哪些方法。使用调用父类toString方法的模板。
@Override
public String toString() {return super.toString();
}
1.6.3 示例代码1
Rectangle矩形类
package com.atguigu.api;public class Rectangle {private double length;private double width;public Rectangle() {}public Rectangle(double length, double width) {this.length = length;this.width = width;}public double getLength() {return length;}public void setLength(double length) {this.length = length;}public double getWidth() {return width;}public void setWidth(double width) {this.width = width;}//下面的toString方法,使用快捷键Alt + Insert自动生成@Overridepublic String toString() { return "Rectangle{" +"length=" + length +", width=" + width +'}';}
}
测试类
package com.atguigu.api;public class TestRectangle {public static void main(String[] args) {Rectangle r = new Rectangle(8,4);System.out.println(r.toString());System.out.println(r);//打印r对象时,自动会调用r的toString}
}