java 面试题一 Java基础

版权声明:作者:xp_9512 来源:CSDN 版权声明:本文为博主原创文章,转载请附上博文链接! 原文: https://blog.csdn.net/qq_40981804/article/details/88603815

文章目录

项目介绍

大部分情况,这是一场面试的开门题,面试官问这个问题,主要是考察你的概述能力和全局视野。有的人经常抱怨自己每天在堆业务,但没有成长。事实上,很多情况下确实在堆业务,但并不是没有成长的。并非做中间件或者技术架构才是成长,例如我们的需求分析能力,沟通协作能力,产品思维能力,抽象建模能力等都是一个非常重要的硬实力。

需要准备

  • 1、明确项目是做什么的
  • 2、明确项目的价值。(为什么做这个项目,它解决了用户什么痛点,它带来什么价值?)
  • 3、明确项目的功能。(这个项目涉及哪些功能?)
  • 4、明确项目的技术。(这个项目用到哪些技术?)
  • 5、明确个人在项目中的位置和作用。(你在这个项目的承担角色?)
  • 6、明确项目的整体架构。
  • 7、明确项目的优缺点,如果重新设计你会如何设计。
  • 8、明确项目的亮点。(这个项目有什么亮点?)
  • 9、明确技术成长。(你通过这个项目有哪些技术成长?)

Java基础

String a= new String(“abc”);创建了几个对象,用到了几个区,

首先了解java 的字符串常量缓冲区(字符串池,字符串常量池)

  • 答案是:
    创建了两个或者一个对象;用到了三个区;(堆区,栈区,共享区)

  • 解释:
    一个是new String 创建的一个新的对象,一个是常量“abc”对象的内容创建出的一个新的String对象,当字符串常量池中存在常量“abc”时,就是创建了一个对象;

  • 步骤:

  • 实例化:类名/接口名 对象名 = new 类名/接口名 ();

    类名/接口名 ():该对象的构造方法
    
    类名/接口名 对象名:定义一个引用变量
    
    new关键字:实例化
    
    等号:将引用变量与实例化对象关联起来
    

1、List 和 Set 的区别

Set:其中的值不允许重复,无序的数据结构;检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置变化。

List:其中的值允许重复,因为其为有序的数据结构;和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。

2、ArrayList和LinkedList底层实现有什么差别?它们各自适用于哪些场合

  • ArrayList是基于动态数组来实现的,查询效率比较高
  • LinkedList是基于动态链表来实现的,插入和删除效率比较高

2.1、HashSet和TreeSet的区别

  • HashSet是基于哈希表还进行排序(散列)
  • TreeSet的数据结构是红黑树,使用自然顺序或者由自定义的比较器排序

3、HashSet 是如何保证不重复的

什么是hash码值?

在java中存在一种hash表结构,它通过一个算法,计算出的结果就是hash码值;这个算法叫hash算法;

hash算法是怎么计算的呢?

是通过对象中的成员来计算出来的结果;
如果成员变量是基本数据类型的值, 那么用这个值 直接参与计算;
如果成员变量是引用数据类型的值,那么获取到这个成员变量的哈希码值后,再参数计算

保证不重复

  • 第一步:判断hash码值

    如果hash码值不相同,说明是一个新元素,存;
    
    如果没有元素和传入对象(也就是add的元素)的hash值相等,那么就认为这个元素在table中不存在,将其添加进table;
    
  • 第二步:hash 码值相同,则进行equles判断

    如果hash码值相同,且equles判断相等,说明元素已经存在,不存;
    
    如果hash码值相同,且equles判断不相等,说明元素不存在,存
    

4、HashMap 是线程安全的吗,为什么不是线程安全的(最好画图说明多线程环境下不安全)

HashMap线程不安全,hashmap允许key,value为null
hashtable线程安全,方法加synchronized,不允许value为null
HashMap多线程get不安全,多线程扩容阶段中,rehash步骤不安全
具体请看:http://www.importnew.com/22011.html

concurrentHashMap:

1.7版本,采用segment分段锁,一个hashmap中有多个segment,每个segment存放多个k-v桶(数组),每个数组后面是链表。锁是加在每个segment上。

1.8版本,锁的粒度更细了,去掉了segment,直接加在链表的表头,整体结构和普通的hashMap没差别,使用volatile保证可见性。同时在初始化,以及定位到相应数组位置时量表为空的特殊情况下,使用CAS无锁操作。链表长度超过阈值,也会进行树化。

5、HashMap 1.8 的实现原理以及扩容过程

https://blog.csdn.net/u010890358/article/details/80496144

6、HashMap里的hashcode方法和equal方法什么时候需要重写,为什么需要同时重写?

https://blog.csdn.net/qq_40981804/article/details/89022523

当我们在HashMap中存储我们自己定义的类的时候,默认的equal函数的行为可能不能符合我们的要求,所以需要重写。

如果你重载了equals,比如说是基于对象的内容实现的,而保留hashCode的实现不变,那么很可能某两个对象明明是“相等”,而hashCode却不一样。

这样,当你用其中的一个作为键保存到hashMap、hasoTable或hashSet中,再以“相等的”找另一个作为键值去查找他们的时候,则根本找不到。

7、HashMap 1.7 与 1.8 的 区别,说明 1.8 做了哪些优化,如何优化的?

链表长度超过8采用红黑树,resize的时候不是重新计算hash,而是用hash&oldCap(数组容量)来判断是在原位置,还是移动到oldCap+oldIndex位置
http://www.importnew.com/22011.html
http://www.importnew.com/20386.html
https://my.oschina.net/hosee/blog/618828

8、final finally finalize

final 用于声明属性,方法和类, 分别表示属性不可变(不可重新赋值), 方法不可覆盖, 类不可继承。可以增强代码的可读性,明确哪些代码是不可被修改的,一定程度上增加平台的稳定性。
匿名内部类,引用局部变量,局部变量为什么用final修饰:内部类引用局部变量不是直接引用,而是copy一份,用final修饰,可以减少数据一致性问题

finally 是异常处理语句结构的一部分,表示总是执行。对于需要关闭的连接等资源,使用try-with-resources语句。

finalize 是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等. JVM不保证此方法总被调用。不推荐使用,java9中,该方法被标注为deprecated

9、强引用 、软引用、 弱引用、虚引用

https://blog.csdn.net/mazhimazh/article/details/19752475

10、Java反射

  • Java反射的定义
    Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

  • Java反射机制主要提供了以下功能:

    在运行时构造一个类的对象;
    判断一个类所具有的成员变量和方法;
    调用一个对象的方法;
    生成动态代理。反射最大的应用就是框架
    
  • Java反射的主要功能:

    1)确定一个对象的类
    2)取出类的modifiers,数据成员,方法,构造器,和超类.
    3)找出某个接口里定义的常量和方法说明.
    4)创建一个类实例,这个实例在运行时刻才有名字(运行时间才生成的对象).
    5)取得和设定对象数据成员的值,如果数据成员名是运行时刻确定的也能做到.
    6)在运行时刻调用动态对象的方法.
    7)创建数组,数组大小和类型在运行时刻才确定,也能更改数组成员的值.
    
  • Java反射的主要应用

    反射的应用很多,很多框架都有用到
    spring 的 ioc/di 也是反射….
    javaBean和jsp之间调用也是反射….
    struts的 FormBean 和页面之间…也是通过反射调用….
    JDBC 的 classForName()也是反射……
    hibernate的 find(Class clazz) 也是反射….
    

注意:
反射还有一个不得不说的问题,就是性能问题,大量使用反射系统性能大打折扣。

11下列关于反射的说法错误的是:

A.  可以通过newlnstance()来创建一个字节码对象
B.  可以通过 Class<String> clz = Class. forName("String");来获取代表String类的Class对象
C.  Method[] ms = String.class. getMethods()来获取 String 类中的所有方法
D.  通过执行方法对象上的invoke方法来运行该方法
E.  当私有属性想直接赋值的时候,必须先要将其暴力破解

( A、B、C)

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

12、Arrays.sort 实现原理和 Collection.sort 实现原理

不论是Collections.sort方法或者是Arrays.sort方法,底层实现都是TimSort实现的,这是jdk1.7新增的,以前是归并排序。TimSort算法就是找到已经排好序数据的子序列,然后对剩余部分排序,然后合并起来

具体请看:https://blog.csdn.net/u011410529/article/details/56668545

13、LinkedHashMap的应用

有序,entry是双向链表,head,tail
https://www.jianshu.com/p/8f4f58b4b8ab

14、cloneable接口实现原理

cloneable只是一个接口,需要实现接口,重写Object的clone方法,此时是浅拷贝,复制属性,对于对象属性是拷贝引用
深拷贝,被克隆的对象以及对象属性要实现Serializable,用ByteArrayOutputStream,ObjectOutputStream,ObjectInputStream 复制属性
https://blog.csdn.net/boonya/article/details/70849997

15、异常分类以及处理机制

https://www.cnblogs.com/lulipro/p/7504267.html

16、try catch finally 的执行顺序

先执行try或catch里里面的代码,然后再执行finally,再执行try或catch里面的return.基础数据类型finally的修改不影响return,如果是返回对象,因为返回对象的引用,finally的修改会影响。

17、wait和sleep的区别

1、首先,要记住这个差别,“sleep是Thread类的方法,wait是Object类中定义的方法”。尽管这两个方法都会影响线程的执行行为,但是本质上是有区别的。

2、Thread.sleep不会导致锁行为的改变,如果当前线程是拥有锁的,那么Thread.sleep不会让线程释放锁。如果能够帮助你记忆的话,可以简单认为和锁相关的方法都定义在Object类中,因此调用Thread.sleep是不会影响锁的相关行为。

3、Thread.sleep和Object.wait都会暂停当前的线程,对于CPU资源来说,不管是哪种方式暂停的线程,都表示它暂时不再需要CPU的执行时间。OS会将执行时间分配给其它线程。区别是,调用wait后,需要别的线程执行notify/notifyAll才能够重新获得CPU执行时间。

18、数组在内存中如何分配

数组是一种引用数据类型,栈内存保存堆内存的引用,对内存分配连续内存存储数据,如果是对象,那么连续内存里存的是对象的引用

19、抽象类和接口

基本定义

  • 抽象类
    含有 abstract 修饰符的 class 即为抽象类,abstract 类不能创建的实例对象。含有 abstract 方法的类必须定义为abstract class,abstract class类中的方法不必是抽象的。abstract class类中定义抽象方法必须在具体(Concrete)子类中实现,所以,不能有抽象构造方法或抽象静态方法。如果的子类没有实现抽象父类中的所有抽象方法,那么子类也必须定义为 abstract类型。
  • 接口
    接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为 public abstract 类型,接口中的成员变量类型默认为 public static final。

下面比较一下两者的语法区别:

1.抽象类可以有构造方法,接口中不能有构造方法。
2.抽象类中可以有普通成员变量,接口中没有普通成员变量
3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
4. 抽象类中的抽象方法的访问类型可以是 public,protected 和(默认类型,虽然
eclipse 下不报错,但应该也不行),但接口中的抽象方法只能是 public 类型的,并且默认即为 public abstract 类型。
5. 抽象类中可以包含静态方法,接口中不能包含静态方法
6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是 public static final 类型,并且默认即为 public static final 类型。
7. 一个类可以实现多个接口,但只能继承一个抽象类。

下面接着再说说两者在应用上的区别:

接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约。而抽象类在代码实现方面发挥作用,可以实现代码的重用。

20、 transient关键字与@transient注解

transient关键字:用来修饰对象的某个属性,使该对象在序列化时忽略这个属性
@transient注解:当我们希望该属性不会在数据表中产生字段,但又可以在程序中使用它的时候。

private MemberConfig memberConfig; 
@Transient 
public MemberConfig getMemberConfig() { 
	MemberConfig memberConfig = new MemberConfig(getAttr()); 
	return memberConfig;
 } 
 public void setMemberConfig(MemberConfig memberConfig) {
 		this.getAttr().putAll(memberConfig.getAttr()); 
 }

21、 关于final关键字和两个字节相加减的问题

byte b1=1,b2=2,b3,b6,b8;

final byte b4=4,b5=6,b7;

b3=(b1+b2);  /*语句1*/

b6=b4+b5;    /*语句2*/

b8=(b1+b4);  /*语句3*/

b7=(b2+b5);  /*语句4*/

System.out.println(b3+b6);

1、被final修饰的变量是常量,这里的b6=b4+b5可以看成是b6=10;在编译时就已经变为b6=10了

2、而b1和b2是byte类型,java中进行计算时候将他们提升为int类型,再进行计算,b1+b2计算后已经是int类型,赋值给b3,b3是byte类型,类型不匹配,编译不会通过,需要进行强制转换。

3、Java中的byte,short,char进行计算时都会提升为int类型。

22、 关于集合的扩容问题

当底层实现涉及到扩容时,容器或重新分配一段更大的连续内存(如果是离散分配则不需要重新分配,离散分配都是插入新元素时动态分配内存),要将容器原来的数据全部复制到新的内存上,这无疑使效率大大降低。

加载因子的系数小于等于1,意指 即当 元素个数 超过 容量长度*加载因子的系数 时,进行扩容。

另外,扩容也是有默认的倍数的,不同的容器扩容情况不同。

  • List 元素是有序的、可重复

    
    ArrayList、Vector默认初始容量为10
    
    Vector:线程安全,但速度慢
    
        底层数据结构是数组结构
    
        加载因子为1:即当 元素个数 超过 容量长度 时,进行扩容
    
        扩容增量:原容量的 1倍
    
          如 Vector的容量为10,一次扩容后是容量为20
    
    ArrayList:线程不安全,查询速度快
    
        底层数据结构是数组结构
    
        扩容增量:原容量的 0.5倍+1
    
          如 ArrayList的容量为10,一次扩容后是容量为16
    
    
  • Set(集) 元素无序的、不可重复。

     HashSet:线程不安全,存取速度快
    
        底层实现是一个HashMap(保存数据),实现Set接口
    
        默认初始容量为16(为何是16,见下方对HashMap的描述)
    
        加载因子为0.75:即当 元素个数 超过 容量长度的0.75倍 时,进行扩容
    
        扩容增量:原容量的 1 倍
    
        如 HashSet的容量为16,一次扩容后是容量为32
    
     
    
    构造方法摘要HashSet()  HashSet(int initialCapacity) 构造一个新的空 set,其底层 HashMap 实例具有指定的初始容量和默认的加载因子(0.75)。
     HashSet hs=new HashSet(1);
    所以可见 HashSet类,创建对象的时候是可以的制定容量的大小的 ,期中第二个就具有这个工功能。 
    
  • Map是一个双列集合

    HashMap:默认初始容量为16(为何是16:16是2^4,可以提高查询效率,另外,32=16<<1       -->至于详细的原因可另行分析,或分析源代码)
    
     加载因子为0.75:即当 元素个数 超过 容量长度的0.75倍 时,进行扩容
    
     扩容增量:原容量的 1 倍
    
     如 HashSet的容量为16,一次扩容后是容量为32
    

23、论short s=3; s+=2; s=s+2;的区别

当指令为s=s+2进行编译时,会报错.

原因是数字2是int型的常量,s+2会自动转换成int型,将一个int型赋给一个short型的s,自然编译会出错。

如果一定要使用s+2的话,可以将s+2改为(short)(s+2),这时(short)将(s+2)的值强转为short型,这时便可以将(s+2)的值赋给s.

为什么s+=2不会报错呢?

因为编译器自动将+=运算符后面的操作数强制转换为前面变量的类型,所以s+=2不会报错.

同时类似的还有: -= *= /= %=

24、==与equals方法

String s = “hello”;String t = “hello”;char c [ ] = {‘h’,‘e’,‘l’,‘l’,‘o’};下列选项中返回false的语句是

 A.  s==t
 B.  s.equals(t);
 C.  s.equals(String.valueOf(c))
 D.  t.equals(c)
正确答案:D
首先来看一下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;
    }
很明显。char[]不是String的对象所以。返回肯定是false了。

25 Java运算符的优先级

单目>算数运算符>移位>比较>按位>逻辑>三目>赋值

优先级 描述 运算符
1 括号 () []
2 正负号 + -
3 自增自减,非 ++ -- !
4 乘除,取余 * / %
5 加减 + -
6 移位运算 << >> >>>
7 大小关系 > >= < <=
8 相等关系 == !=
9 按位与 &
10 按位异或 ^
11 按位或 |
12 逻辑与&&
13 逻辑或 ||
14 条件运算 ?:
15 赋值运算 = += -= *= /= %=
16 位赋值运算 &=
运算符的分类与结合顺序
优先级 运算符分类 结合顺序 运算符
由高到低 分隔符 左结合 . [] ( ) ; ,
一元运算符 右结合 ! ++ -- - ~
算术运算符
移位运算符
左结合 * / % + - << >> >>>
关系运算符 左结合 < > <= >= instanceof(Java 特有) = = !=
逻辑运算符 左结合 ! && || ~ & | ^
三目运算符 右结合 布尔表达式?表达式1:表达式2
赋值运算符 右结合 = *= /= %= += -= <<= >>= >>>= &= *= |=

例子 :结果为 35

import java.util.Scanner;
	public class Demo{
		public static void main(String[] args){
			Scanner sc=new Scanner(System.in);
			int i=sc.nextInt();
			int j=sc.nextInt();
			i+=i*=i-=j;
			System.out.println(i); 
		}
    } 

i+=i*=i-=j; 相当于: -5 += -5 *= -5 -= 3

26、clone方法:

例题

  • 下列报错的行号是:1、2
	package cn.tedu.test1 
	public class A{
		int age;
	}
	
	package cn.tedu.test2 
	public class C{
		public static void main(String[] args) {
			B b=new B();
			b.clone(); //1
		}
	}
	class B extends A{
		int age;
		public void test1(){
			A a=new B();
			a.clone(); //2
			B b=new B();
			b.clone(); //3
		}
	}

protected是最难理解的一种Java类成员访问权限修饰词。在编程中,碰到涉及protected的调用时,首先要确定出该protected成员来自何方,其可见性范围是什么,然后就正确无误的使用了。实际上,protected的可见性在于两点:

  • 基类的protected成员是包内可见的,并且对子类可见;
  • 若子类与基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的protected方法,而不能访问基类实例的protected方法。

猜你喜欢

转载自blog.csdn.net/qq_40981804/article/details/88603815