JavaGuide学习总结
- java基础(上)
- JVM&JDK&JRE
- 字节码及其好处
- 移位运算符
- 基本类型和包装类型的区别
- 包装类型的缓存机制
- 浮点数精度问题
- 变量
- 静态方法为什么不能调用非静态成员
- 重载和重写有什么区别?
java基础(上)
JVM&JDK&JRE
- JVM
- JVM在不同OS,使用相同字节码会给出相同的结果,因此 “一次编译,随处可以运行”
- JVM虚拟机只能识别字节码文件,任何语言只需要按照JVM规范中关于字节码文件的标准生成字节码文件,就可以在JVM中运行,因此不仅仅只有java可以在JVM中运行,其他如Groovy、kotlin也可以在JVM编译器中运行。
- HotSpot VM,它是Sun JDK和OpenJDK中所带的虚拟机,也是目前使用范围最广的Java虚拟机,除此以外还有 J9 VM、Zing VM、JRockit VM 等 JVM
- JDK 和 JRE
- JDK(Java Development Kit),它是功能齐全的 Java SDK,能够创建和编译 Java 程序。他包含了 JRE,同时还包含了编译 java 源码的编译器 javac 以及一些其他工具比如 javadoc(文档注释工具)、jdb(调试器)、jconsole(基于 JMX 的可视化监控⼯具)、javap(反编译工具)等等。
- JRE(Java Runtime Environment) 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,主要包括 Java 虚拟机(JVM)、Java 基础类库(Class Library)。
字节码及其好处
- 什么事字节码?
JVM 可以理解的代码就叫做字节码(即扩展名为 .class 的文件),它不面向任何特定的处理器,只面向虚拟机。 - Java 程序从源代码到运行的过程如下图所示:
- 在字节码到机器码这一步,JVM解释器首先需要加载字节码文件,然后通过解释器逐行解释执行,这种方式执行速度较慢
- 有些代码是需要经常被调用的(热点代码),因此引进了运行时编译的JIT编译器。 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用
- 上面第一个过程需要一直解释执行,第二个过程只需将第一个过程编译解释后的的热点机器码保存,因此java是一个编译和解释共存的语言
移位运算符
基本类型和包装类型的区别
- 用途:方法参数、对象属性中很少会使用基本类型来定义变量。并且包装类型可用于泛型,而基本类型不可以
- 存储方式:本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被 static 修饰 )存放在 Java 虚拟机的堆中。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中。
- 占用空间:相比于包装类型(对象类型), 基本数据类型占用的空间往往非常小。
- 默认值:成员变量包装类型不赋值就是 null ,而基本类型有默认值且不是 null。
- 比较方式:对于基本数据类型来说,==比较的是值。对于包装数据类型来说,== 比较的是对象的内存地址。所有整型包装类对象之间值的比较,全部使用 equals() 方法。
注意:基本数据类型存放在栈中是一个常见的误区! 基本数据类型的成员变量如果没有被 static 修饰的话就存放在堆中。
包装类型的缓存机制
浮点数精度问题
- 代码展示
float a = 2.0f - 1.9f;
float b = 1.8f - 1.7f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999905
System.out.println(a == b);// false
- 问题原因:
- 计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况
- 就比如说十进制下的 0.2 就没办法精确转换成二进制小数:
// 0.2 转换为二进制数的过程为,不断乘以 2,直到不存在小数为止,
// 在这个计算过程中,得到的整数部分从上到下排列就是二进制的结果。
0.2 * 2 = 0.4 -> 0
0.4 * 2 = 0.8 -> 0
0.8 * 2 = 1.6 -> 1
0.6 * 2 = 1.2 -> 1
0.2 * 2 = 0.4 -> 0(发生循环)
...
- 如何解决问题:BigDecimal 可以实现对浮点数的运算,不会造成精度丢失
变量
- 成员变量与局部变量的区别?
- 语法形式:
- 成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数。
- 成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰。
- 成员变量和局部变量都能被 final 所修饰。
- 存储方式:
- 员变量是使用 static 修饰的,那么这个成员变量是属于类的,如果没有使用 static 修饰,这个成员变量是属于实例的。
- 对象存在于堆内存,局部变量则存在于栈内存。
- 生存时间
- 成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡。
- 默认值:
- 从变量是否有默认值来看,成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。
- 字符型常量和字符串常量的区别?
- 含义 : 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)。
- 占内存大小:字符常量只占 2 个字节; 字符串常量占若干个字节。
静态方法为什么不能调用非静态成员
- 静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态成员属于实例对象,只有在对象实例化之后才存在,需要通过类的实例对象去访问。
- 在类的非静态成员不存在的时候静态方法就已经存在了,此时调用在内存中还不存在的非静态成员,属于非法操作。
重载和重写有什么区别?
重载就是同样的一个方法能够根据输入数据的不同,做出不同的处理
重写就是当子类继承自父类的相同方法,输入数据一样,但要做出有别于父类的响应时,你就要覆盖父类方法
- 重载
- 发生在同一个类中(或者父类和子类之间),方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。
- 重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理
- 重写
- 重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。
- 方法名、参数列表必须相同,子类方法返回值类型应比父类方法返回值类型更小或相等,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。
- 如果父类方法访问修饰符为 private/final/static 则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。
class Animal {
public static void walk() { System.out.println("Animal行走方法"); }
}
public class Horse extends Animal {
public static void walk() {
System.out.println("Horse四条腿行走");
}
public static void main(String [] args) {
new Horse().walk();
}
}