Java基础问答

java可以一次编写,到处运行的原因

jvm是java跨平台的关键,将统一字节码翻译成该平台机器码

java文件有几个public类

1个,这个类必须和java文件名一致

java访问权限

default:被该类内部成员访问,同一包下其他类访问
protected:被该类内部成员访问,同一包下其他类访问,被它的子类访问

java数据类型

整型(byte1、short2、int4、long8),浮点(float4、double8),字符(char2),boolean型

全局或成员(包含实例、类)变量,局部变量**

成员变量:类中定义,有默认值;
实例变量:未被static修饰,存储堆内存
类变量:被static修饰,存储方法区
局部变量:方法中定义,没有默认值,存储栈内存

为什么有java包装类

java面向对象的特性,设计理念为一切皆对象;但是8中基本数据类型不具备对象特性,为了让其具备,为每一个基本数据类型定义了一个引用类型,即包装类
eg:int->Integer,double->Double
显然包装类之间是不能直接比大小的,首先是对象,其次用compare to方法但类型不同,必须先转换为基本类型再比较

自动装箱,自动拆箱

装箱:把一个基本类型的数据赋给对应的包装类型
拆箱:把一个包装类型的对象赋给基本类型
当int和Integer比较大小时,Integer会自动拆箱再比较

面向对象的理解

java设计理念为一切皆对象,使用类、对象、继承、封装、消息等基本概念进行程序设计,将万事万物抽象的表示为系统中的类,作为系统的基本构成单元
三大特征:继承、封装、多态
封装
将对象的成员变量和实现细节隐藏起来,通过预先设定的方法来访问(访问控制符立功)
保护对象信息完整性,代码修改值,可维护性高
多态
子类对象可以直接赋值给父类对象,运行时依然表现出子类行为特征
相同类型变量,调用同一方法时呈现出多种不同的行为特征,即多态

BaseClass obj=new SubClass();
BaseClass obj=new BaseClase();

用重载将方法名统一的情况下,若个数过多,利用多态方式设计可以给所有参数类型约定一个共同的父类型(父类型可以为类、抽象类、接口)

class Driver{                                      class Driver{
     void driver(Car car){...}                         void drive(Vehicle vehicle){...}
     void driver(Bus bus){...}                     }
     void driver(Truck truck){...}
}

Java不用多继承原因

多继承容易发生混淆,例如1个子类的2个父类包含相同方法,调用重写会混淆

重载和重写区别

重载发生在一个类不同方法之间
重写发生在1个父类的不同子类之间(子类不能重写父类的构造方法)

Object类中的方法

getClass()返回该对象运行时的类
equals(Object obj)判断指定对象和该对象是否相等
hashCode()返回该类对象hashCode值(需要重写)
toString()返回该对象的字符串表示
hashCode()equals()关系
两个对象相等,哈希码必相同;哈希码相同,两个对象未必相等
为什么重写hashCode()equals()
equals默认用==来比较,不同类型对象之间没法比较,所以要重写。
hashCode和equals有联动性,一起重写,使其满足二者间相关约定

String类中方法

大概看一看,String无法被继承
char charAt(int index):返回指定索引处的字符;
String substring(int beginIndex, int endIndex):从此字符串中截取出一部分子字符串;
String[] split(String regex):以指定的规则将此字符串分割成数组;
String trim():删除字符串前导和后置的空格;
int indexOf(String str):返回子串在此字符串首次出现的索引;
int lastIndexOf(String str):返回子串在此字符串最后出现的索引;
boolean startsWith(String prefix):判断此字符串是否以指定的前缀开头;
boolean endsWith(String suffix):判断此字符串是否以指定的后缀结尾;
String toUpperCase():将此字符串中所有的字符大写;
String toLowerCase():将此字符串中所有的字符小写;
String replaceFirst(String regex, String replacement):用指定字符串替换第一个匹配的子串;
String replaceAll(String regex, String replacement):用指定字符串替换所有的匹配的子串。
StringStringBuffer区别
String为不可变类,一旦String对象被创建,对象中字符序列不可变
StringBuffer对象创建字符序列可变字符串append(),insert(),reverse(),setCharAt(),setLength
StringBufferStringBuilder区别
二者都代表可变的字符串对象,有共同父类,构造方法和成员方法基本相同
但StringBuffer线程安全,StringBuilder非线程安全,后者性能略高,优先后者
""new使用字符串比较
"hello"使用常量池来管理
new String("hello")先用常量池管理,再创建对象保存堆内存中,效率不如上一个
字符串拼接
拼接字符串直接量,用+;
字符串中有变量,根据要求线程安全与否,用StringBuffer,StringBuilder
两个字符串拼接,含变量concat(若是多个效率不如第二种)

接口和抽象类

同:不能被实例化,继承树顶端,都可以有抽象方法

异:
2021-03-24_114124.jpg

异常处理

2021-03-24_120010.jpg

try:包裹业务代码,创建异常对象
catch:捕获异常,记录日志,进行相应处理
finally:回收在业务代码中打开资源资源,不论异常是否发生,finally总被执行
(在finally块中使用return、throw语句,会导致try,catch中的return、throw失效)

static和final区别

final关键字
final可以修饰成员方法,成员变量,类

修饰成员方法:表示该方法是最终方法,不能被重写
eg:public final void method()
修饰成员变量:表示该变量是常量,不能再次被赋值
eg:public final int age=20
修饰类:表示该类是最终类,不能被继承
eg:public final class Fu{}

static关键字
被static修饰的变量和方法是类变量和类方法
特点:
被类的所有对象共享(是否使用静态关键字条件)
eg:student类中设置:public static String university
可以用类名调用(推荐),也可以用对象名调用
eg:Student.university="邮电大学"; s1.university="邮电大学"

访问特点
非静态成员方法:能访问静态和非静态的成员变量和成员方法
静态成员方法:只能访问静态的成员变量和成员方法

对泛型的理解

针对java集合缺陷:
将一个对象放入集合后,再取出对象的原本数据类型被忘记,编译时类型变为Object,因此取出集合元素后还需要强制类型转换。容易引发ClassCastException异常
纠正方式:
创建集合时指定集合元素类型,即泛型eg:List
泛型擦除
具有泛型信息对象赋给一个没有泛型信息变量时,<>内信息将被扔出
List list1=...; List list2=list1;//list2将元素当做Object处理
将上述操作反过来即是泛型转换
ListList区别
super设定下限,?必须是T的父类型
extends设定上限,?必须是T的子类型

Java反射机制理解

概括可以在运行时得到某个类的完整信息,指在运行时去获取一个类的变量和方法信息。然后通过获取到的信息来创建对象,调用方法的一种机制。
实际情景:当程序接收到外部传入的一个对象,编译时类型是Object,但又要调用该对象类型的方法。就要通过反射使用一个类。先获取类的class对象、就可以再获取类的成员变量,类的成员方法使用
Class clz=Class.forname("java.lang.String")
String str=new String();Class clz=str.getClass();
应用场景:JDBC,创建数据库连接,先通过反射机制加载数据库驱动程序

Java中的IO流

Java把不同的IO源头抽象表示为流。流是从起源到接收的有序数据,程序可以利用其采用同一方式访问不同的IO源

2021-03-24_154509.jpg

主要看访问文件、数组、字符串和缓冲流
https://www.jianshu.com/p/a82af2a28ab8
数据流向分类:输入流(只读不写)、输出流(只写不读)
数据类型分类:字节流(数据单元8字节)、字符流(数据单元16字节)
处理功能分类:节点流(红色)(对特定IO设备读写)、处理流(蓝色)(对节点流连接或封装)
用流打开大文件
1.使用缓冲流,建立缓冲区,减少IO次数
2.使用NIO。NIO将文件或文件的一段区域映射到内存,这样可以像访问内存一样访问文件,IO速度大大提升

Java序列化和反序列化

支持序列化机制:类要带上Serializable接口,来表明该类可序列化
序列化:将Java对象写入IO流中(对象->字节序列)
使用对象流ObjectInputStream中的writeObject()
反序列化:从IO流中恢复Java对象(字节序列->对象)
使用对象流ObjectOutputStream中的readObject()
序列化工具
JSON,格式化数据工具,简单直观,可读性好。有jackson、gson、fastjson等
对大项目而言,效率远高于Java原生的序列化机制

serializable接口要定义serialVersionUID原因

serialVersionUID代表序列化的版本,同一对象序列化和反序列化的时候版本号要一致。若缺失,会使得类和已序列化的数据的格式发生冲突。

多线程

创建线程方式
继承Thread类(子类只能继承一个父类,但接口可以有多个)
创建Thread子类,重写run()方法(执行体);根据子类创建线程对象start()启动
实现Runnable接口(多线程共享对象,处理同一份资源)
定义Runable实现类,实现run()方法;创建实例,创建Thread对象,start()启动
MyRunnable my=new MyRunnable();Thread t1=new Thread(my);
实现Callable接口
...
run()和start()
一般调用start()启动线程,之后再自动运行线程执行体run()方法。
如果直接调用run()方法,在其返回前其他的线程无法并发执行,系统会把线程对象当初普通对象,run()方法也只是普通方法
线程是否能重启
只能对新建状态下的线程调用start()方法,否则将引发illegalThreadStateException异常
线程生命周期
OS中常见5态:新建、就绪、运行、阻塞、死亡
start()后进入就绪态
获得cpu后执行run()方法进入运行态
调用sleep()、阻塞式IO、等待notify、suspend()挂起,同步监视器被占用时进入阻塞态
执行体执行完、抛错、stop()时进程死亡
凭优先级抢占式调度
进程同步实现
synchronized
同步方法:有synchronized修饰的方法,调用前要先获得内置锁(静态方法√)
同步代码块:有synchronize修饰的语句块,开销大,内容越精简越好(静态代码块×)
Lock锁
利用Lock接口的实现类ReentrantLock类,和synchronized具有相同的基本行为语义
区别:
synchronized:关键字,JVM层面加锁解锁,代码块方法上,自动释放,导致线程一直等待锁,无法知道是否获取锁成功
Lock:接口,代码层面加锁解锁,代码中,有finally显示释放锁,设置获取锁超时时间,得知加锁是否成功
在保证进程同步和安全的情况下多进程通信方式
每个锁对象都有就绪队列和阻塞队列两个队列
synchronized(使用的时Object类中声明方法)
1.wait()当前线程释放对象锁,进入阻塞状态
2.notify()唤醒一个正在等待相应对象锁线程,使其进入就绪态
3.notifyAll()唤醒所有正在等待相应对象锁的线程,使其进入就绪态
Lock锁(调用的时condition接口中方法)
await(),signal(),signalAll()和上面对应
sleep()和wait()区别
sleep():Thread类中静态方法,任何地方使用,不释放锁
wait():Object类成员方法,同步部分使用,释放锁
实现子线程先执行,主程序再执行
启动子程序,调用该线程join()方法,此时主线程必须等待子线程完成
join()方法:Thread类提供让一个线程等待另一个完成的方法

Java代码的编译过程

一个准备过程和3个处理过程
初始化插入式注解处理器,解析填充符号表,注解处理,分析与字节码生成

类加载过程

加载——>验证——>准备——>解析——>初始化——>使用——>卸载
双亲委派模型
如果一个类加载器收到了类加载请求,首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成(父类无法完成,子类才自己动手)。对每一层的类加载器都是如此操作,因此所有加载请求都会被传递到最顶层的启动类加载器中。
自定义类加载器——>应用程序类加载器——>扩展类加载器——>启动类加载器

Java垃圾回收机制(即Java GC)

Java内存运行时,堆和方法区两个区域有着显著的不确定性(一个接口多个是实现类内存不一,一个方法不同分支内存不一),只有运行时,程序创建对象的个数才能确定,因此内存的分配和回收必须时动态的。垃圾收集器关注这部分内存如何管理。
定义垃圾
垃圾:不能再被使用的对象
识别垃圾方法
引用计数法:对象中添加一个引用计数器,引用一次,计数器+1,应用失效,计数器-1
(计数器为0则不可能再被使用)
可达性分析算法:从起始节点集GC Roots出发,根据引用关系向下搜索Object,若某个对象不可达(无引用链),则此对象不可能再被使用。
怎样回收垃圾
分代收集:收集器将Java堆划分出不同的区域,将回收对象依据其年龄分配到不同的区域之中存储。(大部分对象朝生夕灭,能熬过越多次收集的对象越难消亡)
标记-清除算法:先标记出所有要回收的对象,标记完成后统一回收
标记-复制算法:内存分为大小相等2块,每次只使用一块,当前块使用完,将其存活块移到另外一块内存上面,然后当前块内存空间一次清理。
标记-整理算法:标记过程和清除算法一样,让错后对象向内存空间一端移动,让后直接清除掉边界以外的内存
完整GC流程图
新建对象分配在新生代中,新生代分为Eden,Survivor,Survivor(8:1:1)3个区,使用ParNew垃圾回收器

25639543-c67a5921666fa652 (1).png

Java什么时候触发GC,FullGC会导致什么后果,如何减少FullGC次数
Eden空间耗尽,出发一次MinorGC收集新生代垃圾,存活下来的送入Survivor区
FullGC会“stop the world”,GC期间暂停用户的应用程序
增加方法区,老年代,新生代空间来减少FullGC次数

内存泄露和内存溢出

内存泄露:程序运行给临时变量分配内存,用完后没有被GC回收,始终占用
解决方案:及早释放无用对象引用,避免循环中创建对象,别用String,少用静态变量(静态变量基本不参与GC)
内存溢出:程序运行中申请内存大于系统能够提供的内存,导致无法申请到足够的内存
解决方案:修改JVM启动参数,直接增加内存;查日志找OutofMemory错误前异常或错误;分析代码,找溢出位置;使用内存查看工具动态查看

Java四种引用方式

面中兴被问到了,没说全,这里总结一下

  • 强引用:Java中最常见的引用,即程序创建一个对象,并把对象赋给引用变量,通过引用变量来操作实际对象。
  • 软引用:当一个对象只有软引用时,有可能被GC回收,是系统内存空间而定。
  • 弱引用:和软引用很像,但弱引用级别更低,不管系统内存是否足够,对象所占内存总被GC回收
  • 虚引用:虚引用类似于完全没有引用。对对象本身没有太大影响。主要用于跟踪对象被GC回收的状态,需和引用队列一起使用。

你可能感兴趣的:(Java基础问答)