Loading

【Java基础】数组、抽象;面向对象:继承、封装、多态

一、数组

单个数组内存分配图

image-20210424235050443

多个数组内存分配图

image-20210424235208265

多个数组指向相同地址

image-20210424235403243

这种情况下,多个数组指向同一个地址值。

中间一行的赋值操作是将arr的地址值赋值给arr2,如果这个时候针对arr2进行操作,那么也就相当于是对arr进行操作,本质上指向的是同一个数组。所以无论操作arr还是arr2,结果上没有本质上的区别。

数组空指针异常

image-20210424235746162

如果数组被赋值为null,那么将找不到数组本身存放的堆内存地址。再次使用的时候会报错:空指针异常

二、内部类、抽象类、包装类、修饰符

内部类

在一个类中定义一个类,类中被定义的类就是内部类

内部类的访问特点

  • 内部类可以直接访问外部类的成员,包括私有

  • 外部类要访问内部类的成员,必须创建对象

public class Outer {
    private int num = 20;
    public class Inner {
        public void show() {
            System.out.println(num);
        }
    }
    private void method() {
        Inner inner = new Inner();
        inner.show();
    }
}

成员内部类

根据内部类的位置不同,可以分为两种:

  • 在类的成员位置:成员内部类
  • 在类的局部位置:局部内部类

成员内部类如何使用呢?两种方式:

一、将内部类的权限名定义为public,之后创建内部类

public class Outer {
    private int num = 20;
    public class Inner {
        public void show() {
            System.out.println(num);
        }
    }
}
public static void main(String[] args) {
    // 创建对象调用内部类方法
    Outer.Inner oi = new Outer().new Inner();
    oi.show();
}

二、如果Inner内部类的权限名不是public,则上述方法失效,那么如何调用呢?

在外部类内创建新的方法,创建内部类,调用方法;外界直接创建外部类,并调用该方法即可

public class Outer {
    private int num = 20;
    private class Inner {
        public void show() {
            System.out.println(num);
        }
    }
    public void method() {
        Inner i = new Inner();
        i.show();
    }
}
public static void main(String[] args) {
    // 创建对象调用内部类
    /*Outer.Inner oi = new Outer().new Inner();
    oi.show();*/
    Outer o = new Outer();
    o.method();
}

局部内部类

局部内部类就是在方法体中的类,所以外界是无法使用的,需要在方法中创建该局部内部类的对象,通过调用对象内部的方法使用

该类可以访问外部的成员,也可以访问方法内的局部变量

public class Outer {
    private int num = 10;
    public void method() {
        class Inner {
            int num2 = 20;
            public void show() {
                System.out.println(num);
                System.out.println(num2);
            }
        }
        // 直接在方法内部创建对象调用局部内部类的方法
        Inner i = new Inner();
        i.show();
    }
}
public static void main(String[] args) {
    Outer o = new Outer();
    o.method();
}

匿名内部类

前提:存在一个类或者一个接口,这里的类可以是具体类也可以是抽象类

格式:

new class/interface() {
    // Override method()
};

本质是***一个继承了该类或实现了该接口的子类匿名对象***

步骤一:有一个类或者接口

public interface Inter {
    void show();
}

步骤二:创建相关的类

public class Outer {
    public void method() {
        /*new Inter() {
            @Override
            public void show() {
                System.out.println("匿名内部类方法执行");
            }
        };
        这样写仅仅是个对象,下面的写法才是对象调用方法:
        new Inter() {
            @Override
            public void show() {
                System.out.println("匿名内部类方法执行");
            }
        }.show();*/

        // 由于该匿名内部类实现的是 Inter 接口,我们可以用接口类型来接受这个匿名内部类
        Inter i = new Inter() {
            @Override
            public void show() {
                System.out.println("匿名内部类方法执行");
            }
        };

        i.show();
        i.show();
    }
}

步骤三:测试

public class Test {
    public static void main(String[] args) {
        Outer o = new Outer();
        o.method();
    }
}

输出结果:

匿名内部类方法执行
匿名内部类方法执行

匿名内部类在开发中的使用

仅使用一次,创建接口操作类的对象,调用接口操作方法,方法的参数是接口

不想创建接口实现类的情况,并且只想用一次,就可以使用匿名内部类

抽象类

Java中,没有方法体的方法应该被定义为抽象方法;类中如果有抽象方法,则应该定义为抽象类

注意事项:

  • 抽象类中的方法不一定是抽象方法,但是抽象方法所属的类一定要是抽象类
  • 抽象类中的子类要么是抽象类,要么重写抽象类中的所有抽象方法
  • 抽象类不能实例化,但是抽象类可以通过子类对象进行实例化,这叫抽象类多态

抽象类的成员特点

抽象类中可有成员变量、成员方法、构造方法

  • 成员变量
    • 可以是变量,也可以是常量
  • 成员方法
    • 可以有抽象方法:限定子类必须完成某些动作
    • 可以有非抽象方法:提高代码的复用性
  • 构造方法
    • 有构造方法,但是不能实例化
    • 抽象方法的实例化是通过子类的对象进行实例化的,子类对象对于父类数据的初始化要使用到这些构造方法
public abstract class Animal {
    // 抽象类中可以包含成员变量
    private int age = 20;
    private final String city = "北京";

    // 因为抽象类是通过子类对象进行实例化的,所以子类对象在使用构造方法创建时,
    // 会隐式的调用父类的构造方法,也就是抽象类的构造方法
    public Animal() {}

    public Animal(int age) {
        this.age = age;
    }

    public void show() {
        age = 40;
        System.out.println(age);
        System.out.println(city);
    }

    /*public void eat() {
        System.out.println("吃东西");
    }*/
    // 抽象方法
    public abstract void eat();
    // 抽象类中可以有具体的方法
    public void sleep() {
        System.out.println("睡觉");
    }
}

包装类

image-20210406145151828

基本数据类型使用虽然非常方便,但是没有对应的方法来操作这些数据。所以我们可以使用包装类将这些基本数据类型进行一定的封装,把基本类型的数据包装起来,这就是包装类。

在包装类中可以定义一些方法,用来操作基本类型的数据。

装箱与拆箱

image-20210406150512203

修饰符

Java中的修饰符分为两大类:权限修饰符、状态修饰符

权限修饰符

image-20210509204638596

权限修饰符,修饰的是访问的权限,指的是在同一个module中的不同类中的访问权限

状态修饰符

final(最终态)

可以修饰成员方法、成员变量、类

  • final修饰方法,表明该方法是最终方法,不能被重写
  • final修饰变量,表明该变量是最终变量,不能被再次赋值
  • final修饰类,表明该类是最终类,不能被继承

final修饰局部变量:

  • final修饰基本数据类型变量,变量的数据值不能发生改变
  • final修饰引用数据类型变量,变量的地址值不能发生改变,但是地址里的内容是可以发生改变的
static(静态)

可以修饰成员方法、成员变量

  • 被类的所有对象共享——这也是我们判断是否使用static关键字的条件
  • 可以通过类名.变量名调用(也可以使用对象名调用),推荐使用类名调用
  • 静态成员方法只能访问静态成员

三、继承、多态

继承

继承是面向对象三大特征之一,可以使得子类具有父类的属性和方法,还可以在子类中重新定义,追加属性和方法

继承的利弊

  • 优点:
    • 提高了代码的复用性(多个类相同的成员可以放到同一个类中)
    • 提高了代码的维护性(如果方法的代码需要修改,修改一处即可)
  • 缺点:
    • 继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时,子类实现也不得不跟着变化,削弱了子类的独立性

继承使用的情况

什么时候使用继承?

当类A是类B的”一种/一个“时,就可以使用继承关系。

继承中成员变量访问

  • 在子类方法中访问变量
    • 子类局部范围找
    • 子类成员范围找
    • 父类成员范围找
    • 如果都没有就报错(不考虑父类的父类)

super与this关键字的使用

  • super:代表父类存储空间的标识(可以理解为父类对象引用)
  • this:代表本类对象的引用

image-20210509194539063

继承中成员方法的访问

继承中的成员方法的访问:(子类访问成员方法)

  • 子类成员范围找
  • 父类成员范围找
  • 如果找不到就报错(不考虑父类的父类)
public static void main(String[] args) {
    Zi zi = new Zi();
    // 调用子类成员方法
    zi.method();
    // 调用子类和父类中的重名无参方法
    // 真正调用的是子类中的同名方法
    zi.show();
    // 调用父类中的方法,需要在子类中的同名方法中添加 super.show();
}

继承中构造方法的访问特点

继承中,关于构造方法的访问:

  • 子类中所有的构造方法都会默认访问父类中的无参构造方法

  • 原因:子类继承自父类,在子类调用父类时可能会用到父类的数据,所以在子类进行初始化的时候需要先对父类进行初始化操作

  • 因为子类会继承父类的数据,可能还会使用父类的数据。所以在子类初始化之前需要先完成父类数据的初始化

  • 每一个子类构造方法的第一句默认都是super();

如果父类中没有无参构造方法,只有带参构造方法,解决方法:

  • 通过使用super关键字显式的调用父类的带参构造方法
  • 在父类中自己提供一个无参构造方法(推荐使用)
public static void main(String[] args) {
    /*
    * 父类无参构造方法被调用
    * 子类无参构造方法被调用
    * */
    Zi z1 = new Zi();
    /*
    * 父类无参构造方法被调用
    * 子类带参构造方法被调用
    * */
    Zi z2 = new Zi(20);
}

super中的内存图

image-20210509201403215

方法重写

子类中出现了和父类中一样的方法声明

当子类中需要父类的功能,而功能主体子类有自己特有内容,可以重写父类中的方法。这样既沿袭了父类中的功能,又定义了子类特有的功能

方法重写时最好添加@Override注解,可以帮忙检查方法重写的方法声明是否正确

注意事项

  • 父类中的私有方法子类不能重写(父类中的私有成员子类是不能被继承的)
  • 子类重写父类方法,子类的访问权限不能更低。例如:父类成员方法为默认,子类重写方法权限为默认或比默认更高(protected,public)
    • 方法访问权限:public > protected > 默认 > private

继承的注意事项

Java中的继承只支持单继承,不支持多继承(一个类继承自多个类,不允许)

继承支持多级继承,子类继承自父类,父类继承自父类的父类(爷爷类)这样的继承是合法的

多态

多态定义的时候:左父右子

多态中成员访问

成员变量:编译看左边,执行看左边

成员方法:编译看左边,执行看右边

成员方法和成员变量执行不同的原因:因为成员方法有重写,而成员变量没有

也就是说:多态的编译是否能通过,要看父类中是否有相关的变量和方法;执行则要看是变量还是方法

多态的利弊

多态的好处:提高了程序的扩展性

  • 定义多态方法的时候,使用父类型作为参数,使用具体的子类型进行操作

多态的弊端:不能使用子类的特有功能

实际使用如下:

①创建Animal类

public class Animal {
    public void eat() {
        System.out.println("动物吃东西");
    }
}

②创建Dog类和Cat类

public class Cat extends Animal{
    public void eat() {
        System.out.println("猫吃老鼠");
    }
}
public class Dog extends Animal{
    public void eat() {
        System.out.println("狗吃骨头");
    }

    public void gatekeeper() {
        System.out.println("狗看门");
    }
}

③创建测试主类

public class AnimalDemo {
    public static void main(String[] args) {
        Animal a = new Cat();
        a.eat();

        Animal b = new Dog();
        // b.gatekeeper();
        b.eat();
    }
}

这个时候我们调用gatekeeper方法则会报错,因为gatekeeper方法是Dog类独有的方法。多态的弊端此时体现出来了:因为在Animal父类中没有定义gatekeeper方法,那么在使用多态的时候就不能调用到子类的独有方法。

解决方法:①在Animal类中添加Dog的特有方法,那这样Cat类也能够调用gatekeeper方法,本质上二者相悖了。

public void gatekeeper() {
    System.out.println("动物看家护院");
}

所以说,多态的弊端就是不能调用子类的特有方法。

②向下转型,将Animal类定义的时候的b对象(Dog)转型为Dog本身的类型。这样转型之后其实也与多态定义的时候的方法不相符,违背了多态本身的定义。

((Dog) b).gatekeeper();
posted @ 2021-06-07 21:49  雨下一整晚Real  阅读(9)  评论(0编辑  收藏  举报  来源