Java 面向对象简单十问

0. 目录

  1. 对面向对象思想的理解
  2. Java 对象初始化顺序
  3. Overload 和 Override 的区别
  4. int 和 Integer 的区别
  5. char 型变量中能否存储一个中文汉字
  6. Java 中 Serializable 和 Externalizable 的区别
  7. 抽象类和接口的区别
  8. String 和 StringBuilder、StringBuffer 的区别
  9. 阐述 final、finally 和 finalize 的区别
  10. Java 中的异常处理机制和简单的应用

1. 面向对象思想的理解

面向对象的思想是相对于面向过程而言的,两者都是解决问题的方式,但是它们的出发点不同。

面向过程的方式关注的是通过怎样的步骤解决问题,一般通过若干紧密联系的步骤达到解决问题的目的。面向对象的方式关注的是怎么样将问题尽量拆分为相互独立的任务,然后以不同的身份去解决这些任务,最后组合这些任务输入达到解决问题的目的。

这样看来面向过程的方式显得更直接一些,但是当遇到的问题规模和复杂程度很大时,这样的处理方式会使得解决的过程变得十分繁琐,思路不够清晰,并且当问题有所变化时,解决方案不好及时作出调整。面向对象的方式,首先将大的问题拆分为相关性很小的不同的子任务模块,这样就可以将这些子模块分配给不同的对象去解决,各个对象之间不需要知道对方内部的具体工作内容,只需要知道相互之间可以怎样交互就可以了,通过模块之间的相互调用协作完成最终的大问题,当问题需求出现变化时,很可能只需要调整各个模块之间的协作方式,就能做出及时的应对,而不需要从头来过。所以面向对象的思想更加适合真实世界中复杂问题场景的需求。

可以这样理解,面向过程是面向对象的基础环节,面向对象是面向过程的上层建筑。面向对象的解决思路中,一个个的子模块中的子问题的解决依然是面向过程的方式,它们通过一层层的包装实现了模块化的结构,就像是一个个提前生产好的工具包,这样可以减少不必要的重复工作,降低任务间的耦合程度,更加灵活地应对不同场景的需求。

2. Java 对象初始化顺序

大致的的顺序是静态资源先于非静态资源被加载。下面以Person类为例进行叙述。

1. 静态初始化只会在首次需要用到时被初始化一次,这个被用到的时机是`Person`类对象被首次创建或者`Person`类中的静态资源(属性或方法)被首次访问。
   2. 静态初始化之后是非静态初始化。当使用`new Person()`创建`Person`对象时(静态资源在这之前已经被加载),`Person`对象中的所有基本类型数据被设置为默认值(0或false或'\u0000'),引用被设置为`null`。之后执行出现在代码中的字段定义处初始化动作。然后执行构造器。

3. Overload 和 Override 的区别

重载(Overload)是在一个类中,方法名相同,而参数列表不同。而返回类型不作为评判标准,即可以相同也可以不同。

重写(Override)是子类对父类的可继承方法的重新实现,其参数列表不能改变,即“换心不换壳”。而对于返回类型,在 Java 5 之前的重写必须保持返回类型不变,Java 5 之后引入了协变返回类型,即子类重写方法可以返回父类方法的返回类型的子类型。所以,如果父类方法的返回值类型是Object,那么子类的重写方法就可以返回任意类型的值。

4. int 和 Integer 的区别

int属于基本数据类型,基本数据类型的变量不需要使用new来创建,而是直接使用一个变量类存储“值”,由于基本数据类型的使用频率很高,这样创建更加方便高效。Integer是基本数据类型int的包装类型,每个基本数据类型都有自己对应的包装类型,用于在堆内存里表示基本类型的数据。

5. char 型变量中能否存储一个中文汉字

Java 中char类型变量使用Unicode编码存储字符,因此可以存储中文汉字。

char c = '中';

6. Java 中 Serializable 和 Externalizable 的区别

两者都是用于对象的序列化,即保存内存中的对象到硬盘中,以供下次直接使用(反序列化)。对象需要序列化就必须使其类实现 Seriablizable 或者 Externalizable。两者的区别如下:

  1. Serializable 序列化时不会调用默认的构造器(即无参构造器),而 Externalizable 序列化时会调用默认构造器。
  2. 只实现 Seriabliable 的对象,其所有属性(包括private属性、其应用的对象)都会被序列化和反序列化来进行保存、传递(当然可以通过 Transient修饰类取消指定属性的序列化);而Externaliable在我们不希望序列化那么多属性的情况下可以使用,它要求我们实现两个方法:writeExternal()readExternal()来指定序列化和反序列化那些属性(此时Transient关键字不再起作用)。

7. 抽象类和接口的区别

  1. 抽象类是包含抽象方法的类。抽象类和抽象方法使用abstract修饰。抽象方法是只有方法声明没有方法体的方法,如下:

    abstract void func();
    

    如果一个类包含一个或多个抽象方法,那么该类本身也必须定义为抽象的,否则编译不通过,如下:

    abstract class Person {
        abstract void speak();
    }
    

    编译器不允许创建抽象类的对象,因此必须在继承并实现其所有抽象方法之后的子类中创建对象。抽象允许同时拥有任何非抽象方法的一般成员,且抽象类的抽象方法可以使用权限修饰符设置访问权限,但禁止被修饰为private,因为这样的话该抽象方法就永远不能被实现。

  2. 接口是相对于抽象类更进一步的抽象。接口使用interface关键字来创建,它的所有方法必须是抽象的,并且它们是默认以public abstract修饰的,所以不需要在方法前添加任何修饰,并且也不能以任何其它方式修饰,它们只能是publid abstract的,如下:

    public interface Person {
        void speak();
        int func();
    }
    

    接口通过使用implements在新的类中实现它的所有抽象方法,之后才能被实例化,如下:

    public class PersonImpl implements Person {
        public void speak() {
            System.out.println("你好!");
        }
        public int func() {
            return 1;
        }
    }
    

    Java 8 之前的接口只允许包含抽象方法,Java 8 之后的接口允许包含默认方法、静态方法以及属性。默认方法使用关键字default修饰,这样就可以在接口中提供方法的实现,并且可以在实现类中调用它。接口中的属性被隐式地修饰为为static final,且不能以其他方式修饰。

8. String 和 StringBuilder、StringBuffer 的区别

  1. 可变性:String类是不可变的,String类中每个看起来会修改本身值得方法,都会创建一个新的String对象来存储修改后得字符串内容;StringBuilderStringBuffer都是可变的。所以,当代码中涉及到频繁更改变换字符串的行为时,使用String的速度比StringBuilder要慢得多。
  2. 线程安全:String不可变,因此是线程安全地,StringBuilder是可变的,不是线程安全的。StringBuffer虽然也是可变的,但它内部的很多方法可以带有 synchronized关键字进行同步,所以可以保证线程安全。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但在单线程运行的情况下,StringBuider更快。

9. 阐述 final、finally 和 finalize 的区别

  1. final是修饰符,用于修饰类、方法或者属性。final修饰类时,它意味着该类不能被继承,因此不能使用final修饰抽象类或接口。final修饰方法时,意味着该方法不能被重写,只能被使用,因此同样地,不能使用final修饰抽象方法。final修饰属性或局部变量时,它只有一次被初始化地机会,且必须在其被使用之前,之后就不能对其进行任何改动,对于对象属性或变量来说,这意味着它不能再引用任何其它的对象,但对象本身的内部属性是可以更改的。
  2. finally是关于异常处理中的关键字,在提供了执行清理操作的办法。不管有没有异常被捕获,即不管try中代码块正常执行或者异常触发被抛出还是异常被捕获进而执行catch代码块,finally代码块中的内容都会被执行,即使try/catch代码块中包含return语句,finally代码块同样会执行。
  3. finalize是方法名。Java 中可以使用finalize()方法在垃圾收集器将对象从内存中清楚出去之前做必要的清理工作。该方法是在Object类中定义的,因此所有的类都继承了它。子类通过重写finalize()方法以整理系统资源或者执行其它清理工作。它是由垃圾回收器,在确定该对象没有被引用,需要被删除之前调用的。

10. Java 中的异常处理机制和简单的应用

Java 中以Throwable类为任何可以作为异常类型的基类,进一步分为两种类型:ErrorException。其中Error用来表示 JVM 无法处理的错误,Exception表示程序执行中的异常。Exception也分为两种:受检异常(Checked Exception)和非受检异常(Unchecked Exception)。受检异常是从语法角度必须被处理的异常,如果不处理,程序就不能编译通过。不受检异常包含的是Exception中的一大分支RuntimeException(运行时异常),这些异常一般是由程序逻辑错误引起的,应该从逻辑角度尽可能地避免这类异常发生,可以选择捕获处理,也可以不处理。

Java 的异常处理实际上就是抛出异常捕获异常

  1. 抛出异常:抛出异常的前提是异常情形(exception condition)的发生,异常情形指的是当前环境下无法获得足够的信息来解决的问题。此时程序能做的就是跳出当前环境,把问题交给上一级环境,此时就发生了抛出异常。抛出异常会首先创建出异常对象,然后当前程序被终止,并从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并转到异常处理程序中继续执行。
  2. 捕获异常:当异常被抛出之后,运行时系统会开始寻找合适的异常处理器(exception handler),合适的异常处理器所能处理的异常和方法抛出的异常类型相符。当找到合适的异常处理器时,开始执行其中处理逻辑,其任务是企图将程序从异常状态中恢复。当未找到合适的异常处理器时,整个 Java 程序就会终止。

所有的异常实际上都是以对象的形式存在的,我们也可以编写自定义的异常类型,并在合适的地方抛出自定义异常对象或者在并合适的地方编写处理程序。

参考

  1. Java中的String,StringBuilder,StringBuffer三者的区别

  2. Java中final、finally和finalize的区别

  3. Externalizable和Serializable

  4. Java提高篇——Java 异常处理

posted @ 2021-08-15 14:13  alterwl  阅读(81)  评论(0编辑  收藏  举报