面向对象设计原则
1.单一职责原则
- 在面向对象设计中,分工理论就是单一职责原则(Single Pesponsibility Prineiple, SRP)
- 两个含义
- 避免相同的职责分散到不同的类中
- 避免一个类承担太多职责
- 为什么要遵循单一设计原则
- 可以减少类之间的耦合:当需求变化时,只修改一个类,从而隔离了变化。
- 提高类的复用性
- 单一职责使得组件可以方便的拆卸和组装
- 应用:用工厂模式来实现不同数据库操作类。
- 工厂模式(Factory): 允许你在代码执行时实例化对象。之所以被称为工厂模式,是因为它负责“生产”对象。
- 一些应该遵循的做法:
- 根据业务流程,把业务对象提炼出来,如果业务流程链路太复杂,就把这个业务对象分离为多个单一业务对象。
当业务链标准化后,对业务对象的内部情况做进一部处理。把第一次标准化视为最高层抽象,第二次视为次高层抽象,以此类推,直到“恰如其分”的设计层次。
- 职责的分类需要注意。有业务职责,还要脱离业务的抽象职责,从认识业务到抽象算法是一个层层递进的过程。
就好比命令模式中的顾客,服务员和厨师的职责,作为老板的你需要规划好各自的职责范围,既要防止越俎代庖,也要防止互相推诿。
2.接口隔离原则:
设计应用程序的时候,如果一个模块包含多个子模块,那么我们应该小心对该模块做出抽象。
- 接口隔离原则(Interface Segregation Principle, ISP): 客户端不应该被强迫实现不会使用的接口,应该把胖接口中的方法分组,然后用多个接口代替它,每个接口服务于一个子模块。简单的说,就是使用多个专门的接口比使用单个接口要好得多。
- 接口隔离原则的主要观点
- 一个类对另外一个类的依赖性应当是建立在最小的接口上
- ISP 可以达到不强迫客户依赖于他们不使用的方法,接口的实现类应该只呈现为单一职责的角色(遵守SRP原则)。
- ISP 还可降低客户端之间的相互影响——当某个客户程序要求提供新的职责(需求变更)而迫使接口发生改变时,影响到其他客户程序的可能性会是最小的。
- 客户端程序不应该依赖它不需要的接口方法
- ISP强调的是接口对客户端的承诺越少越好,并且要做到专一。
- 接口污染:过于臃肿的接口设计是对接口的污染。接口污染就是为接口添加不必要的职责,如果开发人员在接口中增加一个新功能的主要目的只是减少接口实现类的数目,则此设计导致接口被不断的“污染” 并 “变胖”,图一是胖接口,图二是使用隔离原则。
3.开放 - 封闭原则:
- 开放:模块的行为必须是开放的、支持扩展的,而不是僵化的。
- 关闭:在模块的功能进行扩展时,不应该影响或大规模影响已有的程序模块。
- 一个模块在扩展性方面应该是开放的,在更改性方面应该是封闭的。
- 该原则的**核心是想是对抽象编程,而不是具体编程**,因为抽象相对稳定。
让类依赖于固定的抽象,这样的修改就是封闭的,通过面向对象的继承和多态机制,可以实现对抽象体的继承,
通过覆写其方法改变固有行为,实现新的扩展方法,所以对于扩展就是开放的。
- 在设计方面充分应用 抽象 和 封装 的思想。
- 在系统功能编程实现方面应用面向接口的编程。
4.替换原则 :里氏替换原则(Liskov Substiution Principle, LSP)定义以及主要思想:子类型必须能够替换掉它们的父类型、并出现在父类能够出现的任何地方。
- 解决如果正确进行继承设计和合理地应用继承机制:
- 如何正确地进行继承方面的设计?
- 最佳的继承层次如何获得?
- 怎样避免所设计的类层次陷入不符合OCP原则的状况?
- 如何遵守替换原则:
- 父类的方法都要在子类中实现或者重写, 派生类只实现其抽象类中声明的方法, 而不应当给出多余的方法定义或实现。
- 在客户端程序中只应该出现父类对象,而不是直接使用子类对象, 这样可以实现运行期绑定(多态绑定)。
5.依赖倒置原则:依赖倒置的核心原则是解耦,如果脱离这个最原始的原则,那就是本末倒置。
- 将依赖关系倒置为依赖接口:
- 上层模块不应该依赖下层模块, 它们共同依赖于一个抽象。
- 抽象不能依赖于具体, 具体应该要依赖于抽象
- IOC(Inversion of Control) 是依赖倒置原则(Dependence Inversion Principle, DIP)的同义词。
- DI: 依赖注入
- DS: 依赖查找
- 如何满足DIP:
- 每个较高层次类都为它所需要的服务提出一个接口声明, 较低层次实现这个接口
- 每个高层类都通过该抽象接口使用服务
6.深拷贝和浅拷贝:
简单来讲就是复制、克隆;Person p=new Person(“张三”);
浅拷贝就是对对象中的数据成员进行简单赋值,如果存在动态成员或者指针就会报错
深拷贝就是对对象中存在的动态成员或指针重新开辟内存空间
7.值传递和引用传递:
值传递是针对基本数据类型而言,传递的是值得副本,对副本的改变不会影响到原变量
引用传递:就是将一个堆内存空间的使用权交给多个栈内存空间,每一个栈内存空间都可以对堆内存空间进行修改
8.web 容器功能:
通信支持、管理Servlet生命周期,多线程、将jsp转换成java等等
9.java内存分配
寄存器:我们无法控制
静态域:static定义的静态成员
常量池:编译时被确定并保存在.class文件中的(final)常量值和一些文本修饰的符号引用(类和接口的全限定名,字段的名称和描述符,方法和名称和描述符)
非ram存储:硬盘等永久存储空间
堆内存:new创建的对象和数组,由java虚拟机自动垃圾回收器管理,存取速度慢
栈内存:基本类型的变量和对象的引用变量(堆内存空间的访问地址),速度快,可以共享,但是大小与生存期必须确定,缺乏灵活性
11.一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制?
可以有多个类,但只能有一个public的类,并且public的类名必须与文件名相一致。
12.Java有没有goto?
java中的保留字,现在没有在java中使用。
13.简述逻辑操作(&,|,^)与条件操作(&&,||)的区别。
条件操作只能操作布尔型的,而逻辑操作不仅可以操作布尔型,而且可以操作数值型
逻辑操作不会产生短路.
使用逻辑操作符时,我们会遇到一种“短路”现象。即一旦能够明确无误地确定整个表达式的值,就不再计算表达式余下部分了。因此,整个逻辑表达式靠后的部分有可能不会被运算
14.说说&和&&的区别。
==相同点==:&和&&都可以用作逻辑与的运算符,表示逻辑与(and),当运算符两边的表达式的结果都为true时,整个运算结果才为true,否则,只要有一方为false,则结果为false。
==&&具有短路功能==:即如果第一个表达式为false,则不再计算第二个表达式。
举个列子1:对于if(str != null && !str.equals(“”))表达式,当str为null时,后面的表达式不会执行,所以不会出现NullPointerException;如果将&&改为&,则会抛出NullPointerException异常。
再比如: If(x== 33 & ++y>0) y会增长,If(x==33 && ++y>0) Y不会增长。
==&可以用作位运算符==:当&操作符两边的表达式不是boolean类型时,&表示按位与操作,我们通常使用0x0f来与一个整数进行&运算,来获取该整数的最低4个bit位,例如,0x31 & 0x0f的结果为0x01。
按位与: 即二进制对应的位置,如果同时为1,那么即为1,否则为0 。
备注:这道题先说两者的共同点,再说出&&和&的特殊之处,并列举一些经典的例子来表明自己理解透彻深入、实际经验丰富。
15.switch语句能否作用在byte上,能否作用在long上,能否作用在String上?
在switch(expr1)中,expr1只能是一个整数表达式或者枚举常量(更大字体),整数表达式可以是int基本类型或Integer包装类型,由于,byte,short,char都可以隐含转换为int,所以,这些类型以及这些类型的包装类型也是可以的。long不符合switch的语法规定,并且不能被隐式转换成int类型,所以,它不能作用于swtich语句中。
对于string而言:JDK1.7以前是不能作为switch的,以后即可以作用于switch中。
16.short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?
注意数据类型的转换:由于s1+1运算时会自动提升表达式的类型,所以结果是int型,再赋值给short类型s1时,编译器将报告需要强制转换类型的错误。
对于short s1 = 1; s1 += 1;由于 += 是java语言规定的运算符,java编译器会对它进行特殊处理,因此可以正确编译。
17.char型变量中能不能存贮一个中文汉字?为什么?
char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字,所以,char型变量中当然可以存储汉字。
不过要注意的是:
如果某个特殊的汉字没有被包含在unicode编码字符集中,那么,这个char型变量中就不能存储这个特殊汉字
unicode编码占用两个字节,所以,char类型的变量也是占用两个字节。
18.用最有效率的方法算出2乘以8等於几?
方法:将一个数左移n位,就相当于乘以了2的n次方,那么,一个数乘以8(2的3次方),只要将其左移3位即可,而位运算cpu直接支持的,效率最高,所以,2乘以8等於几的最效率的方法是将2左移3位,即2 <<3。
19.使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?
使用final关键字修饰一个变量时,是指引用变量不能变,引用变量所指向的对象中的内容还是可以改变的。例如,对于如下语句:final StringBuffer a=new StringBuffer("immutable");
执行如下语句将报告编译期错误:a=new StringBuffer("");
但是,执行如下语句则可以通过编译:a.append(" broken!");
java
有人在定义方法的参数时,可能想采用如下形式来阻止方法内部修改传进来的参数对象:
public void method(final StringBuffer param) { }
实际上,这是办不到的,在该方法内部仍然可以增加如下代码来修改参数对象:
param.append("a");
20.java 中对象的创建方法有几种?
通过new来创建
通过反射创建
通过复制创建
21."=="和equals方法究竟有什么区别?
如果是在object对象中,那么两者所表示的都是值是否相等(
public boolean equals(Object obj){return (this==obj); }
),可以看到object的equals方法是使用的“==”比较。一般而言,要比较两个基本类型的数据或两个引用变量是否相等,只能用==操作符,也就是比较内存中所存储的两个变量的值是否相等。
比较两个对象是否相等:实际上是比较两个堆内存的首地址是否相等,即使用的“==”(如果一个变量指向的数据是对象类型的,那么,这时候涉及了两块内存,堆内存中放的是栈内存中存储的首地址)
如果比较两个独立对象的内容是否相等,那么使用重写后的equals.(string类型,data类型的equals都是重写了object的方法)
举例:
String a=new String("foo"); String b=new String("foo");
它们的首地址是不同的,即a和b中存储的数值是不相同的,所以,表达式a==b将返回false,而这两个对象中的内容是相同的,所以,表达式a.equals(b)将返回true。在实际开发中,我们经常要比较传递进行来的字符串内容是否等,此时是使用的equals()方法。
对于equals()而言,默认情况是从object类继承的,当希望能够比较该类创建的两个实例对象的内容是否相同,那么你必须覆盖equals方法,由自己写代码来决定在什么情况即可认为两个对象的内容是相同的。
22.静态变量和实例变量的区别?
在语法定义上的区别:静态变量前要加static关键字,而实例变量前则不加。
在程序运行时的区别:
实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。
静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。、
总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。
举例:
java
对于下面的程序,无论创建多少个实例对象,永远都只分配了一个staticVar变量,并且每创建一个实例对象,这个staticVar就会加1;
但是,每创建一个实例对象,就会分配一个instanceVar,即可能分配多个instanceVar,并且每个instanceVar的值都只自加了1次。
public class VariantTest
{
public static int staticVar = 0;
public int instanceVar = 0;
public VariantTest()
{
staticVar++;
instanceVar++;
System.out.println(“staticVar=” + staticVar + ”,instanceVar=” + instanceVar);
}
}
23.是否可以从一个static方法内部发出对非static方法的调用?
不可以。
对于static修饰的静态方法,是随着类的加载而加载,且调用的时可以不用创建对象而直接调用
非静态方法要与对象联系在一起,只有创建了对象了以后才能调用非静态方法,即当一个静态方法被调用的时候,有可能还没有创建任何实例对象。
24.Integer与int的区别
int是java提供的8种原始数据类型之一,系统给的默认值为0。
Java为每个原始类型提供了封装类,Integer是java为int提供的封装类。系统给的默认值为null。
即Integer可以区分出未赋值和值为0的区别,int则无法表达出未赋值的情况:
在JSP开发中,Integer的默认为null,所以用el表达式在文本框中显示时,值为空白字符串,而int默认的默认值为0,用el表达式在文本框中显示时,结果为0,所以,int不适合作为web层的表单数据的类型。
在Hibernate中,如果将OID定义为Integer类型,那么Hibernate就可以根据其值是否为null而判断一个对象是否是临时的,如果将OID定义为了int类型,还需要在hbm映射文件中设置其unsaved-value属性为0。
Integer提供了多个与整数相关的操作方法,例如,将一个字符串转换成整数,Integer中还定义了表示整数的最大值和最小值的常量。
25.Math.round(11.5)等於多少? Math.round(-11.5)等於多少?
Math类中提供了三个与取整有关的方法:ceil(向上取整)、floor(向下取整)、round(四舍五入)
举例:
Math.ceil(11.3)的结果为12,Math.ceil(-11.3)的结果是-11
Math.floor(11.6)的结果为11,Math.floor(-11.6)的结果是-12
算法为Math.floor(x+0.5),即将原来的数字加上0.5后再向下取整,所以,Math.round(11.5)的结果为12,Math.round(-11.5)的结果为-11。
26.下面的代码有什么不妥之处?
1. if(username.equals(“zxx”){}; 2.int x = 1; return x==1?true:false;
答:第一个问题少了一个右括号;第二个问题没有错误
27.说出作用域public,private,protected,以及不写时的区别
有4种访问权限,4个访问范围
28.Overload和Override的区别。Overload的方法是否可以改变返回值的类型?
Overload:表示方法重载,表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同(即参数个数、类型、位置不同),通过定义不同的输入参数来区分这些方法,然后再调用时,JVM就会根据不同的参数样式,来选择合适的方法执行:
在使用重载时只能通过不同的参数样式。例如,不同的参数类型,不同的参数个数,不同的参数顺序(当然,同一方法内的几个参数类型必须不一样,例如可以是fun(int,float),但是不能为fun(int,int));
不能通过访问权限、返回类型、抛出的异常进行重载;
方法的异常类型和数目不会对重载造成影响;
对于继承来说,如果某一方法在父类中是访问权限是priavte,那么就不能在子类对其进行重载,如果定义的话,也只是定义了一个新方法,而不会达到重载的效果。
Override:表示方法重写(覆盖),表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,当通过子类创建的实例对象调用这个方法时,将调用子类中的定义方法,这相当于把父类中定义的那个完全相同的方法给覆盖了,这也是面向对象编程的多态性的一种表现。
重写的方法的标志必须要和被重写的方法的标志完全匹配,才能达到重写的效果;
重写的方法的返回值必须和被重写的方法的返回一致;
重写的方法所抛出的异常必须和被重写方法的所抛出的异常一致,或者是其子类(因为子类是解决父类的一些方法,不能比父类更多问题);
被重写的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行重写(子类方法的访问权限只能比父类的更大,不能更小)。
29.同学贡献的一些题
ClassLoader如何加载class 。
jvm里有多个类加载,每个类加载可以负责加载特定位置的类,例如,bootstrap类加载负责加载jre/lib/rt.jar中的类, 我们平时用的jdk中的类都位于rt.jar中。extclassloader负责加载jar/lib/ext/*.jar中的类,appclassloader负责classpath指定的目录或jar中的类。除了bootstrap之外,其他的类加载器本身也都是java类,它们的父类是ClassLoader。
一个房子里有椅子,椅子有腿和背,房子与椅子是什么关系,椅子与腿和背是什么关系?
如果房子有多个椅子,就是聚合关系,否则是一种关联关系,当然,聚合是一种特殊的关联。椅子与腿和背时组合关系。
说说has a与is a的区别
is-a表示的是属于得关系,比如兔子属于一种动物(继承关系)。has-a表示组合,包含关系,比如兔子包含有腿,头等组件;
Servlet的生命周期: init、 service、 destroy。
30.分层设计的好处
把各个功能按调用流程进行了模块化,模块化带来的好处就是可以随意组合
举例:如果要注册一个用户,流程为显示界面并通过界面接收用户的输入,接着进行业务逻辑处理,在处理业务逻辑又访问数据库,如果我们将这些步骤全部按流水帐的方式放在一个方法中编写,这也是可以的,但这其中的坏处就是,当界面要修改时,由于代码全在一个方法内,可能会碰坏业务逻辑和数据库访问的码,同样,当修改业务逻辑或数据库访问的代码时,也会碰坏其他部分的代码。
分层就是要把界面部分、业务逻辑部分、数据库访问部分的代码放在各自独立的方法或类中编写,这样就不会出现牵一发而动全身的问题了
分层的好处:
实现了软件之间的解耦;
便于进行分工
便于维护
提高软件组件的重用
便于替换某种产品,比如持久层用的是hibernate,需要更换产品用toplink,就不用该其他业务代码,直接把配置一改。
便于产品功能的扩展。
便于适用用户需求的不断变化
31.序列化接口的id有什么用?
对象经常要通过IO进行传送,让你写程序传递对象,你会怎么做?把对象的状态数据用某种格式写入到硬盘,Person->“zxx,male,28,30000”Person,既然大家都要这么干,并且没有个统一的干法,于是,sun公司就提出一种统一的解决方案,它会把对象变成某个格式进行输入和输出,这种格式对程序员来说是透明(transparent)的,但是,我们的某个类要想能被sun的这种方案处理,必须实现Serializable接口。
ObjectOutputStream.writeObject(obj);
Object obj = ObjectInputStream.readObject();假设两年前我保存了某个类的一个对象,这两年来,我修改该类,删除了某个属性和增加了另外一个属性,两年后,我又去读取那个保存的对象,或有什么结果?未知!sun的jdk就会蒙了。为此,一个解决办法就是在类中增加版本后,每一次类的属性修改,都应该把版本号升级一下,这样,在读取时,比较存储对象时的版本号与当前类的版本号,如果不一致,则直接报版本号不同的错!
32.hashCode方法的作用?
hashcode这个方法是用来鉴定2个对象是否相等的。
与equals()方法的区别:
简单来讲,equals方法主要是用来判断从表面上看或者从内容上看,2个对象是不是相等,equals这个方法是给用户调用的,如果你想判断2个对象是否相等,你可以重写equals方法,然后在代码中调用,就可以判断他们是否相等了
hashcode方法一般用户不会去调用,比如在hashmap中,由于key是不可以重复的,他在判断key是不是重复的时候就判断了hashcode这个方法,而且也用到了equals方法。
hashcode相当于是一个对象的编码,他和equals不同就在于他返回的是int型的,比较起来不直观。我们一般在覆盖equals的同时也要覆盖hashcode,让他们的逻辑一致。
==举例==:有个学生类,属性只有姓名和性别,那么我们可以认为只要姓名和性别相等,那么就说这2个对象是相等的。如果姓名和性别相等就算2个对象相等的话,那么hashcode的方法也要返回姓名的hashcode值加上性别的hashcode值,这样从逻辑上,他们就一致了。++要从物理上判断2个对象是否相等,用==就可以了。++
33.构造器Constructor是否可被override?
重写发生在继承过程中(子父类间),但是构造器Constructor不能被继承,因此不能重写Override,但可以被重载Overload。
34.接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承具体类(concrete class)? 抽象类中是否可以有静态的main方法?
接口是特殊的抽象类,他是多态的经典体现。
抽象类是指具有抽象方法的类,该类中出了有抽象方法外,其他普通方法具有的属性抽象方法都有。他们的唯一区别就是不能创建实例对象和允许有abstract方法;
因此:接口可以继承接口。抽象类可以实现(implements)接口,抽象类可继承具体类。抽象类中可以有静态的main方法。
【框架合集】SpringMVC教程汇总
【框架合集】MyBatis教程汇总
【框架合集】Spring教程汇总
【框架整合】SSM教程
【十套项目源码】
【BAT面试真题】