java面试常见问题总结

java常见面试汇总

java中数据类型分为基本数据类型和引用数据类型。

基本数据类型

整型:byte,short,int,long
浮点型:float,double
字符型:char
布尔型:boolean

引用数据类型

数组

接口

Java程序设计语言对对象采用的不是引用调用,实际上,对象引用是按 值传递的

在 Java 中定义一个不做事且没有参数的构造方法的作用

Java 程序在执行子类的构造方法之前,如果没有用 super()来调用父类特 定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中 只定义了有参数的构造方法,而在子类的构造方法中又没有用 super()来 调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类 中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做 事且没有参数的构造方法。  
###创建一个对象用什么运算符?对象实体与对象引用有何不同?
new运算符,new创建对象实例(对象实例在堆内存中),对象引用指向对象 实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象 (一根绳子可以不系气球,也可以系一个气球);一个对象可以有n个引用指 向它(可以用n条绳子系住一个气球)。

关于 final 关键字的一些总结

final关键字主要用在三个地方:变量、方法、类。
对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化 之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让 其指向另一个对象。
当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方 法都会被隐式地指定为final方法。
使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修 改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方 法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何 性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。 类中所有的private方法都隐式地指定为final。

为什么TCP客户端最后还要发送一次确认呢?

一句话,主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。
如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一 个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于 TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服 务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数 据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服 务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务 器再次建立连接,这将导致不必要的错误和资源的浪费。
如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受 到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由 于服务器收不到确认,就知道客户端并没有请求连接。
java面试常见问题总结_第1张图片

为什么在四次挥手客户端最后还要等待2MSL?

MSL(Maximum Segment Lifetime),TCP允许不同的实现可以设置不同 的MSL值。
第一,保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报 文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开 了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于 是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重 传的报文,接着给出回应报文,并且会重启2MSL计时器。
第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现 在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可 以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连 接中不会出现旧连接的请求报文。

为什么建立连接是三次握手,关闭连接确是四次挥手呢?

建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文 后,把ACK和SYN放在一个报文里发送给客户端。
而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。

四 TCP 协议如何保证可靠传输

  • 应用数据被分割成 TCP 认为最适合发送的数据块。
  • TCP 给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传 送给应用层。
  • 校验和: TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP将丢弃这个报文段和不确认收到此报文段。
  • TCP的接收端会丢弃重复的数据。
  • 流量控制: TCP 连接的每一方都有固定大小的缓冲空间,TCP的接收端只 允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的 数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协 议是可变大小的滑动窗口协议。(TCP 利用滑动窗口实现流量控制)
  • 拥塞控制: 当网络拥塞时,减少数据的发送。
  • ARQ协议: 也是为了实现可靠传输的,它的基本原理就是每发完一个分组就 停止发送,等待对方确认。在收到确认后再发下一个分组。
  • 超时重传: 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收 到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
    java面试常见问题总结_第2张图片

五.接口与抽象的异同

1.抽象类

抽象类是关键字abstract修饰的类,既为抽象类,抽象抽象即不能被实例化。而不能被实例化就无用处,所以抽象类只能作为基类(父类),即被继承的类。抽象类中可以包含抽象方法也可以不包含,但具有抽象方法的类一定是抽象类。
  抽象类的使用原则如下:

(1)被继承性:抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public;
(2)抽象性:抽象类不能直接实例化,需要依靠子类采用向上转型的方式处理;
(3)抽象类必须有子类,使用extends继承,一个子类只能继承一个抽象类;
(4)子类(如果不是抽象类)则必须覆写抽象类之中的全部抽象方法(如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。

1、接口与类相似点

一个接口可以有多个方法。
接口文件保存在 .java 结尾的文件中,文件名使用接口名。
接口的字节码文件保存在 .class 结尾的文件中。
接口相应的字节码文件必须在与包名称相匹配的目录结构中。

2、接口与类的区别

接口不能用于实例化对象。
接口没有构造方法。
接口中所有的方法必须是抽象方法。
接口不能包含成员变量,除了 static 和 final 变量。
接口不是被类继承了,而是要被类实现。
接口支持多继承。

3、接口特性

接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
java面试常见问题总结_第3张图片

六.static关键字

  • static关键字并不会改变变量和方法的访问权限。 与C/C++中的static不同,Java中的static关键字不会影响到变量或者方法的作用域。在Java中能够影响到访问权限的只有private、public、protected(包括包访问权限)这几个关键字。
  • 为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。 很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行
  • static成员变量的初始化顺序按照定义的顺序进行初始化。
  • 即使没有显示地声明为static,类的构造器实际上也是静态方法。
  • static方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。
    • static代码块也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。
  • 普通类是不允许声明为静态的,只有内部类才可以。

被static修饰的内部类可以直接作为一个普通类来使用,而不需实例一个外部类

  • static能作用于局部变量么?

在C/C++中static是可以作用域局部变量的,但是在Java中切记:static是不允许用来修饰局部变量。不要问为什么,这是Java语法的规定。

  • 常见的笔试面试题
 `public class Test extends Base{`
 
    ` static{
        System.out.println("test static");
    }`
     
    `public Test(){
        System.out.println("test constructor");
    }
     `
   ` public static void main(String[] args) {
        new Test();
    }
}`
 
` class Base{`
     
    static{
        System.out.println("base static");
    }
     
    public Base(){
        System.out.println("base constructor");
    }
`}`  

输出:

 `base static   
     test static    
     base constructor    
     test constructor    `

先来想一下这段代码具体的执行过程,在执行开始,先要寻找到main方法,因为main方法是程序的入口,但是在执行main方法之前,必须先加载Test类,而在加载Test类的时候发现Test类继承自Base类,因此会转去先加载Base类,在加载Base类的时候,发现有static块,便执行了static块。在Base类加载完成之后,便继续加载Test类,然后发现Test类中也有static块,便执行static块。在加载完所需的类之后,便开始执行main方法。在main方法中执行new Test()的时候会先调用父类的构造器,然后再调用自身的构造器。因此,便出现了上面的输出结果。

  • static块可以出现类中的任何地方(只要不是方法内部,记住,任何方法内部都不行),并且执行是按照static块的顺序执行的。

  • 这段代码的输出结果是什么?

      `  public class Test {  
      Person person = new Person("Test");  
    

    static{
    System.out.println(“test static”);
    }
    `

    `  public Test() {  `
      `    System.out.println("test constructor"); `   
    
      } `
    

    public static void main(String[] args) { new MyClass(); }
    }

    class Person{
    static{ System.out.println("person static"); }
    public Person(String str) {
    System.out.println("person "+str); }
    }

    class MyClass extends Test {
    Person person = new Person("MyClass");
    static{ System.out.println("myclass static"); }

      public MyClass() {  
          System.out.println("myclass constructor");  
      }  
    

    }

输出:

` test static ` 
` myclass static ` 
` person static  `
` person Test`
` test constructor`
` person MyClass`
` myclass constructor`
思路:

首先加载Test类,因此会执行Test类中的static块。接着执行new MyClass(),而MyClass类还没有被加载,因此需要加载MyClass类。在加载MyClass类的时候,发现MyClass类继承自Test类,但是由于Test类已经被加载了,所以只需要加载MyClass类,那么就会执行MyClass类的中的static块。在加载完之后,就通过构造器来生成对象。而在生成对象的时候,必须先初始化父类的成员变量,因此会执行Test中的Person person = new Person(),而Person类还没有被加载过,因此会先加载Person类并执行Person类中的static块,接着执行父类的构造器,完成了父类的初始化,然后就来初始化自身了,因此会接着执行MyClass中的Person person = new Person(),最后执行MyClass的构造器。

七.final

  • 根据程序上下文环境,Java关键字final有“这是无法改变的”或者“终态的”含义,它可以修饰非抽象类、非抽象类成员方法和变量。你可能出于两种理解而需要阻止改变:设计或效率。
    final类不能被继承,没有子类,final类中的方法默认是final的。
    final方法不能被子类的方法覆盖,但可以被继承。
    final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
    final不能用于修饰构造方法。
    注意:父类的private成员方法是不能被子类方法覆盖的,因此private类型的方法默认是final类型的。

  • final类不能被继承,因此final类的成员方法没有机会被覆盖,默认都是final的。在设计类时候,如果这个类不需要有子类,类的实现细节不允许改变,并且确信这个类不会载被扩展,那么就设计为final类。

  • final方法
    如果一个类不允许其子类覆盖某个方法,则可以把这个方法声明为final方法。
    使用final方法的原因有二:
    第一、把方法锁定,防止任何继承类修改它的意义和实现。
    第二、高效。编译器在遇到调用final方法时候会转入内嵌机制,大大提高执行效率。

  • final变量(常量)
    用final修饰的成员变量表示常量,值一旦给定就无法改变!
    final修饰的变量有三种:静态变量、实例变量和局部变量,分别表示三种类型的常量。
    从下面的例子中可以看出,一旦给final变量初值后,值就不能再改变了。
    另外,final变量定义的时候,可以先声明,而不给初值,这中变量也称为final空白,无论什么情况,编译器都确保空白final在使用之前必须被初始化。但是,final空白在final关键字final的使用上提供了更大的灵活性,为此,一个类中的final数据成员就可以实现依对象而有所不同,却有保持其恒定不变的特征。

  • static和final一块用表示什么
    static final用来修饰成员变量和成员方法,可简单理解为“全局常量”!
    对于变量,表示一旦给值就不可修改,并且通过类名可以访问。
    对于方法,表示不可覆盖,并且可以通过类名直接访问。

      ** 特别要注意一个问题: **
      对于被static和final修饰过的实例常量,实例本身不能再改变了,但**对于一些容器类型(比如,ArrayList、HashMap)的实例变量,不可以改变容器变量本身,但可以修改容器中存放的对象,**这一点在编程中用到很多。
    

八.Java关键字this、super使用总结

一、this

Java关键字this只能用于方法方法体内。当一个对象创建后,
Java虚拟机(JVM)就会给这个对象分配一个引用自身的指针,这个指针的名字就是this。因此,this只能在类中的非静态方法中使用,静态方法和静态的代码块中绝对不能出现this,这在“Java关键字static、final使用总结”一文中给出了明确解释。并且this只和特定的对象关联,而不和类关联,同一个类的不同对象有不同的this。

  • 在什么情况下需要用到this:
    第一、通过this调用另一个构造方法,用发是this(参数列表),这个仅仅在类的构造方法中,别的地方不能这么用。
    第二、函数参数或者函数中的局部变量和成员变量同名的情况下,成员变量被屏蔽,此时要访问成员变量则需要用“this.成员变量名”的方式来引用成员变量。当然,在没有同名的情况下,可以直接用成员变量的名字,而不用this,用了也不为错,呵呵。
    第三、在函数中,需要引用该函所属类的当前对象时候,直接用this。

二、super

    super关键和this作用类似,是被屏蔽的成员变量或者成员方法  或变为可见,或者说用来引用被屏蔽的成员变量和成员成员方法。

不过super是用在子类中,目的是访问直接父类中被屏蔽的成员,注意是直接父类(就是类之上最近的超类)。

  • 总结一下super的用法:
    第一、在子类构造方法中要调用父类的构造方法,用“super(参数列表)”的方式调用,参数不是必须的。同时还要注意的一点是:“super(参数列表)”这条语句只能用在子类构造方法体中的第一行。
    第二、当子类方法中的局部变量或者子类的成员变量与父类成员变量同名时,也就是子类局部变量覆盖父类成员变量时,用“super.成员变量名”来引用父类成员变量。当然,如果父类的成员变量没有被覆盖,也可以用“super.成员变量名”来引用父类成员变量,不过这是不必要的。
    第三、当子类的成员方法覆盖了父类的成员方法时,也就是子类和父类有完全相同的方法定义(但方法体可以不同),此时,用“super.方法名(参数列表)”的方式访问父类的方法。

九.java中的匿名内部类总结

匿名内部类也就是没有名字的内部类

正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写

但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口

匿名内部类的基本实现

abstract class Person {

public abstract void eat();}public class Demo {

public static void main(String[] args) {

    Person p = new Person() {

        public void eat() {

            System.out.println("eat something");

        }

    };

    p.eat();

}}  

可以看到,我们直接将抽象类Person中的方法在大括号中实现了

这样便可以省略一个类的书写

并且,匿名内部类还能用于接口上

在接口上使用匿名内部类

interface Person {

public void eat();}public class Demo {

public static void main(String[] args) {

    Person p = new Person() {

        public void eat() {

            System.out.println("eat something");

        }

    };

    p.eat();

}}  

由上面的例子可以看出,只要一个类是抽象的或是一个接口,那么其子类中的方法都可以使用匿名内部类来实现

最常用的情况就是在多线程的实现上,因为要实现多线程必须继承Thread类或是继承Runnable接口

Thread类的匿名内部类实现

public class Demo {

public static void main(String[] args) {

    Thread t = new Thread() {

        public void run() {

            for (int i = 1; i <= 5; i++) {

                System.out.print(i + " ");

            }

        }

    };

    t.start();

}}  

Runnable接口的匿名内部类实现

public class Demo {

public static void main(String[] args) {

    Runnable r = new Runnable() {

        public void run() {

            for (int i = 1; i <= 5; i++) {

                System.out.print(i + " ");

            }

        }

    };

    Thread t = new Thread(r);

    t.start();

}}  

垃圾回收算法原理

转载自:https://blog.csdn.net/FateRuler/article/details/81158510

第一种:标记清除

它是最基础的收集算法。
原理:分为标记和清除两个阶段:首先标记出所有的需要回收的对象,在标记完成以后统一回收所有被标记的对象。
特点:(1)效率问题,标记和清除的效率都不高;(2)空间的问题,标记清除以后会产生大量不连续的空间碎片,空间碎片太多可能会导致程序运行过程需要分配较大的对象时候,无法找到足够连续内存而不得不提前触发一次垃圾收集。
地方 :适合在老年代进行垃圾回收,比如CMS收集器就是采用该算法进行回收的。

第二种:标记整理

原理:分为标记和整理两个阶段:首先标记出所有需要回收的对象,让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
特点:不会产生空间碎片,但是整理会花一定的时间。
地方:适合老年代进行垃圾收集,parallel Old(针对parallel scanvange gc的) gc和Serial old收集器就是采用该算法进行回收的。

第三种:复制算法

原理:它先将可用的内存按容量划分为大小相同的两块,每次只是用其中的一块。当这块内存用完了,就将还存活着的对象复制到另一块上面,然后把已经使用过的内存空间一次清理掉。
特点:没有内存碎片,只要移动堆顶指针,按顺序分配内存即可。代价是将内存缩小位原来的一半。
地方:适合新生代区进行垃圾回收。serial new,parallel new和parallel scanvage
收集器,就是采用该算法进行回收的。

复制算法改进思路:

由于新生代都是朝生夕死的,所以不需要1:1划分内存空间,可以将内存划分为一块较大的Eden和两块较小的Suvivor空间。每次使用Eden和其中一块Survivor。当回收的时候,将Eden和Survivor中还活着的对象一次性地复制到另一块Survivor空间上,最后清理掉Eden和刚才使用过的Suevivor空间。其中Eden和Suevivor的大小比例是8:1。缺点是需要老年代进行分配担保,如果第二块的Survovor空间不够的时候,需要对老年代进行垃圾回收,然后存储新生代的对象,这些新生代当然会直接进入来老年代。

优化收集方法的思路
分代收集算法
原理:根据对象存活的周期的不同将内存划分为几块,然后再选择合适的收集算法。
一般是把java堆分成新生代和老年代,这样就可以根据各个年待的特点采用最适合的收集算法。在新生代中,每次垃圾收集都会有大量的对象死去,只有少量存活,所以选用复制算法。老年代因为对象存活率高,没有额外空间对他进行分配担保,所以一般采用标记整理或者标记清除算法进行回收。

对于以上两种标记算法存在争议,在深入了解JVM最佳实践第二版中,是写的标记需要回收的对象,我也没太深入思考,直到有人提出来,我也去查了一下和想了一下。我个人现在偏向,标记存活的对象。
标记算法的大概流程:通过引用链给所有存活的对象做个标记,然后回收所有没有标记的对象 和 清除存活对象的标记,等待下一次GC

GC用的引用可达性分析算法中,哪些对象可作为GC Roots对象

先说一下可达性分析算法的思想:从一个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。

在java中可以作为GC Roots的对象有以下几种:

转载自:https://blog.csdn.net/ma345787383/article/details/77099522

虚拟机栈中引用的对象、方法区类静态属性引用的对象、方法区常量池引用的对象、本地方法栈JNI引用的对象
虽然这些算法可以判定一个对象是否能被回收,但是当满足上述条件时,一个对象 **不一定会被回收。**当一个对象不可达GC Roots时,这个对象并不会马上被回收,而是处于一个死缓的阶段,若要被真正的回收需要经历两次标记。如果对象在可达性分析中没有与GC Roots的引用链,那么此时就会被第一次标记并且进行一次筛选,筛选的条件是是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者已经被虚拟机调用过,那么就认为是没必要的。
如果该对象有必要执行finalize()方法,那么这个对象将会放在一个称为F-Queue的队列中,虚拟机会触发一个finalize()线程去执行,此线程是低优先级的,并且虚拟机不会承诺一直等待它运行完,这还是因为如果finalize()执行缓慢或者发生了死锁,那么就会造成F-Queue队列一直等待,造成了内存回收系统的崩溃。GC对处于F-Queue中的对象进行第二次被标记,这时,该对象将被移除“即将回收”集合,等待回收。

你可能感兴趣的:(java知识点汇总)