java面试基础题

JAVA面试基础

        • 面向对象和面向过程的区别
        • 重载和重写
        • String
        • 自动装箱和拆箱
        • 静态方法
        • 构造方法
        • 成员变量和局部变量
        • 对象引用和对象实例
        • 返回值
        • == 与equals(重要)
        • hashcode()和equals()
        • 线程,进程和程序
        • final关键字的总结

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

​ 先理解什么是面向对象和什么是面向过程简单来说面向对象是指编程时以对象为中心,通过封装,继承,多态和实现等概念来实现功能,程序以类和对象组成,类包含一组属性和方法,对象是类的一个实例,通过在对象之间传递消息来实现数据的处理和传递。然后面向过程的话是以步骤为中心,代码主要包括函数和过程,而这些函数和过程都是独立的,他们通过传递参数来实现数据的处理和传递。

面向过程

优点:性能比面向对象高,因为面向对象中的对象调用时需要实例化,消耗资源。面向过程的话一般用于单片机,LInux,嵌入式开发等,这些都是有性能要求。

缺点:没有面向对象的易复用,易维护,易拓展

面向对象

优点:易维护,易拓展,易复用,面向对象包含继承,多态,封装等特性,设计出来的系统低耦合,系统更加灵活。

缺点:性能比面向过程低。

重载和重写

重写

发生在父子类之间,子类重写父类中的方法,方法名和参数必须相同,返回值,异常抛出的范围小于等于父类,否则向上转型后的类型不兼容,从而引发异常;子类对象可以调用父类中没被private修饰的方法,当子类中重写了父类中的某个方法,而子类对象想调用父类中的该方法,而不是重载后的方法,可以使用super关键字调用该方法;父类通常情况下不能调用子类中的方法。

重载

在一个类中存在多个相同名称的方法或构造函数,但是参数列表必须不同(参数的类型,名称,个数等),使用重载可以增加代码的灵活性,可读性。

String

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

可变性看是否被final关键字修饰

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

abstract class AbstractStringBuilder implements Appendable, CharSequence {
 char[] value;
 int count;
 AbstractStringBuilder() {
 }
 AbstractStringBuilder(int capacity) {
 value = new char[capacity];
 }

线程安全

线程是进程的执行单位,一个进程包含多个线程

先了解什么是线程安全,每个进程都会有一个共同的区域在内存中,叫做堆(内存),由于这个堆内存每个进程中的线程都可以访问里面的数据,在没有限制的情况下,数据存在被修改的风险。然后这个堆内存存储着对象实例,线程使用对象的引用调用对象的实例从而访问对象的成员变量和方法。

然后分析下String类,StringBuilder类,StringBuffer类是否是线程安全的?

首先String类他是由final修饰的字符数组组成的字符串,是一个常量,因此是线程安全的。

StringBuffer类他使用synchronized关键字来实现同步锁(互斥锁),synchronized修饰方法或者代码块,所以线程安全。就是当一个线程访问由synchronized修饰的方法或者代码块时,线程或尝试获取锁,如果获取成功则执行该方法,如果锁被其他线程占用,则该线程被阻塞,直到锁被释放为止。然后这个同步锁只能保证共享资源的互斥访问,不能保证数据是否正确,因为当多个线程访问同一个数据时,进行读写操作后,数据并不能保证没有被修改,因此可以增加原子性操作,来保证数据的一致性。

StringBulider类并未加同步锁,所以是非线程安全

性能

StringBuilder的性能比StringBuffer的性能高10%~15%,但是要冒着线程不安全的风险。每次对String对象进行改变时都会生成一个新的String对象然后将指针指向新的对象,对StringBuilder和StringBuffer对象进行操作时都是对原有的对象进行操作。

总结:

少量数据=String

单线程操作字符串缓冲区大量数据=StringBuilder

多线程操作字符串缓冲区大量数据=StringBuffer

自动装箱和拆箱

装箱

将基本数据类型用他们对应的引用类型包装起来

int q=1;
Integer w=q;

拆箱

将包装类型转换为基本数据类型

Integer q=10;
int w=q;

静态方法

问:在一个静态方法内调用一个非静态成员为什么是非法的

答:有不同的作用域和生命周期,静态方法直接用类名进行调用,无需创建类的实例,而非静态方法依赖于类的实例,如果你要在静态方法中调用非静态方法,需要先创建类的实例,再来调用非静态方法。

构造方法

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

答:在父子类中,当子类不用super()指定调用父类的构造方法,子类会默认调用无参构造,此时当父类不存在无参构造时,程序就会出现编译错误,解决方法就是在父类中加上一个无参构造。

成员变量和局部变量

问:成员变量和局部变量的区别

答:从语法形式上看,成员变量存在于类中,局部变量存在于方法或代码块中,成员变量可以被控制修饰符修饰,局部变量不能被访问控制修饰符和static修饰,但是两者都可以被final修饰。

从存储方式来看,成员变量因为在对象中因此存储在堆内存里,局部变量存储在栈内存中。

从变量在内存中的生存时间来看,成员变量随对象的创建而创建,局部变量随方法的执行而自动消失。

public class MyClass {
    private int x; // x 是一个成员变量
    public void setX(int value) {
        int b=1;  // b 是一个局部变量
        x = value;  
    }
    public int getX() {
        return x;
    }
}

对象引用和对象实例

问:创建一个对象用什么运算符?对象实例与对象引用有何不同?

使用new运算符来创建对象,对像实例存储在堆内存中,对象引用存储在栈内存中,一个对象实例可以被多个对象引用所指向(可以用 n 条绳子系住一个气球),一个对象引用指向一个或0个对象实例(一根绳子可以不系气球,也可以系一个气球)。

返回值

问:什么是返回值?返回值的作用是什么?

答:返回值就是获取方法体运行结束后返回的结果(前提是有结果),返回值的作用是接收结果,将其用于其他操作。

== 与equals(重要)

问:==和equals的特点

答:==如果比较的是基本数据类型的话,那么就是比较这两个数据的值是否相等,如果比较的是两个对象的话,那么比较的是这两个对象的内存地址是否相等。

​ equals判断两个对象是否相等,但是他有两种使用情况,第一种就是equals()方法没有被类覆盖,那么比较的是两个对象的内存地址,第二种类覆盖了equals()方法,通常都会使用重写该方法的方式去比较两个对象的值是否相等。

​ 然后的话String类重写了equals()方法,比较的是两个对象的值是否相等,Object类里面的equals()方法跟==一样比较的是两个类的内存地址是否相同,StringBuffer类和StringBuilder类并没有重写equals()方法,其比较的还是引用类型的地址。

public class test1 {
 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("aEQb");
 if (42 == 42.0) { // true
 System.out.println("true");
 }
 }
}

hashcode()和equals()

问:你有没有重写过hashcode()和equals()方法,为什么重写equals()方法必须重写hashcode()方法?

答:重写过,首先先了解下hashcode()方法的作用是什么,hashcode()方法用来获取哈希码,实际上就是返回一个int整数。这个哈希码的作用是用来获取对象在哈希表中的位置,hashcode()定义在JDK中的Object类中,因此意味着java中的任何类都包含hashcode()函数。这个哈希表存储着键值对,可以通过键快速检索出对应的值。

我通过hashSet来解释hashcode的作用,当我们存对象到hashset中时,hashset会先获取该对象的hashcode值来判断对象的存储位置,同时也会将其与已经加入对象的hashcode值作比较,如果不存在相同的hashcode值,则hashset同意加入该对象。如果存在hashcode值相等的对象,则会使用equals()方法来检查hashcode值相等的对象是否真的相同。如果不同,则hashset不会让加入操作成功,如果不同,则将该对象散列到其他位置。这样就大大减少了equals的次数,提高了执行速度。

如果两个对象相等,则hashcode一定相等

如果hashcode相同则用equals方法比较不一定相等

如果用equals比较相等的话,hashcode一定相等。

hashcode不相同则用equals方法比较一定不相等

hashcode用==比较int,equals比较对象地址,要保证相等的对象具有相同的hashcode。

线程,进程和程序

问:简述线程,进程,程序的基本概念。以及他们之间的关系是什么?

答:线程与进程相似,但是线程是一个比进程更小的执行单位。一个进程在执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一个内存空间和同一组系统资源,所以系统产生一个或多个线程,并在各个线程之间进行切换工作时,负担要比进程小得多,因此线程也被叫做轻量级进程。

​ 程序是含有指令和数据的文件,被存储在磁盘或者其他存储设备中,也就是说程序是静态的代码。

​ 进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序及是一个进程的创建,执行和消亡的过程。简单来说一个进程就是一个执行中的程序,他在计算机中一个指令一个指令的执行,同时每个进程还会占用某些系统资源,如cpu时间,内存空间,文件,输入输出设备等。线程是进程的执行单位,进程是程序的执行单位。

问:线程的状态

答:

1 、新建状态( New ):新创建了一个线程对象。
2 、就绪状态( Runnable ):线程对象创建后,其他线程调用了该对象的 start() 方法。
该状态的线程位于可运行线程池中,变得可运行,等待获取 CPU 的使用权。
3 、运行状态( Running ):就绪状态的线程获取了 CPU ,执行程序代码。
4 、阻塞状态( Blocked ):阻塞状态是线程因为某种原因放弃 CPU 使用权,暂时停止
运行。直到线程进入就绪状态,才有机会转到运行状态。
阻塞的情况分三种 :
(一)、等待阻塞:运行的线程执行 wait() 方法, JVM 会把该线程放入等待池中。 (wait
会释放持有的锁 )
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,
则 JVM 会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行 sleep() 或 join() 方法,或者发出了 I/O 请求时, JVM
会把该线程置为阻塞状态。当 sleep() 状态超时、 join() 等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。(注意,sleep 是不会释放持有的锁)
5 、死亡状态( Dead ):线程执行完了或者因异常退出了 run() 方法,该线程结束生命周期。

final关键字的总结

问:final关键字的作用是什么

答:final关键字一般用来修饰方法,类,变量

1.用来修饰变量的话有两种情况,当修饰的是基本数据类型的变量,那么该变量的值一旦完成初始化后就不能更改;如果修饰引用类型的变量,那么在完成初始化后,该变量不能指向其他对象。

2.用来修饰类的话,表明该类不能被继承,且该类中的方法隐式为final方法。

3.使用final方法有两个原因,第一个就是方法锁定,防止继承类修改该方法的含义;第二个原因是效率问题,在早期的java版本中,会把final方法内嵌调用,如果方法过于庞大,可能也难以看出内嵌调用带来效率的提升,现在的java版本已经不需要使用final方法来实现性能的优化了,类中的所有private方法都隐式的指定为final。

你可能感兴趣的:(java,面试,开发语言)