十、面向对象进阶
10.1 static
static表示静态,是java中的一个修饰符,可以修饰成员方法,成员变量。
被static修饰的成员变量,叫做静态变量。
特点
- 被该类所有对象共享
- 不属于对象,属于类
- 静态变量是随着类的加载而加载的,优于对象出现的
调用方式
- 类名调用
- 对象名调用
static内存图
被static修饰的成员方法,叫做静态方法。
特点:
- 多用在测试类和工具类中
- javabean类中很少会用
调用方式:
- 类名调用
- 对象名调用
static的注意事项
- 静态方法只能访问静态变量和静态方法
- 非静态方法可以访问静态变量和静态方法,也可以访问非静态的成员变量和成员方法
- 静态方法中没有this关键字
非静态的与对象相关,静态共享与某一个对象无关。
重新认识main方法
- public:被jvm调用,访问权限足够大
- static:被jvm调用,不能创建对象,直接类名访问。因为main方法是静态的,所以测试类中的其他方法也需要是静态的。
- void:被jvm调用,不需要给jvm返回值。
- main:一个通用的名称,虽然不是关键字,但是被jvm识别。
- String[] args:以前用于接收键盘录入数据的,现在没用。
10.2 继承
- java提供一个关键字extends,用这个关键字,我们可以让一个类和另一个类建立继承关系。
public class Student extends Person {}
- Student称为子类(派生类),Person称为父类(基类)。
使用继承的好处
- 可以把多个子类中重复的代码提取到父类中,提高代码的复用性。
- 子类可以在父类的基础上,增加其他的功能,使子类更强大。
什么时候用到继承?
当类与类之间,存在相同的内容,并满足子类是父类中的一种,就可以考虑用继承的来优化代码。
继承的特点
Java只支持单继承,不支持多继承,但支持多层继承。
- 单继承:一个子类只能继承一个父类。
- 不支持多继承:子类不能同时继承多个父类
- 多层继承:子类A继承父类B,父类B可以继承父类C B是A的直接父类,C是A的间接父类
每一个类都直接或者间接继承于Object
子类只能访问父类中非私有的成员。
子类能继承父类中的哪些内容?
两个误区:
- 误区1:父类私有的东西,子类就无法继承
- 误区2:父类中非私有的成员,就被子类继承下来了
只有父类中的虚方法才能被子类继承
继承中成员变量和成员方法的访问特点
继承中成员变量的访问特点:就近原则。
- this关键字 本类中成员变量
- super关键字 父类中成员变量
继承中成员方法的访问特点:就近原则。
方法的重写
当父类的方法不能满足子类现在的需求时,需要进行方法重写。
书写格式:在继承体系中,子类出现了和父类中一模一样的方法申明,我们就称子类这个方法是重写的方法。
@Override重写注释
- @Override是放在重写后的方法上,校验子类重写时语法是否正确。
- 加上注解示意虚拟机这是方法重写,此时如果出现红色波浪线表示语法出现错误。
- 建议重写方法都加上@Override注解,代码安全优雅。
方法重写的本质
方法重写的本质是子类重写的方法是覆盖了虚方法表中的方法。
方法重写的注意事项和要求
- 重写方法的名称、形参列表必须与父类中的一致。
- 子类重写父类方法时,访问权限子类必须大于等于父类。(空<protected<public)
- 子类重写父类方法时,返回值类型必须子类型小于等于父类型。
- 重写方法尽量与父类保持一致。
- 只有被添加到虚方法表中的方法才能被重写。
继承中构造方法的访问特点
- 父类中的构造方法不会被子类继承。(假设可以继承,那么构造方法名与类名不一致)
- 子类中所有的构造方法默认先访问父类中的无参构造,再执行自己。
- 原因:子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类无法使用父类中的数据。
- 子类初始化之前,一定要调用父类构造方法先完成父类数据空间的初始化。
- 怎么调用父类构造方法?
- 子类构造方法的第一行语句默认都是:super(),不写也存在,且必须在第一行。
- 如果想调用父类有参构造,必须手动写super进行调用。
this、super使用总结
- this:理解为一个变量,表示当前方法调用者的地址值。
- super:代表父类存储空间。
10.3 多态
什么是多态?
同类的对象,表现出不同形态。
多态的表现形式
父类类型 对象名称 = 子类对象;
多态的前提
- 有继承关系
- 有父类引用指向子类对象
- 有方法的重写
多态的好处
使用父类类型作为参数,可以接收所有子类对象,体现多态的拓展性与便利。
多态调用成员的特点
- 变量调用:编译看左边,运行也看左边。
- 方法调用:编译看左边,运行看右边。
- 编译看左边:javac编译代码的时候,会看左边的父类中有没有这个对象,如果有编译成功,如果没有编译失败。
- 运行看左边:java运行代码的时候,实际获取的就是左边父类中成员变量的值。
- 运行看右边:java运行代码的时候,实际运行的是子类里面的方法。
如何理解?
Animal a = new dog();
- 现在用a调用变量和方法。而a是animal类的,所以默认都会从animal这个类中去找。
- 成员变量:在子类的对象中,会把父类的成员变量也继承下来。具体调用谁看变量是父类还是子类。
- 成员方法:如果子类对方法进行了重写,那么在虚方法表中会对父类方法进行覆盖。
多态调用成员的内存图理解
多态的优势
- 在多态形式下,右边对象可以实现解耦合,便于拓展和维护。
- 定义方法的时候,使用父类类型作为参数,可以接收所有子类对象,体现多态的拓展性和便利。
多态的弊端
-
不能调用子类的特有的功能。(无法调用子类特有的方法)
-
原因:编译看左边,编译时会先检查左边的父类有没有这个方法,如果没有直接报错。
-
解决方案:将父类型强制转换为子类型。
-
if(a instanceof Dog) { Dog d = (Dog) a; d.lookHome();//调用子类方法 }else if(a instanceof Cat) { Cat c = (Cat) a; c.catchMouse(); }else { System.out.println("无此类型") } //JDK14新特性 //先判断a是否为Dog类型,如果是,则强转成Dog类型 //如果不是,则不强转,直接false if(a instanceof Dog d) { d.lookHome();//直接调用方法 }else if(a instanceof Cat c) { c.catchMouse; }else { System.out.println("无此类型") }
-
包、final、权限修饰符、代码块
什么是包?
包就是文件夹。用来管理各种不同功能的Java类,方便后期代码维护。
- 包名的规则:公司域名反写 + 包的作用,需要全部英文小写,见名知意。
- 使用其他类的规则
- 使用同一个包中的类时,不需要导包。
- 使用java.lang包中的类时,不需要导包。
- 其他情况都需要导包。
- 如果同时使用两个包中的同类名,需要使用全类名。
//方式1
com.xxx.xxx.Student
s = new com.xxx.xxx.Student();
//方式2
import com.xxx.xxx.Student;
Student s = new Student();
final关键字
final关键字可以修饰三种内容:
- 方法:表明该方法是最终方法,不能被重写
- 类:表明该类是最终类,不能被继承
- 变量:叫做常量,只能被赋值一次
常量
在实际开发中,常量一般作为系统的配置信息,方便维护,提高可读性。
常量的命名规范:
- 单个单词:全部大写
- 多个单词:全部大写,单词之间用下划线隔开
细节:
- final修饰的变量是基本类型:那么变量存储的数据值不能发生改变。
- final修饰的变量是引用类型:那么变量存储的地址值不能发生改变,对象内部的可以改变。
权限修饰符
权限修饰符是用来控制一个成员能够被访问的范围的。可以修饰成员变量,方法,构造方法,内部类。
使用规则:实际开发中,一般只用private和public
- 成员变量私有
- 方法公开
- 特例:如果方法中的代码是抽取其他方法中共性代码,这个方法一般也私有。
代码块
- 局部代码块:写在方法内部的大括号
- 构造代码块
- 定义:写在成员位置的代码块
- 作用:可以把多个构造方法中重复的代码抽取出来
- 执行时机:在创建本类对象的时候会执行构造代码块再执行构造方法。
- 逐渐淘汰
- 静态代码块:在构造代码块前加了static关键字
- 格式:static {}
- 特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发,只执行一次。
- 使用场景:在类的加载的时候,做一些数据初始化的时候使用。
10.4 抽象类
抽象类和抽象方法的定义
-
抽象方法:将共性的行为(方法)抽取到父类之后,由于每一个子类执行的内容是不一样,所以,在父类中不能确定具体的方法体。该方法可以定义为抽象方法。
-
抽象类:如果一个类存在抽象方法,那么该类就必须声明为抽象类。
抽象类和抽象方法的定义格式
- 抽象方法的定义格式
public abstract 返回值类型 方法名(参数列表);
- 抽象类的定义格式
public abstract class 类名{}
抽象类和抽象方法的注意事项
- 抽象类不能实例化
- 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
- 可以有构造方法
- 抽象类的子类
- 要么重写抽象类中的所有抽象方法
- 要么是抽象类
10.5 接口
接口就是一种规则,是对行为的抽象。
接口的定义和使用
- 接口用关键字interfa来定义
public interface 接口名{}
- 接口不能实例化
- 接口和类之间是实现关系,通过implements关键字表示
public class 类名 implements 接口名{}
-
接口的子类(实现类)
- 要么重写接口中的所有抽象方法
- 要么是抽象类
-
注意事项
- 接口和类的实现关系,可以单实现,也可以多实现。
public class 类名 implems 接口名1, 接口名2{}
- 实现类还可以在继承一个类的同时实现多个接口
public class 类名 extends 父类 implements 接口名1, 接口名2{}
接口中成员的特点
- 成员变量
- 只能是常量
- 默认修饰符:public static final
- 构造方法:没有构造方法
- 成员方法
- 只能是抽象方法
- 默认修饰符:public abstract
- JDK7以前:接口中只能定义抽象方法。
- JDK8的新特性:接口中可以定义有方法体的方法。
- JDK9的新特性:接口中可以定义私有方法。
接口和类之间的关系
- 类和类的关系:继承关系,只能单继承,不能多继承,但是可以多层继承。
- 类和接口的关系:实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口。
- 接口和接口的关系:继承关系,可以单继承,也可以多继承。
JDK8开始接口中新增的方法
- JDK7以前:接口中只能定义抽象方法。
- JDK8的新特性:接口中可以定义有方法体的方法。(默认、静态)
- 默认方法:
- 需要使用default关键字修饰,解决接口升级的问题。
- 格式:public default 返回值类型 方法名(参数列表){ }
- 注意事项:默认方法不是抽象方法,所以不强制重写。但是如果被重写,重写的时候去掉default关键字。public可以省略,default不能省略。如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法重写。
- 静态方法:
- 格式:public static 返回值类型 方法名(参数列表){ }
- 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用。
- public可以省略,static不能省略。
- 默认方法:
- JDK9的新特性:接口中可以定义私有方法。
- 默认方法定义格式:private void show(){ }//注意没有default
- 静态方法定义格式:private static void method(){ }
接口的应用
- 接口代表规则,是行为的抽象。想让哪个类拥有一个行为,就让这个类实现对应的接口就可以了。
- 当一个方法的参数是接口时,可以传递接口所有实现类的对象,这种方式称之为接口多态。
适配器设计模式
设计模式(design pattern)是一套被反复使用,多数人知晓的,经过分类编目的,代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被人理解、保证代码可靠性、程序的重用性。简单来说,设计模式就是各种套路。
适配器设计模式:解决接口与接口实现类之间的矛盾问题。
- 当一个接口中抽象方法过多,但是只要用其中一部分的时候,就可以适配器设计模式。
- 书写步骤
编写中间类xxxAdapter,实现对应的接口;
对接口中的抽象方法进行空实现;
让真正的实现类继承中间类,并重写需要用的方法;
为了避免其他类创建适配器类的对象,中间的适配器类用abstract进行修饰。
10.6 内部类
内部类就是在一个类的里面,再定义一个类。如在A类的内部定义B类,B类就称为内部类。
public class Outer{//外部类
public class Inner{//内部类
xxx;//内部类的事物是外部类的一部分,内部类单独出现没有任何意义。
}
}
内部类的访问特点:
- 内部类可以直接访问外部类的成员,包括私有。
- 外部类访问内部类成员,必须创建对象。
内部类的分类
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类
成员内部类
- 写在成员位置的,属于外部类的成员。
- 成员内部类可以被一些修饰符修饰,比如:private,默认,protected,public,static等。
- 在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量。
获取成员内部类对象的两种方式
- 方式一:在外部类中编写方法,对外提供内部类对象。(private)
public Inner getInstance(){
return new Inner();
}
- 方式二:直接创建格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象;
Outer.Inner oi = new Outer().new Inner();
成员内部类获取外部类的成员变量
没有重名直接调用外部类即可
public class Outer {
private int a = 10;
class Inner {
private int a = 20;
public void show() {
int a = 30;
System.out.println(a);//30
System.out.println(this.a);//20
//out.this获取了外部类对象的地址值
System.out.println(Outer.this.a);//10
}
}
}
内部类的内存图
静态内部类
静态内部类只能访问外部类中的静态变量和静态方法,如果想要访问非静态的需要创建对象。
创建静态内部类对象的格式:外部类名.内部类名 对象名 = new 外部类名.内部类名()
调用非静态方法的格式:先创建对象,用对象调用
调用静态方法的格式:外部类名.内部类名.方法名()
public class Outer{//外部类
static class Inner{//静态内部类
xxx;
}
}
//创建静态内部类对象
Outer.Inner oi = new Outer.Inner();//new关键字与Inner直接交互
//调用静态方法
Outer.Inner.show2();
局部内部类
-
将内部类定义在方法里面就叫做局部内部类,类似于方法里面的局部变量。
-
外界是无法直接使用局部内部类,需要在方法内部创建对象并使用。
-
该类可以直接访问外部类成员,也可以访问方法内的局部变量。
匿名内部类
匿名内部类本质上就是隐藏了名字的内部类。可以写在成员位置,也可以写在局部位置。
格式:
new 类名或者接口名() {//内容包含继承/实现 -> 方法重写 -> 创建对象
重写方法;
};
//举例
new Inter() {
public void show() {
}
}
public class Student extends Swim {
@override
public void swim() {
xxx;
}
}
实现过程:
- 把前面的class删掉,剩余的内容就变成了一个没有名字的类。
- 这个没有名字的类想要实现swim接口。把swim写在大括号的前面,表示这个名字的类实现了swim接口,所以需要在类中重写接口里所有的抽象方法。
- 根据new 类名() ;的格式完善代码。如下所示:
//匿名类的主体是{}之间的内容
//类实现了接口swim
//new的类的主体不是接口
new Swim() {//若是接口则为实现关系,若为类则为继承关系
@override
public void swim() {
xxx;
}
};
使用场景:如果一个类只需要用一次,那么单独定义一个类太麻烦。此时可以采用内部类。
创建格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象;
Outer.Inner oi = new Outer().new Inner();
成员内部类获取外部类的成员变量
没有重名直接调用外部类即可
public class Outer {
private int a = 10;
class Inner {
private int a = 20;
public void show() {
int a = 30;
System.out.println(a);//30
System.out.println(this.a);//20
//out.this获取了外部类对象的地址值
System.out.println(Outer.this.a);//10
}
}
}