Java面试大全2

移步原文网站,浏览最新博客。

1.java程序编译和运行的过程

(1)源文件编译器编译成字节码(×××.java->×××.class);

(2)字节码java虚拟机(JVM)解释运行。

2.JVM<JRE<JDK

JRE:java开发环境

JDK:java开发包(JRE,java工具和API)

3.如何运行一个.java文件

编译>javac ×××.java

运行>java ×××.class

4.java数据类型

(1)java引用数据类型

类(String,Class)

接口

数组

(2)八种基本数据类型

4种整数类型(8bit、16bit、32bit、64bit;byte、short、int、long)

2种浮点数类型(32bit、64bit;flout、double)

1种字符型(16bit;char)

1种布尔类型(8bit;boolean)

5.java虚拟机内存模型

image.png
程序计数器(线程私有):当线程数超过CPU数或CPU内核数时,线程之间就要根据时间片轮询抢夺CPU时间资源。为了线程切换之后能够恢复到正确的执行位置,每条线程都需要一个独立的程序计数器去记录其正在执行的字节码地址。

虚拟机栈(线程私有):描述的是java方法执行的内存执行。每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。而且每个方法从调用至完成的过程,对应一个栈帧在虚拟机中入栈到出栈的过程。

本地方法栈(线程私有):与虚拟机栈非常相似。区别是虚拟机栈执行java服务方法,而本地方法栈为虚拟机执行native方法服务(一个java调用非java代码的接口)。

java堆(线程共享):唯一目的存放内存实例。是垃圾收集器管理的主要区域,所以又称为GC堆。(java可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可)

方法区(线程共享):用于存储已被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码等数据。包含运行时常量池,用于存放编译期间生成的各种字面量(文字字符串、声明为final的常量)和符号引用。

6.Java内存模型

image.png

Java内存模型的主要目标是定义程序中变量的访问规则。即在虚拟机中将变量存储到主内存或者将变量从主内存取出这样的底层细节。需要注意的是这里的变量跟我们写java程序中的变量不是完全等同的。这里的变量是指实例字段静态字段构成数组对象的元素,但是不包括局部变量和方法参数(因为这是线程私有的)。这里可以简单的认为主内存是java虚拟机内存区域中的堆,局部变量和方法参数是在虚拟机栈中定义的。但是在堆中的变量如果在多线程中都使用,就涉及到了堆和不同虚拟机栈中变量的值的一致性问题了。

主内存->堆

局部变量和方法参数->虚拟机栈

Java内存模型中涉及到的概念有:

  • 主内存:java虚拟机规定所有的变量(不是程序中的变量)都必须在主内存中产生,为了方便理解,可以认为是堆区。可以与前面说的物理机的主内存相比,只不过物理机的主内存是整个机器的内存,而虚拟机的主内存是虚拟机内存中的一部分。
  • 工作内存:java虚拟机中每个线程都有自己的工作内存,该内存是线程私有的为了方便理解,可以认为是虚拟机栈。线程的工作内存保存了线程需要的变量在主内存中的副本。虚拟机规定,线程对主内存变量的修改必须在线程的工作内存中进行,不能直接读写主内存中的变量。不同的线程之间也不能相互访问对方的工作内存。如果线程之间需要传递变量的值,必须通过主内存来作为中介进行传递。

扩展
堆区:
1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身
栈区:
1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中
2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
方法区:
1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。

7.switch表达式的条件类型

byte、String、枚举、short、int、char。

8.java常见数据结构

ArrayLIst、Vector、LinkedList、HashMap、HashTable、TreeMap。

9.java面向对象

面向对象的核心:类和对象

面向对象的三大特征:封装、继承、多态

面向对象举例:人把大象装进冰箱

8.java变量分类

成员变量:方法外部、类的内部定义的变量。

​ 类变量(静态域)

​ 实例变量(heap)

局部变量:方法或语句块内部定义的变量

​ 形参(Stack)

​ 方法内局部变量(Stack)

​ 代码块局部变量(Stack)

9.java程序所有的运行都是在虚拟机中的进行的

10.java参数传递

java里方法的参数传递只有一种:值传递;

实际参数的副本传入方法内,参数本身不受影响;

基本类型数据作为参数是值拷贝;对象作为参数是传引用,可以认为是把对象的地址传过去。

11.java垃圾回收机制

11.1.内存分类

栈内存:超出作用域,java自动释放内存。

堆内存:由JVM的自动垃圾回收器管理。

11.2.如何确定一个对象是否可以回收

(1)引用计数法:对象无引用即可回收。(对象相互引用时无法回收)

(2)可达性分析法:判断对象的引用链是否可达来决定对象是否可以被回收。

11.3.垃圾收集算法

(1)标记清除算法:标记-清除算法分为标记和清除两个阶段。该算法首先从根集合进行扫描,对存活的对象标记,标记完毕后,再扫描整个空间中未被标记的对象并进行回收;

(2)复制算法:复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉;(事实上,现在商用的虚拟机都采用这种算法来回收新生代。因为研究发现,新生代中的对象每次回收都基本上只有10%左右的对象存活,所以需要复制的对象很少,效率还不错)

(3)标记整理算法:标记整理算法的标记过程类似标记清除算法,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存;

(4)分代收集算法:不同的对象的生命周期(存活情况)是不一样的,而不同生命周期的对象位于堆中不同的区域,因此对堆内存不同区域采用不同的策略进行回收可以提高 JVM 的执行效率。当代商用虚拟机使用的都是分代收集算法:新生代对象存活率低,就采用复制算法;老年代存活率高,就用标记清除算法或者标记整理算法。Java堆内存一般可以分为新生代、老年代和永久代三个模块。

12.java中类的构造器

java中父类构造器无法被子类继承

子类归根结底会调用父类的构造方法(当一个类继承了某个类时,在子类的构造方法里,super()必须先被调用;如果你没有写,编译器会自动调用super()方法,即调用了父类的构造方法)

this()可以作为一个类内构造器相互调用的特殊格式【this()必须放在构造器的首行】,保证至少有一个构造器不用this()。

13.java只支持单继承,不支持多继承

14.java修饰符作用域

image.png

15.java多态性

对象多态性:编译时类型和运行时类型不一致

方法的重载和重写:

​ 重载:同一个类内部存在一个以上同名方法

​ 重写:子类重写父类同名方法(相同返回值、名称、参数列表)

成员变量不具有多态性,只看引用变量所属的类。

16.java接口与抽象类

1.抽象类

在了解抽象类之前,先来了解一下抽象方法。抽象方法是一种特殊的方法:它只有声明,而没有具体的实现。抽象方法的声明格式为:

public abstract void open();

抽象方法必须使用abstract关键字进行修饰。如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract关键字修饰。因为抽象类中无具体实现的方法,所以不能用抽象类创建对象。
抽象类就是为了继承而存在的,如果你定义了一个抽象类,却不去继承它,那么等于白白创建了这个抽象类,因为你不能用它来做任何事情。包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法。注意,抽象类和普通类的主要有三点区别:

1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。

2)抽象类不能用来创建对象;

3)如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。

2.接口

在Java中,定一个接口的形式如下:

public interface TestInterface {
    public  void beforeHandle();
    public  void dealHandle();
    public void afterHandle();

}

接口中可以含有变量和方法。但是要注意:
接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误);
方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误);
并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量。要让一个类遵循某组特地的接口需要使用implements关键字,具体格式如下:

public class TestInterfaceImpl implements TestInterface{
    @Override
    public void dealHandle() {
        // TODO Auto-generated method stub
        
    }
    @Override
    public void beforeHandle() {
        // TODO Auto-generated method stub  
    }
    @Override
    public void afterHandle() {
        // TODO Auto-generated method stub  
    }

}

允许一个类遵循多个特定的接口,即接口允许多继承。如果一个非抽象类遵循了某个接口,就必须实现该接口中的所有方法。对于遵循某个接口的抽象类,可以不实现该接口中的抽象方法。
接口和抽象类的差异
接口可以实现多继承,而抽象类不可以;
接口中的方法必须是抽象方法(不能有具体的实现),而抽象类不是。

17.final关键字

1.修饰类

当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法

2.修饰方法

下面这段话摘自《Java编程思想》第四版第143页:
“使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。“
因此,如果只有在想明确禁止“该方法在子类中被覆盖”的情况下才将方法设置为final的。

3.修饰变量

对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象

18.java异常

1.异常分类

image.png

Error:java虚拟机无法处理的严重问题,主要是OutOfMemoryError。

Exception:程序内部可以解决的问题。

​ 编译时异常:要求程序员必须捕获和声明。

​ 运行时异常:Java编译器不会检查它,也就是说,当程序中出现这类异常时,也会编译通过。

2.异常处理机制

在 Java 应用程序中,异常处理机制为:抛出异常,捕捉异常

抛出异常的方法:throws和throw
throws:通常被用在声明方法时,用来指定方法可能抛出的异常,多个异常可使用逗号分隔。throws关键字将异常抛给上一级,如果不想处理该异常,可以继续向上抛出,但最终要有能够处理该异常的代码。
throw:通常用在方法体中或者用来抛出用户自定义异常,并且抛出一个异常对象。程序在执行到throw语句时立即停止,如果要捕捉throw抛出的异常,则必须使用try-catch语句块或者try-catch-finally语句。

捕获异常:try-catch-finally

19.String、StringBuffer、StringBuilder

String:不可变字符序列;(String是不可变的对象,因此每次在对String类进行改变的时候都会生成一个新的string对象,然后将指针指向新的string对象,所以经常要改变字符串长度的话不要使用string)

StringBuffer:可变字符序列,效率低,线程安全;

StringBuilder:可变字符序列,效率高,线程不安全。

20.java线程详解

1.线程生命周期

(1)新建状态(New)

当线程对象创建后,即进入新建状态,如:Thread t = new MyThread();

(2)就绪状态(Runnable)

当调用线程对象的start()方法时,线程即进入就绪状态。处于就绪状态的线程只是说明此线程已经做好准备,随时等待CPU调度执行,并不是说执行了start()方法就立即执行。

(3)运行状态(Running)

当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。

(4)阻塞状态(Blocked)

处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。
阻塞状态分类
等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程占用),它会进入到同步阻塞状态;
其他阻塞:通过调用线程的sleep()或join()或发出I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
(5)死亡状态

线程执行完毕或者是异常退出,该线程结束生命周期。

2.线程安全

线程安全的优先级高于性能;

线程不安全——线程的调度顺序会影响到最终结果。

3.线程的调度方式

抢占式(按优先级先后顺序占用CPU时间资源)

时间片轮询

4.如何创建线程

(1) 继承Thread类并覆盖run()方法
//定义线程类
public class MyThread extends Thread{
    run(){
        System.out.println("This is myThread");
    }
}

//线程是通过start()方法调用的
new MyThread().start();
(2)实现Runnalbe接口并重写run()方法
//定义线程类
public class MyThread implements Runnable{
    run(){
        System.out.println("This is myThread");
    }
}
//线程是通过start()方法调用的
new MyThread().start();

21.包装类

java是面向对象的,但是java中的基本数据类型却不是面向对象的。因此在设计类时为每个基本数据类型设计了一个对应的类进行代表,即包装类。

byte——Byte

boolean——Boolean

short——Short

char——Charactor

int——Integer

long——Long

float——Float

double——Double

equals==都是比较指向对象是否相同的,但包装类和String重写的object.equals()方法,比较的是内容。

22.非静态内部类和内部静态类的区别

一、静态内部类的使用目的

在定义内部类的时候,在其前面加上一个权限修饰符static。这个内部类就变为了静态内部类。如在进行代码程序测试的时候,如果在每一个Java源文件中都设置一个主方法(主方法是某个应用程序的入口,必须具有),那么会出现很多额外的代码。而且最主要的是这段主程序的代码对于Java文件来说,只是一个形式,其本身并不需要这种主方法。但是少了这个主方法又是万万不行的。在这种情况下,就可以将主方法写入到静态内部类中,从而不用为每个Java源文件都设置一个类似的主方法。这对于代码测试是非常有用的。在一些中大型的应用程序开发中,则是一个常用的技术手段。

二、静态内部类的使用限制

将某个内部类定义为静态类,跟将其他类定义为静态类的方法基本相同,引用规则也基本一致。不过其细节方面仍然有很大的不同。具体来说,主要有如下几个地方要引起各位程序开发人员的注意。

一是静态成员(包括静态变量与静态成员)的定义。

在非静态内部类中不可以声明静态成员。如现在在一个student类中定义了一个内部类Age,如果没有将这个类利用static关键字修饰,即没有定义为静态类,那么在这个内部类中如果要利用static关键字来修饰某个成员方法或者成员变量是不允许的。在编译的时候就通不过。故程序开发人员需要注意,只有静态内部类才能够定义静态的成员变量与成员方法。

二是在成员的引用上,有比较大的限制。

一般的非静态内部类,可以随意的访问外部类中的成员变量与成员方法。即使这些成员方法被修饰为private(私有的成员变量或者方法)。因为在其他类中是无法访问被定义为私有的成员变量或方法。但是如果一个内部类被定义为静态的,那么在引用外部类的成员方法或者成员变量的时候,就会有诸多的限制。如不能够从静态内部类的对象中访问外部类的非静态成员(包括成员变量与成员方法)。这是什么意思呢?如果在外部类中定义了两个变量,一个是非静态的变量,一个是静态的变量。静态内部类只能引用外部类中的静态的成员(变量或方法),而不能够访问非静态的变量。对于那些在外部类中的非静态的成员变量与成员方法,在静态内部类中是无法访问的。这就是静态内部类的最大使用限制。在普通的非静态内部类中是没有这个限制的。也正是这个原因,决定了静态内部类只应用在一些特定的场合。其应用范围远远没有像非静态的内部类那样广泛。

三是在创建静态内部类时不需要将静态内部类的实例绑定在外部类的实例上

通常情况下,在一个类中创建成员内部类的时候,有一个强制性的规定,即内部类的实例一定要绑定在外部类的实例中。也就是说,在创建内部类之前要先在外部类中要利用new关键字来创建这个内部类的对象。如此的话如果从外部类中初始化一个内部类对象,那么内部类对象就会绑定在外部类对象上。也就是说,普通非静态内部类的对象是依附在外部类对象之中的。通常情况下,程序员在定义静态内部类的时候,是不需要定义绑定在外部类的实例上的。也就是说,要在一个外部类中定义一个静态的内部类,不需要利用关键字new来创建内部类的实例。

从以上的分析中可以看出,静态内部类与非静态的内部类还是有很大的不同的。一般程序开发人员可以这么理解,非静态的内部类对象隐式地在外部类中保存了一个引用,指向创建它的外部类对象。

非静态内部类实例:

package common.lang;

public class Student {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public class Child{
        private String name1;
        private int age1;

        public String getName1() {
            return name1;
        }
        public void setName1(String name1) {
            this.name1 = name1;
        }
        public int getAge1() {
            System.out.println(age);
            return age1;
        }
        public void setAge1(int age1) {
            this.age1 = age1;
        }


    }

    public static void main(String[] args) {
        Student s = new Student();
        s.setAge(12);
        s.setName("yao");
        Child c = s.new Child();
        System.out.println(c.getAge1());
    }
}

静态内部类实例:

package common.lang;

public class Student {


    private String name;
    private static int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    public static int getAge() {
        return age;
    }

    public static void setAge(int age) {
        Student.age = age;
    }


    public static class Child{
        private String name1;
        private int age1;

        public String getName1() {
            return name1;
        }
        public void setName1(String name1) {
            this.name1 = name1;
        }
        public int getAge1() {
            System.out.println(age);
            return age1;
        }
        public void setAge1(int age1) {
            this.age1 = age1;
        }


    }

    public static void main(String[] args) {
        Student s = new Student();
        Child c = new Child();
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

red-fox-yj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值