在回顾Java基础知识的时候做的笔记,有什么用词不准确或者理解错误的地方欢迎大家在评论区指正。参考:JavaLearn
Java是一种先编译、后解释执行的语言。所有的.java文件需要通过javac命令先编译为.class文件,也就是字节码。然后,由JVM从.class文件逐行读取并执行。
Java通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时保留了解释性语言可移植的特点。此外,因为字节码对于不同平台的JVM是通用的,因此Java程序无需重新编译就可以在不同的计算机上运行,也就是“一次编译,到处运行”。
| 基本类型 | 字节 | 默认 | ----- | 基本类型 | 字节 | 默认 |
|---|---|---|---|---|---|---|
| byte | 1 | 0 | ----- | char | 2 | ‘u0000’ |
| short | 2 | 0 | ----- | float | 4 | 0f |
| int | 4 | 0 | ----- | double | 8 | 0d |
| long | 8 | 0L | ----- | boolean | 1bit | flase |
首先要明白switch底层的机制,switch底层使用整形int进行判断。
hashcode()转换为int,但是可能冲突,此时就需要equals()总结:switch支持所有能隐式转换为int的类型以及其包装类型,enum根据序号转换为int,String通过hashcode来转换为int。
引用不可变的变量,只能被赋值一次。但是如果变量是对象,其属性值可以变化。final修饰的全局变量必须立即初始化,而修饰的局部变量可以不立即初始化。
String以及所有的包装类的对象都是不可变对象,任何的修改都会创建一个新的对象。不可变对象的最大好处是线程安全。
try/catch语句中,表示最终一定被执行。System.exit(0)可以阻断finally的执行。通常用new创建类对象时,才会分配数据存储空间,类内方法才能被调用。但static修饰的成员变量和方法可以在所属类没有实例的情况下被访问。
static方法是不能被覆盖的,因为方法的覆盖是基于运行时动态绑定。而static方法是编译时静态绑定的,它跟类的任何实例都不相关,概念上不适用。
动态绑定:在程序运行过程中,根据具体的实例对象来确定具体用哪一个方法。
在继承中代码的执行顺序:

多态分为编译时多态和运行时多态,又称静态多态和动态多态。重载overload就是编译时多态,也就是说编译的时候就已经确定了运行的时候要调用的方法。
在Java中,我们说多态,通常都是指运行时多态,也就是编译时不确定要调用的是哪一个方法,一直延迟到运行时才能够确定。这也是多态方法又被称为延迟方法的原因。
Java实现多态需要有三个必要条件:继承、重写、向上转型。
overload和override都是实现多态的方式,区别如下。
构造器不能被继承,因此不能被重写,但是可以被重载,比如无参构造和全参构造。每个类必须有输入自己的构造函数,子类不会覆盖掉父类的构造函数,相反的,必须一开始调用父类的构造函数。
从语法层面来说:
从设计层面来说:
在Java中只有值传递。首先,基本类型传递的时候肯定是值传递,关键是引用类型。在Java中,引用类型作为参数也是值传递,只不过这个值是引用的地址。也就是说,会现将地址复制一份,然后用作参数。这个跟引用传递还是比较容易混淆的。
==通常用于比较基本数据类型,两者值相同返回true;用于比较两个对象,对象地址相同返回ture。Object类中的equals,也就是直接调用==。
hashCode()可以获取一个对象的哈希码,也叫散列码,返回的是一个整数。哈希码的作用是确定该对象在哈希表中的索引位置。hashCode()是Object类中的方法,因此所有的对象都可以使用这个方法。
哈希表存储的是键值对,能根据键快速检索到对应的值,应用到了哈希码。
hashcode很重要,以HashSet为例。当要将对象存入HashSet时,HashSet会先计算对象的hashcode来判断对象加入的位置,如果HashSet不存在此hashcode,HashSet会假设对象没有重复出现,直接存入。如果此hashCode已经存在,会调用equals()来判断两个对象是否真的相同。如果相同,就不会存入此对象。如果不同,重新散列此对象。
如果单单使用equals()来判断对象是否相等,如果集合内存在1000个元素,就需要将1000个元素一个一个与目标元素相比较,效率较低。
如果单单采用hashcode(),可以很快速的缩小需要比较的范围。但是不同的对象也可能有相同的hashcode,因此hashcode并不能保证准确性。
所以我们需要使用hashcode()和equals()的组合,既保证了速度,又保证了准确性。先用hashcode()缩小需要比较的范围,然后用equals()比较两个对象是否相等。
可以看一下源码中Object类里面hashcode()的注释部分,里面有一句话:
Whenever it is invoked on the same object more than once during an execution of a Java application, the {@code hashCode} method must consistently return the same integer, provided no information used in {@code equals} comparisons on the object is modified.
也就是说,对于一个对象,如果这个对象内的所有被equals()方法用到的信息没有被修改,那么hashcode()就应该返回同一个int值。
而equals()和hashcode()本身就是配套使用的,在判断的时候会根据hashcode()的结果来决定是否需要调用equals()。Object类本身内部的equals()是直接调用==,如果只重写equals()而不重写hashcode(),会造成hashcode值不同,而equals()判断出来的结果为true。
比如Java的一些容器中,不允许有两个完全相同的对象。如果只重写了equals(),那么这两个对象的hashcode()就直接采用==,也就是根据对象的存储地址转换形成一个哈希值,就会导致存入两个完全相容的对象。
synchronized修饰方法来实现线程安全。StringBuilder非线程安全。String设计为不可变是出于多方面的考量。
String str1 = "a";
String str2 = "a";
System.out.println(str1 == str2); //true
intern()在jdk1.6和1.7及以后有着不同的处理。注意s.intern()会返回一个地址,但是对于s是没有任何影响的。
s.intern()会先判断字符串常量池中是否存在字符串s
s.intern()先判断字符串常量池中是否存在字符串s
反射的主要作用:
个人觉得反射最大的意义就是我们在只知道类全路径名的情况下访问其所有成员,包括私有成员。
优点:
缺点:
Java的序列化就是将Java对象转化为字节序列的过程,反序列化就是将字节序列恢复为Java对象的过程。拿游戏来类比,序列化其实很像游戏的存档,将一个个角色对应的对象存储到硬盘。同样的,反序列化也就对应从存档中读取游戏数据的过程。
将对象序列化无非两个目的,一个是用于网络传输,一个是对象的持久化保存。
那么问题又来了,为啥对象会需要网络传输或者是持久化的保存呢?可以结合下面的应用场景理解一下:
Java可以通过实现Serializable或Externalizable接口来实现序列化。可序列化接口的所有子类型都是可以序列化的,因为父类引用可以指向子类实现,比如People people = new Woman();。
Serializable:此接口内部没有方法或者字段,仅仅用于表明当前类可进行序列化。Externalizable:此接口继承自Serializable,内部定义了writeExternal()和readExternal()两个方法。使用当前接口进行序列化与反序列化需要重写这两个方法,否则所有的属性都会变成默认值。两者相比之下,Serializable易于实现,仅需实现接口,系统会自动存储必要信息,但是性能较差;Externalizable性能较好,但是需要程序员来重写接口内的两个方法,决定存储哪些信息。
Java的序列化机制通过serialVersionUID来验证版本一致性。
在不显式指定serialVersionUID的情况下,JVM在序列化时会根据属性自动生成。显式指定后,序列化和反序列化用的就是我们指定的serialVersionUID。
在开发中我们的类不可能一成不变,是需要进行迭代的。一旦类被修改,旧对象的反序列化就会出错。因此我们在开发过程中最好显式指定serialVersionUID,值是多少无所谓,重要的是不能变。那么什么时候serialVersionUID可以修改呢?
《阿里巴巴开发手册》:如果序列化类新增属性,不要修改serialVersionUID字段;如果是不兼容升级,为了避免反序列化混乱,那么请修改serialVersionUID值。
对于类中不想序列化的变量,可以用transient进行修饰。加上transient关键字可以避免变量被序列化到文件中,反序列化后变量的值会被设置为默认值。transient只能修饰变量,不能修饰类和方法。
不会。
序列化是针对对象而言的,静态变量是先于对象存在的,随着类的加载而加载。serialVersionUID也是用static修饰,所以序列化时也不会序列化,而是JVM在序列化时生成一个serialVersionUID,然后用我们显式设置的值来赋值。
Exception和Error都继承自Throwable类。这两者都是Java异常处理的重要子类,各自都包含大量子类。
Exception:异常,程序本身可以进行处理。遇到异常,需要通过catch进行捕获,或者用throw、throws做抛出处理,以便程序能够正常运行。Exception又可以分为运行时异常(非受检查异常)和非运行时异常(受检查异常)。运行时异常是RuntimeException及其子类,表示JVM运行期间可能出现的异常,比如NullPointException(空指针异常),IndexOutOfBoundsException(数组越界),ClassCastException(类转换异常),NumberFormatException(字符转换为数字)。受检查异常能够被Java编译器检查出来,常见的有ClassNotFoundException,SQLException,以及IO相关的一些异常。Error:错误,仅靠程序自身无法处理。比如,系统崩溃、内存不足、堆栈溢出等等。NoClassDefFoundError是一个Error,是由JVM引起的,因为JVM或ClassLoader在尝试加载某类时在内存中找不到该类的定义,可能是类在编译后被删除了。
ClassNotFoundException是一个受检查异常,需要我们使用try/catch进行显式的捕获和处理,或者使用throws将异常抛出。当使用Class.forName()动态加载类到内存中时,通过传入的类路径没有找到对应类,就会抛出此异常;另一种出现此异常的情况是某类已经被一个加载器加载到内存中后,另外一个加载器又尝试加载该类。
当读写文件时需要对内容进行处理就选用字符流;仅读写文件,与内容无关,选用字节流。
BIO:Blocking IO,同步并阻塞,在服务器中实现模式为一个连接一个线程。每当用户发起连接请求,服务器就需要启动一个线程进行处理。如果连接闲置,就会造成不必要的开销。当然,可以通过线程池机制来进行改善。BIO通常适用于连接数目少且固定的架构,对于服务器资源要求较高,且并发局限于应用中。不过程序直观简单,容易理解。
NIO:Non-blocking IO,同步并非阻塞,在服务器中的实现模式为一个请求一个线程。客户端的所有连接都会注册到多路复用器上,当多路复用器轮训到连接有IO请求时才会启动一个线程进行处理。NIO通常适用于连接比较多且连接比较短的架构,并发局限于应用内部,编程比较复杂。
AIO:Asynchronous IO,异步并非阻塞,在服务器中的实现模式为一个有效请求一个线程。客户端所有的IO请求都会先经过操作系统处理后,再通知服务器启动线程进行处理。AIO通常适用于连接较多且连接比较长的架构,充分调动操作系统参与并发,编程比较复杂。
Reader reader = new InputStreamReader(inputStream);

new BufferedInputStream(new FileInputStream(inputStream));