Java面试/Java必会题/必刷题(1)

1、JDK、JRE、JVM三者之间的关系?

       JDKJAVA Development Kit(它是Java开发运行环境,在程序员的电脑上当然必要安装JDK) 不仅提供了Java程序运行所需的JRE,还提供了一系列的编译,运行等工具,如javac.exe,jar.exe等。

       JRE:  JAVA Runtime Environment(它是Java运行环境,如果你不需要开发,只需要运行Java程序【不是绝对的,比如使用JSP部署Web应用程序还是要JDK的】,那么你可以安装JRE)包含了java虚拟机java基础类库。

       JVMJAVA Virtual Machine(JAVA虚拟机)是运行Java字节码的虚拟机字节码不同系统的JVM是实现Java语言”一次编译,到处运行"的关键。

三者的包含关系如下图:

JDK = JRE + 开发工具集

JRE = JVM + JAVA的核心类库

2、面向对象和⾯向过程的区别

面向对象:面向对象易维护易复用易扩展。因为面向对象有封装继承多态性,但是面向对象性能比面向过程低

面向过程面向过程性能比面向对象高类需要实例化开辟空间,耗资源。但是面向过程不易维护、易复用、易扩展

3、Java和C++的区别

  • 都是面向对象的语言,都支持封装继承多态
  • Java不支持指针访问内存程序更安全
  • java是单继承(接口可以多继承),C++支持多继承
  • Java有自动内存管理机制,不需要程序猿手动释放内存
  • C语言中的字符串最后有一个额外的 '\0' 来表示结束,Java语言没有结束符。

4、对Java的加载与执行的理解

问题拆解:Java程序非常重要的哪两个阶段

编译阶段运行阶段

编译期间

Java程序员直接编写的Java代码源程序)是无法执行无法被JVM识别的。必须得经历一个编译:将这个 "普通文本" 转为 "字节码" (JVM能识别的"字节码"

普通文本转为字节码的过程是编译。xxx.java源代码符合语法规则就会编译通过,形成xxx.class文件

注意字节码不是二进制文件,如果是二进制文件,那还要JVM干嘛。

运行期间

JRE起作用JVM将.class字节码文件装载进去,对字节码进行解释(解释器逐行执行),转为二进制。后面JVM将生成的二进制码交给OS操作系统执行二进制码,操作系统就会执行二进制码和硬件进行交互。

在windows上可以运行,其他系统也可以运行:如果是在linux上运行,只要把生成的.class文件拷贝过去。在这个过程(转二进制到与硬件交互),没有.java文件任何事,即使现在删除也没关系(最好不要,你还会修改代码的呢),对字节码没有影响。

在这使用CMD编写代码过程:

javac 命令负责编译(JDK的bin目录下有javac.exe),java 命令负责运行 (JDK的bin目录下有java.exe)

5、Java变量分为哪几类

在Java中所有的变量分为:成员变量 局部变量

先了解它们的区别:成员变量是指这个的变量,局部变量是类中的方法内定义的变量

成员变量包括:实例变量  和 类变量  

实例变量是没有static修改的变量,类变量是指以static修饰的。

实例变量类变量的区别:

1)访问:实例变量是通过定义类的对象访问,类变量可以通过或者类的对象访问。

2)生命周期:实例变量与类的对象生命周期共存亡,类变量与共存亡。

3)位置:实例变量存放在中,类变量存放在方法区

局部变量包括:形参方法局部变量(在方法内定义)存放位置栈中

 局部变量成员变量的区别?

1、定义位置不同:
    局部变量:在方法的内部
    成员变量:在方法的外面,直接写在类当
2、作用域不同:
    局部变量:只有在方法中才能使用,出了方法就不能再用了
    成员变量:整个类全部可以用
3、默认值不一样
     局部变量:没有默认值,如果使用必须手动进行赋值
     成员变量:如果没有赋值会有一个起始值,规则和数组一样

6、&& 与 & 的区别是?|| 和 | 的区别是?

首先这个两者&&和&运算结果没什么区别,完全相同,只不过 && 会发生"短路"的现象:比如:Flag1 && Flag2,当Flag1为false是Flag2就不会判断。

|| 和 |也存在上述问题:如果第一个操作数是true,||运算符就返回true,无需考虑第二个操作数的值。

但&和|却不是这样,它们总是要计算两个操作数。性能效率就不如上面的两个。

重要区别简洁描述条件布尔运算符性能比较好。它检查第一个操作数的值,再根据该操作数的值进行操作可能根本就不处理第二个操作

7、++i和i++的小问题

++无论出现在变量前还是后,只要++运算结束变量一定会自加1.

int i = 10;
i++;
System.out.println(i); // 11

int k = 10;
++k;
System.out.println(k); // 11

i++使用变量的值,在执行++的操作++i执行++操作在使用变量的值

解开以上题目的窍门是什么拆分代码过程:

// ++  在变量的后面 如 i++
int i = 10;
int a = i++; // 拆分代码 int a = i; i= i++;
System.out.println(i); // 11
System.out.println(a); // 10

// ++ 在变量前面 ++i
int b = 10;
int c = ++b; // 拆分代码 b = b++; int c = b;
System.out.println(b); // 11
System.out.println(c); // 11

8、基本数据类型的运算问题

首先Java的数值有默认的类型

整数 :默认为int类型,比如 4 + 1;返回的类型是int类型

带有小数:默认为double类型,比如 1.2 + 1.2;返回的类型是double类型

理解基本数据类型运算关键:"低级别"数据类型 和 "高级别"数据类型运算得出的结果会自动转向"高级别"数据类型

byte,short,char 类型混合运算时,先各自转换成 int 类型再做运算

多说也记不住,先看看案例:

//案例1
char a = 97;
char b = 'B';
b = a + 1; //编译不通过的,需要强转;a + 1 这里的"高级别"类型是int,转为int结果,不可以赋值给char的

// 案例2
byte b1 =1;
byte b2 =2;
b1 =b1 + b2;//编译不通过,需要强转为int类型,byte,short,char 类型混合运算时,先各自转换成 int 类型再做运算
b2 +=b1;//编译通过,这个等同于 b2 = (byte)(b2+b1)

// 案例3
float f1 = 1.2; //不可以通过,因为1.2在Java中默认是double类型
float f2 = 1.2f;//通过
float f3 = 1.2f;//通过
f2 = 1.2+f3;   //不可以通过,需要强转为double类型,1.2为double类型
f3=1.2+1.4;   // 不可以通过编译,1.2+1.4都是double类型相加,需要强转

总结一下大致规则:

    1)第一条:八种基本数据类型中,除 boolean 类型不能转换,剩下七种类型之间都可以进行转换

    2)第二条:如果整数型字面量没有超出 byte,short,char 的取值范围,可以直接将其赋值给byte char 类,short,型的变量;

    3)第三条小容量向大容量转换称为自动类型转换,容量从小到大的排序为:byte < short(char) < int < long < float < double,其中 short和 char 都占用两个字节,但是char 可以表示更大的正整数

    4)第四条:大容量转换成小容量,称为强制类型转换,编写时必须添加“强制类型转换符”,但运行时可能出现精度损失,谨慎使用;

    5)第五条byte,short,char 类型混合运算时,先各自转换成 int 类型再做运算

    6)第六条多种数据类型混合运算,各自先转换成容量最大的那一种再做运算

9、⾃动装箱与拆箱

装箱:将基本类型用它们对应的引用类型包装起来。

拆箱:将包装类型转换基本类型

基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
booleanBoolean
charCharacter

在老版本的装箱:Integer i = new Integer(10);后面只需要 Integer i =5;

看一下简单代码:

public class Main {
    public static void main(String[] args) {
        Integer i =1;//装箱,1是int类型
        int n = i; // i是Integer类赋值给int
    }
}

 装箱过程的debug

拆箱过程的bebug

 得知自动调用静态方法Integer.valueof(int)装箱,在拆箱自动调用Integer.intValue方法。不用使用debug模式查看调用方法情况也可以使用反编译

装箱过程调用包装器的valueof方法实现的,拆箱过程是调用包装器的xxxValue方法包装类转基本类型:调用xxxValue()方法

面试问题

public class Main {
    public static void main(String[] args) {
        Integer i1 =1;// 底层是去方法去缓存池中找创建好的对象
        Integer i2 =1;// 底层是去方法去缓存池中找创建好的对象
        Integer i3 =300;// 底层还是 Integer i3 = new Integer(300); i3是引用,保存内存地址执行对象 
        Integer i4 =300;// 底层还是 Integer i4 = new Integer(300); i4是引用,保存内存地址执行对象
        System.out.println(i1==i2);//true
        System.out.println(i3==i4);//false
    }
}

 Java中为了提高程序的执行效率,将byte范围的[-128,127]之间所有的包装对象提前创建好,放到方法区的"缓存池"中,目的是 :只要在这个区间的数据不需要再new。直接从缓存池中取出。

i1i2变量保存对象的地址是一样,输出true。JVM图如下

查看源代码分析如下:下面这段代码是Integer的valueOf方法具体实现

@HotSpotIntrinsicCandidate
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

IntegerCache类的实现为:

private static class IntegerCache {
     static final int low = -128;
     static final int high;
     static final Integer[] cache;
     static Integer[] archivedCache;

     static {
       // high value may be configured by property
       int h = 127;
       String integerCacheHighPropValue =
           VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    h = Math.max(parseInt(integerCacheHighPropValue), 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
               } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
              }
        }
        high = h;

        // Load IntegerCache.archivedCache from archive, if possible
            VM.initializeFromArchive(IntegerCache.class);
        int size = (high - low) + 1;

        // Use the archived cache if it exists and is large enough
        if (archivedCache == null || size > archivedCache.length) {
           Integer[] c = new Integer[size];
           int j = low;
           for(int i = 0; i < c.length; i++) {
               c[i] = new Integer(j++);
            }
              archivedCache = c;
        }
        cache = archivedCache;
        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
}

 当Integer i1=1; 调用valueOf(1)判断i是否在[-128,127]之间,如果在这范围,去加载内部类IntegerCache,在类加载同时执行static代码块,这时就初始化缓存池创建256个对象。返回指向缓存池已经存在的对象引用;不在范围中就创建一个新对象(返回

Integer i2=1;也是和上面一样操作,这时它们会指向同一个对象的地址

触发自动拆箱、自动装箱:(使用debug模式可以清楚看到过程

自动装箱

1)插入基本数据类型到集合中(插入对象)

2)Integer i1 =1

3)基本类型包装类比较equals();变量转为包装类

自动拆箱

1)包装类之间进行 +-*/等运算,拆箱为基本类型

2)包装类基本类型执行==操作。

10、== 与 equals()重要

==:可以使用在基本类型变量引用类型变量,它是一个运算符

如果比较的是基本类型变量,比较的是两个变量保存的数据是否相等不一定要相同类型

如果比较的是引用类型变量,比较的是两个变量地址值是否相等即引用变量是否指向同一个对象实体

案例代码操练

public class Main1 {
    public static void main(String[] args) {
        A a1 = new A("A",20);
        A a2 = new A("A",20);
        double d = 5.0;
        int i = 5;
        System.out.println(d==i);// true
        System.out.println(a1==a2);//false,地址值不一样,保存的内容相同的
    }
}

是不是有疑问?为什么int类型的和double类型比较会相等,其实又回到上面的第8点,运算符操作前先把类型统一转为"高级别"类型,都变成double类型,在进行比较值是否相等。

equals():它属于java.lang.Object类型的方法,作⽤也是判断两个对象是否相等。一般分为两种情况:

  • 情况一:类没有重写equals()方法覆盖Object类的),则通过equals()比较,等价于"=="
  • 情况二:类重写了equals()方法,一般重写equals()方法比较对象的内容(类中相应的属性都相等否是否相等;内容相等就返回true。
public class test {
    public static void main(String[] args) {
        String a = new String("ab"); // a 为⼀个引⽤
        String b = new String("ab"); // b为另⼀个引⽤,对象的内容⼀样
        String aa = "ab"; // 放在常量池中
        String bb = "ab"; // 从常量池中查找
        if (aa == bb) // true
        System.out.println("aa==bb");
        if (a == b) // false,⾮同⼀对象
        System.out.println("a==b");
        if (a.equals(b)) // true
            System.out.println("a equals b");
        if (42 == 42.0) { // true
            System.out.println("true");
        }
    }
}

上面的String类中重写了equals()方法,不是比较两个引用对象的地址值,而是比较两个对象的"实体内容"是否相同。其实还有像FileDate包装类等都重写了equals()方法比较的都变成了实体内容。通常情况下,我们自定义的类如果使用equals()方法也需要重写equals()方法,这样也就比较对象的属性值是否都相等

 说了这么多,还是看看Object类中的equals()方法原型:

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

这个Object的equals()和==作用相同的,比较对象的地址值 

11 、hashCode 与 equals(重要)

   hashCode()介绍

hashCode()的作用是获取哈希码散列码)返回一个int类型。这个哈希码的作用确定该对象在哈希表中的索引位置

   hashCode与equals之间的区别

  • hashCode如果相等的情况下,对象的值不一定相等
  • 但是equals比较对象的内容相同,那么hashCode一定相等

  如果重写了equals()方法后,一定要重写hashCode()方法为什么

我们要遵循

hashCoed相等的情况下,对象的值不一定相等

但是equals比较对象的内容相同,那么hashCode一定相等,所有重写了equals()方法需要重写hashCode保证原则不变不然重写了equals()方法不重写hashCode方法两个对象内容相同,但是hashCode不同。

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

构造方法的作用是为堆区中的对象的属性初始化

Java程序在执行子类的构造器方法之前,如果没有使用super()调用父类的特定的构造方法,则会默认(不写super()也是默认写了调用父类中的无参构造方法,这时父类没有无参构造方法子类所有构造方法又没有调用super()调用父类的其他有参构造方法。子类编译时会报错。除非父类加上无参构造方法或者使用super调用其他有参构造方法一般使用在子类的有参构造方法中且必须出现在首行

既然体到super关键字,就顺便提一下:super的使用:子类重写了父类的方法后,在子类调用父类的方法必须加上super.方法,表明调用父类的方法。不加上super关键字默认是在子类中找,没找到再去父类中找

13、数组的初始化方式

第一种静态初始化:创建和赋值同时进行

int [] a  = new int[]{1,2,3,4,5}
//也或者
int str []={1,2,3,4};

第二种:动态初始化:数组定义与数组元素赋值分开进行

int a [] = new int[4];
a[0]=1;
a[1]=2;
a[2]=3;
a[3]=4;
//最常见错误方式
int bb [] = new int[2]{1,2};//错误❌

未完待续......

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值