1. Java的四个基本特性(抽象、封装、继承,多态),对多态的理解(多态的实现方式)以及在项目中那些地方用到多态
Java的四个基本特性
- 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
- 封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。
- 继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。
- 多态:多态性是指允许不同子类型的对象对同一消息作出不同的响应。
-
- 多态的理解(多态的实现方式)
方法重载(overload):实现的是编译时的多态性(也称为前绑定)。
方法重写(override):实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西。
要实现多态需要做两件事:
方法重写(子类继承父类并重写父类中已有的或抽象的方法);
对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。
项目中对多态的应用
举一个简单的例子,在物流信息管理系统中,有两种用户:订购客户和卖房客户,两个客户都可以登录系统,他们有相同的方法Login,但登陆之后他们会进入到不同的页面,也就是在登录的时候会有不同的操作,两种客户都继承父类的Login方法,但对于不同的对象,拥有不同的操作。
面相对象开发方式优点
较高的开发效率:可以把事物进行抽象,映射为开发的对象。
保证软件的鲁棒性(健壮性):高重用性,可以重用已有的而且在相关领域经过长期测试的代码。
保证软件的高可维护性:代码的可读性非常好,设计模式也使得代码结构清晰,拓展性好。
2. 什么是重载和重写?
- 重载:重载发生在同一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载。
- 重写:重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。根据不同的子类对象确定调用的那个方法。
-
3. 面向对象和面向过程的区别?用面向过程可以实现面向对象吗?那是不是不能面向对象
- 面向过程就像是一个细心的管家,事无具细的都要考虑到。而面向对象就像是个家用电器,你只需要知道他的功能,不需要知道它的工作原理。
- 面向过程是一种是“事件”为中心的编程思想。就是分析出解决问题所需的步骤,然后用函数把这些步骤实现,并按顺序调用。面向对象是以“对象”为中心的编程思想。
- 简单的举个例子:汽车发动、汽车到站
- 这对于 面向过程
来说,是两个事件,汽车启动是一个事件,汽车到站是另一个事件,面向过程编程的过程中我们关心的是事件,而不是汽车本身。针对上述两个事件,形成两个函数,之
后依次调用。(事件驱动,动词为主) - 然而这对于面向对象来说,我们关心的是汽车这类对象,两个事件只是这类对象所具有的行为。而且对于这两个行为的顺序没有强制要求。(对象驱动,名词为主,将问题抽象出具体的对象,而这个对象有自己的属性和方法,在解决问题的时候是将不同的对象组合在一起使用)
- 用面向过程可以实现面向对象吗 ?
如果是c语言来展现出面向对象的思想,c语言中是不是有个叫结构体的东西,这个里面有自己定义的变量 可以通过函数指针就可以实现对象
那是不是不能面向对象 ?
4. 面向对象开发的六个基本设计模式原则(单一职责、开放封闭、里氏替换、依赖倒置、合成聚合复用、接口隔离),迪米特法则。
六个基本原则(参考《设计模式之禅》)
- 单一职责(Single Responsibility Principle 简称
SRP):一个类应该仅有一个引起它变化的原因。在面向对象中,如果只让一个类完成它该做的事,而不涉及与它无关的领域就是践行了高内聚的原则,这个类就只有单一职责。 - 里氏替换(Liskov Substitution Principle 简称
LSP):任何时候子类型能够替换掉它们的父类型。子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用当然没有任何问题。 - 依赖倒置(Dependence Inversion Principle 简称
DIP):要依赖于抽象,不要依赖于具体类。要做到依赖倒置,应该做到:①高层模块不应该依赖底层模块,二者都应该依赖于抽象;②抽象不应该依赖于具体实现,具体实现应该依赖于抽象。 - 接口隔离(Interface Segregation Principle 简称 ISP):不应该强迫客户依赖于他们不用的方法
。接口要小而专,绝不能大而全。臃肿的接口是对接口的污染,既然接口表示能力,那么一个接口只应该描述一种能力,接口也应该是高度内聚的。 - 最少知识原则(Least Knowledge Principle 简称
LKP):只和你的朋友谈话。迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解。 - 开放封闭(Open Closed Principle 简称
OCP):软件实体应当对扩展开放,对修改关闭。要做到开闭有两个要点:①抽象是关键,一个系统中如果没有抽象类或接口系统就没有扩展点;②封装可变性,将系统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂而换乱。
- 其他原则
合成聚和复用:优先使用聚合或合成关系复用代码
面向接口编程
优先使用组合,而非继承
一个类需要的数据应该隐藏在类的内部
类之间应该零耦合,或者只有传导耦合,换句话说,类之间要么没关系,要么只使用另一个类的接口提供的操作
在水平方向上尽可能统一地分布系统功能
- 项目中用到的原则
单一职责、开放封闭、合成聚合复用(最简单的例子就是String类)、接口隔离、
5. 内部类有哪些
可以将一个类的定义放在另一个类的定义内部,这就是内部类。
在Java中内部类主要分为成员内部类、局部内部类、匿名内部类、静态内部类
(一)成员内部类
成员内部类也是最普通的内部类,它是外围类的一个成员,所以他是可以无限制的访问外围类的所有成员属性和方法,尽管是private的,但是外围类要访问内部类的成员属性和方法则需要通过内部类实例来访问。
public class OuterClass {
private String str;
public void outerDisplay(){
System.out.println("outerClass...");
}
public class InnerClass{
public void innerDisplay(){
str = "chenssy..."; //使用外围内的属性
System.out.println(str);
outerDisplay(); //使用外围内的方法
}
}
// 推荐使用getxxx()来获取成员内部类,尤其是该内部类的构造函数无参数时
public InnerClass getInnerClass(){
return new InnerClass();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.getInnerClass();
inner.innerDisplay();
}
}
--------------------
chenssy...
outerClass...
在成员内部类中要注意两点:
- 成员内部类中不能存在任何static的变量和方法;
成员内部类是依附于外围类的,所以只有先创建了外围类才能够创建内部类。
(二)局部内部类
有这样一种内部类,它是嵌套在方法和作用于内的,对于这个类的使用主要是应用与解决比较复杂的问题,想创建一个类来辅助我们的解决方案,到那时又不希望这个类是公共可用的,所以就产生了局部内部类,局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法和属性中被使用,出了该方法和属性就会失效。
//定义在方法里:
public class Parcel5 {
public Destionation destionation(String str){
class PDestionation implements Destionation{
private String label;
private PDestionation(String whereTo){
label = whereTo;
}
public String readLabel(){
return label;
}
}
return new PDestionation(str);
}
public static void main(String[] args) {
Parcel5 parcel5 = new Parcel5();
Destionation d = parcel5.destionation("chenssy");
}
}
//定义在作用域内:
public class Parcel6 {
private void internalTracking(boolean b){
if(b){
class TrackingSlip{
private String id;
TrackingSlip(String s) {
id = s;
}
String getSlip(){
return id;
}
}
TrackingSlip ts = new TrackingSlip("chenssy");
String string = ts.getSlip();
}
}
public void track(){
internalTracking(true);
}
public static void main(String[] args) {
Parcel6 parcel6 = new Parcel6();
parcel6.track();
}
}
(三)匿名内部类
在做Swing编程中,我们经常使用这种方式来绑定事件
public class OuterClass {
// ★当所在方法的形参需要被匿名内部类使用,那么这个形参就必须为final!!!这里要注意
public InnerClass getInnerClass(final int num,String str2){
return new InnerClass(){
int number = num + 3;
public int getNumber(){
return number;
}
}; /* 注意:分号不能省 */
}
public static void main(String[] args) {
OuterClass out = new OuterClass();
InnerClass inner = out.getInnerClass(2, "chenssy");
System.out.println(inner.getNumber());
}
}
interface InnerClass {
int getNumber();
}
----------------
Output:
这里我们就需要看清几个地方
- 匿名内部类是没有访问修饰符的。
- new 匿名内部类,这个类首先是要存在的。如果我们将那个InnerClass接口注释掉,就会出现编译出错。
- 注意getInnerClass()方法的形参,第一个形参是用final修饰的,而第二个却没有。同时我们也发现第二个形参在匿名内部类中没有使用过,所以当所在方法的形参需要被匿名内部类使用,那么这个形参就必须为final。
- 匿名内部类是没有构造方法的。因为它连名字都没有何来构造方法。
(四)静态内部类
关键字static中提到Static可以修饰成员变量、方法、代码块,其他它还可以修饰内部类,使用static修饰的内部类我们称之为静态内部类,不过我们更喜欢称之为嵌套内部类。静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。
- 它的创建是不需要依赖于外围类的。
- 它不能使用任何外围类的非static成员变量和方法。
public class OuterClass {
private String sex;
public static String name = "chenssy";
// 静态内部类
static class InnerClass1{
// 在静态内部类中可以存在静态成员
public static String _name1 = "chenssy_static";
public void display(){
// 静态内部类只能访问外围类的静态成员变量和方法
// 不能访问外围类的非静态成员变量和方法
System.out.println("OutClass name :" + name);
}
}
// 非静态内部类
class InnerClass2{
// 非静态内部类中不能存在静态成员
public String _name2 = "chenssy_inner";
// 非静态内部类中可以调用外围类的任何成员,不管是静态的还是非静态的
public void display(){
System.out.println("OuterClass name:" + name);
}
}
// 外围类方法
public void display(){
// 外围类访问静态内部类:内部类
System.out.println(InnerClass1._name1);
// 静态内部类 可以直接创建实例不需要依赖于外围类
new InnerClass1().display();
// 非静态内部的创建需要依赖于外围类
OuterClass.InnerClass2 inner2 = new OuterClass().new InnerClass2();
// 方位非静态内部类的成员需要使用非静态内部类的实例
System.out.println(inner2._name2);
inner2.display();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.display();
}
}
----------------
Output:
chenssy_static
OutClass name :chenssy
chenssy_inner
OuterClass name:chenssy
6. 组合、继承和代理的区别
定义
组合:在新类中new 另外一个类的对象,以添加该对象的特性。
继承:从基类继承得到子类,获得基类的特性。
代理:在代理类中创建某功能的类,调用类的一些方法以获得该类的部分特性。
使用场合
- 组合:各部件之间没什么关系,只需要组合即可。like组装电脑,需要new CPU(),new RAM(),new Disk()……
public class Computer {
public Computer() {
CPU cpu=new CPU();
RAM ram=new RAM();
Disk disk=new Disk();
}
}
class CPU{ }
class RAM{ }
class Disk{ }
- 继承:子类需要具有父类的功能,各子类之间有所差异。like
Shape类作为基类,子类有Rectangle,CirCle,Triangle……代码不写了,大家都经常用。 - 代理:飞机控制类,我不想暴露太多飞机控制的功能,只需部分前进左右转的控制(而不需要暴露发射导弹功能)。通过在代理类中new一个飞机控制对象,然后在方法中添加飞机控制类的各个需要暴露的功能。
public class PlaneDelegation{
private PlaneControl planeControl; //private外部不可访问
// 飞行员权限代理类,普通飞行员不可以开火
PlaneDelegation(){
planeControl=new PlaneControl();
}
public void speed(){
planeControl.speed();
}
public void left(){
planeControl.left();
}
public void right(){
planeControl.right();
}
}
final class PlaneControl {// final表示不可继承,控制器都能继承那还得了
protected void speed() {}
protected void fire() {}
protected void left() {}
protected void right() {}
}
说明:
继承:代码复用,引用不灵活; 组合:代码复用, 接口:引用灵活; 推荐组合+接口使用,看IO中包装流FilterInputStream中的策略模式
7. 什么是构造函数
构造函数是函数的一种特殊形式,特殊在哪里?构造函数中不需要定义返回类型(void是无需返回值的意思,请注意区分两者),且构造函数的名称与所在的类名完全一致,其余的与函数的特性相同,可以带有参数列表,可以存在函数的重载现象。
一般用来初始化一些成员变量,当要生成一个类的对象(实例)的时候就会调用类的构造函数。如果不显示声明类的构造方法,会自动生成一个默认的不带参数的空的构造函数。
public class Demo{
private int num=0;
//无参构造函数
Demo()
{
System.out.println("constractor_run");
}
//有参构造函数
Demo(int num)
{
System.out.println("constractor_args_run");
}
//普通成员函数
public void demoFunction()
{
System.out.println("function_run");
}
}
在这里要说明一点,如果在类中我们不声明构造函数,JVM会帮我们默认生成一个空参数的构造函数;如果在类中我们声明了带参数列表的构造函数,JVM就不会帮我们默认生成一个空参数的构造函数,我们想要使用空参数的构造函数就必须自己去显式的声明一个空参的构造函数。
构造函数的作用
通过开头的介绍,构造函数的轮廓已经渐渐清晰,那么为什么会有构造函数呢?构造函数有什么作用?构造函数是面向对象编程思想所需求的,它的主要作用有以下两个:
- 创建对象。任何一个对象创建时,都需要初始化才能使用,所以任何类想要创建实例对象就必须具有构造函数。
- 对象初始化。构造函数可以对对象进行初始化,并且是给与之格式(参数列表)相符合的对象初始化,是具有一定针对性的初始化函数。
8. 向上转型和向下转型
父类引用能指向子类对象,子类引用不能指向父类对象;
向上转型
父类引用指向子类对象,例如:Father f1 = new Son();
向下转型
把指向子类对象的父类引用赋给子类引用,需要强制转换,例如:
Father f1 = new Son();
Son s1 = (Son)f1;
但有运行出错的情况:
Father f2 = new Father();
Son s2 = (Son)f2;//编译无错但运行会出现错误
在不确定父类引用是否指向子类对象时,可以用instanceof来判断:
if(f3 instanceof Son){
Son s3 = (Son)f3;
}