JavaSE
切勿背死知识
这些都是必须知道并且面试中常问到一些问题,我们在学习的时候不能死记硬背,我们更应该去融会贯通,在我们自己所做的项目中,哪里用到了这些知识,当面试官问到的时候,我们不能只说这些死知识,我们更应该说的是在自己的项目中的应用,这样你的回答才能够让面试官信服。
JavaSE
如何实现跨平台
对不同的平台有不同的虚拟机,虚拟机会将字节码文件解释为对应平台的指令.
JDK:java development kit 里面包含jdk和 编译器,能够创建和编译程序
JRE:java运行环境,为java程序提供运行环境,里面包括JVM,类库,java命令
JVM:java虚拟机,它是java实现跨平台的主要原因
谈谈你对面向对象的认识理解
面向对象是将一个事务模块化,分块完成,不像面向过程是流程化的,需要一步一步分析一步步实现,就拿一个大家熟知的事情,冰箱装大象,面向过程一步步来,打开门,大象进冰箱,关门. 面向对象则是分析其中的对象,有人有大象,有冰箱,然后每个对象有自己独特的功能和属性
面向对象的三大特征
封装
隐藏类的信息,不向外界暴露,隐藏实现细节,向外提供特定的方法访问.
就像挂在墙上的空调,我们并不知道他的内部的零件,但是可以通过遥控器来控制空调,
成员变量私有化,单例模式是封装的一种体现
继承
不同的对象之间可能会有共同点,就像我和同学之间,都有学生的特性,都有学号,有班级,每个学生也有自己独特的优势,继承在已存在的类上建立新类,就像孩子继承了父亲的 一些特征,他又有自己的新的属性,通过继承可以快速的创建有关联的新的类,提高代码的重用性
子类拥有父类的非private的属性和方法
可以对父类进行扩展(重写)
优点:
代码复用,易于扩展,维护
缺点:
打破了父类的封装性
父类和子类联系紧密,耦合度高 , 父类一旦发生了变化,子类可能会受影响
单一 聚合
面向对象设计7大原则
- 单一职责:类专注于一个功能,高内聚,低耦合
- 开闭:开放扩展(添加新功能,不改原由代码),关闭修改
- 里氏替换(多态):父类出现的地方,子类可以代替出现
- 依赖倒置:尽量依赖抽象,不依赖具体实现
- 接口隔离:为客户端提供小的单独接口
- 迪米特法则:实体之间应减少相互作用
- 聚合复用:尽量使用合成达到服用,减少继承,一个类中有另一个类的对象
多态(面试官最喜欢问的是你都在那些场景下使用了多态)
一个对象的多种状态,封装和继承都是为多态服务的,方法的重载和重写是实现多态的两种方式
三要素: 继承 重写 父类引用指向子类对象
优点:提升程序可维护性,可扩展性
缺点:不能调用子类特有的方法 解决方法:向下转型
好处
(1)向上转型:隐藏了子类类型,提高代码的扩展性。
(2)向下转型:可以使用子类特有功能。
弊端
(1)向上转型:只能使用父类共性的内容,无法使用子类特有功能。
(2)向下转型:容易发生类型转换异常(ClassCastException)
访问权限
public 修饰类,变量,方法 在任何地方都可以访问
proected 修饰变量,方法 在同包中,不通包的子类中访问
默认 修饰类,变量,方法 在同包类中可以访问
private 修饰变量,方法,内部类 只能在本类中访问
Java类初始化顺序
基类静态代码块,基类静态成员字段(并列优先级,按照代码中出现的先后顺序执行,且只有第一次加载时执行)——>派生类静态代码块,派生类静态成员字段(并列优先级,按照代码中出现的先后顺序执行,且只有第一次加载时执行)——>基类普通代码块,基类普通成员字段(并列优点级,按代码中出现先后顺序执行)——>基类构造函数——>派生类普通代码块,派生类普通成员字段(并列优点级,按代码中出现先后顺序执行)——>派生类构造函数.
java中创建对象的方式
- new + 类名
- 反射(Class类的newInstance方法、使用Constructor类的newInstance方法) Class.forName(“com.ff.Car”)
- 对象反序列化 IO(类实现Serializable接口,构建对象流ObjectOutputStream,用writeObject()将对象写入文件,构建ObjectInputStream读取文件,使用readObject() 构建一个对象)
- 对象clone()
对象创建过程(从JVM角度出发)
- 当遇到new时,去运行时常量池查找该引用指向的类有没有被加载
- 没有加载就进行类加载,解析和初始化
- 在堆内为该对象分配空间
- 分配完后,将对象的字段进行零值初始化,除去对象头,对创建的对象进行信息标记,所有的标记存放在对象头信息内,赋值为0或null
- 执行构造方法,调用对象的init方法进行初始化
对象头
实例数据:对象的实例数据就是在java代码中能看到的属性和他们的值
对齐填充字节:因为JVM要求java的对象占的内存大小应该是8bit的倍数,所以后面有几个字节用于把对象的大小补齐至8bit的倍数,没有特别的功能。
1. Mark Word
Mark Word记录了对象和锁有关的信息,当这个对象被synchronized关键字当成同步锁时,围绕这个锁的一系列操作都和Mark Word有关,锁升级的过程和Mark Word有着密不可分的关系。
2. 指向类的指针
Java对象的类数据保存在方法区。
3.数组长度(只有数组对象才有)
包括两部分信息,第一部分用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等,这部分数据的长度在32位和64位的虚拟机(暂 不考虑开启压缩指针的场景)中分别为32个和64个Bits,官方称它为“Mark Word”。
对象头的另外一部分是类型指针,即是对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说查找对象的元数据信息并不一定要经过对象本身。另外,如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小。
对象克隆,浅克隆,深克隆
为什么使用克隆?
new的对象是一个全新的对象,里面没有数据,通过克隆的对象会保留被克隆对象的值
什么是对象克隆?
创建一个新的对象,将原对象中的数据复制到新对象中
浅克隆?实现cloneable
基本类属于浅克隆,
只将对象中关联的对象的引用地址复制属于浅克隆,不拷贝对象包含的引用指向的对象
深克隆?()实现serializable读取二进制流
只将对象中关联的对象也进行了克隆,多级克隆.
解决多级克隆: 在关联的对象中 继续重写克隆方法
通过对象序列化,反序列化.
构造方法
方法名与类名相同,没有返回值,不可以被void修饰
一个类中可以有多个构造方法, 默认有一个无参的构造(隐式),一旦显示的定义有参的构造方法,默认的就失效,
一般情况,显示的定义无参的构造方法
作用: 初始化创建对象的成员变量
new Car(int age,int a) Car() age=0; String name=null
重载和重写
重载: 在一个类中,有多个名称相同的方法,参数不同,返回值和访问修饰符可以不同
重写:在继承关系中,父类实现不能满足子类的需求,在子类中重写(覆盖,覆写)父类方法, 功能的扩展,super.父类()
两同:方法名相同,参数相同
一大:访问修饰符要大于等于父类
一小:返回值小于等于父类,抛出的异常小于等于父类
为什么构造方法不能重写
构造方法名与当前类名相同, 重写要求与父类方法结构一致.
在子类的构造方法中调用父类的构造方法, 必须放在子类构造方法的第一行.
静态static
表示静态(指在内存只有一份的),被类的实例对象共享,可以任意赋值
修饰内部类,成员变量,成员方法,代码块,静态导包
都是随着类的加载而加载
非静态可以访问静态的
抽象类和接口
作用:抽象,功能定义 开闭原则,里氏替换原则都依赖接口,抽象类.
相同点:
都可以包含抽象方法, 不能被实例化.
单继承 | 多继承 |
---|---|
可以有普通属性 | 常量,被public static final修饰 |
抽象 | 接口 |
有构造方法,不是创建对象,用来被调用进行初始化 | 没有构造方法 |
可以有普通方法,抽象方法没有方法体 | default和static有方法体,抽象方法没有方法体 |
Object 类中的方法
equals()
hashCode()
wait()
notify()
notifyAll()
finalize()
clone()
== 和 equals()的区别
Object()中的 equals()的源码
、
对于基本数据类型
== 和equals() 的作用是一样的,都是比较的是值是否一样
对于引用类型
==比较的是两个对象的地址是否相同,而equals()则需要看是否重写了Object类中的equals()方法,就比如下面的String类中,重写了equals()方法,它比较的就是对象中的值是否相同
String
特征
一旦创建时赋值后,值不可变.不可以被继承.
底层是用final修饰的char数组
final class String {
final char[] value;
}
为什么设计为不可变
- 便于实现字符串常量池
因为大量使用string,每次声明都会创建一个string对象,造成空间浪费,于是在堆里开辟了string pool,初始化string变量时现在里面找,有了就返回字符串的引用
- 使多线程安全
在并发场景下,多个线程同时读一个资源,是安全的,不会引发竞争,但对资源进行写操作时是不安全的,不可变对象不能被写,所以保证了多线程的安全。
- 避免安全问题
在网络连接和数据库连接中字符串常常作为参数,例如,网络连接地址URL,文件路径path,反射机制所需要的String参数。其不可变性可以保证连接的安全性。如果字符串是可变的,黑客就有可能改变字符串指向对象的值,那么会引起很严重的安全问题。
- 加快字符串处理速度
由于String是不可变的,保证了hashcode的唯一性,于是在创建对象时其hashcode就可以放心的缓存了,不需要重新计算。这也就是Map喜欢将String作为Key的原因,处理速度要快过其它的键对象。所以HashMap中的键往往都使用String。
总体来说,String不可变的原因要包括 设计考虑,效率优化,以及安全性这三大方面。
String的值不可以改变吗?
我们可以通过Java的反射机制进行改变
//通过反射改变String底层数组值
public static void main(String[] args) throws Exception {
String s = "Hello World";
System.out.println("s = " + s); //Hello World
//获取String类中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value");
//改变value属性的访问权限
valueFieldOfString.setAccessible(true);
//获取s对象上的value属性的值
char[] value = (char[]) valueFieldOfString.get(s);
//改变value所引用的数组中的第5个字符
value[5] = '_';
System.out.println("s = " + s); //Hello_World
System.out.println(s);//Hello_World
}
String对象创建方式
String s1 = “abc”;
方法1中,先在常量池中查找有没有"abc"这个字符串对象存在,
如果存在就把常量池中的指向这个字符串对象;
String s2 = new String(“abc”);
方法2中,不论常量池中中是否已经存在"abc"这个字符串对象,都会新建一个对象。
String strA = " abc " ;
String strB = " abc " ;
String strAA = new String( " abc " );
String strBB = new String( " abc " );
System.out.println(strA == strB);//true
System.out.println(strAA == strBB);// false
创建了几个对象?
1.
String s = new String("abc")创建了几个对象
情况1: 创建两个对象
String s = new String("abc");
直接new对象,此前在字符串常量池中不存在"abc",此种情况在堆中创建一个对象,然后在字符串常量池中创建一个对象
情况2: 创建一个对象
String s = "abc";
String s1 = new String("abc");
因为字符串常量池中已存在"abc",只需要在堆中创建即可.
2.
创建了一个对象,底层优化创建了一个StringBuilder对象,append()追多个字符串对象,只产生一个对象.
String st1 = "a" + "b" + "c";//只创建了一个字符串对象
String st2 = "abc";
System.out.println(st1 == st2);//true
3.
String st1 = "ab";
String st2 = "abc";
String st3 = st1 + "c";//一旦发生拼接中有变量,会重新创建一个字符串对象
System.out.println(st2 == st3);//false
String,StringBuffer,StringBuilder区别
String 值不可以改变 操作少量数据
StringBuffer 继承 AbstractStringBuilder类 值可变 线程安全的 多线程大量数据 16*2+2
StringBuilder 继承 AbstractStringBuilder类 值可变 线程不安全 单线程大量数据
基本类型和包装类
int和Integer区别
Integer是int的包装类,int则是java的一种基本数据类型
Integer变量必须实例化后才能使用,而int变量不需要
Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值
Integer的默认值是null,int的默认值是0
为每一个基本类提供了一个类包装基本类.使用面向对象方式操作
自动装箱( valueOf() ) Integer a = 10
注意 -128 +127 之间会从缓存池中直接获取
自动拆箱(intvalue() ) int b = a;
语法糖
所谓语法糖,可简单理解为Java平台为我们自动进行了一些转换,保证不同的写法在运行时等价。因此它们是发生在编译阶段的,也就是说生成的字节码是一致的。
自动装箱与拆箱实际上算是一种“语法糖”。
异常
什么是异常?
运行时异常,可以使用异常处理机制处理 不是错误(OOM)
异常体系
异常分类
运行时异常
编译期(检查)异常 编写代码时,需要处理
常见的异常类有哪些?
NullPointerException 空指针异常
ClassNotFoundException 指定类不存在
NumberFormatException 字符串转换为数字异常
IndexOutOfBoundsException 数组下标越界异常
ClassCastException 数据类型转换异常
FileNotFoundException 文件未找到异常
NoSuchMethodException 方法不存在异常
IOException IO 异常
SocketException Socket 异常
异常处理
try{
//可能会发生异常代码
}catch( 异常类型 ){
//处理异常
return 0;
}finally{
//无论是否出现异常,都会执行
return 1;
}//最终返回的是1,因为finally会覆盖catch中return的值
try{
//可能出现异常的代码
}finally{
//处理
}
//通过throws和throw来处理异常
throws
用于在方法声明处, 表示此方法可能会出现某种异常(),然后在调用处处理
throw
在方法体中,主动抛出一个异常对象. 当某种情况不满足时,向调用处抛出异常
自定义异常
根据业务需要,自定义不同的异常类.
extends Exception{
private int detail;
public MyException(int a){
this.detail = a;
}
**toString();** //异常的打印信息
}