(整理)java面试题(二)基本概念

1、java中==和eqauls()的区别,equals()和hashcode的区别
详情请查看java中==和eqauls()的区别,equals()和hashcode的区别
==是运算符,用于比较两个变量是否相等,而equals是Object类的方法,用于比较两个对象是否相等。默认Object类的equals方法是比较两个对象的地址,此时和==的结果一样。换句话说:基本类型比较用==,比较的是他们的值。默认下,对象用==比较时,比较的是内存地址,如果需要比较对象内容,需要重写equal方法。

2、equals()和hashcode()的联系
hashCode()是Object类的一个方法,返回一个哈希值。如果两个对象根据equal()方法比较相等,那么调用这两个对象中任意一个对象的hashCode()方法必须产生相同的哈希值。
如果两个对象根据eqaul()方法比较不相等,那么产生的哈希值不一定相等(碰撞的情况下还是会相等的。)

3、a.hashCode()有什么用?与a.equals(b)有什么关系
hashCode() 方法是相应对象整型的 hash 值。它常用于基于 hash 的集合类,如 Hashtable、HashMap、LinkedHashMap等等。它与 equals() 方法关系特别紧密。根据 Java 规范,使用 equal() 方法来判断两个相等的对象,必须具有相同的 hashcode。

将对象放入到集合中时,首先判断要放入对象的hashcode是否已经在集合中存在,不存在则直接放入集合。如果hashcode相等,然后通过equal()方法判断要放入对象与集合中的任意对象是否相等:如果equal()判断不相等,直接将该元素放入集合中,否则不放入。

4、有没有可能两个不相等的对象有相同的hashcode
有可能,两个不相等的对象可能会有相同的 hashcode 值,这就是为什么在 hashmap 中会有冲突。如果两个对象相等,必须有相同的hashcode 值,反之不成立。

5、可以在hashcode中使用随机数字吗?
不行,因为同一对象的 hashcode 值必须是相同的

6、a==b与a.equals(b)有什么区别
如果a 和b 都是对象,则 a==b 是比较两个对象的引用,只有当 a 和 b 指向的是堆中的同一个对象才会返回 true,而 a.equals(b) 是进行逻辑比较,所以通常需要重写该方法来提供逻辑一致性的比较。例如,String 类重写 equals() 方法,所以可以用于两个不同对象,但是包含的字母相同的比较。

7、3*0.1==0.3返回值是什么
false,因为有些浮点数不能完全精确的表示出来。

public class Main {
public static void main(String[] args) {
System.out.print("0.1*1="+0.1*1+" ");System.out.println(0.1*1==.1);
System.out.print("0.1*2="+0.1*2+" ");System.out.println(0.1*2==.2);
System.out.print("0.1*3="+0.1*3+" ");System.out.println(0.1*3==.3);
System.out.print("0.1*4="+0.1*4+" ");System.out.println(0.1*4==.4);
System.out.print("0.1*5="+0.1*5+" ");System.out.println(0.1*5==.5);
System.out.print("0.1*6="+0.1*6+" ");System.out.println(0.1*6==.6);
System.out.print("0.1*7="+0.1*7+" ");System.out.println(0.1*7==.7);
System.out.print("0.1*8="+0.1*8+" ");System.out.println(0.1*8==.8);
System.out.print("0.1*9="+0.1*9+" ");System.out.println(0.1*9==.9);
}
}
运算结果:
0.1*1=0.1 true
0.1*2=0.2 true
0.1*3=0.30000000000000004 false
0.1*4=0.4 true
0.1*5=0.5 true
0.1*6=0.6000000000000001 false
0.1*7=0.7000000000000001 false
0.1*8=0.8 true
0.1*9=0.9 true

8、a=a+b与a+=b有什么区别吗?
+=操作符会进行隐式自动类型转换,此处a+=b隐式的将加操作的结果类型强制转换为持有结果的类型,而a=a+b则不会自动进行类型转换。如:

byte a = 127; 
byte b = 127;
b = a + b; // error : cannot convert from int to byte
b += a; // ok

9、short s1= 1; s1 = s1 + 1; 该段代码是否有错,有的话怎么改?
有错误,short类型在进行运算时会自动提升为int类型,也就是说s1+1的运算结果是int类型。
Java中的byte,short,char进行计算时都会提升为int类型。
10、& 和 &&的区别
首先记住&是位操作,而&&是逻辑运算符。另外需要记住逻辑运算符具有短路特性,而&不具备短路特性。

  • 使用&时,当前面的表达式为假的时候,程序依旧会继续执行后面的表达式,然后再得出FALSE的结果
  • 当使用&&(短路与)时,则相反,当前面的表达式结果为假时则不会再执行后面的表达式,直接得出FALSE的结果。
  • 当使用|时,若前面的表达式为真时,程序会继续执行后面的表达式,然后在得出TRUE的结果。
  • 当使用||(短路或)时,若前面的表达式结果为真,则程序不会再执行后面的表达式,直接得出TRUE的结果。
public class Main {
public static void main(String[] args) {
int i = 0;
if (10 != 10 & (i++) == 1) {

} else {
System.out.println("结果为假 " + i);
}
i = 0;
if (10 != 10 && (i++) == 1) {

} else {
System.out.println("结果为假 " + i);
}
i = 0;
if (10 == 10 | (i++) != 0) {
System.out.println("结果为真 " + i);
} else {
}
i = 0;
if (10 == 10 || (i++) != 0) {
System.out.println("结果为真 " + i);
} else {
}
}
}
运行结果:
结果为假 1
结果为假 0
结果为真 1
结果为真 0

11、一个java文件内部可以有类?(非内部类)

  • 只能有一个public公共类,但是可以有多个default修饰的类。
  • 在一个.java文件中可以有多个同级类, 其修饰符只可以public/abstract/final/和无修饰符
  • public修饰的只能有一个,且必须要与文件名相同;

因为jvm虚拟机为了提高查找类的速度,使用import语句导入的时候,只会导入对应空间的文件名所对应的class文件,而public文件是大家都要使用的,因此直接导入这个类名对应的class文件即可。

  • 若没有public的则可与文件名不同

Java编译器在编译的时候,如果整个Java文件(编译单元)都没有public类(对外的公开接口类),类加载器子就无需从这方面直接去加载该编译单元产生的所有的字节码文件(.class文件),那么也就是无需去寻找编译后字节码文件存放位置。而类名和文件名一致是为了方便虚拟机在相应的路径中找到相应的类所对应的字节码文件。所以在没有public类的Java文件中,文件名和类名都没什么联系。

  • 该文件同级的类之间可以互相调用,但是除了public的类,其他不能够在其他文件调用
  • 在一个.java文件中由类/Enum/接口/Anontation其中至少一个类型组成。单独一个方法/变量不能独自存在与文件中,所以公用方法的封装也是做成类方法。原因是java是类加载机制,需要编译一个java文件成多个class文件,当类来使用。
  • 用javac 编译这个.java文件的时候,它会给每一个类生成一个.class文件

12、如何正确的退出多层嵌套循环?

  • 使用标号和break
ok:
for(int i=0;i<10;i++) {
for(int j=0;j<10;j++) {
System.out.println("i=" + i + ",j=" + j);
if(j == 5) break ok;
}
}
  • break跳出当前循环,通过内部跳出条件控制跳出外部循环
for(int i=0;i<4;i++){
for(int j=0;j<5;j++){
System.out.println("i="+i+"; j="+j);
if(j==3) {
i=4;
break;
}
}
}
  • 抛出异常也可以跳出多重循环
try {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 5; j++) {
System.out.println("i=" + i + "; j=" + j);
if (j == 3) {
throw new Exception();
}
}
}
} catch (Exception e) {
System.out.println("e");
}
  • 通过在外层循环中添加标识符
boolean found = false;
for(int i=0;i<4&&!found;i++){
for(int j=0;j<5;j++){
System.out.println("i="+i+"; j="+j);
if(j==3) {
found=true;
break;
}
}
}

13、内部类的作用

内部类是指在一个外部类的内部再定义一个类,类名不需要和文件夹相同。内部类可以声明 public 、protected 、private 等访问限制,可以声明为 abstract的供其他内部类或外部类继承与扩展,或者声明为static 、final 的,也可以实现特定的接口(而外部顶级类即类名和文件名相同的只能使用 public 和 default)。static 的内部类行为上象一个独立的类,非 static 在行为上类似类的属性或方法且禁止声明 static 的方法。内部类可以访问外部类的所有方法与属性,但 static 的内部类只能访问外部类的静态属性与方法。

14、final, finalize和finally的不同之处

  • final
    用于修饰类、成员变量和成员方法。final修饰的类,不能被继承(String、StrngBuilder、StringBuffer、Math,不可变类),其中所有的方法都不能被重写,所有不能同时用abstract和final修饰(abstract修饰的是抽象类,抽象类是用于被子类继承的,和final起相反的作用);final修饰的方法不能被重写,但是子类可以用父类中final修饰的方法;final修饰的成员变量是不可变的,如果成员变量是基本数据类型,初始化之后成员变量的值不能被改变,如果成员变量是引用类型,那么它只能指向初始化时指向的那个对象,不能再指向别的对象,但是对象中的内容是允许改变的。

  • finally
    finally是在异常处理时提供finally块来执行任何清除操作。不管有没有异常被抛出、捕获都会被执行。try块中的内容是在无异常时执行到结束。catch块中的内容,是在try块内容发生catch所声明的异常时,跳转到catch块中执行。finally块则是无论异常是否发生都会执行finally块的内容,所以在代码逻辑中有需要无论发生什么都必须执行的代码,可以放在finally块中。

            try{
    System.out.println("try");
    throw new Exception();
    }catch (Exception e)
    {
    System.out.println("catch");
    }
    finally {
    System.out.println("finally");
    }
    运行结果:
    try
    catch
    finally
  • finalize
    finalize是方法名,java技术允许使用finalize()方法在垃圾收集器将对象从内存中清楚出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的,它是在Object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。

    public class Main {
    public static void main(String[] args) {

    A a1=new A(1);
    A a2=new A(2);
    new A(3);
    System.gc();

    }
    }
    class A{
    private int m_id;

    A(int id){
    this.m_id=id;
    }
    protected void finalize()
    {
    switch(m_id)
    {
    case 1:
    System.out.print("《飘》");
    break;
    case 2:
    System.out.print("《java程序设计教程》");
    break;
    case 3:
    System.out.print("《罗马假日》");
    break;
    default:
    System.out.print("未知书籍");
    break;
    }
    System.out.println("所对应的实例对象存储单位被收回");
    }
    }
    运行结果:
    《罗马假日》所对应的实例对象存储单位被收回

15、clone()是哪个类的方法?
java.lang.Cloneable 是一个标示性接口,不包含任何方法,clone 方法在 object 类中定义。并且需要知道 clone() 方法是一个本地方法,这意味着它是由 c 或 c++ 或 其他本地语言实现的。

16、深拷贝和浅拷贝的区别是什么?
浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。

深拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。

17、static都有哪些用法?

  • 用来修饰成员变量,将其变为类的成员,从而实现所有对象对于该成员的共享;
  • 用来修饰成员方法,将其变为类方法,可以直接使用“类名.方法名”的方式调用,常用于工具类;
  • 静态块用法,将多个类成员放在一起初始化,使得程序更加规整,其中理解对象的初始化过程非常关键;
public class Main {
public static void main(String[] args) {
new StaticCodeTest();
new StaticCodeTest();
}
}
class StaticCodeTest {
public StaticCodeTest(){
System.out.println("我是构造方法,我被执行了");
}
static {
System.out.println("我是static代码块,我被执行了,我只会被执行一次");
}
}
运行结果:
我是static代码块,我被执行了,我只会被执行一次
我是构造方法,我被执行了
我是构造方法,我被执行了

static代码块会在JVM加载类的时候执行,并且只会执行一次,应用较多的场景一般是项目启动的时候加载初始化配置。

  • 静态导包用法,将类的方法直接导入到当前类中,从而直接使用“方法名”即可调用类方法,更加方便。
package demo;
/**
* MyPrint.java
*/
public class MyPrint {
public static void myprint(String string)
{
System.out.println(string);
}
}
import static demo.MyPrint.*;
/**
* Main.java
*/
public class Main {
public static void main(String[] args) {
myprint("hehe");
}
}
运行结果:
hehe

18、final有哪些用法

final也是很多面试喜欢问的地方,能回答下以下三点就不错了:

  1. 被final修饰的类不可以被继承
  2. 被final修饰的方法不可以被重写
  3. 被final修饰的变量不可以被改变。如果修饰引用,那么表示引用不可变,引用指向的内容可变。
  4. 被final修饰的方法,JVM会尝试将其内联,以提高运行效率
  5. 被final修饰的常量,在编译阶段会存入常量池中

回答出编译器对final域要遵守的两个重排序规则更好:
1.在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
2.初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。

-------------本文结束感谢您的阅读-------------