java后端面试题:Java基础
1、解释一下什么是面向对象?面向对象和面向过程的区别?
面向对象是一种基于面向过程的编程思想,是向现实世界模型的自然延伸,这是一种“万物皆对象”的编程思想。由执行者变成指挥者,在现实生活中任何物体都可以归为一类事物,而每一个个人都是一类事物的实例。面向对象的编程是以对象为中心,以消息为驱动。
区别:
(1)编程思路不同:面向过程以实现功能的函数开发为主,而面向对象首先要抽象出类、属性以及方法,然后通过实例化类、执行方法来完成功能。
(2)封装性:都具有封装性,但是面向过程封装的是功能,而面向对象封装的是数据和功能。
(3)面向对象具有继承性和多态性,但是面向过程没有继承性和多态性,所以面向对象的优势更加明显。
2、面向对象的三大特性?分别解释一下?
(1)封装:通常认为封装是把数据和操作数据的方法封装起来,对数据的访问只能通过已定义的接口。
(2)继承:继承是从已有类得到继承的信息创建新类的过程。提供继承信息的类称为父类(超类/基类),得到继承信息的被称为子类(派生类)。
(3)多态:分为编译时多态(方法重装)和运行时多态(方法重写)。要实现多态需要做两件事:一是子类继承父类并重写父类中的方法,二是父类引用子类对象(比如User user = new Student()),这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为。
关于继承的几点补充:
(1)子类拥有父类对象的所有属性和方法(包括私有属性和私有方法),但是父类中的私有属性和私有方法子类无法访问,只是拥有。因为在一个子类被创建的时候,首先会在内存中创建一个父类对象,然后在父类对象的外部放上子类独有的属性,两者和起来形成一个子类的对象。
(2)子类可以拥有自己的属性和方法。
(3)子类可以用自己的方式实现父类的方法。(重写)
3、JDK、JRE、JVM三者之间的关系?
JDK(Java Development Kit):是Java开发工具包,是整个Java的核心,包括了Java运行环境JRE,Java工具和Java基础类库。
JRE(Java Runtime Environment):是Java的运行环境,包含JVM标准实现及Java核心类库。
JVM(Java Virtual Machine):是Java虚拟机,是整个Java实现跨平台的最核心的部分,能够运行以Java语言写作的软件程序。所有的Java程序首先会被编译成.class的类文件,这种类文件可以在虚拟机上执行。
4、重载和重写的区别?
(1)重载:编译时多态、同一类中同名的方法具有不同的参数列表、不能根据返回类型进行区分【原因:因为函数调用时不能指定类型信息,编译器不知道你要调用哪个函数】。
(2)重写(又名覆盖):运行时多态、子类和父类之间、子类重写父类的方法具有相同的返回类型、更好的权限访问。
5、Java中是否可以重写一个private或者static方法?
Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。
Java中也不可以覆盖private方法,因为private修饰的变量和方法只能在当前类中使用,如果是其他的类继承当前类是不能访问到private变量或方法的,当然也不能覆盖。
静态方法的补充:
静态的方法可以被继承,但是不能重写。如果父类和子类中存在想同名称和参数的静态方法,那么该子类的方法会把原来继承过来的父类的方法隐藏,而不是覆盖。通俗来讲就是父类的方法和子类的方法是两个没有关系的方法,具体调用哪一个方法是看哪个对象引用;这种父子类方法也不在存在多态的性质。
6、构造器是否可以被重写?
在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以Constructor也就不能被Override(重写),但是可以被Overload(重载),所以你可以看到一个类中有多个构造函数的情况。
7、构造方法有哪些特性?
(1)名字和类名相同;
(2)没有返回值,但不能用void声明构造函数;
(3)成类的对象时自动执行,无需调用。
8、在Java中定义一个不做事且没有参数的构造方法有什么作用?
Java程序在执行子类构造方法之前,如果没有用super()来调用父类特定的构造方法,则会默认调用父类中“无参构造方法”。
因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用特定的构造方法,则编译时讲发生错误,因为Java程序在父类中找不到无参构造方法可供执行。
解决方法:在父类中加一个不做事且没有参数的构造方法。
9、Java中创建对象的几种方式?
(1)使用new关键字;
(2)使用Class类的newInstance方法,该方法调用无参的构造器创建对象(反射):Class.forName.newInstance();
(3)使用clone();
(4)反序列化,比如调用ObjectInputStream类的readObject()方法。
10、抽象类和接口有什么区别?
(1)抽象类中可以定义构造方法,而接口中不可以定义构造方法;
(2)抽象类中可以有抽象方法和具体方法,而接口中只能有抽象方法(public abstract);
(3)抽象类中的成员权限可以是public、默认(default)、protected(抽象类中的抽象方法就是为了重写,所以不能被private修饰),而接口中的成员只可以是public(方法默认:public abstact,成员变狼默认:public static final);
(4)抽象类中可以包含静态方法,而接口中不可以包含静态方法。
JDK8中的改变:
(1)在JDK1.8中,允许在接口中包含带有具体实现的方法,使用default修是,这类方法就是默认方法。
(2)在JDK1.8之前接口中不能包含静态方法,而JDK1.8以后可以包含。之前不能包含是因为接口中不可以实现方法,只可以定义方法,所以不能使用静态方法(因为静态方法必须实现)。现在可以包含了,只能用接口直接调用静态方法(如:接口名调用静态方法)。JDK1.8仍然不可以包含静态代码块。
11、静态变量和实例变量的区别?
静态变量:是被static修饰的变量,而称为类变量,它属于类,因此不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;静态变量可以实现让多个对象共享内存。
实例变量:属于某一实例,需要先创建对象,然后通过对象才能访问到它。
12、short s1 = 1; s1 = s1 + 1; 有什么错?那么short s1= 1; s1 += 1; 呢?有没有什么错误?
对于 short s1 = 1; s1 = s1 + 1; 来说,在 s1 + 1 运算时会自动提升表达式的类型为int,那么将int型值赋值给short型变量,s1会出现类型转换错误。
对于 short s1= 1; s1 += 1; 来说,+= 是Java语言规定的运算符,Java编译器会对它进行特殊处理,因此可以正确编译。
13、Integet和int的区别?
(1)int是Java的八种基本数据类型之一,而Integer是Java为int类型的提供的封装类;
(2)int型变量的默认值是0,Integer变量的默认值是null,这一点说明Integer可以区分出未赋值和值为0的区分;
(3)Integer变量必须实例化后才可以使用,而int不需要。
关于Integer和int的比较的延伸:
(1)由于 Integer 变量实际上是对一个 Integer 对象的引用,所以两个通过 new 生成的 Integer 变量永远是不相等的,因为其内存地址是不同的;
(2)Integer 变量和 int 变量比较时,只要两个变量的值是相等的,则结果为 true。因为包装类 Integer 和基本数据类型 int 类型进行比较时,Java 会自动拆包装类为 int,然后进行比较,实际上就是两个 int 型变量在进行比较;
(3)非 new 生成的 Integer 变量和 new Integer() 生成的变量进行比较时,结果为 false。因为非 new 生成的 Integer 变量指向的是 Java 常量池中的对象,而 new Integer() 生成的变量指向堆中新建的对象,两者在内存中的地址不同;
(4)对于两个非 new 生成的 Integer 对象进行比较时,如果两个变量的值在区间 [-128, 127] 之间,则比较结果为 true,否则为 false。Java 在编译 Integer i = 100 时,会编译成 Integer i = Integer.valueOf(100),而 Integer 类型的 valueOf 的源码如下所示:
public static Integer valueOf(int var0) { return var0 >= -128 && var0 <= Integer.IntegerCache.high ? Integer.IntegerCache.cache[var0 + 128] : new Integer(var0); }
从上面的代码中可以看出:Java 对于 [-128, 127] 之间的数会进行缓存,比如:Integer i = 127,会将 127 进行缓存,下次再写 Integer j = 127 的时候,就会直接从缓存中取出,而对于这个区间之外的数就需要 new 了。
- 包装类的缓存:
Boolean:全部缓存
Byte:全部缓存
Character:<= 127 缓存
Short:-128 — 127 缓存
Long:-128 — 127 缓存
Integer:-128 — 127 缓存
Float:没有缓存
Doulbe:没有缓存
14、装箱和拆箱
自动装箱是Java编译器在基本数据类型和对应的包装类之间做的一个转换。比如:int转换成Integer,double转换成Double等等。反之就是自动拆箱。
原始类型:boolean、char、byte、short、int、long、float、double
封装类型:Boolean、Character、Byte、Short、Integer、Long、Float、Double
15、swicth语句能否作用在byte上,能否作用在long上,能否作用在String上?
在swicth(expr1)中,expr1只能是一个整数表达式或者枚举常量。而整数表达式可以是int基本数据类型或者Integer包装类型。由于,byte、short、char都可以隐式的转换成int,所以,这些类型以及这些类型的包装类型也都是可以的。而long和String类型不符合swicth的语法规定,并且不能被隐式的转换成int类型,所以,它们不能作用于switch语句中。
不过,需要注意的是在JDK1.7版本之后switch就可以作用在String上了。
16、字节和字符的区别?
字节是存储容量的基本单位;
字符是数字、字母、汉字已经其他语言的各种符号;
1字节 = 8个二进制单位,一个字符由一个字节或多个字节的二进制单位组成。
17、String为什么要设计为不可变类?
在Java中将String设计成不可变的是考虑到各种因素的结果。主要原因是有一下三点:
(1)字符串常量池的需要:字符串常量池是Java堆内存中一个特殊的存储区域,当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象;
(2)允许String对象缓存HashCode:Java中String对象的哈希码被频繁地使用,比如在HashMap中等容器中。字符串不变性保证了hash码地唯一性,因此可以放心地进行缓存。这也是一种i性能优化优化手段,意味着不必每次去计算新的哈希码。
(3)String被许多的Java类(库)用来当作参数,例如:网络连接地址URL、文件路径path、还有反射机制所需要的String参数等,假若String不是固定不变的,将会引起各种隐患。
18、String、StringBuilder、StringBuffer的区别?
String:用于字符串操作,属于不可变类;【补充:String不是基本数据类型,是引用类型,底层用char数组实现的】
StringBuilder:与StringBuffer类似,都是字符串缓冲区,但线程不安全;
StringBuffer:也用于字符串操作,不同之处是StringBuffer属于可变类,对方法加了同步锁,线程安全。
StringBuffer补充:
说明:StringBuffer 中并不是所有方法都使用了 Synchronized 修饰来实现同步:
@Override public StringBuffer insert(int dstOffset, CharSequence s) { // Note, synchronization achieved via invocations of other StringBuffer methods // after narrowing of s to specific type // Ditto for toStringCache clearing super.insert(dstOffset, s); return this; }
执行效率:StringBuilder > StringBuffer > String
19、String字符串修改实现的原理?
当用String类型对字符串进行修改时,其实现方法是首先创建一个StringBuffer,其次调用StringBuffer的append()方法,最后调用StringBuffer的toString()方法把结果返回。
例如:
public class Test { public static void main(String[] args) { String s = "abc"; for (int i=0; i<10000; i++) { s += "abc"; } } }
反编译后代码为:
public class Test { public static void main(String args[]) { String s = "abc"; for(int i = 0; i < 1000; i++) { s = (new StringBuilder()).append(s).append("abc").toString(); } } }
20、String str = "i" 与 String str = new String("i") 一样吗?
不一样,因为内存的分配方式不一样。String str = "i" 的方式,Java 虚拟机会将其分配到常量池中;而 String str = new String("i") 则会被分到堆内存中。
public class StringTest { public static void main(String[] args) { String str1 = "abc"; String str2 = "abc"; String str3 = new String("abc"); String str4 = new String("abc"); System.out.println(str1 == str2); // true System.out.println(str1 == str3); // false System.out.println(str3 == str4); // false System.out.println(str3.equals(str4)); // true } }
在执行 String str1 = "abc" 的时候,JVM 会首先检查字符串常量池中是否已经存在该字符串对象,如果已经存在,那么就不会再创建了,直接返回该字符串在字符串常量池中的内存地址;如果该字符串还不存在字符串常量池中,那么就会在字符串常量池中创建该字符串对象,然后再返回。所以在执行 String str2 = "abc" 的时候,因为字符串常量池中已经存在“abc”字符串对象了,就不会在字符串常量池中再次创建了,所以栈内存中 str1 和 str2 的内存地址都是指向 "abc" 在字符串常量池中的位置,所以 str1 = str2 的运行结果为 true。
而在执行 String str3 = new String("abc") 的时候,JVM 会首先检查字符串常量池中是否已经存在“abc”字符串,如果已经存在,则不会在字符串常量池中再创建了;如果不存在,则就会在字符串常量池中创建 "abc" 字符串对象,然后再到堆内存中再创建一份字符串对象,把字符串常量池中的 "abc" 字符串内容拷贝到内存中的字符串对象中,然后返回堆内存中该字符串的内存地址,即栈内存中存储的地址是堆内存中对象的内存地址。String str4 = new String("abc") 是在堆内存中又创建了一个对象,所以 str 3 == str4 运行的结果是 false。str1、str2、str3、str4 在内存中的存储状况如下图所示: