第七章、面向对象编程03

96 阅读9分钟

1、类变量和类方法

1.1 类变量

以前我们只学过成员变量,这些成员变量是存在于每一个对象的,但有时候我们希望这个变量是以类的变量形式出现的,比如一个圆类,计算周长和面积都需要常数π,所以希望它是属于整个类的范畴,而不是一个一个对象的变量。我们可以使用static修饰这个变量。

public class Circle {
    private double radius;
    private static double π = 3.14;

    public Circle(double radius) {
        this.radius = radius;
    }

    public static void main(String[] args) {
        Circle circle = new Circle(3);
        System.out.println(circle.perimeter(circle.radius));
        System.out.println(circle.area(circle.radius));
    }

    public double perimeter(double radius){
        return 2 * π * radius;
    }

    public double area(double radius){
        return π * radius * radius;
    }
}

static修饰的变量是类中所有对象共享的,而且static修饰的变量在类加载的时候就生成了,所以即使不创建对象,也可以使用该变量。

语法:

访问修饰符 static 数据类型 变量名; //比较常见

static 访问修饰符 数据类型 变量名;

类名.变量名 //调用
对象名.变量名

1.2 类方法

也叫做静态方法,语法如下:

访问修饰符 static 数据返回类型 方法名(){//声明
   
}

static 访问修饰符 数据返回类型 方法名(){
   
}

类名.类方法名	//调用
对象名.类方法名

其实有很多方法,我们希望可以快速的使用,而不是new对象后用对象去调,比如一些工具的方法,计数、计时等等,new对象后调用很麻烦,可以使用下面这种方式,加static修饰方法:

public class Calculation {
    public static void main(String[] args) {
        System.out.println(count(1, 2, 3, 4, 5));
    }

    public static int count(int...args){
        int count = 0;
        for (int i = 0; i < args.length; i++) {
            count+=args[i];
        }
        return count;
    }
}

一些细节:

  • 类方法和普通方法都是随着类的加载而加载,将信息存储在方法区中
  • 类方法中没有this参数,普通方法有
  • 类方法中不能包含对象的内容,所以this和super都不能有
  • 普通成员方法可以访问静态成员也可访问非静态成员,静态方法不能访问非静态成员

2、代码块

代码块又叫初始化块,属于类的成员之一,它没有名字,没有返回值,没有参数,里面只有方法体,无法手动显示调用,只能等类加载或对象创建时隐式调用。

语法:

[修饰符]{
    代码;
}

注意:

  • 修饰符要写也只能写static
  • 使用static修饰的代码块叫做静态代码块,其他叫非静态代码块
public class CodeBlock1 {
    public static void main(String[] args) {
        Class_ class_ = new Class_("数学课",40);
        System.out.println("今天上的是:" + class_.introduction + ",一共" + class_.times + "分钟");
    }
}

class Class_{
    public String introduction;
    public int times;

    public Class_(String introduction, int times) {
        this.introduction = introduction;
        this.times = times;
    }

    {
        System.out.println("开始上课");
        System.out.println("自我介绍");
        System.out.println("互相介绍");
    }
}

细节:

  • 静态代码块是类加载时执行,只会执行一次,普通代码块每次创建对象的时候都会执行
  • 类什么时候被加载呢?
    • 创建对象实例的时候
    • 创建子类对象实例的时候,父类也会被加载
    • 使用类的静态成员时(静态属性,静态方法)
  • 普通代码块,创建对象的时候会被隐式调用,创建一次调用一次,只是使用静态成员的话,普通代码块不会执行

调用的顺序:

  1. 先调用静态代码块和静态属性的初始化,这两者的优先级一样
  2. 然后调用普通代码块和普通属性的初始化,优先级一样
  3. 调用构造方法

所以创建一个子对象时候,调用顺序如下:(重要!!记住♥♥)

  1. 父类静态代码块和静态属性
  2. 子类静态代码块和静态属性
  3. 父类普通代码块和普通属性
  4. 父类构造方法
  5. 子类普通代码块和普通属性
  6. 子类构造方法

测试如下:

package com.oliver.codeblock;

/**
 * @author :16140
 * @description :
 * @create :2022-08-08 09:18:00
 */
public class CodeBlock2 {
    public static void main(String[] args) {
        //类被加载的情况举例
        //1. 创建对象实例时(new)
        AA aa = new AA();

        //2. 创建子类对象实例,父类也会被加载, 而且,父类先被加载,子类后被加载
        AA aa2 = new AA();

        //3. 使用类的静态成员时(静态属性,静态方法)
        System.out.println(Cat.n1);
        //static 代码块,是在类加载时,执行的,而且只会执行一次. // DD dd = new DD();

        DD dd1 = new DD();
        //普通的代码块,在创建对象实例时,会被隐式的调用。
        // 被创建一次,就会调用一次。
        // 如果只是使用类的静态成员时,普通代码块并不会执行
        System.out.println(DD.n1);//8888, 静态模块块一定会执行

    }
}

class DD {
    public static int n1 = 8888;//静态属性

    //静态代码块
    static {
        System.out.println("DD 的静态代码 1 被执行...");//
    }

    //普通代码块, 在 new 对象时,被调用,而且是每创建一个对象,就调用一
    //可以这样简单的,理解 普通代码块是构造器的补充
    {
        System.out.println("DD 的普通代码块...");
    }
}

class Animal {

    //静态代码块
    static {
        System.out.println("Animal 的静态代码 1 被执行...");//
    }
}

class Cat extends Animal {
    public static int n1 = 999;//静态属性

    //静态代码块
    static {
        System.out.println("Cat 的静态代码 1 被执行...");//
    }
}

class BB {

    //静态代码块
    static {
        System.out.println("BB 的静态代码 1 被执行...");//1
    }
}

class AA extends BB {

    //静态代码块
    static {
        System.out.println("AA 的静态代码 1 被执行...");//2
    }
}


//结果
BB 的静态代码 1 被执行...
AA 的静态代码 1 被执行...
Animal 的静态代码 1 被执行...
Cat 的静态代码 1 被执行...
999
DD 的静态代码 1 被执行...
DD 的普通代码块...
8888

3、单例设计模式

设计模式是前人总结出来非常重要的方式,可以帮我们优化代码,避免出现代码问题。

单例模式是采用一定的方式保证在整个软件系统中对某个类只能存在一个对象实例,并且对该类只提供一个取得其实例的方法。

使用后解决的问题

单例模式同时解决了两个问题, 所以违反了单一职责原则

  • 保证一个类只有一个实例。 为什么会有人想要控制一个类所拥有的实例数量? 最常见的原因是控制某些共享资源 (例如数据库或文件) 的访问权限。

它的运作方式是这样的: 如果你创建了一个对象, 同时过一会儿后你决定再创建一个新对象, 此时你会获得之前已创建的对象, 而不是一个新对象。

注意, 普通构造函数无法实现上述行为, 因为构造函数的设计决定了它必须总是返回一个新对象。

一个对象的全局访问节点

客户端甚至可能没有意识到它们一直都在使用同一个对象。

  • 为该实例提供一个全局访问节点。 还记得你 (好吧, 其实是我自己) 用过的那些存储重要对象的全局变量吗? 它们在使用上十分方便, 但同时也非常不安全, 因为任何代码都有可能覆盖掉那些变量的内容, 从而引发程序崩溃。

和全局变量一样, 单例模式也允许在程序的任何地方访问特定对象。 但是它可以保护该实例不被其他代码覆盖。

还有一点: 你不会希望解决同一个问题的代码分散在程序各处的。 因此更好的方式是将其放在同一个类中, 特别是当其他代码已经依赖这个类时更应该如此。

如今, 单例模式已经变得非常流行, 以至于人们会将只解决上文描述中任意一个问题的东西称为单例

种类

单例模式有两种:懒汉式和饿汉式。

单例模式

3.1 饿汉式

步骤如下:

  • 构造器私有化
  • 类内部创建对象,这个时候直接new个对象作为静态变量
  • 暴露静态公共方法

3.2 懒汉式

  • 构造器私有化
  • 类内部创建对象,只创建变量,不初始化
  • 暴露静态公共方法,方法中创建实例
public class Test {
    public static void main(String[] args) {
        //Singleton01 singleton01 = new Singleton01();  这样不行了
        System.out.println(Singleton01.getInstance());
        System.out.println(Singleton02.getInstance());
    }
}

//饿汉式
class Singleton01{
    private Singleton01() {
    }

    private static Singleton01 instance = new Singleton01();

    public static Singleton01 getInstance(){
        System.out.println("调用构造器Singleton01");
        return instance;
    }
}

//懒汉式
class Singleton02{
    private Singleton02(){
    }

    private static Singleton02 instance;

    public static Singleton02 getInstance(){
        if (instance == null){
            System.out.println("调用构造器Singleton02");
            return new Singleton02();
        }
        return instance;
    }
}

区别:

image.png

4、final关键字

细节:

  • final可以修饰类、属性、方法、局部变量
  • 类上,不能继承 --->不让改了继承也没什么用了
  • 方法上,不能覆盖--->同理,方法也不让你改了
  • 变量上,不能修改--->变量的引用不能改了,值可以改,但必须初始化
  • 图1:

image.png

  • 图2:

image.png

5、抽象类

5.1 介绍

有时候几个类有相似的特征和功能的时候,但不确定如何实现时,可以将其声明为抽象类,里面的方法就是抽象方法。

public class Abstract01 {
    public static void main(String[] args) {
        Animal animal = new Cat();
        animal.eat();
    }
}

abstract class Animal{
    private String name;

    public Animal() {
    }

    public Animal(String name) {
        this.name = name;
    }

    public abstract void eat();
}

class Cat extends Animal{

    @Override
    public void eat() {
        System.out.println("吃小鱼");
    }
}

细节:

  • 抽象类不能被实例化
  • 抽象类不一定包含abstract方法,抽象类可以没有abstract方法
  • 一旦类包含abstract方法,类必须是abstract类
  • abstract只能修饰类和方法,不能修饰其他
  • 抽象方法不能有主体
  • 一个类继承了abstract方法,如果不设置为抽象类,就必须实现抽象方法
  • 抽象类不能使用private、final、static来修饰,因为这些都是和重写违背的

5.2 模板方法模式

模板设计模式是通过抽象类当作子类的通用模板,子类在抽象类基础上扩展方法的一种理念。

代码如下:

模板抽象类:

package com.oliver.abstract_;

/**
 * @author :16140
 * @description :
 * @create :2022-08-08 22:15:00
 */
abstract public class Template {
    public abstract void job();

    public void calculateTime(){
        long start = System.currentTimeMillis();
        job();
        long end = System.currentTimeMillis();
        System.out.println("执行时间" + (end - start));
    }
}

实现类A:

package com.oliver.abstract_;

/**
 * @author :16140
 * @description :
 * @create :2022-08-08 22:18:00
 */
public class A extends Template{
    @Override
    public void job() {
        long num = 0;
        for (int i = 0; i < 100000000; i++) {
            num+=i;
        }
    }
}

实现类B:

package com.oliver.abstract_;

/**
 * @author :16140
 * @description :
 * @create :2022-08-08 22:19:00
 */
public class B extends Template{
    @Override
    public void job() {
        int num = 10;
        for (int i = 0; i < 1900000000; i++) {
            num+=i;
        }
    }
}

6、接口

接口主要是定义一些行为的,接口当中的方法没有实现,用于子类去实现,语法如下:

interface 接口名{
    //属性
    
    //方法
}

class 类名 implements 接口名{
    //属性
    //实现的方法
}

Java8以后接口类可以有静态方法、默认方法,也就是说可以有方法的实现。

实例:

接口:

public interface DBInterface {
    public void connect();  //链接的方法
    public void close();  //关闭的方法
}

实现类1:

package com.oliver.interface_;

/**
 * @author :16140
 * @description :
 * @create :2022-08-08 22:29:00
 */
public class MySQL_DB implements DBInterface{
    @Override
    public void connect() {
        System.out.println("连接mysql");
    }

    @Override
    public void close() {
        System.out.println("关闭mysql");
    }
}

实现类2:

package com.oliver.interface_;

/**
 * @author :16140
 * @description :
 * @create :2022-08-08 22:30:00
 */
public class Oracle_DB implements DBInterface {
    @Override
    public void connect() {
        System.out.println("连接Oracle");
    }

    @Override
    public void close() {
        System.out.println("关闭Oracle");
    }
}

测试:

package com.oliver.interface_;

/**
 * @author :16140
 * @description :
 * @create :2022-08-08 22:31:00
 */
public class TestDB {
    public static void main(String[] args) {
        DBInterface mysqlUser = new MySQL_DB();
        mysqlUser.connect();
        mysqlUser.close();

        DBInterface oracleUser = new Oracle_DB();
        oracleUser.connect();
        oracleUser.close();
    }
}

细节:

  • 接口不能被实例化
  • 接口方法是public方法,其中的方法可以不用abstract修饰,也就是public abstract可以忽略
  • 如果实现接口,就必须实现其所有方法
  • 抽象类实现接口,可以不用实现的方法
  • 类可以实现多个接口
  • 接口中的属性是public static final的
  • 接口中的属性访问方式是:接口名.属性名
  • 接口不能继承类,而且接口的修饰符是public的

比较简单就不演示了。

接口和抽象类的区别

接口比抽象类更加灵活

接口需要满足like - a的关系,继承需要满足is - a的关系

image.png

7、内部类

内部类分为4种:

  • 局部内部类
  • 匿名内部类
  • 成员内部类
  • 静态内部类

7.1 局部内部类

一般是在类的方法内部创建了新的类,一般有下面几个注意点:

  • 可以直接访问外部类的所有成员,包括私有的成员
  • 内部类不能添加访问修饰符,但是可以使用final
  • 作用域就是方法或代码块种
  • 内部访问外部是直接访问,外部访问内部需要创建对象,不是本类的外部无法访问局部内部类
public class LocalInnerClass{
    public static void main(String[] args) {
        Outer02 outer02 = new Outer02();
        outer02.m1();
        System.out.println("outer02的hashcode = " + outer02);
    }
}

class Outer02{
    private int n1 = 100;

    private void m2(){
        System.out.println("outer02.m2()");
    }

    public void m1(){
        final class Inner02{
            private int n1 = 800;
            public void f1(){
                System.out.println("n1=" + n1 + " 外部类的 n1=" + Outer02.this.n1);
                System.out.println("Outer02.this hashcode = " + Outer02.this);
                m2();
            }
        }

        Inner02 inner02 = new Inner02();
        inner02.f1();
    }
}

7.2 匿名内部类

语法:

new 类或接口名(参数列表){
    类体;
}

细节:

  • 可以访问外部类的所有成员,也包含私有的
  • 不能添加访问修饰符,因为它就是一个局部变量
  • 作用域在方法或代码块中
  • 匿名内部类访问外部类是直接访问,外部其他类不能访问匿名内部类,如果外部和内部参数重名了,遵循究竟原则

实例:

public class AnonymousInnerClass {
    public static void main(String[] args) {
        Outer03 outer03 = new Outer03();
        outer03.f1();
        System.out.println("main outer05 hashcode=" + outer03);
    }
}

class Outer03{
    private int n1 = 10;
    public void f1(){
        Person p = new Person(){
            private int n1 = 88;

            public void hi(){
                System.out.println("匿名内部类重写了hi方法, n1=" + n1 + "外部内的n1="
                 + Outer03.this.n1);
                System.out.println("Outer05.this hashcode=" + Outer03.this);
            }
        };
        p.hi();

        new Person(){

            @Override
            public void hi() {
                System.out.println("匿名内部类重写了hi方法");
            }

            @Override
            public void ok(String str) {
                super.ok(str);
            }
        }.ok("zhangsan");
    }
}

class Person{
    public void hi(){
        System.out.println("Person hi()");
    }

    public void ok(String str){
        System.out.println("Person ok()" + str);
    }
}

7.3 成员内部类

细节:

  • 成员内部类是定义在外部类的成员位置,并且没有static修饰的
  • 可以直接访问外部类的所有成员可以直接访问,包含私有的
  • 可以添加任意访问修饰符(public、protected、默认、private)
  • 需要在外部类的成员方法中创建成员内部类对象,再调用方法
  • 成员内部类访问外部类,直接访问
  • 外部类访问成员内部类,创建对象再访问
  • 方法也同样是就近原则

实例:

public class MemberInnerClass01 {
    public static void main(String[] args) {
        Outer04 outer04 = new Outer04();
        outer04.t1();

        Outer04.Inner01 inner01 = outer04.new Inner01();
        inner01.say();

        Outer04.Inner01 inner01Instance = outer04.getInner01Instance();
        inner01Instance.say();

    }
}

class Outer04 {
    private int i1 = 10;
    public String name = "张三";

    public void hi() {
        System.out.println("hi()方法");
    }

    public class Inner01 {
        private double sal = 10.5;
        private int i1 = 66;

        public void say() {
            System.out.println("i1 = " + i1 + " name = " + name + " 外部类的 n1=" + Outer04.this.i1);
            hi();
        }
    }

    public Inner01 getInner01Instance(){
        return new Inner01();
    }

    public void t1(){
        Inner01 inner01 = new Inner01();
        inner01.say();
        System.out.println(inner01.sal);
    }
}

7.4 静态内部类

细节:

  • 静态内部类是定义在外部类的成员位置,并且有static修饰的
  • 可以直接访问外部类的所有成员可以直接访问,包含私有的,但是不能直接访问非静态成员
  • 可以添加任意访问修饰符(public、protected、默认、private)
  • 作用域:整个类体
  • 静态内部类访问外部类,直接访问
  • 外部类访问内部类,创建对象再访问
  • 外部其他类无法访问内部类
  • 成员重名时,遵循就近原则
public class StaticInnerClass01 {
    public static void main(String[] args) {
        Outer05 outer05 = new Outer05();
        outer05.m1();

        //外部其他类 使用静态内部类
        //方式 1
        //因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
        Outer05.Inner05 inner05 = new Outer05.Inner05();
        inner05.say();

        //方式 2
        //编写一个方法,可以返回静态内部类的对象实例. Outer05.Inner05 inner051 = outer05.getInner05();
        System.out.println("============");
        inner05.say();
        Outer05.Inner05 inner05_ = Outer05.getInner05_();
        System.out.println("************");
        inner05_.say();
    }
}

class Outer05 {
    private int n1 = 05;
    private static String name = "张三";

    private static void cry() {}

    static class Inner05 {
        private static String name = "张三";
        public void say() {
            //如果外部类和静态内部类的成员重名时,静态内部类访问的时,
            //默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.成员)
            System.out.println(name + " 外部类 name= " + Outer05.name);
            cry();
        }
    }

    public void m1() {
        //外部类---访问------>静态内部类 访问方式:创建对象,再访问
        Inner05 inner05 = new Inner05();
        inner05.say();
    }

    public Inner05 getInner05() {
        return new Inner05();
    }

    public static Inner05 getInner05_() {
        return new Inner05();
    }
}