目录
2.请你解释一下,内存中的栈(stack)、堆(heap) 和静态区(static area) 的用法。
3.在开发过程你用到过那些设计模式?用在什么场合?除单例模式外写出其他一种你常用的设计模式?
简答题:
1.请你谈一下面向对象的"六原则一法则"。
六原则主要指单一职责原则、开闭原则、依赖倒转原则、里氏替换原则、接口隔离原则、合成聚合复用原则。
-
单一职责原则:一个类只做它该做的事情,不要赋予过多的功能。
-
开闭原则:软件实体应当对拓展开放,对修改关闭。当我们需要为一个软件系统增加新功能时,只需要从原来的系统派生出一些新类就可以,不需要修改原来的任何一行代码。要做到开闭有两个要点:
-
系统需要有抽象类或接口
-
封装可变性,将系统中的各种可变因素封装到一个继承结构中。
-
-
依赖倒转原则:面向接口编程。在定义方法时,尽量使用抽象类型而不使用具体类型,因为抽象类可以被他的任何子类代替。
-
里氏替换原则:任何时候都可以用子类型替换掉父类型。简单的说就是能用父类型的地方就一定能使用子类型。
-
作用:里氏替换原则可以检查继承关系是否合理,如果一个继承关系违背了里氏替换原则,那么这个继承关系一定是错误的,需要对代码进行重构。
-
需要注意的是:子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用当然没有任何问题
-
-
接口隔离原则:接口要小而专,不能大而全。
-
合成聚合复用原则:类与类之间简单的说有三种关系,Is-A关系、Has-A关系、Use-A关系,分别代表继承、关联和依赖。优先考虑Has-A关系而不是Is-A关系复用代码。任何时候都不要继承工具类,工具是可以拥有并可以使用的,而不是拿来继承的。
一法则主要指迪米特法则。
迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解。
迪米特法则简单的说就是如何做到"低耦合",门面模式和调停者模式就是对迪米特法则的践行。
-
拓展:
对于门面模式可以举一个简单的例子,你去一家公司洽谈业务,你不需要了解这个公司内部是如何运作的,你甚至可以对这个公司一无所知,去的时候只需要找到公司入口处的前台美女,告诉她们你要做什么,她们会找到合适的人跟你接洽,前台的美女就是公司这个系统的门面。再复杂的系统都可以为用户提供一个简单的门面,Java Web开发中作为前端控制器的Servlet或Filter不就是一个门面吗,浏览器对服务器的运作方式一无所知,但是通过前端控制器就能够根据你的请求得到相应的服务。调停者模式也可以举一个简单的例子来说明,例如一台计算机,CPU、内存、硬盘、显卡、声卡各种设备需要相互配合才能很好的工作,但是如果这些东西都直接连接到一起,计算机的布线将异常复杂,在这种情况下,主板作为一个调停者的身份出现,它将各个设备连接在一起而不需要每个设备之间直接交换数据,这样就减小了系统的耦合度和复杂度。
如下图所示:
使用门面模式后:
迪米特法则用通俗的话来将就是不要和陌生人打交道,如果真的需要,找一个自己的朋友,让他替你和陌生人打交道。
2.请你解释一下,内存中的栈(stack)、堆(heap) 和静态区(static area) 的用法。
-
堆:专门用来保存对象的实例(new 创建的对象和数组),实际上也只是保存对象实例的属性值,属性的类型和对象本身的类型标记等,并不保存对象的方法(方法是指令,保存在Stack中)
-
1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
-
2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身.
-
3.一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。
-
-
栈:对象实例在堆中分配好以后,需要在Stack中保存一个4字节的堆内存地址,用来定位该对象实例在堆中的位置,便于找到该对象实例。
-
1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中
-
2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
-
3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
-
4.由编译器自动分配释放 ,存放函数的参数值,局部变量的值等.
-
-
静态区/方法区:
-
1.方法区又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
-
2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
-
3.全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。
-
3.请谈一谈,什么情况下会发生死锁?解决死锁的策略有哪些?
-
死锁发生的四个必要条件是: 1.资源互斥使用。 即存在这样一种资源,它在某个时刻只能被分配给一个执行绪(也称为线程)使用; 2.多个进程保持一定的资源,但又请求新的资源。 3.资源不可被剥夺。即执行程序获得到的互斥资源不可被强行剥夺,换句话说,只有资源占用者自己才能释放资源; 4.多个进程循环等待。若干执行绪以不同的次序获取互斥资源,从而形成环形等待的局面,想象在由多个执行绪组成的环形链中,每个执行绪都在等待下一个执行绪释放它持有的资源。
-
一般死锁的应对策略有: 1.死锁预防。如进程需要的所有资源,在一开始就全部申请好得到之后再开始执行。 2.死锁避免。如进程每次申请申请资源的时候,根据一定的算法,去看该请求可能不可能造成死锁,如果可能,就不给它分配该资源。 3.死锁处理。破坏四个必要条件的其中一个,比如kill掉一个进程。 4.死锁忽略。不管死锁,由用户自行处理,比如重启电脑。一般的系统其实都采取这种策略。
编程题:(来自力扣)
1.一年中的第几天 (将自己的分析思路写出)
给你一个按 YYYY-MM-DD 格式表示日期的字符串 date, 请你计算并返回该日期是当年的第几天。
思路:
-
用字符串分割出月日
int YY= Integer.parseInt(date.split("-")[0]); int MM = Integer.parseInt(date.split("-")[1]); int DD = Integer.parseInt(date.split("-")[2]);
-
利用数组储存每个月的前一个月有多少天
int []monthsDay = {0,31,28,31,30,31,30,31,31,30,31,30,31};
-
利用月份进行循环
-
当月份等于2时,执行闰年的判断
-
判断之后,如果是闰年可以直接加一天,或者修改数组第2个元素的值为29。
-
-
for(int i=0;i<MM;i++){ days += monthsDay[i]; if (i==2&((YY%4==0&&YY%100!=0)||(YY%400==0))){ days =days+1; //或者用monthsDay[2]=29 } }
-
最后把当前之前每个月的累计和与分割出来的DD进行加法计算。
totalDays = days+DD; System.out.println(date+"是今年的第"+totalDays+"天");
通常情况下,我们认为 1 月 1 日是每年的第 1 天, 1 月 2 日是每年的第 2 天,依此类推。每个月的天 数与现行公元纪年法(格里高利历)一致。
示例 1:
输入:date = "2019-01-09" 输出:9 示例 2:
输入:date = "2019-02-10" 输出:41 示例 3:
输入:date = "2003-03-01" 输出:60 示例 4:
输入:date = "2004-03-01" 输出:61
提示:date.length == 10 date[4] == date[7] == '-',其他的 date[i] 都是数字。 date 表示的范围从 1900 年 1 月 1 日至 2019 年 12 月 31 日。
完整程序
public class test { public static void main(String[] args) { String date = "2004-03-01"; int YY= Integer.parseInt(date.split("-")[0]); int MM = Integer.parseInt(date.split("-")[1]); int DD = Integer.parseInt(date.split("-")[2]); int []monthsDay = {0,31,28,31,30,31,30,31,31,30,31,30,31}; int days = 0; int totalDays = 0; for(int i=0;i<MM;i++){ days += monthsDay[i]; if (i==2&((YY%4==0&&YY%100!=0)||(YY%400==0))){ days =days+1; } } totalDays = days+DD; System.out.println(date+"是今年的第"+totalDays+"天"); } }
2.编写一个加密程序。
要求从键盘上输入一字符串(可含数字、大小写字母、特殊符号(空格 , . ?!) ),及一个密码K(密码取值为 1-99)。 加密规则是: 特殊符号(空格 ,. ?!)不加密,直接原样输出; 大小写字母在字母空间内向后循环移动 K位; 数字在数字空间中向后循环移动 K位。
思路:
-
1.实际上是在空间内循环,所以先用ASCII码(也在空间内)减去空间第一个元素的值,所得的差值X相当于空间内第X个值
-
2.用X加上密码值进行加密
-
3.对加密后的值取余,余数Y相当于空间内第Y个值
-
4.最后加上空间第一个元素的值,即可得到字母或数字的ASCII码值。
-
字符转ASCII码值:在字符前面用(int)进行强转
-
ASCII码转字符:在数值前用(char)进行强转
-
实现:
import java.util.Scanner; public class test2 { public static void main(String[] args) { System.out.println("请输入加密字符:"); Scanner input = new Scanner(System.in); String encryption = input.nextLine(); System.out.println("请输入密码(1-99):"); int keyword = input.nextInt(); char[] strArr1 = encryption.toCharArray(); System.out.println("加密前的字符:"+encryption); for (int i = 0; i < strArr1.length; i++) { if (Character.isDigit(strArr1[i])){ /* 1.实际上是在空间内循环,所以先用ASCII码(也在空间内)减去空间第一个元素的值,所得的差值X相当于空间内第X个值 2.用X加上密码值进行加密 3.对加密后的值取余,余数Y相当于空间内第Y个值 4.最后加上空间第一个元素的值,即可得到字母或数字的ASCII码值。 */ strArr1[i] = (char)((((int)strArr1[i]-48+keyword)%10)+48); } else if (Character.isUpperCase(strArr1[i])){ //如果是字母,则将其转化为ASCII码,然后再做运算,再转为char strArr1[i] = (char)((((int)strArr1[i]-65+keyword)%26)+65); } else if (Character.isLowerCase(strArr1[i])){ //如果是字母,则将其转化为ASCII码,然后再做运算,再转为char strArr1[i] = (char)((((int)strArr1[i]-97+keyword)%26)+97); } } String str = String.valueOf(strArr1); System.out.print("加密后的字符为:"+str); } }
3.在开发过程你用到过那些设计模式?用在什么场合?除单例模式外写出其他一种你常用的设计模式?
MVC设计模式:M-Model(模型,也叫DAO层):封装应用程序的状态;V-View(视图):表示用户界面;C-Controlle(bean层)