【Java面试题系列】:Java基础知识罕见面试题汇总 第二篇

2019年7月2日11:00:13【Java面试题系列】:Java基础知识罕见面试题汇总 第二篇已关闭评论 274

文中面试题从茫茫网海中经心挑选,若有毛病,迎接斧正!

第一篇链接:【Java面试题系列】:Java基础知识罕见面试题汇总 第一篇

1.JDK,JRE,JVM三者之间的联络和辨别

你是不是斟酌过我们写的xxx.java文件被谁编译,又被谁实行,又为何能够或许跨平台运转?

1.1基本观点

JVM:Java Virtual Machine,Java虚拟机。

JVM其实不克不及辨认我们日常平凡写的xxx.java文件,只能辨认xxx.class文件,它能够或许将class文件中的字节码指令举行辨认并挪用操作体系上的API完成指定的行动。所以,JVM是Java能够或许跨平台的中心。

JRE:Java Runtime Environment,Java运转时情况。

JRE重要包罗2个部分,JVM的范例完成和Java的一些基本类库。比拟于JVM,多出来的是一部分Java类库。

JDK:Java Development Kit,开辟东西包。

JDK是全部Java开辟的中心,它集成了JRE和一些好用的小东西,比方:javac.exe,java.exe,jar.exe等。

上一篇博客中也提到了,我们能够经由过程javac敕令将xxx.java文件编译为xxx.class文件。

1.2联络和辨别

相识完3者的基本观点,我们能够看出来3者的干系为一层层嵌套,即:JDK > JRE > JVM。

这里,我们提出一个题目:为何我们装置完JDK后会有两个版本的JRE?

我电脑装置的JDK是1.8版本,装置完的目次以下图所示:

【Java面试题系列】:Java基础知识罕见面试题汇总 第二篇

而jdk目次下也有1个jre:

【Java面试题系列】:Java基础知识罕见面试题汇总 第二篇

我电脑情况变量设置装备摆设的是:

JAVA_HOME C:\Program Files\Java\jdk1.8.0_191

Path变量末了增添的是%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin。

也就是说,我电脑用的是jdk目次下的jre,而不是和jdk同级目次下的jre,或许大部分人都是如许的,能够没人注重,说实话,我之前还真没在乎,看了网上的文章才晓得,看来真的是要多问为何。

这两个分歧版本的JRE实在没甚么联络,你能够修正下Path变量,指向恣意1个都能够,只是许多人在装置JDK的时刻,其实不清晰JDK和JRE的辨别,所以都邑装置,好比说我,哈哈。

在jdk的目次下,有一些可实行文件,好比说javac.exe,实在内部也是挪用的java类,所以jdk目次下的jre既供应了这些东西的运转时情况,也供应了我们编写的Java顺序的运转时情况。

所以,能够得出以下结论:

若是你是Java开辟者,装置JDK时能够挑选不装置JRE

若是你的机械只是用来布置和运转Java顺序,能够不装置JDK,只装置JRE便可

1.3Java 为何能跨平台,完成一次编写,多处运转?

Java引入了字节码的观点,JVM只能辨认字节码,并将它们诠释到体系的API挪用,针对分歧的体系有分歧的JVM完成,有Lunix版本的JVM完成,也有Windows版本的JVM完成,然则一致段代码在编译后的字节码是一致的,而一致段字节码,在分歧的JVM完成上会映射到分歧体系的API挪用,从而完成代码不修正便可跨平台运转。

所以说Java能够或许跨平台的中心在于JVM,不是Java能够或许跨平台,而是它的JVM能够或许跨平台。

2.接口和笼统类的辨别

2.1笼统要领

当父类的一些要领不确定时,能够用abstract关键字将其声明为笼统要领,声明语法以下:

public abstract double area();

笼统要领与一样平常要领的辨别:

  1. 笼统要领须要用关键字abstract润饰

  2. 笼统要领没有要领体,即只要声明,而没有详细的完成

  3. 笼统要领地点的类必需声明为笼统类

  4. 笼统要领必需声明为public或许protected,不克不及声明为private

    由于若是为private,则不克不及被子类继续,子类便没法完成该要领,笼统要领也就失去了意义

2.2笼统类

若是一个类包罗笼统要领,则这个类是笼统类,必需由关键字abstract润饰。

笼统类是为了继续而存在的,若是你界说了一个笼统类,却不去继续它,那末即是白白建立了这个笼统类,由于你不克不及用它来做任何事情,即没由起到笼统类的意义。关于一个父类,若是它的某个要领在父类中没有详细的完成,必需依据子类的现实需求来举行分歧的完成,那末就能够或许将这个要领声明为abstract要领,此时这个类也就成为abstract类了。

笼统类与一样平常类的辨别:

  1. 笼统类不克不及被实例化,即不克不及经由过程new来建立对象
  2. 笼统类须要用关键字abstract润饰
  3. 若是一个类继续于一个笼统类,则子类必需完成父类的笼统要领。若是子类没有完成父类的笼统要领,则必需将子类也界说为abstract类。
  4. 笼统类除能够具有一样平常类的成员变量和成员要领,还能够具有笼统要领

值得注重的是,笼统类不肯定必需包罗笼统要领,只是一样平常人人运用时,都包罗了笼统要领

举个详细的例子,好比我们有一个平面图形类Shape,它有两个笼统要领area()和perimeter(),离别用来猎取图形的面积和周长,然后我们有矩形类Rectangle和圆形类Circle,来继续笼统类Shape,各自完成area()要领和和perimeter()要领,由于矩形和圆形盘算面积和周长的要领是不一样的,下面看详细代码:

package com.zwwhnly.springbootdemo;

public abstract class Shape {

    public abstract double area();

    public abstract double perimeter();
}
package com.zwwhnly.springbootdemo;

public class Rectangle extends Shape {
    private double length;
    private double width;

    public double getLength() {
        return length;
    }

    public void setLength(double length) {
        this.length = length;
    }

    public double getWidth() {
        return width;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    @Override
    public double area() {
        return getLength() * getWidth();
    }

    @Override
    public double perimeter() {
        return (getLength() + getWidth()) * 2;
    }
}
package com.zwwhnly.springbootdemo;

public class Circle extends Shape {
    private double diameter;

    public double getDiameter() {
        return diameter;
    }

    public void setDiameter(double diameter) {
        this.diameter = diameter;
    }

    @Override
    public double area() {
        return Math.PI * Math.pow(getDiameter() / 2, 2);
    }

    @Override
    public double perimeter() {
        return Math.PI * getDiameter();
    }
}
public static void main(String[] args) {
    Rectangle rectangle = new Rectangle();
    rectangle.setLength(10);
    rectangle.setWidth(5);

    double rectangleArea = rectangle.area();
    double rectanglePerimeter = rectangle.perimeter();
    System.out.println("矩形的面积:" + rectangleArea + ",周长" + rectanglePerimeter);

    Circle circle = new Circle();
    circle.setDiameter(10);

    double circleArea = circle.area();
    double circlePerimeter = circle.perimeter();
    System.out.println("圆形的面积:" + circleArea + ",周长" + circlePerimeter);
}

输出效果:

矩形的面积:50.0,周长30.0

圆形的面积:78.53981633974483,周长31.41592653589793

2.2接口

接口,是对行动的笼统,声明语法为:

package com.zwwhnly.springbootdemo;

public interface Alram {
    void alarm();
}

能够看出,接口中的要领没有详细的完成(会被隐式的指定为public abstract要领),详细的完成由完成接口的类来完成,类完成接口的语法为(这里以ArrayList类为例):

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    ......
}

能够看出,一个类能够完成多个接口。

若是一个非笼统类完成了某个接口,就必需完成该接口中的一切要领。

若是一个笼统类完成了某个接口,能够不完成该接口中的要领,但其子类必需完成。

2.3笼统类和接口的辨别

语法层面上的辨别:

  1. 一个类只能继续一个笼统类,而一个类却能够完成多个接口
  2. 接口中不克不及含有静态代码块以及静态要领,而笼统类能够有静态代码块和静态要领
  3. 笼统类能够供应成员要领的完成细节,而接口中的要领不克不及够
  4. 接口的要领默许是public,一切要领在接口中不克不及有完成,笼统类能够有非笼统的要领
  5. 笼统类中的成员变量能够是各种范例的,而接口中的成员变量只能是public static final范例的
  6. 接口不克不及用new实例化,但能够声明,然则必需援用一个完成该接口的对象, 从设想层面来讲,笼统是对类的笼统,是一种模板设想,接口是行动的笼统,是一种行动的范例。

设想层面上的辨别:

  1. 笼统类是对全部类团体举行笼统,包罗属性、行动,然则接口倒是对类部分(行动)举行笼统。

    继续是一个 "是不是是"的干系,而 接口完成则是 "有无"的干系。若是一个类继续了某个笼统类,则子类必定是笼统类的品种,而接口完成则是有无、具有不具有的干系,好比鸟是不是能飞(或许是不是具有遨游飞翔这个特征),能遨游飞翔则能够完成这个接口,不克不及遨游飞翔就不完成这个接口。

  2. 设想层面分歧,笼统类作为许多子类的父类,它是一种模板式设想。而接口是一种行动范例,它是一种辐射式设想。

    关于笼统类,若是须要增添新的要领,能够直接在笼统类中增添详细的完成,子类能够不举行调换;而关于接口则不可,若是接口举行了调换,则一切完成这个接口的类都必需举行响应的修改。

这里援用下网上的门和警报的例子,门都有open()和close()两个行动,此时我们能够经由过程笼统类或许接口界说:

public abstract class Door {
    public abstract void open();

    public abstract void close();
}

或许运用接口:

public interface Door {
    void open();

    void close();
}

如今我们须要门具有警报alarm功用,该怎样设想呢?

你能够想到的2个思绪为:

1)在笼统类中增添alarm()要领,如许一来,一切继续于这个笼统类的子类都具有了报警功用,然则有的门其实不肯定具有报警功用。

2)在接口中增添alarm()要领,如许一来,用到报警功用的类就必须要完成接口中的open()和close()要领,或许这个类基础就不具有open()和close()这两个功用,好比火警报警器。

从这里能够看出,Door的open(),close()和alarm()属于两个分歧领域内的行动,open()和close()属于门自身固有的行动特征,而alarm()属于延长的附加行动。

因而最好的设想体式格局是零丁将报警设想为一个接口Alarm,包罗alarm()行动,Door设想为零丁的笼统类,包罗open()和close()行动,再设想一个报警门继续Door类并完成Alarm接口:

public abstract class Door {
    public abstract void open();

    public abstract void close();
}
public interface Alarm {
    void alarm();
}
public class AlarmDoor extends Door implements Alarm {

    @Override
    public void alarm() {

    }

    @Override
    public void open() {

    }

    @Override
    public void close() {

    }
}

3.重载与重写的辨别

3.1基本观点

重载(Overload):发生在1个类内里,是让类以一致的体式格局处置惩罚分歧范例数据的一种手腕,本质显示就是许可一个类中存在多个具有分歧参数个数或许范例同名函数/要领,是一个类中多态性的一种显示。

返回值范例可随便,不克不及以返回范例作为重载函数的辨别范例

重载划定规矩以下:

  1. 必需具有分歧的参数列表
  2. 能够有分歧的返回范例
  3. 能够有分歧的接见润饰符
  4. 能够抛出分歧的非常

重写(Override):发生在父子类中,是父类与子类之间的多态性,本质是对父类的函数举行从新界说,若是在子类中界说某要领与父类有雷同的要领称号和参数则该要领被重写,不外子类函数的接见润饰符权限不克不及小于父类的;若子类中的要领与父类中的某一要领具有雷同的要领名、返回范例和参数列表,则新要领将掩盖原有的要领,如需挪用父类华夏有的要领可运用super关键字挪用。

重写划定规矩以下:

  1. 参数列表必需完整与被重写的要领雷同,不然不克不及称其为重写而是重载
  2. 返回范例必需一向与被重写的要领雷同,不然不克不及称其为重写而是重载
  3. 接见润饰符的限定肯定要大于即是被重写要领的接见润饰符
  4. 重写要领肯定不克不及抛出新的搜检非常或许比被重写要领说明越发广泛的搜检型非常,比如父类要领声清楚明了一个搜检非常 IOException,在重写这个要领时就不克不及抛出 Exception,只能抛出 IOException 的子类非常,能够抛出非搜检非常

总之,重载与重写是Java多态性的分歧显示,重写是父类与子类之间多态性的显示,在运转时起作用;而重载是一个类中多态性的显示,在编译时起作用

3.2示例

实在JDK的源码中就有许多重载和重写的例子,重载的话,我们看下Math类的abs()要领,就有以下几种完成:

public static int abs(int a) {
    return (a < 0) ? -a : a;
}

public static long abs(long a) {
    return (a < 0) ? -a : a;
}

public static float abs(float a) {
     return (a <= 0.0F) ? 0.0F - a : a;
}

public static double abs(double a) {
     return (a <= 0.0D) ? 0.0D - a : a;
}

重写的话,我们以String类的equals()要领为例,基类中equals()是如许的:

public boolean equals(Object obj) {
    return (this == obj);
}

而子类String的equals()重写后是如许的:

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

我们再来看一个特别的例子:

package com.zwwhnly.springbootdemo;

public class Demo {

    public boolean equals(Demo other) {
        System.out.println("use Demo equals.");
        return true;
    }

    public static void main(String[] args) {
        Object o1 = new Demo();
        Object o2 = new Demo();
        Demo o3 = new Demo();
        Demo o4 = new Demo();

        if (o1.equals(o2)) {
            System.out.println("o1 is equal with o2.");
        }

        if (o3.equals(o4)) {
            System.out.println("o3 is equal with o4.");
        }
    }
}

输出效果:

use Demo equals.

o3 is equal with o4.

是不是是和你预期的输出效果不一致呢,涌现这个的原因是,该类的equals()要领并没有真正重写Object类的equals()要领,违反了参数划定规矩,因而o1.equals(o2)时,挪用的还是Object类的equals()要领,即对照的是内存地点,因而返回false。而o3.equals(o4)对照时,由于o3,o4都是Demo范例,因而挪用的是Demo类的equals()要领,返回true。

4.成员变量和部分变量的辨别

4.1界说的地位不一样

成员变量:在要领外部,能够被public,private,static,final等润饰符润饰

部分变量:在要领内部或许要领的声明上(即在参数列表中),不克不及被public,private,static等润饰符润饰,但能够被final润饰

4.2作用局限不一样

成员变量:全部类全都能够通用

部分变量:只要要领傍边才能够运用,出了要领就不克不及再用

4.3默许值不一样

成员变量:若是没有赋值,会有默许值(范例的默许值)

部分变量:没有默许值,运用前必需赋值,不然编译器会报错

4.4内存的地位不一样

成员变量:位于堆内存

部分变量:位于栈内存

4.5.生命周期不一样

成员变量:跟着对象建立而降生,跟着对象被渣滓收受接管而消逝

部分变量:跟着要领的挪用或许代码块的实行而存在,跟着要领的挪用终了或许代码块的实行终了而消逝

package com.zwwhnly.springbootdemo;

public class VariableDemo {
    private String name = "成员变量";

    public static void main(String[] args) {
        new VariableDemo().show();
    }

    public void show() {
        String name = "部分变量";
        System.out.println(name);
        System.out.println(this.name);
    }
}

输出效果:

部分变量

成员变量

5.字符型常量和字符串常量的辨别

  1. 形式上: 字符常量是单引号引发的一个字符 字符串常量是双引号引发的多少个字符
  2. 寄义上: 字符常量相当于一个整形值(ASCII值),能够列入表达式运算 字符串常量代表一个地点值(该字符串在内存中寄存地位)
  3. 占内存大小:字符常量只占一个字节 字符串常量占多少个字节

6.参考链接

弄懂 JRE、JDK、JVM 之间的辨别与联络

Java笼统类和笼统要领例子

深切明白Java的接口和笼统类

JAVA重写和重载的辨别

JAVA中部分变量 和 成员变量有哪些辨别

成员变量与部分变量的辨别

最最最罕见的Java面试题总结——第二周

avatar