java 面试设计模式专题

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
可观测可视化 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() {}  // 重写父类方法
}
目录
相关文章
|
20小时前
|
设计模式 算法 Java
Java的前景如何,好不好自学?,万字Java技术类校招面试题汇总
Java的前景如何,好不好自学?,万字Java技术类校招面试题汇总
|
20小时前
|
存储 网络协议 前端开发
es集群安装,邮储银行java面试
es集群安装,邮储银行java面试
|
20小时前
|
消息中间件 JSON Java
十五,java高级程序员面试宝典
十五,java高级程序员面试宝典
|
20小时前
|
NoSQL 算法 Java
【redis源码学习】持久化机制,java程序员面试算法宝典pdf
【redis源码学习】持久化机制,java程序员面试算法宝典pdf
|
21小时前
|
消息中间件 Java Kafka
Java大文件排序(有手就能学会),kafka面试题2024
Java大文件排序(有手就能学会),kafka面试题2024
|
22小时前
SpringJDK动态代理实现,2024Java面试真题精选干货整理
SpringJDK动态代理实现,2024Java面试真题精选干货整理
|
23小时前
|
安全 前端开发 Java
Java岗大厂面试百日冲刺 - 日积月累,每日三题【Day15
Java岗大厂面试百日冲刺 - 日积月累,每日三题【Day15
|
1天前
|
Java
阅读《代码整洁之道》总结(1),java多线程面试
阅读《代码整洁之道》总结(1),java多线程面试
|
2天前
|
存储 Java
面试官:素有Java锁王称号的‘StampedLock’你知道吗?我:这什么鬼?
面试官:素有Java锁王称号的‘StampedLock’你知道吗?我:这什么鬼?
44 23
|
1天前
|
NoSQL Dubbo Java
StringBoot编程式事务与声明式事务java工程师面试突击第一季
StringBoot编程式事务与声明式事务java工程师面试突击第一季