这篇文章专注于Java基础知识,不涉及List、Map、多线程、锁相关的内容,需要的可以查看我的其他博客hofe's bloghhf443.github.io
JDK&JRE&JVM
JDK(Java Development Kit)是针对 Java 开发员的产品,是整个 Java 的核心,包括了 Java 运行环境 JRE、Java 工具(编译、开发工具)和 Java 核心类库。
Java Runtime Environment(JRE)是运行 JAVA 程序所必须的环境的集合,包含 JVM 标准实 现及 Java 核心类库。
JVM 是 Java Virtual Machine(Java 虚拟机)的缩写,是整个 java 实现跨平台的最核心的部 分,能够运行以 Java 语言写作的软件程序
跨平台
字节码是在虚拟机上运行的,而不是编译器。换而言之,是因为 JVM 能跨平台安装,所以 相应JAVA字节码便可以跟着在任何平台上运行。只要JVM自身的代码能在相应平台上运行, 即 JVM 可行,则 JAVA 的程序员就可以不用考虑所写的程序要在哪里运行,反正都是在虚拟 机上运行,然后变成相应平台的机器语言,而这个转变并不是程序员应该关心的
一、基本数据类型
0.1*3精度问题(6.6f+1.3f)
可参考这篇:https://mp.weixin.qq.com/s?__biz=MzIwNTk5NjEzNw==&mid=2247490447&idx=2&sn=ef68a5adbad88fe4012b78356a25bdf5&chksm=97293289a05ebb9f29e9d6c96d9f86d4f4a7bd69c47d6fe92a3591d3c3ab76701b40f544d4e8&mpshare=1&scene=23&srcid=&sharer_sharetime=1590383496510&sharer_shareid=d476d18cbe4a83b141ea1ff413565f8c#rd
二、包装类
2.1 为什么需要包装类
由于基本数据类型不是对象,所以 java 并不是纯面向对象的语言,好处是效率较高(全部 包装为对象效率较低)。 Java 是一个面向对象的编程语言,基本类型并不具有对象的性质,为了让基本类型也具有 对象的特征,就出现了包装类型(如我们在使用集合类型 Collection 时就一定要使用包装类 型而非基本类型),使得它具有了对象的性质,并且为其 添加了属性和方法,丰富了基本类型的操作。
2.2 自动装箱、自动拆箱(编译器行为)
自动装箱:可以将基础数据类型包装成对应的包装类
自动拆箱:可以将包装类转为对应的基础数据类型
Integer i = 10000; // 编译器会改为 new Integer(10000) int i = new Integer(1000);//编译器会修改为 int i = new Integer(1000).intValue();
自动拆箱时如果包装类是 null,那么会抛出 NPE
2.3 Integer数组范围(-128~127)
java中如果Integer不是new出Integer对象,而是Integer.valueOf或者直接赋值如:
Integer b1 = 12;
Integer b2 = 12;
这种情况是在常量池中开辟出同一个空间来存储12,所以b1和b2都指向12
接下来说说,Integer的缓冲范围,因为不是在堆区new一个对象,那么在常量池中就必须对其的大小范围做出一个规定,就是数值多少的可以存放在缓存内
如果超出了范围,会从堆区new一个Integer对象来存放值
源码中static final int low = -128;规定了下限为-128,但是最大范围没有确定下来,上限可以通过设置JDK的AutoBoxCacheMax参数调整。
所以在比较-128~127内的两个Integer数据时因为都是常量池的对象,所以==或equals都是true;超过这个范围会在堆中new 对象,==比较的是内存地址,返回 false。
2.4 == 与 equals的区别
如果两个引用类型变量使用==运算符,那么比较的是地址,它们分别指向的是否是同一地 址的对象。要求是两个对象都不是空值,与空值比较返回 false。 ==不能实现比较对象的值是否相同(无法重写)。
所有对象都有 equals 方法,默认是 Object 类的 equals,其结果与==一样。 如果希望比较对象的值相同,必须重写 equals 方法。
public boolean equals(Object obj) { return (this == obj); }
2.5 hashCode 与 equals的区别
Object 中的 equals:
public boolean equals(Object obj) { return (this == obj); }
equals 方法要求满足:
自反性 a.equals(a)
对称性 x.equals(y) ==y.equals(x)
一致性 x.equals(y) 多次调用结果一致
对于任意非空引用 x,x.equals(null) 应该返回 false
Object 中的 hashCode:
public native int hashCode();
它是一个本地方法,它的实现与本地机器有关,这里我们暂且认为他返回的是对象存储的物理位置。
当 equals 方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规约定:值相同的对象必须有相同的 hashCode。
object1.equals(object2)为 true,hashCode 也相同;
hashCode 不同时,object1.equals(object2)为 false;
hashCode 相同时,object1.equals(object2)不一定为 true; // 多个key的hash值相同
2.6 hashCode 与 equals重写问题
向一个 Hash 结构的集合中添加某个元素时,先调用 hashCode,唯一则存储,不唯一则再调用 equals,结果相同则不再存储,结果不同则散列到其他位置。
2.6.1 为什么要重写equals()方法?
因为object中的equals()方法比较的是对象的引用地址是否相等,如果你需要判断对象里的内容是否相等,则需要重写equals()方法。
2.6.2 为什么改写了equals(),也需要改写hashcode()
如果你重写了equals,比如说是基于对象的内容实现的,而保留hashCode的实现(基于内存地址的hash值)不变,那么在添加进map中时需要比对hashcode,很可能某两个对象明明是“相等”,而hashCode却不一样。
2.6.3 为什么改写了hashcode(),也需要改写equals()
改写hashcode()方法是为了让两个值相同的对象hashcode也一样,而不再是基于内存地址,在map中表现为可能是存储在同一位置的一个对象。而如果不改写equals,还是基于内存地址进行比较,这样的话,两个值相同的对象就不被映射到同一位置。
Hashmap的key可以是任何类型的对象,例如User这种对象,为了保证两个具有相同属性的user的hashcode相同,我们就需要改写hashcode方法,比方把hashcode值的计算与User对象的id关联起来,那么只要user对象拥有相同id,那么他们的hashcode也能保持一致了,这样就可以找到在hashmap数组中的位置了。如果这个位置上有多个元素,还需要用key的equals方法在对应位置的链表中找到需要的元素,所以只改写了hashcode方法是不够的,equals方法也是需要改写。
2.7 String
String 是 final 类,不可被继承,也不可重写一个 java.lang.String(类加载机制)。 一般是使用 StringUtils 来增强 String 的功能。
字符串修改的时候会创建一个新的字符串,编译时会将+转为 StringBuilder 的 append 方法。 注意新的字符串是在运行时在堆里创建的。
String#intern(JDK1.7 之后) JDK1.7 之后 JVM 里字符串常量池放入了堆中,之前是放在方法区。 intern()方法设计的初衷,就是重用 String 对象,以节省内存消耗。 一定是 new 得到的字符串才会调用 intern,字符串常量没有必要去 intern。 当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由equals(Object) 方法确定),则返回池中的字符串。否则,常量池中直接存储堆中该字符串 的引用(1.7 之前是常量池中再保存一份该字符串)。
2.8 StringBuffer与StringBuilder
StringBuffer 是线程安全的,StringBuilder 不是线程安全的,但它们两个中的所有方法都是 相同的。StringBuffer 在 StringBuilder 的方法之上添加了 synchronized,保证线程安全。
StringBuilder 比 StringBuffer 性能好10%-15%。
三、关键字
final关键字
可以修饰类,函数,变量。
被final修饰的类不可以被继承,final类中的方法默认是final的
被final修饰的方法不能被重写
被final修饰的变量是一个常量只能赋值一次
static关键字
一句话来概括:方便在没有创建对象的情况下来进行调用。
修饰内部类、成员变量、成员方法、代码块
1、static关键字修饰内部类
java里面static一般用来修饰成员变量或函数。但有一种特殊用法是用static修饰内部类,普通类是不允许声明为静态的,只有内部类才可以。下面看看如何使用。
public class StaticTest {//static关键字修饰内部类public static class InnerClass{InnerClass(){System.out.println("====== 静态内部类======");}public void InnerMethod() {System.out.println("===== 静态内部方法=====");}}public static void main(String[] args) {//直接通过StaticTest类名访问静态内部类InnerClassInnerClass inner=new StaticTest.InnerClass();//静态内部类可以和普通类一样使用inner.InnerMethod();}}/*输出是* ============= 静态内部类=============* ============= 静态内部方法=============*/
如果没有用static修饰InterClass,则只能new 一个外部类实例。再通过外部实例创建内部类。
成员内部类和静态内部类的区别:
1)前者只能拥有非静态成员;后者既可拥有静态成员,又可拥有非静态成员
2)前者持有外部类的引用,可以访问外部类的静态成员和非静态成员;后者不持有外部 类的引用,只能访问外部类的静态成员
3)前者不能脱离外部类而存在;后者可以
2、static关键字修饰方法
修饰方法的时候,其实跟类一样,可以直接通过类名来进行调用:
public class StaticMethod {public static void test() {System.out.println("======= 静态方法====");};public static void main(String[] args) {//方式一:直接通过类名StaticMethod.test();//方式二:StaticMethod fdd=new StaticMethod();fdd.test();}}
在静态方法中不能访问类的非静态成员变量和非静态成员方法; 在非静态成员方法中是可以访问静态成员方法/变量的; 即使没有显式地声明为 static,类的构造器实际上也是静态方法
3、static关键字修饰变量
被static修饰的成员变量叫做静态变量,也叫做类变量,说明这个变量是属于这个类的,而不是属于是对象,没有被static修饰的成员变量叫做实例变量,说明这个变量是属于某个具体的对象的。
我们同样可以使用上面的方式进行调用变量:
public class StaticVar {private static String name="java的架构师技术栈";public static void main(String[] args) {//直接通过类名StaticVar.name;}}
静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本, 它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候 被初始化,存在多个副本,各个对象拥有的副本互不影响。 静态成员变量并发下不是线程安全的,并且对象是单例的情况下,非静态成员变量也不是线 程安全的。 怎么保证变量的线程安全? 只有一个线程写,其他线程都是读的时候,加 volatile;线程既读又写,可以考虑 Atomic 原 子类和线程安全的集合类;或者考虑 ThreadLocal
4、static关键字修饰代码块
用来构造静态代码块以优化程序性能。static 块可以置于类中的任何地方,类中可以有多个 static 块。在类初次被加载的时候,会按照 static 块的顺序来执行每个 static 块,并且只会执 行一次。
静态代码块在类第一次被载入时执行,在这里主要是想验证一下,类初始化的顺序。
- 父类静态变量、父类静态代码块
- 子类静态变量、子类静态代码块
- 父类普通变量、父类普通代码块、父类构造函数
- 子类普通变量、子类普通代码块、子类构造函数
四、面向对象
4.1 面向对象与面向过程的本质的区别
在于考虑问题的出发点不同,
面向过程是以事件流程为考虑问题的出发点,
而面向对象则是以参与事件的角色(对象)为考虑问题的出发点
4.2 抽象类与接口
区别:
1)抽象类中方法可以不是抽象的;接口中的方法必须是抽象方法;
2)抽象类中可以有普通的成员变量;接口中的变量必须是 static final 类型的,必须被初始 化 , 接口中只有常量,没有变量。
3)抽象类只能单继承,接口可以继承多个父接口;
4)Java8 中接口中会有 default 方法,即方法可以被实现。
使用场景:
如果要创建不带任何方法定义和成员变量的基类,那么就应该选择接口而不是抽象类。
如果知道某个类应该是基类,那么第一个选择的应该是让它成为一个接口,只有在必须要有 方法定义和成员变量的时候,才应该选择抽象类。
因为抽象类中允许存在一个或多个被具体 实现的方法,只要方法没有被全部实现该类就仍是抽象类
4.3 对象三大特性
面向对象的三个特性:封装;继承;多态
封装:将数据与操作数据的方法绑定起来,隐藏实现细节,对外提供接口。
继承:代码重用;可扩展性
多态:允许不同子类对象对同一消息做出不同响应 多态的三个必要条件:继承、方法的重写、父类引用指向子类对象
封装 封装是指将某事物的属性和行为包装到对象中,这个对象只保留一些对外接口使之与外部发生联系。用户无需知道对象内部的细节,但可以通过对象对外提供的接口来访问该对象。
继承 子类继承父类的特征和行为,子类可以有父类的方法和属性,子类也可以对父类进行扩展,也可以提供重写的方法;继承的方式有两种:实现继承和接口继承
多态 多态就是指多种状态,就是说当一个操作在不同的对象时,会产生不同的结果。 多态分为编译时多态和运行时多态,编译时多态主要指方法的重载,运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定,运行时多态主要通过重写来实现。 多态的作用:消除类型之间的耦合关系。
那么JAVA的多态是怎么实现的? 接口实现、抽象类、继承父类进行方法重写、同一个类中进行方法重载。
4.4 JAVA中重载与重写的概念?
(Overload)重载:发生在同一个类之中,方法名相同、参数列表不同,与返回值无关、与final无关、与修饰符无关、与异常无关。 (Override)重写:发生在子类和父类之间,方法名相同、参数列表相同、返回值相同、不能是final的方法、重写的方法不能有比父类方法更为严格的修饰符权限、重写的方法所抛出的异常不能比父类的更大。
五、引用
强引用
StringReference GC 时不回收 当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会 靠随意回收具有强引用的对象来解决内存不足问题。
软引用
SoftReference GC 时如果 JVM 内存不足时会回收 软引用可用来实现内存敏感的高速缓存。 软引用可以和一个引用队列(ReferenceQueue) 联合使用,如果软引用所引用的对象被垃圾回收,Java 虚拟机就会把这个软引用加入到与 之关联的引用队列中。
弱引用
WeakReference GC 时立即回收 弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃 圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。
虚引用
PhantomReference 如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚 引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时, 如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用 队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将 要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对 象的内存被回收之前采取必要的行动。
六、ThreadLocal
在线程之间共享变量是存在风险的,有时可能要避免共享变量,使用 ThreadLocal 辅助类为 各个线程提供各自的实例。
每个线程内部都会维护一个类似 HashMap 的对象,称为 ThreadLocalMap,里边会包含 若干了 Entry(K-V 键值对),相应的线程被称为这些 Entry 的属主线程; Entry 的 Key 是一个 ThreadLocal 实例,Value 是一个线程特有对象。Entry 的作用即是: 为其属主线程建立起一个 ThreadLocal 实例与一个线程特有对象之间的对应关系; Entry 对 Key 的引用是弱引用;Entry 对 Value 的引用是强引用。
ThreadLocalMap 的 Key 是弱引用,如果是强引用,ThreadLocal 将无法被释放内存。 因为如果这里使用普通的 key-value 形式来定义存储结构,实质上就会造成节点的生命周期 与线程强绑定,只要线程没有销毁,那么节点在 GC 分析中一直处于可达状态,没办法被回 收,而程序本身也无法判断是否可以清理节点。弱引用是 Java 中四档引用的第三档,比软 引用更加弱一些,如果一个对象没有强引用链可达,那么一般活不过下一次 GC。当某个 ThreadLocal 已经没有强引用可达,则随着它被垃圾回收,在 ThreadLocalMap 里对应的 Entry 的键值会失效,这为 ThreadLocalMap 本身的垃圾清理提供了便利
七、异常
try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
会执行
1、不管有没有异常,finally中的代码都会执行 2、当try、catch中有return时,finally中的代码依然会继续执行 3、finally是在return后面的表达式运算之后执行的,此时并没有return运算之后的值,而是把值保存起来,不管finally对该值做任何的改变,返回的值都不会改变,依然返回保存起来的值。也就是说方法的返回值是在finally运算之前就确定了的。 4、如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。 5、finally代码中最好不要包含return,程序会提前退出,也就是说返回的值不是try或catch中的值
八、反射
概念
Java 中的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法; 并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方 法的功能称为Java 语言的反射机制
Java反射机制的作用?
应用场合:在Java程序中许多对象在运行是都会出现两种类型:编译时类型和运行时类型。 编译时的类型由 声明对象时用的类型来决定,运行时的类型由实际赋值给对象的类型决定 。如Person p=new Student(); 其中编译时类型为Person,运行时类型为Student
程序在运行时还可能接收到外部传入的对象,该对象的编译时类型为 Object,但是程序有需要调用 该对象的运行时类型的方法。为了解决这些问题,程序需要在运行时发现对象和类的真实信息。 此时就必须使用到反射了 。
总结
- 在运行时能够判断任意一个对象所属的类、创建新类对象
- 在运行时构造任意一个类的对象、判断任意一个类所具有的成员变量和方法
- 在运行时调用任一对象的方法
应用场景
JDBC中,利用反射动态加载了数据库驱动程序。
很多框架都用到反射机制,注入属性,调用方法,如Spring。
Web服务器中利用反射调用了Sevlet的服务方法。
读取配置文件
如何使用Java的反射?
获得Class对象的三种方法
创建对象的两种方法
反射机制的优缺点?
优点:可以动态执行,在运行期间根据业务功能动态执行方法、访问属性,最大限度发挥了java的灵活性。 缺点:对性能有影响,这类操作总是慢于直接执行java代码。