一、继承
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
@Override
public void m1(){
System.out.println("Zi.m1方法");
}
//因为父类Fu1的m2方法的权限修饰符是protected
//子类Zi1的m2方法,权限修饰符可以是public 或 protected
@Override
public void m2(){
System.out.println("Zi.m2方法");
}
//因为父类Fu1的m3方法的权限修饰符是缺省
//子类Zi1的m3方法,权限修饰符可以是public 或 protected 或缺省
@Override
void 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方法的返回值类型只能是void
public void m1(){
System.out.println("Zi.m1的方法");
}
//因为Fu2类的m2方法的返回值类型是int
//Zi2的m2方法的返回值类型只能是int
public int m2(){
System.out.println("Zi.m2的方法");
return 1;
}
//因为Fu2类的m3方法的返回值类型是Object
//Zi2的m3方法的返回值类型可以是Object,或 Object的子类
//String < Object
public 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@4eec7777
System.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自动生成
@Override
public 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
}
}