初识Java 6-1 复用

目录

组合

继承

委托

组合和继承的结合

确保正确的清理

名称隐藏

在组合和继承之间选择

protected关键字

向上转型

final关键字

final数据

final方法

final类

初始化及类的重载


 

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


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

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

组合

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

class WaterSource {private String s;WaterSource() {System.out.println("一个WaterSource类的构造器");s = "构造器";}@Overridepublic String toString() {return s;}
}public class SprinklerSystem {private String value1, value2, value3, value4;private WaterSource source = new WaterSource();private int i;private float f;@Overridepublic 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 = "构造器";}@Overridepublic 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;}@Overridepublic 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()");}@Overridepublic 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 {@Overridepublic 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;}@Overridepublic 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("画一个圆");}@Overridevoid 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);}@Overridevoid 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("合并构造器");}@Overridepublic 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 {@Overridevoid 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;}@Overridepublic 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;}@Overridepublic 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 };@Overridepublic 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.mzph.cn/news/70736.shtml

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

相关文章

Windows系统管理一:操作系统概述

计算机系统的层次结构 五大基本功能 处理器管理 其工作主要是进程调度&#xff0c;在单用户单任务的情况下&#xff0c;必处理器仅为一个用户的一个任务所独占&#xff0c;进程管理的工作十分简单。但在多道程序或多用户的情况下&#xff0c;组织多个作业或任务时&#xff0c…

AJAX学习笔记8 跨域问题及解决方案

AJAX学习笔记7 AJAX实现省市联动_biubiubiu0706的博客-CSDN博客 跨域:指一个域名的网页去请求另外一个域名资源.比如百度页面去请求京东页面资源. 同源与不同源三要素:协议,域名,端口 协议一致,域名一致,端口一致.才算是同源.其他一律不同源 新建项目测试: 1.window.open();…

layui--记录

layui 行点击事件&#xff1a;点了没反应&#xff1f; //监听行工具事件layui.table.on(tool(demo), function (obj) {//alert(222) });原因&#xff1a;检查下id与lay-filter是否一致&#xff1b;id与lay-filter必须一致。 <table id"demo" lay-filter"dem…

Yarn资源调度器

文章目录 一、Yarn资源调度器1、架构2、Yarn工作机制3、HDFS、YARN、MR关系4、作业提交之HDFS&MapReduce 二、Yarn调度器和调度算法1、先进先出调度器&#xff08;FIFO&#xff09;2、容量调度器&#xff08;Capacity Scheduler&#xff09;3、公平调度器&#xff08;Fair …

pdf怎么转换成dwg格式?简单转换方法分享

当我们需要在CAD中编辑PDF文件中的向量图形时&#xff0c;将PDF转换成DWG格式是一个非常好的选择。因为PDF是一种非常流行的文档格式&#xff0c;很多时候我们会接收到PDF文件&#xff0c;但是PDF文件中的向量图形无法直接在CAD中编辑。而将PDF转换成DWG格式后&#xff0c;就可…

【网络层】网络基础 -- IP协议

引入IP协议头格式网段划分特殊的IP地址IP地址的数量限制 私有IP地址和公网IP地址分片与组装如何分片与组装&#xff1f; 引入 我们前面学习了传输层的相关知识&#xff0c;难道真的就是直接传送吗&#xff1f;当然不是&#xff0c;那TCP究竟做了什么&#xff1f;IP又扮演什么角…

图床项目详解

文章目录 一、图床项目介绍二、图床项目架构三、图床功能实现3.1 注册功能3.2 登录功能3.3 用户文件列表3.4 上传文件3.5 上传文件之秒传3.6 获取共享文件列表或下载榜3.7 分享/ 删除文件/ 更新下载数3.8 取消分享/ 转存/ 更新下载计数3.9 图床分享图片 一、图床项目介绍 实现…

想考PMP,符合报名条件么?怎么报考?

报考PMP第一步就是了解报名条件&#xff1a; PMP考试如何报名&#xff1f; 先在PMI官网报英文报名&#xff0c;再在人才交流基金会上报中文报名以及缴费。 1、英文报名 PMP英文报名就是在PMI网站上提交对应的英文材料信息。不限时间&#xff0c;随时可以报名&#xff0c;报…

2023年高教社杯 国赛数学建模思路 - 案例:最短时间生产计划安排

文章目录 0 赛题思路1 模型描述2 实例2.1 问题描述2.2 数学模型2.2.1 模型流程2.2.2 符号约定2.2.3 求解模型 2.3 相关代码2.4 模型求解结果 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 最短时…

leetcode645. 错误的集合(java)

错误的集合 题目描述优化空间代码演示 题目描述 难度 - 简单 LC645 - 错误的集合 集合 s 包含从 1 到 n 的整数。不幸的是&#xff0c;因为数据错误&#xff0c;导致集合里面某一个数字复制了成了集合里面的另外一个数字的值&#xff0c;导致集合 丢失了一个数字 并且 有一个数…

Talk | ICCV‘23南洋理工大学博士后李祥泰:面向统一高效的视频分割方法设计

本期为TechBeat人工智能社区第528期线上Talk&#xff01; 北京时间9月6日(周三)20:00&#xff0c;南洋理工大学博士后研究员—李祥泰的Talk已准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “面向统一高效的视频分割方法设计”&#xff0c;他分享了其在视…

华为云云服务器评测|安装Java8环境 配置环境变量 spring项目部署 【!】存在问题未解决

目录 引出安装JDK8环境查看是否有默认jar上传Linux版本的jar包解压压缩包配置环境变量 上传jar包以及运行问题上传Jar包运行控制台开放端口访问失败—见问题记录关闭Jar的方式1.进程kill -92.ctrl c退出 问题记录&#xff1a;【!】未解决各种方式查看端口情况联系工程师最后排查…

在Ubuntu Linux系统上安装RabbitMQ服务并解决公网远程访问问题

文章目录 前言1.安装erlang 语言2.安装rabbitMQ3. 内网穿透3.1 安装cpolar内网穿透(支持一键自动安装脚本)3.2 创建HTTP隧道 4. 公网远程连接5.固定公网TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址 前言 RabbitMQ是一个在 AMQP(高级消息队列协议)基…

智能电销机器人,主要体现的价值是什么

21世纪科技的迅速发展&#xff0c;人工智能逐渐走入大家的视线&#xff0c;越来越多的机器人出现在我们生活中。见的最多的有电销公司的智能语音机器人、在仓库拣货打包的机器人、商场店铺供娱乐对话的机器人。机器人活跃在各行各业中&#xff0c;降低了人工成本&#xff0c;代…

录音工具哪个好用?亲身测评,推荐这几个

“电脑录音用什么工具呀&#xff0c;前几天录制的视频声音没有录进去&#xff0c;现在需要重新补录声音&#xff0c;但是找不到合适的录音工具&#xff0c;就想问问大家&#xff0c;有没有好用的录音工具推荐呀&#xff1f;” 现如今&#xff0c;录音工具在我们的生活和工作中…

Kubernetes(k8s)上安装Prometheus和Grafana监控

Kubernetes上安装Prometheus和Grafana监控 环境准备Kubernetes准备 安装项目开始安装下载安装的项目安装项目替换镜像替换kube-state-metrics替换prometheus-adapter 修改Service修改alertmanager-service.yaml修改grafana-service.yaml修改prometheus-service.yaml 执行这些ya…

Vue + Element UI 前端篇(三):工具模块封装

Vue Element UI 实现权限管理系统 前端篇&#xff08;三&#xff09;&#xff1a;工具模块封装 封装 axios 模块 封装背景 使用axios发起一个请求是比较简单的事情&#xff0c;但是axios没有进行封装复用&#xff0c;项目越来越大&#xff0c;会引起越来越多的代码冗余&am…

浅谈Spring

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器&#xff08;框架&#xff09;。 一、什么是IOC&#xff1f; IoC Inversion of Control 翻译成中⽂是“控制反转”的意思&#xff0c;也就是说 Spring 是⼀个“控制反转”的容器。 1.1控制反转推导 这个控制反转怎…

c语言逻辑思维

c语言逻辑思维 1.如何问问题? 有甲、乙两人&#xff0c;其中&#xff0c;甲只说假话&#xff0c;而不说真话;乙则是只说真话&#xff0c;不说假话。但是&#xff0c;他们两个人在回答别人的问题时&#xff0c;只通过点头与摇头来表示&#xff0c;不讲话。有一天&#xff0c;一…

Unity中Shader的时间_Time

文章目录 前言一、_Time.xyzw分别代表什么二、_Time怎么使用 前言 Unity中Shader的时间_Time 一、_Time.xyzw分别代表什么 _Time.y 代表当前时间 二、_Time怎么使用 在需要使用的地方直接 * _Time.y 或 x / z / w 测试代码&#xff1a; Shader "MyShader/P0_9_5&qu…