面向对象编程(高级)

类变量和类方法

静态变量放在哪里和jdk版本有关系,记住一点:static变量是对象共享
不管static变量在哪里,共识:
(1) static变量是同一个类所有对象共享
(2) static类变量,在类加载的时候就生成了

类变量

类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。

定义语法:
访问修饰符 static 数据类型变量名;[推荐]
static 访问修饰符 数据类型变量名;

访问类变量:
类名.类变量名 或者对象名.类变量名【静态变量的访问修饰符的访问权限和范围和普通属性是一样的。】
推荐使用:类名.类变量名;

类变量使用注意事项和细节讨论

  1. 什么时候需要用类变量 当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):比如:定义学生类,统计所有学生共交多少钱。Student (name, staticfee)
  2. 类变量与实例变量(普通属性)区别 类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
  3. 加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量/实例变量
  4. 类变量可以通过类名.类变量名或者对象名.类变量名来访问,但Java设计者推荐我们使用类名.类变量名方式访问。【前提是满足访问修饰符的访问权限和范围】
  5. 实例变量不能通过类名.类变量名方式访问。
  6. 类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了。
  7. 类变量的生命周期是随类的加载开始,随着类消亡而销毁。

类方法

基本介绍
类方法也叫静态方法。形式如下:
访问修饰符static数据返回类型方法名(){}【推荐】
static访问修饰符数据返回类型方法名(){}

类方法的调用:
使用方式:类名.类方法名或者对象名.类方法名【前提是满足访问修饰符的访问权限】

类方法经典的使用场景

当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率。比如:工具类中的方法utils
Math类、Arrays类、Collections集合类

在程序员实际开发,往往会将一些通用的方法,设计成静态方法,这样我们不需要创建对象就可以使用了,比如打印一维数组,冒泡排序,完成某个计算任务等..

类方法使用注意事项和细节讨论

1)类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区∶
类方法中无this的参数
普通方法中隐含着this的参数
2)类方法可以通过类名调用,也可以通过对象名调用。
3)普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调用。
4)类方法中不允许使用和对象有关的关键字,比如this和super。普通方法(成员方法)可以
5)类方法(静态方法)中只能访问静态变量或静态方法。
6)普通成员方法,既可以访问非静态成员,也可以访问静态成员。

小结:静态方法,只能访问静态的成员,非静态的方法,可以访问静态成员和非静态成员(必须遵守访问权限)

main方法

解释main方法的形式:public static void main(String[] args){}

  1. main方法时虚拟机调用
  2. java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
  3. java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
  4. 该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数 java执行的程序 参数1 参数2 参数3

特别提示:

  1. 在 main()方法中,我们可以直接调用 main 方法所在类的静态方法或静态属性。
  2. 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员

代码块

代码化块又称为初始化块,属于类中的成员[即 是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。

基本语法
[修饰符]{ 代码 }; 说明注意: 1)修饰符可选,要写的话,也只能写static
2)代码块分为两类,使用static 修饰的叫静态代码块,没有static修饰的,叫普通代码块/非静态代码块。
3)逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
4);号可以写上,也可以省略。

代码块使用注意事项和细节讨论

1)static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行。

2)类什么时候被加载[重要背!]
创建对象实例时(new)
创建子类对象实例,父类也会被加载
使用类的静态成员时(静态属性,静态方法)

案例演示:A类extends B类的静态块

3)普通的代码块,在创建对象实例时,会被隐式的调用。
被创建一次,就会调用一次。
如果只是使用类的静态成员时,普通代码块并不会执行。

小结:

  1. static代码块是类加载时,执行,只会执行一次
  2. 普通代码块是在创建对象时调用的,创建一次,调用一次
  3. 类加载的3种情况,需要记住

4)创建一个对象时,在一个类调用顺序是:
①调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
②调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
③调用构造方法。 5)构造器的最前面其实隐含了super()和调用普通代码块,静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于构造器和普通代码块执行的 class A { public A(){//构造器 //这里有隐藏的执行要求 //(1) super(); //(2)调用普通代码块的 System.out.println("ok"); } }
6)创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下: ①父类的静态代码块和静态属性(优先级一样,按定义顺序执行) ②子类的静态代码块和静态属性(优先级一样,按定义顺序执行) ③父类的普通代码块和普通属性初始化(优先级-样,按定义顺序执行) ④父类的构造方法 ⑤子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行) ⑥子类的构造方法

7)静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。

单例设计模式

  1. 静态方法和属性的经典使用
  2. 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思考和摸索

什么是单例模式
单例(单个的实例)

  1. 所谓类的单例设计模式,就是采取一 定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例, 并且该类只提供一个取得其对象实例的方法
  2. 单例模式有两种方式: 1)饿汉式 2)懒汉式

演示饿汉式和懒汉式单例模式的实现。 步骤如下:
1)构造器私有化=》防止直接new
2)类的内部创建对象
3)向外暴露一个静态的公共方法。getInstance

饿汉式(可能造成创建了对象但是没有使用)

package com.fyx.single_;

public class SingleTon01 {
    public static void main(String[] args) {
        //通过方法可以获取对象
        GirlFriend instance = GirlFriend.getInstance();
        System.out.println(instance);

        GirlFriend instance2 = GirlFriend.getInstance();
        System.out.println(instance);

        System.out.println(instance == instance2);//T
        System.out.println(GirlFriend.n1);

    }
}
//有一个类, GirlFriend
//只能有一个女朋友
class GirlFriend {

    private String name;
    public static  int n1 = 100;

    //为了能够在静态方法中,返回 gf对象,需要将其修饰为static
    private static GirlFriend gf = new GirlFriend("小红红");
    //如何保障我们只能创建一个 GirlFriend 对象
    //步骤[单例模式-饿汉式]
    //1. 将构造器私有化
    //2. 在类的内部直接创建对象(该对象是static)
    //3. 提供一个公共的static方法,返回 gf对象
    private GirlFriend(String name) {
        this.name = name;
    }
    public static GirlFriend getInstance() {
        return gf;
    }
    @Override
    public String toString() {
        return "GirlFriend{" +
                "name='" + name + '\'' +
                '}';
    }
}

懒汉式

package com.fyx.single_;

public class SingleTon02 {
    public static void main(String[] args) {
        //System.out.println(Cat.n1);
        Cat instance = Cat.getInstance();
        System.out.println(instance);

        //再次調用getInstance
        Cat instance2 = Cat.getInstance();
        System.out.println(instance2);

        System.out.println(instance == instance2);//T

    }
}
//希望在程序运行过程中,只能创建一個Cat对象
//使用单例模式
class Cat {
    private String name;
    public static int n1 = 999;
    private static Cat cat; //默认是null
    //步驟
    //1.仍然构造器私有化
    //2.定义一个static静态属性对象
    //3.提供一个public的static方法,可以返回一个cat对象
    //4.懒汉式,只有当用戶使用getInstance时,才返回cat对象, 后面在调用时,会返回上次创建的cat对象
    private Cat(String name){
        System.out.println("构造器调用...");
        this.name = name;
    }
    public static Cat getInstance() {

        if(cat == null) {//如果還沒有創建cat對象
            cat = new Cat("小可爱");
        }
        return cat;
    }
    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                '}';
    }
}

饿汉式 VS 懒汉式

  1. 二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建。
  2. 饿汉式不存在线程安全问题,懒汉式存在线程安全问题。
  3. 饿汉式存在浪费资源的可能。因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题。
  4. 在我们javaSE标准类中,java.lang.Runtime就是经典的单例模式。

final 关键字

final中文意思:最后的,最终的
final可以修饰类、属性、方法和局部变量
在某些情况下,程序员可能有以下需求,就会使用到final: 1)当不希望类被继承时,可以用final修饰
2)当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰。 3)当不希望类的的某个属性的值被修改,可以用final修饰
4)当不希望某个局部变量被修改,可以使用final修饰

final 使用注意事项和细节讨论

1)final修饰的属性又叫常量一般用XX XX XX来命名
2)final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一[ 选择一个位置赋初值即可] : ①定义时:如public final double TAX RATE=0.08; ②在构造器中 ③在代码块中 3)如果final修饰的属性是静态的,则初始化的位置只能是 ①定义时
②在静态代码块不能在构造器中赋值。
4)final类不能继承,但是可以实例化对象。
如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承。 5)一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法
6) final不能修饰构造方法(即构造器)
7) final和static往往搭配使用,效率更高,不会导致类加载.底层编译器做了优化处理。

抽象类

当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类

当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法,这个方法就是抽象方法,用abstract来修饰该类就是抽象类。

抽象类介绍

1)用abstract关键字来修饰一个类时,这个类就叫抽象类 访问修饰符 abstract 类名{
} 2)用abstract关键字来修饰一个方法时,这个方法就是抽象方法 访问修饰符abstract返回类型方法名(参数列表);//没有方法体
3)抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现
抽象类()
4)抽象类,是考官比较爱问的知识点,在框架和设计模式使用较多

抽象类使用的注意事项和细节讨论

1)抽象类不能被实例化
2)抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法
3)一旦类包含了abstract方法则这个类必须声明为abstract
4)abstract只能修饰类和方法,不能修饰属性和其它的
5)抽象类可以有任意成员 [抽象类本质还是类],比如: 非抽象方法、构造器、 静态属性等等
6)抽象方法不能有主体,即不能实现
7)如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类。
8)抽象方法不能使用private、final 和static来修饰,因为这些关键字都是和重写相违背的。

抽象类最佳实践-模板设计模式

抽象类体现的就是一种模板模式的设计, 抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。

模板设计模式能解决的问题

1)当功能内部一部分实现是确定,-部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。 2)编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给其子类实现,就是一种模板模式。

接口

接口就是给出一 些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来。

语法: interface 接口名{ //属性 //抽象方法 } class 类名 implements 接口{ 自己属性; 自己方法; 必须实现的接口的抽象方法 } 小结:接口是更加抽象的抽象的类,抽象类里的方法可以有方法体,接口里的所有方法都没有方法体【jdk7.0】。接口体现了程序设计的多态和高内聚低偶合的设计思想。 特别说明: jdk8.0后接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现

注意事项和细节

1)接口不能被实例化
2)接口中所有的方法是public方法,接口中抽象方法,可以不用abstract修饰
3)一个普通类实现接口,就必须将该接口的所有方法都实现。
4)抽象类实现接口,可以不用实现接口的方法。
5)一个类同时可以实现多个接口
6)接口中的属性,只能是final的,而且是public static final修饰符。比如:int a=1;实际上是public static final int a= 1; (必须初始化)
7)接口中属性的访问形式:接口名.属性名
8)接口不能继承其它的类,但是可以继承多个别的接口
interface A extends B,C{}
9)接口的修饰符只能是public和默认,这点和类的修饰符是一样的。

实现接口 vs 继承类

package com.fyx.interface_;

public class ExtendsVsInterface {
    public static void main(String[] args) {
        LittleMonkey wuKong = new LittleMonkey("悟空");
        wuKong.climbing();
        wuKong.swimming();
        wuKong.flying();
    }
}
//猴子
class Monkey {
    private String name;

    public Monkey(String name) {
        this.name = name;
    }
    public void climbing() {
        System.out.println(name + " 会爬树...");
    }

    public String getName() {
        return name;
    }
}
//接口
interface Fishable {
    void swimming();
}
interface Birdable {
    void flying();
}
//继承
//小结:  当子类继承了父类,就自动的拥有父类的功能
//      如果子类需要扩展功能,可以通过实现接口的方式扩展.
//      可以理解 实现接口 是 对java 单继承机制的一种补充.
class LittleMonkey extends Monkey implements Fishable,Birdable {
    public LittleMonkey(String name) {
        super(name);
    }

    @Override
    public void swimming() {
        System.out.println(getName() + " 通过学习,可以像鱼儿一样游泳...");
    }

    @Override
    public void flying() {
        System.out.println(getName() + " 通过学习,可以像鸟儿一样飞翔...");
    }
}

接口和继承解决的问题不同 继承的价值主要在于:解决代码的复用性和可维护性。
接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法。即更加的灵活..
接口比继承更加灵活
接口比继承更加灵活,继承是满足is-a的关系,而接口只需满足like-a的关系。
接口在一定程度上实现代码解耦[即:接口规范性+动态绑定机制]

接口的多态特性

1)多态参数 在前面的Usb接口案例,UsbInterface usb 既可以接收手机对象,又可以接收相机对象,就体现了接口多态(接口引用可以指向实现了接口的类的对象)
2)多态数组
给Usb数组中,存放Phone和相机对象,Phone类还有一个特有的方法call() ,请遍历Usb数组,如果是Phone对象,除了调用Usb接口定义的方法外,还需要调用Phone特有方法call.
3)接口存在多态传递现象

内部类

如果定义类在局部位置(方法中/代码块) :(1) 局部内部类 (2) 匿名内部类

定义在成员位置 (1) 成员内部类 (2) 静态内部类

基本介绍

一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。是我们类的第五大成员[类的五大成员是哪些?[属性、方法、构造器、代码块内部类],内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。

基本语法

class Outer{ //外部类
class Inner{//内部类
} }

class Other{ //外部其他类
}

内部类的分类

➢定义在外部类局部位置上(比如方法内) 1)局部内部类(有类名) 2)匿名内部类(没有类名)
➢定义在外部类的成员位置上: 1)成员内部类(没用static修饰)
2)静态内部类(使用static修饰)

局部内部类的使用

说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。

  1. 可以直接访问外部类的所有成员,包含私有的
  2. 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final 修饰,因为局部变量也可以使用final
  3. 作用域:仅仅在定义它的方法或代码块中。
  4. 局部内部类---> 访问 --->外部类的成员[访问方式:直接访问]
  5. 外部类---> 访问 ---->局部内部类的成员[访问方式:创建对象,再访问(注意:必须在作用域内)]
    记住:(1 )局部内部类定义在方法中/代码块
    (2) 作用域在方法体或者代码块中
    (3) 本质仍然是一个类
  6. 外部其他类---> 不能访问 ---->局部内部类(因为局部内部类地位是一个局部变量)
  7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

匿名内部类的使用(重要!!!!!!!)

(1)本质是类(2)内部类(3)该类没有名字(4)同时还是一个对象

说明:匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名

  1. 匿名内部类的基本语法 new 类或接口(参数列表){
    类体
    };
  2. 匿名内部类的语法比较奇特,因为匿名内部类既是一个类的定义,同时它本身也是一个对象, 因此从语法上看,它既有定义类的特征,也有创建对象的特征,因此可以调用匿名内部类方法。
  3. 可以直接访问外部类的所有成员,包含私有的。
  4. 不能添加访问修饰符,因为它的地位就是一个局部变量。
  5. 作用域:仅仅在定义它的方法或代码块中。
  6. 匿名内部类---> 访问 ---->外部类成员[访问方式直接访问]
  7. 外部其他类---> 不能访 ---->匿名内部类(因为匿名内部类地位是一个局部变量)
  8. 如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员) 去访问

成员内部类的使用

说明:成员内部类是定义在外部类的成员位置,并且没有static修饰。

  1. 可以直接访问外部类的所有成员,包含私有的
  2. 可以添加任意访问修饰符(public、 protected、默认、private),因为它的地位就是一个成员。
  3. 作用域和外部类的其他成员一样,为整个类体,比如前面案例,在外部类的成员方法中创建成员内部类对象,再调用方法
  4. 成员内部类---> 访向 ---> 外部类成员(此如:属性) [访问方式直接访问]
  5. 外部类---> 访问 --->成员内部类[访问方式:创建对象,再访问]
  6. 外部其他类---> 访问 --->成员内部类
  7. 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员) 去访问

静态内部类的使用

说明:静态内部类是定义在外部类的成员位置,并且有static修饰

  1. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
  2. 可以添加任意访问修饰符(public、protected、 默认、private),因为它的地位就是一个成员。
  3. 作用域:同其他的成员,为整个类体
  4. 静态内部类---> 访问 --->外部类(比如:静态属性) [访问方式:直接访问所有静态成员]
  5. 外部类---> 访问 ---> 静态内部类[访问方式:创建对象,再访问]
  6. 外部其他类---> 访问---->静态内部类
  7. 如果外部类和静态内部类的成员重名时,静态内部类访问的时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员) 去访问

本文章使用limfx的vscode插件快速发布