一、接口
接口是更加彻底的抽象,接口成分仅有抽象方法、常量。(JDK1.8之前),接口同样是不能创建对象的。
从jdk1.9开始:接口中允许定义私有方法
定义格式
修饰符 interface 接口名称{
// 抽象方法
// 常量
}
1、接口成分
抽象方法
接口定义的方法全部都是抽象方法,会自动省略 public abstract
// 接口抽象方法
// public abstract void run();
void run();
常量
接口定义的变量是常量,会自动省略 public final static。也就是说在接口中定义的成员变量实际上是一个常量。
public static final修饰后,变量值就不可被修改,并且是静态化的变量可以直接用接口名访问,所以也叫常量。
常量必须要给初始值。常量命名规范建议字母全部大写,多个单词用下划线连接。
// public static final int AGE = 12 ;
int AGE = 12; //常量
// public static final String SCHOOL_NAME = "黑马程序员";
String SCHOOL_NAME = "黑马程序员"; // 字符串常量
2、接口的实现和继承
实现
类与接口的关系为实现关系,即类实现接口,实现使用 implements 关键字。 该类可以称为接口的实现类,也可以称为接口的子类。
Java中的类可以实现一个接口,也可以同时实现多个接口,可以理解成实现多个规范。
实现的多个接口中的抽象方法全部需要重写,没有全部重写抽象方法的实现类也变成抽象类。
class 实现类 implements 接口1,接口2,...{
}
继承
接口和接口之间可以继承,可以继承一个/多个(但继承/多几次)。
接口继承接口,就是把其他接口的抽象方法与本接口进行了合并。
- 1、提高代码的复用性
- 2、提高代码的拓展性
interface 子接口 extends 父接口1,父接口2,...{
}
类与接口是实现关系
接口与接口是继承关系
3、JDK1.8以后接口新增特性
含默认方法和静态方法(>=JDK1.8)
默认方法
使用 default 修饰,不可省略,供子类调用或者子类重写(子类对象.默认方法)。
权限修饰符默认是 public(默认加上),可以被继承、被重写,但是不能像子类一样使用super调用。
静态方法
使用 static 修饰,供接口直接调用(接口名.静态方法)。
- 继承、实现都无法重写静态方法
- 静态方法可以被继承,但是不能通过实现继承
理解: 类1 extends 类2 implements 接口,类1能拿到类2的静态方法,但不能拿到接口的静态方法。
接口1 extends 接口2,接口1可以拿到接口2的静态方法
public interface InterFaceName {
// 默认方法
public default void method() {
// 执行语句
}
// 静态方法
public static void method2() {
// 执行语句
}
}
重写默认方法注意(了解):
子接口重写默认方法时,default关键字可以保留。
实现类重写默认方法时,default关键字不可以保留。
含私有方法和私有静态方法(>=JDK1.9)
私有方法: 使用 private 修饰,供接口中的默认方法或者静态方法调用。
- 接口定义公开的默认方法和公开的静态方法调用接口的私有方法
定义格式
private [static] 返回值 方法名(参数...) {
}
代码示例:
public interface InterFaceName {
//私用方法
private void method() {
// 执行语句
}
//私有静态方法
private static void method() {
// 执行语句
}
}
普通方法与静态方法间的调用
- 静态方法能调用静态方法
- 普通方法能调用普通方法和静态方法
4、类实现多个接口注意事项
接口多实现情况下各个接口的静态方法调用
- 调用方式:
各自接口名.静态方法 - 静态方法名一致不影响,可以通过接口名区分
父类中的方法和接口中的默认方法重名问题
方法优先级,就近原则:本类 -> 父类 -> 接口
当多实现情况下,接口中有同名的default方法时,会报错(二义性)
二、final关键字
final是不可改变,最终的含义。可以用于修饰类、方法和变量(成员变量、静态成员变量、局部变量)。
- 类:被修饰的类,不能被继承。
- 方法:被修饰的方法,不能被重写。
- 变量:被修饰的变量,有且仅能被赋值一次。(在声明变量的时候必须同事赋值)
1、修饰类
被final修饰的类无法被继承(太监类)
final class 类名 {
}
查询API发现像 public final class String 、 public final class Math 、 public final class Scanner等,很多学习过的类,都是被final修饰的,目的就是供我们使用,而不让我们所以改变其内容。
2、修饰方法
final修饰的方法无法被子类重写。
修饰符 final 返回值类型 方法名(参数列表){
//方法体
}
代码:
class Fu2 {
//void(返回值)前面的修饰符可以随意调换顺序
final public void show1() {
System.out.println("Fu2 show1");
}
public void show2() {
System.out.println("Fu2 show2");
}
}
class Zi2 extends Fu2 {
// @Override
// public void show1() {
// System.out.println("Zi2 show1");
// }
@Override
public void show2() {
System.out.println("Zi2 show2");
}
3、修饰变量(局部变量、静态成员变量、成员变量)
局部变量
局部变量:在方法中定义的变量。
基本类型的局部变量,被final修饰后,只能赋值一次,不能再更改。
public class FinalDemo1 {
public static void main(String[] args) {
// 声明变量,使用final修饰
final int a; //报错
// 第一次赋值
a = 10;
// 第二次赋值
a = 20; // 报错,不可重新赋值
// 声明变量,直接赋值,使用final修饰
final int b = 10;
// 第二次赋值
b = 20; // 报错,不可重新赋值
}
}
final修饰 静态成员变量
static修饰的变量,再被final修饰称之为【常量】。 即:final static 变量名 = 变量值;
- static修饰变量在声明是可以不初始化(赋值),final修饰只能被赋值一次且在声明时必须初始化(赋值)
被final修饰的常量名称,一般都有书写规范,所有字母都大写。
class Video {
//等价静态的基本类型变量
//常量有一个命名规范:常量名一般用大写
//快捷键 :ctrl+shift+u,选择部分变大写
//直接通过类名.LEVEL调用,不需要经过构造方法,必须在声明的时候进行初始化
public static final char LEVEL = 3;
}
final修饰 实例成员变量
实例成员变量涉及到初始化的问题,初始化方式有显示初始化和构造器初始化,只能选择其中一个方式初始化
- 显示初始化: 在声明成员变量的时候立即赋值
public class Student {
final int num = 10;
}
- 构造器初始化: 在构造方法中赋值一次。
每个构造方法中(有参/无参)都要赋值一次
public class Student {
final int num = 10;
final int num2;
//在无参中赋值
public Student() {
this.num2 = 20;
// this.num2 = 20;
}
//在有参中赋值
public Student(String name) {
this.num2 = 20;
// this.num2 = 20;
}
}
三、单例设计模式
单例设计模式,即一个类只有一个对象实例。正常情况下一个类可以创建无数个对象,在单例模式中一个类只能创建一个对象
单例设计模式的概念/作用:
使得JAVA程序的某个类在运行阶段只能创建一个对象。
实现步骤
- 1、私有构造方法,private修饰构造方法
- 2、定义私有静态成员变量(常量)存储单例
- 3、通过公开的静态getter方法获取单例
单例设计模式的分类
1.饿汉式单例案例
饿汉单例设计模式就是使用类的时候已经将对象创建完毕,不管以后会不会使用到该实例化对象,先创建了再说。很着急的样子,故被称为“饿汉模式”。
public class Singleton {
// 1.将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
private Singleton() {}
// 2.在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型的成员变量。
private static final Singleton instance = new Singleton();
// 3.定义一个静态方法返回这个唯一对象。
public static Singleton getInstance() {
return instance;
}
}
获取单例
Singleton sing = Singleton.getInstance();
2.懒汉式单例
懒汉单例设计模式就是调用getInstance()方法时实例才被创建,先不急着实例化出对象,等要用的时候才例化出对象。不着急,故称为“懒汉模式”。
public class Singleton {
// 2.在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型的成员变量。
private static Singleton instance;
// 1.将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
private Singleton() {}
// 3.定义一个静态方法返回这个唯一对象。要用的时候才例化出对象
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
3、双重锁形式(解决懒汉式的线程安全问题)
懒汉单例设计模式在多线程环境下可能会实例化出多个对象,不能保证单例的状态。我们在学习完多线程的时候还会再讲解如何解决这个问题。
饿汉式和懒汉式的区别
静态成员变量是声明的时候初始化,还是get的时候初始化
饿汉式还是懒汉式用的多呢?
饿汉式实现更简单,懒汉式其实有线程安全问题(学多线程的时候会学)。
四、多例设计模式(与单例设计模式对应)
1、多例设计模式
https://blog.youkuaiyun.com/qq_38737992/article/details/89644649
2、老王讲设计模式(四)——多例模式
https://www.jianshu.com/p/78061e1f79a5
五、枚举
枚举是一个特殊的类,有固定实例个数的类型,我们可以把枚举理解成有固定个数实例的多例模式。即枚举能用来表达固定的几个特定的选项。
枚举定义格式:
public enum 枚举名{
//第一行都是罗列枚举实例,这些枚举实例直接写大写名字即可。
选项1,选项2,....;
}
枚举的使用:
作为数据类型使用
枚举名.选项
1、不使用枚举存在的问题
假设我们要定义一个人类,人类中包含姓名和性别。通常会将性别定义成字符串类型,代码演示如下:
public class Person {
private String name;
private String sex;
public Person() {
}
public Person(String name, String sex) {
this.name = name;
this.sex = sex;
}
// 省略get/set/toString方法
}
public class Demo01 {
public static void main(String[] args) {
Person p1 = new Person("张三", "男");
Person p2 = new Person("张三", "abc"); // 因为性别是字符串,所以我们可以传入任意字符串
}
}
不使用枚举存在的问题:可以给性别传入任意的字符串,导致性别是非法的数据,不安全,违反代码的逻辑性。
2、枚举的作用与应用场景
枚举的作用:一个方法接收的参数是固定范围之内的时候,即可以穷举的固定几个选项,那么可使用枚举。
枚举的应用:枚举通常可以用于做信息的分类,如性别,方向,季度等。
枚举的最大的意义在于,提高代码的可读性。
入门案例
1、 定义枚举:BOY表示男,GIRL表示女
enum Sex {
BOY, GIRL; // 男,女
}
2、Perosn中的性别有String类型改为Sex枚举类型
public class Person {
private String name;
private Sex sex;
public Person() {
}
public Person(String name, Sex sex) {
this.name = name;
this.sex = sex;
}
// 省略get/set/toString方法
}
3.、使用是只能传入枚举中的固定值
public class Demo02 {
public static void main(String[] args) {
Person p1 = new Person("张三", Sex.BOY);
Person p2 = new Person("张三", Sex.GIRL);
Person p3 = new Person("张三", "abc");
}
}
测试运行。
入门案例枚举底层分析
枚举的本质是一个类,我们刚才定义的Sex枚举最终效果如下(通过编译-反编译后分析):
enum Sex {
BOY, GIRL; // 男,女
}
//1、枚举Sex是一个final clss 继承了Enum类
final class Sex extends Enum{
//2、枚举中的选项其实是枚举类的常量,用枚举类做数据类型,在静态代码块中初始化
public static final Sex BOY;
public static final Sex GIRL;
public static SEX[] values(){};
public static SEX valueOf(java.lang.String){};
//3、枚举中的构造方法是私有的,多例(单例)
private Sex(){
}
//初始化枚举选项
static {
BOY = new SEX();
GIRL = new SEX();
};
}
因为枚举的本质是一个类,所以枚举中还可以有成员变量,成员方法等。
public enum Sex {
BOY(18), GIRL(16);
//成员变量
public int age;
// getter/setter方法
Sex(int age) {
this.age = age;
}
//成员方法
public void showAge() {
System.out.println("年龄是: " + age);
}
}
测试类
public class Demo03 {
public static void main(String[] args) {
Person p1 = new Person("张三", Sex.BOY);
Person p2 = new Person("张三", Sex.GIRL);
Sex.BOY.showAge();
Sex.GIRL.showAge();
}
}
编译和反编译研究枚举底层
枚举是一种特殊的类
编译:
- java文件 -> javac编译 -> class文件
反编译工具:
- class文件 -> 反编译工具 -> java文件
怎么找class文件:
- 模块名右键->show in explorer -> out -> …->包名->class文件
- 如果没有:要编译的类右键->recompile
反编译工具:
- jd-gui、xjad:配合起来用
小结:
枚举的底层
1、枚举是一个final clss 继承了 Enum类
2、枚举中的选项其实是枚举类的常量,在静态代码块中初始化
3、枚举中的构造方法是私有的,多例(单例)。一个选项单例,多个选项多例
4、枚举也能定义方法、构造方法
枚举如果想携带额外信息(改造枚举类Sex):
- 1、在枚举中定义新的成员变量
enum Sex {
BOY, GIRL; // 男,女
//如果想枚举额外添加更多的信息
//1、在枚举中定义新的成员变量
private String name;
}
- 2、修改构造方法,把新的成员变量加入
enum Sex {
BOY, GIRL; // 男,女
//如果想枚举额外添加更多的信息
//1、在枚举中定义新的成员变量
private String name;
// 2、修改构造方法,把新的成员变量加入
private Direction(String name) {
this.name = name;
}
//3、getter/setter
}
- 3、枚举中常量需要根据新的构造方法进行调整
enum Sex {
//3、枚举中常量需要根据新的构造方法进行调整,传入参数。
BOY("男"), GIRL("女"); // 男,女
//如果想枚举额外添加更多的信息
//1、在枚举中定义新的成员变量
private String name;
// 2、修改构造方法,把新的成员变量加入
private Direction(String name) {
this.name = name;
}
}
这时候的底层,定义新成员变量后反编译,初始化选项的static代码块
//初始化枚举选项
static {
BOY = new SEX("BOY","男");
GIRL = new SEX("GIRL","女");
};
- 4、通过成员变量对应的getter/setter方法可以获得枚举选项携带的额外信息
BOY.getName(),GIRL.getName()
理解:即定义新的变量,是给选项定义的。选项要传入这个参数。static代码块初始化后选项会携带这个参数。