2023年Java基础面试题全解析,大学生必读!

本文详细介绍了Java编程中关于集合、线程、面向对象、异常处理等核心知识点,包括集合的底层原理、线程状态转换、面向对象的封装、继承和多态,以及异常的处理方式。特别讨论了String类的特性,如不可变性,以及与StringBuilder的区别。此外,还涵盖了异常的处理方式,如try-catch-finally结构,以及线程同步的相关方法如synchronized和wait/notify。
摘要由CSDN通过智能技术生成

JAVA基础

在博客里面,主要包含java基础的五大板块,按面试概率从高到低罗列:集合、线程、面向对象、异常、IO流;

博客排版分两块:
概述是简洁版本,以大白话的方式总结陈述,直指该类面试题的核心,帮助大家成体系的记住相关面试题;
标准是参考答案,相对来说比较官方;

注意:出去面试一定要用自己的话把标准答案表达出来,体现自己的专业性,只会背诵标准答案在现在社招环境是行不通的;

概述

1.集合

集合的面试技巧:从数据结构出发

1.集合与集合的区别
    例如:list、set、map的区别?
    list、set是继承与Collection接口,map不是
    特点:
    list:有序有下标可重复
    set:无序的无下标不可重复
    map:键值对形式存在

2.集合子类的区别
    列如:
    ArrayList与LinkedList的区别?
        ArrayList:数据结构是数组
        LinkedList:数据结构是链表
        特点:ArrayList查询修改较快,新增删除较慢
        Linkedlist查改慢,新增删除较快

    hashset与TreeSet的区别?
        hashset:数据结构是哈希表
        TreeSet:数据结构是二叉树
        hashset中存储的对象判重,底层是跟hashcode、equals方法相关
        TreeSet:可以排序,需要定义排序规则
            java.lang.Comparable:自然排序接口
            java.util.Comparator:比较器接口


    hashmap与hashTable的区别?
        数据结构都是哈希表
        HashMap是线程不安全的,方法前没有被synchronized修饰
        Hashtable是线程安全的,方法前有被synchronized修饰,代表它的方法都是同步方法


    hashmap与treeMap的区别?
        HashMap<Student,Teacher> hashmap = new HashMap();
        new Student(1,"zhangsan")
        new Student(1,"zhangsan")


        hashmap:数据结构是哈希表
        treeMap:数据结构是二叉树
        hashmap中存储的key对象判重,底层是跟hashcode、equals方法相关
        HashMap:可以排序,需要定义排序规则
            java.lang.Comparable:自然排序接口
            java.util.Comparator:比较器接口
		
3.集合的底层原理
    列如: 
    list如何去重?
        调用contains方法,以及重写equals完成的
    set如何去重?
        跟hashcode、equals相关

2.线程

掌握线程的核心基础内容

掌握线程概念、线程的实现、线程状态、线程状态转换、线程的常用方法、线程中的死锁。
概念:最小的运行单元

线程与进程的区别:多对一的关系,一个进程进程可以对应多个线程,一个线程是属于一个进程的

如何实现:extends Thread,implement runnable

状态:就绪、初始化、运行、阻塞、销毁

状态转换:start、wait、run方法中的线程内容是运行完毕的

常用方法及区别:
run、start的区别?run指的是线程运行的内容,start指的是开启线程
sleep、wait的区别?sleep是休眠,到了一定时间会自动唤醒,wait是需要人为的唤醒

死锁:
两个及以上线程共同抢占锁资源,有锁A与锁B,形成互锁,线程呈现僵持状态,抢占服务器资源的现象

3.面向对象

掌握封装、继承、多态的特点

封装
    类属性与对象属性的区别/static?
        类属性是共用的
        对象属性是独有的,每个对象的属性都是单独存在的;
    final修饰的类、方法、属性的的特点?
        final代表最终
        类:不能被继承
        方法:不能被重写
        属性:指向不能发生变化

继承
    方法的重写与重载?
        重写:同名同参
        重载:同名不同参
    构造器 是否可以被重写?
        不可以
	
多态
	抽象类与接口的区别?

4.异常

异常的处理方式?
    捕捉跟声明

runtimeException与Exception的区别?
	Exception是需要声明或者扑捉的,runtimeException则不需要

异常与错误的区别?
    异常通常是由程序导致的,是可以修复的,而错误则不然

如何出现栈内存溢出?递归

5.IO流

字节流:InputStream、OutputStream
字符流:Reader、Writer

文件拷贝:

深拷贝与浅拷贝:IO流序列化与反序列
	IO流-》对象-》IO流

标准

面向对象

1. &和&&的区别

	&和&&都可以用作逻辑与的运算符,表示逻辑与(and),当运算符两边的表达式的结果都为true时,整个运算结果才为true,否则,只要有一方为false,则结果为false。
	
	&&还具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式,例如,对于if(str != null && !str.equals(“”))表达式,当str为null时,后面的表达式不会执行,所以不会出现NullPointerException如果将&&改为&,则会抛出NullPointerException异常。If(x==33 & ++y>0) y会增长,If(x==33 && ++y>0)不会增长
	
	&还可以用作位运算符,当&操作符两边的表达式不是boolean类型时,&表示按位与操作,我们通常使用0x0f来与一个整数进行&运算,来获取该整数的最低4个bit位,例如,0x31 & 0x0f的结果为0x01。 

	备注:这道题先说两者的共同点,再说出&&和&的特殊之处,并列举一些经典的例子来表明自己理解透彻深入、实际经验丰富。 

2.八种基本数据类型的大小,以及他们的封装类

基本类型大小(字节)默认值封装类
byte1(byte)0Byte
short2(short)0Short
int40Integer
long80LLong
float40.0fFloat
double80.0dDouble
boolean~falseBoolean
char2\u0000(null)Character

注:

1.int是基本数据类型,Integer是int的封装类,是引用类型。int默认值是0,而Integer默认值是
null,所以Integer能区分出0和null的情况。一旦java看到null,就知道这个引用还没有指向某个对象,
再任何引用使用前,必须为其指定一个对象,否则会报错。

2.基本数据类型在声明时系统会自动给它分配空间,而引用类型声明时只是分配了引用空间,必须
通过实例化开辟数据空间之后才可以赋值。数组对象也是一个引用对象,将一个数组赋值给另一个数组
时只是复制了一个引用,所以通过某一个数组所做的修改在另一个数组中也看的见。
虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何
供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机
中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素
占8位。这样我们可以得出boolean类型占了单独使用是4个字节,在数组中又是1个字节。使用int的原
因是,对于当下32位的处理器(CPU)来说,一次处理数据是32位(这里不是指的是32/64位系统,而
是指CPU硬件层面),具有高效存取的特点。

3.switch是否能作用在byte上,是否能作用在long上,是否能作用在String上?

	在switch(expr1)中,expr1只能是一个整数表达式或者枚举常量(更大字体),整数表达式可以是int基本类型或Integer包装类型,由于,byte,short,char都可以隐含转换为int,所以,这些类型以及这些类型的包装类型也是可以的。显然,long和String类型都不符合sitch的语法规定,并且不能被隐式转换成int类型,所以,它们不能作用于swtich语句中(之前的回答是这样的)。但是JDK1.7之后,switch也支持了String(注意)

4.short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?

	对于short s1 = 1; s1 = s1 + 1; 由于s1+1运算时会自动提升表达式的类型,所以结果是int型,再赋值给short类型s1时,编译器将报告需要强制转换类型的错误。
	对于short s1 = 1; s1 += 1;由于 += 是java语言规定的运算符,java编译器会对它进行特殊处理,因此可以正确编译。 

5.char型变量中能不能存贮一个中文汉字?为什么?

	char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字,所以,char型变量中当然可以存储汉字啦。不过,如果某个特殊的汉字没有被包含在unicode编码字符集中,那么,这个char型变量中就不能存储这个特殊汉字。补充说明:unicode编码占用两个字节,所以,char类型的变量也是占用两个字节。
	备注:后面一部分回答虽然不是在正面回答题目,但是,为了展现自己的学识和表现自己对问题理解的透彻深入,可以回答一些相关的知识,做到知无不言,言无不尽。 

6.使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?

使用final关键字修饰一个变量时,是指引用变量不能变,引用变量所指向的对象中的内容还是可以改变的。例如,对于如下语句:
 final StringBuffer a=new StringBuffer("immutable");
执行如下语句将报告编译期错误:
 a=new StringBuffer("");
但是,执行如下语句则可以通过编译:
a.append(" broken!"); 

有人在定义方法的参数时,可能想采用如下形式来阻止方法内部修改传进来的参数对象:
	public void method(final  StringBuffer  param)
	{
	} 
实际上,这是办不到的,在该方法内部仍然可以增加如下代码来修改参数对象:
	param.append("a");

7."=="和equals方法究竟有什么区别?

	(单独把一个东西说清楚,然后再说清楚另一个,这样,它们的区别自然就出来了,混在一起说,则很难说清楚)
	==操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用==操作符。
	如果一个变量指向的数据是对象类型的,那么,这时候涉及了两块内存,对象本身占用一块内存(堆内存),变量也占用一块内存,例如Objet obj = new Object();变量obj是一个内存,new Object()是另一个内存,此时,变量obj所对应的内存中存储的数值就是对象占用的那块内存的首地址。对于指向对象类型的变量,如果要比较两个变量是否指向同一个对象,即要看这两个变量所对应的内存中的数值是否相等,这时候就需要用==操作符进行比较。
	equals方法是用于比较两个独立对象的内容是否相同,就好比去比较两个人的长相是否相同,它比较的两个对象是独立的。例如,对于下面的代码:
    String a=new String("foo");
    String b=new String("foo");
	两条new语句创建了两个对象,然后用a,b这两个变量分别指向了其中一个对象,这是两个不同的对象,它们的首地址是不同的,即a和b中存储的数值是不相同的,所以,表达式a==b将返回false,而这两个对象中的内容是相同的,所以,表达式a.equals(b)将返回true。
	在实际开发中,我们经常要比较传递进来的字符串内容是否等,例如,String input = …;input.equals(“quit”),许多人稍不注意就使用==进行比较了,这是错误的,随便从网上找几个项目实战的教学视频看看,里面就有大量这样的错误。记住,字符串的比较基本上都是使用equals方法。
	如果一个类没有自己定义equals方法,那么它将继承Object类的equals方法,Object类的equals方法的实现代码如下:
    boolean equals(Object o){
    return this==o;
    }
	这说明,如果一个类没有自己定义equals方法,它默认的equals方法(从Object 类继承的)就是使用==操作符,也是在比较两个变量指向的对象是否是同一对象,这时候使用equals和使用==会得到同样的结果,如果比较的是两个独立的对象则总返回false。如果你编写的类希望能够比较该类创建的两个实例对象的内容是否相同,那么你必须覆盖equals方法,由你自己写代码来决定在什么情况即可认为两个对象的内容是相同的。

8.静态变量和实例变量的区别?

    在语法定义上的区别:静态变量前要加static关键字,而实例变量前则不加。
    在程序运行时的区别:实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。
	例如,对于下面的程序,无论创建多少个实例对象,永远都只分配了一个staticVar变量,并且每创建一个实例对象,这个staticVar就会加1;但是,每创建一个实例对象,就会分配一个instanceVar,即可能分配多个instanceVar,并且每个instanceVar的值都只自加了1次。
    public class VariantTest
    {
            public static int staticVar = 0; 
            public int instanceVar = 0; 
            public VariantTest()
            {
                staticVar++;
                instanceVar++;
                System.out.println(“staticVar=” + staticVar + ”,instanceVar=” + instanceVar);
            }
    }
	备注:这个解答除了说清楚两者的区别外,最后还用一个具体的应用例子来说明两者的差异,体现了自己有很好的解说问题和设计案例的能力,思维敏捷,超过一般程序员,有写作能力!

9.Integer与int的区别?

	int是java提供的8种原始数据类型之一。Java为每个原始类型提供了封装类,Integer是java为int提供的封装类。int的默认值为0,而Integer的默认值为null,即Integer可以区分出未赋值和值为0的区别,int则无法表达出未赋值的情况,例如,要想表达出没有参加考试和考试成绩为0的区别,则只能使用Integer。在JSP开发中,Integer的默认为null,所以用el表达式在文本框中显示时,值为空白字符串,而int默认的默认值为0,所以用el表达式在文本框中显示时,结果为0,所以,int不适合作为web层的表单数据的类型。
	Integer提供了多个与整数相关的操作方法,例如,将一个字符串转换成整数,Integer中还定义了表示整数的最大值和最小值的常量。

10.作用域public,private,protected,以及不写时的区别

这四个作用域的可见范围如下表所示。	
说明:如果在修饰的元素上面没有写任何访问修饰符,则表示friendly。

作用域    当前类  同一package  子孙类    其他package 
public      √        √         √            √ 
protected    √        √         √           × 
friendly     √        √         ×           × 
private      √        ×         ×           × 

备注:只要记住了有4种访问权限,4个访问范围,然后将全选和范围在水平和垂直方向上分别按排从小到大或从大到小的顺序排列,就很容易画出上面的图了。

11.Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型?

	Overload是重载的意思,Override是覆盖的意思,也就是重写。
	重载Overload表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同(即参数个数或类型不同)。
	重写Override表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,通过子类创建的实例对象调用这个方法时,将调用子类中的定义方法,这相当于把父类中定义的那个完全相同的方法给覆盖了,这也是面向对象编程的多态性的一种表现。子类覆盖父类的方法时,只能比父类抛出更少的异常,或者是抛出父类抛出的异常的子异常,因为子类可以解决父类的一些问题,不能比父类有更多的问题。子类方法的访问权限只能比父类的更大,不能更小。
	至于Overloaded的方法是否可以改变返回值的类型这个问题,要看你倒底想问什么呢?这个题目很模糊。如果几个Overloaded的方法的参数列表不一样,它们的返回者类型当然也可以不一样。但我估计你想问的问题是:如果两个方法的参数列表完全一样,是否可以让它们的返回值不同来实现重载Override。这是不行的,我们可以用反证法来说明这个问题,因为我们有时候调用一个方法时也可以不定义返回结果变量,即不要关心其返回结果,例如,我们调用map.remove(key)方法时,虽然remove方法有返回值,但是我们通常都不会定义接收返回结果的变量,这时候假设该类中有两个名称和参数列表完全相同的方法,仅仅是返回类型不同,java就无法确定编程者倒底是想调用哪个方法了,因为它无法通过返回结果类型来判断。 

12.构造器Constructor是否可被override?

构造器Constructor不能被继承,因此不能重写Override,但可以被重载Overload。

13.面向对象的特征有哪些方面

	计算机软件系统是现实生活中的业务在计算机中的映射,而现实生活中的业务其实就是一个个对象协作的过程。面向对象编程就是按现实业务一样的方式将程序代码按一个个对象进行组织和编写,让计算机系统能够识别和理解用对象方式组织和编写的程序代码,这样就可以把现实生活中的业务对象映射到计算机系统中。
	面向对象的编程语言有封装、继承 、多态等3个主要的特征。

封装:

	封装是保证软件部件具有优良的模块性的基础,封装的目标就是要实现软件部件的“高内聚、低耦合”,防止程序相互依赖性而带来的变动影响。在面向对象的编程语言中,对象是封装的最基本单位,面向对象的封装比传统语言的封装更为清晰、更为有力。面向对象的封装就是把描述一个对象的属性和行为的代码封装在一个“模块”中,也就是一个类中,属性用变量定义,行为用方法进行定义,方法可以直接访问同一个对象中的属性。通常情况下,只要记住让变量和访问这个变量的方法放在一起,将一个类中的成员变量全部定义成私有的,只有这个类自己的方法才可以访问到这些成员变量,这就基本上实现对象的封装,就很容易找出要分配到这个类上的方法了,就基本上算是会面向对象的编程了。
	例如,人要在黑板上画圆,这一共涉及三个对象:人、黑板、圆,画圆的方法要分配给哪个对象呢?由于画圆需要使用到圆心和半径,圆心和半径显然是圆的属性,如果将它们在类中定义成了私有的成员变量,那么,画圆的方法必须分配给圆,它才能访问到圆心和半径这两个属性,人以后只是调用圆的画圆方法、表示给圆发给消息而已,画圆这个方法不应该分配在人这个对象上,这就是面向对象的封装性,即将对象封装成一个高度自治和相对封闭的个体,对象状态(属性)由这个对象自己的行为(方法)来读取和改变。一个更便于理解的例子就是,司机将火车刹住了,刹车的动作是分配给司机,还是分配给火车,显然,应该分配给火车,因为司机自身是不可能有那么大的力气将一个火车给停下来的,只有火车自己才能完成这一动作,火车需要调用内部的离合器和刹车片等多个器件协作才能完成刹车这个动作,司机刹车的过程只是给火车发了一个消息,通知火车要执行刹车动作而已。

继承:

	在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并可以加入若干新的内容,或修改原来的方法使之更适合特殊的需要,这就是继承。继承是子类自动共享父类数据和方法的机制,这是类之间的一种关系,提高了软件的可重用性和可扩展性。

多态:

	多态是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。多态性增强了软件的灵活性和扩展性。例如,下面代码中的UserDao是一个接口,它定义引用变量userDao指向的实例对象由daofactory.getDao()在执行的时候返回,有时候指向的是UserJdbcDao这个实现,有时候指向的是UserHibernateDao这个实现,这样,不用修改源代码,就可以改变userDao指向的具体类实现,从而导致userDao.insertUser()方法调用的具体代码也随之改变,即有时候调用的是UserJdbcDao的insertUser方法,有时候调用的是UserHibernateDao的insertUser方法:
    UserDao userDao = daofactory.getDao();  
    userDao.insertUser(user);

14.abstract class和interface有什么区别?

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

下面比较一下两者的语法区别:
1.抽象类可以有构造方法,接口中不能有构造方法。
2.抽象类中可以有普通成员变量,接口中没有普通成员变量
3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
4. 抽象类中的抽象方法的访问类型可以是public,protected和默认类型,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
5. 抽象类中可以包含静态方法,接口中不能包含静态方法
6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static类型,并且默认即为public static类型。
7. 一个类可以实现多个接口,但只能继承一个抽象类。
	
	下面接着再说说两者在应用上的区别:
	接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约。而抽象类在代码实现方面发挥作用,可以实现代码的重用,例如,模板方法设计模式是抽象类的一个典型应用,假设某个项目的所有Servlet类都要用相同的方式进行权限判断、记录访问日志和处理异常,那么就可以定义一个抽象的基类,让所有的Servlet都继承这个抽象基类,在抽象基类的service方法中完成权限判断、记录访问日志和处理异常的代码,在各个子类中只是完成各自的业务逻辑代码,伪代码如下:
    public abstract class BaseServlet extends HttpServlet
    {
            public void service(HttpServletRequest request, HttpServletResponse response) throws IOExcetion,ServletException
            {
                记录访问日志
                进行权限判断
    if(具有权限)
    {
        try
        {
            doService(request,response);
    }
        catch(Excetpion e)
        {
                记录异常信息
        }
    }
            } 
            protected abstract void doService(HttpServletRequest request, HttpServletResponse response) throws IOExcetion,ServletException;  
    //注意访问权限定义成protected,显得既专业,又严谨,因为它是专门给子类用的
    }

    public class MyServlet1 extends BaseServlet
    {
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws IOExcetion,ServletException
            {
                本Servlet只处理的具体业务逻辑代码
            } 

    }
	父类方法中间的某段代码不确定,留给子类干,就用模板方法设计模式。
	备注:这道题的思路是先从总体解释抽象类和接口的基本概念,然后再比较两者的语法细节,最后再说两者的应用区别。比较两者语法细节区别的条理是:先从一个类中的构造方法、普通成员变量和方法(包括抽象方法),静态变量和方法,继承性等6个方面逐一去比较回答,接着从第三者继承的角度的回答,特别是最后用了一个典型的例子来展现自己深厚的技术功底。

15.String是最基本的数据类型吗?

	基本数据类型包括byte、int、char、long、float、double、boolean和short。 
	java.lang.String类是final类型的,因此不可以继承这个类、不能修改这个类。为了提高效率节省空间,我们应该用StringBuffer类 

16.String s = “Hello”;s = s + " world!";这两行代码执行后,原始的String对象中的内容到底变了没有?

	没有。因为String被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。在这段代码中,s原先指向一个String对象,内容是 "Hello",然后我们对s进行了+操作,那么s所指向的那个对象是否发生了改变呢?答案是没有。这时,s不指向原来那个对象了,而指向了另一个 String对象,内容为"Hello world!",原来那个对象还存在于内存之中,只是s这个引用变量不再指向它了。
通过上面的说明,我们很容易导出另一个结论,如果经常对字符串进行各种各样的修改,或者说,不可预见的修改,那么使用String来代表字符串的话会引起很大的内存开销。因为 String对象建立之后不能再改变,所以对于每一个不同的字符串,都需要一个String对象来表示。这时,应该考虑使用StringBuffer类,它允许修改,而不是每个不同的字符串都要生成一个新的对象。并且,这两种类的对象转换十分容易。
同时,我们还可以知道,如果要使用内容相同的字符串,不必每次都new一个String。例如我们要在构造器中对一个名叫s的String引用变量进行初始化,把它设置为初始值,应当这样做:
    public class Demo {
    private String s;
    ...
    public Demo {
    s = "Initial Value";
    }
    ...
    }
而非
	s = new String("Initial Value");
后者每次都会调用构造器,生成新对象,性能低下且内存开销大,并且没有意义,因为String对象不可改变,所以对于内容相同的字符串,只要一个String对象来表示就可以了。也就说,多次调用上面的构造器创建多个对象,他们的String类型属性s都指向同一个对象。
上面的结论还基于这样一个事实:对于字符串常量,如果内容相同,Java认为它们代表同一个String对象。而用关键字new调用构造器,总是会创建一个新的对象,无论内容是否相同。
至于为什么要把String类设计成不可变类,是它的用途决定的。其实不只String,很多Java标准类库中的类都是不可变的。在开发一个系统的时候,我们有时候也需要设计不可变类,来传递一组相关的值,这也是面向对象思想的体现。不可变类有一些优点,比如因为它的对象是只读的,所以多线程并发访问也不会有任何问题。当然也有一些缺点,比如每个不同的状态都要一个对象来代表,可能会造成性能上的问题。所以Java标准类库还提供了一个可变版本,即 StringBuffer。

17.是否可以继承String类?

String类是final类故不可以继承。 

18.String s = new String(“xyz”);创建了几个String Object? 二者之间有什么区别? xyz是字面量

两个,一个放在常量区,不管写多少遍,都是同一个。New String每写一遍,就创建一个新。

19.String 和StringBuffer的区别

JAVA平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行修改。当你知道字符数据要改变的时候你就可以使用StringBuffer。典型地,你可以使用StringBuffers来动态构造字符数据。另外,String实现了equals方法,new String(“abc”).equals(new String(“abc”)的结果为true,而StringBuffer没有实现equals方法,所以,new StringBuffer(“abc”).equals(new StringBuffer(“abc”)的结果为false。

接着要举一个具体的例子来说明,我们要把1到100的所有数字拼起来,组成一个串。
StringBuffer sbf = new StringBuffer();  
for(int i=0;i<100;i++)
{
	sbf.append(i);
}
上面的代码效率很高,因为只创建了一个StringBuffer对象,而下面的代码效率很低,因为创建了101个对象。
String str = new String();  
for(int i=0;i<100;i++)
{
	str = str + i;
}

异常

20.try {}里有一个return语句,那么紧跟在这个try后的finally {}里的code会不会被执行,什么时候被执行,在return前还是后?

会执行,在return前执行。 
我的答案是在return中间执行,参看下一题的讲解。
public  class Test {
	public static void main(String[] args) {
		System.out.println(new Test().test());;
	}
	static int test()
	{
		int x = 1;
		try
		{
			return x;//产生中断  保存断点  压栈   x=1
		}
		finally
		{
			System.out.println(++x);//接着恢复断点  弹栈  x=1
		}
	}
}
---------执行结果 ---------
1

21.final, finally, finalize的区别。

	final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。 
内部类要访问局部变量,局部变量必须定义成final类型,例如,一段代码……

	finally是异常处理语句结构的一部分,表示总是执行。

//析构函数 
	finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。JVM不保证此方法总被调用

22.运行时异常与一般异常有何异同?

	异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。

23.error和exception有什么区别?

	error 表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。不可能指望程序能处理这样的情况。 exception 表示一种设计或实现问题。也就是说,它表示如果程序运行正常,从不会发生的情况。 

24.给我一个你最常见到的runtime exception。

ArithmeticException, ArrayStoreException, BufferOverflowException, BufferUnderflowException, CannotRedoException, CannotUndoException, ClassCastException, CMMException, ConcurrentModificationException, DOMException, EmptyStackException, IllegalArgumentException, IllegalMonitorStateException, IllegalPathStateException, IllegalStateException, ImagingOpException, IndexOutOfBoundsException, MissingResourceException, NegativeArraySizeException, NoSuchElementException, NullPointerException, ProfileDataException, ProviderException, RasterFORMatException, SecurityException, SystemException, UndeclaredThrowableException, UnmodifiableSetException, UnsupportedOperationException 

线程

25.sleep() 和 wait() 有什么区别?

	sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。 wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。

	sleep就是正在执行的线程主动让出cpu,cpu去执行其他线程,在sleep指定的时间过后,cpu才会回到这个线程上继续往下执行,如果当前线程进入了同步锁,sleep方法并不会释放锁,即使当前线程使用sleep方法让出了cpu,但其他被同步锁挡住了的线程也无法得到执行。wait是指在一个已经进入了同步锁的线程内,让自己暂时让出同步锁,以便其他正在等待此锁的线程可以得到同步锁并运行,只有其他线程调用了notify方法(notify并不释放锁,只是告诉调用过wait方法的线程可以去参与获得锁的竞争了,但不是马上得到锁,因为锁还在别人手里,别人还没释放。如果notify方法后面的代码还有很多,需要这些代码执行完后才会释放锁,可以在notfiy方法后增加一个等待和一些代码,看看效果),调用wait方法的线程就会解除wait状态和程序可以再次得到锁后继续向下运行。对于wait的讲解一定要配合例子代码来说明,才显得自己真明白。
package com.huawei.interview;
public class MultiThread {
	public static void main(String[] args) {
		new Thread(new Thread1()).start();
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		new Thread(new Thread2()).start();		
	}
	private static class Thread1 implements Runnable
	{
		@Override
		public void run() {
//由于这里的Thread1和下面的Thread2内部run方法要用同一对象作为监视器,我们这里不能用this,因为在Thread2里面的this和这个Thread1的this不是同一个对象。我们用MultiThread.class这个字节码对象,当前虚拟机里引用这个变量时,指向的都是同一个对象。
			synchronized (MultiThread.class) {
				System.out.println("enter thread1...");
				System.out.println("thread1 is waiting");
				try {
			//释放锁有两种方式,第一种方式是程序自然离开监视器的范围,也就是离开了synchronized关键字管辖的代码范围,另一种方式就是在synchronized关键字管辖的代码内部调用监视器对象的wait方法。这里,使用wait方法释放锁。
					MultiThread.class.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("thread1 is going on...");
				System.out.println("thread1 is being over!");			
			}
		}
	}
	private static class Thread2 implements Runnable
	{
		@Override
		public void run() {
			synchronized (MultiThread.class) {
				System.out.println("enter thread2...");
				System.out.println("thread2 notify other thread can release wait status..");
//由于notify方法并不释放锁, 即使thread2调用下面的sleep方法休息了10毫秒,但thread1仍然不会执行,因为thread2没有释放锁,所以Thread1无法得不到锁。
				MultiThread.class.notify();
				System.out.println("thread2 is sleeping ten millisecond...");
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("thread2 is going on...");
				System.out.println("thread2 is being over!");
			}
		}
	}	
}

26.同步和异步有何异同,在什么情况下分别使用他们?举例说明。

	如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。 
当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。 

27.当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?

分几种情况:
     1.其他方法前是否加了synchronized关键字,如果没加,则能。
     2.如果这个方法内部调用了wait,则可以进入其他synchronized方法。
     3.如果其他个方法都加了synchronized关键字,并且内部没有调用wait,则不能。

28.多线程有几种实现方法?同步有几种实现方法?

多线程有两种实现方法,分别是继承Thread类与实现Runnable接口 

同步的实现方面有两种,分别是synchronized,wait与notify 
wait():使一个线程处于等待状态,并且释放所持有的对象的lock。 
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。 
notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。 
Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

29.启动一个线程是用run()还是start()?

	启动一个线程是调用start()方法,使线程就绪状态,以后可以被调度为运行状态,一个线程必须关联一些具体的执行代码,run()方法是该线程所关联的执行代码。 

30.线程的基本概念、线程的基本状态以及状态之间的关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6r1o6eyy-1681716718100)(java方向面试.assets/1681714994288.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sER49kG5-1681716718104)(java方向面试.assets/1681715012172.png)]

31.简述synchronized和java.util.concurrent.locks.Lock的异同 ?

主要相同点:Lock能完成synchronized所实现的所有功能 
主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。Lock还有更强大的功能,例如,它的tryLock方法可以非阻塞方式去拿锁。 
举例说明(对下面的题用lock进行了改写):
package com.zking.interview;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadTest {
	private int j;
	private Lock lock = new ReentrantLock();
	public static void main(String[] args) {
		ThreadTest tt = new ThreadTest();
		for(int i=0;i<2;i++)
		{
			new Thread(tt.new adder()).start();
			new Thread(tt.new subtractor()).start();
		}
	}
	private class subtractor implements Runnable
	{
		@Override
		public void run() {
			while(true)
			{
				/*synchronized (ThreadTest.this) {			
					System.out.println("j--=" + j--);
					//这里抛异常了,锁能释放吗?
				}*/
				lock.lock();
				try
				{
					System.out.println("j--=" + j--);
				}finally
				{
					lock.unlock();
				}
			}
		}
		
	}
	private class adder implements Runnable
	{
		@Override
		public void run() {
			while(true)
			{
				/*synchronized (ThreadTest.this) {
				System.out.println("j++=" + j++);	
				}*/
				lock.lock();
				try
				{
					System.out.println("j++=" + j++);
				}finally
				{
					lock.unlock();
				}				
			}			
		}
	}
}

32.设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。写出程序。

以下程序使用内部类实现线程,对j增减的时候没有考虑顺序问题。 
public class ThreadTest1 
{ 
private int j; 
public static void main(String args[]){ 
   ThreadTest1 tt=new ThreadTest1(); 
   Inc inc=tt.new Inc(); 
   Dec dec=tt.new Dec(); 
   for(int i=0;i<2;i++){ 
       Thread t=new Thread(inc); 
       t.start(); 
		   t=new Thread(dec); 
       t.start(); 
       } 
   } 
private synchronized void inc(){ 
   j++; 
   System.out.println(Thread.currentThread().getName()+"-inc:"+j); 
   } 
private synchronized void dec(){ 
   j--; 
   System.out.println(Thread.currentThread().getName()+"-dec:"+j); 
   } 
class Inc implements Runnable{ 
   public void run(){ 
       for(int i=0;i<100;i++){ 
       inc(); 
       } 
   } 
} 
class Dec implements Runnable{ 
   public void run(){ 
       for(int i=0;i<100;i++){ 
       dec(); 
       } 
   } 
} 
} 

集合

33.ArrayList和LinkedList的区别

ArrayList和LinkedList在性能上各有优缺点,都有各自所适用的地方,总的说来可以描述如下: 
1.对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。
2.在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。
3.LinkedList不支持高效的随机元素访问。
4.ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间

可以这样说:当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能;当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了

34.HashMap和Hashtable的区别

(条理上还需要整理,也是先说相同点,再说不同点)
HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,在只有一个线程访问的情况下,效率要高于Hashtable。 
HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。 
HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因为contains方法容易让人引起误解。 
Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现。 
最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访问Hashtable时,不需要自己为它的方法实现同步,而HashMap 就必须为之提供外同步。 
Hashtable和HashMap采用的hash/rehash算法都大概一样,所以性能不会有很大的差异。

就HashMap与HashTable主要从三方面来说。 
一.历史原因:Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现 
二.同步性:Hashtable是线程安全的,也就是说是同步的,而HashMap是线程序不安全的,不是同步的 
三.值:只有HashMap可以让你将空值作为一个表的条目的key或value 

35.List, Set, Map是否继承自Collection接口?

List,Set是,Map不是 

36.List、Map、Set三个接口,存取元素时,各有什么特点?

List 以特定次序来持有元素,可有重复元素。Set 无法拥有重复元素,内部排序。Map 保存key-value值,value可多值。

HashSet按照hashcode值的某种运算方式进行存储,而不是直接按hashCode值的大小进行存储。例如,"abc" ---> 78,"def" ---> 62,"xyz" ---> 65在hashSet中的存储顺序不是62,65,78。LinkedHashSet按插入的顺序存储,那被存储对象的

hashcode方法还有什么作用呢?学员想想!hashset集合比较两个对象是否相等,首先看hashcode方法是否相等,然后看equals方法是否相等。new 两个Student插入到HashSet中,看HashSet的size,实现hashcode和equals方法后再看size。
同一个对象可以在Vector中加入多次。往集合里面加元素,相当于集合里用一根绳子连接到了目标对象。往HashSet中却加不了多次的。 

37.去掉一个List集合中重复的元素

public class Demo5 {
	public static void main(String[] args) {
		List list = new ArrayList<>();
		// list.add("a");
		// list.add("b");
		// list.add("c");
		// if(!list.contains("b")) {
		// list.add("b");
		// }
		// System.out.println(list);

		list.add(new Student("zs", 16));
		list.add(new Student("wyf", 18));
		list.add(new Student("hzl", 17));
		if (!list.contains(new Student("wyf", 18))) {
			list.add(new Student("wyf", 18));
		}
		for (Object object : list) {
			System.out.println(object);
		}
	}
}

class Student {
	private String name;
	private int age;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public Student(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Student other = (Student) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

}

38.Collection 和 Collections的区别。

Collection是集合类的上级接口,继承与他的接口主要有Set 和List. 

Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。 

39.Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别?

	Set里的元素是不能重复的,元素重复与否是使用equals()方法进行判断的。 
	equals()和==方法决定引用值是否指向同一对象equals()在类中被覆盖,为的是当两个分离的对象的内容和类型相配的话,返回真值。

40.两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?

对。
如果对象要保存在HashSet或HashMap中,它们的equals相等,那么,它们的hashcode值就必须相等。
如果不是要保存在HashSet或HashMap,则与hashcode没有什么关系了,这时候hashcode不等是可以的,例如arrayList存储的对象就不用实现hashcode,当然,我们没有理由不实现,通常都会去实现的。

IO流

41.java中有几种类型的流?JDK为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类?**

字节流,字符流。字节流继承于InputStream OutputStream,字符流继承于Reader ,Writer。在java.io包中还有许多其他的流,主要是为了提高性能和使用方便。 

42.什么是java序列化,如何实现java序列化?

	我们有时候将一个java对象变成字节流的形式传出去或者从一个字节流中恢复成一个java对象,例如,要将java对象存储到硬盘或者传送给网络上的其他计算机,这个过程我们可以自己写代码去把一个java对象变成某个格式的字节流再传输,但是,jre本身就提供了这种支持,我们可以调用OutputStream的writeObject方法来做,如果要让java 帮我们做,要被传输的对象必须实现serializable接口,这样,javac编译时就会进行特殊处理,编译的类才可以被writeObject方法操作,这就是所谓的序列化。需要被序列化的类必须实现Serializable接口,该接口是一个mini接口,其中没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的。 


	例如,在web开发中,如果对象被保存在了Session中,tomcat在重启时要把Session对象序列化到硬盘,这个对象就必须实现Serializable接口。如果对象要经过分布式系统进行网络传输或通过rmi等远程调用,这就需要在网络上传输对象,被传输的对象就必须实现Serializable接口。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值