面向对象高级
static 关键字
当加载了类字节码文件时,如果类中存在静态成员,那么会存放到堆内存中的静态成员变量区。
工具类:为数据提供处理方法服务,通常这些方法都是静态方法。 且通常私有化该类的构造方法(使用private
)。
继承
子类中定义了与父类相同命名的变量,在子类调用这种同名的变量时根据就近原则会优先使用子类里的变量。但如果我们就想使用父类里的变量,我们可以使用super
解决。
方法重写
在继承体系中,子类可以继承到父类的方法。但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改这就需要采用方法的重写,方法重写又称方法覆盖。
可以通过在方法上方写注解@Override
就能判断是否是重写方法。
@Override
public void show()
常使用最多的是 private
和 public
。
继承特点:Java 只支持单继承,不支持多继承,但支持多层继承。即子类的子类。
上面代码运行结果说明了:除了Object类,在所有构造方法的第一行代码,都默认隐藏了一句话 super();通过这句代码,访问父类的空参数构造方法。
细节:Java当中所有的类,都直接或者间接的继承到了Object类。
public class pearsonTest {
public static void main(String[] args) {
zi z = new zi(0,10);
z.show();
}
}
class Fu{
private int num;
public Fu(){};
public Fu(int n){
this.num = n;
}
public int getNum(){
return this.num;
}
}
class zi extends Fu{
private int age;
public zi() {
}
public zi(int n,int a) {
super(n);
this.age = a;
}
public void show(){
System.out.println("编号:"+super.getNum()+" 年龄"+this.age);
}
}
输出编号:0 年龄10
有了这种特性,我们可以将父类的数据交给父类处理,子类的数据交给子类去处理。
我们在new子类时,内存中会有专门一块区域存放父类的变量成员。
在子类中省略super
访问父类的写法规则:
- 被调用的变量和方法在父类中不能带有
private
。 - 被调用的变量和方法,在子类中不存在。
省略规则
因为继承行为中,子类完全继承父类的变量和方法成员,所以当我们去访问的时候可以使用this
,然后根据this
的省略规则就可以不需要写this
或者super
。
然而如果子类存在与父类定义相同的变量和方法成员,那么在调用父类的变量和方法成员时需要使用super
。
class Fu{
int num;
public Fu(){};
public Fu(int n){
this.num = n;
}
}
class zi extends Fu{
private int age;
public zi() {}
public zi(int n,int a) {
super(n);
this.age = a;
}
public void show(){
System.out.println("编号:"+num+" 年龄"+age);
}
}
例如上面的this.num
和 this.age
就可以省略 this。
开闭原则:对功能扩展做开放,对修改代码做关闭。尽量不要修改源代码,有新功能重新编写。
细节:可以使用this();
来调用自身的构造函数。例如下面:
class A{
String a,b,c;
public A(){};
public A(String s1,String s2){
a = s1;
b = s2;
};
public A(String s1,String s2,String s3){
this(s1,s2);
c = s3;
};
}
final 修饰符
final 修饰变量特点:
基本数据类型:数据值不可改变。
引用数据类型:地址值不可改变,但是内容可以改变。
final 修饰成员变量的注意事项:
- final 修饰成员变量,不允许修饰默认值。即需要在构造方法结束之前完成赋值。
- final 修饰成员变量的初始化时机
a. 在定义的时候直接赋值【推荐】
b. 在构造方法中完成赋值
final 修饰变量的命名规范:
- 如果变量名是一个单词,所有字母大写。例如 PI
- 如果变量名是多个单词,所有字母大写,中间使用下划线分割。例如:DEGREES_TO_RADIANS
包
抽象类
抽象类是一种特殊的父类,允许编写抽象方法。
抽象方法:将共性的行为(方法)抽取到父类之后,发现该方法的实现逻辑无法在父类中给出具体明确,该方法就可以定义为抽象方法。
抽象类:如果一个类中存在抽象方法,那么该类就必须声明为抽象类。
要求子类必须重写方法就可以使用抽象类。
例如创建了一个动物的父类,编写“吃”这种成员方法,但没法具体明确每个动物吃什么,那么只能要求每个子类重写“吃”这种成员方法。
abstract class Animal{
public abstract void eat();
}
class Cat extends Animal{
public void eat(){
System.out.println("吃鱼");
};
}
class Dog extends Animal{
public void eat(){
System.out.println("骨头");
};
}
接口
Java中的接口更多体现的是对行为的抽象,也是对行为的规范,是一套行为规则。
下面创建了接口类 Inter,规定了子类必须重写两个行为(方法)。再编写了 InterImpl 实现类用实现 Inter 定义的两个行为(方法)。
接口的子类除了是实现类,还可以是抽象类,但这种写法非常麻烦,在实战中少见。继承类并实现多个接口,这种写法比较常见。
接口成员特点
- 成员变量
- 只能是常量。默认修饰符:public static final
- 构造方法
- 没有
- 成员方法
- 可以新建抽象方法(带abstract关键字)、非抽象方法(带default关键字)、静态方法(带static关键字)、私有方法(带private关键字)
DK9 之后可以在接口内创建私有方法,也就是可以使用private
修饰符。
下面的代码中可以看到Zi
类同时实现接口A、B,Zi
应当重写show
方法,但Zi
同时还继承了Fu
,在Fu
中已经有了show
所以现在不需要重写show
方法了,因为已经实现了。
下面表现了接口可以继承多个接口,他们要求子类重写的方法只有一个相同的
show
方法,所以InterCImpl
在实现时,只需要实现show
方法即可。
- 可以新建抽象方法(带abstract关键字)、非抽象方法(带default关键字)、静态方法(带static关键字)、私有方法(带private关键字)
抽象类和接口的对比
- 成员变量:
- 抽象类:可以定义变量,也可以定义常量
- 接口:只能定义常量
- 成员方法:
- 抽象类和接口都可以是具体方法,也可以是定义抽象方法。
- 构造方法:
- 抽象类:有
- 接口:没有
抽象类非常灵活,本身是在类的基础上增加了对行为的约束,但其本身的作用还是用于对事物行为进行抽象(描述事物)。
接口相对严格,主要用于在对业务行为有了充分认识后制定了一个行为规范,后续实现业务接口只需按照规范来即可。
多态
多态的前提:
- 有继承/实现的关系
- 有方法重写
- 有父类引用指向子类对象
第三点可以使用代码直观的展示。
Animal a1 = new Dog();
其中Dog
类继承Animal
,且Dog
类重写了父类里面的方法,而上面这种代码就是第三点的体现。
对象多态:
父类引用指向子类对象。
- 如果是父类类型变量,可以指向不同的子类。
- 如果方法的形参定义为父类类型,这个方法就可以接收到该父类的任意子类对象。
class Animal{
public void eat(){};
}
class Cat extends Animal{
public void eat(){
System.out.println("吃鱼");
};
}
class Dog extends Animal{
public void eat(){System.out.println("骨头");};
}
对象多态体现在变量上:
Animal a1 = new Dog();
Animal a2 = new Cat();
对象多态体现在方法上:
public static void main(String[] args) {
Animal a1 = new Dog();
Animal a2 = new Cat();
doit(a1);
doit(a2);
}
public static void doit(Animal obj) {
obj.eat();
}
输出:
骨头
吃鱼
行为多态:
同一个行为具有多个不同表现形式或形态能力。就是抽象行为可以有不同的实现方式。
多态的成员访问特点
成员变量:
编译看左边(父类),运行看左边(父类)。
成员方法:
编译看左边(父类),运行看右边(子类)。
这个看左边是指编译时会检查父类中是否有定义这个成员变量/成员方法,如果没有则没法编译。即在多态情景下,不能直接访问子类特有的成员。
如果一定要访问子类,那么需要强转到子类。
class Animal{
public void eat(){};
}
class Cat extends Animal{
int num = 10;
public void eat(){
System.out.println("吃鱼");
};
}
class Dog extends Animal{
public void eat(){System.out.println("骨头");};
}
根据上面代码进行更改,其中Cat
类中新增了一个成员变量num
。我希望在执行doit
方法时,打印该成员变量。
public static void main(String[] args) {
Animal a1 = new Dog();
Animal a2 = new Cat();
doit(a1);
doit(a2);
}
public static void doit(Animal obj) {
if (obj instanceof Cat){
System.out.println(((Cat) obj).num);
}
obj.eat();
}
先是使用了instanceof
判断当前传入的对象是否是Cat
子类,如果是就使用向下强转实现访问子类成员的目的。
代码块
构造代码块不经常使用,静态代码块适用于类中的静态成员变量初始化复杂的情况使用。
构造代码块编写:
编译后的 class 文件显示。
下面是静态代码块用于初始化类中静态成员变量的演示。
class Animal{
static int num;
static {
num = 1;
}
}
内部类
成员内部类
创建内部类。
class Outer{
class Inner{
int num = 10;
}
}
创建内部类对象。
public static void main(String[] args) {
Outer.Inner innerClass = new Outer().new Inner();
System.out.println(innerClass.num);
}
内部类中,访问外部类成员,可以直接访问,包括设置的私有成员。
外部类中,访问内部类成员,需要创建对象访问。
如何区分内外部类同名成员使用情况。内部类访问外部类但成员同名可以使用外部类名.this
。
静态内部类
class Outer{
static class Inner{
public static void show(){
System.out.println(1);
}
}
}
当你的想调用静态内部类的静态方法时,例如上面这种情况,你可以直接 Outer.Inner.show();
这样链式访问。
静态只能访问静态,静态内部类中,访问外部类的成员,静态成员可以直接访问,非静态需要先创建对象访问。
局部内部类
局部内部类,放在于方法、代码块、构造器等执行体中,非常鸡肋。
上面就在方法中创建了内部类,只有运行该方法时才会用上。
匿名内部类【重要】
匿名内部类可以作为方法的实际参数进行传输。
new 类名(){}
代表继承这个类。new 接口名(){}
代表实现这个接口。
class A{
void show(){
System.out.println("show");
};
}
存在一个命名为A
的类,且存在命名为showA
的函数,需要传入一个类型为A
的对象。
public static void showA(A a){
a.show();
}
不想新建变量,可以直接使用new
。
public static void main(String[] args) {
showA(new A());
}
换成接口。
interface A{
void show();
}
函数不变。
public static void showA(A a){
a.show();
}
直接实现该接口进行调用。
public static void main(String[] args) {
showA(new A(){
@Override
public void show() {
System.out.println("show");
}
});
}
如果接口内的抽象方法过多,不建议这么写。
Lambda表达式
注意:Lambda 表达式只能简化函数式接囗的匿名内部类的写法形式
函数式接口的定义:
- 首先必须是接口,其次接口中有且仅有一个抽象方法的形式。
- 通常我们会在接口上加上一个 注解
@FunctionalInterface
标记该接囗必须是满足函数式接口。
@FunctionalInterface
interface A{
void show();
}
存在一个参数,且只有一行代码也不需要return。省略方式展示。
存在多个参数,且需要return。省略方式展示。
适配器设计模式(Adapter)
只想实现部分方法,但实现类是必须重写所有方法的,为了避免这种情况。我们可以编写一个抽象实现类当作适配器,然后我们再通过继承这个类来只重写我们需要的方法。
模板设计模式(Template)
我们定义了抽象类A
,其中需要我们重写body
方法。其中show
方法就相当于模板,而body
方法就相当于填充,我们只需要关注body
即可。
模板设计模式的优势,模板已经定义了通用结构,使用者只需要关心自己需要实现的功能即可。