一、继承
1.1 引入继承
对类和对象做了解后我们不难发现,类是对现实生活的实体进行描述,而类实例化成对象之后,则可以用对象来表示实体。但是现实世界往往更加复杂,事物之间往往存在某种关联,设计程序就需要考虑这些
例如:猫和狗,他们都是动物
下面用Java来对猫和狗进行描述:
class Cat{
public String name;
public int age;
public float weight;
public void eat(){
System.out.println("吃饭");
}
public void sleep(){
System.out.println("睡觉");
}
public void miao(){
System.out.println("喵喵");
}
}
class Dog{
public String name;
public int age;
public float weight;
public void eat(){
System.out.println("吃饭");
}
public void sleep(){
System.out.println("睡觉");
}
public void wang(){
System.out.println("汪汪");
}
}
通过上述代码发现,猫和狗的类中有很多重复:
那能否将这些共性抽取呢?面向对象思想中提出了继承的概念,专门用来进行共性抽取,实现代码复用。
1.2 继承的概念
继承是面向对象软件技术当中的一个概念,与多态、封装共为面向对象的三个基本特征。继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等。
上述猫和狗的例子可以抽取共性,然后用继承的思想来达到共用:
上述图示,Dog和Cat都继承了Animal类,其中:Animal类称为父类/基类或超类,Dog和Cat可以称为Animal的
子类/派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可
问题:子类继承了父类的什么?
继承了父类除构造方法外所有的
1.3 继承的语法
在Java中表示继承关系要用到关键字extends
Eg:
class Animal{
public String name;
public int age;
public float weight;
public void eat(){
System.out.println("吃饭");
}
public void sleep(){
System.out.println("睡觉");
}
}
class Cat extends Animal{ //子类 extends 父类
public void miao(){
System.out.println("喵喵");
}
}
class Dog extends Animal{ //子类 extends 父类
public void wang(){
System.out.println("汪汪");
}
}
public class Test1 {
public static void main(String[] args) {
Dog dog=new Dog();
System.out.println(dog.name);
System.out.println(dog.age);
System.out.println(dog.weight);
dog.eat();
dog.sleep();
dog.wang();
}
}
运行结果:
Dog类中并没有定义任何成员变量,name和age属性是从父类Animal中继承下来的,eat()和sleep()方法也是从Animal中继承下来的
即:子类会将父类中的成员变量或者成员方法继承到子类中
1.4 父类成员访问
1.4.1 子类中访问父类的成员变量
a、子类和父类不存在同名的变量成员
class Fa{
int a;
int b;
}
class S1 extends Fa{
int c;
public void func1(){
a=10;//访问继承的a
b=10;//访问继承的b
c=30;//访问自己的c
}
}
b、子类和父类存在同名的变量成员
class Fa{
int a;
int b;
int c;
}
class S1 extends Fa{
int a; // 与父类中成员a同名,且类型相同
char b; // 与父类中成员b同名,但类型不同
public void func1(){
a=10;
b=20;
//如果访问的成员变量与父类中成员变量同名,则优先访问自己的
c=30;
//如果访问的成员变量子类中无,则访问父类继承下来的
}
}
成员变量访问遵循就近原则,自己有优先自己的,如果没有则访问父类中的
1.4.2 子类中访问父类的成员方法
a、成员方法名字不同
class Faa{
public void func1(){
System.out.println("父类中的func1");
}
}
class So1 extends Faa{
public void func2(){
System.out.println("子类中的func2");
}
public void func3(){
func1();//访问父类中的func1
func2();//访问子类自己的func2
}
}
a、成员方法名字相同
class Faa{
public void func1(){
System.out.println("父类中的func1");
}
public void func2(){
System.out.println("父类中的func2");
}
}
class So1 extends Faa{
public void func1(int n){
System.out.println("子类中的func1");
}
public void func2(){
System.out.println("子类中的func2");
}
public void func3(){
func1(); // 没有传参,访问父类中的func1()
func1(10); //传递int参数,访问子类中的func1()
func2(); //直接访问,则永远访问到的都是子类中的func2(),父类的无法访问到
}
}
- 通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到
则访问,否则编译报错 - 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用
方法适传递的参数选择合适的方法访问,如果没有则报错
但是,如果子类中存在与父类中相同的成员时,那如何在子类中访问父类相同名称的成员呢?
这里就要用到super关键字
1.5 super关键字
主要作用:在子类方法中访问父类的成员
class Faa{
int a;
int b;
public void func1(){
System.out.println("父类中的func1");
}
public void func2(){
System.out.println("父类中的func2");
}
}
class So1 extends Faa{
int a;
char b;
public void func1(int n){ // 与父类中func1()构成重载
System.out.println("子类中的func1");
}
public void func2(){ // 与父类中func1()构成重写
System.out.println("子类中的func2");
}
public void func3(){
// 对于同名的成员变量,直接访问时,访问的都是子类的
a=10;
b=20;
// 访问父类的成员变量时,需要借助super关键字
// super是获取到子类对象中从基类继承下来的部分
super.a=100;
super.b=200;
// 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法
func1();
func1(1);
// 如果在子类中要访问重写的基类方法,则需要借助super关键字
func2();
super.func2();
}
}
注意: 只能在非静态方法中使用
1.6 子类构造方法
子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法
class Fb{
public Fb(){
System.out.println("Fb()");
}
}
class Sb extends Fb{
public Sb(){
// super(); // 注意子类构造方法中默认会调用基类的无参构造方法:super()
// 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句并且只能出现一次
System.out.println("Sb()");
}
}
public class Test4 {
public static void main(String[] args) {
Sb sb=new Sb();
}
}
运行结果:
注意:
- 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构
造方法 - 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的
父类构造方法调用,否则编译失败。 - 在子类构造方法中,super(…)调用父类构造时,必须是子类构造函数中第一条语句。
- super(…)只能在子类构造方法中出现一次,并且不能和this同时出现
1.7 super和this
用法:
1.8 初始化
1.8.1 实例代码块和静态代码块在没有继承关系时的执行顺序:
class Person{
public String name;
public int age;
public Person(String name,int age){
this.name=name;
this.age=age;
System.out.println("构造方法执行");
}
{
System.out.println("实例代码块执行");
}
static {
System.out.println("静态代码块执行");
}
}
public class Test1 {
public static void main(String[] args) {
Person person1=new Person("Ethan",18);
System.out.println("_____________________________");
Person person2=new Person("Jack",18);
}
}
执行结果:
通过上述代码可以发现:
1、静态代码块先执行,并且只执行一次,在类加载阶段执行
2、当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行
1.8.2 继承关系上的执行顺序
class Person{
public String name;
public int age;
public Person(String name,int age){
this.name=name;
this.age=age;
System.out.println("Person构造方法执行");
}
{
System.out.println("Person实例代码块执行");
}
static {
System.out.println("Person静态代码块执行");
}
}
class Student extends Person{
public Student(String name,int age){
super(name,age);
System.out.println("Student构造方法执行");
}
{
System.out.println("Student实例代码块执行");
}
static {
System.out.println("Student静态代码块执行");
}
}
public class Test1 {
public static void main(String[] args) {
Student student1=new Student("Ethan",18);
System.out.println("_______________________________");
Student student2=new Student("jack",18);
}
}
执行结果:
通过分析执行结果,得出以下结论:
1、父类静态代码块优先于子类静态代码块执行,且是最早执行
2、父类实例代码块和父类构造方法紧接着执行
3、子类的实例代码块和子类构造方法紧接着再执行
4、第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行
1.9 protected关键字
在类和对象部分中,为了实现封装特性,Java中引入了访问限定符:
下面用代码进行演示:
//B1中
public class F {
private int a;
protected int b;
public int c;
int d;
}
//B1中
//同一个包中的子类
public class S extends F{
public void func1(){
//super.a=10; error 父类private成员在相同包子类中不可见
super.b=20; // 父类中protected成员在相同包子类中可以直接访问
super.c=30; // 父类中public成员在相同包子类中可以直接访问
super.d=40; // 父类中默认访问权限修饰的成员在相同包子类中可以直接访问
}
}
//B2中
//不同包中的子类
public class S2 extends F {
public void func1(){
// super.a = 10; error 父类中private成员在不同包子类中不可见
super.b=20; // 父类中protected修饰的成员在不同包子类中可以直接访问
super.c=30; // 父类中public修饰的成员在不同包子类中可以直接访问
//super.d=40; error 父类中private成员在不同包子类中不可见
}
}
//B2中
//不同包中的非子类
public class Fs {
public static void main(String[] args) {
S2 s2=new S2();
s2.func1();
//System.out.println(s2.a); error 父类中private成员在不同包其他类中不可见
//System.out.println(s2.b); errer 父类中protected成员在不同包其他类中不能直接访问
System.out.println(s2.c); // 父类中public成员在不同包其他类中可以直接访问
//System.out.println(s2.d); error 父类中默认访问权限修饰的成员在不同包其他类中不能直接访问 }
}
}
注意:父类中private成员变量虽然在子类中不能直接访问,但是也继承到子类了
1.10 继承方式
1.10.1 单继承
public class A{
......
}
public class B extends A{
......
}
1.10.2 多层继承
public class A{
......
}
public class B extends A{
......
}
public class C extends B{
......
}
1.10.3 不同类继承同一个类
public class A{
......
}
public class B extends A{
......
}
public class C extends A{
......
}
1.10.4 多继承(Java不支持)
public class A{
......
}
public class B{
......
}
public class C extends A,B{
......
}
1.11 final 关键字
final关键可以用来修饰变量、成员方法以及类
- 修饰变量或字段,表示常量(即不能修改)
final int a = 10;
a = 20; // 编译出错
- 修饰类:表示此类不能被继承
final public class A{
...
}
public class B extends A{
...
}
//error 无法继承
- 修饰方法:表示该方法不能被重写
public class A{
final public void func(){
......
}
}
public class B extends A{
public void func(){
......
}
}
//error 该方法无法重写
1.12 继承与组合
和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法
(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段
继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物
组合表示对象之间是has-a的关系,比如:汽车和其轮胎、发动机、方向盘、车载系统等的关系就应该是组合,因为汽车是有这些部件组成的。
// 轮胎类
class Tire{
...
}
// 发动机类
class Engine{
...
}
// 车载系统类
class VehicleSystem{
...
}
class Car{
private Tire tire; // 可以复用轮胎中的属性和方法
private Engine engine; // 可以复用发动机中的属性和方法
private VehicleSystem vs; // 可以复用车载系统中的属性和方法 // ...
}
// 奔驰是汽车
class Benz extend Car{
//将汽车中包含的:轮胎、发送机、车载系统全部继承下来
}
二、多态
2.1 多态的概念
可以理解为去完成某个行为,当不同的对象去完成时会产生出不同的状态
比如吃:狗狗会吃狗粮,而猫咪会吃猫粮
2.2 多态的实现条件
1. 必须在继承体系下
2. 子类必须要对父类中方法进行重写
3. 通过父类的引用调用重写的方法
class Animal {
String name;
int age;
public Animal(String name,int age){
this.name=name;
this.age=age;
}
public void eat(){
System.out.println(name+"eat");
}
}
class Cat extends Animal{
public Cat(String name,int age){
super(name,age);
}
@Override
public void eat() {
System.out.println(name+"eat fish");
}
}
class Dgo extends Animal{
public Dgo(String name,int age){
super(name, age);
}
@Override
public void eat() {
System.out.println(name+"eat bone");
}
}
//————————————————————————————————————————————————————————————————————————————————————————————————————————————————
public class Test {
// 编译器在编译代码时,并不知道要调用Dog 还是 Cat 中eat的方法
// 等程序运行起来后,形参a引用的具体对象确定后,才知道调用那个方法
// 注意:此处的形参类型必须时父类类型才可以
public static void eat(Animal a){
a.eat();
}
public static void main(String[] args) {
Cat cat=new Cat("mimi",2);
Dgo dog=new Dgo("maomao",3);
eat(cat);
eat(dog);
}
}
运行结果:
在上述代码中, 分割线上方的代码是 类的实现者 编写的, 分割线下方的代码是 类的调用者 编写的.
2.3 重写
返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定
于自己的行为。 也就是说子类能够根据需要实现父类的方法。
2.3.1 重写的规则
1、子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致
2、被重写的方法返回值类型可以不同,但是必须是具有父子关系的
3、访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方
法就不能声明为 protected
4、父类被static、private修饰的方法、构造方法都不能被重写
2.3.2 重写与重载的区别
2.4 向上转型和向下转型
2.4.1 向上转型
父类引用 引用子类对象
Animal animal = new Cat("mimi",2);
使用演示:
1、直接赋值
class Animal1{
protected String name;
public Animal1(String name){
this.name=name;
}
public void eat(){
System.out.println(name+" Animal1::eat()");
}
}
class Cat extends Animal1 {
public Cat(String name){
super(name);
}
int count;
}
//Main_____________________________________________________________
public class TestDou {
public static void main(String[] args) {
//向上转型----》父类引用 引用子类对象
Animal1 animal1=new Cat("mimi");
System.out.println(animal1.name);
//animal1.count; error! 向上转型后 通过父类的引用只能访问父类自己的属性或方法
}
}
2、传参赋值
class Animal1{
protected String name;
public Animal1(String name){
this.name=name;
}
public void eat(){
System.out.println(name+" Animal1::eat()");
}
}
class Cat extends Animal1 {
public Cat(String name){
super(name);
}
int count;
}
//Main_____________________________________________________________
public class TestDou {
public static void func(Animal1 animal1){ //父类引用 引用子类对象
animal1.eat();
}
public static void main(String[] args) {
Cat cat=new Cat("mimi");
func(cat);
}
}
3、返回值
class Animal1{
protected String name;
public Animal1(String name){
this.name=name;
}
public void eat(){
System.out.println(name+" Animal1::eat()");
}
}
class Cat extends Animal1 {
public Cat(String name){
super(name);
}
int count;
}
//Main_____________________________________________________________
public class TestDou {
public static Animal1 func1(){
return new Cat("mimi");
}
public static void main(String[] args) {
Animal1 animal1=func1();
animal1.eat();
}
}
2.4.2 向下转型
将父类引用赋值给 子类引用
向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入
了 instanceof ,如果该表达式为true,则可以安全转换
if(animal instanceof Cat){
Cat cat=(Cat)animal; //将父类引用赋值给 子类引用
cat.eat();
}
2.5 运行时绑定
运行时绑定(动态绑定):父类引用 引用子类对象。同时,通过父类引用调用同名的覆盖方法,此时就会发生运行时绑定
编译的时候调用的是父类的eat() 但是运行的时候调用的是cat的eat(),故称为动态时绑定
Over————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
My girlfriend