初识Java 6-1 复用

news2024/12/24 9:01:03

目录

组合

继承

委托

组合和继承的结合

确保正确的清理

名称隐藏

在组合和继承之间选择

protected关键字

向上转型

final关键字

final数据

final方法

final类

初始化及类的重载


 

本笔记参考自: 《On Java 中文版》


        对面向对象的编程语言而言,复用意味着可以在新类中使用其他人已经构建和调试过的类,而不必从头开始编写它们。要在不污染原有代码的基础上使用新类,可以通过两种方式:

  1. 组合:在新类中创建现有类的对象;
  2. 继承:直接复制原有类的形式,然后向其中添加代码,不修改原有类。

组合

        组合,意味着将对象引用放入到新类中。

class WaterSource {
    private String s;

    WaterSource() {
        System.out.println("一个WaterSource类的构造器");
        s = "构造器";
    }

    @Override
    public String toString() {
        return s;
    }
}

public class SprinklerSystem {
    private String value1, value2, value3, value4;
    private WaterSource source = new WaterSource();
    private int i;
    private float f;

    @Override
    public String toString() {
        return "value1 = " + value1 + ", " +
                "value2 = " + value2 + ", " +
                "value3 = " + value3 + ", " +
                "value4 = " + value4 + "\n" +
                "i = " + i + ", " +
                "f = " + f + "\n" +
                "source = " + source; // 为了将对象source转换为字符串,会调用toString()方法
    }

    public static void main(String[] args) {
        SprinklerSystem sprinklers = new SprinklerSystem();
        System.out.println(sprinklers);
    }
}

        程序执行的结果如下:

        在上述定义的方法中,存在一个特殊的方法:toString()。每个非基本类型的对象都有一个toString()方法,这个方法会在一些特殊情况下(比如将对象转换为字符串的时候)被调用。

    @Override被用在了toString()方法的修饰中,这种用法有助于检测问题,比如拼写错误。

        一致,编译器会初始化类中的基本类型字段,将其全部置为0。但是对于引用而言,编译器并不会简单地为其创建默认对象,这会产生不必要的开销。因此,引用的初始化一般有4种方式:

  1. 在定义对象时进行初始化,这会使得它们在调用构造器前被初始化;
  2. 在类的构造器中进行初始化;
  3. 延迟初始化,即在对象被实际使用之前进行初始化,减少开销;
  4. 实例初始化

        4中初始化方式的使用例如下:

class Soap {
    private String s;

    Soap() {
        System.out.println("Soap()的构造器");
        s = "构造器";
    }

    @Override
    public String toString() {
        return s;
    }
}

public class Bath {
    private String s1 = "Hello", s2 = "World"; // 在定义时进行初始化
    private String s3, s4;
    private Soap castile;
    private int i;
    private float toy;

    public Bath() {
        System.out.println("在构造器Bath()中:");
        s3 = "Begin"; // 在构造器中进行初始化
        toy = 3.14f;
        castile = new Soap();
    }

    { // 实例初始化
        i = 47;
    }

    @Override
    public String toString() {
        if (s4 == null) // 延迟初始化
            s4 = "Java";
        return "s1 = " + s1 + ", " +
                "s2 = " + s2 + ", " +
                "s3 = " + s3 + ", " +
                "s4 = " + s4 + "\n" +
                "i = " + i + ", " +
                "toy = " + toy + "\n" +
                "castile = " + castile;
    }

    public static void main(String[] args) {
        Bath b = new Bath();
        System.out.println(b);
    }
}

        程序运行的结果是:

        若在对象初始化之前,向对象引用发送消息,就会得到一个运行时异常。

继承

        事实上,当创建一个类时,继承总是在发生:若没有明确指定要继承的类,那么就会隐式继承Java的标准根类Object

        继承使用的是一种特殊的语法,要求在类主体的左花括号之前,使用extends关键字(后跟基类的名称)进行声明。这会使新类自动获得基类的所有字段和方法:

class Cleanser {
    private String s = "Cleanser";

    public void append(String a) {
        s += a;
    }

    public void scrub() {
        append("+scrub()");
    }

    @Override
    public String toString() {
        return s;
    }

    public static void main(String[] args) {
        Cleanser x = new Cleanser();
        x.scrub();
        System.out.println(x);
    }
}

public class Detergent extends Cleanser {
    @Override
    public void scrub() { // 修改方法
        append("+Detergent.scrub()");
        super.scrub(); // 调用当前方法的基类版本
    }

    public void foam() { // 添加新的方法
        append("+foam()");
    }

    public static void main(String[] args) {
        Detergent x = new Detergent();
        x.scrub();
        x.foam();
        System.out.println(x);

        System.out.println("\n调用基类:");
        Cleanser.main(args);
    }
}

        程序运行的结果如下:

        在上述的程序中,CleanserDetergent都有一个main()方法。每个类都可以有一个main(),方便后继的测试。但是,唯一能够运行的main()是在命令行中被调用的那一个。

        Cleanser中的方法都是public的,因此在同一个包中的所有类都可以访问这些方法。这种做法就是考虑了继承:作为一般规则,将所有字段设为private,将所有方法设为public(或protected)。

        最后,可以在子类中对基类中定义的方法进行修改,例如上述例子中的scrub()

    使用super关键字来指代当前类继承的基类(又称“超类”)。因此super.scrub()调用的是基类的scrub()方法。

初始化基类

        基类和子类的关系并不简单。当创建了一个子类的时候,其中就会包含一个基类的子对象(和直接通过积累创建的对象是一样的)。但从外面看,基类的子对象被包裹在了子类的对象中。为了正确初始化基类的自对象,只能在子类构造器中调用基类构造器来执行初始化。Java会自动往子类构造器中插入基类构造器。

class Art {
    Art() {
        System.out.println("类Art的构造器");
    }
}

class Drawing extends Art {
    Drawing() {
        System.out.println("Drawing的构造器");
    }
}

public class Cartoon extends Drawing {
    public Cartoon() {
        System.out.println("Cartoon的构造器");
    }

    public static void main(String[] args) {
        Cartoon x = new Cartoon();
    }
}

        程序运行的结果如下:

        从上述程序可以总结出构造过程的顺序:即从基类开始,“向外”进行。因此基类会在子类构造器访问它之前被初始化。另外,即使子类没有构造器,编译器也会自动合成一个可以调用基类构造器的无参构造器。

---

带参数的构造器

        若基类没有无参构造器,或者必须调用具有参数的构造器,则需要使用super关键字和其对应的参数列表,显式地调用基类构造器(否则会报错):

class Game {
    Game(int i) {
        System.out.println("含参的Game构造器,传入参数为:" + i);
    }
}

class BoardGame extends Game {
    BoardGame(int i) {
        super(1);
        System.out.println("含参的BoardGame构造器,传入参数为:" + i);
    }
}

public class Chess extends BoardGame {
    Chess() {
        super(11);
        System.out.println("Chess的构造器");
    }

    public static void main(String[] args) {
        Chess x = new Chess();
    }
}

        程序运行的结果如下:

        注意对基类构造器的调用必须是子类构造器的第一个操作。

委托

        尽管Java没有提供直接支持,但是Java中确实存在除组合和继承以外的第三种关系:委托。这种关系介于组合和继承之间,有两者的一些特点:

  • 类似组合:将成员对象放在正在构建的类中;
  • 类似继承:在新类中公开了成员对象的所有方法。

        例如,现在有一个飞船控制模块:

public class SapceShipControls {
    void up(int velocity) {
    }

    void down(int velocity) {
    }

    void left(int velocity) {
    }

    void right(int velocity) {
    }

    void forward(int velocity) {
    }

    void back(int velocity) {
    }

    void turboBoost() {
    }
}

        为了在构造飞船时使用上述模块,一般会使用继承:

public class DerivedSpaceShip extends SapceShipControls {
    private String name;

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

    @Override
    public String toString() {
        return name;
    }

    public static void main(String[] args) {
        DerivedSpaceShip protector = new DerivedSpaceShip("保护器");
        protector.forward(100);
    }
}

        但是,这里存在着一个问题。当这种类的继承发生的时候,也就意味着基类的所有方法都会因为这个子类而被暴露给了外部。因此,就需要使用委托进行解决:

public class SpaceShipDelegation {
    private String name;
    private SapceShipControls controls = new SapceShipControls();

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

    // 下面开始使用委托
    public void up(int velocity) {
        controls.up(velocity);
    }

    public void down(int velocity) {
        controls.down(velocity);
    }

    public void left(int velocity) {
        controls.left(velocity);
    }

    public void right(int velocity) {
        controls.right(velocity);
    }

    public void forward(int velocity) {
        controls.forward(velocity);
    }

    public void back(int velocity) {
        controls.back(velocity);
    }

    public void turboBoost() {
        controls.turboBoost();
    }

    public static void main(String[] args) {
        SpaceShipDelegation protector = new SpaceShipDelegation("保护器");
        protector.forward(100);
    }
}

        通过委托,方法调用会被转发到隐藏的controls对象,这样接口可以得到的就和使用继承得到的是相同的。当然,更好的委托控制的方式是仅提供部分的方法。

    尽管Java本身不支持委托,但是开发工具通常支持。

组合和继承的结合

        在实际的编程中,会将继承和组合同时进行使用。例如:

class Plate {
    Plate(int i) {
        System.out.println("Plate的构造器");
    }
}

class DinnerPlate extends Plate {
    DinnerPlate(int i) {
        super(i); // 调用基类构造器
        System.out.println("DinnerPlate的构造器");
    }
}

class Custom {
    Custom(int i) {
        System.out.println("Custom的构造器");
    }
}

public class PlaceSetting extends Custom {
    private DinnerPlate pl;

    public PlaceSetting(int i) {
        super(i + 1);
        pl = new DinnerPlate(i + 2);
        System.out.println("PlaceSetting的构造器");
    }

    public static void main(String[] args) {
        PlaceSetting x = new PlaceSetting(3);
    }
}

        程序执行的结果如下:

    虽然编译器会强制要求对基类的初始化,但对于类的成员对象,编译器不会进行监督。

确保正确的清理

        Java没有C++的析构函数,或许是因为通过垃圾收集器就可以在需要时回收内存。但在类的生命周期中,也可能存在一些需要清理的行为。因为不知道垃圾收集器合适被调用,所以为了清理某些东西,就必须在finally子句中设置清理活动。

class Shape {
    Shape(int i) {
        System.out.println("开始Shape的构造");
    }

    void dispose() {
        System.out.println("进行Shape的清理工作");
    }
}

class Circle extends Shape {
    Circle(int i) {
        super(i);
        System.out.println("画一个圆");
    }

    @Override
    void dispose() {
        System.out.println("擦除圆");
        super.dispose();
    }
}

class Line extends Shape {
    private int start, end;

    Line(int start, int end) {
        super(start);
        this.start = start;
        this.end = end;

        System.out.println("画一条线:" + start + ", " + end);
    }

    @Override
    void dispose() {
        System.out.println("擦除线:" + start + ", " + end);
        super.dispose();
    }
}

public class CADSystem extends Shape {
    private Circle c;
    private Line[] lines = new Line[3];

    public CADSystem(int i) {
        super(i + 1);
        for (int j = 0; j < lines.length; j++)
            lines[j] = new Line(j, j * j);
        c = new Circle(1);

        System.out.println("合并构造器");
    }

    @Override
    public void dispose() {
        System.out.println();
        System.out.println("开始总的清理工作");

        // 清理的顺序和初始化的顺序相反
        c.dispose();
        for (int i = lines.length - 1; i >= 0; i--)
            lines[i].dispose();
        super.dispose();
    }

    public static void main(String[] args) {
        CADSystem x = new CADSystem(47);
        try {
            // 代码及异常处理...
        } finally {
            x.dispose();
        }
    }
}

        上述程序的运行结果是:

        在上述程序中,每一个类都有自己的dispose()方法,用来将非内存相关的事物回复到原本的状态。

        上述程序中出现了这样的结构:

try {
    // ...
} finally {
    // ...
}

其中,try关键字后面的代码块是应该保护区域,用来进行特殊处理。这种特殊处理之一,就是无论try以何种形式推出,保护区域后面的finally子句都会执行(这种设定允许以各种非常规的方式退出try代码块)。

        除此之外,在调用清理方法时,应该注意调用顺序,防止子对象依赖另一个的情况出现。类的特定清理工作,顺序应该和创建顺序相反。

    除了内存回收,其他情况并不建议依赖垃圾收集。


名称隐藏

        若Java基类的方法名称被多次重载,那么子类中重新定义的该方法名称将不会隐藏任何基类版本

class Homer {
    char doh(char c) {
        System.out.println("方法的形式是doh(char)");
        return 'd';
    }

    float doh(float f) {
        System.out.println("方法的形式是doh(float)");
        return 1.0f;
    }
}

class Milhouse {
}

class Bart extends Homer {
    void doh(Milhouse m) {
        System.out.println("方法的形式是doh(Milhouse)");
    }
}

public class Hide {
    public static void main(String[] args) {
        Bart b = new Bart();
        b.doh(1);
        b.doh('x');
        b.doh(1.0f);
        b.doh(new Milhouse());
    }
}

        程序执行的结果如下:

        上述程序中,Homer类所有重载的doh()方法都可以在Bart中被使用,并且Bart中也引入了一个新的重写方法。为了区分重写和重载,在重写同名方法时,应该使用与基类完全相同的签名和返回类型

        @Override可以帮助检测,分析一个方法是重载或是重写。例如,如果不小心进行了重载:

public class Lisa extends Homer {
    @Override
    void doh(Milhouse m) {
        System.out.println("方法的形式是doh(Milhouse)");
    }
}

        尝试编译,发生报错:

在组合和继承之间选择

        一般,可以参考下列意见进行选择:

  1. 使用组合时:往往希望在新类中使用现有类的功能,而不是其接口。对于新类中通过组合得到的成员,有时可以允许类的使用者直接访问它们(可将这种成员设为public)。因为成员对象的实现是隐藏的,所以这种做法也是安全的。
  2. 使用继承时:需要通过现有类生成一个其的特殊版本。也就是说,需要对通用类进行“定制”,使其满足特定需求。

        除此之外,也可以这样说:继承表示的是“is-a”的关系,而组合表示的是“has-a”的关系。以汽车为例,汽车(is)一种交通工具,因此使用“交通工具”来组合(has)一部“汽车”是无意义的。

    继承的使用应该是谨慎的,因此只有在继承能够明显发挥作用时再使用它。确定使用继承或是组合,除了上述标准外,还可以通过判断是否需要从新类向上转型到基类来进行。

protected关键字

        protected关键字,对于类的用户而言,这是private的,但对于继承该类的任何类或同一个包中的其他类而言,这是可用的(protected会提供包访问权限)。

        虽然字段也可以是protected的,但是最好将字段设置为private(保证修改的权利),而将方法设置为protected,以此来控制继承者的访问权限。例如:

class Output {
    private String name;

    protected void set(String nm) {
        name = nm;
    }

    Output(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "这是Output函数,即将输出 " + name;
    }
}

public class Orc extends Output {
    private int orcNumber;

    public Orc(String name, int orcNumber) {
        super(name);
        this.orcNumber = orcNumber;
    }

    public void change(String name, int orcNumber) {
        set(name); // 可以使用protected的方法
        this.orcNumber = orcNumber;
    }

    @Override
    public String toString() {
        return "Orc " + orcNumber + ": " + super.toString();
    }

    public static void main(String[] args) {
        Orc orc = new Orc("Chicken", 12);
        System.out.println(orc);
        orc.change("Duck", 22);
        System.out.println(orc);
    }
}

        程序运行的结果是:

向上转型

        继承除了用来为新类提供方法,更重要的是表达了新类和基类之间的关系,这种关系具体可以概括为:新类是现有类的一种类型。例如:

class Instrument {
    public void play() {
        System.out.println("调用play方法");
    }

    static void tune(Instrument i) {
        // ...
        i.play();
    }
}

public class Wind extends Instrument {
    public static void main(String[] args) {
        Wind flute = new Wind(); 
        
        // Wind方法也是Instrument,它们有相同的接口
        // 下方语句发生了向上转型
        Instrument.tune(flute);
    }
}

        tune()方法接受一个Instrument引用。但是在上述程序中,它也接受了Wind对象,这是因为Wind对象也是一个Instrument对象,Wind拥有所有的Instrument接口。因此,tune()Instrument及其的任何子类起作用。这种将子类引用转换为基类引用的行为就是向上转型

    也可以将子类称为基类的超集。

final关键字

        final关键字一般表示“这是无法更改的”。之所以需要阻止更改,可能的原因有两个:设计或是效率。final关键字可以在三个地方进行使用:数据、方法和类。

final数据

        在此之前需要先讨论常量,常量会有用,有两个原因:

  1. 这种数据可以是一个永远不会改变的编译时常量
  2. 这种数据可以是在运行时初始化的值,并且程序员不希望数据被修改。

        在Java中,常量必须是基本类型,并且使用final关键字表示,必须在初始化时为其提供一个值。

    一个即是static又是final的字段只会分配一块不能改变的储存空间。

        若一个非基本类型(对象引用)使用了final关键字,那么final会使得这一引用无法改变,但是对象本身是可以进行修改的(Java没有提供使对象恒定不变的方法)

        final字段的使用例:

import java.util.*;

class Value {
    int i; // 拥有包访问权限的字段

    Value(int i) {
        this.i = i;
    }
}

public class FinalData {
    private static Random rand = new Random(47);
    private String id;

    public FinalData(String id) {
        this.id = id;
    }

    // 编译时常量
    private final int valueOne = 9;
    private static final int VALUE_TWO = 99;

    // 典型的公共常量
    public static final int VALUE_THREE = 39;

    // 不可作为编译时常量
    private final int i4 = rand.nextInt(20);
    static final int INT_5 = rand.nextInt(20);

    private Value v1 = new Value(11);
    private final Value v2 = new Value(11);

    // 数组
    private final int[] a = { 1, 2, 3, 4, 5, 6 };

    @Override
    public String toString() {
        return id + ": " + "i4 = " + i4 + ", " + "INT_5 = " + INT_5;
    }

    public static void main(String[] args) {
        FinalData fd1 = new FinalData("fd1");

        // final修饰,无法改变值
        // fd1.valueOne++;
        // fd1.VALUE_TWO++;

        // 非恒定不变的对象
        fd1.v2.i++;
        fd1.v1 = new Value(9);
        for (int i = 0; i < fd1.a.length; i++)
            fd1.a[i]++;

        // 对象引用无法修改
        // fd1.v2 = new Value(0);
        // fd1.a = new int[3];

        System.out.println(fd1);
        System.out.println();
        System.out.println("创建一个新的FinalData对象");
        FinalData fd2 = new FinalData("fd2");
        System.out.println(fd1);
        System.out.println(fd2);
    }
}

        程序执行的结果如下:

        在上述程序中,创建第二个对象并没有改变INT_5的值,因为这个数据是静态的,它只会在加载时初始化一次。

    按照惯例,具有常量初始值的final static基本类型(编译时常量)全部使用大写字母命名,单词之间使用下划线分隔。

空白final

        空白final,即没有初始值的final字段。编译器会确保这种空白final字段在使用前被初始化。这种做法可以让类中的final字段对每个对象而言都是不同的,同时保持其不可改变的特性:

class Poppet {
    private int i;

    Poppet(int i2) {
        i = i2;
    }
}

public class BlankFinal {
    private final int i = 0; // 进行了初始化的final字段

    private final int j; // 空白final字段
    private final Poppet p; // 空白final引用

    // 空白final必须在构造器中进行初始化
    public BlankFinal() {
        j = 1;
        p = new Poppet(1);
    }

    public BlankFinal(int x) {
        j = x;
        p = new Poppet(x);
    }

    public static void main(String[] args) {
        new BlankFinal();
        new BlankFinal(3);
    }
}

        final的赋值操作只能发生在:

  1. 字段定义处;
  2. 构造器中。

---

final参数

        在参数列表中也可以创建final参数。在方法内部无法改变final参数指向的内容,而只能进行参数的读取。


final方法

        使用final方法有两个原因:

  1. 防止继承的类通过重写改变该方法的含义;
  2. 为了提高效率(但一般不推荐这样考虑)。

        类中的任何一个private方法都是隐式的final。因为private方法即不可以访问,也不能被重写。但若尝试重写一个private方法,会发现编译器不会进行报错:

class WithFinal {
    private final void f() { // final使用与否没有区别
        System.out.println("类WithFinal的方法f()");
    }

    private void g() {
        System.out.println("类WithFinal的方法g()");
    }
}

class HavePrivate extends WithFinal {
    private final void f() {
        System.out.println("类HavePrivate的方法f()");
    }

    private void g() {
        System.out.println("类HavePrivate的方法g()");
    }
}

class OverridingPrivate extends HavePrivate {
    public final void f() {
        System.out.println("类OverridingPrivate的方法f()");
    }

    public void g() {
        System.out.println("类OverridingPrivate的方法g()");
    }
}

public class FinalOverridingIllusion {
    public static void main(String[] args) {
        OverridingPrivate op = new OverridingPrivate();
        op.f();
        op.g();

        // 可以使用向上转型
        HavePrivate hp = op;
        // 但是hp的方法是不可被调用的
        // hp.f();
        // hp.g();

        // 基类的方法也无法使用
        WithFinal wf = op;
        // wf.f();
        // wf.g();
    }
}

        程序运行的结果如下:

        注意:重写只有在方法是基类接口的一部分时才会发生。

        若一个方法是private的,那么它就不是基类接口的一部分。它是隐藏在类中的代码,只是恰好具有相同的名称罢了。即使在子类中创建了具有相同名称的方法,这个方法也与基类中被隐藏的代码无关

    使用@Override可以产生有用的报错信息。


final类

        将一个类定义为final时,会阻止该类的所有继承。这么做往往是不希望类的设计被修改,或者不允许该类存在子类。

class SmallBrain {
}

final class Dinosaur {
    int i = 7;
    int j = 1;
    SmallBrain x = new SmallBrain();

    void f() {
    };
}

// class Further extends Dinosaur{} // 无法继承final类

public class Jurassic {
    public static void main(String[] args) {
        Dinosaur n = new Dinosaur(); // 可以创建final类的对象
        n.f();
        n.i++;
        n.j++;
    }
}

        由于上述Dinosaur类的方法都是隐式的,所以无法被继承,也就无法进行重写了。

    无论类是否被定义为final,相同的规则都会适用于字段的final定义。

初始化及类的重载

        在Java中,每个类的编译代码都存在于自己的单独文件中,只有在需要使用时才会加载。一般认为“类的代码在第一次使用时才加载”(在构造类的第一个对象或访问静态成员时)。因为这种更加灵活的加载方式,Java变得更容易操作。在这里需要说明的是:

  • 构造器是一个静态方法,当一个类的任何静态成员被访问时,都会触发其的加载。
  • 静态初始化发生在初次使用时,所有静态对象和静态代码块在加载时按照文本顺序进行初始化。静态成员只初始化一次
class Insect {
    private int i = 0;
    protected int j;

    Insect() {
        System.out.println("i = " + i + ", j = " + j);
        j = 39;
    }

    private static int x1 = printInit("静态成员Insect.x1初始化完毕");

    static int printInit(String s) {
        System.out.println(s);
        return 47;
    }
}

public class Beetle extends Insect {
    private int k = printInit("成员Beetle.k初始化完毕");

    public Beetle() {
        System.out.println("k = " + k);
        System.out.println("j = " + j);
    }

    private static int x2 = printInit("静态成员Beetle.x2初始化完毕");

    public static void main(String[] args) {
        System.out.println("调用Beetle的构造器");
        Beetle b = new Beetle();
    }
}

        程序执行的结果是:

        从程序的输出可以看出加载器加载的规律:当运行java Beetle时,加载器会在Beetle.class中找到Beetle的编译代码。在编译过程中,加载器发现还有一个基类,就会先去加载这一基类。而若基类还有自己的基类,那么第二个基类也会被加载,以此类推。直到执行完根基类(上述程序中是Insect)的静态初始化,然后继加载根基类的子类。

    这种加载方式的合理性在于,子类的静态初始化可能会依赖于基类成员的正确初始化。

        而对象的创建规律是:① 子类Beetle中的所有基本类型先被设为默认值,对象置为null。② 然后基类构造器会被调用,重复子类构造器相同的过程。③ 基类构造器完毕后,子类的实例变量按文本顺序初始化。④ 执行子类构造器的剩余部分。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/985391.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023-09-07工作心得:String 和 LocalDate 的比较

1、SQL查询时间 如果根据某个日期区间检索&#xff1a; 假设有张t_order表&#xff0c;其中有个字段 create_time 在数据库里的格式是”yyyy-MM-dd HH:mm:ss“ 如果我在前端&#xff0c;选择2023-09-06-2023-09-07&#xff0c;这个区间&#xff0c;其实我期待的是查出这两天…

pip cryptography 遇到的 OpenSSL 问题

pip install -r requirements.txt 遇到 bug Collecting cryptography2.7Downloading cryptography-2.7.tar.gz (495 kB)------------------------------------- 495.9/495.9 kB 15.7 MB/s eta 0:00:00Installing build dependencies: startedInstalling build dependencies:…

CVE-2017-12615 PUT方法漏洞复现

这里的端口出现占用问题&#xff0c;需要修改端口 使用nuclei工具 找到[CVE-2017-12615] [http] [high] http://192.168.142.151/poc.jsp?cmdcat%2Fetc%2Fpasswd 浏览器访问&#xff0c;获得/etc/passwd信息

lv3 嵌入式开发-5 linux shell命令(进程管理、用户管理)

目录 1 进程处理相关命令 1.1 进程的概念 1.2 查看进程的命令 1.3 发送信号命令 2 用户管理相关命令 2.1 用户管理相关文件介绍 2.2 用户管理相关命令介绍 1 进程处理相关命令 1.1 进程的概念 进程的概念主要有两点&#xff1a; 进程是一个实体。每一个进程都有它自己…

曾国藩农民出身,弯道超车实现逆袭的大智慧

曾国藩从小就笨笨的&#xff0c;读书多了才开窍&#xff0c;实现人生逆袭。农民出身&#xff0c;弯道超车&#xff0c;贵在坚持。 约翰生说过&#xff1a;“成大事不在于力量的大小&#xff0c;而在于能坚持多久。” 很多家长认为“不让孩子输在起跑线上”&#xff0c;这是错…

c++ day 2

1、封装一个结构体&#xff0c;结构体中包含一个私有数组&#xff0c;用来存放学生的成绩&#xff0c;包含一个私有变量&#xff0c;用来记录学生个数&#xff0c; 提供一个公有成员函数&#xff0c;void setNum(int num)用于设置学生个数 提供一个公有成员函数&#xff1a;v…

【送书活动】网络安全(黑客)自学

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

Unity 之利用Audio Source(音频源)组件用于播放声音

文章目录 Unity中的Audio Source&#xff08;音频源&#xff09;是一个用于播放声音的组件&#xff0c;通常附加到游戏对象上&#xff0c;以便在游戏中播放音频效果、音乐或对话。以下是Audio Source的详细介绍&#xff1a; 添加Audio Source&#xff1a; 要在Unity中使用Audio…

Apache Tomcat漏洞复现

文章目录 弱口令启动环境漏洞复现 本地文件包含启动环境漏洞复现 弱口令 启动环境 来到vulhub/tomcat/tomcat8/靶场 cd vulhub/tomcat/tomcat8/安装环境并启动&#xff1a; sudo docker-compose up -d && sudo docker-compose up -d修改端口后启动&#xff1a; su…

AWR仿真报错invalid noise data found

在AWR microwave office 使用外部导入的s参数时遇到报错&#xff1a;“ invalid noise data found”&#xff0c;如下图所示&#xff0c;然而导入的s参数中&#xff0c;并没有噪声参数。 分析了好一阵&#xff0c;发现是sp文件中出现了频率不单调的数据&#xff0c;如下图所示…

荣耀9x使用体验

第一次使用鸿蒙系统&#xff0c;感觉还行&#xff0c;虽然各种操作和手势不太习惯&#xff0c;但是不影响什么&#xff0c;这是已经发布了4年的手机&#xff0c;用起来没什么毛病&#xff0c;各方面比较均衡。 2年前买的&#xff0c;原价1500块&#xff0c;现在&#xff08;20…

不用额外插件?RunnerGo内置压测模式怎么选

我们在做性能测试时需要根据性能需求配置不同的压测模式如&#xff1a;阶梯模式。使用jmeter时我们需要安装插件来配置测试模式&#xff0c;为了方便用户使用&#xff0c;RunnerGo内嵌了压测模式这一选项&#xff0c;今天给大家介绍一下RunnerGo的几种压测模式和怎么根据性能需…

Python Opencv实践 - Harris角点检测

参考资料&#xff1a;https://blog.csdn.net/wsp_1138886114/article/details/90415190 import cv2 as cv import numpy as np import matplotlib.pyplot as pltimg cv.imread("../SampleImages/chinease_tower.jpg", cv.IMREAD_COLOR) plt.imshow(img[:,:,::-1])#…

l8-d8 TCP并发实现

一、TCP多进程并发 1.地址快速重用 先退出服务端&#xff0c;后退出客户端&#xff0c;则服务端会出现以下错误&#xff1a; 地址仍在使用中 解决方法&#xff1a; /*地址快速重用*/ int flag1,len sizeof (int); if ( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &a…

Java高并发(线程创建以及线程池,异步调用,CompletableFuture)

线程 是进程中的一个实体&#xff0c;线程本身是不会独立存在的。 进程是代码在数据集合上的一次运行活动 是系统进行资源分配调度的基本单位 。 线程则是进程的一个执行路径&#xff0c; 一个进程中至少有 线程&#xff0c;进程中多个线程共享进程的资源。 操作系统在分配资源…

工单提交管理H5小程序开发

工单提交管理H5小程序开发 工单系统&#xff0c;支持多工程师分配、知识库、工单评价、自定义字段、工单通知&#xff08;短信&邮件&#xff09;、自定义产品或服务等功能。提供全部无加密服务端源码和前端源代码&#xff0c;独立部署&#xff0c;不受限制。 功能&#…

【C++】类的封装 ② ( 封装最基本的表层概念 | 类对象作为参数传递的几种情况 )

文章目录 一、类的封装 : 将数据和方法封装到一个类中1、封装最基本的表层概念2、代码分析 - 基本封装3、代码分析 - 类对象作为参数传递的几种情况 ( 指针 / 引用 / 直接 )4、完整代码示例 一、类的封装 : 将数据和方法封装到一个类中 1、封装最基本的表层概念 将数据和方法封…

concrt140.dll怎么下载,concrt140.dll修复工具(修复精灵下载)一键修复问题

今天&#xff0c;我将为大家介绍一个非常常见的问题&#xff1a;由于找不到concrt140.dll,无法继续执行代码怎么办。这个问题可能会让很多网友感到头疼&#xff0c;但是别担心&#xff0c;我会为大家提供5种最全详细的恢复方法。在接下来我将详细介绍这些问题及其解决方法。希望…

索尼 toio™ 应用创意开发征文互动小企鹅

文章目录 背景介绍产品应用动手实践环境安装工具安装更新 setuptools 到最新版本安装 toio.py安装 bleak安装 ipykernel检查是否安装成功 定义电子小企鹅的功能和逻辑小企鹅移动小企鹅脸变色小企鹅尖叫综合实现 总结 背景介绍 索尼推出的toio™ 是一款创新的游戏玩具&#xff…

改进深度学习网络的几个思路

由于要优化网络&#xff0c;老师给提供的几个思路&#xff1a; 个人学习后的几个认知&#xff1a; 1.联级特征融合模块 主要用于残差网络最后的残差块融合上 其中 R5 是经过 Res5 通过 33 的卷积获得的特征图&#xff0c;该特征图保持空间分辨率不变&#xff0c;并将通道的数…