当前位置: 首页 > news >正文

【设计模式】- 结构型模式

代理模式

给目标对象提供一个代理以控制对该对象的访问。外界如果需要访问目标对象,需要去访问代理对象。

分类:

  1. 静态代理:代理类在编译时期生成
  2. 动态代理:代理类在java运行时生成
    • JDK代理
    • CGLib代理

主要角色】:

  • 抽象主题类(Subject):通过接口或抽象类声明真实主题和代理对象
  • 真实主题类(Real Subject):实现抽象主题中的具体业务,是最终要引用的对象
  • 代理类(Proxy):提供了与真实主题相同的接口,内部含有对真实主题的引用

静态代理

火车站买票】:要买火车票需要去火车站买票。火车站是目标对象,代售点是代理对象。

卖票(抽象主题类):

public interface SellTickets {void sell();
}

火车站卖票(具体主题类):

public class TrainStation implements SellTickets {@Overridepublic void sell() {System.out.println("火车站卖票");}
}

代售点类(代理类):

public class ProxyPoint implements SellTickets {/*火车站类对象*/private TrainStation trainStation = new TrainStation();@Overridepublic void sell() {System.out.println("代售点收取服务费用");trainStation.sell(); // 代售点卖票其实调用的还是火车站里的sell()方法}
}

测试类:

public class Client {public static void main(String[] args) {// 1. 创建代售点类对象ProxyPoint proxyPoint = new ProxyPoint();// 2. 调用方法进行买票proxyPoint.sell();}
}

代售点作为访问对象和目标对象的中介,避免了访问类直接访问目标对象

JDK动态代理

代理对象工厂(获取代理对象):

public class ProxyFactory {/*目标对象*/private TrainStation station = new TrainStation();/*创建代理对象*/public SellTickets getProxyObject() {/*获取代理对象的方法ClassLoader loader:类加载器,用于加载代理类(可以通过目标对象获取类加载器)Class<?>[] interfaces:代理类实现的接口的字节码对象InvocationHandler h:代理对象的调用处理程序(外界使用使用代理对象调用方法,实际上就是调用这里的invoke()方法)*/SellTickets proxyObject = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),station.getClass().getInterfaces(),new InvocationHandler() {/*Object proxy:代理对象,和proxyObject是同一个对象(在invoke()方法中基本不用)Method method:对接口中的方法进行封装的method对象Object[] args:调用方法的实际参数(方法如果有)返回值:就是方法的返回值(没有返回值,就是返回null)*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("代售点收取一定的服务费用(jdk动态代理)");// 执行目标对象的方法Object obj = method.invoke(station, args);return obj;}});return proxyObject;}
}

测试类:

public class Client {public static void main(String[] args) {ProxyFactory proxyFactory = new ProxyFactory();SellTickets proxyObject = proxyFactory.getProxyObject();proxyObject.sell();}
}

CGLIB动态代理

如果没有定义SellTickets,只定义了TrainStation,JDK代理就无法使用了。(因为JDK代理必须要求定义接口,对接口进行代理)

CGLIB:功能强大,高性能的代码生成包,为没有实现接口的类提供代理,为JDK的动态代理提供很好的补充

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>2.2.2</version></dependency>

火车站类(没有实现SellTickets接口):

public class TrainStation {public void sell() {System.out.println("火车站卖票");}
}

代理对象工厂(获取代理对象):

public class ProxyFactory implements MethodInterceptor {/*火车站对象*/private TrainStation station = new TrainStation();public TrainStation getProxyObject() {// 1. 创建Enhancer对象,类似于JDK代理中的Proxy类Enhancer enhancer = new Enhancer();// 2. 设置父类的字节码(CGLIB的代理类属于目标类的子类)enhancer.setSuperclass(TrainStation.class);// 3. 设置回调函数enhancer.setCallback(this);// 4. 创建代理对象TrainStation proxyObject = (TrainStation) enhancer.create();return proxyObject;}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {// 调用目标对象的方法System.out.println("代售点收取一定的费用(CGLIB代理)");Object obj = method.invoke(station, objects); // 调用sell()方法return obj;}
}

CGLIB的代理类属于:目标类的子类

测试类:

public class Client {public static void main(String[] args) {// 1. 创建代理工厂对象ProxyFactory proxyFactory = new ProxyFactory();// 2. 获取代理对象TrainStation proxyObject = proxyFactory.getProxyObject();// 3. 调用代理对象中的sell()方法卖票proxyObject.sell();}
}

三种代理的对比

JDK代理 vs CGLIB代理

CGLIB:不能对声明为final的类或方法进行代理(CGLIB原理是动态生成被代理的子类)
JDK代理效率高于CGLIB代理。
原理】如果有接口使用JDK动态代理,没有接口使用CGLIB代理

动态代理 vs 静态代理

动态代理:类或接口中声明的所有方法都被转移到调用的一个集中的方法中处理,接口数量较多的时候,可以灵活处理
静态代理:如果接口增加了一个方法,除了所有实现类要实现这个方法,所有的代理类也需要实现这个方法

使用场景

  1. 远程代理:本地服务通过网络请求远程服务,我们可以将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,不必过多关心通信的细节(RPC)
  2. 防火墙:当浏览器配置成使用代理功能时,防火墙就会将浏览器的请求转发给互联网,互联网返回响应时,服务器再把它转到浏览器。
  3. 保护代理:控制一个对象的访问,如果需要可以给不同的用户提供不同级别的使用权限。

适配器模式

将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

主要角色】:

  • 目标接口(Target):当前业务系统锁期待的接口
  • 适配者类(Adaptee):被访问和适配的现存组件库的组件接口
  • 适配器类(Adapter):转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者

类适配器模式(应用少)

场景分析】:有一台电脑只能读取SD卡,如果要读取TF卡中的内容就需要用到适配器模式,创建一个读卡器,将TF卡中的内容读取出来。

目标接口(SD卡):

public interface SDCard {/*从SD卡中读取数据*/String readSD();/*往SD卡中写数据*/void writeSD(String msg);
}public class SDCardImpl implements SDCard {@Overridepublic String readSD() {return "SDCard read msg : hello world";}@Overridepublic void writeSD(String msg) {System.out.println("SDCard write msg : " + msg);}
}

适配者类(TF卡):

public interface TFCard {/*从TF卡中读取数据*/String readTF();/*往TF卡中写数据*/void writeTF(String msg);
}public class TFCardImpl implements TFCard {@Overridepublic String readTF() {return "TFCard read msg : hello world";}@Overridepublic void writeTF(String msg) {System.out.println("TFCard write msg : " + msg);}
}

计算机类:

public class Computer {/*从SD卡中读取数据*/public String readSD(SDCard sdCard) {Assert.isTrue(sdCard != null, ()-> new NullPointerException("sdCard is not null"));return sdCard.readSD();}
}

适配器类(继承适配者类,实现目标类接口):

public class SDAdapterTF extends TFCardImpl implements SDCard { // 继承适配者类,实现目标类接口@Overridepublic String readSD() {System.out.println("adapter read TFCard");return readTF(); // TFCardImpl里实现的方法}@Overridepublic void writeSD(String msg) {System.out.println("adapter write TFCard");writeTF(msg);}
}

测试类:

public class Client {public static void main(String[] args) {// 1. 创建计算机对象Computer computer = new Computer();// 2. 读取sd卡中的数据String msg = computer.readSD(new SDCardImpl());System.out.println(msg);System.out.println("===========");// 3. 使用该电脑读取TF卡中的数据SDAdapterTF sdAdapterTF = new SDAdapterTF();System.out.println(sdAdapterTF.readSD());}
}

局限性:由上边适配器的代码可知,适配器类需要继承适配者类,实现目标类接口。但是如果目标类没有接口只有一个类,而java又是单继承,就无法使用类适配器模式。所以实际多使用对象适配器模式

对象适配器模式(应用多)

对象适配器模式和类适配器模式基本一样,主要是在适配器类的不同。

  • 类适配器模式:集成适配者类
  • 对象适配器模式:聚合适配者对象

适配器类:

public class SDAdapterTF implements SDCard { // 继承适配器类,实现目标类接口private TFCard tfCard;public SDAdapterTF(TFCard tfCard) {this.tfCard = tfCard;}@Overridepublic String readSD() {System.out.println("adapter read TFCard");return tfCard.readTF(); // 通过成员变量调用}@Overridepublic void writeSD(String msg) {System.out.println("adapter write TFCard");tfCard.writeTF(msg);}
}

测试类:

public class Client {public static void main(String[] args) {// 1. 创建计算机对象Computer computer = new Computer();// 2. 读取sd卡中的数据String msg = computer.readSD(new SDCardImpl());System.out.println(msg);System.out.println("===========");// 3. 使用该电脑读取TF卡中的数据SDAdapterTF sdAdapterTF = new SDAdapterTF(new TFCardImpl());System.out.println(computer.readSD(sdAdapterTF));}
}

使用场景

  1. 以前开发的系统存在满足新系统功能需求的类,但是接口和新系统不一致
  2. 使用第三方提供的组件,但是接口定义和自己要求的接口定义不同

装饰者模式

在不改变现有对象的情况下,动态的给该对象添加一些职责的模式(添加额外功能)

主要角色】:

  • 抽象构建角色(Component):定义一个抽象接口以规范准备接收附加责任的对象
  • 具体构建角色(Concrete Component):实现抽象构建,通过装饰角色为其添加一些职责
  • 抽象装饰角色(Decorator):集成或实现抽象构建,并包含具体构建的实例,可以通过其子类扩展具体构建的功能
  • 具体装饰角色(COncrete Decorator):实现抽象装饰的相关方法,并给具体构建对象添加附加的责任

案例:快餐店

快餐类(抽象构建角色):

@Data
@AllArgsConstructor
@NoArgsConstructor
public abstract class FastFood {/*价格*/private float price;/*描述*/private String desc;/*计算价格*/public abstract float cost();
}

炒面、炒饭(具体构建角色)

public class FriedNoodles extends FastFood {public FriedNoodles() {super(12, "炒面");}@Overridepublic float cost() {return getPrice();}
}public class FriedRice extends FastFood {public FriedRice() {super(10, "炒饭");}@Overridepublic float cost() {return getPrice();}
}

修饰类(抽象修饰者类):

@Data
public abstract class Garnish extends FastFood {private FastFood fastFood;public Garnish(FastFood fastFood, float price, String desc) {super(price, desc);this.fastFood = fastFood;}
}

鸡蛋、培根(具体修饰者类):

public class Egg extends Garnish {public Egg(FastFood fastFood) {super(fastFood, 1, "鸡蛋");}@Overridepublic float cost() {return getPrice() + getFastFood().cost();}@Overridepublic String getDesc() {return super.getDesc() + getFastFood().getDesc();}
}public class Bacon extends Garnish{public Bacon(FastFood fastFood) {super(fastFood, 2, "培根");}@Overridepublic float cost() {return getPrice() + getFastFood().cost();}@Overridepublic String getDesc() {return super.getDesc() + getFastFood().getDesc();}
}

测试类:

public class Client {public static void main(String[] args) {// 1. 点一份炒饭FastFood food = new FriedRice();System.out.println(food.getDesc() + food.cost());// 2. 在炒饭里加鸡蛋food = new Egg(food);System.out.println(food.getDesc() + food.cost());// 3. 在炒饭里再加一个鸡蛋food = new Egg(food);System.out.println(food.getDesc() + food.cost());// 4. 在炒饭里再加一个培根food = new Bacon(food);System.out.println(food.getDesc() + food.cost());}
}

使用场景

装饰者模式是继承的一个替代模式,可以动态扩展一个实现类的功能

  1. 不能使用继承的方式对系统进行扩充。其中,不能采用继承的情况:
    • 系统中存在需要大量独立的扩展,为支持每一种组合将产生大量的子类,使子类数目呈爆炸性增长
    • 类不能被继承(被final修饰)
  2. 不影响其他对象的情况下,可以动态的给单个对象添加职责
  3. 对象的功能要求可以动态的添加、撤销时

静态代理 vs 装饰者模式

相同点:

  • 都要实现与目标类相同的业务接口
  • 在两个类中都要声明目标对象
  • 都可以在不修改目标类的前提下增强目标方法

不同点:

  • 装饰者模式是为了增强对象;静态代理是为了保护和隐藏目标对象
  • 装饰者获取对象的方式是通过外界传递过来(构造方法参数);静态代理是在代理类内部创建(new 实现类对象),来隐藏目标对象

桥接模式

在这里插入图片描述
问题】:圆、长方形、正方形都有黑色、灰色、白色三种颜色。如果新增一个颜色,需要新增好多实现类。

将抽象和实现分离,使他们可以独立变化。用组合关系代替继承关系。

比如上边的三种图形,抽象和实现就是:图形和颜色这两种不同维度的变化。

主要角色】:

  • 抽象化角色(Abstraction):定义一个抽象类,并包含一个对实现化对象的引用
  • 扩展抽象化角色(Refined Abstraction):抽象化角色的子类,是实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法
  • 实现化角色(Implementor):定义实现化角色的接口,供扩展抽象化角色调用
  • 具体实现化角色(Concrete Implementor):给出实现化角色接口的具体实现

案例:视频播放器

需求】:需要一个跨平台的视频播放器,可以在不同操作系统平台(windows、mac)上播放多种格式(rmvb、avi)的视频

视频文件格式(实现化角色):

public interface VideoFile {/*解码功能*/void decode(String fileName);
}

rmvb、avi视频格式文件(具体实现化角色):

public class RmvbFile implements VideoFile {@Overridepublic void decode(String fileName) {System.out.println("rmvb视频文件:" + fileName);}
}public class AviFile implements VideoFile {@Overridepublic void decode(String fileName) {System.out.println("avi视频文件:" + fileName);}
}

抽象的操作系统类(抽象化角色):

@AllArgsConstructor
public abstract class OpratingSystem {protected VideoFile videoFile;public abstract void play(String fileName);
}

mac、windows操作系统(扩展抽象化角色):

public class Mac extends OpratingSystem {public Mac(VideoFile videoFile) {super(videoFile);}@Overridepublic void play(String fileName) {videoFile.decode(fileName);}
}public class Windows extends OpratingSystem {public Windows(VideoFile videoFile) {super(videoFile);}@Overridepublic void play(String fileName) {videoFile.decode(fileName);}
}

桥接模式提高了系统的可扩展性,在两个变化的维度中任意扩展一个维度,都不需要修改原系统。
比如我添加了一个linux操作系统,只需要新增一个Linux类,继承OpratingSystem,不需要修改原有类

使用场景

  1. 一个类存在两个独立变化的维度,且这两个维度都要进行扩展
  2. 当一个系统不希望使用继承或因多层次继承导致系统类的个数急剧增加时
  3. 当一个系统需要在构建的抽象化角色和具体角色之间增加更多的灵活性时,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使他们在抽象层建立一个关联关系。

外观模式

通过为多个复杂子系统提供一个统一接口,该模式对外由一个统一接口,外部应用程序不需要关心内部子系统的具体细节。

外观模式是“迪米特法则”的典型应用

主要角色】:

  • 外观角色(Facade):为多个子系统对外提供一个共同的接口
  • 子系统角色(Sub System):实现系统的部分功能,客户可以通过外观角色访问它

案例:智能家电控制

电灯类、电视机类、空调类(子系统角色):

public class Light {/*开灯*/public void on() {System.out.println("打开电灯");}/*关灯*/public void off() {System.out.println("关闭电灯");}
}public class Tv {/*开启电视机*/public void on() {System.out.println("开启电视机");}/*关闭电视机*/public void off() {System.out.println("关闭电视机");}
}public class AirCondition {/*开启空调*/public void on() {System.out.println("开启空调");}/*关闭空调*/public void off() {System.out.println("关闭空调");}
}

智能家电类(外观类,用户主要和该类对象进行交互)

public class SmartAppliancesFacade {// 聚合电灯对象、电视机对象、空调对象private Light light;private Tv tv;private AirCondition airCondition;public SmartAppliancesFacade() {light = new Light();tv = new Tv();airCondition = new AirCondition();}// 通过语音控制public void say(String message) {if (message.contains("打开")) {on();}else if(message.contains("关闭")) {off();} else {System.out.println("我还听不懂你说的");}}/*** 一键打开*/private void on() {light.on();tv.on();airCondition.on();}/*** 一键关闭*/private void off() {light.off();tv.off();airCondition.off();}
}

测试类:

public class Client {public static void main(String[] args) {// 1. 创建智能音箱对象SmartAppliancesFacade facade = new SmartAppliancesFacade();// 2. 控制家电facade.say("打开家电");}
}
  1. 降低子系统和客户端之间的耦合,使子系统的变化不会影响调用它的客户类
  2. 对客户屏蔽了子系统,减少了子系统处理的对象数目

使用场景

  1. 对分层结构系统构建时,使用外观模式定义子系统中每层的入口可以简化子系统之间的依赖关系
  2. 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界调用(框架就是这样做的,底层很多方法,但是给我们使用就只需要一个方法)
  3. 当客户端与多个子系统存在很大联系时,使用外观模式可以将他们分离

组合模式

组合模式用于把一组相似的对象当作单一的对象,组合模式依据树形结构来组合对象,用来表示部分和整体的层次。

主要角色】:

  • 抽象根节点(Component):定义系统各层次对象的共有属性和方法
  • 树枝节点(Composite):存储子节点,组合树枝节点和叶子节点形成一个树形结构
  • 叶子节点(Leaf):叶子节点对象下再无分支,是系统遍历的最小单位

案例:软件菜单

需求】:在访问管理系统时,一个菜单可能包含菜单项(叶子节点),也可能包含带有其他菜单项的菜单(树枝节点)。
在这里插入图片描述
菜单组件(抽象根节点):

@AllArgsConstructor
public abstract class MenuComponent {/*菜单组件的名称*/protected String name;/*菜单组件的层级*/protected int level;/*添加子菜单(菜单项不能添加)*/public void add(MenuComponent component) {throw new UnsupportedOperationException();}/*移除子菜单*/public void remove(MenuComponent component) {throw new UnsupportedOperationException();}/*获取指定的子菜单*/public MenuComponent getChild(int idx) {throw new UnsupportedOperationException();}/*获取菜单或菜单项的名称*/public String getName() {return name;}/*打印菜单项名称(包含菜单项和子菜单项)*/public abstract void print();
}

菜单项类(叶子节点):

public class MenuItem extends MenuComponent {public MenuItem(String name, int level) {super(name, level);}@Overridepublic void print() {for(int i = 0; i < level; i++) {System.out.print("*");}System.out.println(name);}
}

菜单类(树枝节点):

public class Menu extends MenuComponent {/*菜单可以有多个子菜单 或 子菜单项*/private List<MenuComponent> menuComponentList = new ArrayList<>();public Menu(String name, int level) {super(name, level);}@Overridepublic void add(MenuComponent component) {menuComponentList.add(component);}@Overridepublic void remove(MenuComponent component) {menuComponentList.remove(component);}@Overridepublic MenuComponent getChild(int idx) {return menuComponentList.get(idx);}@Overridepublic void print() {// 打印菜单名称for(int i = 1; i < level; i++) {System.out.print("*");}System.out.println(name);// 打印子菜单 或 子菜单项名称menuComponentList.forEach(menu -> {menu.print();});}
}

测试类:

public class Client {public static void main(String[] args) {// 1. 创建菜单树MenuComponent menu1 = new Menu("菜单管理", 2);menu1.add(new MenuItem("页面访问", 3));menu1.add(new MenuItem("展开菜单", 3));menu1.add(new MenuItem("编辑菜单", 3));menu1.add(new MenuItem("删除菜单", 3));menu1.add(new MenuItem("新增菜单", 3));MenuComponent menu2 = new Menu("权限管理", 2);menu2.add(new MenuItem("页面访问", 3));menu2.add(new MenuItem("提交保存", 3));MenuComponent menu3 = new Menu("角色管理", 2);menu3.add(new MenuItem("页面访问", 3));menu3.add(new MenuItem("新增角色", 3));menu3.add(new MenuItem("修改角色", 3));// 2. 创建一级菜单MenuComponent component = new Menu("系统管理", 1);// 3. 将二级菜单添加到一级菜单component.add(menu1);component.add(menu2);component.add(menu3);// 打印菜单名称component.print();}
}

分类

  1. 透明组合模式(组合模式的标准形式):在上边的代码里,MenuComponent类(抽象根节点)里声明了add()、remove()、getChild()这些方法,并没有把他们声明成抽象方法,这样是为了确保所有的构建类都能有相同的接口

缺点:不够安全,叶子节点对象(MenuItem类)不可能有下一层次的对象,在MenuComponent类里提供add()、remove()这些方法也只是简单的抛出异常而已,并没有意义。

  1. 安全组合模式:在MenuComponent类(抽象根节点)没有任何用于管理成员对象的方法,这样会导致叶子节点和容器构建具有不同的方法,必须区别对待叶子构建和容器构建。

使用场景

树形结构很适合用组合模式。比如:文件目录显示、多级目录显示树形结构数据

享元模式

共享已存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,提高资源的利用率。
两种状态】:

  • 内部状态:不会随着环境的改变而改变的可共享部分。
  • 外部状态:随环境改变而改变的不可以共享的部分。
    主要角色】:
  • 抽象享元角色(Flyweight):通常是接口 或 抽象类,在抽象享元类中声明了具体享元类的公共方法,可以向外界提供享元对象的内部数据,同时可以通过这些方法来设置外部数据
  • 具体享元角色(Concrete Flyweight):实现了抽象享元类,在具体享元类中为内部状态提供了空间(通常结合单例模式为每个具体享元类提供唯一的享元对象)
  • 非享元角色(Unsharable Flyweight):不能被共享的子类可以设计成非共享享元类
  • 享元工厂角色(Flyweight Factory):负责创建和管理享元角色,当客户请求一个享元对象,享元工厂先检查是否存在符合要求的享元对象,如果存在就直接提供给用户,不存在就创建一个新的享元对象

案例:俄罗斯方块

俄罗斯方块有不同的形状,可以对这些形状向上抽取AbstractBox,用来定义共性的属性和行为。
在这里插入图片描述
抽象享元角色:

public abstract class AbstractBox {/*获取图形*/public abstract String getShape();/*显示图形和颜色*/public void display(String color) {System.out.println("方块形状" + getShape() + ", 颜色:" + color);}
}

I、L、O图形类(具体享元角色):

public class IBox extends AbstractBox {@Overridepublic String getShape() {return "I";}
}public class LBox extends AbstractBox {@Overridepublic String getShape() {return "L";}
}public class OBox extends AbstractBox {@Overridepublic String getShape() {return "O";}
}

工厂类(设计为单例):

public class BoxFactory {private HashMap<String, AbstractBox> map;private static BoxFactory factory = new BoxFactory();// 在构造方法中进行初始化操作private BoxFactory() {map = new HashMap<>();map.put("I", new IBox());map.put("O", new OBox());map.put("L", new LBox());}// 提供一个方法获取该工厂类对象public static BoxFactory getInstance() {return factory; // 饿汉式}// 根据名称获取图形对象public AbstractBox getShape(String name) {return map.get(name);}
}

测试类:

public class Client {public static void main(String[] args) {AbstractBox iBox = BoxFactory.getInstance().getShape("I");iBox.display("灰色");AbstractBox lBox1 = BoxFactory.getInstance().getShape("L");lBox1.display("蓝色");AbstractBox lBox2 = BoxFactory.getInstance().getShape("L");lBox2.display("绿色");System.out.println("lBox1 == lBox2:" + (lBox1 == lBox2));// true}
}

享元模式的外部状态相对独立,不影响内部状态;
极大减少内存中相似或相同对象数量,节约系统资源

使用场景

  1. 当一个系统有大量相同或相似的对象,使用享元模式可以减少内存浪费;
  2. 对象的大部分状态都可以外部化,并且可以将这些外部状态传到对象中;
  3. 使用享元模式需要额外维护一个存储享元对象的享元池,这需要耗费一定的系统资源。

JDK源码解析

public class Demo {public static void main(String[] args) {Integer i1 = 127, i2 = 127, i3 = 128, i4 = 128;System.out.println(i1 == i2); // trueSystem.out.println(i3 == i4); // false}
}

Integer默认会先创建并缓存-128~127之间数的Integer对象,当调用valueOf,如果参数是在-128~127之间,则计算下标从缓存中返回;否则就会创建一个新对象。

相关文章:

【设计模式】- 结构型模式

代理模式 给目标对象提供一个代理以控制对该对象的访问。外界如果需要访问目标对象&#xff0c;需要去访问代理对象。 分类&#xff1a; 静态代理&#xff1a;代理类在编译时期生成动态代理&#xff1a;代理类在java运行时生成 JDK代理CGLib代理 【主要角色】&#xff1a; 抽…...

python报错:使用json.dumps()时,报错type xxx is not json serializable错误原因及解决方案

文章目录 一、错误原因分析二、解决方案1. **自定义对象序列化方法一&#xff1a;使用default参数定义转换逻辑方法二&#xff1a;继承JSONEncoder类统一处理 2. **处理特殊数据类型场景一&#xff1a;datetime或numpy类型场景二&#xff1a;bytes类型 3. **处理复杂数据结构 三…...

Vue3中实现轮播图

目录 1. 轮播图介绍 2. 实现轮播图 2.1 准备工作 1、准备至少三张图片&#xff0c;并将图片文件名改为数字123 2、搭好HTML的标签 3、写好按钮和图片标签 ​编辑 2.2 单向绑定图片 2.3 在按钮里使用方法 2.4 运行代码 3. 完整代码 1. 轮播图介绍 首先&#xff0c;什么是…...

flutter缓存网络视频到本地,可离线观看

记录一下解决问题的过程&#xff0c;希望自己以后可以参考看看&#xff0c;解决更多的问题。 需求&#xff1a;flutter 缓存网络视频文件&#xff0c;可离线观看。 解决&#xff1a; 1&#xff0c;flutter APP视频播放组件调整&#xff1b; 2&#xff0c;找到视频播放组件&a…...

2025年Ai写PPT工具推荐,这5款Ai工具可以一键生成专业PPT

上个月给客户做产品宣讲时&#xff0c;我对着空白 PPT 页面熬到凌晨一点&#xff0c;光是调整文字排版就改了十几版&#xff0c;最后还是被吐槽 "内容零散没重点"。后来同事分享了几款 ai 写 PPT 工具&#xff0c;试完发现简直打开了新世界的大门 —— 不用手动写大纲…...

【深度学习】#11 优化算法

主要参考学习资料&#xff1a; 《动手学深度学习》阿斯顿张 等 著 【动手学深度学习 PyTorch版】哔哩哔哩跟李牧学AI 目录 深度学习中的优化挑战局部极小值鞍点梯度消失 凸性凸集凸函数 梯度下降一维梯度下降学习率局部极小值 多元梯度下降 随机梯度下降随机梯度更新动态学习率…...

数学复习笔记 13

前言 继续做线性相关的练习题&#xff0c;然后做矩阵的例题&#xff0c;还有矩阵的练习题。 646 A 明显是错的。因为假设系数全部是零&#xff0c;就不是线性相关了。要限制系数不全是零&#xff0c;才可以是线性相关。 B 这个说法好像没啥问题。系数全为零肯定线性组合的结…...

AI预测3D新模型百十个定位预测+胆码预测+去和尾2025年5月16日第79弹

从今天开始&#xff0c;咱们还是暂时基于旧的模型进行预测&#xff0c;好了&#xff0c;废话不多说&#xff0c;按照老办法&#xff0c;重点8-9码定位&#xff0c;配合三胆下1或下2&#xff0c;杀1-2个和尾&#xff0c;再杀6-8个和值&#xff0c;可以做到100-300注左右。 (1)定…...

阳台光伏+储能:安科瑞智能计量仪表来助力

随着可再生能源的普及和家庭储能需求的增长&#xff0c;阳台光伏储能系统逐渐成为家庭能源管理的新趋势。如何精准计量储能系统的发电量、用电量及电网交互数据&#xff0c;成为优化能源利用效率的关键。安科瑞计量仪表凭借高精度、多功能及智能化特性&#xff0c;为家庭阳台储…...

Unable to determine the device handle for GPU 0000:1A:00.0: Unknown Error

Unable to determine the device handle for GPU 0000:1A:00.0: Unknown Error 省流&#xff1a;我遇到这个问题重置bios设置就好了 这个错误信息表明系统无法识别或访问GPU&#xff08;0000:1A:00.0&#xff09;&#xff0c;通常与CUDA、驱动程序或硬件相关。以下是可能的原…...

多态性标记设计

1.确定区间 2.获取该区间内的序列&#xff0c;如果只有一个位置&#xff0c;可以前后扩100bp 使用ncbi primer blast进行引物设计&#xff08;https://blast.ncbi.nlm.nih.gov/Blast.cgi&#xff09;...

Jenkins 最佳实践

1. 在Jenkins中避免调度过载 过载Jenkins以同时运行多个作业可能导致资源竞争、构建速度变慢和系统性能问题。分配作业启动时间可以防止瓶颈&#xff0c;并确保更顺畅的执行。如何实现&#xff1f; 在Cron表达式中使用H&#xff1a;引入抖动&#xff08;jitter&#xff09;&a…...

如何查询Ubuntu系统中最大的几个目录以G单位显示大小,从大到小排列?

环境&#xff1a; Ubuntu 20,04 问题描述&#xff1a; 如何查询系统中最大的几个目录以G单位显示大小&#xff0c;从大到小排列&#xff1f; 解决方案&#xff1a; 想查看整个系统&#xff08;单一文件系统内&#xff09;最大的20个目录&#xff0c;结果按大小从大到小排序…...

深入浅出拆分学习,图神经网络拆分学习,混合联邦学习

深入浅出解析拆分学习&#xff08;Split Learning&#xff09;、图神经网络拆分学习&#xff08;Split Learning for Graph Neural Networks&#xff09;以及混合联邦学习&#xff08;Hybrid Federated Learning&#xff09;&#xff0c;这三者都体现了在分布式数据环境下进行机…...

DDD领域驱动介绍

&#x1f4d5;我是廖志伟&#xff0c;一名Java开发工程师、《Java项目实战——深入理解大型互联网企业通用技术》&#xff08;基础篇&#xff09;、&#xff08;进阶篇&#xff09;、&#xff08;架构篇&#xff09;清华大学出版社签约作家、Java领域优质创作者、CSDN博客专家、…...

ubuntu22鼠键失灵恢复记录笔记chatgpt解决

ChatGPT 说&#xff1a; 你提到“Ubuntu 22 鼠键失灵”&#xff0c;这个问题可能涉及以下几方面&#xff1a; &#x1f9ed; 先确认问题 是鼠标问题还是键盘问题&#xff0c;还是触控板&#xff1f; “鼠键”一般理解为“鼠标键”&#xff0c;请确认你是指鼠标左键/右键失灵&a…...

在服务器上安装AlphaFold2遇到的问题(1)

犯了错误&#xff0c;轻信deepseek&#xff0c;误将cuDNN8.9.7删掉 [rootlocalhost ~]# cat /usr/local/cuda/include/cudnn_version.h | grep CUDNN_MAJOR -A 2 #define CUDNN_MAJOR 8 #define CUDNN_MINOR 9 #define CUDNN_PATCHLEVEL 7 -- #define CUDNN_VERSION (CUDNN_MA…...

ch10 题目参考思路

ch10 - 最小生成树 有线通讯网 知识点&#xff1a;Prim 算法思路&#xff1a; 该题要求对 n 座城市铺设 n - 1 条光缆&#xff0c;并要求所有城市连通&#xff0c;那本质上是一棵树&#xff0c;又要求铺设光缆的费用最低&#xff0c;即要求选取的 n - 1 条光缆的长度最小&…...

Hudi、Iceberg 、 Paimon 数据湖选型对比

Hudi、Iceberg 和 Paimon 是当前数据湖领域的三大主流开源框架,均致力于解决数据湖场景下的增量更新、事务支持、元数据管理、流批统一等核心问题,但设计理念和适用场景存在差异。以下从技术特性、适用场景和选型建议三方面对比分析: 一、核心技术特性对比 维度HudiIceberg…...

2025认证杯数学建模第二阶段A题完整论文(代码齐全):小行星轨迹预测思路

2025认证杯数学建模第二阶段A题完整论文&#xff08;代码齐全&#xff09;:小行星轨迹预测思路,详细内容见文末名片 第二阶段问题 1 分析 问题起源与相关性&#xff1a;为了更全面地评估近地小行星对地球的潜在威胁&#xff0c;需要对其轨道进行长期预测。三个月内的观测数据为…...

信息安全基础知识

信息系统 信息系统能进行&#xff08;数据&#xff09;的采集、传输、存储、加工&#xff0c;使用和维护的计算机应用系统 例如&#xff1a;办公自动化、CRM/ERP、HRM、12306火车订票系统等。 信息安全 信息安全是指保护信息系统中的计算机硬件、软件、数据不因偶然或者恶意…...

UE RPG游戏开发练手 第二十六课 普通攻击1

UE RPG游戏开发练手 第二十六课 普通攻击1 1.定义攻击的InputTag MyGameplayTags.h代码 RPGGAMETEST_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(InputTag_LightAttack_Axe);MyGameplayTag.cpp代码 UE_DEFINE_GAMEPLAY_TAG(InputTag_LightAttack_Axe, "InputTag.LightAttack.Ax…...

SAP ABAP 程序中归档数据读取方式

上一篇文章记录了字段目录&#xff0c;归档信息结构&#xff0c;这篇文章记录如何通过字段目录&#xff0c;归档信息结构&#xff0c;归档对象读取归档数据。未归档数据是从数据库表直接抽取&#xff0c;本样例是通过归档读取方式复写sql。 发布时间&#xff1a;2025.05.16 示…...

每周资讯 | 腾讯Q1财报:国内游戏业务收入同比增长24%;Tripledot 8亿美元收购AppLovin游戏业务

内容速览&#xff1a; 广州“服务贸易和数字贸易22条”助推游戏产业发展Tripledot Studios 8亿美元收购AppLovin游戏业务苹果紧急申请暂停执行AppStore新规4月中国手游出海收入下载榜&#xff0c;点点互动《Kingshot》收入激增 腾讯Q1财报&#xff1a;国内游戏业务收入同比增长…...

iOS SwiftUI的具体运用实例(SwiftUI库的运用)

最近接触到一个 SwiftUI的第三方框架&#xff0c;它非常的好用。以下是 具体运用实例&#xff0c;结合其核心功能与开发场景&#xff0c;分多个维度进行详细解析&#xff1a; 一、基础 UI 组件开发 登录界面 SwiftUI 的 VStack、TextField 和 Button 可快速构建用户登录表单。例…...

杰理ac696配置sd卡随机播放

#define FCYCLE_LIST 0 // 列表循环&#xff08;按顺序播放文件列表&#xff09; #define FCYCLE_ALL 1 // 全部循环&#xff08;播放完所有文件后重新开始&#xff09; #define FCYCLE_ONE 2 // 单曲循环&#xff08;重复播放当前文件&#xff09; #define …...

MCP协议的核心机制和交互过程

MCP的核心是JSON-RPC 2.0 MCP使用了 JSON-RPC 2.0 作为client和server端的消息传输。JSON-RPC 2.0是一个用JSON编码的轻量级远程过程调用协议。它的优越性如下: 易读,易调试与编程语言无关,环境无关技术成熟,规范清晰且应用广泛JSON-NPC 2.0定义了request、response、noti…...

论信息系统项目的范围管理

论信息系统项目的范围管理 前言一、规划范围管理&#xff0c;收集需求二、定义范围三、创建工作分解结构四、确认范围五、控制范围 前言 为了应对烟草零售客户数量大幅度增长所带来的问题&#xff0c;切实履行控烟履约的相关要求&#xff0c;同时也为了响应国务院“放管服”政策…...

米勒电容补偿的理解

米勒电容补偿是使运放放大器稳定的重要手法&#xff0c;可以使两级运放的两个极点分离&#xff0c;从而可以得到更好的相位裕度。 Miller 电容补偿的本质是增加一条通路流电流&#xff0c;流电流才是miller效应的本质。给定一个相同的输入&#xff0c;Miller 电容吃掉的电流比…...

力扣654题:最大二叉树(递归)

小学生一枚&#xff0c;自学信奥中&#xff0c;没参加培训机构&#xff0c;所以命名不规范、代码不优美是在所难免的&#xff0c;欢迎指正。 标签&#xff1a; 二叉树、递归 语言&#xff1a; C 题目&#xff1a; 给定一个不重复的整数数组 nums 。最大二叉树可以用下面的算…...

Go语言实现生产者-消费者问题的多种方法

Go语言实现生产者-消费者问题的多种方法 生产者-消费者问题是并发编程中的经典问题&#xff0c;涉及多个生产者生成数据&#xff0c;多个消费者消费数据&#xff0c;二者通过缓冲区&#xff08;队列&#xff09;进行协调&#xff0c;保证数据的正确传递和同步。本文将从简单到…...

深度学习驱动下的目标检测技术:原理、算法与应用创新(二)

三、主流深度学习目标检测算法剖析 3.1 R - CNN 系列算法 3.1.1 R - CNN 算法详解 R - CNN&#xff08;Region - based Convolutional Neural Networks&#xff09;是将卷积神经网络&#xff08;CNN&#xff09;应用于目标检测领域的开创性算法&#xff0c;其在目标检测发展历…...

提权脚本Powerup命令备忘单

1. 获取与加载 从 GitHub 下载&#xff1a;(New-Object Net.WebClient).DownloadFile("https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Privesc/PowerUp.ps1", "C:\Temp\PowerUp.ps1")本地加载&#xff1a;Import-Module .\Power…...

人工智能 (AI) 在无线接入网络 (RAN) 中的变革性作用

随着电信行业向更智能、更高效的系统迈进&#xff0c;将 AI 集成到 RAN 中已不再是可有可无&#xff0c;而是至关重要。 随着 6G 时代的到来&#xff0c;人工智能 (AI) 有望降低运营成本&#xff0c;并带来更大的盈利机会。AI-RAN 正处于这一变革的前沿&#xff0c;在 RAN 环境…...

从硬件角度理解“Linux下一切皆文件“,详解用户级缓冲区

目录 前言 一、从硬件角度理解"Linux下一切皆文件" 从理解硬件是种“文件”到其他系统资源的抽象 二、缓冲区 1.缓冲区介绍 2.缓冲区的刷新策略 3.用户级缓冲区 这个用户级缓冲区在哪呢&#xff1f; 解释关于fork再加重定向“>”后数据会打印两份的原因 4.内核缓冲…...

Python-感知机以及实现感知机

感知机定义 如果有一个算法&#xff0c;具有1个或者多个入参&#xff0c;但是返回值要么是0&#xff0c;要么是1&#xff0c;那么这个算法就叫做感知机&#xff0c;也就是说&#xff0c;感知机是个算法 感知机有什么用 感知机是用来表示可能性的大小的&#xff0c;我们可以认…...

根据台账批量制作个人表

1. 前期材料准备 1&#xff09;要有 人员总的信息台账 2&#xff09;要有 个人明白卡模板 2. 开始操作 1&#xff09;打开 人员总的信息台账&#xff0c;选择所需要的数据模块&#xff1b; 2&#xff09;点击插入&#xff0c;选择数据透视表&#xff0c;按流程操作&…...

ohttps开启群晖ssl证书自动更新

开启群晖ssl证书自动更新OHTTPS ohttps是一个免费自动签发ssl证书、管理、部署的项目。 https://ohttps.com 本文举例以ohttps项目自动部署、更新群晖的ssl证书。 部署 签发证书 打开ohttps-证书管理-创建证书-按你实际情况创建证书。创建部署节点 打开Ohttps-部署节点-添加…...

【Elasticsearch】flattened`类型在查询嵌套数组时可能返回不准确结果的情况

好的&#xff01;为了更清楚地说明flattened类型在查询嵌套数组时可能返回不准确结果的情况&#xff0c;我们可以通过一个具体的例子来展示。这个例子将展示如何在文档中没有完全匹配的嵌套对象时&#xff0c;flattened类型仍然可能返回该文档。 示例文档结构 假设你有以下文…...

【知识点】语义分割任务中有哪些损失函数?

在语义分割任务中,模型需要对图像中的每个像素进行分类。因此,损失函数的设计不仅要关注整体精度,还需要特别注意目标物体的边界区域。以下是一些常用的损失函数及其适用场景,包括数学公式、PyTorch 实现和是否适合处理边界问题。 📌 一、交叉熵损失 Cross-Entropy Loss …...

Node.js 同步加载问题详解:原理、危害与优化策略

文章目录 一、什么是同步加载&#xff1f;二、同步加载的危害场景三、检测同步加载问题四、解决方案与代码优化 一、什么是同步加载&#xff1f; 1.核心概念 在 Node.js 的 CommonJS 模块系统中&#xff0c;require() 是同步操作&#xff1a; // 模块加载会阻塞后续代码执行 …...

linux下tcp/ip网络通信笔记1,

本文章主要为博主在学习网络通信的笔记一个Udp_echo_server,和client的代码实现 1&#xff0c;网络发展&#xff0c;网络协议&#xff0c;意识到网络通信——不同主机的进程间通信&#xff0c; 2&#xff0c;学习如何在应用层调用系统提供的接口进行通信&#xff0c;echo_Udp…...

网络攻防模拟:城市安全 “数字预演”

在当今数字化快速发展的时代&#xff0c;网络安全和城市安全面临着前所未有的挑战。为有效应对这些挑战&#xff0c;利用先进的技术搭建模拟演练平台至关重要。图扑软件的 HT for Web 技术&#xff0c;为网络攻防模拟与城市安全演练提供了全面且高效的解决方案。 三维场景搭建&…...

在 Ubuntu 20.04 中使用 init.d 或者systemd实现开机自动执行脚本

Ubuntu 20 默认使用的是 systemd 系统管理器&#xff0c;但传统的 SysV Init&#xff08;/etc/init.d/&#xff09;脚本依然兼容并可用。本文将介绍如何通过 init.d 写脚本来在开机时自动设置某个 GPIO&#xff08;如 GPIO407&#xff09;为高电平&#xff0c;适用于嵌入式系统…...

2024 睿抗机器人开发者大赛CAIP-编程技能赛-本科组(国赛) 解题报告 | 珂学家

前言 题解 2024 睿抗机器人开发者大赛CAIP-编程技能赛-本科组(国赛)。 国赛比省赛难一些&#xff0c;做得汗流浃背&#xff0c;T_T. RC-u1 大家一起查作弊 分值: 15分 这题真的太有意思&#xff0c;看看描述 在今年的睿抗比赛上&#xff0c;有同学的提交代码如下&#xff1…...

【生成式AI文本生成实战】从GPT原理到企业级应用开发

目录 &#x1f31f; 前言&#x1f3d7;️ 技术背景与价值&#x1fa79; 当前技术痛点&#x1f6e0;️ 解决方案概述&#x1f465; 目标读者说明 &#x1f9e0; 一、技术原理剖析&#x1f4ca; 核心概念图解&#x1f4a1; 核心作用讲解&#x1f527; 关键技术模块说明⚖️ 技术选…...

【目标检测】RT-DETR

DETRs Beat YOLOs on Real-time Object Detection DETR在实时目标检测任务中超越YOLO CVPR 2024 代码地址 论文地址 0.论文摘要 YOLO系列因其在速度与精度间的均衡权衡&#xff0c;已成为实时目标检测领域最受欢迎的框架。然而我们观察到&#xff0c;非极大值抑制&#xf…...

数据库行业竞争加剧,MySQL 9.3.0 企业版开始支持个人下载

最新发现&#xff0c;Oracle 官方网站放开了 MySQL 9.3.0 企业版下载链接&#xff0c;个人用户也可以免费下载&#xff0c;不过只能用于学习、开发或者原型测试&#xff0c;不能用于生产环境。 通常我们都是下载 MySQL 社区版&#xff0c;不过 MySQL 企业版可以支持更多高级功能…...

QMK宏全面实战教程:从入门到精通(附17个实用案例)(理论部分)

🎯 QMK宏全面实战教程:从入门到精通(附17个实用案例) 大家好!作为一名机械键盘DIY爱好者和QMK固件深度玩家,今天我要带大家彻底掌握QMK宏的使用技巧!无论你是刚接触机械键盘的新手,还是想提升定制化水平的老玩家,这篇包含17个实战案例的教程都能满足你的需求! 🔍…...

H3C网络设备(交换机、路由器、防火墙)常用命令整理

H3C网络设备&#xff08;交换机、路由器、防火墙&#xff09;的常用命令整理。 一、H3C交换机常用命令 1. 基础操作 命令说明system-view进入系统视图quit返回上一级视图save保存配置display current-configuration查看当前配置&#xff08;类似 show run&#xff09;display…...