为什么要有继承
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。其中,多个类可以称为子类,单独那一个类称为父类、超类或者基类。
继承的概念
继承:就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。继承描述的是事物之间的所属关系,这种关系是: is-a 的关系。继承的关系中,“子类就是一个父类”,也就是说子类可以被当做父类看待。例如,兔子属于食草动物,食草动物属于动物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。继承主要解决的问题是共性抽取。
好处
- 提高代码的复用性。
- 类与类之间产生了关系,是多态的前提。
继承关系当中的特点
- 子类拥有父类中非私有的属性和行为
- 子类还可以拥有自己独有的属性和行为
继承的格式
通过 extends 关键字,可以声明一个子类继承另外一个父类,定义格式如下:
注意:父类只是一个普通的类
代码举例
定义父类
package MyCode.demo01; // 定义一个父类 public class MyFather { //父类中的成员变量 public int age = 18; //父类中的成员方法 public void method() { System.out.println("我是父类中的方法"); } }
定义子类
package MyCode.demo01; // 定义子类MySon继承MyFather public class MySon extends MyFather { }
定义测试类
package MyCode.demo01; public class MyTest { public static void main(String[] args) { //创建子类对象 MySon son = new MySon(); //子类里面什么代码都没有,但是继承了父类中非私有的方法和属性 son.method(); System.out.println(son.age); } }
代码执行后的结果
继承中成员变量的访问特点
创建父类对象
- 只能使用父类的成员变量,没有任何子类的成员变量
创建子类对象
- 子类对象可以使用本类中的成员变量或者使用父类中非私有的成员变量
- 如果子类父类中出现不重名的成员变量,这时子类对象可以根据不同的成员变量名来访问需要的成员变量。
- 如果子类父类中出现重名的成员变量,这时子类对象有2种访问方式
访问方式1:直接通过子类对象访问成员变量:等号左边是谁,就优先用谁,没有则向上(父类)找。
访问方式2:间接通过成员方法访问成员变量:该方法属于谁,就优先用谁,没有则向上(父类)找
继承中成员方法的访问特点
创建父类对象
- 只能使用父类的成员方法,没有任何子类的成员方法
创建子类对象
- 子类对象可以使用本类中的成员方法或者使用父类中非私有的成员方法
- 访问成员方法的规则:创建的对象是谁,就优先用谁,如果没有则向上(父类)找。
方法重写 (Override)
如果子类父类中出现重名的成员方法,这时的访问是一种特殊情况,叫做方法重写 (Override)。
- 方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效 果,也称为重写或者复写。简单理解:声明不变,重新实现。
方法重写的注意事项:
1. 必须保证父子类之间方法的名称相同,参数列表也相同。
- tips:@Override:写在方法前面,用来检测是不是有效的正确覆盖重写。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。
2. 子类方法的返回值必须【小于等于】父类方法的返回值范围。
- tips:java.lang.Object类是所有类的公共最高父类(祖宗类),java.lang.String就是Object的子类。
3. 子类方法的访问权限必须【大于等于】父类方法的权限修饰符。
- tips:public > protected > (default) > private 备注:(default)不是关键字default,而是什么都不写,留空。
重写的应用
子类可以根据需要,定义特定于自己的行为。既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从 而进行扩展增强。设计原则:对于已经投入使用的类,尽量不要修改,推荐一个新的类,来重复利用其中共性内容,添加新的内容。
代码举例
定义老款手机
package MyCode.demo02; // 本来的老款手机 public class Phone { public void call() { System.out.println("打电话"); } public void send() { System.out.println("发短信"); } public void show() { System.out.println("显示号码"); } }
定义新款手机类,方法覆盖重写应用。
package MyCode.demo02; // 定义一个新手机,使用老手机作为父类 public class NewPhone extends Phone { //重写父类的来电显示号码功能,并增加自己的显示姓名和图片功能 @Override public void show() { //调用父类已经存在的功能使用super super.show(); //增加自己特有显示姓名和图片功System.out.println("显示来电姓名"); System.out.println("显示头像"); } }
定义测试类
package MyCode.demo02; public class Demo01Phone { public static void main(String[] args) { // 创建子类对象 NewPhone np = new NewPhone(); //调用继承下来的方法 np.call(); np.send(); // 调用子类重写的方法 np.show(); } }
代码执行后的结果
Java关键字this和super用法总结
this关键字含义
- this :代表当前对象的引用(谁调用就代表谁)。
super的含义
- super :代表父类的存储空间标识(可以理解为父亲的引用)。
super关键字用来访问父类内容,用法有三种:
- 在子类的成员方法中,访问父类的成员变量。格式:super.成员变量名
- 在子类的成员方法中,访问父类的成员方法。格式:super.成员方法名
- 在子类的构造方法中,访问父类的构造方法。格式:super(参数列表)
this关键字用来访问本类内容,用法也有三种:
- 在本类的成员方法中,访问本类的成员变量。 格式:this.成员变量名
- 在本类的成员方法中,访问本类的另一个成员方法。 格式:this.成员方法名()
- 在本类的构造方法中,访问本类的另一个构造方法。 格式:this(参数列表)
第三种用法当中要注意:
- this(...)调用也必须是构造方法的第一个语句,唯一一个。
- super和this两种构造调用,不能同时使用。
代码举例
package MyCode.demo03; class Animal { public void eat() { System.out.println("animal : eat"); } }
创建子类Cat
package MyCode.demo03; class Cat extends Animal { public void eat() { System.out.println("cat : eat"); } public void eatTest() { // this 调用本类的方法 this.eat(); // super 调用父类的方法 } super.eat(); } }
创建测试类
package MyCode.demo03; public class Test { public static void main(String[] args) { //创建父类对象Animal Animal a = new Animal(); a.eat(); //创建子类对象 Cat c = new Cat(); c.eatTest(); } }
代码执行后的结果
继承关系中父子类构造方法的访问特点
- 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
- 子类构造方法当中有一个默认隐含的“super()”调用,所以一定是先调用的父类构造,后执行的子类构造方法
- 子类构造可以通过super关键字来调用父类重载构造。
- super的父类构造调用,必须是子类构造方法的第一个语句。不能一个子类构造调用多次super构造。
- 总结:子类构造方法必须调用父类构造方法,不写则赠送super();写了则用写的指定的super调用,super只能有一个,还必须是第一个。
父类空间优先于子类对象产生
在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类空间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。代码体现在子类的构造方法调用时,一定先调用父类的构造方法。
继承的特点
Java只支持单继承,不支持多继承。
Java支持多层继承(继承体系)。
子类和父类是一种相对的概念。tips:顶层父类是Object类。所有的类默认继承Object,作为父类。
综合案例:群主发普通红包
- 群主的一笔金额,从群主余额中扣除,平均分成n等份,让成员领取。
- 成员领取红包后,保存到成员余额中。请根据描述,完成案例中所有类的定义以及指定类之间的继承关系,并完成发红包的操作。
案例分析
- 根据描述分析,得出如下继承体系:
代码实现
定义User类
package MyCode.demo05; public class User { private String name; // 姓名 private int money; // 余额,也就是当前用户拥有的钱数 public User() { } public User(String name, int money) { this.name = name; this.money = money; } // 展示一下当前用户有多少钱 public void show() { System.out.println("我叫:" + name + ",我有多少钱:" + money); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getMoney() { return money; } public void setMoney(int money) { this.money = money; } }
定义Manager类
package MyCode.demo05; import java.util.ArrayList; // 群主的类 public class Manager extends User { public Manager() { } public Manager(String name, int money) { super(name, money); } public ArrayListsend(int totalMoney, int count) { // 首先需要一个集合,用来存储若干个红包的金额 ArrayList redList = new ArrayList<>(); // 首先看一下群主自己有多少钱 int leftMoney = super.getMoney(); // 群主当前余额 if (totalMoney > leftMoney) { System.out.println("余额不足"); return redList; // 返回空集合 } // 扣钱,其实就是重新设置余额 super.setMoney(leftMoney - totalMoney); // 发红包需要平均拆分成为count份 int avg = totalMoney / count; int mod = totalMoney % count; // 余数,也就是甩下的零头 // 除不开的零头,包在最后一个红包当中 // 下面把红包一个一个放到集合当中 for (int i = 0; i < count - 1; i++) { redList.add(avg); } // 最后一个红包 int last = avg + mod; redList.add(last); return redList; } }
定义Member类
package MyCode.demo05; import java.util.ArrayList; import java.util.Random; // 普通成员 public class Member extends User { public Member() { } public Member(String name, int money) { super(name, money); } public void receive(ArrayListlist) { // 从多个红包当中随便抽取一个,给我自己。 // 随机获取一个集合当中的索引编号 int index = new Random().nextInt(list.size()); // 根据索引,从集合当中删除,并且得到被删除的红包,给我自己 int delta = list.remove(index); // 当前成员自己本来有多少钱: int money = super.getMoney(); // 加法,并且重新设置回去 super.setMoney(money + delta); } }
定义测试类
package MyCode.demo05; import java.util.ArrayList; public class MainRedPacket { public static void main(String[] args) { Manager manager = new Manager("群主", 100); Member one = new Member("成员A", 0); Member two = new Member("成员B", 0); Member three = new Member("成员C", 0); manager.show(); // 100 one.show(); // 0 two.show(); // 0 three.show(); // 0 System.out.println("==============="); // 群主总共发20块钱,分成3个红包 ArrayListredList = manager.send(20, 3); // 三个普通成员收红包 one.receive(redList); two.receive(redList); three.receive(redList); manager.show(); // 100-20=80 // 6、6、8,随机分给三个人 one.show(); two.show(); three.show(); } }
代码执行后的结果