Java面试之Java基础篇

1、什么是字符串常量池?
答: 字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价。JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化。为 了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串池,每当代码创建字符串常量时,JVM会首先检查字符串常量池。如果字符串已经存在池中, 就返回池中的实例引用。如果字符串不在池中,就会实例化一个字符串并放到池中。Java能够进行这样的优化是因为字符串是不可变的,可以不用担心数据冲突 进行共享。

Java面试之Java基础篇_第1张图片

Java中的字符串常量池是存储在Java堆内存重的字符串池。我们知道String是java里面比较特殊的类,我们使用new运算符创建String对象,也可以使用双引号创建字符串对象。

之所以有字符串常量池,是因为String在Java中是不可变的。字符串常量池有助于为Java运行时节省大量空间,虽然创建字符串时需要更多的时间。当我们使用双引号创建一个字符串时,首先在字符串常量池中查找是否有相同的字符串,如果发现则返回其引用,否则它会在池中创建一个新的字符串,然后返回新字符串的引用。
如果用new运算符创建字符串,则会强制String类在堆空间中创建一个新的String对象,我们可以使用intern()方法将其放入字符串常量池或从字符串常量池中查找具有相同的值字符串对象并返回其引用

2、在下面的语句中创建了多少个字符串对象:

String str = new String ("Cat");

答:在上面的语句中,可能创造1个或2个字符串对象,如果池中已经有一个字符串"Cat",那么池中只会创建一个字符串"str",如果池中没有字符串"Cat",那么它首先在池中创建,因此将创建总共2个字符串对象。

3.为什么要有字符串常量池?
答:为了让数据不冲突进行共享。

4.String 为什么是不可变的?
答:这个关系到String的源码实现,JDK里面对String字符串是对字符数组的封装,里面value,offset和count分别表示存放的数组和起始位置以及个数,他们这三个变量都是private的,而且没有提供set,get方法来修改这些值,而且这三个变量都是final的,也就是说这三个变量一旦被初始化了就不能改变了,这就是为什么String不可变的原因。但是通过反射有可能改变String的值。

5.String拼接字符串效率低,你知道原因吗?
答:如果在循环体中用"+"拼接字符串,每次循环都会new一个StringBuilder;而在循环体外面先把StringBuilder创建出来,循环体就不用每次都new一个了;所以在循环体中用“”+“拼接字符串效率慢”

--------------------------2019.6.15以后会更新--------------------------------

6.你真的了解String的常见API吗?(API:指的是java标准类库提供的函数)
答:String中的常用的方法有:lenght()//计算字符串长度,charAt();//截取一个字符,equal();//比较两个字符串,indexOf();//查找字符获知字串第一次出现的地方,substring();//截取字符串,但是像replace(),substring(),toLowerCase()这三个方法需要注意下,因为这三个方法是重新创建一个新的字符串return出,所以如果不把return出来的值从新赋值给之前定的变量,那么变量值将不变。如下图所示:
Java面试之Java基础篇_第2张图片

7.Java中的subString()真的会引起内存泄露么?
答:这里首先有个问题,什么是内存泄露,那就向讲一讲内存溢出和内存泄漏的含义,所谓的内存溢出呢,就上内存不够用了,比如在一个无限的循环里不断的创建一个大的对象,使得沾满内存溢出,这就是所谓的内存溢出;而内存泄漏呢,就上指为一个对象分配好内存之后,在对象已经不再使用时未及时的释放,倒是一直占据内存单元,使实际可用内存减少,就好像内存泄漏了一样。内存泄漏问题其实就是在JDK1.6中,例如有一个字符串a,然后字符串b是字符串a的一个子字符串,就上使用substring()截取,这样的话a和b用的value是同一个引用,如果我们把a的引用为空,本意是释放a所占用的空间,但是这个时候,垃圾回收机制是没办法回收这个value的,因为这个还在被字符串b所引用,所以就会一直占着堆内存,就会导致内存泄漏。

8.浅析Java中的final关键字?
答:在Java中,final关键字可以用来修饰类,修饰方法和修饰变量,那么我们就从这三点来说。第一,当一个final修饰一个类时,表明这个类不能被继承,如果你想一个类永远不要被继承,那么你就可以用final进行修饰,final类中的所有成员方法都会隐式的指定为final方法。第二,当一个final修饰方法时,是把方法锁定,以防任何继承类修改它的含义,那么这个方法就不可以被子类重写。第三,final修饰变量是final用的最多的地方,对于一个final变量,如果是基本数据类型的变量,其数据以但在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能在让其指向另一个对象。final修饰基本变量这个变量就会变成常量。final修饰引用变量一般初始化之后就不能指向其他的对象,但是它指向的对象的内存是可变的。

9.浅析Java中的static关键字?
答:static方法就上没有this的方法。在static方法内部不能调用非静态方法,反过来是可以的。而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。这实际上正是static方法的主要用途。static可以用来修饰类的成员方法、类的成员变量,还可以编写static代码块来优化程序性能。第一,static方法,static方法一般称为静态方法,由于静态方法不依赖任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附仍和对象,因为这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为这些必须依赖具体的对象才能够被调用。第二,static变量,static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。切记static不能用来修饰局部变量,这是Java语法规定的

--------------------------2019.6.17以后会更新--------------------------------

10.你对Java中的volatile关键字了解多少?
答:volatile就是表示某人或某物是不稳定的,易变的。volatile作为java中的关键词之一,用来声明变量的值可能随时会被别的线程修改,使用volatile修饰的变量会强制将修改的值立即写入主存,主存中的值的更新回事缓存中的值失效,就是说,Volatile修饰的成员变量每次被线程访问时,都强迫从共享内存重新读取改成员的 值,而且当成员变量值发生改变的时候,强迫将变化的值重新写入共享内存中,这样两个线程在访问同一个共享变量的值时,始终只会看到同一个值。Volatile的优点就是当多线程同时访问一个共享变量时,可以使用volatile,这样保证访问的值一致,而缺点就是使用volatile将使得JVM优化失去作用,导致效率较低。volatile的特性,volatile具有可见性,有序性但是不具备原子性。

11.i++是线程安全的吗?如何解决线程安全性?
答:i++不是线程安全的,如果想解决i++这种线程不安全问题,可以对i++操作的方法加同步锁,只允许一个线程执行i++操作。也可以使用支持原子性操作的类,如 java.util.concurrent.atomic.AtomicInteger,它使用的是CAS 算法,效率优于第 1 种;

12.从字节码角度深度解析 i++ 和 ++i 线程安全性原理?
答:说到字节码,就要讲一讲java栈,在jvm中有这么一个数据结构叫java栈,当线程启动的时候,会分配一块内存当做该线程的栈,每个栈由一些列的栈帧组成,每个栈帧对应着一个方法,当线程执行方法时,就是栈帧出栈入栈的过程。每个栈帧包含三个数据:本地变量(参数+方法内的变量)、炒作数栈和其他数据。
Java面试之Java基础篇_第3张图片

可以发现变量a在执行iinc的时候已经变成1了,但是istore_1又把变量a所在位置覆盖成0,所以执行完i=i++,i还是原来那个值。
Java面试之Java基础篇_第4张图片

和i++不同的地方在于,在变量进入操作数栈之前,就先执行了iinc指令,所以进入操作数的值是加1后的值,最后写回的值也是最新值。

总结来说就是i++,他是变量执行了iinc指令后,变量已经改变,但是后面操作数栈又把变量覆盖成原来的值;而++i是进入操作数栈之前,就先执行了iinc指令,所以进入操作数的值是加1后的值,最后写回的值也是最新值。

--------------------------2019.6.18以后会更新--------------------------------

13.请谈谈什么是CAS?
答:cas的英文全名叫做compare and swap,意思是比较并交换,是实现并发算法常用到的一种技术。cas机制使用了3个基本操作数:内存地址,旧的预期值,要更改的新值。当且仅当预期值和内存值相同时,才将内存值修改为新值并返回true,否则什么都不做,直接放回false。与cas相关的一个概念就是原子操作,原子操作就是不可被中断的一个或一些列操作,而cas则是java中保证原子操作的一种方式。从java1.5开始,jdk的并发包中就提供了一些类来支持原子操作,都是以Atomic开头。

14.从源码角度看看ArrayList的实现原理?
答:ArrayList实现了List接口,意味着ArrayList元素元素是有序的,可以由null元素的集合,还有实现了Cloneable接口,标志着它可以被复制,但是ArrayList里面的clone()复制其实是浅复制。浅复制在这里简单说一下,浅复制就算指对象的字段值被赋值时,字段引用的对象不会被复制。浅复制会创建另一个对象,然后两个对象都指向同一个引用。
ArrayList的底层是Object数组储存数据。他还有一个扩容机制,默认的大小是10,如果长度不够的话就会重新创建一个数组,长度是之前的长度加上其二分之一,就相当于原来的1.5倍,然后再把之前的数组放进去。如果我们平时在使用的时候可以确切的知道数组的大小,尽量用明确容量的方式创建ArrayList,避免不必要的浪费。还有就是添加或者删除的话,例如添加到数组的指定位置,它的原理是利用arraycopy()方法,把你要插入位置的后面全部数据复制到下一个位置,然后指定位置就可以赋值了。这样的话会挪动大量的数组元素,并且还有可能促发扩容机制,删除也是同一个道理。但是查询和修改。因为ArrayList底层是数组,所以支持快速访问,要访问哪个位置直接访问即可,所以他们的速度很快,是O(1).

--------------------------2019.6.19以后会更新--------------------------------

15.手写LinkedList实现。
简单的实现了LinkedList链表的实现。双向链表的关键是利用节点。每个数据都分为上一个节点,值,下一个节点。当需要增加或者删除的时候,只需要把链表指定位置截断然后把数据放进去然后把放入数据的上一个节点与上一个数据的下一个节点连接,放入数据的下一个节点和下一个数据的上一个节点相连就可以了;删除也是同样道理。但是如果要查询指定位置的话要用for循环一个一个找。所以LinkedList链表的插入删除快,但是查询慢。

package com.ms;
/**
 * 实现了add方法,get方法,size方法吗,remove方法,在指定位置add方法
 * @author xand1106
 *
 * @param 
 */
public class MyLinkedList {
	private int size;//实际长度
	private Node first;//上一个节点
	private Node last;//下一个节点
	
	//add方法
	public void add(E e){
		Node node = new Node();//创造一个节点
		node.object = e;
		if(first == null) {
			//添加第一个节点
			first = node;
			last = node;
		}else {
			node.prev = last;
			last.next = node;
		}
		last = node;
		size++;
	}      
	
	//size方法
	public int size() {
		return size;
	}
	
	//get方法
	public Object get(int index) {
		checkElement(index);
		Node node = getNode(index);
		return node.object;
	}
	
	private void checkElement(int index) {
		if(!isElementIndex(index)) {
			throw new IndexOutOfBoundsException("查询出错");
		}
	}
	
	private boolean isElementIndex(int index) {
		return index >= 0 && index

16.Java中方法参数的传递规则?
答:Java方法中的参数传递方式只有一种:值传递;
对于基本类型来说,传递的是实参的副本(值传递),故在方法内修改传递进来的值并不会影响实参本身;
对于引用类型来说,传递进来的是引用变量的副本(也是值传递),因此该副本与实参均是引用变量,他们均可以操作所引用的对象,在方法内通过引用变量对堆区的对象进行操作时均会对该对象有影响。

17.Java中throws和throw的区别是什么?
答:throws是用 来声明一个方法可能产生的所有异常,不做任何处理而是将异常上传,谁调用我我就抛给谁。用在方法声明后面,跟的是异常类名;可以跟一个或多个异常类名,用逗号隔开;throws表示出现异常的一种可能性,并不一定会发生这些异常。
throw则是用来抛出一个具体的异常类型的;用在方法体内,跟的是异常对象名;只能抛出一个异常对象名;throw表示抛出的异常由方法体内的语句处理;执行throw一定是抛出了某种异常

18.一个“”.java”源文件中是否可以包含多个类?有什么限制?
答:一个源文件中是可以包含多个类的,但是只能有一个public类,而且这个类的类名必须和文件名一致。

19.说说&和&&的区别。
答:&和&&都可以使用作为逻辑与的运算符,表示逻辑与。&&具有具有短路的功能,什么是短路,就是如果第一个表达式是假的话,那么无论后面是真是假,结果都是为假,短路就是在第一个表达式为false的情况下,就不会在计算后面第二个表达式。这样做不仅速度更快而且效率也更高。

20.如果跳出多重循环语句
答:三个方法,第一个是利用标记语言,使用带有标号的break语句,但是这就是使用到goto语句,不建议使用。第二种是外层循环条件受到内层代码控制限制。第三种是抛出异常强制跳出循环。

21.switch语句能否作用在byte上,能否作用在long上,能否作用在String上?
答:因为byte,short,char都是属于隐性int类型,所以他们可以。long就不行,因为long的范围比int大,如果进行窄化转换的话,会使得精度丢失,数据可能出错。java5开始支持枚举,而String从Java7开始也支持。

22.char型变量能不能存储一个中文汉字?
答:char是用来存储Unicode编码的字符的,Unicode编码字符集中包含了汉字,所以,char型变量当然是可以存储汉字的。

23.使用最有效率的方法计算2*8等于多少
答:2<<3,代表2乘2的3次方

24.是否可以从一个static方法内部发出对非static方法的调用?
答:不可以,static方法是静态方法,在程序开始加载的时候他就已经进行便加载了,而非static方法是与对象绑定的,是得等到创建对应的对象实例后才能通过这个实例进行调用,static方法调用非static方法他不知道非static关联的是哪个具体的对象。

25.integer和int的区别
答:Integer是一个类,是int的包装类,int是基本数据类型。integer的默认值是null,int的默认值是0。int的自动装箱是用Integer.valueOf(10)方法,Integer的自动拆箱是用intvalue();还有就是自动装箱是问题valueOf(10)方法中如果这个传入的int是在-128~127之间,那么就会将传入的int值放入缓存中,下次就可以直接取缓存里面的。

26.Overload和Override的区别,Overloaded的方法是否可以改变返回值的类型?
答:首先,Overload是重载的意思,Override是重写的意思。重载表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同,即参数的个数和参数类型不同。重写表示子类中的 方法可以与父类中的某个方法的名称和参数完全相同,通过子类创建的实例对象调用方法时,将调用子类中的定义方法,相当于把父类定义的相同的 方法给覆盖了,这也是面向对象多态性的一种表现。子类重写父类方法时,只能把父类抛出更少的异常或者是父类的子异常,还有就是子类访问权限只能比父类大,不能比父类小,所以当父类的访问权限是private的时候,子类不能重写父类方法。
其次,如果说OverLoad是否可以改变返回值的类型,如果两个方法的方法名参数一样,只有返回值不同,而我们调用这个方法的时候并不考虑它返回什么值,那么jvm就没办法确定调用哪一个方法,所以这个是不成立的。

27.接口是否可继承接口?抽象类是否可实现(implements)接口?抽象类是否可继承具体类(concreteclass)?抽象类中是否可以有静态的main方法?
答:接口可以继承接口,抽象类可以实现接口,抽象类可以继承具体类,抽象类可以有静态的main方法。

28.面向对象编程的三大特性?
答:封装,继承,多态。

29.Java中实现多态的机制是什么?
答:java实现多态的三个必要条件是继承,重写,向上转型。对于Java而言,它多态的实现机制遵循一个原则:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。

30.Java中abstract class(抽象类) 和 interface(接口)的区别
答:相同点:1.两者都是抽象类,都不能实例化。2.interface实现类及abstract实现类都要实现已经声明的抽象方法
不同点:1.interface需要实现,要用implement,而abstract需要继承,要用extends。 2.一个类可以实现多个接口,但是只能继承一个抽象类。3.接口强调实现的功能,而抽象类强调所属关系。4.尽管接口实现类和抽象类的子类都要实现各自的抽象方法,但是实现的形式不同,接口中的每一个方法都是抽象方法,实现类必须实现,而抽象类中不仅有抽象方法还有非抽象方法,他只需要实现抽象类中的抽象方法。

31.abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized
答:都不可以,因为abstract声明的方法是要求子类去实现的,abstract只能告诉你有这样一个接口,至于你的具体实现可以是native或者synchronized,也可以不是。而且,static方法是不会被覆盖的,而abstract方法正是要子类去覆盖它,所以也不能是static的。

32.内部类可以引用它的包含类的成员吗?有没有什么限制?
答:完全可以。如果不是静态内部类,那没有什么限制。 一个内部类对象可以访问创建它的外部类对象的成员包括私有成员。
  如果你把静态嵌套类当作内部类的一种特例,那在这种情况下不可以访问外部类的普通成员变量,而只能访问外部类中的静态成员。

33.内部类的四种形式:
答:1.静态内部类;2.成员内部类;3.局部内部类;4.匿名内部类

34.String s=“Hello”; s=s+“world!”;这两行代码执行后,原始的String对象中的内容到底变了没有?
答:答案是没有的,在前面我们解释过,String是不可变类,顾名思义就是累点实例是不可以被修改的,在上面的代码中,首先是如果String常量池中没有Hello的话那就创建一个Hello字符串,然后把这个字符串的引用给了s,如果有这个对象就直接抛出引用给s,下一句进行了字符串拼接,后面的s+“world”,因为String对象是不可变的,所以他是创建了一个新的String对象,s的引用从之前的对象变成现在的引用。之前的对象就等着垃圾回收机制回收。

你可能感兴趣的:(Java面试)