java 面试设计模式专题

本文涉及的产品
应用实时监控服务-应用监控,每月50GB免费额度
应用实时监控服务-用户体验监控,每月100OCU免费额度
可观测可视化 Grafana 版,10个用户账号 1个月
简介: 设计模式专题

JAVA SPI设计模式之策略模式文字版主页有视频-腾讯云开发者社区-腾讯云 (tencent.com)

请列举出在 JDK 中几个常用的设计模式?

JDK中常用的设计模式有:

  1. 工厂模式:java.util.Calendar、java.util.ResourceBundle 等

calendar.getInstance() 为工厂方法,根据参数返回不同的Calendar实现。ResourceBundle.getBundle() 也是典型的工厂方法。

  1. 单例模式:java.lang.Runtime 等

Runtime通过私有构造器和getInstance()静态方法实现单例。

  1. 适配器模式:java.util.Arrays#asList() 等

Arrays#asList()把数组适配成List接口,是适配器模式的典型实现。

  1. 代理模式:java.lang.reflect.Proxy 等

Proxy类提供了创建动态代理的方法,是代理模式的体现。

  1. 命令模式:java.lang.Runnable 等

Runnable接口体现了命令模式,实现了命令与执行者之间的解耦。

  1. 迭代器模式:java.util.Iterator 等

各个集合类的iterator()方法都返回一个迭代器,用于遍历集合元素,是迭代器模式的实现。

  1. 观察者模式:java.util.Observer/Observable 等

Observer接口和Observable类实现了观察者模式,用于观察者和被观察者之间的订阅关系。

  1. 装饰器模式:java.io.InputStream/OutputStream等

InputStream/OutputStream的子类体现了装饰器模式,通过Decorator对组件进行装饰。

  1. 模板方法模式:java.util.Collections#sort() 等

Collections#sort()提供了一个模板方法,由子类实现比较规则,体现了模板方法模式。

还有建造者模式、责任链模式、策略模式、状态模式等在JDK中也有体现。JDK作为业界成熟的Java类库,其设计具有很高的参考价值。学习JDK的设计模式,有助于我们在实际工作中运用设计模式和设计高质量的系统。

设计模式已经成为软件工程师和架构师必备的技能,需要我们在实际工作中灵活运用,并不断总结和发展。熟练掌握设计模式有助我们成为高效的工程师和架构师。

工厂模式

java
Calendar calendar = Calendar.getInstance();

单例模式

java
Runtime runtime = Runtime.getInstance();

适配器模式

java
List<Integer> list = Arrays.asList(1, 2, 3);

代理模式

java
Subject subject = (Subject) Proxy.newProxyInstance(
    Subject.class.getClassLoader(),
    new Class[] { Subject.class },
    new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // TODO 代理方法实现
        }
    } 
);

命令模式

java 
public class Command implements Runnable {
    @Override
    public void run() {
        // TODO 命令执行
    }
}

迭代器模式

java
List<Integer> list = Arrays.asList(1, 2, 3);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
    Integer num = iterator.next();
    // TODO 
}

观察者模式

java
public class Subject {
    private List<Observer> observers = new ArrayList<>();
    
    public void addObserver(Observer observer) {
        observers.add(observer);
    }
    
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

public class Observer {
    public void update() {
        // TODO 更新逻辑
    }
} 

装饰器模式

java
public abstract class Component {
    public abstract void operate();
}

public class ConcreteComponent extends Component {
    @Override
    public void operate() {
        // TODO 具体对象逻辑
    }
}

public class Decorator extends Component {
    private Component component;

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void operate() {
        this.component.operate();
        // TODO 装饰方法执行
    } 
}

这仅为举例,更多设计模式实例可以参考JDK源码。理解并应用设计模式需要我们靠实践积累经验。

Java中什么叫单例设计模式?请用Java写出线程安全的单例模式

单例设计模式(Singleton Pattern)确保一个类只有一个实例,并提供一个全局访问点。线程安全的单例模式在Java中可以这么实现:

java
public class Singleton {
    private volatile static Singleton instance;
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

这是双重检查锁定的实现,它具有以下特点:1. instance变量被volatile修饰,保证多个线程对这个变量的可见性。2. 首先检查instance非空,如果非空直接返回,这是第一次检查。3. 如果instance为空,进行同步,这是第二次检查。4. 在同步块内再次判断instance是否为空,如果为空才进行实例化。5. 同步块保证只有一个线程能够实例化instance。这种实现既保证了线程安全,也尽量减小了同步带来的性能影响。实例代码:

java
public class Test {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton1 == singleton2); // true
    }
}

运行结果:

true

这证明Singleton类确实起到了单例的效果,各处获取到的实例都是同一个对象。单例模式有很多实用场景,如日志对象、配置对象等。但如果过度使用也会带来一定的问题,我们需要根据实际需求选择合适的模式。单例模式作为最常用的设计模式之一,是每个工程师必备的技能。

观察者设计模式(observerdesignpattern)

观察者设计模式(Observer Pattern)定义对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。它包含以下两个角色:1. Subject:也称为被观察者,它实现一个登记观察者和通知观察者的接口。

  1. Observer:也称为观察者,它实现一个更新接口用来响应Subject的通知。观察者模式的目的是实现发布-订阅模型。观察者模式的实例代码:

Subject.java:

java
public class Subject {
    private List<Observer> observers = new ArrayList<>();
    
    public void addObserver(Observer observer) {
        observers.add(observer);
    }
    
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

Observer.java:

java 
public class Observer {
    public void update() {
        // TODO 响应Subject的通知,更新自身
    }
}

Test.java:

java
public class Test {
    public static void main(String[] args) {
        Subject subject = new Subject();
        
        Observer observer1 = new Observer();
        Observer observer2 = new Observer();
        
        subject.addObserver(observer1);
        subject.addObserver(observer2);
        
        subject.notifyObservers();
    } 
}

当Subject调用notifyObservers()方法时,所有的Observer观察者都会收到update()通知,并作出响应。观察者模式的应用场景很广,例如事件驱动系统、新闻系统提醒等。理解观察者模式有助于我们设计出松耦合的系统。观察者模式虽然简单,但使用并不一定简单。我们在使用时,要考虑:1. 何时向Subject添加Observer

  1. 观察者处理逻辑的完成度
  2. Subject和Observer的生命周期问题等所以熟练使用观察者模式需要较长时间的实践和总结

使用工厂模式最主要的好处是什么?在哪里使用?给出示例代码


工厂模式的主要好处是:

  1. 降低类与类之间的耦合度。工厂模式可以将类与类之间的依赖关系解耦,用户只需要依赖工厂类。
  2. 方便后期替换和维护。由于用户只与工厂类有关联,所以替换或增加新的产品就很容易了,无须修改原来的代码。这提高了系统的可扩展性和维护性。
  3. 可以进行细粒度的控制。工厂可以根据不同的参数返回不同的产品,这种灵活性提高了系统的适应性。

工厂模式适用于以下场景:

  1. 一个类不知道它所需要的对象的类。
  2. 一个类希望由其子类来指定它所需要的对象的类。
  3. 如果创建一个对象需要很长的代码,并且是重复的,那么可以使用工厂模式进行简化。 
  4. 当一个产品有多个版本或类型时,可以使用工厂方法创建不同的产品对象。

示例代码:

java
public interface Product {
    void show();
}

public class ConcreteProduct1 implements Product {
    @Override
    public void show() {
        System.out.println("产品1");
    }
}

public class ConcreteProduct2 implements Product {
    @Override
    public void show() {
        System.out.println("产品2");
    }
}

public class Factory {
    public static Product makeProduct(int kind) {
        switch (kind) {
            case 1:
                return new ConcreteProduct1();
            case 2:
                return new ConcreteProduct2();
            default:
                return null;
        }
    }
}


public class Client {
    public static void main(String[] args) {
        Product p1 = Factory.makeProduct(1);
        p1.show();
        
        Product p2 = Factory.makeProduct(2);
        p2.show();
    } 
}

运行结果:

产品1
产品2

这是一个简单的工厂模式示例。Client代码只依赖Factory工厂类,无须关心创建哪一种产品,这实现了解耦。

工厂模式作为最常用的设计模式之一,需要我们熟练掌握和运用

什么是Java实现的装饰模式(decoratordesignpattern)?它是作用于对象层次还是类层次?

装饰器模式(Decorator Pattern)是一种结构型设计模式,它通过将对象装饰来扩展对象的行为。它是作用于对象层次的。

装饰器模式的主要思想是:动态地扩展一个对象的行为。它通过创建一个装饰类来包装真实的对象。装饰类可以增加真实对象的新行为。它继承真实对象并包含它的实例。装饰器模式的关键在于正确的抽象出各个装饰对象和被装饰对象的共同点。通常我们会定义一个抽象的Component类和多个具体的ConcreteComponent类,以及一个抽象的Decorator类和多个具体的ConcreteDecorator类。以咖啡为例,装饰器模式的实现可以是:

// 组件接口
public interface Beverage {
    double getCost(); 
    String getDescription();
}

// 具体组件
public class Espresso implements Beverage {
    public double getCost() {
        return 1.99; 
    }
    public String getDescription() {
        return "Espresso";
    }
}

// 抽象装饰器  
public abstract class BeverageDecorator implements Beverage {
    protected Beverage beverage;

    public BeverageDecorator(Beverage beverage) {
        this.beverage = beverage;
    }
    
    public double getCost() {
        return beverage.getCost();
    }
    
    public String getDescription() {
        return beverage.getDescription(); 
    }
}

// 具体装饰器1 
public class Milk extends BeverageDecorator {
    public Milk(Beverage beverage) {
        super(beverage);
    }

    @Override
    public double getCost() {
        return super.getCost() + 0.5;
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", Milk"; 
    }
}

// 具体装饰器2
public class Whip extends BeverageDecorator {
    public Whip(Beverage beverage) {
        super(beverage);
    }

    @Override
    public double getCost() {
        return super.getCost() + 0.7; 
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", Whip"; 
    }
}  

我们定义了一个Beverage接口和Espresso类作为组件,以及BeverageDecorator作为抽象装饰器,Milk和Whip作为具体装饰器。各个装饰器可以装饰Espresso并增加不同的描述和价格。所以装饰器模式是作用于对象( Beverage)层次的,动态地为对象新增职责。而不是作用于类(Espresso)层次。

为什么不允许从静态方法中访问非静态变量

在Java中,静态方法和非静态变量/方法属于不同的作用域。静态方法属于类作用域,可以访问静态变量和静态方法。非静态变量和方法属于实例作用域,只能在实例方法中访问。

主要原因有两点:

  1. 非静态变量是属于实例的,每一个实例都有自己的非静态变量的副本。如果允许从静态方法中访问非静态变量,这个变量究竟属于哪一个实例就不清晰了。这会引起逻辑错误和语义上的混乱。
  2. 静态方法在类加载的时候就已经存在了,它不依赖任何实例。但是非静态变量只有在实例创建之后才被初始化。所以如果从静态方法中访问非静态变量,很可能这个变量还没初始化,会引起NullPointerException。

例如:

public class Test {
    private int num;  // 非静态变量
    
    public static void increment() {
        num++;  // 编译错误,无法从静态方法访问非静态变量
    }
}

这个例子中,num是非静态变量,只有在Test的实例创建后才会被初始化。但是increment()方法是静态方法,在类加载时就存在,并不依赖任何实例。所以如果允许这样的代码,很有可能在调用increment()的时候num还未初始化,导致NullPointerException。

所以总结来说,不允许从静态方法中访问非静态变量的原因是:

1) 语义混乱:非静态变量属于实例,不清晰该访问哪个实例的变量

2) 存在空指针风险:静态方法在类加载时就存在,很可能访问的非静态变量还未被初始化

这是Java在设计时的一个很重要的decision,以保证语义清晰和避免空指针异常。

在 Java 中,什么时候用重载,什么时候用重写?

在Java中,重载(overload)和重写(override)是两个非常相似而又不同的概念。正确理解并运用它们是成为一名合格的Java程序员的必要条件。重载(overload)发生在同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。返回值不同并不算是重载。重写(override)发生在子类中,指子类提供的一个方法与父类中的一个方法有相同的方法名称、参数列表、返回值。子类方法的访问权限不能低于父类方法。重载的注意事项:1. 发生在同一个类中

  1. 方法名称相同
  2. 参数列表不同(参数类型不同、个数不同、顺序不同)
  3. 返回值可以相同也可以不同
  4. 方法访问权限可以相同也可以不同重写的注意事项:1. 发生在父类与子类间
  5. 方法名称相同
  6. 参数列表相同
  7. 返回值相同
  8. 方法访问权限不能低于父类方法所以总结来说:重载:在同一个类中,方法名称相同,参数不同,用于实现一种方法的多种形式。

重写:子类中实现父类的方法,用于实现方法的特定实现形式。举例来说:
重载:


public void test() {}
public void test(int a) {}
public void test(String s) {}

重写:


public class Father {
    public void test() {}
}

public class Son extends Father {
    @Override
    public void test() {}  // 重写父类方法
}
目录
相关文章
|
29天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
66 2
|
17天前
|
Java 程序员
Java社招面试题:& 和 && 的区别,HR的套路险些让我翻车!
小米,29岁程序员,分享了一次面试经历,详细解析了Java中&和&&的区别及应用场景,展示了扎实的基础知识和良好的应变能力,最终成功获得Offer。
45 14
|
28天前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
1月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
17天前
|
设计模式 消息中间件 搜索推荐
Java 设计模式——观察者模式:从优衣库不使用新疆棉事件看系统的动态响应
【11月更文挑战第17天】观察者模式是一种行为设计模式,定义了一对多的依赖关系,使多个观察者对象能直接监听并响应某一主题对象的状态变化。本文介绍了观察者模式的基本概念、商业系统中的应用实例,如优衣库事件中各相关方的动态响应,以及模式的优势和实际系统设计中的应用建议,包括事件驱动架构和消息队列的使用。
|
22天前
|
Java 编译器 程序员
Java面试高频题:用最优解法算出2乘以8!
本文探讨了面试中一个看似简单的数学问题——如何高效计算2×8。从直接使用乘法、位运算优化、编译器优化、加法实现到大整数场景下的处理,全面解析了不同方法的原理和适用场景,帮助读者深入理解计算效率优化的重要性。
27 6
|
27天前
|
设计模式 Java 数据库连接
Java编程中的设计模式:单例模式的深度剖析
【10月更文挑战第41天】本文深入探讨了Java中广泛使用的单例设计模式,旨在通过简明扼要的语言和实际示例,帮助读者理解其核心原理和应用。文章将介绍单例模式的重要性、实现方式以及在实际应用中如何优雅地处理多线程问题。
32 4
|
1月前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
53 4
|
1月前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
105 4
|
1月前
|
设计模式 JavaScript Java
Java设计模式:建造者模式详解
建造者模式是一种创建型设计模式,通过将复杂对象的构建过程与表示分离,使得相同的构建过程可以创建不同的表示。本文详细介绍了建造者模式的原理、背景、应用场景及实际Demo,帮助读者更好地理解和应用这一模式。