Java Guide 基础面试题

1. 面向对象和面向过程的区别

  • 面向过程:面向过程的性能比面向对象是要高的,虽然面向过程也需要分配内存,计算内存偏移量,但是面向对象类调用时的实例化开销更大,更消耗资源。

  • 面向对象:因为面向对象存在封装,继承,多态的特性,所以面向对象易扩展,易维护,易复用,但是性能相较于面向过程要低一些。

而Java性能差的根本原因是java是半编译语言,最终的执行代码不是可以直接被cpu运行的二进制机械码,而大多数面向过程语言的最终执行代码都是直接编译成机械码被cpu执行。

2. 关于jvm jdk jre的解答

JVM:

是运行java字节码(.class)的虚拟机,针对不同的系统存在特定的实现,目的是为了跨平台。

java程序从源代码(.java)到运行的步骤:
在这里插入图片描述
以前,.class–>机器码的过程中,jvm类加载器首先加载字节码文件,然后通过解释器逐行解释执行。这种方法执行速度相对缓慢,而且,有些方法和代码块是需要被经常调用的(热点代码)
后来,引进了JIT编译器,JIT属于运行时编译,当JIT编译器完成第一次编译后,其会将热点代码的字节码对应的机器码保存,用于下次直接使用。这种情况下,热点代码就属于编译执行而不是解释执行了。

JDK & JRE
  • jdk:包含了jre,还有编译器javac和工具javadoc,jdb等。

  • jre:java运行环境,它是运行已编译java程序所需要的所有内容的合集,包括JVM,Java类库,java命令和一些基础构建。

3. Java和c++的区别

  • java不提供指针来直接访问内存,程序内存更加安全
  • java只能单继承(可以多实现接口),c++支持多重继承
  • java由内存管理机制,c++需要手动释放内存

4. 字符型常量和字符串常量有什么区别

  • 形式上:字符型常量 ‘A’ ,字符串常量 “ABC”
  • 含义上:字符型常量相当于一个整形值(ASCII),可以参与表达式运算;字符串常量代表一个内存地址
  • 占内存大小:字符型通常只占2个字节,字符串常量占若干个字节

5. 构造器是否可以被override(重写)

继承时就讲过,父类的私有属性和构造方法不能被继承,SB问题

6. 重载和重写的区别

  • 重载:发生在同一个类中,方法名字必须相同,参数不同,返回值和修饰符可以不同,参考构造方法。

  • 重写:发生在父子类中,方法名字,参数列表必须相同,返回值和抛出异常的范围欸必须小于等于父类,访问修饰符范围必须大于父类,如果父类访问修饰符为private,则子类不能重写该方法。

7. Java面向对象编程三大特性:封装 继承 多态

  • 封装
    封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法。

  • 继承
    继承是使用已存在的类(父类)的定义作为基础建立新类(子类)的技术,拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。子类也可以对父类进行扩展。通过使用继承我们能够非常方便地复用以前的代码。

  • 多态
    多态简单来说就是一个接口,因为使用不同的实例而执行不同的操作。
    比较难理解所以通过一个继承实现多态的实例进行解释:

父类Employee

class Employee{
    private String name;
    private String addr;
    private int id;
	
    public void mailCheck(){
        System.out.println("邮寄支票给:"+this.name + " "+this.addr);
    }

    public Employee() {
    }

    public Employee(String name, String addr, int id) {
        System.out.println("Employee 构造");
        this.name = name;
        this.addr = addr;
        this.id = id;
    }

    public get and set ...
}

子类Salary 继承了 Employee,获得了Employee的所有属性和方法,增加了属性sal,修改了方法mailCheck

class Salary extends Employee {
    private double sal;

    public Salary(String name, String addr, int num, double sal) {
        super(name, addr, num);
        this.sal = sal;
    }

    @Override
    public void mailCheck() {
        System.out.println("Sal类的mailCheck");
        System.out.println("邮寄支票给:"+ getName() +" "+ sal);
    }

    public get and set ...    
}
  • 执行Demo,实例化了两个Salary对象,一个使用Salary引用s,一个使用Employee引用e。
  • 当调用s.mailCheck()时,编译器在编译时会在Salary类中寻找mailCheck(),执行过程 JVM 就调用 Salary 类的 mailCheck()。
  • 当调用e.mailCheck()时,编译器在编译时会在Employee类中寻找mailCheck(),也就是先检查父类中是否存在该方法,如果不存在,则编译错误;如果有,则调用子类的同名方法。所以执行过程JVM 就调用 Salary 类的 mailCheck()。

也就是我们常说的,编译看左边,运行看右边

public class Demo11 {
    public static void main(String[] args) {
        Salary s = new Salary("员工A","beijing",3,3600.00);
        Employee e = new Salary("员工B","shanghai",2,2400.00);
        s.mailCheck();
        e.mailCheck();
    }
}

执行结果

Employee 构造
Employee 构造
Sal类的mailCheck
邮寄支票给:员工A 3600.0
Sal类的mailCheck
邮寄支票给:员工B 2400.0

8. String StringBuffer和StringBuilder的区别是什么?String为什么是不可变的?

可变性

String类中使用final关键字修饰字符数组来保存字符串,private final char value[ ],所以String对象是不可变的。
而StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[ ] value,但是没有用final关键字修饰,所以这两种对象都是可变的。

线程安全性

String中的对象是不可变的,也就可以理解为常量,线程安全。
AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity,append,insert,indexOf等公共方法。 StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。

性能

每次对String类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String对象。
StringBuffer/Builder每次都会对其对象本身进行操作,而不是生成新的对象并改变对象引用。StringBuilder相比使用StringBuffer仅能获得10%~15%左右的性能提升,但却要冒多线程不安全的风险。

对于三者使用的总结:
操作少量的数据:适用String
单线程操作字符串缓冲区下操作大量数据:适用StringBuilder
多线程操作字符串缓冲区下操作大量数据:适用StringBuffer

9. 在一个静态方法内调用一个非静态成员为什么是非法的?

静态方法属于整个类,而不是某个对象,也就是被该类下的所有对象共享。静态成员可以使用类名直接访问,不需要实例化类来调用。
简单来说,把类比作一个设计图,静态方法或属性作为该设计图的一部分;把实例对象比作完成品。我们不能在设计图中找到任何完成品的属性,因为每一个完成品都是不同的。

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

回想以下我们在通过类创建一个对象时,会通过调用它的构造方法完成创建。如果该类是某个父类的子类,还会在构造方法中super()隐式调用父类的无参构造方法。

且如果父类中只定义了特定的有参构造方法,没有重载无参构造(如果不添加构造的情况下,会默认隐式创建一个无参构造),而在子类的构造方法中没有用super()调用父类特定的构造方法,则编译时将发生错误,因为Java程序在父类中找不到无参构造方法执行。

11. 接口和抽象类的区别

参数抽象类接口
默认的方法实现可以有非抽象的方法实现接口只定义抽象方法,接口的所有方法默认会隐式加上public abstract(JDK1.8 可以在接口中定义静态方法)
实现子类使用extends关键字继承抽象类,且必须重写抽象父类的抽象方法子类使用关键字 implements 来实现接口。它需要提供接口中所有声明的方法的实现
与普通java类的区别抽象类不能实例化接口可以实例化,但接口并不是一个类,它和类同属于java数据类型的引用类型
构造器抽象类可以有构造器接口不是类不存在构造器
访问修饰符抽象方法可以有public protected default 修饰符,但不能使用private,因为抽象方法目的就是为了重写接口方法默认修饰符是 public,不可以使用其它修饰符
main方法接口中可以有main,所以能够运行抽象类接口没有 main 方法,因此不能运行接口
效率比接口效率高接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法
变量任意接口中所有变量必须为static final

12. 成员变量和局部变量区别

属性成员变量局部变量
语法形式成员变量属于类局部变量是在方法中定义的变量或方法的参数
访问修饰符成员变量可以被各种修饰符修饰局部变量不能被访问控制修饰符以及静态修饰,除了final
存储方式属于实例对象,存在于堆内存存在于栈内存
生存时间随对象的创建而存在随方法的调用而消失
赋值成员变量会根据其类型默认赋值局部变量不会自动赋值

13. 创建一个对象用什么运算符?对象实体与对象引用有何不同?

new运算符,new创建对象实例并存储在堆内存,对象引用则指向对象实例(对象引用存放在栈内存)
比较难理解所以通过实例说明:

public class Demo{
	public Demo{}//默认构造
}

接下来用Demo类创建一个对象

Demo demo = new Demo();

这个类实例化语句包含了4个动作:

  1. 右边的new Demo是以Demo类为模板,在堆内存中创建了一个Demo对象
  2. 末尾的()意味着,在对象创建后,立即调用Demo类的构造函数,对刚生成的对象进行初始化
  3. 左边的Demo demo创建了一个Demo类的引用变量demo,它存放在栈内存,用于指向Demo对象
  4. "="操作符使对象引用指向刚创建的Demo对象的内存地址
    在这里插入图片描述
    一个对象引用可以指向0-1个对象;一个对象实体可以有n个引用指向它。

14. 什么是方法的返回值?返回值在方法中的作用

方法的返回值是我们获取到某个方法体中代码执行后产生的结果。
其作用是接收结果,使其可以用于其他操作

15. 一个类的构造方法作用?若一个类没有声明构造方法,程序能否正确执行?

构造方法主要是完成对类对象的初始化工作,可以参考13题中对象实体在堆内存的创建过程。

一个类即使没有显式声明构造方法也会存在默认无参构造方法。

16. 构造方法特性

无返回值
名字与类名相同
生成类的对象时自动执行,不需要调用

17. 静态方法和实例方法的区别

属性静态方法实体方法
外部调用通过"类名.方法名"/“对象名.方法名”通过"对象名.方法名"
访问范围静态方法在访问本类成员时,只能访问静态成员实例方法无限制

18. 对象的相等和指向它们的引用相等,两者有何不同

对象的相等,比较的是内存中存放的内容是否相等。
对象的引用相等,比较的是内存中的地址是否相等。

19. 在调用子类构造方法之前会先调用父类的无参构造,其目的

帮助子类做初始化工作

20. ==与equals

==:判断两个对象地址是否相等,是否为同一个对象(基本数据类型比较值,引用数据类型比较地址)
equals():

  • 情况1:类没有override equals方法,那么它将继承Object类的equals方法
boolean equals(Object o){
	return this==o;
}

这说明,如果一个类没有override equals方法,默认的equals也就等同于"=="

  • 情况2:类覆盖了 equals() 方法。一般我们都通过override equals()来比较两个对象的内容是否相等;若相等返回ture。

String的equals方法是被重写的,因为object的equals()是比较对象的内存地址,而String的equals()经过重写后比较的是对象的值

//string重写的equals方法
public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

当创建String类型的对象时,JVM会在常量池中查找是否有存在和要创建的值相同的字符串对象,如果有就把它赋给当前引用,如果没有就在常量池重新创建。

21. hashCode 与 equals。为什么重写equals时必须重写hashCode方法?

hashCode():

其作用时获取哈希码,实际上返回的是一个int整数,这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 在定义JDK的Object中,这就意味着Java中的任何类都包含有hashCode的函数。

//这是Object类中定义的hashCode方法,且是一个本地方法
public native int hashCode();
为什么要使用hashCode:

以HashSet如何查重为例,说明为什么需要hashCode:当把对象加入到HashSet时,HashSet会先计算对象的hash值判断对象加入的位置,同时也会与其他已经加入的对象的hash值进行比较,如果没有相同的hash值,HashSet会假设对象没有重复;但如果发现有相同hash值的对象,会调用equals()检查hash值相等的对象是否相同。如果相同则不让其加入,如果不同则重新散列到其他位置。
想象以下,如果没有hashCode,在HashSet加入对象的时候,需要一个一个地进行对象equals()比较,这样效率就会非常低。现在我们只需要通过比较hashCode就可以完成HashSet的查重操作。

通过我们可以看出:hashCode() 的作用就是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode()在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。

hashCode()与equals()的相关规定

1:如果两个对象的hash值相等,两个对象不一定相同
2:如果两个对象相同,hash值一定相同
3:两个对象相等,对两个对象调用equals()返回true
4:如果重写了equals,则hashCode也必须重写
5:hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值