【Java基础】数组、抽象;面向对象:继承、封装、多态
一、数组
单个数组内存分配图
多个数组内存分配图
多个数组指向相同地址
这种情况下,多个数组指向同一个地址值。
中间一行的赋值操作是将arr的地址值赋值给arr2,如果这个时候针对arr2进行操作,那么也就相当于是对arr进行操作,本质上指向的是同一个数组。所以无论操作arr还是arr2,结果上没有本质上的区别。
数组空指针异常
如果数组被赋值为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("睡觉");
}
}
包装类
基本数据类型使用虽然非常方便,但是没有对应的方法来操作这些数据。所以我们可以使用包装类将这些基本数据类型进行一定的封装,把基本类型的数据包装起来,这就是包装类。
在包装类中可以定义一些方法,用来操作基本类型的数据。
装箱与拆箱
修饰符
Java中的修饰符分为两大类:权限修饰符、状态修饰符
权限修饰符
权限修饰符,修饰的是访问的权限,指的是在同一个module中的不同类中的访问权限
状态修饰符
final(最终态)
可以修饰成员方法、成员变量、类
- final修饰方法,表明该方法是最终方法,不能被重写
- final修饰变量,表明该变量是最终变量,不能被再次赋值
- final修饰类,表明该类是最终类,不能被继承
final修饰局部变量:
- final修饰基本数据类型变量,变量的数据值不能发生改变
- final修饰引用数据类型变量,变量的地址值不能发生改变,但是地址里的内容是可以发生改变的
static(静态)
可以修饰成员方法、成员变量
- 被类的所有对象共享——这也是我们判断是否使用static关键字的条件
- 可以通过类名.变量名调用(也可以使用对象名调用),推荐使用类名调用
- 静态成员方法只能访问静态成员
三、继承、多态
继承
继承是面向对象三大特征之一,可以使得子类具有父类的属性和方法,还可以在子类中重新定义,追加属性和方法
继承的利弊
- 优点:
- 提高了代码的复用性(多个类相同的成员可以放到同一个类中)
- 提高了代码的维护性(如果方法的代码需要修改,修改一处即可)
- 缺点:
- 继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时,子类实现也不得不跟着变化,削弱了子类的独立性
继承使用的情况
什么时候使用继承?
当类A是类B的”一种/一个“时,就可以使用继承关系。
继承中成员变量访问
- 在子类方法中访问变量
- 子类局部范围找
- 子类成员范围找
- 父类成员范围找
- 如果都没有就报错(不考虑父类的父类)
super与this关键字的使用
- super:代表父类存储空间的标识(可以理解为父类对象引用)
- this:代表本类对象的引用
继承中成员方法的访问
继承中的成员方法的访问:(子类访问成员方法)
- 子类成员范围找
- 父类成员范围找
- 如果找不到就报错(不考虑父类的父类)
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中的内存图
方法重写
子类中出现了和父类中一样的方法声明
当子类中需要父类的功能,而功能主体子类有自己特有内容,可以重写父类中的方法。这样既沿袭了父类中的功能,又定义了子类特有的功能
方法重写时最好添加@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();