logo

面向对象

面向对象和面向过程的区别

  1. 面向过程:
  • ⾯向过程就是分析出解决问题所需要的步骤,然后⽤函数把这些步骤⼀步⼀ 步实现,使⽤的时候再⼀个⼀个的⼀次调⽤就可以
  • 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
  • 缺点:没有面向对象易维护、易复用、易扩展
  1. 面向对象:
  • ⾯向对象,把构成问题的事务分解成各个对象,⽽建⽴对象的⽬的也不是为 了完成⼀个个步骤,⽽是为了描述某个事件在解决整个问题的过程所发⽣的⾏为。 ⽬的是 为了写出通⽤的代码,加强代码的重⽤,屏蔽差异性
  • 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
  • 缺点:性能比面向过程低


详细说说面向对象的3大特性是什么!

  1. 封装

封装把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。换句话说就是 把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。

  1. 继承

**继承 **就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的,一般形式如下:

class 父类 {
}

class 子类 extends 父类 {
}

继承概念的实现方式有二类:实现继承接口继承

  • 实现继承是指直接使用基类的属性和方法而无需额外编码的能力
  • 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力
  • 一般我们继承基本类和抽象类用 extends 关键字,实现接口类的继承用 implements 关键字。

注意点:

  • 通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。
  • 继承的过程,就是从一般到特殊的过程。要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。
  • ⼦类拥有⽗类对象所有的属性和⽅法(包括私有属性和私有⽅法),但是⽗类中的私有属 性和⽅法⼦类是**⽆法访问,只是拥有**
  • 子类可以拥有自己的属性和方法, 即⼦类可以对⽗类进⾏扩展。
  • 子类可以重写覆盖父类的方法。
  • JAVA 只支持单继承,即一个子类只允许有一个父类,但是可以实现多级继承,及子类拥有唯一的父类,而父类还可以再继承。

使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。

# implements 关键字

public interface A {
    public void eat();
    public void sleep();
}

public interface B {
    public void show();
}

public class C implements A,B {
}
  1. 多态

同一个行为具有多个不同表现形式或形态的能力就是 多态。网上的争论很多,笔者个人认同网上的这个观点:重载也是多态的一种表现,不过多态主要指运行时多态

Java 多态可以分为 重载式多态重写式多态

  • 重载式多态,也叫编译时多态。编译时多态是静态的,主要是指方法的重载overload,它是根据参数列表的不同来区分不同的方法。通过编译之后会变成两个不同的方法,在运行时谈不上多态。也就是说这种多态再编译时已经确定好了。
  • 重写式多态,也叫运行时多态。运行时多态是动态的,主要指继承父类和实现接口override时,可使用父类引用指向子类对象实现这个就是大家通常所说的多态性

这种多态通过动态绑定(dynamic binding)技术来实现,是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。也就是说,只有程序运行起来,你才知道调用的是哪个子类的方法。 这种多态可通过函数的重写以及向上转型来实现。

Java语言是如何实现多态的?一般有3种方式

  1. 继承
  2. 重写
  3. 父类引用指向子类对象:Parent p = new Child();

关于继承如下 3 点请记住:

  • 子类拥有父类非 private 的属性和方法。
  • 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
  • 子类可以用自己的方式实现父类的方法。

拓展提升: 深挖⾯向对象编程三⼤特性 --封装、继承、多态

抽象类和接口的区别是什么?

相同点:

  • 都不能被实例化
  • 都包含抽象方法,其子类都必须覆写这些抽象方法
  • 都位于继承的顶端,用于被其他实现或继承

不同点在于:

  1. 接口, 通常以interface来声明
public interface UserIF() {
//定义
} 
  1. 抽象类, 通常以abstract来声明
public abstract class Employee {
    private String name;
    private String address;
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    // 无参构造
    public Employee() {
    }
    // 有参构造
    public Employee(String name, String address) {
        this.name = name;
        this.address = address;
    }
    // 抽象的方法
    public abstract Integer num(Integer a, Integer b); 
    
}
  1. 抽象类中可以进行方法的定义和实现; 在接口中,只允许进行方法的定义,不允许有方法的实现
    由于Java 8 可以用 default 关键字在接口中定义默认方法,所以2者都可以有默认实现的方法
  2. 接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值
  3. 变量:接口中定义的变量只能是公共的静态常量,抽象类中的变量是普通变量。
  4. Java中只能extends继承一个类,但是可以implements继承多个接口
  5. 抽象级别不同:抽象程度由高到低依次是 接口 > 抽象类 > 类
  6. 类如果要实现一个接口,它必须要实现接口声明的所有方法。但是,类可以不实现
    抽象类声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。
  7. Java 接口中声明的变量默认都是 static 和 final 的。抽象类可以包含非 final 的变量

普通类和抽象类有哪些区别?

普通类不能包含抽象方法,抽象类可以包含抽象方法

抽象类不能直接实例化,只能被继承,普通类可以直接实例化

public abstract class AbstractFruit {
    public AbstractFruit(){
        System.out.println("抽象类");
    }
    public abstract void say();
}

...

AbstractFruit abstractFruit = new AbstractFruit();//报错

普通类可以被其他类继承和使用,而抽象类一般用于作为基类,被其他类继承和扩展使用

抽象类能使用 final 修饰吗?

不能,定义抽象类就是让其他类继承的,如果定义为final该类就不能被继承,这样彼此就会产生
矛盾,所以final不能修饰抽象类

重载(overload)和重写(override)的区别?

⽅法的重载和重写都是实现多态的⽅式,区别在于前者实现的是编译时的多态性,⽽后者实
现的是运⾏时的多态性。

重载发⽣在⼀个类中,同名的⽅法如果有不同的参数列表(参数类型不同、参数个数不同
或者⼆者都不同)则视为重载;

重写发⽣在⼦类与⽗类之间,重写要求⼦类被重写⽅法与⽗类被重写⽅法有相同的返回类
型,⽐⽗类被重写⽅法更好访问,不能⽐⽗类被重写⽅法声明更多的异常(⾥⽒代换原
则)。

⽅法重载的注意事项:

  1. ⽅法名⼀致,参数列表中参数的顺序,类型,个数不同。
  2. 重载与⽅法的返回值⽆关,存在于⽗类和⼦类,同类中。
  3. 可以抛出不同的异常,可以有不同修饰符。

构造器constructor是否可被重写override?

构造器不能被继承,因此不能被重写,但可以被重载

面向对象五大基本原则是什么

  1. 单一职责原则SRP(Single Responsibility Principle)

类的功能要单一,不能包罗万象,跟杂货铺似的。

  1. 开放封闭原则OCP(Open-Close Principle)

一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改,哼,一万个不乐意。

  1. 里式替换原则LSP(the Liskov Substitution Principle LSP)

子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活。哈哈~~

  1. 依赖倒置原则DIP(the Dependency Inversion Principle DIP)

高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。就是你出国要说你是中国人,而不能说你是哪个村子的。比如说中国人是抽象的,下面有具体的xx省,xx市,xx县。你要依赖的抽象是中国人,而不是你是xx村的。

  1. 接口分离原则ISP(the Interface Segregation Principle ISP)

设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。就比如一个手机拥有打电话,看视频,玩游戏等功能,把这几个功能拆分成不同的接口,比在一个接口里要好的多。

什么是内部类?

内部类:可以将一个类的定义放在里另一个类的内部,这就是内部类。内部类可分为四种:成员内部类、静态内部类、局部(方法)内部类、匿名内部类

  1. 静态内部类,定义在类内部的静态类,就是静态内部类
class Outer {
    static class Inner {
        // 定义了一个Inner Class
    }
}
  1. 成员内部类,定义在类内部,成员位置上的非静态类,就是成员内部类
class Outer {
    class Inner {
        // 定义了一个Inner Class
    }
}
  1. 局部内部类,定义在方法中的内部类,就是局部内部类
public class Outer {
   
	public void test(){
   
		class Inner {

			private void fun(){
              ...
			}
		}
        ...

	}
}
  1. 匿名内部类,匿名内部类就是没有名字的内部类,常常被用来重写某个或某些方法
public class Outer {

    public static IAnimal getInnerInstance(String speak){
        return new IAnimal(){
            @Override
            public void speak(){
                System.out.println(speak);
            }};
        	//注意上一行的分号必须有
    }
    
    public static void main(String[] args){
    	//调用的speak()是重写后的speak方法。
        Outer.getInnerInstance("test").speak();
    }
}

内部类有以下优点:

  • 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据!
  • 内部类不为同一包的其他类所见,具有很好的封装性
  • 内部类有效实现了“多重继承”,优化 java 单继承的缺陷
  • 匿名内部类可以很方便的定义回调

Java创建对象有哪几种方式

创建对象方式 是否调用了构造器
new关键字
Class.newInstance
Constructor.newInstance
Clone
反序列化
  1. 使用new关键字,最常见也是最简单的创建对象的方式了。通过这种方式,直接调用类的构造方式(无参的和带参数的)来创建对象
Student s = new Student();
  1. 使用Class类的newInstance方法,通过反射机制,使用Class类的newInstance()方法创建对象
Student s = Student.class.newInstance();
  1. 使用Constructor类newInstance方法,通过反射机制,使用Constructor类newlnstance方法创建对象
Constructor<Student> constructor = Student.class.getConstructor();
Student s = constructor.newInstance();
  1. 使用clone方法,要使用克隆clone方法,需要先实现Cloneable接口并实现其定义的clone方法
Student s1 = new Student();

Student s2 =(Student)s1.clone(); 
  1. 使用反序列化,通过将对象序列化到文件或流中,然后再进行反序列化来创建对象
ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj"));
Student s = (Student) in.readObject();