• 极客专栏正式上线!欢迎访问 https://www.jikewenku.com/topic.html
  • 极客专栏正式上线!欢迎访问 https://www.jikewenku.com/topic.html

Java后端开发面试问题总结(下4)

技术杂谈 勤劳的小蚂蚁 3个月前 (01-08) 99次浏览 已收录 0个评论 扫描二维码

本文主要涉及下面10个【SSM】问题以及3个【操作系统】问题以及一些【其他】问题:

  • SSM
    • 1、Spring有什么好处(特性),怎么管理对象的?
    • 2、什么是IOC?
    • 3、什么是DI?DI的好处是什么?
    • 4、什么是AOP,AOP的好处?
    • 5、AOP的实现原理:Spring AOP使用的动态代理。
    • 6、Spring的生命周期?
    • 7、Spring的配置方式,如何装配bean?bean的注入方法有哪些?
    • 8、bean的作用域?
    • 9、Spring中涉及到哪些设计模式?
    • 10、MyBatis和Hibernate的区别和应用场景?
  • 海量数据处理
  • 操作系统
    • 1、Linux中怎么查看CPU使用率、内存、磁盘、进程?
    • 2、写一个LRU?
    • 3、地址总线和数据总线分别影响了计算机的什么?
  • 其他
    • 1、你说你用到了线性回归,怎么理解回归的意思?
    • 2、一个c/c++程序是怎么从代码到可执行文件的?
    • 3、一致性哈希了解吗?
    • 4、虚拟机VM和Docker的区别?
    • 5、写一个观察者模式?

SSM

1、Spring有什么好处(特性),怎么管理对象的?

  • IOC:Spring的IOC容器,将对象之间的创建和依赖关系交给Spring,降低组件之间的耦合性。即Spring来控制对象的整个生命周期。其实就是平常说的DI或者IOC。
  • AOP:面向切面编程。可以将应用各处的功能分离出来形成可重用的组件。核心业务逻辑与安全、事务、日志等这些非核心业务逻辑分离,使得业务逻辑更简洁清晰。
  • 使用模板消除了样板式的代码。比如使用JDBC访问数据库。
  • 提供了对像关系映射(ORM)、事务管理、远程调用和Web应用的支持。
Spring使用IOC容器创建和管理对象,比如在XML中配置了类的全限定名,然后Spring使用反射+工厂来创建Bean。BeanFactory是最简单的容器,只提供了基本的DI支持,ApplicationContext基于BeanFactory创建,提供了完整的框架级的服务,因此一般使用应用上下文。

2、什么是IOC?

IOC(Inverse of Control)即控制反转。可以理解为控制权的转移。传统的实现中,对象的创建和依赖关系都是在程序进行控制的。而现在由Spring容器来统一管理、对象的创建和依赖关系,控制权转移到了Spring容器,这就是控制反转。

3、什么是DI?DI的好处是什么?

DI(Dependency Injection)依赖注入。对象的依赖关系由负责协调各个对象的第三方组件在创建对象的时候进行设定,对象无需自行创建或管理它们的依赖关系。通俗点说就是Spring容器为对象注入外部资源,设置属性值。DI的好处是使得各个组件之间松耦合,一个对象如果只用接口来表明依赖关系,这种依赖可以在对象毫不知情的情况下,用不同的具体类进行替换。
IOC和DI其实是对同一种的不同表述

4、什么是AOP,AOP的好处?

AOP(Aspect-Orientid Programming)面向切面编程,可以将遍布在应用程序各个地方的功能分离出来,形成可重用的功能组件。系统的各个功能会重复出现在多个组件中,各个组件存在于核心业务中会使得代码变得混乱。使用AOP可以将这些多处出现的功能分离出来,不仅可以在任何需要的地方实现重用,还可以使得核心业务变得简单,实现了将核心业务与日志、安全、事务等功能的分离。
具体来说,散布于应用中多处的功能被称为横切关注点,这些横切关注点从概念上与应用的业务逻辑是相分离的,但是又常常会直接嵌入到应用的业务逻辑中,AOP把这些横切关注点从业务逻辑中分离出来。安全、事务、日志这些功能都可以被认为是应用中的横切关注点。
通常要重用功能,可以使用继承或者委托的方式。但是继承往往导致一个脆弱的对像体系;委托带来了复杂的调用。面向切面编程仍然可以在一个地方定义通用的功能,但是可以用声明的方法定义这个功能要在何处出现,而无需修改受到影响的类。横切关注点可以被模块化为特殊的类,这些类被称为切面(Aspect)。好处在于:
  • 每个关注点都集中在一个地方,而非分散在多处代码中;
  • 使得业务逻辑更简洁清晰,因为这样可以只关注核心业务,次要的业务被分离成关注点转移到切面中了。
AOP术语介绍
通知:切面所做的工作称为通知。通知定义了切面是什么,以及在何时使用。Spring切面可以应用5种类型的通知
  • 前置通知(Before):在目标方法被调用之前调用通知功能;
  • 后置通知(After):在目标方法被调用或者抛出异常之后都会调用通知功能;
  • 返回通知(After-returning):在目标方法成功执行之后调用通知;
  • 异常通知(After-throwing):在目标方法抛出异常之后调用通知;
  • 环绕通知(Around):通知包裹了被通知的方法,在目标方法被调用之前和调用之后执行自定义的行为。
连接点:可以被通知的方法
切点:实际被通知的方法
切面:即通知和切点的结合,它是什么,在何时何处完成其功能。
引入:允许向现有的类添加新方法或属性,从而可以在无需修改这些现有的类情况下,让它们具有新的行为和状态。
织入:把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入:
  • 编译期,切面在目标类编译时织入。
  • 类加载期,切面在目标类加载到JVM时被织入。
  • 运行期,切面在应用运行的某个时刻被织入,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面的
Spring AOP构建在动态代理基础之上,所以Spring对AOP的支持仅限于方法拦截。
Spring的切面是由包裹了目标对象的代理类实现的。代理类封装了目标类,并拦截被通知方法的调用,当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。其实切面只是实现了它们所包装bean相同接口的代理

5、AOP的实现原理:Spring AOP使用的动态代理。

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
Spring使用动态代理,代理类封装了目标类,当代理拦截到方法调用时,在调用目标bean的方法之前,会执行切面逻辑。

6、Spring的生命周期?

Spring创建、管理对象。Spring容器负责创建对象,装配它们,配置它们并管理它们的整个生命周期。
  • 实例化:Spring对bean进行实例化
  • 填充属性:Spring将值和bean的引用注入到bean对应的属性中
  • 调用BeanNameAware的setBeanName()方法:若bean实现了BeanNameAware接口,Spring将bean的id传递给setBeanName方法
  • 调用BeanFactoryAware的setBeanFactory()方法:若bean实现了BeanFactoryAware接口,Spring调用setBeanFactory方法将BeanFactory容器实例传入
  • 调用ApplicationContextAware的setApplicationContext方法:如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext方法将bean所在的应用上下文传入
  • 调用BeanPostProcessor的预初始化方法:如果bean实现了BeanPostProcessor,Spring将调用它们的叛postProcessBeforeInitialization方法
  • 调用InitalizingBean的afterPropertiesSet方法:如果bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet方法
  • 如果bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessAfterInitialzation方法
  • 此时bean已经准备就绪,可以被应用程序使用,它们将一直驻留在应用杀死那个下文中,直到该应用的上下文被销毁。
  • 如果bean实现了DisposableBean接口,Spring将调用它的destroy方法。

7、Spring的配置方式,如何装配bean?bean的注入方法有哪些?

  • XML配置,如 <beanid="">
  • Java配置即JavaConfig,使用 @Bean注解
  • 自动装配,组件扫描(component scanning)和自动装配(autowiring), @ComponentScan和 @AutoWired注解
bean的注入方式有:
  • 构造器注入
  • 属性的setter方法注入
推荐对于强依赖使用构造器注入,对于弱依赖使用属性注入。

8、bean的作用域?

  • 单例(Singleton):在整个应用中,只创建bean一个实例。
  • 原型(Prototype):每次注入或通过Spring应用上下文获取时,都会创建一个新的bean实例。
  • 会话(Session):在Web应用中,为每个会话创建一个bean实例。
  • 请求(Request):在Web应用中,为每个请求创建一个bean实例。
默认情况下Spring中的bean都是单例的。

9、Spring中涉及到哪些设计模式?

  • 工厂方法模式。在各种BeanFactory以及ApplicationContext创建中都用到了;
  • 单例模式。在创建bean时用到,Spring默认创建的bean是单例的;
  • 代理模式。在AOP中使用Java的动态代理;
  • 策略模式。比如有关资源访问的Resource类
  • 模板方法。比如使用JDBC访问数据库,JdbcTemplate。
  • 观察者模式。Spring中的各种Listener,如ApplicationListener
  • 装饰者模式。在Spring中的各种Wrapper和Decorator
  • 适配器模式。Spring中的各种Adapter,如在AOP中的通知适配器AdvisorAdapter

10、MyBatis和Hibernate的区别和应用场景?

Hibernate :是一个标准的ORM(对象关系映射) 框架; SQL语句是自己生成的,程序员不用自己写SQL语句。因此要对SQL语句进行优化和修改比较困难。适用于中小型项目。
MyBatis: 程序员自己编写SQL, SQL修改和优化比较自由。 MyBatis更容易掌握,上手更容易。主要应用于需求变化较多的项目,如互联网项目等。

海量数据处理

首先要了解几种数据结构和算法:
  • HashMap,记住对于同一个键,哈希出来的值一定是一样的,不同的键哈希出来也可能一样,这就是发生了冲突(或碰撞)。
  • BitMap,可以看成是bit数组,数组的每个位置只有0或1两种状态。Java中可以使用int数组表示位图,arr[0]是一个int,一个int是32位,故可以表示0-31的数,同理arr[1]可表示32-63…实际上就是用一个32位整型表示了32个数。
  • 大/小根堆,O(1)时间可在堆顶得到最大值/最小值。利用小根堆可用于求Top K问题。
  • 布隆过滤器。使用长度为m的bit数组和k个Hash函数,某个键经过k个哈希函数得到k个下标,将k个下标在bit数组中对应的位置设置为1。对于每个键都重复上述过程,得到最终设置好的布隆过滤器。对于新来的键,使用同样的过程,得到k个下标,判断k个下标在bit数组中的值是否为1,若有一个不为1,说明这个键一定不在集合中。若全为1,也可能不在集合中。就是说:查询某个键,判断不属于该集合是绝对正确的;判断属于该集合是低概率错误的。因为多个位置的1可能是由不同的键散列得到。
对上亿个无重复数字的排序,或者找到没有出现过数字,注意因为无重复数字,而BitMap的0和1正好可以表示该数字有没有出现过。如果要求更小的内存,可以先分出区间,对落入区间的进行计数。必然有的区间数量未满,再遍历一次数组,只看该区间上的数字,使用BitMap,遍历完成后该区间中必然有没被设置成0的的地方,这些地方就是没出现的数。
数据在小范围内波动,比如人类年龄,而且数据允许重复,可用计数排序处理数值排序或查找没有出现过的值,计数的桶中频次为0的就是没有出现过的数。
数据是数字,要找最大的Top K,直接用大小为K的小根堆,不断淘汰最小元素即可。
数据是数字或非数字,要找频次最高的Top K。可使用HashMap统计频次,统计出频次最大的前K个即可。统计频次选出前K的过程可以用小根堆。还可以用Hash分流的方法,即用一个合适的hash函数将数据分到不同的机器或者文件中,因为对于同样的数据,由于hash函数的性质,必然被分配到同一个文件中,因此不存在相同的数据分布在不同的文件这种情况。对每个文件采用HashMap统计频次,用小根堆选出Top K,然后汇总全部文件,从所有部分结果的Top K中再利用小根堆得到最终的Top K。
查找数值的排名,比如找到中位数。比如将数划分区间,对落入每个区间的数进行计数。然后可以得知中位数落在哪个区间,再遍历所有数,这次只关心落在该区间的数,不划分区间的对其进行计数,就可以找出中位数。

操作系统

1、Linux中怎么查看CPU使用率、内存、磁盘、进程?

使用 top命令,如下第三行显示了CPU使用率。
  1. top - 17:35:12 up 5 min,  1 user,  load average: 0.05, 0.16, 0.09
  2. Tasks: 141 total,   1 running, 105 sleeping,   0 stopped,   0 zombie
  3. %Cpu(s):  3.5 us,  1.7 sy,  0.0 ni, 94.8 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
  4. KiBMem :  3073524 total,  1547424 free,   528024 used,   998076 buff/cache
  5. KiBSwap:  4194304 total,  4194304 free,        0 used.  2316492 avail Mem

  6. PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+  
  7. 585 root      20   0  978032  91332  44924 S  2.0  3.0   0:03.97Xorg      
  8. 2129 sun       20   0  533920  39272  30000 S  1.7  1.3   0:01.95 deepin-termin+
  9.  911 sun       20   0  796128  24480  19976 S  1.0  0.8   0:00.94 dde-session-i+
  10.  330 root      20   0  629996  17244  13288 S  0.7  0.6   0:00.38 dde-system-da+
  11.  751 sun       20   0  118800   2184   1796 S  0.3  0.1   0:00.90VBoxClient      
%CPU:进程占用CPU的使用
%MEM:进程使用的物理内存和总内存的百分
还可以使用 free命令,简单查看内存状况
  1.              total        used        free      shared  buff/cache   available
  2. Mem:        3073524      536724     1603808       75300      932992     2302016
  3. Swap:       4194304           0     4194304
使用 df-h查看磁盘使用情况。
  1. 文件系统        容量  已用  可用 已用% 挂载点
  2. udev            1.5G     0  1.5G    0% /dev
  3. tmpfs           301M  1.2M  299M    1% /run
  4. /dev/sda1        30G   20G  8.2G   71% /
  5. tmpfs           1.5G     0  1.5G    0% /dev/shm
  6. tmpfs           5.0M  4.0K  5.0M    1% /run/lock
  7. tmpfs           1.5G     0  1.5G    0% /sys/fs/cgroup
  8. vmshare         238G  193G   46G   81% /mnt/work
  9. tmpfs           301M   12K  301M    1% /run/user/1000
  10. /dev/sr0         56M   56M     0  100% /media/sun/VBox_GAs_5.2.12
使用 ps–ef 查看进程的状态。
  1. UID        PID  PPID  C STIME TTY          TIME CMD
  2. root         1     0  015:56 ?        00:00:01 /sbin/init splash
  3. root         2     0  015:56 ?        00:00:00 [kthreadd]
  4. root         4     2  015:56 ?        00:00:00 [kworker/0:0H]
  5. root         6     2  015:56 ?        00:00:00 [mm_percpu_wq]
  6. root         7     2  015:56 ?        00:00:00 [ksoftirqd/0]
  7. root         8     2  015:56 ?        00:00:00 [rcu_sched]

2、写一个LRU?

Least Recently Used(LRU),即最近最少使用页面置换算法。选择最长时间没有被引用的页面进行置换,思想是:如果一个页面很久没有被引用到,那么可以认为在将来该页面也很少被访问到。
当发生缺页(CPU要访问的页不在内存中),计算内存中每个页上一次被访问的时间,置换上次使用到当前时间最长的一个页面。
如何实现?可以使用双向链表+哈希表的方式
维护一个按最近一次访问时间排序的页面链表。
  • 链表头结点是最近刚刚访问过的页面
  • 链表尾结点是最久未被访问的页面
HashMap主要是为了判断是否命中缓存。
访问内存时,若命中缓存,找到响应的页面,将其移动到链表头部,表示该页面是最近刚刚访问的。缺页时,将链表尾部的页面移除,同时新页面放到链表头。
  1. import java.util.HashMap;
  2. import java.util.LinkedList;

  3. publicclassLRUCache2<K,V> {
  4.    privatefinalint cacheSize;
  5.    privateLinkedList<K> cacheList = newLinkedList<>();
  6.    privateHashMap<K,V> map = newHashMap<>();

  7.    publicLRUCache2(int cacheSize) {
  8.        this.cacheSize = cacheSize;
  9.    }

  10.    publicsynchronizedvoid put(K key, V val) {
  11.        if (!map.containsKey(key)) {
  12.            if (map.size() >= cacheSize) {
  13.                removeLastElement();
  14.            }
  15.            cacheList.addFirst(key);
  16.            map.put(key, val);
  17.        } else {
  18.            moveToFirst(key);
  19.        }
  20.    }

  21.    public V get(K key) {
  22.        if (!map.containsKey(key)) {
  23.            returnnull;
  24.        }
  25.        moveToFirst(key);
  26.        return map.get(key);
  27.    }

  28.    privatesynchronizedvoid moveToFirst(K key) {
  29.        cacheList.remove(key);
  30.        cacheList.addFirst(key);
  31.    }

  32.    privatesynchronizedvoid removeLastElement() {
  33.        K key = cacheList.removeLast();
  34.        map.remove(key);
  35.    }

  36.    @Override
  37.    publicString toString() {
  38.        return cacheList.toString();
  39.    }

  40.    publicstaticvoid main(String[] args) {
  41.        LRUCache2<String,String> lru = newLRUCache2<>(4);
  42.        lru.put("C", null);
  43.        lru.put("A", null);
  44.        lru.put("D", null);
  45.        lru.put("B", null);
  46.        lru.put("E", null);
  47.        lru.put("B", null);
  48.        lru.put("A", null);
  49.        lru.put("B", null);
  50.        lru.put("C", null);
  51.        lru.put("D", null);

  52.        System.out.println(lru);
  53.    }
  54. }

  55. /* out:
  56. [D, C, B, A]
  57. */
Java的LinkedHashMap是双向链表和HashMap的结合。可以直接用其搞定,构造方法设置参数 accessOrder=true即可按访问顺序排序。而 removeEldestEntry就是用来删除最久未被使用的结点。
  1. import java.util.LinkedHashMap;
  2. import java.util.Map;

  3. publicclassLRUCache<K,V> extendsLinkedHashMap<K, V> {
  4.    staticfinalfloat LOAD_FACTOR = 0.75f;
  5.    staticfinalboolean ACCESS_ORDER = true;
  6.    privateint cacheSize;
  7.    publicLRUCache(int initialCapacity, int cacheSize) {
  8.        super(initialCapacity, LOAD_FACTOR, ACCESS_ORDER);
  9.        this.cacheSize = cacheSize;
  10.    }

  11.    @Override
  12.    protectedsynchronizedboolean removeEldestEntry(Map.Entry eldest) {
  13.        return size() > cacheSize;
  14.    }

  15.    @Override
  16.    public V get(Object key) {
  17.        returnsuper.get(key);
  18.    }

  19.    @Override
  20.    publicsynchronized V put(K key, V value) {
  21.        returnsuper.put(key, value);
  22.    }

  23.    publicstaticvoid main(String[] args) {
  24.        LRUCache<String, String> lru = newLRUCache<>(4, 4);
  25.        lru.put("C", null);
  26.        lru.put("A", null);
  27.        lru.put("D", null);
  28.        lru.put("B", null);
  29.        lru.put("E", null);
  30.        lru.put("B", null);
  31.        lru.put("A", null);
  32.        lru.put("B", null);
  33.        lru.put("C", null);
  34.        lru.put("D", null);

  35.        System.out.println(lru);
  36.    }
  37. }
上面打印是{A, B, C, D}这样的顺序,倒过来就行了(我也不知道为啥要这样)。

3、地址总线和数据总线分别影响了计算机的什么?

  • 地址总线决定了计算机的寻址能力(范围),比如32位的寻址能力就是4GB
  • 数据总线决定每次传输数据的大小

其他

1、你说你用到了线性回归,怎么理解回归的意思?

线性回归的英文是Regression toward the mean,直译就是”回归到均值”。各种数据都有其平均值,比如人类的身高。高的人大概率孩子也高,矮的人大概率孩子也矮,但是高的人身高每增加一个单位,其孩子可能只能增加半个单位;矮的人身高每减少一个单位,其孩子可能只会减少半个单位。也就是子代的平均高度会向着一个中心趋势靠拢,这就是回归到均值。
回归问题等价于函数拟合,线性回归也就是用线性的函数去拟合所有观测数据,利用最小二乘法使得误差(预测值和真实值差值的平方和)最小化。对于新的观测数据,使用该函数计算得到一个预测值。

2、一个c/c++程序是怎么从代码到可执行文件的?

额,面试官是C++的吧,然而我面Java。
推荐这篇博客:
主要经历以下步骤:
  • 预处理
  • 编译
  • 汇编
  • 链接
编译的过程又分为以下几步:词法分析,语法分析,语义分析,源代码优化,代码生成和目标代码优化
然后链接的过程如下

3、一致性哈希了解吗?

推荐阅读这篇博客:
这是一种分布式数据缓存的方案,常用于负载均衡。解决将许多缓存均匀分布到各个服务器上的问题。
  • 首先求出服务器(节点)的哈希值,一致性哈希算法将哈希值对 2^32取模,将服务器配置到0~2^32的圆上。
  • 然后采用同样的方法求出存储数据的键的哈希值,并映射到相同的圆上。
  • 然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。如果超过2^32仍然找不到服务器,就会保存到第一台服务器上。
优点:
  • 在删除服务器结点后,失效的缓存只是一部分,不会影响到所有缓存
  • 新增服务器结点,缓存迁移的代价很小
但是有时会出现服务器hash得不均匀的情况,这样有大量的缓存都存在一个服务器中。
这种情况可以通过设置虚拟节点,如图中淡蓝色中的A、B、C就是虚拟结点。添加虚拟结点后,1、3被分到A中,4、5分到B中,2、6被分到C中,分布就很均匀了。

4、虚拟机VM和Docker的区别?

Docker容器不是虚拟机。
容器和 VM(虚拟机)的主要区别是,容器提供了基于进程的隔离,而虚拟机提供了资源的完全隔离。
虚拟机的启动比容器慢很多,虚拟机可能要一分钟来启动,但是容器只需要几秒甚至不到一秒。
容器使用宿主操作系统的内核,而虚拟机使用独立的内核。Docker使用的是Linux容器(LinuX Container, 简称LXC),跟其宿主运行同样的操作系统。多个容器之间是共用同一套操作系统资源的。

5、写一个观察者模式?

先定义主题和观察者的接口。
  1. publicinterfaceSubject {
  2.    void resisterObserver(Observer o);

  3.    void removeObserver(Observer o);

  4.    void notifyObserver();
  5. }

  6. publicinterfaceObserver {
  7.    void update(float temp, float humidity, float pressure);
  8. }
然后是实现类,这里以Head First里经典的气象站为例。
  1. publicclassWeatherDataimplementsSubject {
  2.    privateList<Observer> observers;
  3.    privatefloat temperature;
  4.    privatefloat humidity;
  5.    privatefloat pressure;

  6.    publicWeatherData() {
  7.        observers = newArrayList<>();
  8.    }

  9.    publicvoid setMeasurements(float temperature, float humidity, float pressure) {
  10.        this.temperature = temperature;
  11.        this.humidity = humidity;
  12.        this.pressure = pressure;
  13.        notifyObserver();
  14.    }

  15.    @Override
  16.    publicvoid resisterObserver(Observer o) {
  17.        observers.add(o);
  18.    }

  19.    @Override
  20.    publicvoid removeObserver(Observer o) {
  21.        int i = observers.indexOf(o);
  22.        if (i >= 0) {
  23.            observers.remove(i);
  24.        }
  25.    }

  26.    @Override
  27.    publicvoid notifyObserver() {
  28.        for (Observer o : observers) {
  29.            o.update(temperature, humidity, pressure);
  30.        }
  31.    }
  32. }
  1. /* 观察者1 */
  2. publicclassStatisticsDisplayimplementsObserver {

  3.    publicStatisticsDisplay(Subject weatherData) {
  4.        weatherData.resisterObserver(this);
  5.    }

  6.    @Override
  7.    publicvoid update(float temp, float humidity, float pressure) {
  8.        System.out.println("StatisticsDisplay.update: " + temp + " " + humidity + " " + pressure);
  9.    }
  10. }
  1. /* 观察者2 */
  2. publicclassCurrentConditionsDisplayimplementsObserver {

  3.    publicCurrentConditionsDisplay(Subject weatherData) {
  4.        weatherData.resisterObserver(this);
  5.    }

  6.    @Override
  7.    publicvoid update(float temp, float humidity, float pressure) {
  8.        System.out.println("CurrentConditionsDisplay.update: " + temp + " " + humidity + " " + pressure);
  9.    }
  10. }
  1. /* 测试类 */
  2. publicclassWeatherStation {
  3.    publicstaticvoid main(String[] args) {
  4.        WeatherData weatherData = newWeatherData();
  5.        CurrentConditionsDisplay currentConditionsDisplay = newCurrentConditionsDisplay(weatherData);
  6.        StatisticsDisplay statisticsDisplay = newStatisticsDisplay(weatherData);

  7.        weatherData.setMeasurements(0, 0, 0);
  8.        weatherData.setMeasurements(1, 1, 1);
  9.    }
  10. }




丨极客文库, 版权所有丨如未注明 , 均为原创丨
本网站采用知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行授权
转载请注明原文链接:Java后端开发面试问题总结(下4)
喜欢 (0)
[247507792@qq.com]
分享 (0)
勤劳的小蚂蚁
关于作者:
温馨提示:本文来源于网络,转载文章皆标明了出处,如果您发现侵权文章,请及时向站长反馈删除。

您必须 登录 才能发表评论!

  • 精品技术教程
  • 编程资源分享
  • 问答交流社区
  • 极客文库知识库

客服QQ


QQ:2248886839


工作时间:09:00-23:00