继承(Inheritance)是面向对象编程的三大特征之一,它让我们更加容易实现对于已有类的扩展、更加容易实现对于现实世界的建模。
1 .1 继承及其作用
继承让我们更加容易实现类的扩展。 比如,我们定义了人类,再定义Boy类就只需要扩展人类即可。实现了代码的重用,不用再重新发明轮子(don’t reinvent wheels)。
从英文字面意思理解,extends的意思是“扩展”。子类是父类的扩展。现实世界中的继承无处不在。比如:
上图中,哺乳动物继承了动物。意味着,动物的特性,哺乳动物都有;在我们编程中,如果新定义一个Student类,发现已经有Person类包含了我们需要的属性和方法,那么Student类只需要继承Person类即可拥有Person类的属性和方法。
public class Animal {//extends Object
private String color;//颜色
private int age;//年龄
public Animal() {
super();
}
public Animal(String color, int age) { //alt+insert
this.color = color;
this.age = age;
}
public void eat(){
System.out.println("eating ..........");
}
public void sleep(){
System.out.println("sleeping............");
}
public void introduce(){
System.out.println(color+" "+age);
}
}
public class Dog extends Animal{
private String nickName;//昵称
public Dog() {
}
public Dog(String color,int age){
}
public Dog(String color,int age,String nickName){
// this.color = color;
// this.age = age;
super(color,age);
this.nickName = nickName;
}
public void guard(){
System.out.println("Dog guarding......");
}
}
public class Cat extends Animal {
private int eysSight;//视力
public Cat() {
super();//默认调用父类的无参数构造方法
}
public Cat(String color, int age, int eysSight) {
super(color,age);
this.eysSight = eysSight;
}
public void grabMouse(){
System.out.println("Cat grab mouse..........");
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog("黑色",3,"旺财");
dog.eat();//从父类继承的方法
dog.sleep();//从父类继承的方法
dog.introduce();//从父类继承的方法
dog.guard();//子类特有的方法
Cat cat = new Cat("花色",3,5);//alt+enter
cat.eat();
cat.sleep();
cat.introduce();
cat.grabMouse();
}
}
继承使用要点
- 父类也称作超类、基类。子类:派生类等。
- Java中只有单继承,没有像C++那样的多继承。多继承会引起混乱,使得继承链过于复杂,系统难于维护。
- 子类继承父类,可以得到父类的全部属性和方法 (除了父类的构造方法),但不见得可以直接访问(比如,父类私有的属性和方法)。
- 如果定义一个类时,没有调用extends,则它的父类是:java.lang.Object。
1.2方法重写
父类的方法introduce()已经无法满足子类的需求,怎么办?同理,Object类的toString()已经无法满足Animal类、Dog类的需求,怎么办?可通过方法重写(override)解决,或者称为方法覆盖。
方法重写示例
public class Animal {//extends Object
protected String color;//颜色
private int age;//年龄
public Animal() {
super();
}
public Animal(String color, int age) {
this.color = color;
this.age = age;
}
public void introduce(){
System.out.println(color+" "+age);
}
@Override
public String toString() {
//return super.toString();
return "Animal[color="+color+",age="+age+"]";
}
}
public class Dog extends Animal{
private String nickName;//昵称
public Dog() {
}
public Dog(String color, int age){
}
public Dog(String color, int age, String nickName){
// this.color = color;
// this.age = age;
super(color,age);
this.nickName = nickName;
}
public void introduce(){
//this.introduce();
//super.introduce();
System.out.println(color+" "+this.getAge()+" "+nickName);
}
public void guard(){
//this.guard();
//super.guard();
System.out.println("Dog guarding......");
}
@Override
public String toString() {
//return super.toString()+" "+nickName;
return "Dog[name="+color+",age="+getAge()+
",nickName="+this.nickName+"]";
}
}
* 方法重写的规则:
* 1.方法名称和参数列表必须相同。
* 可以使用@override检测是否重写成功
* 2.子类返回值范围必须【小于等于】父类返回值范围
* 3.子类方法的权限必须【大于等于】父类的权限的修饰符
* 修饰符权限:public>protected>(default)什么都不写,默认>private.
重写注意:
1. 方法的返回值类型是【基本数据类型】 则必须完全一致
方法的返回值类型是【引用数据类型】 子类可以是父类返回值的孩子 <=父类
2. 权限修饰符 不能比父类更加严格
3.方法名字必须一致
4.方法的参数 也要保持一致
易混点:方法重载和方法重写:
总 | 方法重载和方法重写(覆盖)是面向对象中两个重要概念,其实这两个概念之间没有什么关系,但是毕竟都是关于方法的,毕竟容易引起混淆。对此我也做了一些归纳,感觉能够把这两个概念很好的区分开。我打算从总体区别、细节区别两个方面来说明。 | ||||||||||||||||||||||||||||||
分 | 总体的区别:最主要的区别,是解决的问题不同,即作用不同。
细节的区别:一个方法的声明自左向右包括权限修饰符、方法返回值、方法名、参数列表、抛出的异常类型等。下面从这几方面说明区别
| ||||||||||||||||||||||||||||||
总 | 重载实例:构造方法重载、println()方法重载 重写实例:Object类的toString()、equals()、hashCode()等都可以被子类重写 | ||||||||||||||||||||||||||||||
可选 |
|
2.1 Object类
Object类是所有Java类的根基类,也就意味着所有的Java对象都拥有Object类的属性和方法。如果在类的声明中未使用extends关键字指明其父类,则默认继承Object类。
方法摘要 | |
boolean | equals(Object obj) 指示其他某个对象是否与此对象“相等”。 |
Class<?> | getClass() 返回此 Object 的运行时类。 |
int | hashCode() 返回该对象的哈希码值。 |
void | notify() 唤醒在此对象监视器上等待的单个线程。 |
void | notifyAll() 唤醒在此对象监视器上等待的所有线程。 |
String | toString() 返回该对象的字符串表示。 |
void | wait() 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。 |
void | wait(long timeout) 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。 |
void | wait(long timeout, int nanos) 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。 |
以上方法是Object类所有的public方法。
方法摘要 | |
protected Object | clone() 创建并返回此对象的一个副本。 |
protected void | finalize() 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。 |
Object类的Protected方法。
扩展:native关键字
|
2.2 成员变量的隐藏
如果父类和子类中有同名的成员变量,不存在变量的重写,分别占有自己的空间。子类的成员变量优先,称为成员变量的隐藏。
示例 成员变量的隐藏
public class Animal {
String color="Animal的color";
int age;
public String getColor(){
return color;
}
}
public class Dog extends Animal {
String color ="Dog的color";
String nickName;
public String getColor(){
return color;
}
public String getSuperColor(){
return super.getColor();
}
public void show(){
String color = "方法的color";
System.out.println(color);
System.out.println(this.color);
System.out.println(super.color);
}
public static void main(String[] args) {
Dog dog = new Dog();
dog.show();
System.out.println(dog.getColor());
System.out.println(dog.getSuperColor());
}
}
2.3 继承情况下构造方法的调用过程
继承条件下构造方法的执行顺序
- 构造方法的第一条语句默认是super(),含义是调用父类无参数的构造方法
- 构造方法的第一条语句可以显式的指定为父类的有参数构造方法:super(.....)
- 构造方法的第一条语句可以显式的指定为当前类的构造方法:this(.....)
注意事项
- 每个类最好要提供无参数的构造方法
- 构造方法的第一条语句可以是通过super或者this调用构造方法,须是第一条语句
- 构造方法中不能同时使用super和this调用构造方法,并不是说不能同时出现this和super
示例 继承情况下构造方法的调用过程
public class Animal {
String color;
private int age;
public Animal(){
super();
}
public Animal(String color,int age){
this.color = color;
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Dog extends Animal {
private String nickName;
private String type;
public Dog(){
super();
}
public Dog(String color,int age,String nickName){
//super();
super(color,age);
//this(color,age,nickName,"aaa");
this.nickName = nickName;
}
public Dog(String color,int age,String nickName,String type){
//super();
//super(color,age);
this(color,age,nickName);
//this.nickName = nickName;
this.type = type;
}
public String toString() {
return this.color+" "+ this.getAge()+" "+this.nickName+" "+this.type;
}
public static void main(String[] args) {
Dog dog = new Dog("黑色",3,"旺财","泰迪");
System.out.println(dog.toString());
}
}
重写toString():
* 直接输出属性的信息 不再 去额外定义一个方法 进行信息展示
3.1 super关键字
前面的示例中已经多次使用到了super关键字。
super“可以看做”是直接父类对象的引用。每一个子类对象都会有一个super引用指向其直接父类对象。
使用super可以
- 调用成员变量 super.color
- 调用成员方法 super.introduce();
- 调用构造方法 super(color,age);
注意
- 使用super调用普通方法,语句没有位置限制,可以在子类中随便调用。
- 在一个类中,若是构造方法的第一行代码没有显式的调用super(...)或者this(...);那么Java默认都会调用super(),含义是调用父类的无参数构造方法。这里的super()可以省略。
3.2 ==和equals方法
“==”代表比较双方是否相同。如果是基本类型则表示值相等,如果是引用类型则表示地址相等即是同一个对象。
Object类中定义有:public boolean equals(Object obj)方法,提供定义“对象内容相等”的逻辑。比如,判断两个Dog是否是一个Dog,要求color、age、nickName等所有属性都相同。
Object 的 equals 方法默认就是比较两个对象的hashcode,是同一个对象的引用时返回 true 否则返回 false。显然,这无法满足子类的要求,可根据要求重写equals方法。
/*
问题: 当 属性值完全相同时 认为是同一个对象
*/
public class TestPerson3 {
public static void main(String[] args) {
Person p1 = new Person("杜甫",18);
Person p2 = new Person("杜甫",18);
System.out.println(p1==p2);//false
System.out.println(p1.equals(p2));//true false
Person p3 = p1;
System.out.println(p1.equals(p3));//true
Person p4 = null;
System.out.println(p1.equals(p4));//false
}
}
public class Person {
String name;
int age;
public Person(){}
public Person(String name,int age){
this.age = age;
this.name = name;
}
@Override
public boolean equals(Object obj) {
//1.当两者的地址相同时肯定相等
if(this==obj){
return true;
}
//2.当obj==null时 则肯定不相等
if(obj==null){
return false;
}
/* double d = 3.14;
int i = (int)d;*/
//比较 属性值
Person p = (Person)obj;
//比较 属性值
if(this.age==p.age && this.name.equals(p.name)){
return true;
}
return false;
}
@Override
public String toString() {
public class Man extends Person {
String sex;
int score;
public Man() {
}
public Man(String sex, int score) {
this.sex = sex;
this.score = score;
}
public Man(String sex, int score, String name, int age) {
super(name, age);
this.sex = sex;
this.score = score;
}
@Override
public boolean equals(Object obj) {//Object obj = man2; Man
//比较父亲的属性
boolean flag = super.equals(obj);
if (flag) {//如果父亲属性值比较成功 再进行 子类元素的比较
Man man = (Man) obj;
if (this.score == man.score && this.sex.equals(man.sex)) {
return true;
}
}
return false;
}
@Override
public String toString() {
return super.toString() + "sex = " + sex + ",score =" + score;
}
}
public class TestMan {
public static void main(String[] args) {
Man man = new Man("男", 88);
System.out.println(man.toString());
Man man1 = new Man("男", 99, "白居易", 99);
Man man2 = new Man("男", 99, "白居易", 99);
boolean flag = man1.equals(man2);
System.out.println(flag);
System.out.println(man2);
}
}
2.3 组合
继承和组合是复用代码的两种方式;
继承 is-a Dog is-a Animal Cat is-a Animal
组合 has-a Computer has-a cpu memery mainBoard。
扩展: 面向对象的设计原则之一:组合聚合复用原则(优先使用组合,而不是继承)
|
示例:组装一台电脑 ,电脑有CPU 主板 GPU 屏幕 等等 用 has a的关系组一台电脑
CPU:
public class CPU {
String name;
public CPU(String name){
this.name = name;
}
public void start(){
System.out.println(name+" CPU开始运行");
}
}
GPU:
public class GPU {
String name;
public GPU(String name){
this.name = name;
}
public void start(){
System.out.println(name+" GPU 开始工作ing");
}
}
主板:
public class MainBoard {
String name;
public MainBoard(String name){
this.name = name;
}
public void start(){
System.out.println(name +" 主板开始执行。。。。。。");
}
}
屏幕:
public class Screen {
private String name;
public Screen(String name){
this.name = name;
}
public void start(){
System.out.println(name+ " Screen 开始执行。。。。。。");
}
}
电脑类:
public class Computer {
CPU cpu;
MainBoard mainBoard;
Screen screen;
GPU gpu;
public Computer(){}
public Computer(CPU cpu,MainBoard mainBoard,Screen screen,GPU gpu){
this.cpu = cpu;
this.gpu = gpu;
this.mainBoard = mainBoard;
this.screen = screen;
}
public void start(){
cpu.start();
mainBoard.start();
gpu.start();
screen.start();
}
}
组合
/*
组合: has a的关系
把一个类作为另一个类的属性。
电脑 has :
CPU AMD intel
mainBoard
memory
GPU
Screen
*/
public class Test {
public static void main(String[] args) {
//买配件
CPU cpu = new CPU("AMD");
Screen screen = new Screen("京东方");
GPU gpu = new GPU("雷蛇");
MainBoard mainBoard = new MainBoard("华硕");
//装电脑
Computer computer = new Computer(cpu,mainBoard,screen,gpu);
//启动
computer.start();
}
}