如果老师这么讲面向对象,当年我就不会挂科了吧

C++学籍管理系统:从零构建面向对象数据管理应用 面向对象编程(OOP)是软件工程的核心范式,其核心思想是通过类与对象来模拟现实世界,实现代码的封装、继承和多态。在C++实践中,数据持久化是构建实用程序的关键技术,它涉及将内存中的数据通过文件读写或数据库操作进行持久存储,确保程序重启后数据不丢失。掌握这些技术对于开发数据管理类应用具有重要价值,例如学生信息管理、库存系统等。本文以学生学籍管理系统为例,深入探讨如何运用C++的类设计、STL容器和文件流操作,实现一个结构清晰、功能完整的CRUD应用。通过解析环境配置、链表数据结构等常见问题,为初学者提供一个从 阅读详情

我上大学的时候不爱学习,爱打游戏,每次都临考试前一个月疯狂抱佛脚,抱得佛估计都怕了,印象最深的是我大二学期的 Java 课考了 60 分,别看我及格了但也是极其危险,笔试前又去抱佛的脚,结果答得一般般,上机考试就更别提了,瞎写了几行,编译器都快看不下去了... 估计能考及格也是老师高抬贵手,当时找过系里老师两三次,就差提果篮去老师家上门了。

有时候经常会想,假如时光能倒流,让自己教大学时期学不进去的自己计算机、编程这类的知识,自己能不能学进去,考试的时候还会不会挂科,是老师当时照本宣科根本吸引不了我,还是我当时就没心思学习。

于是我特地总结了下当时大学觉得最难的面向对象的知识,由于这部分知识实在太多我分成了基础和进阶篇,我确认大学教的时候没这么多,这次先上基础篇,反射、各种内部类、抽象类、枚举、接口什么的下期再讲,大纲如下:

类是用来描述同一类事物的,可以在类中定义任意数量的、不同类型的变量,作为这一类事物的属性。这种属性叫做成员变量(member variable),除了类的属性外,类还可以有一组描述自己行为的方法(method)。其实类就相当于我们自己定义了一种数据类型。

比如下面我们定义了一个代表商品的类:

public class Commodity {
    
    String name;
    String id;
    int count;
    double price;

}
复制代码

public class代表这个类是公共类(公共类名必须和文件名相同),商品类里定义了四个成员变量用来代表商品的四个属性--商品名、商品标识、商品数量和商品价格。

类的定义,其实就是创建了一个模版。描述了一种我们需要的数据类型。

初探类和对象

从数据类型的角度看,类就是我们根据自己的需要,创建了一种新的数据类型。所以类也叫做“自定义类型”。 一个 Java 程序中不允许类同名。

而对象(object)是类这个“自定义类型”的具体实例(instance)。

后面可能有时候会说对象、有时候会说类的实例,记住咱们要表述的其实是同一个东西,不必太刻意区分。

在 Java 中可以使用 new 操作符创建一个类的对象。在使用 new 创建的对象中,类中定义的那些成员变量都会被赋以其类型的初始值。看下面这个使用类对象的例子:

public class UserClassCase {
    public static void main(String[] args) {

        // 创建一个Commodity类的实例,用变量m1指向它。
        Commodity m1 = new Commodity();
        // 使用点操作符,给m1指向的实例赋值。
        m1.name = "茉莉花茶包 20 包";
        m1.id = "000099518";
        m1.count = 1000;
        m1.price = 99.9;

        // 创建另外一个Commodity类的实例,用变量m1指向它。
        Commodity m2 = new Commodity();
        m2.name = "可口可乐 330ml";
        m2.id = "000099519";
        m2.count = 1000;
        m2.price = 3.0;


        // 卖出一个商品1
        int m1ToSold = 1;
        System.out.println("感谢购买" + m1ToSold + "个" + m1.name + "。商品单价为"
                + m1.price + "。消费总价为" + m1.price * m1ToSold + "。");
        m1.count -= m1ToSold;
        System.out.println(m1.id + "剩余的库存数量为" + m1.count);

        // 卖出3个商品2
        int m2ToSold = 3;
        System.out.println("感谢购买" + m2ToSold + "个" + m2.name + "。商品单价为"
                + m2.price + "。消费总价为" + m2.price * m2ToSold + "。");
        m2.count -= m2ToSold;
        System.out.println(m2.id + "剩余的库存数量为" + m2.count);


    }
}

复制代码

引用类型

引用类型是使用 Java 时经常被提到的一个名词,Java 中的数据类型分为:基本数据类型和引用数据类型。上节说到变量的名和实,有这样一个概念 :

  • 变量的名指代一个供程序访问的内存地址,变量的实就是地址指向的内存里的值。

那么引用数据类型和基本数据类型的差异在,基本数据类型变量的值,就内存地址里存放的值,而引用数据类型的变量值还是一个地址,需要跳到“值地址”对应的内存才能找到实例。

上面说的就是引用类型的实质,引用类型是Java的一种内部类型,是对所有自定义类型和数组引用的统称,并非特指某种类型。

下面看个例程加深下理解: 例程中 m1 是一个Commodity 类型的引用,它只能指向 Commodity 类型的实例,引用数据类型的变量包含两部分信息:类型和实例。也就是说,每一个引用数据类型的变量(简称引用),都是指向某个类( class /自定义类型)的一个实例/对象(instance / object)。不同类型的引用在 Java 的世界里都是引用。

引用的类型信息在创建时就已经确定,可以通过给引用赋值,让其指向不同的实例,比如 m1 就是 Commodity 类型的引用,可以通过赋值操作让它指向不同的 Commodity 类型实例。这个其实很好理解,就跟把一个整型的值赋值给浮点型的变量在 Java 里是不被允许的一样,基础类型也是相同类型的值才能对变量进行赋值操作。

public class ReferenceAndPrimaryDataType {
    public static void main(String[] args) {
        Commodity m1;
        m1 = new Commodity();
        Commodity m2 = new Commodity();
        Commodity m3 = new Commodity();
        Commodity m4 = new Commodity();
        Commodity m5 = new Commodity();

        // 给一个引用赋值,则两者的类型必须一样。m5可以给m1赋值,因为他们类型是一样的
        m1 = m5;

        System.out.println("m1=" + m1);
        System.out.println("m2=" + m2);
        System.out.println("m3=" + m3);
        System.out.println("m4=" + m4);
        System.out.println("m5=" + m5);

        Commodity m6 = m1;
        System.out.println("m6=" + m6);
        m6 = m5;
        System.out.println("m6=" + m6);


        System.out.println("m1=" + m1);
        System.out.println("m2=" + m2);
        System.out.println("m3=" + m3);
        System.out.println("m4=" + m4);
        System.out.println("m5=" + m5);


        int a = 999;

    }
}

复制代码

怎么更好的理解引用和实例

Commodity m1 = new Commodity();
复制代码
  • 使用 new 操作符可以创建某个类的一个实例。在Java程序运行的时候,所有这些创建出来的实例都被Java放在内存一个叫做堆(heap)的地方,堆可以理解为一个类似公告板的东西。
  • 创建类的一个实例,就是根据类的定义,点出需要的“纸”(内存区域),加工成一个本子,挂在公告板上。实例本身,可以认为是一个小本子。
  • 引用里存放的,相当于某个本子(实例)在布告板上的位置,通过这个位置我们才能定位到这本子(实例),从而访问其中的纸张(属性)。

类,对象和引用的关系

类和对象的关系

  • 类相当于对象的模板,对象是类的一个实例。
  • 一个 Java 程序中类名相同的类只能有一个,也就是类型不会重名。
  • 一个类可以有很多对象。
  • 一个对象只能根据一个类(模板)来创建。

引用和类以及对象的关系

  • 引用必须是,只能是一个类的引用。
  • 引用只能指向其所属的类型的类对象。
  • 相同类型的引用之间可以赋值。
  • 只能通过指向一个对象的引用,来操作一个对象,比如访问某个成员变量。

下面通过例程加深一下理解

public class Commodity {
    String name;
    String id;
    int count;
    double price;

}

public class Commodity1 {
    String name;
    String id;
    int count;
    double price;

}

public class ClassInstanceAndRef {
    public static void main(String[] args) {
        Commodity m = new Commodity();
        Commodity1 m1 = new Commodity1();

        Commodity commodity = m;
        // 即使 Commodity 和 Commodity1 的内容一摸一样,那也是不同的类。
        // 不同类的引用不可以互相赋值,因为它们本质上是不同的对象。
        Commodity commodity = m1;
    }
}
复制代码

引用类型/对象的缺省值

null 是引用类型的缺省值,null 代表空,不存在,也常被成为空指针,因为它不指向任何已存的实例。引用类型的数组创建出来,每个元素的初始值就都是null。

null 带来的问题

Java里比较常见的错误 NullPointerException 就是因为 null 带来的问题,看一下下面这个例程,Commodity 类型的数组,因为 Commodity 是引用类型,数组创建后默认元素值是 null, 接下来我们选择性的给数组元素进行赋值,然后当做每个元素都已经被赋值 Commodity 对象一样,在循环里调用 Commodity 对象的属性,看看会发生什么问题。

public class RefAndNull {
    public static void main(String[] args) {
        // 数组在创建出来之后,会按照类型给数组中的每个元素赋缺省值。
        // 引用类型的缺省值是null
        Commodity[] ms = new Commodity[9];
        // 给索引为偶数的元素赋值
        for (int i = 0; i < ms.length; i++) {
            if (i % 2 == 0) {
                ms[i] = new Commodity();
            }
        }
        // 依次输出数组的值
        for (int i = 0; i < ms.length; i++) {
            System.out.println(ms[i]);
        }

        for (int i = 0; i < ms.length; i++) {
            Commodity m = ms[i];
            System.out.println(m.price);
            System.out.println(m.count);
            System.out.println(m.name);
            System.out.println(m.id);
        }
    }
}

复制代码

上面这个程序,当打印实例属性的循环执行到数组的奇数索引元素的时候就会出现运行时错误 NullPointerException,程序中断。

Exception in thread "main" java.lang.NullPointerException
	at RefAndNull.main(RefAndNull.java:21)
复制代码

这个问题,只要足够细心就能避免,在使用引用之前一定要检查一下它是不是 null,所以上面的例程加一个判断就能正常运行。

if (m[i] != null) {
    System.out.println(m.price);
    System.out.println(m.count);
    System.out.println(m.name);
    System.out.println(m.id);
}
复制代码

Java中的包和访问控制符

上面咱们说过了,Java 程序中,不允许类名重复,其实这里说的类名指的的类全限定名,不是我们看到的简单类名,如果是的话也太容易重复了,什么是全限定名呢,就是加上包名的类名。

为了避免类太多,放在在一起混乱,可以把类放在文件夹里。这时就需要使用 package 语句告诉 Java 这个类在哪个 package 里。 package 语句要和源文件的目录完全对应,大小写也要一致。 我们常把 package 称作包。一般来说,类都会在包里,而不会直接放在项目的根目录。

看一下下面这个例子:

package com.phone.parts;

public class Mainboard {
    public CPU cpu;
    public Memory memory;
    public Storage storage;
    public String model;
    // 上市年份
    public int year;
}

复制代码
  • 上面这个Mainboard类文件中,使用 package 声明了包名 com.phone.parts。 所以Mainboard.java 文件必须放在 src/com/phone/parts这个目录里,不能有差异。
  • 一个类只能有一个package语句,如果有package语句,则必须是类的第一行有效代码。
  • 不同的包里可以有相同名字的类,原因是 Java 使用的是包名+类名作为类的全限定名。

使用包里的类 import

当使用另一个包里的类的时候,需要使用类的全限定名,即要带上包名。比如我们在com.phone包下有一个 phone 类,想要使用上面定义的com.phone.parts包里的Mainboard类,那么就必须像下面这样:

package com.phone;


public class Phone {
    com.phone.parts.Mainboard mainboard;
}

复制代码

如果每次都使用带包名的类就太繁琐了,这个时候就可以在类的上面使用 import 语句,把类导入使用,就可以省略掉包名。

package com.phone;

import com.phone.parts.Mainboard;

public class Phone {
    Mainboard mainboard;
}

复制代码

如果要导入多个类,则需要使用多个 import 语句

package com.phone;

import com.phone.parts.Mainboard;
import com.phone.parts.CPU;

public class Phone {
    Mainboard mainboard;
    CPU cpu;
    
}

复制代码

如果需要导入一个包的多个类,可以使用 * 通配符,它会导入包目录下的所有类

package com.phone;

import com.phone.parts.*;

public class Phone {
    Mainboard mainboard;
    CPU cpu;
    
}

复制代码

属性访问修饰符

  • 访问修饰符是一种限制或者允许属性访问的修饰符。
  • 被 public 修饰的属性是可导出的,可以被任意包中的类访问。
  • 如果缺省访问修饰符,则属性只能被同一个包内的其他类和自己的对象访问到。
package com.phone.parts;

public class Mainboard {
    // 缺省访问修饰符,所以cpu属性只能在包内部访问
    CPU cpu;
    public Memory memory;
    public Storage storage;
    public String model;
    // 上市年份
    public int year;
}

---

package com.phone;

public class TestUseMainboard {
    public static void main(String[] args) {
        Mainboard mainboard = new Mainboard();
        // 这里会编译报错,在com.phone包里访问不到mainboard的cpu属性
        mainboard.cpu = new CPU();
        mainboard.cpu.producer="aaa";
    }
}

复制代码

类的全限定名

  • 包名 + 类名= 类的全限定名,也可以简称为类的全名。
  • 同一个 Java 程序中全限定名字不可重复。

方法

面向对象里,方法代表的是类的行为,是它们的动态描述(而属性是静态描述),比如下面的商品类进行商品描述的方法 describe,计算商品利率的 calculateProfit,获取商品数量的 getCurrentCount 都是方法。

方法的构成形式是: 【访问控制符 返回值类型 方法名 方法参数列表 方法体 】这几个部分构成,结合下面几个方法的例子会更容易理解,每个关键部分都用注释做了说明。

public class CommodityV2 {

    public String name;
    public String id;
    public int count;
    public double soldPrice;
    public double purchasePrice;
    String madeIn;

    // >> 访问修饰符 public/protected/private 当然也可以缺省
    // >> 返回值类型:无需返回值则用void表示,void是Java中的关键字
    // >> 方法名:任意合法的标识符都可以
    // >> 参数列表:后续讲解
    // >> 方法体:方法的代码
    // >> 方法体内部定义的变量叫做局部变量
    public void describe() {
        double netIncome = soldPrice - purchasePrice;
        System.out.println("商品名字叫做" + name + ",id是" + id + "。 商品售价是" + soldPrice
                + "。商品进价是" + purchasePrice + "。商品库存量是" + count +
                "。销售一个的毛利润是" + netIncome + "。制造地为" + madeIn);
    }
    
    // >> 在方法定义中指定方法的返回值类型
    // >> Java中一个方法只能有一种返回值,如果不需要返回值则用void表示
    // >> 如果定义了返回值,则必须使用 return 语句返回方法的返回值,return 是 Java 的关键字
    // >> 可以认为,返回值必须要能够用来给返回值类型的变量赋值
    public double calculateProfit(){
        double profit = soldPrice - purchasePrice;
        // >> 这个return是代码块里的return,是return所在代码块的最后一个语句
        if (profit <= 0) {
            return 0;
        }
        //  >> return 语句必须是所在代码块的最后一个语句,否则就是语法错误
        return profit;

        // >> 一个方法可以有多个返回语句,但只能有一个生效。
    }

    // >> 返回值如果是基本类型,则要类型完全相同,或者符合类型自动转换规则
    public double getCurrentCount(){
        return count;
    }

    // >> 如果不符合规则,可以使用强制类型转换
    public int getIntSoldPrice(){
        return (int) soldPrice;
    }
}

复制代码
  • 参数和方法里的局部变量可以认为是一样的东西。只是在方法调用之前,会用实参给参数的形参赋值。
  • 方法执行完毕后,参数和方法的局部变量的数据就会被回收。
  • 调用一个有返回值的方法时,就好像访问一个成员变量。
  • 对象创建出来后,被放在了内存的堆上(heap) ,所以方法里创建的对象是不会随着方法结束被清除的,只要有引用指向一个对象,这个对象的数据就可以通过这个引用来访问。

方法是什么

  • 方法是 Java 中代码执行的单元,是代码的载体,所有的代码,都必须属于某一个方法。
  • 方法就是一串语句,加上数据输入 this 自引用和参数,执行后得到一个返回值。所以使用一个对象调用一个方法,可以叫做调用对象的方法,也可以叫做“在这个对象上调用方法( invoke a method on an object )”
  • 方法不是对象的一部分,它是类的一部分。每个对象可以给成员变量赋不同的值,但是无法让方法有不同的行为。同理,无论是一个类中定义多少方法,都不会影响创建一个对象所占用的内存。
  • 类通过成员变量和方法描述世界,成员变量是描述一类事物的属性,是数据;方法是描述一类事物的行为和功能,是对数据的操作。
  • 方法中的代码可以通过操作一个对象的成员变量,完成一个功能。

方法隐藏的 this 自引用

方法里隐藏着一个this自引用,指向调用这个方法的对象。使用一个对象调用方法,也叫做在这个对象上调用方法,因为方法可以访问这个对象的值。访问一个成员变量的完整形态,是"this.成员变量的名字" , 这个 this 是可以省略的,方法访问成员变量的时候默认就是访问的当前调用方法的对象。 下面两个方法 addCount 和 addCountWithoutThis 都是正确的,且效果一致。

public class CommodityV2 {

    ...
    public int count;
  
    public void addCount(int count) {
        this.count += count;
    }
  
    public void addCountWithoutThis(int count) {
      count += count;
    }
}
复制代码

方法的签名和重载

  • 方法签名:方法名 + 参数列表中各参数的类型组成了方法的签名。 返回值不属于方法签名 。方法签名是一个方法在一个类中的唯一标识。
  • 同一类中方法可以重名,但是方法签名不能重复。一个类中如果定义了名字相同,签名不同的方法,就叫做方法的重载。
public class CommodityWithOverload {

    public String name;
    public String id;
    public int count;
    public double soldPrice;
    public double purchasePrice;

    public void init(String name, String id, int count, double soldPrice, double purchasePrice) {
        this.name = name;
        this.id = id;
        this.count = count;
        this.soldPrice = soldPrice;
        this.purchasePrice = purchasePrice;
    }

    public void describe() {
        System.out.println("商品名字叫做" + name + ",id是" + id + "。 商品售价是" + soldPrice
            + "。商品进价是" + purchasePrice + "。商品库存量是" + count +
            "。销售一个的毛利润是" + (soldPrice - purchasePrice));
    }


    // >> TODO 重载的方法可以调用别的重载方法,当然也可以调用别的不重载的方法。
    // >> TODO 实际上,像这种补充一些缺省的参数值,然后调用重载的方法,是重载的一个重要的使用场景。
    // >> TODO 在这里我们举的例子就是这样的,但是不是语法要求一定要这样。重载的方法的方法体内代码
    //    TODO 可以随便写,可以不调用别的重载方法
    public double buy() {
        return buy(1);
    }


    public double buy(int count) { 
        return buy(count, false);
    }

    // TODO  最后都补充好参数,调用参数最全的一个方法
    public double buy(int count, boolean isVIP) {
        if (this.count < count) {
            return -1;
        }
        this.count -= count;
        double totalCost = count * soldPrice;
        if (isVIP) {
            return totalCost * 0.95;
        } else {
            return totalCost;
        }
    }

}
复制代码

重载的方法可以调用别的重载方法,实际上,像这种补充一些缺省的参数值,然后调用重载的方法,一直调用到最后都补充好参数,调用参数最全的那个重载方法,是重载的一个重要的使用场景。

但是,不是语法要求一定要这样。重载的方法的方法体内代码可以随便写,可以不调用别的重载方法。

构造方法

  • 构造方法(constructor)的方法名必须与类名一样,而且构造方法没有返回值。这样的方法才是构造方法。
  • 构造方法可以有参数列表,语法与普通方法一样。
public class CommodityWithConstructor {

    public String name;
    public String id;
    public int count;
    public double soldPrice;
    public double purchasePrice;

    public CommodityWithConstructor(String name, String id, int count, double soldPrice, double purchasePrice) {
        this.name = name;
        this.id = id;
        this.count = count;
        this.soldPrice = soldPrice;
        this.purchasePrice = purchasePrice;
    }
}
复制代码
  • 创建对象时,构造函数的参数由 new 语句后类名后面跟的括号实参传入。
CommodityWithConstructor m = new CommodityWithConstructor("书桌", "DESK9527", 40, 999.9, 500);
复制代码
  • 在一个类里,如果没有显示的添加一个构造方法,Java会给类都默认自带一个无参数的构造方法,这个方法什么也不做。
  • 如果类里已经定义了构造方法,Java就不会再给类添加无参数的构造方法。这时候,就不能直接 new 一个对象时不给类名后面传参数了(除非类内自己定义了无参的构造函数)所以我们一直都在使用构造方法,这也是为什么创建对象的时候类名后面要有一个括号的原因。
  • 构造方法无法被点操作符调用或者在普通方法里调用,只能通过 new 语句在创建对象的时候,间接调用。这也是为什么构造方法没有返回值的原因,因为有返回值也没有意义,new 语句永远返回的是创建出来的对象的引用。

构造方法的重载和相互调用

与普通方法一样,也能给类的构造方法定义重载方法,在构造方法里才能调用重载的构造方法。语法为: this(实参列表)。

  • 构造方法不能自己调用自己,这会是一个死循环。
  • 在调用重载的构造方法时,不可以使用成员变量。因为语意上讲,这个对象还没有被初始化完成,处于中间状态。
  • 在构造方法里调用重载的构造方法时,调用语句必须是方法的第一行,后面可以继续有代码。
public class CommodityV3 {

    public String name;
    public String id;
    // >> TODO 构造方法执行前,会执行给局部变量赋初始值的操作
    // >> TODO 我们说过,所有的代码都必须在方法里,那么这种给成员变赋初始值的代码在哪个方法里?怎么看不到呢?
    //    TODO 原来构造方法在内部变成了<init>方法。学习就是要脑洞大,敢想敢试,刨根问底。
    public int count = 999;// 999/0;
    public double soldPrice;
    public double purchasePrice;

    public CommodityV3(String name, String id, int count, double soldPrice, double purchasePrice) {
        this.name = name;
        this.id = id;
        this.count = count;
        this.soldPrice = soldPrice;
        this.purchasePrice = purchasePrice;
        soldPrice = 9/0;
    }


    // 构造函数体内调用构造函数的重载方法时,调用语句必须是第一行
    public CommodityV3(String name, String id, int count, double soldPrice) {
        this(name, id, count, soldPrice, soldPrice * 0.8);
        // double purPrice = soldPrice * 0.8;
    }


    public CommodityV3() {
        this("无名", "000", 0, 1, 1.1);

    }
}
复制代码
  • 因为我们添加了构造方法之后,Java就不会再添加无参数的构造方法。如果需要的话,我们可以自己添加这样的构造方法,在无参的构造方法里再通过参数的缺省值去调用有参的构造方法。比如下面的
public class CommodityV3 {
    ......
    public CommodityV3() {
       this("无名", "000", 0, 1, 1.1);
    }
}
------
public class useCommodityV3App {
    public static void main(String[] args) {
        CommodityV3 m3 = new CommodityV3()
    }
}
复制代码

就是构造函数重载的一个常用的使用场景。

静态属性和方法

静态变量

下面的类定义里除了普通的成员变量还增加了静态变量。

public class CommodityWithStatic {

    public String name;
    public String id;
    public int count;
    public double soldPrice;
    public double purchasePrice;
    
    public static double DISCOUNT_FOR_VIP = 0.95;
    static int STATIC_VARIABLE_CURR_PACKAGE_ONLY = 100;
    

    public CommodityWithStatic(String name, String id, int count, double soldPrice, double purchasePrice) {
        this.name = name;
        this.id = id;
        this.count = count;
        this.soldPrice = soldPrice;
        this.purchasePrice = purchasePrice;
    }
}
复制代码
  • 静态变量使用 static 修饰符。
  • 静态变量如果不赋值,Java也会给它赋以其类型的初始值。
  • 静态变量一般使用全大写字母加下划线分割。这是一个习惯用法。
  • 所有的代码都可以使用静态变量,只要根据访问控制符的规范,这个静态变量对其可见即可。
  • 比如 public 的静态变量,所有的代码都可以使用它,但是如果是没有public修饰的静态变量,只能当前包的代码能使用它。
public class UseStaticVariableApp {
    public static void main(String[] args) {
        // 使用别的类的静态变量的时候,需要使用完整形态:类名.静态变量名字
        CommodityWithStatic.DISCOUNT_FOR_VIP = 0.5;

        System.out.println("VIP的折扣是 " + CommodityWithStatic.DISCOUNT_FOR_VIP);

    }
}

复制代码
  • 使用别的类的静态变量的时候,需要使用完整形态:类名.静态变量名。
  • 静态变量也叫做类变量,一个静态变量在整个Java程序中只有一份,与之对比实例变量,是每个实例都有一份,所以静态变量一旦变化,所有使用这个静态变量的地方的值都会变。

静态方法

类除了静态变量外,还有静态方法。

public class CommodityWithStatic {

    public String name;
    public String id;
    public int count;
    public double soldPrice;
    public double purchasePrice;
    
    public static double DISCOUNT_FOR_VIP = 0.95;
    static int STATIC_VARIABLE_CURR_PACKAGE_ONLY = 100;

    public static double getVIPDiscount() {
        // 静态方法可以访问静态变量,包括自己类的静态变量和访问控制符允许的别的类的静态变量
        return DISCOUNT_FOR_VIP;
    }
    

    public CommodityWithStatic(String name, String id, int count, double soldPrice, double purchasePrice) {
        this.name = name;
        this.id = id;
        this.count = count;
        this.soldPrice = soldPrice;
        this.purchasePrice = purchasePrice;
    }
}
复制代码
  • 静态方法使用 static 修饰符方法,静态方法的定义和成员方法一样,也有方法名,返回值和参数。
  • 静态方法并没有名称全部大写的约定俗成。
  • 静态方法可以访问静态变量,包括自己类的静态变量和在访问控制符允许的别的类的静态变量。
  • 静态方法里没有this自引用,它不属于某个实例,调用的时候也无需通过实例调用,直接用类名调用,所以它也不能通过 this 自引用的方式访问成员变量。
  • 当然在静态方法里面,也可以自己创建对象,或者通过参数,获得对象的引用,进而调用方法和访问成员变量,静态方法只是没有 this 自引用而已。

静态方法通过类名调用

public class UseStaticMethodeApp {
    public static void main(String[] args) {
		double vipDiscount = CommodityWithStatic.getVIPDiscount()

        System.out.println("VIP的折扣是 " + vipDiscount);

    }
}

复制代码

静态方法的访问和静态变量一样,通过类名访问,不过当前类在访问自己类里的静态方法和变量时可以省略类名

public class CommodityWithStatic {

    public String name;
    public String id;
    public int count;
    public double soldPrice;
    public double purchasePrice;
    
    public static double DISCOUNT_FOR_VIP = 0.95;
    static int STATIC_VARIABLE_CURR_PACKAGE_ONLY = 100;

    public static double getVIPDiscount() {
        // 静态方法可以访问静态变量,包括自己类的静态变量和访问控制符允许的别的类的静态变量
        return DISCOUNT_FOR_VIP;
    }
     
    public double buy(int count, boolean isVIP) {
        if (this.count < count) {
            return -1;
        }
        this.count -= count;
        double totalCost = count * soldPrice;
        if (isVIP) {
            // 静态方法的访问和静态变量一样,可以带上类名,当前类可以省略类名
            return totalCost * getVIPDiscount();
        } else {
            return totalCost;
        }
    }
}
复制代码

静态代码块

除了上面看到的静态变量和方法外,Java里还有一个东西叫静态代码块。见下面的例程

public class DiscountMgr {
    
    public static void main(String[] args) {
        System.out.println("最终main 方法中使用的SVIP_DISCOUNT是" + SVIP_DISCOUNT);
    }

    public static double BASE_DISCOUNT;

    public static double VIP_DISCOUNT;

    public static double SVIP_DISCOUNT;
    
    static {
        BASE_DISCOUNT = 0.99;
        VIP_DISCOUNT = 0.85;
        SVIP_DISCOUNT = 0.75;

        // 静态代码块里可以有任意的合法代码
        System.out.println("静态代码块1里的SVIP_DISCOUNT" + SVIP_DISCOUNT);

    }
    
    static {
        SVIP_DISCOUNT = 0.1;
        System.out.println("静态代码块2里的SVIP_DISCOUNT" + SVIP_DISCOUNT);
    }

}

复制代码
  • 静态代码块里以有任意的合法 Java 代码。
  • 静态代码块可以有多个,是从上向下顺序执行的。
  • 使用某个静态变量的代码块必须在静态变量后面。
  • 可以认为静态代码块都被组织到了一个方法里,即 class init,会在每个class 初始化的时候被调用一次。

访问控制符

  • 类,静态方法,静态变量,成员变量,构造方法,成员方法都可以使用访问控制修饰符。
  • Java的访问修饰符的可见性
    • public,全局可见,在包外也可以使用。
    • protected,在本类、子类内可见(子类在其他包也可见,这点与缺省控制符不同)。
    • 缺省,在当前包可见,对外部包不可见。
    • private,只在当前类内可见。
      类内部本包子类外部包
      private
      defaultdefault
      protectedprotectedprotected
      publicpublicpublicpublic
package com.example.factory;

// 类,静态方法,静态变量,成员变量,构造方法,成员方法都可以使用访问修饰符
public class Commodity {

    private String name;
    private String id;
    private int count;
    private double soldPrice;
    private double purchasePrice;
    private NonPublicClassCanUseAnyName nonPublicClassCanUseAnyName;
    public static double DISCOUNT = 0.1;

    // 构造方法如果是private的,那么就只有当前的类可以调用这个构造方法
    public Commodity(String name, String id, int count, double soldPrice, double purchasePrice) {
        this.name = name;
        this.id = id;
        this.count = count;
        this.soldPrice = soldPrice;
        this.purchasePrice = purchasePrice;
    }

    // 有些时候,会把所有的构造方法都定义成private的,然后使用静态方法调用构造方法
    // 同样的,这样的好处是可以通过代码,检查每个属性值是否合法。
    public static Commodity createCommodity(String name, String id, int count,
                                                  double soldPrice, double purchasePrice) {
        if (soldPrice < 0 || purchasePrice < 0) {
            return null;
        }
        return new Commodity(name, id, count, soldPrice, purchasePrice);
    }

    public Commodity(String name, String id, int count, double soldPrice) {
        this(name, id, count, soldPrice, soldPrice * 0.8);
    }

    // public的方法类似一种约定,既然外面的代码可以使用,就意味着不能乱改。比如签名不能改之类的
    public void describe() {
        System.out.println("商品名字叫做" + name + ",id是" + id + "。 商品售价是" + soldPrice
            + "。商品进价是" + purchasePrice + "。商品库存量是" + count +
            "。销售一个的毛利润是" + (soldPrice - purchasePrice));
        freeStyle();
    }

    // 对于private的方法,因为类外面掉不到,所以无论怎么改,也不会影响(直接影响)类外面的代码
    private void freeStyle() {

    }

    public double calculateProfit() {
        double profit = soldPrice - purchasePrice;
        return profit;
    }

    public double buy(int count) {
        if (this.count < count) {
            return -1;
        }
        return this.count -= count;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public double getSoldPrice() {
        return soldPrice;
    }

    public void setSoldPrice(double soldPrice) {
        this.soldPrice = soldPrice;
    }

    public double getPurchasePrice() {
        return purchasePrice;
    }

    public void setPurchasePrice(double purchasePrice) {
        this.purchasePrice = purchasePrice;
    }
}

复制代码
  • 成员变量应该都声明为private,如果要读写这些成员变量,最好是使用 getXXX setXXX方法,这些方法应该是public的,这样做的好处是,如果有需要,可以通过代码,检查每个属性值是否合法,这就是所谓的 getter 和 setter。
  • 构造方法如果是private的,那么就只有当前的类可以调用这个构造方法,不过,有些时候会把所有的构造方法都定义成private的,然后使用静态方法调用构造方法(比如上面代码里的 createCommodity 静态方法),这样的一个静态方法还会被称为工厂方法。同样的,这样的好处是可以通过代码,检查每个属性值是否合法。
  • 访问控制符的目的不只是为了限制不让人使用,而是更像是一种约定。比如 public 的方法既然外面的代码可以使用,就意味着不能乱改。比如方法签名不能随便改之类的。而对于 private 的方法,因为类外面调用不到,所以无论怎么改,也不会影响(直接影响)类外面的代码

上面的Commodity 类里还用到了同包下的 NonPublicClassCanUseAnyName 类

package com.example.factory;

class NonPublicClassCanUseAnyName {
}
复制代码

起这个类型也是为了表示,非public的类,类名可以使用任意名字,不用必须和文件名相同,不过这个类它也就只能在包内被使用了。

上面成员变量的 getter 和 setter 非常多,一般我们不用自己定义,lombok 包提供了我们一些注解,能自动给类加上getter、setter、构造方法等。这个后面用到了再去学。

继承

上边一直在用的 Commodity 类是对商品的抽象,但是它只包含了商品最基本的属性和方法,比如商品名,价格,库存数、进价和售价这些属性。但是商品是可以分门别类划分成很多品类,每一个品类又都有自己的通用属性和方法。

比如手机也是一个商品,但它又是个更细分的品类,除了商品的名称、库存数等商品的通用属性外,它还有自己这个品类独有的属性和方法,比如CPU,内存,品牌,操作系统,屏幕大小等描述信息。那么如果每次有个新商品品类,都把Commodity里定义的内容拷贝在细分的商品类里,就得不偿失了。这个时候就需要类的继承了。

看一下下面对手机类的定义

package com.example.factory;

public class Phone extends Commodity {

    // 给Phone类增加新的属性和方法
    private double screenSize;
    private double cpuHZ;
    private int memoryG;
    private int storageG;
    private String brand;
    private String os;

    public Phone(
        String name, String id, int count, double soldPrice, double purchasePrice,
        double screenSize, double cpuHZ, int memoryG, int storageG, String brand, String os
    ) {

        this.screenSize = screenSize;
        this.cpuHZ = cpuHZ;
        this.memoryG = memoryG;
        this.storageG = storageG;
        this.brand = brand;
       this.os = os;

        this.setName(name);
        this.setId(id);
        this.setCount(count);
        this.setSoldPrice(soldPrice);
        this.setPurchasePrice(purchasePrice);
    }

    public void describePhone() {
        System.out.println("此手机商品属性如下");
        describe();
        System.out.println("手机厂商为" + brand + ";系统为" + os + ";硬件配置如下:\n" +
            "屏幕:" + screenSize + "寸\n" +
            "cpu主频" + cpuHZ + " GHz\n" +
            "内存" + memoryG + "Gb\n" +
            "存储空间" + storageG + "Gb\n");

    }
    ...... // 省略了属性的Getter 和 Setter

}
复制代码
  • 继承的语法就是在类名后面使用 extends 关键字加要继承的类的名字。
  • 被继承的类叫做父类(Parent Class),比如本例中的Commodity类。
  • 继承者叫做子类(Sub Class),比如本例中的Phone类。
  • Java中的继承是单继承,即一个类只允许有一个直接的父类.
  • 没错,别的类也可以继承子类,比如可以有一个HuaweiPhone继承 Phone 这时候,HuaweiPhone就是Phone的子类了。
  • 子类继承了父类 (这里说的父类也包括祖先类) 的属性和方法。但是子类并不能访问父类的private的成员(包括方法和属性)。
  • 使用子类的实例可以访问父类的公共属性和方法,就好像可以一物二用一样,子类的实例既可以当做子类的引用使用,也可以当成父类的引用使用。

看下面的例程

package com.example;

import com.example.factory.Phone;

public class UsePhoneAppMain {
    public static void main(String[] args) {
        Phone phone = new Phone(
            "手机001","Phone001",100, 1999, 999,
            4.5,3.5,4,128,"索尼","安卓"
        );
        // 调用了父类的describe方法
        phone.describe();
        // 调用了子类的describePhone方法
        phone.describePhone();
    }
}

复制代码

子类覆盖父类的方法

package com.example.facotry;

public class Phone extends Commodity {

    // 给Phone增加新的属性和方法
    private double screenSize;
    private double cpuHZ;
    private int memoryG;
    private int storageG;
    private String brand;
    private String os;
    private static int MAX_BUY_ONE_ORDER = 5;

    public Phone(
        String name, String id, int count, double soldPrice, double purchasePrice,
        double screenSize, double cpuHZ, int memoryG, int storageG, String brand, String os
    ) {

        ...... // 跟上面的Commodity内容一样
    }


    public double buy(int count) {

        // TODO 这个方法里代码大部分和父类一样,肯定有方法解决
        if (count > MAX_BUY_ONE_ORDER) {
            System.out.println("购买失败,手机一次最多只能买" + MAX_BUY_ONE_ORDER + "个");
            return -2;
        }
        if (this.count < count) {
            System.out.println("购买失败,库存不够");
            return -1;
        }
        this.count -= count;
        double cost = count * soldPrice;
        System.out.println("购买成功,花费为" + cost);
        return cost;
    }

    // 返回值必须一样,不是类型兼容,而是必须一摸一样。
    // 如果签名一样,但是返回值不一样,会是错误
//    public int buy(int count) {
//        if (count > MAX_BUY_ONE_ORDER) {
//            return -2;
//        }
//        if (this.count < count) {
//            return -1;
//        }
//        return this.count -= count;
//    }

    // 返回值必须一样,不是类型兼容,而是必须一摸一样
//    public boolean buy(int count) {
//        return true;
//    }

    public String getName() {
        return this.brand + ":" + this.os + ":" + name;
    }

    public void describePhone() {
        System.out.println("此手机商品属性如下");
        describe();
        System.out.println("手机厂商为" + brand + ";系统为" + os + ";硬件配置如下:\n" +
            "屏幕:" + screenSize + "寸\n" +
            "cpu主频" + cpuHZ + " GHz\n" +
            "内存" + memoryG + "Gb\n" +
            "存储空间" + storageG + "Gb");
    }

    ...... // 省略了属性的Getter 和 Setter
}

复制代码

例程里的 buy 方法,在父类Commodity里也有,**通过使用和父类方法签名一样,而且返回值也必须一样的方法,可以让子类覆盖(override)掉父类的方法。**也就是说,子类并不是只能把父类的方法拿过来,而且可以通过覆盖来替换其中不适合子类的方法。

覆盖父类的方法时,方法的签名和返回值都必须一样(返回值类型必须一模一样,不能是兼容模型),如果签名一样但是返回值不一样,程序会报错,因为 Java 是认为定义了两个签名重复的方法(同一个类里方法签名不能重复)。

覆盖可以覆盖掉父类的方法。同一个方法,不同的行为。这就是多态!方法可以覆盖,而属性访问不可以,所以这也是属性访问推荐使用 Getter 和 Setter 方法的一个原因。即使在父类里,只是一个简单的 getName 方法读取name的值,但是这样做,子类就可以覆盖掉父类的方法,方法不止眼前的代码,还有子类的覆盖。所以,用方法,才能覆盖,才能实现面向对象的多态。

注意:子类覆盖父类的方法,不可以用可见性更低的修饰符,但是可以用更高的修饰符

使用super调用父类的方法

使用super可以调用父类的 public 方法和属性,当然因为子类能继承父类的 public 属性,所以一般 super 只用来在子类方法里调用父类的方法,最常见的还是在子类的方法里再去用 super 调用父类中被子类覆盖的方法,比如:

package com.example.facotry;

public class Phone extends Commodity {

	private static int MAX_BUY_ONE_ORDER = 5;
	......
    
    public double buy(int count) {
        if (count > MAX_BUY_ONE_ORDER) {
            System.out.println("购买失败,手机一次最多只能买" + MAX_BUY_ONE_ORDER + "个");
            return -2;
        }
        // 调用父类Commodity的buy方法
        return super.buy(count);
    }
    
    ......
}

复制代码

上面例程里,子类Phone覆盖了父类 Commodity 的 buy 方法,子类的 buy 方法在检查购买数量是否超限,成立后再去调用父类的 buy 方法完成购买功能。这也是方法覆盖比较经典的用法。

除此之外,super 还常用于在子类的构造方法里调用父类的构造方法,我们可以把Phone类的构造方法优化为,调用父类的构造方法初始化通用属性,然后自己再初始化Phone类独有的属性,这样就避免了在子类里再重复写初始化商品通用属性的代码。

package com.example.factory;

public class Phone extends Commodity {

    // 给Phone增加新的属性和方法
    private double screenSize;
    private double cpuHZ;
    private int memoryG;
    private int storageG;
    private String brand;
    private String os;
    private static int MAX_BUY_ONE_ORDER = 5;

    public Phone(
            String name, String id, int count, double soldPrice, double purchasePrice,
            double screenSize, double cpuHZ, int memoryG, int storageG, String brand, String os
    ) {
        super(name, id, count, soldPrice * 1.2, purchasePrice);
        init(screenSize, cpuHZ, memoryG, storageG, brand, os);
    }


    public void init(double screenSize, double cpuHZ, int memoryG, int storageG, String brand, String os) {
        this.screenSize = screenSize;
        this.cpuHZ = cpuHZ;
        this.memoryG = memoryG;
        this.storageG = storageG;
        this.brand = brand;
        this.os = os;
    }
    
    ......
}
复制代码
  • 注意:使用super调用父类的构造方法,必须是子类构造方法的第一个语句。

父类和子类引用赋值的关系

可以用子类的引用给父类的引用赋值,也就是说,父类的引用可以指向子类的对象,但是反之则不行,不能让子类的引用指向父类的对象。因为父类并没有子类的属性和方法。

因为子类继承了父类的方法和属性,所以父类的对象能做到的,子类的对象肯定能做到。换句话说,我们可以在子类的对象上,执行父类的方法。

instanceof 操作符

instanceof 操作符,可以判断一个引用指向的对象是否是某一个类或者其子类的实例,是则返回true,否则返回false。

package com.example;

import com.example.factory.Phone;
import com.example.factory.Commodity;

public class InstanceOfTest {
    public static void main(String[] args) {
        Phone phone = new Phone(
            "手机001","Phone001",100, 1999, 999,
            4.5,3.5,4,128,"索尼","安卓"
        );
        if (phone instanceof Phone) {
            System.out.println("it's an instance of class Phone");
        }
        if (phone instanceof Commodity) {
            System.out.println("it's an instance of class Commodity");
        } else {
            System.out.println("not an instance");
        }
    }
}

复制代码

final 修饰符

  • final 修饰类,那么该类就不能被继承,比如 Java 的字符串 String 类不允许被扩展,就是通过final实现的
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
}
复制代码
  • final 修饰成员变量,那么成员变量在构造方法或者声明时初始化后不允许被修改(必须赋值,只能赋值一次,且不允许在其他地方赋值)
  • final 修饰静态变量与成员变量的情况类似,静态变量必须被赋值(声明或者在static块里赋值)且不能被更改。
  • final 修饰方法,该方法不允许被子类覆盖。
  • final 不能修饰构造方法。
  • final 修饰形参/局部变量,在函数内不能再给形参/局部变量 赋值。

总结

这就是面向对象理念用 Java 这种语言的呈现啦,后面还有一些进阶内容放到下篇再讲,不然内容实在是太多了,大家敬请期待😚

SpringBoot+Vue+MySQL 高校学科竞赛平台 完整可运行版(源码+文档) 本项目基于SpringBoot+Vue+MySQL构建高校学科竞赛管理平台,覆盖学生、教师、领队教师、管理员四大角色,实现竞赛发布、报名、刷题、评分、公示等全流程闭环管理。系统架构清晰,技术主流,配套完整源码、数据库脚本、万字论文、开题报告、答辩PPT及部署文档,支持一键运行,适用于毕业设计、课程设计及院系二次开发,真正实现“开箱即用”。 阅读详情

相关推荐

OFDM调制matlab仿真详细代码

MATLAB仿真代码 % ================================================== % 作者: 肆拾伍 % 时间:11/30 2019 % 版本:V3 % =================OFDM仿真参数说明:================ % % 子载波数 carrier_count ---200 % 总符号数 symbol_count ...

肆拾伍的博客 3万+

如果老师这么讲面向对象,当年就不会挂科了吧

我上大学的时候不爱学习,爱打游戏,每次都临考试前一个月疯狂抱佛脚,抱得佛估计都怕了,印象最深的是我大学的 Java 课考了 60 分,别看我及格了但也是极其危险,笔试前又去抱佛的脚,结果答得一般般,上机考试就更别提了,瞎写了几行,编译器都快看不下去了... 估计能考及格也是老师高抬贵手,当时找过系里老师两三次,就差提果篮去老师家上门了。有时候经常会想,假如时光能倒流,让自...

kevin_tech的博客,微信搜「网管叨bi叨」 181

【新华三】通过路由复制实现Public与VRF路由互通

如上图所示,S6900设备里有两张表,一张是public路由表,默认所有的路由都存放在public路由表里;除此之外还创建了VRF test,因此此设备还有VRF test路由表。通过路由复制实现Public路由和VRF路由互通,默认情况下VRF里的路由单独一张路由表,默认情况下与public路由表相互隔离,两张表之间不可互访。此时的public路由表里面并没有test的路由表。public路由表与VRF路由表之间相互隔离。此时VRF test里的路由也没有其他VRF或Public里的路由,相互隔离。

五大连池的镜子 5283

Python游戏开发-01推箱子(面向对象)

本文是建立在的基础之上,且以读者已经学习完为前提的。也就是说,按照之前的约定,我们以面向对象的思维再去实现一次推箱子游戏,并简单说明一下大概的设计及编码思路,供读者参考、比较。但是,这里不会再从头到尾详细的讲述项目的开发过程,而是站在面向过程的肩膀上,重点讲解面向对象思维在该项目中的应用以及它与面向过程实现的区别,意在让读者对Python的面向对象编程有更深刻的理解。我们在。

xjt921122的博客 1408

树莓派4B系统安装及SSH远程控制(简单易操作)

​自从2022年4月树莓派官网更新了RaspberryPiImager版本v1.7.2后,便不再支持自行建立ssh文件,并用默认用户名pi和密码raspberry的方式进行远程控制(亲测不行,输密码时一直access denied)。但是该版本软件提供了烧录系统前设置ssh用户及密码功能。本文记录了系统安装和SSH远程操作步骤。........................

机械人的代码路 5791

VFIO框架源码分析(二十二)- IOMMUFD 的脏页跟踪与 Dirty Bits 读取

在虚拟化热迁移(Live Migration)的场景中,脏页追踪(Dirty Page Tracking)是最具挑战性的环节之一。它的核心任务是实时捕获设备(DMA)对内存的写操作,以便 VMM(如 QEMU)知道哪些内存页需要被重新传输到目标主机。在强耦合与粗粒度:脏页追踪是绑定在 Container 上的。这意味着,如果一个 Container 内有多个设备,哪怕只有一个设备在写内存,脏页追踪也会影响 Container 内的所有设备。软件模拟开销大。

qq_36071928的博客 1046

3月份读文+学习思考记录

学习之路,任重而道远,唯有努力前行,才能无愧我心。大佬强调的一定要多读paper,看来这是大佬成长的秘籍。

shaozheng0503的博客 974

[计算机头歌]爬虫进阶

【代码】爬虫进阶。

M_inherit的博客 922

Python期末高效复习指南:核心语法、数据结构与面向对象编程精讲

Python作为一门动态强类型语言,其核心在于理解变量、数据类型与运算符的工作原理,这构成了程序逻辑的基石。从基础语法到复杂的数据结构,如列表、字典、集合和元组,掌握其特性和操作是编写高效代码的关键。函数作为模块化编程的基本单位,其参数传递机制和作用域规则是构建可复用、可维护程序的核心技术。面向对象编程(OOP)则通过封装、继承和多态三大特性,将数据和操作封装成对象,极大地提升了代码的组织性和扩展性,是构建复杂系统的有力工具。这些技术广泛应用于数据分析、Web开发、自动化脚本等场景。本文聚焦于Python期

weixin_34413802的博客 482

第一节:从Chat_Bot 到 AI_Agent——一个范式转换

本文系统剖析了工业级AI代理系统ClaudeCode的设计原理与工程实践。通过对比ChatBot与Agent的三个本质差异:自主循环决策vs单轮应答、环境交互vs纯文本I/O、持久状态管理vs无状态对话,揭示了构建生产级Agent系统的关键挑战。文章详细解析了ClaudeCode的五层架构设计、1800+文件组成的运行时系统,包括工具调度、权限管理、状态持久化等核心机制,并阐述了从简单API调用到完整Agent系统的演进路径。

2201_75869398的博客 69

在 FreeStyle SAP UI5 应用里优雅落地 Draft 草稿机制:用 DraftManager 封装 DraftController 的一套实战套路

SAP Fiori应用中,Draft草稿机制是解决业务中断、多人协作和长表单填写等痛点的核心能力。本文提出了一套针对FreeStyle SAP UI5应用的Draft管理方案,通过封装sap.ui.generic.app.transaction.DraftController为DraftManager服务类,将复杂的草稿操作逻辑集中管理。该方案实现了草稿的创建、激活、丢弃等核心功能,使控制器只需调用简单意图式API即可完成操作,同时解决了组件化架构下的上下文切换、统一错误处理等问题。文章还详细介绍了后端Dr

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 2752

自学Java半年的心路历程

为何想要转行程序员? 我不知道有多少人在当年报志愿的时候真正的清楚自己专业是干什么的? 或者在填志愿之前就清楚自己是要学啥或者特别喜欢某个专业。 我感觉我就是一个现实主义者,在我高考完我都不知道我到底要学啥,但是我知道我打死都不会干的三大职业:老师、医生、程序员。对于老师,我清楚知道自己是一个偏内向的一个人 同时没有特别的喜欢表达,对于教书育人我怕是顶不住,还有就是钱少,(百度搜索全球教师薪资待遇排行)。对于医生,那是要读书读到穿,我是不敢冒这个险。对于程序员(后面打脸了),当时想的是干这行的个个都是“聪明

qq_46416572的博客 1289

Python 爬虫实战:豆瓣 / IMDB 电影数据抓取(评分 + 剧情 + 演员阵容结构化处理)

在数据为王的时代,电影数据是进行市场分析、推荐系统构建和学术研究的重要资源。豆瓣和 IMDB 作为国内外最具影响力的电影评分和资讯网站,包含了海量高质量的电影数据。本文将带领你从零开始,构建一个健壮的 Python 爬虫,用于抓取豆瓣和 IMDB 的电影信息,并将其中的非结构化数据(如剧情简介、演员列表)进行清洗和结构化处理,最终存储到便于分析的结构化文件(如 CSV)中。

yansideyucsdn的博客 956

你是否会有这样的体验:上班后,才发现大学白学了

还记得大学时那些让人头秃的课吗?《编译原理》里推导语法树,《计算机网络》背三次握手,《JSP》调包调到怀疑人生…当时以为这些就是程序员必备神技,上班后才发现,公司里压根不用这些!这种感觉就像苦练十年屠龙术,结果上班后发现世界上根本没有龙。当然很多人基本在大一大二就可能已经悟道了,于是开始自己去主动规划路线,学习相关技术栈,参加相关实习,然后准备秋招和春招,但大多数基本都是在大二下和大三这个阶段,因为实习和别人一些消息才知道就业到底是个什么情况。

qq_67358181的博客 1000

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

软考高项经验分享:我的备考之路与实战心得

软考,尤其是信息系统项目管理师(高项)考试,对于众多追求职业提升与专业认可的人士来说,是一场充满挑战与机遇的征程。我在当年参加软考高项的经历,可谓是一波三折,其中既有成功的喜悦,也有遇到困难时的困惑与挣扎。在此,我想把自己的经验分享给各位考友,希望能为大家的备考之路点亮一盏明灯。

AllenLV的博客 2262

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

InnoDB 内存结构总览:Change Buffer、AHI 与 Log Buffer

本文系统梳理InnoDB内存结构中的ChangeBuffer、Adaptive Hash Index(AHI)与LogBuffer。ChangeBuffer缓存二级索引页不在内存时的变更,减少随机IO,仅在页加载时合并;聚簇索引修改立即执行,因数据本身不可延后。AHI基于高频等值查询自动构建哈希索引,加速定位,仅支持等值操作。LogBuffer为redo log内存缓冲,配合磁盘日志实现崩溃恢复。三者均服务于性能优化,其中ChangeBuffer与AHI共享BufferPool空间,而LogBuffer独立

bksczm的博客 307

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

信创环境下PageOffice文档控件适配与实践

信创适配做到文档控件这一层,拼的已经不是"有没有国产版本",而是同一个功能在两类终端上是否真的等价。等价的标准很具体:同一次点击能得到同样的结果,同一份公文能得到同样的版式,同一个项目不需要维护两套代码。这背后是三件并不轻松的工作:服务端用一套接口同时支撑两类终端,客户端把两种系统与四种芯片的安装包分发清楚,文档内核层由产品侧跟着 WPS 的版本走。浏览器这一层的压力,则被收进了产品自己的窗口里——升级节奏由产品掌握,不再交给运气。

zi_wu_xian的专栏 334

第三篇 第 6 课

前置:第三篇第 1 课(stdx 引入),第二篇第 1 课(std.regex)。环境:仓颉 SDK 1.2.0。本文所有代码与输出均逐行实测。

weixin_41618057的博客 557

【从零理解 Spring AI · 12】MCP 的两种角色:做客户端还是做服务端

上一篇讲了 MCP 的概念:标准协议、三大角色、M+N 替代 M×N。

nice_lcj520的博客 605

Java 集合容器高频面试题全解:HashMap 树化阈值、1.7 死循环与 ConcurrentHashMap 演进(2026 招聘 JD 真题复盘)

HashMap 树化阈值 8 的泊松分布依据、退化阈值 6 的防抖设计、JDK 1.7 头插成环死循环的排查与修复、1.8 尾插后为何仍线程不安全、ConcurrentHashMap 放弃分段锁的原因与 ForwardingNode 协作扩容、computeIfAbsent 死锁陷阱、大集合找重复的场景题选型,附三层追问链自测。题目来源为 38 期公开招聘 JD 样本与字节、拼多多、建行、美团、快手公开面经。

蒙眼过河 425

spdlog 同步日志库使用详解

spdlog是一个轻量级 C++ 日志库,支持同步日志、异步日志、多线程日志、日志文件轮转和自定义日志格式。对于嵌入式 Linux 应用开发,我建议先使用。它可以帮我们完成:日志级别过滤。时间戳和线程 ID 输出。日志格式化。终端和文件输出。多线程安全写入。日志文件按大小或时间轮转。我们不需要自己实现互斥锁、文件写入、日志格式化和轮转等底层功能。

zengjiajun2006的博客 1121

Java 多态 学习笔记

多态是面向对象编程的重要特性,指同一操作作用于不同对象时产生不同行为。其核心在于“父类引用指向子类对象”,结合方法重写实现运行时动态绑定。多态使代码更灵活、可扩展,如用Person p = new Student();统一处理各类人员注册,无需修改调用逻辑。但多态无法直接调用子类特有方法,需通过instanceof判断并强制转换。编译时看左边(类型),运行时看右边(实际对象),实现解耦与维护便利。

AA_AA114514的博客 370

JavaSE通关手册:数据类型与变量

代码无需额外处理,编译时编译器自动完成转换。

xys_678的博客 234

IDEA 简介:从零认识 IntelliJ IDEA

IDEA 全称,是由 JetBrains 公司开发的一款主流 Java 集成开发环境(IDE)。它集代码编辑、编译、调试、测试、版本控制、构建管理等功能于一体,被大量 Java 开发者视为日常开发的首选工具。所谓 IDE(Integrated Development Environment,集成开发环境),就是把写代码所需的各类工具整合到同一个界面中。相比只用文本编辑器加命令行编译,IDE 能显著减少重复操作,让开发者把精力集中在业务逻辑上。

ztI10的博客 180
上一篇: 基于 nacos/灰度发布 实现减少本地启动微服务数量的实践
下一篇: Netty 的整体架构是怎样的?
程序员职业指南
博客等级 码龄4年 2139粉丝 960原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值