继承-------对共性抽取,从而达到对代码进行重用,实现多态的基础
定义:是面向对象的过程中是代码可以进行复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生的新的类,叫做派生类/子类
1)继承我们是使用extends关键字我们的子类会将父类除构造方法之外的字段和属性全部继承,在父类中被private修饰的字段是可以继承于子类的,但是不可以访问;
2)当我们的子类继承父类之后,必须要添加自己特有的成员,体现出与基类的不同,否则就没有必要进行继承了
3)JAVA是属于单继承的,只能继承一个类
在这里面我们还是需要注意一些问题:(没有涉及到向上转型或者向下转型)
当我们的父类和子类中的都拥有同名的变量的时候,优先访问子类自己的,子类没有在去访问父类的,如果我们非要进行访问父类的同名字段,我们就可以通过super来进行访问,因为成员变量遵循就近原则
1)在我们的子类方法中通过this关键字或者通过子类对象来进行访问成员的时候,我们会优先访问自己的成员变量
2)如果我们的访问的成员变量子类中没有,那么我们就进行访问父类中所继承下来的,如果说我们的父类中也没有定义,那么我们就直接编译报错
3)如果说我们的访问的成员变量与我们的父类的成员变量同名,那么我们就直接优先访问自己的
4)我们如果说我们的子类对象访问父类和子类的不同名的方法的时候,我们优先在子类里面找,找到就访问,找不到就去父类里面找,如果说我们的父类也没有那么就直接报错
class Father{
public void run(int a){
System.out.println("我是父类的run方法");
}
}
class Child extends Father{
public void run(){
System.out.println("我是子类的run方法");
}
}
public class UserController{
public static void main(String[] args) {
Child child=new Child();
child.run(9);
child.run();
}
}
5)通过子类对象访问父类和子类同名的方法的时候,如果父类和子类同名方法的参数列表不同,构成重载,那么我们直接根据调用方法选择进行传递的参数选择不同的方法,如果没有就进行直接报错
6)我们通过子类对象进行访问父类和子类的同名方法的时候,如果父类和子类的同名方法的参数列表,返回值都相同,那么我们直接遵循就近原则,直接访问子类中的方法,不会访问父类中的方法
7)在Java中是单继承,不支持多继承的,不能继承两个类以上的类
8)此时我们虽然执行了父类的构造方法,但是却并没有生成父类的对象,在这里这是为了帮助子类初始化从父类那里面继承过来的属性
class Father{ public String name; public int age; public Father(String name,int age){ this.name=name; this.age=age; } } class Child extends Father{ public Child(){ super("李佳伟",90);//调用父类带有两个参数的构造方法 } }
super()在子类帮助父类进行构造的时候只能放在构造方法里面,况且只能放在构造方法的第一行,不能出现在static代码块里面
主要是我们在设计不好的时候,或者我们因场景需要,父类和子类中可能存在相同的名字的成员,我们要想在子类方法中访问父类的同名成员的时候,我们该怎么操作呢?我们直接访问是访问不到的,因为局部有限的原则,我们会优先访问子类中的成员
1)所以我们在Java中提供了super关键字,该关键字的主要作用是,在我们的子类方法中访问我们的父类的实例属性和方法的
2)在子类中,如果没有显式指定super()方法,那么子类会隐藏生成一个super()方法,用来调用父类无参的构造方法,这就是咱们每一个类在实例化的时候之所以可以调用到Object类的原因,就是默认是super方法起作用了,super()和this()必须使用都放在第一行
3)为什么要把super()方法放到首行呢?因为只要将super()方法放到首行,那么在实例化子类的时候才可以保证父类已经被初始化过了,我们在进行调用子类的构造方法的时候,实例化子类对象的时候,一定要先帮助父类进行构造,先帮助父类进行初始化操作,况且super()一定放在第一行
1)super(),调用父类的构造方法,帮助父类进行构造,必须放到第一行
2)super.data;访问父类的属性
3)super.func(),访问父类的方法
this表示当前对象的引用
1)this()调用自己的构造方法
2)this.data调用自己的属性
3)this.func(),调用当前对象的方法
总结:
1)虽然在子类的构造方法中并没有写任何有关于基类构造的代码,但是我们再进行构建子类对象的时候,却先进行执行父类的构造方法,因为在子类中的成员是有两部分构成的,基类继承下来的以及子类中新增加的部分,父类子类肯定是先有父再有子,所以我们在构建对象的时候,肯定是先有父亲,再有儿子
2)所以在我们进行创建子类对象的时候,就要先进行调用基类的构造方法,将基类继承下来的成员构造完整,然后在进行调用子类自己的构造方法,将子类自己新增加的成员初始化完整
3)如果说在父类中显示定义无参或者是默认的构造方法,在我们的子类构造方法中的第一行是默认有隐含的super调用,即调用基类的构造方法
4)如果说父类的构造方法是带有参数的,此时需要用户为子类显示定义构造方法,并且在子类构造方法中选择合适的父类构造方法进行调用,否则会编译失败
5)在我们的子类构造方法中,super()进行调用父类构造的时候,必须是子类构造函数中的第一条语句
6)super()只能在子类构造方法中出现一次,并且不能和this一起出现
protected关键字
子类继承了出构造方法的所有方法,但是如果父类的字段中出现了有private修饰的字段,此时子类就无法访问了,那么我们如何既在子类中可以访问父类用private修饰的字段,又可以还不改变它封装的本质呢?这是就要用到protected关键字
package PACKAGE; class father{ public int id=90; public String name; public int age; public father(String name,int age){ this.name=name; this.age=age; } public void run() { System.out.println("我是父亲的run方法"); } } class child extends father{ public child(String name,int age){ super(name,age); super.run(); System.out.println(super.id); } } public class Servlet { public static void main(String[] args) { child t1=new child("李佳伟",23); } } 打印结果: 1)我是父类的构造方法 2)90
protected关键字只能在同一个包中进行访问,如果实在不同包中进行访问那么就需要必须有父子类的继承关系
class Father{ protected String name="A"; } class Child extends Father{ public void run(){ System.out.println(name); } }
this和super有什么区别?
相同点:都是我们Java中的关键字,都是只能在类的非静态方法中使用,用来访问非静态成员方法和字段,在我们的构造方法进行调用的时候,我们必须是构造方法中的第一条语句,并且不能同时存在
不同点:
1)指代对象不同:this是表示当前对象的引用,当前对象是指调用实例方法的对象,super相当于是子类对象从父类中继承下来的部分成员的引用(注意在这里面并不是父类的引用,子类继承父类并没有创建父类对象)
2)查找范围不同:在非静态成员方法中,this是用来访问本类的方法和属性,子类对象中如果没有重名现象的发生,this也是可以访问父类继承下来的方法和属性,重名的话优先访问子类属性,子类没有再去父类中寻找,但是super只能访问父类继承下来的方法和属性,甚至this还可以访问父类的方法,this会先从本类中进行查找,查找不到再去访问父类的方法
3)在我们的构造方法中,this()用于调用本类构造方法,super()用于调用父类构造方法,两者调用不能同时在构造方法中出现
4)本类属性赋值不同:this可以为本类的实例属性赋值,那么super不能使用此功能
5)this可用于synchronized使用,表示给该对象加锁
方法重写的注意事项:
1)方法重写是一种语言特性,他是多态的具体表现,它允许子类重新定义父类中已有的方法,其子类中的方法名,参数类型和个数,都必须和父类保持一致;
2)子类权限控制符不能变小,public>protected>default>private,子类的访问修饰限定符必须要大于等于父类;
3)子类返回值类型只能变小,Number类是Long的父类,和父类的类型返回保持一致也是可以的,如果将子类的返回类型变大就会出现报错了,比如说父类返回了Number类型,但是子类返回了Object类型,要构成父子类关系;
package Demo; class Father{ public Number run() { System.out.println(1); return 8; } } class Child extends Father{ public Integer run(){ return 1; } }
4)在方法名后面的抛出异常的类型只能变小,如果子类抛出的异常类型比父类大,那么程序会出现报错,也就是说子类方法抛出的异常类型比父类方法抛出的异常类型大,那么程序就会出现报错,所以说要保持父类和子类抛出的异常类型相同;
package Demo; class Father{ public Number run() throws NumberFormatException { System.out.println(1); return 8; } } class Child extends Father{ public Integer run()throws Exception{ return 1; } }
5)方法名,参数类型和个数必须和父类方法保持一致;
int 的默认值是0
String的默认值是空
char 类型的默认值是 '/uoooo’
double和float的默认值是0.0
boolean的默认值是false
1.1)方法重写是多态的具体表现,是允许子类重新定义父类已有的方法,况且子类的方法名,参数类型和个数都要和父类保持一致,比较经典的是Object类中的equals方法,需要重写的方法,是不可以被final修饰的,被final修饰之后,他是密封方法,不可以进行修改,非静态,非private,非final,非构造方法,非static修饰的方法,加上一个@Override注解表示重写
1.2)方法重载指的是在同一个类中,定义了多个同名方法,但是同名方法的参数类型和参数个数不同就是方法重载,返回值不做要求,比较经典的使用就是String中的ValueOf方法,里面参数可以是Object,boolean,一共九种,他可以将数组对象和基本数据类型转化成字符串,没有访问权限的要求
静态绑定:也被称之为前期绑定,即在编译的时候,根据用户所传实参就已经确定了具体要调用哪一个方法,典型的代表是函数重载(下面这种就是根据你用户所进行传入的参数就知道你调用的是哪一个方法了)
class Father{ } class Child extends Father{ public void run(String name){ System.out.println(name); } public void run(String username,String password){ System.out.println(username+password); } public void run(int a,int b){ System.out.println(a+b+"大帅锅"); } } public class UserController{ public static void main(String[] args) { Child child=new Child(); child.run("李佳伟"); child.run("李佳伟","12503487"); child.run(1,2); } }
动态绑定: 也被称之为后期绑定,即在编译的时候,是不能够确定方法的行为,等到程序运行的时候,才知道确定下来到底调用哪一个类的方法
我们可以通过javap -c 类的名字,可以查看反汇编字节码指令,发生动态绑定的时候,还进行显示调用父类中重写的方法,编译的时候,还是显式的进行调用父类的方法,但是在运行的时候,最终调用的是子类的方法,进行了篡改
发生向上转型的时机:
//下面是发生向上转型的时机 Child child=new Child(); Father father1=child;//1.将子类的引用直接给父类 Father father2=new Child();//2.直接将父类引用指向子类对象
class Father{ public String name; public int age; } class Child1 extends Father{ public String username; public String password; public void run() { System.out.println("生命在于运动"); } } class Child2 extends Father{ public String name; public String word; public void start(){ System.out.println("阳光"); } } public class TestDemo{ public static void main(String[] args) { Father father=new Child1(); Child2 child2= (Child2) father; child2.start(); } } //上面的程序会报错,会出现类型转换异常,应该改成这样 public static void main(String[] args) { Father father=new Child1(); if(father instanceof Child2){ Child2 child2= (Child2) father; child2.start(); }else{ Child1 child1=(Child1) father; child1.run(); } } //多态:上面的结论就是说通过一个引用,调用同一个方法所展现出来的形态是不一样的
1)父类名字 父类引用名字=new 子类对象();
2)父类名字 父类引用=子类引用
3)写一个方法,调用这个方法,形参是子类的引用,实参是父类的引用来进行接受,调用同一个方法而展现出来的不同的行为
4)返回值类型是父类类型,但是实际上在方法中return了一个new 子类对象;
我们直接进行赋值,作为方法的参数,直接作为方法的返回值,都可以发生向上转型
class Father{ public void run(){ System.out.println("我是父类的run方法"); } } class Child extends Father { public void run(){ System.out.println("我是子类中的run方法"); } public static Father Start(){ return new Child(); } } public class UserController{ public static void main(String[] args) { Father father1=Child.Start(); Father father2=Child.Start(); Father father3=Child.Start(); father1.run(); father2.run(); father3.run(); } }
总结:多态就是一个引用调用同一个方法,因为这个引用所指向的对象不一样,导致调用这个方法所展现的行为不一样,这就叫做多态
多态:通过一个引用指向多个对象,调用同一个方法展现出不同的形态,因为一个引用引用不同的对象,所以说表现出哪一种行为,完全取决于这个引用引用那一个对象;
1)具体来说就是去完成某一个行为,当不同的对象去完成的时候会产生不同的状态
2)比如说都是打印机,彩色打印机和黑白打印机都是打印机,当她们都去完成打印的时候,虽然都是打印机,虽然都是在干同一种行为,但是所展现的行为和结果却完全不同,黑白打印机打印的照片结果是黑色的,彩色打印机打印的结果是彩色的
*****发生多态的条件*******
1)通过父类引用引用子类对象,必须在继承体系下面
2)父类与子类有同名的覆盖方法
3)通过父类引用调用重写的重名方法之后
一:向上转型:缺点就是不能调用子类特有的方法和属性
通过父类引用来指定子类对象,进行向上转型之后,通过父类的引用,只能访问父类自己的成员,方法和字段属性,只能访问父类自己特有的;不能访问到子类特有的方法和属性
时机:
1)把子类对象给父类的引用
2)实参是子类引用,形参是父类引用:只关注父类的代码,同时可以进行兼容各种子类的一个情况
二:动态绑定:是多态的基础,JAVA中的多态包括运行时多态和编译时期多态
发生条件:
1)父类引用引用子类的对象
2)通过这个父类引用地用用父类和子类同名的覆盖方法,重写(方法名称相同,参数列表相同,参数个数相同,在父类和子类的基础),在编译的时候还不能确定我此时到底是调用父类的方法还是子类的方法,在运行的时候,我才知道了我要进行调用谁的方法,这也叫做运行时绑定动态绑定,甚至就是说在父类的构造方法里面,调用父类和子类的同名的覆盖方法,也会发生运行时绑定
3)重写方法访问修是限定符不能是static,final,private
常见用法: 1)使用java中的集合类,例如说List<String> list=new ArrayList(); 2)多线程情况下,继承Thread,重写runnable,都必须提供run方法,JVM内部调用父类的run方法,从而执行到用户自己定义的run方法 3)DataSource dataSource=new mysqlDataSource(); 4)再尝试进行数据库链接的时候,会用到向下转型,MysqlDataSource是DataSource的子类 ((MysqlDataSource) datasource).setURL(url); ((MysqlDataSource) datasource).setUser(username); ((MysqlDataSource) datasource).setPassword(password);
咱们此时在PowerShell窗口反编译一下javap -c查看JAVA的反汇编代码,看到生成的.class文件,此时看到的还是调用的是父类引用.eat()相当于此时还是调用父类的eat,也就是说编译的时候,我此时还不知道调用的是谁的方法,运行的时候,我才知道调用的是谁的方法,在运行的时候,发现重写了,就调用子类的方法
静态绑定:根据你给的参数类型和个数在编译时期来进行决定推导你调用哪一种重载的函数
4)编译时绑定,编译时多态,我们利用重载来进行实现多态,即在一个类中定义多个同名的不同方法来实现多态,会根据你给的参数的个数和类型,在编译时期确定你要进行调用的一个方法
通过父类引用只能访问我们父类自己的成员,找到汇编,编译的时候就知道调用哪一个具体的方法
三.向下转型:子类引用引用父类对象
可能会出现类型转换异常ClassCastException,在运行的时候才会发现是否出现类型转换异常
instance of本质上是进行判断前面的引用是否引用了后面引用所引用的对象
class Father{ public String name; public int age; } class Child1 extends Father{ public String username; public String password; public void run() { System.out.println("生命在于运动"); } } class Child2 extends Father{ public String name; public String word; public void start(){ System.out.println("阳光"); } } public class TestDemo{ public static void main(String[] args) { Father father=new Child1();//先发生了向上转型 Child2 child2= (Child2) father;//再发生了向下转型 child2.start(); } } //因为之前的父类引用了Child1对象,所以说上面的程序会报错,会出现类型转换异常,应该改成这样 public static void main(String[] args) { Father father=new Child1(); if(father instanceof Child2){ Child2 child2= (Child2) father; child2.start(); }else{ Child1 child1=(Child1) father; child1.run(); } }
使用多态有什么好处呢?
现在我们的需求是画一个圆,画一个矩形,花一朵花
class Shape{ public void draw(){ System.out.println("我即将要画画"); } } class Rect extends Shape{ public void draw(){ System.out.println("我即将要画一个矩形"); } } class Cycle extends Shape{ public void draw(){ System.out.println("我即将要花一个圆形"); } } class Triangle extends Shape{ public void draw(){ System.out.println("画一个三角形"); } } public class UserController{ public static void drawShape(Shape shape){ shape.draw(); } public static void main(String[] args) { Shape shape1=new Rect(); Shape shape2=new Cycle(); Shape shape3=new Triangle(); Shape[] shapes=new Shape[3]; shapes[0]=shape1; shapes[1]=shape2; shapes[2]=shape3; for(Shape shape:shapes){ drawShape(shape); } // Shape shape1=new Rect(); // shape1.draw(); // Shape shape2=new Cycle(); // shape2.draw(); // Shape shape3=new Triangle(); // shape3.draw(); } }
//如果说没有多态,就只能通过大量的if else语句来进行判断 public static void main(String[] args) { String[] strings=new String[]{"Rect","Cycle","Triangle"}; for(String str:strings){ if(str.equals("Rect")){ System.out.println("矩形"); }else if(str.equals("Cycle")){ System.out.println("圆形"); }else{ System.out.println("正方形"); } } }
我们在drawShape方法中,Shape引用指向的子类对象不一样,同一个引用,调用同一个方法,最后所展现出来的形态也是不一样的,我们就把这种思想称之为多态
使用多态的好处:
1)可以进行降低代码的圈复杂度,避免使用大量的if-else
圈复杂度就是一种描述代码复杂程度的方式,如果有很多条件复杂的分支或者是循环语句,那么就显得比较难理解,圈复杂度就是一段代码中条件语句或者是循环语句中出现的个数,如果说一个方法中圈复杂度太多,就需要考虑重构
//减少了大量的if else语句的使用 public static void drawShape(Shape shape){ shape.draw(); } public static void main(String[] args) { Shape[] shapes=new Shape[]{new Rect(),new Triangle(),new Cycle()}; //这是一个Shape数组,数组中的每一个类型都是Shape类型 for(Shape shape:shapes){ shape.draw(); } }
2)可扩展能力极强:如果说我们想要新增一种新的形状,使用多态的代码改动成本也是极低的
对于类的调用者来说,只要进行创建一个新的类的实例就可以了
但是如果说我们使用if else,那么就会将进行添加大量的if else语句改动成本就比较大了
三:类的调用者对类的使用成本进一步降低
封装是让类的调用者不需要知道类的实现细节
多态可以让类的调用者连这个类的类型是什么都不知道,只需要知道他所指向对象有这个方法即可
所以说多态就是封装的更进一步,让调用者对类的使用成本进一步降低
多态的缺点:属性和构造方法都没有多态性
总结:
封装是将这个类的实现细节隐藏起来,通过一些公用方法来进行暴露该对象的功能,对象的状态信息都被隐藏在内部,外界无法进行操作和修改,不允许访问 ,将方法暴露出来,让方法控制这些成员变量进行安全的访问和操作
如果说我们在父类的构造方法中调用子类继承于重写的方法,实际上会调用子类的重写方法
class Father{ public Father(){ run();//此时会进行调用子类中的run方法,并且会打印子类中的num的值是99 } public void run(){ System.out.println(); } } class Child extends Father{ int num=99; public void run(){ System.out.println(num+"我只是子类中的run方法"); } } public class Task { public static void main(String[] args) { Father father=new Child(); } }
上述代码的执行过程是这样的:
1)当我们构建子类对象的时候,会调用父类的构造方法
2)Father的构造方法中又调用了run方法,此时就会发生动态绑定,会进行调用到子类中的run方法
3)因为此时子类对象还没有进行构造(因为此时子类还没有进行调用构造方法,构造方法是没有多态性的,此时我们的num还在处于未初始化的状态,值为0),如果说具有多态性,那么num的值应该是1
4)所以在构造方法中部要调用重写的方法的,调用静态的,final的方法是没有关系的
类和类的关系:单继承
类和接口的关系:多实现
接口和接口的关系:多继承,一个接口可以继承多个接口1)包是一组类的集合,包是能够防止类的名字冲突,import static能够导入一些静态方法,但是我们说import能够导入一个具体的包,是错误的,通过import不可以导入一个具体的包,只能导入这个包某一个具体的类:java.util.collections
2)下面的这个代码不能通过编译:因为当我们进行构建Derived类的时候,因为他的父类是有构造方法的,所以说Derived类要先帮助父类来进行构造,所以应该写super(s);
class Base{ public Base(String s) { System.out.println("B"); } } public class Derived extends Base{ public Derived(String s) { System.out.println("D"); } } public static void main(String[] args){ new Derived("C"); }
父类静态代码块
子类静态代码块
父类实例代码块
父类构造方法
子类实例代码块
子类构造方法
import java.util.*; class A{ B b=new B();------相当于是实例代码块 public A(){ System.out.println("A"); } } class B{ public B(){ System.out.println("B"); } } class C extends A{ B b=new B();-----子类的实例代码块 public C(){ System.out.println("C"); } public static void main(String[] args) { new C(); } } //所以最终打印结果是B A B C super关键字实在子类对象里面指代父类对象的引用
接口的访问修是限定符不可以是private,protected,final但是可以是abstract来进行修饰接口
boolean占一个字节
package com.example.demo; class Father{ public Father(){ System.out.println("我是父类的构造方法"); } } public class Solution extends Father{ public static void main(String[] args) { new Solution(); new Father(); } }
这个题打印的是basebase
1)因为此时父类的构造方法是无参的,但是创建子类对象的时候,会先帮助父类来进行构造,因为编译器会默认提供一个无参的构造方法,所以这个提的代码是这样子的:
class Father{ public Father(){ System.out.println("我是父类的构造方法"); } } public class Solution extends Father{ public Solution(){ super(); } public static void main(String[] args) { new Solution(); new Father(); } }
2)当我们假设父类的构造方法是有参数的,那么我们子类在new对象的时候,必须显式地调用父类的构造方法
package com.example.demo; class Father{ public Father(String username){ System.out.println("我是父类的构造方法"); } } public class Solution extends Father{ public Solution(){ super("我是李佳伟"); } public static void main(String[] args) { new Solution(); new Father(); } }