目录
1.继承
1.背景和基本定义和意义
日常生活中的事物错综复杂,其中可能就会有存在某个关联的事物,比如猫,狗,动物这三个对象,动物包括了猫和狗,那么在继承的角度上就是猫狗继承了动物。
基本定义
class Animal {
String name;
}
class cat extends Animal {
}
class 类名A extends 类名B ,就是类A继承了类B
那继承之后会有什么情况呢
Cat继承了Animal,也就是Cat类可以用自己的类方法属性,也可以用父类(也就是类Animal)的方法属性。
也就是我们在定义猫和狗类的时候,可以吧他们公共的那一部分抽象出来定义一个父类,然后猫狗继承,这样可以简化代码并减少复用。
2.访问优先级
调用子类中的方法或者属性,他会优先在子类中寻找,如果找不到就去他的父类找,如果再找不到编译器就报错了。
class A {
int a = 1;
int b = 2;
int c = 3;
public void func1() {
System.out.println("这是A的func1方法");
}
public void func2() {
System.out.println("这是A的func2方法");
}
}
class B extends A {
char a = 97;
char b = 98;
public void func1() {
System.out.println("这是B的func1方法");
}
}
public class TestDemo2 {
public static void main(String[] args) {
B b = new B();
System.out.println(b.a);
System.out.println(b.b);
b.func1();
b.func2();
}
}
可以看到对子类B的调用都是优先调用自己,如果自己没有就去找父类的方法。
3.super关键字和this关键字
1.如果出现父类的方法和子类的方法重名,编译器会优先用子类本身的方法,那如果我想在子类中调用父类的方法,就可以用super关键字来实现,super和this本质是一样的,都是对代表对对象的引用。super是对父类对象的引用
this代表当前对象引用,父类中用就是代表父类,子类中用就代表子类,但是super是一定是引用父类。
2.与this的用法一样,super的调用也是super.属性/方法
class A {
int a = 1;
int b = 2;
int c = 3;
}
class B extends A {
char a = 97;
char b = 98;
public void func1() {
System.out.println(this.a);
System.out.println(this.b);
System.out.println(super.a);
System.out.println(super.b);
System.out.println("改变前");
super.a = 100;
super.b=200;
this.a=98;
this.b=99;
System.out.println(this.a);
System.out.println(this.b);
System.out.println(super.a);
System.out.println(super.b);
}
}
public class TestDemo2 {
public static void main(String[] args) {
B b = new B();
b.func1();
}
}
可以看到对this的改变是改变当前类也就是B的,对super是对A的
3.super()的作用,涉及到构造方法,在下一点一起说明
4.继承的构造方法的先后顺序
在继承的构造方法中遵循一个规则:在构造子类之前一定要先构造父类
接下来先看一段代码
class Animal {
String name;
}
class cat extends Animal {
public void bark() {
System.out.println(this.name + " miaomiao");
}
public void eat() {
System.out.println("puchi puchi");
}
}
public class TestDemo1 {
public static void main(String[] args) {
cat a = new cat();
a.bark();
a.eat();
}
}
在没有自主定义父类及子类的构造方法前,程序正常运行
但是在给父类添加构造方法后,不论子类有无构造方法编译器都报错了,这就涉及到了构造规则的问题:在构造子类前要先构造父类。
原因就是你写了一个父类的构造方法,那么编译器就不会提供默认的无参数构造方法给你,而在子类的构造方法中的第一行,编译器在编译时会自动加上super(),代表调用默认的父类构造方法;
不论你有没有写子类的构造方法,如果写了,就在第一行加上super(),如果没写就是在默认的无参数并且不做任何操作的构造方法中的第一行加上super(),表示构造父类,
在你写了父类的构造方法后,默认的super()就没用了。
解决方法就是在你子类的构造方法中第一行自己加上super(XXXXX);XXX是父类构造方法要的参数
class Animal {
String name;
public Animal(String name) {
this.name = name;
}
}
class cat extends Animal {
public cat(String name) {
super(name);
this.name = name;
}
public void bark() {
System.out.println(this.name + " miaomiao");
}
public void eat() {
System.out.println("puchi puchi");
}
}
public class TestDemo1 {
public static void main(String[] args) {
cat a = new cat("cat");
a.bark();
}
}
注意:
1.super()与this()都是要放在第一行来执行构造方法的,因为不可同时使用这两操作,
2.super,this都不能在static静态中使用,原因就是super,this都代表对对象的引用,而static方法在没有对象时就可以使用,那这时的super,this代表什么?此时根本没有对象
3.注意构造的先后顺序,先构造父类,在构造子类
5.protected关键字---->继承权限
protect用来修饰类中成员,在同一个包内该怎么用怎么用
在不同包内,只有子类才能找到父类中的被protected修饰的这个成员变量
比如A继承B,也就是A子类,B父类
B中有一个 protected fly, 在与B同一个包内,任意调用无碍
在不同包下,只有继承了B的才可以访问到这个fly,其他没继承的不可以访问它
总结:实现类的时候尽量实现封装好细节,严格规范权限,能用private就尽量不用public
6.代码段的执行先后顺序问题
class C{
String name;
int age;
public C(String name,int age){
this.name=name;
this.age=age;
System.out.println("父类的构造方法代码块");
}
{
System.out.println("父类的实例代码块");
}
static {
System.out.println("父类的静态代码块");
}
}
class D extends C{
public D(String name,int age){
super(name,age);
System.out.println("子类的构造方法代码块");
}
{
System.out.println("子类的实例代码块");
}
static {
System.out.println("子类的静态代码块");
}
}
public class TestDemo1 {
public static void main(String[] args) {
D d = new D("zhangsan",18);
System.out.println("======================================");
D d2 = new D("zhangsan",18);
}
}
分别给两个类C,D(D继承C)写了实例,静态,构造代码块
这是执行顺序
可以看到第一次先执行静态,然后是父类的实例代码,再是构造;最后才是子类
第二次没有执行静态(代表静态代码块只执行一次)
总结:优先执行静态代码块,然后先执行父类的实例代码块,然后才是构造方法,最后再到子类
7.java继承的注意问题
java不支持多继承,也就是A同时继承B,C,这是后面引入接口后的
但是java支持连续继承,如A继承B,B继承C
如果不想一个类被继承,可以用final修饰
final class 类名,代表这个类不可以被继承
2.多态
多态通俗理解就是一个事物多种形态,比如描述动物叫声,有猫叫,狗叫等等,这时候用多态的方式完成对事物的抽象可以减少对代码冗沉。
1.多态需要的几个前提条件
1.构成继承关系
2.向上转型
什么是向上转型?通俗理解就是我创建了一个子类,然后实例化这个子类,但是我的类型确实他的父类,可以理解成创建的子类往上提高了一个级别变成了父类。
class Animal{
}
class cat extends Animal{
}
public class TestDemo1 {
public static void main(String[] args) {
Animal a = new cat();
}
}
用父类Animal实例化了一个子类cat,此时a就发生了向上转型,a本来应该是子类,但是此时a的类型确实父类Animal。
3.方法重写
多态的实现需要父类,子类的方法构成重写
class Animal {
public void func(){
}
}
class cat extends Animal {
@Override
public void func() {
super.func();
}
}
在intellij idea中可以快捷生成,右键,然后选中要重写的方法即可
4.调用重写的方法
class Animal {
public void func(){
System.out.println("父类的func");
}
}
class cat extends Animal {
@Override
public void func() {
System.out.println("子类的func");
}
}
public class TestDemo1 {
public static void main(String[] args) {
Animal b = new cat();
b.func();
}
}
最后打印结果是调用了子类的func方法
b的类型是父类Animal,b.func理论也是在父类中找func方法。事实上也是如此,在编译期间确实是找到了父类的func方法,但是运行的时候编译器发现这里发生了向上转型,并且该方法被重写了,就会去调用转型前的子类的重写方法,这个过程就是动态连接。
就是编译期间是原本的方法,但是运行的时候却调用了另一个方法。
那有没有静态连接呢?有,我们的重载就是一种静态连接,在编译期间就确定了调用什么方法。
总结:有了以上几个条件,我们就可以实现多态的思想了,因为调用的时候发生动态连接,所以我们可以用父类当一个模板,在操作不同对象的时候,会有不同的行为。
2.三种常见的向上转型方式
1.第一种就是如上图的直接赋值形式
2.可以作为方法的参数来形成向上转型
class Animal{
}
class cat extends Animal{
}
public class TestDemo1 {
public static void func(Animal a){
System.out.println("zhangsan");
}
public static void main(String[] args) {
cat a = new cat();
func(a);
}
}
可以看到我传递的是 用子类cat实例化出来的变量a,然后却是用父类Animal 类型的a来接受
3.利用方法的返回值
class Animal {
}
class cat extends Animal {
}
public class TestDemo1 {
public static Animal func(int a) {
return new cat();
}
public static void main(String[] args) {
int a = 0;
Animal b = func(a);
}
}
可以看到返回的是cat的实例,但是返回类型是Animal的父类的类型
3.重写的注意事项
1.静态方法不可重写
2.private修饰的方法不可重写
3.重写的时候注意父类方法的权限要大于等于子类方法的权限,如子类是public修饰的方法(最大的权限),那父类只能是public权限
4.返回值要相同,不相同的情况只能是构成协变类型(返回值的类型构成父类子类的关系)
5.被final修饰的不可
6.重写基本操作,如名字,参数列表相同
4.向下转型
向上转型后就无法调用子类的方法,(只能调用父类的方法,而父类方法发生动态连接找回子类重写的方法),但是无法调用子类没有重写的方法,因此有时为了需要,也会把向上转型后的引用向下转型回子类来调用子类方法
但是转换回来的一定是原本的子类吗,java中是一个父类可以对应多个子类,如果转换回去的不是原本的子类,就会出现问题,比如B,C继承A,而B,C都有一个自己的方法,转换错误调用方法就会错误。
class Animal {
public void func(){
System.out.println("父类的func");
}
}
class cat extends Animal {
@Override
public void func2() {
System.out.println("子类的func2");
}
}
public class TestDemo1 {
public static void main(String[] args) {
Animal a = new cat();
cat b = (cat)a;
b.func2();
}
}
就是利用强制类型转换,转换回去,但是前面提到这种行为是不安全的,因此我们可以引入一个判断操作来让他变得安全
public class TestDemo1 {
public static void main(String[] args) {
Animal a = new cat();
if(a instanceof cat){
cat b = (cat)a;
b.func();
}
}
用instanceof 来判断a是不是由cat的实例向上转型上来的。