JDK包含JRE,JER包含JVM
Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以,Java程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。
OpenJDK是一个参考模型并且是完全开源的,而Oracle JDK 是 Open JDK 的一个实现,并不是完全开源的。
在稳定性方面,Oracle JDK 比 Open JDK更稳定。
在响应性和JVM性能方面,Oracle JDK 与 Open JDK 相比提供了更好的性能。
Java 语言的数据类型分为两种:基本数据类型和引用数据类型。
Java 中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持4种不同的访问权限。
final用于修饰变量、方法和类。
引用不可变
和对象不可变
,final是引用不可变,final修饰的变量必须初始化,通常修饰常量。finally作为异常处理的一部分,出现在try/catch
语句中,并且附带一个语句块表示这段语句最终一定被执行(无论是否出现异常),经常被用在需要释放资源的情况下,System.exit(0)
可以阻断finally执行。
finalize是在java.lang.Object
里定义的方法,finalize被调用不一定会立即回收该对象,不推荐使用finalize方法。
static关键字表明一个成员变量或者是成员方法可以在没有所属类的实例变量的情况下被访问。
Java的static方法不能被覆盖,static变量在Java中是属于类的,它在所有的实例中的值都是一样的。
代码块执行顺序:
静态代码块——> 构造代码块 ——> 构造函数——> 普通代码块
继承中代码块执行顺序:
父类静态块——> 子类静态块——> 父类代码块——> 父类构造器——> 子类代码块——> 子类构造器
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态,而后者实现的是运行时的多态性。
重写发生在子类与父类之间,重写方法返回值和形参都不能改变,与方法返回值和访问修饰符无关
重载是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重载的方法(或者构造函数)都必须有个独一无二的参数类型列表,最常用的地方就是构造器的重载。
语法层面上的区别:
设计层面上的区别:
值传递:在方法调用时,传递的参数是按值的拷贝传递,传递的是值的拷贝
引用传递:在方法调用时,传递的参数是按引用进行传递,其实传递的是引用的地址,也就是变量所对应的内存空间地址
==
常用于相同的基本数据类型之间的比较,也可以用于相同类型的对象之间的比较
如果==
比较的是基本数据类型,那么比较的是两个基本数据类型的值是否相等
如果==
比较的是两个对象,那么比较的是两个对象的引用,也就是判断两个对象是否指向了同一块内存区域
equals方法主要用于两个对象之间,检测一个对象是否等于另一个对象
Object类的equals方法:
public boolean equals(Object obj) {
return (this == obj);
}
hashCode() 的作用是获取哈希码,返回一个int整数,哈希码的作用是确定该对象在哈希表中的索引位置。
判断的时候先根据hashcode进行判断,相同的情况下再根据equals()方法进行判断。如果只是重写equals方法,而不重写hashcode方法,会造成hashcode的值不同,而equals()方法判断出来的结果为true。
在Java中的一些容器中,不允许有两个完全相同的对象,插入的时候,如果判断相同则会进行覆盖。这时候如果只是重写了equals()方法,而不重写hashcode方法,Object中hashcode是根据对象的存储地址转换而形成的一个哈希值。这时候就有可能因为没有重写hashcode方法,造成相同的对象散列到不同的位置而造成对象的不能覆盖的问题。
总体来说,String不可变的原因包括 设计考虑,效率优化,以及安全性这三大方面。
可变与不可变
String类中使用字符数组保存字符串,因为有final修饰符,所以String对象是不可变。对于已经存在的String对象的修改都是重新创建一个新的对象,然后再把新的值保存进去。
private final char value[];
StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,这两种对象都是可变的。
char[] value;
是否线程安全
String对象是不可变的,显然是线程安全的
StringBuilder是非线程安全的
StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
性能
每次对String类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String对象。
StringBuffer每次对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象的引用。
相同情况下,使用StringBuilder相比使用StringBuffer仅能获得 10% ~ 15%
左右的性能提升,但是却要冒着线程不安全的风险。
字符常量是单引号引起的一个字符,字符串常量是双引号引起的若干个字符
字符常量相当于一个整型值(ASCII),可以参加表达式运算,字符串常量代表一个地址值(该字符串在内存中存放位置,相当于对象)
字符常量占2个字节(char在Java中占两个字节),字符串常量占若干个字节(至少一个字符结束标志)
HashMap内部实现是通过key的hashcode来确定value的存储位置,因为字符串是不可变的,所以当创建字符串时,它的hashcode被缓存下来,不需要再次计算,所以相比于其他对象更快。
Java 为每一个基本数据类型都引入了对应的包装类型,int的包装类是Integer,从Java5开始引入了自动装箱/拆箱机制,把基本类型转换成包装类型的过程叫做装箱(boxing),反之,把包装类型转换成基本类型的过程叫做拆箱(unboxing),使得二者可以相互转换。
Java为每个基本类型提供了包装类型
基本类型 | 包装类型 |
---|---|
boolean | Boolean |
char | Character |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
包装类型可以为null,而基本类型不可以
包装类型可以用于泛型,而基本类型不可以
基本类型比包装类型更高效
反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
优点:
能够运行时动态获取类的实例,提高灵活性;可与动态编译结合
加载MySQL的驱动类
Class.forName("com.mysql.jdbc.Driver")
缺点:
使用反射性能较低,需要解析字节码,将内存中的对象进行解析。其解决方案是:通过setAccessible(true)
关闭JDK的安全检查来提升反射速度;多次创建一个类的实例时,有缓存会快很多
1、Class.forName(“类的路径”);当你知道该类的全路径名时,你可以使用该方法获取Class类对象。
Class clz = Class.forName("java.lang.String");
2、类名.class。这种方法只适合在编译前就知道操作的类
Class clz = String.class;
3、对象名.getClass()
String str = new String("test");
Class clz = str.getClass();
4、如果是基本类型的包装类,可以调用包装类的Type属性来获得该包装类的Class对象。
反射API用来生成JVM中的类、接口和对象信息
泛型是JDK1.5的一个新特性,泛型就是将类型参数化,其在编译时才确定具体的参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
使用泛型时加上的类型参数,编译器在编译的时候去掉类型参数。
大部分情况下,泛型类型都会以Object进行替换,而有一种情况则不是,那就是使用到了extends和super语法的有界类型。
编译器会正常的将使用泛型的地方编译并进行类型擦除,然后返回实例。但是除此之外,如果构建泛型实例时使用了泛型语法,那么编译器将该标记该实例并关注该实例后续所有方法的调用,每次调用前都进行安全检查,非指定类型的方法都不能调用成功。
编译器不仅关注一个泛型方法的调用,它还会为某些返回值为限定的泛型类型的方法进行强制类型转换,由于类型擦除,返回值为泛型类型的方法都会擦除成Object类型,当这些方法被调用后,编译器会额外插入一行 checkcast
指令用于强制类型转换。
Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程
对内存中的对象进行持久化或网络传输,这个时候都需要序列化或反序列化
实现Serializable
接口或者Externalizable
接口
Serializable接口
类通过实现java.io.Serializable
接口以启用其序列化功能。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。
Externalizable接口
Externalizable继承自Serializable,该接口中定义了两个抽象方法:writeExternal()
与readExternal()
当使用Externalizable接口进行序列化与反序列化的时候需要开发人员重写writeExternal()
与readExternal()
方法。否则,所有变量的值都会变成默认值。
实现Serializable接口 | 实现Externalizable接口 |
---|---|
系统自动存储必要的信息 | 程序员决定存储哪些信息 |
Java内建支持,易于实现,只需要实现该接口即可,无需任何代码支持 | 必须实现接口内的两个方法 |
性能略差 | 性能略好 |
serialVersionUID 用来表明类的不同版本间的兼容性
Java的序列化机制是通过在运行时判断类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。
不会,因为序列化是针对对象而言的,而静态变量优先于对象存在,随着类的加载而加载,所以不会被序列化。
Java中,所有的异常都有一个共同的祖先Throwable
类。Throwable类有两个重要的子类Exception
(异常)和Error
(错误)
Exception:程序本身可以处理的异常,可以通过catch来进行捕获,通常遇到这种错误,应该对其进行处理,使应用程序可以继续正常运行。Exception又可以分为运行时异常和非运行时异常。
Error:程序无法处理的错误,我们不能通过catch来进行捕获。例如,系统崩溃、内存不足、堆栈溢出等,编译器不会对这类错误进行检测,一旦这类错误发生,通常应用程序会被终止,仅靠应用程序本身无法恢复。
是否强制要求调用者必须处理此异常,如果强制要求调用者必须进行处理,那么就是受检查异常,否则就是非受检查异常。
在一个方法中,如果发生异常,这个方法会创建一个异常对象,并交给JVM,该异常对象包含异常名称,异常描述以及异常发生时应用程序的状态。创建异常对象并转交给JVM的过程称为抛出异常。可能有一系列的方法调用,最终才进入抛出异常的方法,这一系列方法调用的有序列表叫做调用栈。
JVM会顺着调用栈查找是否有可以处理异常的代码,如果有,则调用异常处理代码。当JVM发现可以处理异常的代码时,会把发生的异常传递给它。如果JVM没有找到可以处理该异常的代码块,JVM就会将该异常转交给默认的异常处理器(默认处理器为JVM的一部分),默认异常处理器打印出异常信息并终止应用程序。
字节输入流转字符输入流通过InputStreamReader
实现,该类的构造函数可以传入InputStream
对象
字节输出流转字符输出流通过OutputStreamWriter
实现,该类的构造函数可以传入OutputStream
对象