【转载】Java工程师面试题1-10

目录

1、抽象方法(abstract)是否可以同时是静态的?是否同时是本地的(native)?是否可以同时是synchronized?

2、静态变量和实例变量的区别是什么?

3、break和continue的区别是什么?

4、抽象类(abstract)和接口(interface)之间有什么异同?

5、复制对象和复制引用的区别?

6、什么是深拷贝什么是浅拷贝?

7、两个对象值相同(即x.equals(y) == true)是否可以有不同的hashCode?

8、String s = "Hello"; s = s + "World";执行完这两行代码后,原始String对象的内容变了吗?

9、Java中实现多态的机制是什么?

10、Java中的异常分类?


1、抽象方法(abstract)是否可以同时是静态的?是否同时是本地的(native)?是否可以同时是synchronized?

都不能。抽象方法需要子类重写,而静态的方法都是无法被重写的,因此二者是矛盾的。

本地方法由本地代码(C/C++)实现的,抽象方法是没有实现的,也是矛盾的。

synchronized和方法的实现细节有关系,抽象方法不涉及具体实现细节,因此也是互相矛盾的。

2、静态变量和实例变量的区别是什么?

静态变量:被static修饰符修饰的变量就是静态变量,也称为类变量,它是属于类的,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个。静态变量可以实现让多个对象共享内存。

实例变量:实例变量必须依存于某一具体的实例,需要首先创建对象,然后通过对象才可以访问到它。

3、break和continue的区别是什么?

break和continue都是用来控制循环语句的,break用于完全结束一个循环,跳出循环体执行循环后面的语句,continue用于跳出本次循环,继续判断循环条件是否满足,满足的话继续执行下次循环。

4、抽象类(abstract)和接口(interface)之间有什么异同?

不同点:

抽象类:

1.抽象类中可以定义构造器;

2.抽象类中可以有抽象方法和具体方法;

3.抽象类中可以定义成员变量;

4.有抽象方法的类必须被定义为抽象类,而抽象类中不必一定要有抽象方法;

5.抽象类中可以包含静态方法;

6.一个类只能继承一个抽象类;

7.抽象类中的成员可以是private、默认、protected、public。

接口:

1.接口中不能定义构造器;

2.接口中的方法全部是抽象方法,不包含具体实现;

3.接口中的成员全部是public的;

4.接口中定义的成员变量实际上都是常量;

5.接口中不能有静态方法;

6.一个类可以实现多个接口。

相同点:

1.接口和抽象类都不能实例化;

2.抽象类和接口类型都可以作为引用类型;

3.一个类如果继承了某个抽象类或者实现了某个接口,都需要对其中的抽象方法进行全部实现,否则该类仍然需要被声明为抽象类。

5、复制对象和复制引用的区别?

有如下程序:

Person p = new Person(23,"zhang");
Person p1 = p;
System.out.println(p);
System.out.println(p1);

程序执行后看打印结果:

com.xidian.Person@2f4ee1ac
com.xidian.Person@2f4ee1ac

可以看出,两个对象的地址相同,所以执行“Person p1 = p;”并不是创建了一个新对象,p和p1是同一个引用,他们都指向了一个相同的对象Person(23,"zhang")。我们把这种现象叫做引用的复制,上面的代码执行完成之后,内存中的情形如下图所示:

而下面的代码才是实现真正克隆了一个对象:

Person p = new Person(23,"zhang");
Person p1 = (Person) p.clone();
System.out.println(p);
System.out.println(p1);

打印结果如下:

com.xidian.Person@2f4ee1ac
com.xidian.Person@67f1fba0

从打印结果可以看出,两个对象地址是不同的,也就说创建了新的对象,而不是把原来的对象地址赋值给一个新的引用变量。执行完以上打码后,内存中的情形如下图所示:

6、什么是深拷贝什么是浅拷贝?

Person有两个成员变量,分别是name和age,name是String类型,age是int类型,代码非常简单,如下所示:

public class Person implements Cloneable {
    
    private int age;
    
    private String name;
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return (Person)super.clone();
    }
}

由于age是基本数据类型,那么对它的拷贝没什么疑义,直接将一个4字节的整数值拷贝过来就行了。但是name是String类型,他只是一个引用,指向一个真正的String对象,所以对他的拷贝方式有两种,分别是:直接将原对象中的name的引用值拷贝给新对象的name字段,或者是根据原对象的name指向的字符串对象创建一个新的,相同的字符串对象,将这个新字符串对象的引用赋值给新拷贝的Person对象的name字段。这两种方式分别叫做浅拷贝和深拷贝。深拷贝和浅拷贝示例图如下所示:

总结:如果两个Person对象的name的地址值相同,说明两个对象的name都指向同一个String对象,也就是浅拷贝,而如果两个对象的name地址值不相同,那么就说明指向不同的String对象了,也就是说在拷贝Person对象的时候,同时拷贝了name引用的String对象,也就是深拷贝。如果想要深拷贝一个对象,这个对象必须要实现Cloneable接口,实现clone方法,并且在clone方法内部,把该对象引用的其他对象也要clone一份,这就要求这个被引用的对象也必须要实现Cloneable接口并且实现clone方法。

7、两个对象值相同(即x.equals(y) == true)是否可以有不同的hashCode?

不可以。如果两个对象x和y满足x.equals(y) == true,他们的hashCode应当相同。

Java对于equals方法和hashCode方法是这样规定的:

(1)如果两个对象相同(equals方法返回true),那么他们的hashCode值一定相同。

(2)如果两个对象的hashCode相同,他们并不一定相同。

当然了,我们可以违背以上两个原则,但是如果违背了这两个原则,我们就会发现,再使用容器的时候,相同的对象可以同时出现在Set集合中,对于使用哈希存储的系统,哈希碰撞的概率会大大提升。

关于equals方法,首先equals方法必须要满足

自反性:x.equals(x) == true ;

对称性:x.equals(y) == true ==> y.equals(x) == true;

传递性:x.equals(y) == true && y.equals(z) == true ==> x.equals(z) == true;

一致性:x和y如果没有做任何修改时,多次调用x.equals(y)应该得到相同的值。

实现高质量equals方法的诀窍有:

1.使用“==”操作符检查参数是否为这个对象的引用;

2.使用instanceof操作符检查参数是否为正确的类型;

3.对于类中的关键属性,检查参数传入对象的属性是否与之相匹配;

4.编写完equals方法后,检查是否满足对称性、一致性、传递性;

5.重写equals方法时,总是要重写hashCode;

6.不要将equals方法参数中Object对象替换成其他类型,在重写时不要忘记加上@Override注解。

8、String s = "Hello"; s = s + "World";执行完这两行代码后,原始String对象的内容变了吗?

没有,因为String被设计成不可变类(immutable),所以它的所有对象都是不可变对象,在这两句代码中,s原先指向一个String对象,对象内容为Hello,然后我们对s进行了“+”操作,这时候,s不指向原来那个对象了,转而指向另外一个String对象了,这个对象的内容是“HelloWorld”,原来那个对象仍然存在于内存中,只是s这个引用变量不再指向它了。

所以,如果经常需要对字符串进行各种修改操作,那么使用String对象来代表字符串的话会引起很大的内存开销。因为String对象建立之后就不能再修改了,对一个不同的String就需要一个不同的String对象来表示。面对这种情况,应该考虑使用StringBuffer类,它允许修改,而不是每一个不同的字符串都要新创建一个对象来表示,并且这两种类的对象很容易进行转换。另外,如果要使用内容相同的字符串,不必每一次都new一个String,因为String对象不可变,所以对于内容相同的字符串,只要一个String对象来表示就可以了。

对于字符串常量,如果内容相同,Java认为他们代表同一个String对象,但是使用关键字new调用构造器的时候,无论内容是否相同,总是会创建一个新对象。至于为什么要把String对象设计成不可变类呢,这是由于它的用途决定的,不可变类有一个优点,比如因为他的对象是只读的,所以多线程并发访问的时候也不会有任何问题。

9、Java中实现多态的机制是什么?

Java中实现多态的机制靠的是父类或者接口定义的引用变量可以指向子类或者具体实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体事例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。

10、Java中的异常分类?

按照异常需要处理的时机可以将异常分为:

编译时异常(也称为强制异常)也叫做CheckedException;

运行时异常(也称为非强制异常)也叫做RunntimeException。

只有Java语言提供了Checked异常。Java认为Checked异常都是可以处理的异常,所以Java程序必须显式的处理,如果程序中存在Checked异常,该程序在编译的时候就会发出错误,无法通过。这体现了Java的设计哲学:没有完善错误处理的代码根本没有运行的机会。对Checked异常通常有两种处理方法,第一是当前方法知道如何处理该异常,则使用try...catch块来处理该异常,第二是不知道该如何处理,则在定义该方法的时候声明抛出该异常。

运行时异常只有当代码在运行的时候才发出的异常,编译时不需要try...catch块,RunntimeException比如除数是0和数组下标越界,其产生频繁,处理麻烦,若要声明或者捕获将会对程序的可读性和运行效率产生很大影响,所以交由系统自动检测并将它们交给缺省的异常处理程序。

猜你喜欢

转载自blog.csdn.net/Kevin_Gu6/article/details/88387786