知识屋:更实用的电脑技术知识网站
所在位置:首页 > 科技

Java开发面试

发表时间:2022-03-24来源:网络

基础问题

进程和线程的区别

进程:资源分配的最小单位
线程:程序执行的最小单位

比喻:进程是一列火车,线程就是这列火车的某个车厢。

什么是上下文切换

对于单核CPU来说(对于多核CPU,此处就理解为一个核),CPU在一个时刻只能运行一个线程,当在运行一个线程的过程中转去运行另外一个线程,这个叫做线程上下文切换(对于进程也是类似)。

内存的堆和栈(类型 变量名和new 类型的区别)

jvm内存分为:方法区、堆、栈、程序计数器
方法区:存放方法
栈:放变量,对象
堆:放内容
程序计数器:用来存放程序数量

博客园
举例:

String a = "abc"; String b = new String("abc");

a在内存的栈地址中生成,由系统自动进行变量周期管理;内存的栈区域是连续内存占用,就像数组那样
b在内存的堆地址中生成,由用户自己进行变量周期管理;内存的堆区域是非连续内存占用,就像链表那样

String, StringBuffer与StringBuilder的区别

String 字符串常量,显然,一旦赋值不可修改内容。

String a = "Hello"; a = a + " world!";//注意,这里实际上是在堆内存中创建了"Hello"," world!",以及"Hello world!"三个不同的内存地址,然后将a指向"Hello world!"。也就是说创建了三个常量地址,只不过a的指针指向的位置最后改为最后一个位置。

StringBuffer和StringBuilder是字符串变量
如果不申请新的堆内存地址,只是对字符串变量进行修改。就用这两个。
但是他们有如下区别:

StringBuilder的速度比StringBuffer快,但是非线程安全。StringBuffer虽然速度稍慢,但线程安全。即多线程可使用。

参考CSDN

泛型

泛型就是泛化类型的简称。顾名思义,泛型就是需要写类型的地方暂时空着,用泛型替代,以后实际使用到的时候,可以接收任何类型。
百度知道
知乎

序列化和反序列化

把对象转换为字节序列的过程称为对象的序列化。
把字节序列恢复为对象的过程称为对象的反序列化。

简书

equals和hashcode

equals(): 用来判断两个对象是否相同,再Object类中是通过判断对象间的内存地址来决定是否相同hashCode(): 获取哈希码,也称为散列码,返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。

Java和C++的区别

CSDN大体区别
CSDN细节区别

静态与非静态的区别

非静态方法是相对于静态方法来说的。静态方法使用static关键字来标示,非静态方法没有此关键字。

他们之间最大的区别在于它们生命周期的不同,静态方法属于全局方法,当静态方法第一次被访问的时候,就将常驻内存直到整个系统被销毁;

而非静态方法则与类有关,只有该方法所在的类被实例化之后,该方法才能被访问,类被销毁的同时方法也被销毁。

生命周期的不同决定了它们的调用方式的不同,静态方法使用(类名.方法名)来调用,而非静态方法则需要(new 类名().方法名)来调用。
同时与之相关的就是,静态方法不能调用非静态方法和属性。在了解了它们生命周期的不同后,这一点也比较好理解,因为静态方法生命周期比非静态方法和属性长,当调用非静态方法和属性时就会产生非静态方法和属性已经被销毁的情况导致程序出错。

==和equals

在Java中:
==比较的是地址
equals比较的是内容

比如:
ex.1

String a = "abc"; String b = "abc";

a==b返回True//注意这里是因为a和b都是在内存的栈地址生成的变量,由于栈地址的生成规则,a和b实际上指向内存的同一地址,因此 ==的结果为True;

a.equals(b)自然返回也是True

ex.2

String a = "abc"; String b = new String("abc"); String c = new String("abc"); a==b==False a==c==False b==c==False a.equals(b)==True a.equals(c)==True b.equals(c)==True

重载和重写的区别

重写:在子类中对父类的虚函数进行实现。
重载:在同一个类中,不同方法使用相同的函数名,但参数类型和参数个数不同。
1、重载的规则:

必须具有不同的参数列表。可以有不同的访问修饰符。可以抛出不同的异常。

2、重写方法的规则:

参数列表必须完全与被重写的方法相同,否则不能称其为重写而是重载。返回的类型必须一直与被重写的方法的返回类型相同,否则不能称其为重写而是重载。访问修饰符的限制一定要大于或等于被重写方法的访问修饰符。重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常。

try-catch和finally中的return执行顺序

try-catch中的语句->finally->try-catch中的return
如果finally中有return则try-catch中的return不执行

public,protected,private,friendly(default)

默认:同一包中的类可以访问,声明时没有加修饰符,认为是friendly。

JDK, JRE, JVM的区别

JDK: 开发工具包,是Java开发环境的核心组件,并且提供编译、调试和运行一个Java程序所需要的所有工具,可执行文件和二进制文件。
JRE: Java运行时环境,是JVM的实现,提供了运行Java程序的平台。JRE包含了JVM。
JVM: Java虚拟机,当我们运行一个程序时,JVM负责将字节码转换为特定机器代码,JVM提供了内存管理/垃圾回收和安全机制等。

区别和联系:

JDK是开发工具包,用来开发Java程序,而JRE是Java的运行时环境JDK和JRE中都包含了JVMJVM是Java编程的核心,独立于硬件和操作系统,具有平台无关性,而这也是Java程序可以一次编写,多处执行的原因

Java的平台无关性是怎样实现的

JVM屏蔽了操作系统和底层硬件的差异Java面向JVM编程,先编译生成字节码文件,然后交给JVM解释成机器码执通过规定基本数据类型的取值范围和行为

Java语言是编译型还是解释型语言

Java的执行经历了编译和解释的过程,是一种先编译,后解释执行的语言,不可以单纯归到编译性或者解释性语言的类别中。

抽象类和接口的主要区别

抽象类中可以没有抽象方法,也可以抽象方法和非抽象方法共存接口中的方法在JDK8之前只能是抽象的,JDK8版本开始提供了接口中方法的default实现抽象类和类一样是单继承的;接口可以实现多个父接口抽象类中可以存在普通的成员变量;接口中的变量必须是static final类型的,必须被初始化,接口中只有常量,没有变量

总结:抽象类就像领导提出一个大概的思路或者想法,这是抽象的,需要被员工(继承这个抽象类的子类)去实现;而接口从名字来看,就是接上就能用的,这种拿来就用的设计思路意味着接口中的方法必须是不可继承的,必须是可以被用户拿去使用的,而为了保证接口的功能强大,接口可以同时继承多个父类,也就是把多家公司的技术整合一下。即,抽象类的使用者关注的是实现,接口的使用者关注的是使用。

抽象类和接口应该如何选择?分别在什么情况下使用呢?

根据抽象类和接口的不同之处,当我们仅仅需要定义一些抽象方法而不需要其余额外的具体方法或者变量的时候,我们可以使用接口。反之,则需要使用抽象类,因为抽象类中可以有非抽象方法和变量。

一个类使用了两个不同的接口,但这两个接口有同名的抽象方法怎么办

重写多个接口中的相同的默认方法在实现类中指定要使用哪个接口中的默认方法

JDK8中为什么会出现接口默认方法

使用接口,使得我们可以面向抽象编程,但是其有一个缺点就是当接口中有改动的时候,需要修改所有的实现类。在JDK8中,为了给已经存在的接口增加新的方法并且不影响已有的实现,所以引入了接口中的默认方法实现。在我们实际开发中,接口的默认方法应该谨慎使用,因为在复杂的继承体系中,默认方法可能引起歧义和编译错误。

Java中的8种基本数据类型及其取值范围

byte:1字节short:2字节int:4个字节long:8字节float:4字节double:8字节char:2字节boolean:Java规范中并没有规定boolean类型所占字节数

Java中的元注解有哪些

Java中提供了4个元注解,元注解的作用是负责注解其它注解。
他们分别是:

@Target 说明注解所修饰的对象范围@Retention 保留策略定义了该注解被保留的时间长短。@Documented 该注解用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被javadoc此类的工具文档化。@Inherited 该注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

注解的作用:代替繁杂的配置文件,简化开发。

如何定义一个注解

public @interface MyAnn { //@interface用于定义一个注解 String value(); int value1(); } // 使用注解MyAnn,可以设置属性 @MyAnn(value1=100,value="hello") public class MyClass { }

反射机制

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

反射机制的作用:

在运行时判断任意一个对象所属的类在运行时构造一个类的对象在运行时判断任意一个类所具有的成员变量和方法在运行时调用任意一个对象的方法,生成动态代(dai)理

与反射相关的类:

Class:表示类,用于获取类的相关信息Field:表示成员变量,用于获取实例变量和静态变量等Method:表示方法,用于获取类中的方法参数和方法类型等Constructor:表示构造器,用于获取构造器的相关参数和类型等

获取Class类有三种基本方式

类名称.classClass c = int.class; Class c = int[ ].class; Class c = String.class 对象.getClass( )Class c = obj.getClass( ); Class.forName( )Class c = Class.forName(“cn.ywq.Demo”);

以反射方式创建对象

package com.ywq; public class Demo1 { public static void main(String[] args) throws Exception { String className = "com.ywq.User"; // 获取Class对象 Class clazz = Class.forName(className); // 创建User对象 User user = (User)clazz.newInstance(); // 和普通对象一样,可以设置属性值 user.setUsername("yangwenqiang"); user.setPassword("19931020"); System.out.println(user); } } class User { private String username; private String password; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User [username=" + username + ", password=" + password + "]"; } }

Exception和Error的区别

Exception是程序正常运行中预料到可能会出现的错误,并且应该被捕获并进行相应的处理,是一种异常现象Error是正常情况下不可能发生的错误,Error会导致JVM处于一种不可恢复的状态,不需要捕获处理,比如说OutOfMemoryError

总结:Exception是可预料的异常,比如读写文件路径不存在等。而Error通常是硬件,系统等突发性状况产生的,不是可预料的。

Exception的分类:

编译时异常(受检异常)表示当前调用的方法体内部抛出了一个异常,所以编译器检测到这段代码在运行时可能会出异常,所以要求我们必须对异常进行相应的处理,可以捕获异常或者抛给上层调用方。运行时异常(非受检异常)表示在运行时出现的异常,常见的运行时异常包括:空指针异常,数组越界异常,数字转换异常以及算术异常等。

异常Exception应该被捕获,我们可以使用try – catch – finally 来处理异常,并且使得程序恢复正常。

捕获异常应该遵循哪些原则

尽可能捕获比较详细的异常,而不是使用Exception一起捕获。当本模块不知道捕获之后该怎么处理异常时,可以将其抛给上层模块。上层模块拥有更多的业务逻辑,可以进行更好的处理。捕获异常后至少应该有日志记录,方便之后的排查。不要使用一个很大的try – catch包住整段代码,不利于问题的排查。

NoClassDefFoundError 和 ClassNotFoundException 有什么区别

ClassNotFoundException :通过Class.forName获取类对象的时候,传入的类名没有找到对应的类。
NoClassDefFoundError :程序通过了编译,但是执行的时候发现要new的对象找不到对应的类定义,这种情况一般是由于打包的时候漏掉了部分类或者Jar包被篡改已经损坏。

ArrayList和LinkedList有哪些区别

ArrayList底层使用了动态数组实现,实质上是一个动态数组LinkedList底层使用了双向链表实现,可当作堆栈、队列、双端队列使ArrayList在随机存取方面效率高于LinkedList LinkedList在节点的增删方面效率高于ArrayListArrayList必须预留一定的空间,当空间不足的时候,会进行扩容操作 LinkedList的开销是必须存储节点的信息以及节点的指针信息

总结:ArrayList是动态数组,LinkList是链表。多线程环境下,我们可以使用CopyOnWriteArrayList替代ArrayList来保证线程安全。还有一个集合Vector,它是线程安全的ArrayList。

HashSet和TreeSet有哪些区别

HashSet底层使用了Hash表实现。
保证元素唯一性的原理:判断元素的hashCode值是否相同。如果相同,还会继续判断元素的equals方法,是否为true

TreeSet底层使用了红黑树来实现。
保证元素唯一性是通过Comparable或者Comparator接口实现

其实,HashSet的底层实现还是HashMap,只不过其只使用了其中的Key,具体如下所示:
HashSet的add方法底层使用HashMap的put方法将key = e,value=PRESENT构建成key-value键值对,当此e存在于HashMap的key中,则value将会覆盖原有value,但是key保持不变,所以如果将一个已经存在的e元素添加中HashSet中,新添加的元素是不会保存到HashMap中,所以这就满足了HashSet中元素不会重复的特性。
HashSet的contains方法使用HashMap得containsKey方法实现

LinkedHashMap和LinkedHashSet

LinkedHashMap内部的Entry继承于HashMap.Node,这两个类都实现了Map.EntryLinkedHashMap的Entry不光有value,next,还有before和after属性,这样通过一个双向链表,保证了各个元素的插入顺序通过构造方法public LinkedHashMap(int initialCapacity,floatloadFactor,boolean accessOrder), accessOrder传入true可以实现LRU缓存算法(访问顺序)LinkedHashSet底层使用LinkedHashMap实现,两者的关系类似与HashMap和HashSet的关系,大家可以自行类比。

什么是LRU算法?LinkedHashMap如何实现LRU算法?

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

由于LinkedHashMap可以记录下Map中元素的访问顺序,所以可以轻易的实现LRU算法。只需要将构造方法的accessOrder传入true,并且重写removeEldestEntry方法即可。具体实现参考如下:

package pak2; import java.util.LinkedHashMap; import java.util.Map; public class LRUTest { private static int size = 5; public static void main(String[] args) { Map map = new LinkedHashMap(size, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > size; } }; map.put("1", "1"); map.put("2", "2"); map.put("3", "3"); map.put("4", "4"); map.put("5", "5"); System.out.println(map.toString()); map.put("6", "6"); System.out.println(map.toString()); map.get("3"); System.out.println(map.toString()); map.put("7", "7"); System.out.println(map.toString()); map.get("5"); System.out.println(map.toString()); } }

Iterator和ListIterator的区别是什么

Iterator可以遍历list和set集合;ListIterator只能用来遍历list集合Iterator前者只能前向遍历集合;ListIterator可以前向和后向遍历集合ListIterator其实就是实现了前者,并且增加了一些新的功能。

数组和集合List之间的转换

Arrays.asList以及List.toArray方法

package niuke; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class ConverTest { public static void main(String[] args) { // list集合转换成数组 ArrayList list = new ArrayList(); list.add("zhangsan"); list.add("lisi"); list.add("yangwenqiang"); Object[] arr = list.toArray(); for (int i = 0; i "niuke", "alibaba"}; List asList = Arrays.asList(arr2); for (int i = 0; i @Override public int compareTo(Object o) { // 在这里边定义其比较规则 return 0; } } public static void main(String[] args) { // 方式二:创建TreeMap的时候,可以指定比较规则 new TreeMap(new Comparator() { @Override public int compare(User o1, User o2) { // 在这里边定义其比较规则 return 0; } }); }

Comparable接口和Comparator接口有哪些区别

Comparable实现比较简单,但是当需要重新定义比较规则的时候,必须修改源代码,即修改User类里边的compareTo方法Comparator接口不需要修改源代码,只需要在创建TreeMap的时候重新传入一个具有指定规则的比较器即可。

衍生语言及框架问题

Spring和Spring boot

JavaScript

Android

软件工程

软件开发的周期

每个周期的经典方法

收藏
  • 人气文章
  • 最新文章
  • 下载排行榜
  • 热门排行榜