继承
定义:从已经存在的类中定义新的类,这个过程就叫做继承。也可以这么理解:新建的类属于原类的拓展,但是不会改变原有类的代码。
在学习继承的过程中有几个值得注意的点:
- 继承是类与类之间的关系。
- 新建的类就叫做子类(派生类或者拓展类),原来的类叫做父类(基类)。
- 继承关系使一个子类继承父类的特征,并且附加一些新特征。
对于父类和子类的理解
记忆方法:基就是地基是盖房子的第一步,所以是之前的就存在的,派生就是派生出新的被,肯定是后出现的,所以叫派生,在原来的类上派生出新的类,那么有原来的类才有后来派生的,有父亲才能有儿子,儿子是父亲生下来的,所以也可以叫父类和子类。
举个例子来说明一下就知道了:
比如有一个宠物游戏,每个宠物都有几个属性:名字,健康值,爱心值,自白,喂食,他们各自的品种等。假设要设计类建模像Dog和Penguin这样的宠物对象。宠物对象有许多共同的行为和属性。这样一个通用类Pet可以用来建模所有的宠物对象。
分析:Dog 和 Penguin他们都是宠物类中的两种,但是宠物不只这两种吧。无论是哪种宠物,都有公共特征:名字,健康值,爱心值,自白,喂食。不同的在于他们的品种等。因此父类就可以是Pet类,而Dog 和 Penguin类就是他的子类。
/**
* 宠物这一类事务共同的属性和行为抽取在一个通用的公共类-----Pet
* @author Tiger
*
*/
public class Pet {
//共同的特征---名字
private String name;
//共同的特征---健康值
private int health;
//共同的特征---爱心值
private int love;
public Pet(String name, int health, int love) {
System.out.println("pet---构造方法");
this.name = name;
this.health = health;
this.love = love;
}
/**
* 自白
*/
public void sayHello() {
System.out.print("我的名字叫" + this.name + ",我的健康值是" + health + ",我和主人的亲密度是" + love);
}
/**
* 吃东西
*/
public void eat() {
if(this.health <= 95) {
this.health += 3;
this.love -= 5;
}
}
/**
* 陪主人玩
*/
public void play() {
if(this.health > 60) {
this.health -= 5;
this.love += 3;
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getHealth() {
return health;
}
public void setHealth(int health) {
this.health = health;
}
public int getLove() {
return love;
}
public void setLove(int love) {
this.love = love;
}
}
以上就是Pet类,他的子类就是dog 和penguin 因此我们再创建两个类:
Dog类继承了Pet类所有可以访问的数据域和方法。除此之外,它还有个新的数据域strain以及与strain相关的setter和getter方法。
/**
* Dog扩展自通用的宠物类Pet(继承)
*
*/
public class Dog extends Pet{
//dog所特有的属性(成员变量)
private String strain; //dog的品种
//在子类中通过super()调用父类的构造方法
public Dog(String name,int health,int love,String strain) {
super(name,health,love);//调用父类的构造方法必须在第一句
this.strain = strain;
System.out.println("dog---构造方法");
}
/**
* super关键字
* 1、子类调用父类中的方法
* 2、在子类构造方法中调用父类的构造方法
*/
public void sayHello() {
super.sayHello();//子类调用父类中的方法
System.out.println(",我的类型是" + strain);
}
public String getStrain() {
return strain;
}
public void setStrain(String strain) {
this.strain = strain;
}
}
Penguin类继承了Pet类所有可以访问的数据域和方法。除此之外,它还有个新的数据域gender以及与gender相关的setter和getter方法。
/**
* 子类是父类的特殊化,每个子类的实例都是父类的实例。
*子类继承父类,使用关键字:extends
*/
public class Penguin extends Pet{
private String gender; //企鹅的特有成员变量 ------> 性别
public Penguin(String name,String gender) {
super(name,100,100);
this.gender = gender;
System.out.println("penguin---构造方法");
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
/**
* 为满足子类的需要,子类需要覆盖父类中定义的方法实现----------方法重写
* 1、访问修饰符:子类重写方法访问权限不能小于父类的访问修饰权限
* 2、返回值类型:一般具有相同的返回值类型,子类重写的方法返回值类型不能大于父类的返回值类型
* 3、异常类型:子类重写的方法不能抛出比父类更大类型的异常
* 4、仅当实例方法可访问时,才能进行方法重写
* 5、静态方法也能被继承,静态方法不能被覆盖。
* @Override 编译器可帮我们检查是否是符合方法重写要求的
*/
@Override
public void sayHello() {
System.out.println("我的名字叫" + this.getName() + ",我的健康值是" + getHealth() + ",我和主人的亲密度是" + getLove() + ",我的性别是:" + gender);
} //企鹅所特有的方法:撒娇 public void 撒娇(){ }
}
- 和传统的理解不同,子类并不是父类的一个子集。实际上,一个子类比它的父类包含更多的信息和方法。
- 父类中的私有属性在该类之外是不可访问的。因此,不能在子类中直接使用。但是,如果父类中定义了公共的setter/getter,那么可以通过这些公共的setter/getter来修改和访问它们
- 继承是用来为is-a关系建模的。不要仅仅为了重用方法而盲目扩展一个类。例如:尽管Person类和Tree类可以共享类似高度和重量这样的通用特性,但是从Person类扩展出Tree类是毫无意义的。一个父类和它的子类之间必须存在“是一种”(is-a)关系。
- 不是所有的is-a关系都该用继承来建模。例如:正方形是一种矩形,但是不应该定义一个Square类来扩展Rectangle类,因为width和height属性并不适合于正方形。
- java是不允许多重继承的。一个java类只能直接继承自一个父类。多重继承可以通过接口来实现。
Super关键字
Super关键字指代的是父亲,即子类可以通过该关键字继承父类普通方法和构造方法。
Super可以用于两种途径:
- 调用父类构造方法。
当子类重写了父类的方法时,如果想要在子类中调用父类中被重写的方法,可以使用super关键字。这通常用于在子类方法中实现多态,同时保留父类方法的行为。
//创建一个父类 parentclass
public class ParentClass {
public void show() {
System.out.println("Parent show");
}
}
//其子类 childclass 继承其父类中的一个方法show(),就要使用关键字super。
public class ChildClass extends ParentClass {
@Override
public void show() {
super.show(); // 调用父类的show方法
System.out.println("Child show");
}
}
- 访问父类的成员。
当子类需要访问父类的成员(包括属性和方法)时,可以使用super关键字。这通常用于在子类构造器中调用父类的构造器,或者在子类方法中访问父类的成员。
public class ParentClass {
public String name = "Parent";
}
public class ChildClass extends ParentClass {
public String name = "Child";
public void printName() {
System.out.println(super.name ); // 访问父类的name属性
}
}
构造方法链
在任何情况下,构造一个类的实例时,将会调用沿着继承链的所有父类的构造方法。当构造一个子类对象时,子类构造方法会在完成自己任务之前,首先调用它的父类构造方法。如果父类继承自其他类,那么父类构造方法又会在完成自己任务前调用它自己的父类的构造方法。这个过程持续到沿着这个继承体系结构的最后一个构造方法被调用为止。
如果没有被显式的调用,编译器将会自动添加super()作为构造方法的第一条语句。
方法重写
父类允许子类提供一个特定于其类的实现,以替换继承自父类的方法实现。在Java中,方法重写是实现多态性的关键机制之一。
出现的前提:有子类继承父类
出现的场景:当子类继承了父类的方法后发现父类的方法不适用于子类,则子类可以通过重写父类的方法。 在调用时,调用的是子类重写后的方法,而不是来自于父类的方法。
方法重写的规范
1)要求子类与父类的方法:返回值类型、方法名、形参列表必须完全相同。
2)子类的修饰符权限不能小于父类的修饰符权限。
3)若父类方法抛异常,那么子类方法抛的异常类型不能大于父类方法抛的异常类型。
4)子类可以重写父类的静态方法,但是必须是同为静态的。(对父类静态方法来说是隐藏,可以使用父类.静态方法的形式访问)
补充访问修饰符的权限大小:
Object类及其toString()方法
java中的所有类都继承自java.lang.Object类。toString()方法:
1)当我们打印一个引用数据类型的对象时,默认调用的是这个对象的toString();//Loan@15037e5
2)当我们重写了toString()后再打印该对象,会调用重写后的toString()。
3)像String、File、Date等类已经重写了toString方法。
多态
多态允许一个接口被不同的对象以不同的方式实现。多态体现为父类引用变量可以指向子类对象。
对多态的现实意义的理解:
- 现实事物经常会体现出多种形态,如学生,学生是人的一种,则一个具体的同学张三既是学生也是人,即出现两种形态。
- Java作为面向对象的语言,同样可以描述一个事物的多种形态。如Student类继承了Person类,一个Student的对象便既是Student,又是Person。
多态的定义格式和使用
父类类型型 变量名 = new 子类类型型();
静态绑定
静态绑定是在编译期间完成的绑定。这意味着编译器在编译代码时就已经确定了方法调用的具体实现。
动态绑定
动态绑定是在运行时完成的绑定。这意味着方法调用的具体实现是在程序运行时根据对象的实际类型来确定的。
技巧:编译看左边(父类型),运行看右边。
静态绑定和动态绑定的区别
- 程序在JVM运行过程中,会把类的类型信息、static属性和方法、final常量等元数据加载到方法区,这些在类被加载时就已经知道,不需对象的创建就能访问的,就是静态绑定的内容;
- 需要等对象创建出来,使用时根据堆中的实例对象的类型才进行取用的就是动态绑定的内容。
对象转换
在java中,每个对象变量都属于一个类型。将一个值存入变量时,编译器将检查是否允许该操作。将一个子类的引用赋给超类类型(向上转型),编译器是允许的。但将一个超类的引用赋给一个子类变量(向下转型),必须进行类型转换。
【子类 to 父类 ----------编译器允许 √ 父类 to 子类 ------------需要进行转换 ×】
向上转型是被允许的,向下转型就需要进行类型转换。
- 向上转型:多态本身就是向上转型过的过程
使用格式:父类类型 变量名=new 子类类型();
适用场景:当不需要面对子类类型时,通过提高扩展性,或者使用父类的功能就能完成相应的操作。
- 向下转型:一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用类型转为子类引用各类型
使用格式:子类类型 变量名=(子类类型) 父类类型的变量;
适用场景:当要使用子类特有功能时。
instanceof
作用:用来判断某个对象是否属于某种数据类型。
- 注意: 返回类型为布尔类型
Object类的equals方法
- equals不适用于基本数据类型,equals只适用于引用数据类型
- 如果不重写equals方法,则比较的是两个对象的地址值,等效于“==”.因此,应该根据需要在自己的客户类中重写equals方法。
- 像String、File、Date等类重写了Object的equals方法,比较的是两个String对象的具体的值,而不是地址值。
防止扩展和重写
- final关键字:最终的,可以用来修饰类、属性、方法
- final修饰类:这个类不能被继承
- final修饰方法:这个方法不能被重写,当一个方法所要体现的功能已经被确定,则用final修饰。
- final修饰属性:表示常量,则该常量必须有初始化值。