JAVA基础面试题

  • 1.Java 基础 知识
  • 1.1 面向对象的特征
  • 1.2 JAVA中基本数据类型
  • 1.3 JDK JRE JVM 的区别
  • 1.4 重载和重写的区别
  • 1.5 Java 中 == 和 equals 的区别
  • 1.6 String StringBuffer StringBuilder 区别
  • 1.7 接口和抽象类的区别是什么
  • 1.8 String 类的常用方法有哪些
  • 1.9 什么是单例模式 有几种
  • 1.10 反射
  • 1.11 JDK 1.8 的新特性 (高薪常问)
  • 1.12 Java 的异常
  • 1.13 BIO、NIO、AIO 有什么区别?(高薪常问)
  • 1.14 Threadloal的原理(高薪常问)
  • 1.16 同步锁、死锁、乐观锁、悲观锁 (高薪常问)
  • 1.17说一下 synchronized 底层实现原理?(高薪常问)
  • 1.18 synchronized 和 volatile 的区别是什么?(高薪常问)
  • 1.19synchronized 和 Lock 有什么区别? (高薪常问)
  • 1.20 手写冒泡


1.Java 基础 知识

1.1 面向对象的特征

**面向对象的特征 : 封装 继承 多态 抽象**

			**封装** : 就是把对象的属性和行为 (数据) 结合为一个独立的整体, 并尽可能隐藏对象的内部实现细节, 
			就是把不想告诉或者不该告诉别人的东西隐藏起来, 把可以告诉别人的公开, 别人只能用的提供的功能实现需求, 
			而不知道是如何实现的. 增加安全性

			简单理解 : 就是把数据封装成一个整体的数据模型   
					所有对象也好属性也好行为也好 其实本质都是数据 
					把多种数据组合到一起形成一个整体 我叫做一个数据模型

			**继承** : 子类继承父类的数据属性和行为
						并能根据自己的需求扩展出新的行为,提高代码的复用性
			简单理解  让子类拥有父类的数据 本质上是对父类数据的引用


			**多态** : 指允许不同的对象对同一个消息做出响应.
						即同一消息可以根据发送对象的不同而采用多种不同的行为方式 (发送消息就是函数调用).
						封装和继承几乎都是为多态而准备的,在执行期间判断引用对象的实际类型,
						根据其实际的类型调用其相应的方法。
			简单理解  多态就是一个对象的多种形态,前提这个对象属于某一个分类的子类,例如猫属于动物分类的一个子类  
			本质也是对父类数据的引用 
			注意 编译看右 运行看左原则

			**抽象** : 表示对问题领域进行分析 设置中得出的抽象概念 
			是对一系列看上去不同 但是本质上相同的具体概念的抽象 
			在JAVA中 抽象用abstract 关键字来修饰 用 abstract修饰类时 此类就不能被实例化 
			从这里可以看出 抽象类(接口) 就是为了继承而存在的.

			简单理解 抽象就是概念性的问题 需要具象化成实例来解决问题

1.2 JAVA中基本数据类型

**分类** 分为四类八种  这种一般就是简单的叫法 整型 浮点型 布尔型 字符型
				具体分为 byte short int long float double char Boolean
				
				在JAVA中基本数据类型详解
				
				字节数 在内存中占用的字节数 位数 就是比特位
				
				byte 字节型 字节数 1 位数 8
				
				short 短整型 字节数 2 位数 16
				
				int 整数型 字节数 4 位数 32
				
				long 长整型 字节数 8 位数 64
				
				float 单精度浮点数 字节数 4 位数 32
				
				double 双精度浮点数 字节数 8 位数 64
				
				boolean 逻辑型 字节数 1 位数 8
				
				char 字符型 字节数 2 位数 16

1.3 JDK JRE JVM 的区别

**包含关系图如下**

java 基础知识 面试 java基础知识面试宝典_java


套娃模式 JDK > JRE > JVM

JDK  Java Development Kit 是整个 Java 的和兴 是 Java开发工具包,包括了 Java 运行环境 JRE Java 工具和Java 基础类库.
	
	基础类库 就是 Java 提供的 ...class文件里面提供了 很多的功能 
	
	我们开发Java 只需要安装一个JDK就行了不用安装JRE和JVM
	
	JRE  Java Runtime Environment 是运行 JAVA 程序所必须的环境的集合
	包含java虚拟机和java程序的一些核心类库。

    JVM 是 Java Virtual Machine(Java 虚拟机)的缩写,是整个 java 实现跨平台的最核心的部分
    能够运行以 Java 语言写作的软件程序。

1.4 重载和重写的区别

重载: 在同一个类中,方法名必须相同,参数类型不同 个数不同顺序不同 
		方法返回值和访问修饰符可以不同 发生在编译时.
		
		简单理解: 前提 同一个类中 方法名相同 
				参数类型或且个数不同或且顺序不同

	重写: 发生在父类中 方法名 参数列表必须相同 返回值范围小于等于父类
	抛出的异常范围小于等于父类 访问修饰符范围大于等于附列 如果父类方法访问修饰符为private 则子类不能重写该方法
	
		简单理解 前提 被重写方法修饰符不能为 private 修饰的 且 必须具备继承或实现关系
			注意 重写的方法必须和被重写的方法参数列表 返回值 异常范围 修饰符等保持一致

1.5 Java 中 == 和 equals 的区别

== 的作用:
	基本类型: 比较的值是否相同
	引用类型: 比较的就是地址值是否相同


	equals 的作用

	引用类型: 默认情况下 比较的是地址值

	注意 当 equals 方法被重写时比较的内容就不一定是地址了

	面试题:请解释字符串比较之中 “ == ” 和 equals() 的区别?
	答: ==:比较的是两个字符串内存地址(堆内存)的数值是否相等
	属于数值比较; equals():比较的是两个字符串的内容,属于内容比较。

1.6 String StringBuffer StringBuilder 区别

String 字符串常量
	StringBuffer 字符串变量 (线程安全)
	StringBuilder 字符串变量 (非线程安全)


		String 中的 String 类中使用 final 关键字修饰字符数组来保存字符串 private final char value[]  String 对象时不可变的
		也就可以理解为常量 线程安全
		
		AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类
		定义了一些字符串的基本操作 如 expandCapacity append insert indexOf 等公共方法

		StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁 所以是线程安全的

		StringBuilder 并没有对方法进行加同步锁 所以是非线程安全的

			用法
				如果要操作少量的数据用String
				多线程操作字符串缓冲区下操作大量数据用 StringBuffer
				单线程操作字符串缓冲区下操作大量数据用StringBuilder

1.7 接口和抽象类的区别是什么

实现而言 
			抽象类的子类使用 extends来继承 
			接口必须使用 implement 来实现接口

	构造函数 
			抽象类可以有构造函数
			接口不能有

	main 方法
			抽象类可以有main方法 并且我们能运行它
			接口不能有 main 方法
	
	实现数量 
			类可以实现多个接口
			类只能继承一个抽象类

	访问修饰符
			接口中的方法默认使用 public 修饰
			抽象类中的方法可以是任务访问修饰符

1.8 String 类的常用方法有哪些

indexOf()  返回指定字符的索引
			例
			String address = "中华人民共和国";
			int index = address.indexOf('人');
			index 结果为 2;

	charAt()  返回指定索引处的字符
			例 
			String address = "中华人民共和国";
			char result = address.charAt(1);
			result 结果为 '中'

	replace()  字符串替换
			例
			String str = "哟西花姑凉哟西,米西米西";
			String resultStr = str.replace("哟西" , "");
			resultStr 结果为 "花姑凉,米西米西"
	
	trim() 去除字符串两端空白
			例
			String str = " helloworld ";
			String resultStr = str.trim();
			resultStr 结果为 helloworld

	split() 分割字符串 返回一个分割后的字符串数组
			例
			String str = "a-b-c";
			String resultStr[] = str.split("-");
			resultStr 结果为 {a, b, c}

	getBytes() 返回字符串的 byte 类型数组
			例
			String str = "curd";
			byte bytes[] = str.getBytes();
			bytes 结果为 {c的byte, u的byte, r的byte, d的byte}

	length() 返回字符串长度
			例
			String str = "北京欢迎你";
			int length = str.length();
			length 结果为 str字符串的长度

	toLowerCase() 将字符串转成小写字母
			例
			String str = "ABC";
			String resultStr = str.toLowerCase();
			resultStr 结果为 "abc"

	toUpperCase() 将字符串抓成大写字母
			例
			String str = "abc";
			String resultStr = str.toUpperCase();
			resultStr 结果为 "ABC"

	substring() 截取字符串
			例
			String str = "helloworld";
			String resultStr = str.substring(1);
			resultStr 结果为 "elloworld"

	equals() 字符串比较
			例
			String stra = "helloworld";
			String strb = "helloworld";
			boolean result = stra.equals(strb);
			result 结果为 true

1.9 什么是单例模式 有几种

单例模式 某个类的实例在 多线程环境下只会被创建一次出来
	单例模式有饿汉单例模式  懒汉单例模式 双检锁单例模式三种

饿汉单例模式 : 线程安全 一开始就初始化

java 基础知识 面试 java基础知识面试宝典_经验分享_02


懒汉单例模式 : 非线程安全 延迟初始化

java 基础知识 面试 java基础知识面试宝典_经验分享_03


双检锁 : 线程安全 延迟初始化

java 基础知识 面试 java基础知识面试宝典_经验分享_04

1.10 反射

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

			调用某个对象的getClass() 方法
				Account account = new Account();
				Class clazz = account.getClass();

			调用某个类的 class 属性来获取该类对应的 Class 对象
				Class clazz = Account.class;

			使用 Class 类中的 forName() 静态方法 (最安全/性能最好)
				Class clazz = Class.forName("类的全路径"); (最常用)

1.11 JDK 1.8 的新特性 (高薪常问)

1 Lambda 表达式 

	Lambda 允许把函数作为一个方法的参数。 
	
	例 new Thread( () -> System.out.println("线程A")).start();

2 方法引用

	方法引用允许直接引用已有 Java 类或对象的方法或构造方法

	例
		ArrayList<String> list = new ArrayList<>();
		list.add("a");
		list.forEach(System.out::println);
	
	上例中我们将 System.out::println 方法作为静态方法来引用

3 函数式接口
	有且仅有一个抽象方法的接口叫做函数式接口 函数式接口可以被隐式转换为 Lambda 表达式 通常函数式接口上会天剑 @Functionallnterface 注解

4 接口允许定义默认方法和静态方法
	从JDK8 开始 允许接口中存在一个或多个默认非抽象方法和静态方法

5 Stream API
	新添加的Stream API (java.util.stream) 把真正的函数式编程风格引入到 Java 中 这种风格 将要处理的元素集合看作一种流 流在管道中传输 并且可以在管道的节点上进行处理 比如筛选 排序 聚合等
List<String> list = Arrays.asList("a", "a", "b", "c", "a");
list.stream()//获取到集合的流对象
			.filter( str -> !str.isEmpty("c"))//过滤掉包含"c"的字符串
			.distinct()//去重
			.forEach(str -> System.out.println(str));//遍历打印集合
6 日期/时间类改进 
	之前的 JDK 自带的日期处理类非常不方便,
	我们处理的时候经常是使用的第三方工具包,
	比如 commons-lang 包等。不过 JDK8 出现之后这个改观了很多,
	比如日期时间的创建、比较、调整、格式化、时间间隔等。 
	这些类都在 java.time 包下,LocalDate/LocalTime/LocalDateTime。
 
7 Optional 类 
	Optional 类是一个可以为 null 的容器对象。如果值存在则 isPresent() 方法会返回 true,
	调用 get()方法会返回该对象。
	
		String string = "abc";
		Optional<String> optional = Optional.of(string);
		boolean present = optional.isPresent();
		String value = optional.get();
		System.out.println(persent + "/" + value);

8 Java8 Base64 实现
	Java 8 内置了 Base64 编码的编码器和解码器。

1.12 Java 的异常

java 基础知识 面试 java基础知识面试宝典_面试_05


Throwable是所有Java程序中错误处理的父类,有两种资类:Error和Exception。

Error:表示由JVM所侦测到的无法预期的错误,由于这是属于JVM层次的严重错误

导致JVM无法继续执行,因此,这是不可捕捉到的,无法采取任何恢复的操作,顶多只能显示错误信息。

Exception:表示可恢复的例外,这是可捕捉到的。

1.运行时异常:
 	都是RuntimeException类及其子类异常,
 	如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,
 	程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,
 	程序应该从逻辑角度尽可能避免这类异常的发生。运行时异常的特点是Java编译器不会检查它,
 	也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

2.非运行时异常 (编译异常):
	是RuntimeException以外的异常,类型上都属于Exception类及其子类。
	从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。
	如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
	常见的RunTime异常几种如下:
	NullPointerException - 空指针引用异常 
	ClassCastException - 类型强制转换异常。 
	IllegalArgumentException - 传递非法参数异常。 
	ArithmeticException - 算术运算异常 
	ArrayStoreException - 向数组中存放与声明类型不兼容对象异常 
	IndexOutOfBoundsException - 下标越界异常 
	NegativeArraySizeException - 创建一个大小为负数的数组错误异常 
	NumberFormatException - 数字格式异常 
	SecurityException - 安全异常 
	UnsupportedOperationException - 不支持的操作异常

1.13 BIO、NIO、AIO 有什么区别?(高薪常问)

BIO:
	Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。

NIO:
	New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。

AIO:
	Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。

1.14 Threadloal的原理(高薪常问)

ThreadLocal:
	为共享变量在每个线程中创建一个副本,每个线程都可以访问自己内部的副本变量。通过threadlocal保证线程的安全性。
	其实在ThreadLocal类中有一个静态内部类ThreadLocalMap(其类似于Map),
	用键值对的形式存储每一个线程的变量副本,ThreadLocalMap中元素的key为当前ThreadLocal对象,而value对应线程的变量副本。
  	ThreadLocal 本身并不存储值,它只是作为一个 key保存到ThreadLocalMap中,
  	但是这里要注意的是它作为一个key用的是弱引用,因为没有强引用链,弱引用在GC的时候可能会被回收。
  	这样就会在ThreadLocalMap中存在一些key为null的键值对(Entry)。因为key变成null了,我们是没法访问这些Entry的,
  	但是这些Entry本身是不会被清除的。如果没有手动删除对应key就会导致这块内存即不会回收也无法访问,也就是内存泄漏。
	使用完ThreadLocal之后,记得调用remove方法。 在不使用线程池的前提下,
	即使不调用remove方法,线程的"变量副本"也会被gc回收,即不会造成内存泄漏的情况。

1.16 同步锁、死锁、乐观锁、悲观锁 (高薪常问)

同步锁:
	当多个线程同时访问同一个数据时,很容易出现问题。
	为了避免这种情况出现,我们要保证线程同步互斥,就是指并发执行的多个线程,在同一时间内只允许一个线程访问共享数据。
	Java 中可以使用 synchronized 关键字来取得一个对象的同步锁。 
	
死锁:
	何为死锁,就是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。
	
乐观锁:
	总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,
	但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。
	乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_conditio机制,
	其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
	
悲观锁:
	总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,
	这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,
	用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,
	比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

1.17说一下 synchronized 底层实现原理?(高薪常问)

一、syn锁住的是对象,对象里边的组成如下:  

	Java的对象布局
	
	        必须是要求对象的大小必须是8的整数倍
	        1.首先是有一个object header
	        2.填充数据 :当对象大小不足8的倍数的时候,他会把当前对象填充成8的倍数,假如他现在本身就是8的byte的倍数,此时填充数据就不用了
	        3.成员变量 :就是咱们成员变量


二、锁升级

	2.1 偏向锁
	
		线程要尝试去加锁,他会去判断当前这个mark-word里边是否包含线程id,如果没有线程id的话,
		他会去利用cas把自己的线程id写入到mark-word里边去第二次这个线程再次过来的时候,
		他会去判断当前这个mark-word里边是否包含线程id,如果有了的话,
		他就会把他自己的线程id和对象头里边的线程id进行对比,如果发现是一样的此时就标识获得到了锁
		如果你在没加锁的情况打印对象头:他默认就是 无锁可偏向,如果你没加锁的情况计算了hashCode码,无锁不可偏向,
		如果此时你加锁无法成为偏向锁,直接膨胀成一把轻量级锁  

	2.2 轻量级锁
	
	    1、 升级成轻量级锁三个条件
	        a、你现在已经是无锁不可偏向,此时加锁那么他就直接是一把轻量级锁
	        b、没有关闭延迟偏向锁打开,他会自动成为轻量级锁
	        c、如果程序出现交替执行,他也会成为一把轻量级锁
	    2、原理 
	       a、首先方法压栈,此时这个方法栈帧就压栈,栈帧种就创建两个和锁有关系的空间 displace hrd ,owner
	       b、他会将锁里边的mark-word里边信息拷贝到hrd种
	       c、他会用栈帧owner 指针去指向我们的对象头
	       d、对象头中轻量级指针会指向当前创建出来的这个栈帧
	       e、锁会把当前的状态修改成00
	       f、如果说完成了以上4件事情,那么此时才表示加锁成功,这把锁就是属于当前线程的

	2.3 重量级锁
	
		1、原理
		
		   a、Java如果发现要创建一把重量级锁,我们Java就会为我们创建一个C++的ObjectMonitor,
		   会让对象头中monitor指向我们这个ObjectMonitor对象如果你进入到锁内部时,这个ObjectMonitor他会 发起汇编指定 monitorenter,
		   当你出syn代码代码块的时候,他会发出monitorexit指令如果你在执行syn过程中出现了异常,其实上他还是会执行monitorexit这样一个指令
		   
	       b、对象头里边monitor指针会指向 ObjectMonitor 对象,当多个线程来加锁的时候他,他们就会执行monitorenter 指令,
	       进入到 ObjectMonitor进入到entrylist中等待抢锁,他们会利用cas 来进行抢锁,如果抢锁成功,ObjectMonitor,
	       他会把他内部的owner的指针去指向咱们抢锁成功 线程,然后会让计数器+1,如果此时是抢锁的线程是持有锁的线程,
	       那么此时count就会再+1 ,释放锁的时候,把count进行--,直到count== 0的时候就把owner 置为null,
	       如果你对线程调用wait方法,此时这些被wait的线程他就会进入到waitSet中,只有当你去调用notifyall方法的时候他才会从新开始这样一套流程
	      
	       c、如果是同步方法,那么他执行的 指令 acc_synchronized  ,但是这哥们是隐式调用

1.18 synchronized 和 volatile 的区别是什么?(高薪常问)

volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; 
	synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
	
volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。

volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性。

volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。

volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。

1.19synchronized 和 Lock 有什么区别? (高薪常问)

首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
synchronized会自动释放锁(a 线程执行完同步代码会释放锁;b 线程执行过程中发生异常会释放锁),
Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,
线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可);
Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

1.20 手写冒泡

public class Sort {

	public static void sort() {
        Scanner input = new Scanner(System.in);
        int sort[] = new int[10];
        int temp;
        System.out.println("请输入10个排序的数据: ");
        for (int i = 0; i < sort.length; i++) {
            sort[i] = input.nextInt();
        }
        for (int i = 0; i < sort.length - 1; i++) {
            for (int j = 0; j < sort.length - i - 1; j++)
                if (sort[j] < sort[j + 1]) {
                    temp = sort[j];
                    sort[j] = sort[j + 1];
                    sort[j + 1] = temp;
                }
        }
        System.out.println("排列后的顺序为: ");
        for (int i = 0; i < sort.length; i++) {
            System.out.print(sort[i] + "=");
        }
    }

    public static void main(String[] args) {
        sort();
    }
}