目录
8.2.1. 为什么浮点数值不适用于无法接受舍入误差的金融计算中?
12.2.6. 重写(Override)与重载(Overload)
目录
8.2.1. 为什么浮点数值不适用于无法接受舍入误差的金融计算中?
12.2.6. 重写(Override)与重载(Overload)
13. String&StringBuffer&StringBuilder(提高)
13.1. StringBuffer 和 StringBuilder 的区别
????????Java的历史可以追溯到1990年代初期,当时Sun Microsystems成立了一个名为“Green Team”的团队,旨在开发一种跨平台的编程语言。这个团队由Java之父James Gosling领导,并且在他的领导下,Java的前身Oak(橡树)于1991年诞生。
????????1995年,Java语言正式发布,并且得到了广泛的关注和接受。Java语言的主要特点是平台无关性,即可以在任何支持Java的平台上运行,无需重新编译。这个特性使得Java成为了一种非常流行的编程语言,特别是在Web开发和企业应用领域。
java语言版本发展历程:
版本 | 发布日期 | 说明 |
jdk beta | 1995 | java语言诞生 |
java 1.0 | 1996 | 首个java版本,包含基本的核心api和applet支持 |
java 1.1 | 1997 | 添加了内部类,反射,jar文件,事件模型等功能。 |
java 1.2 | 1998 | 引入了swing用户界面工具包,集合框架,javabeans等,增强了平台独立性。 |
java 1.3 | 2000 | 增加了javasound api,jndi,jpda调试工具,并改进了性能和稳定性。 |
java 1.4 | 2002 | 引入了nio,正则表达式,xml解析器等新特性,提高了性能和安全性。 |
java 5 | 2004 | 引入了泛型,注解,自动装箱/拆箱,枚举类型等新特性。 |
java 6 | 2006 | 引入了jdbc4.0,jax-ws,java compiler api等新特性,并优化了垃圾回收机制。 |
java 7 | 2011 | 引入了trywith-resources语句,二进制字面量,钻石操作符等新特性,并提升了性能和安全性。 |
java 8 | 2014 | 引入了lambda表达式,streamsapi,函数式接口,默认方法等新特性,并改进了集合框架和日期/时间api |
java 9 | 2017 | 引入了模块化系统,jshell工具,集合工厂方法等新特性,并优化了垃圾回收机制和性能 |
java 10 | 2018 | 引入了局部变量类型推断,线程本地握手,堆内存分配等新特性 |
java 11 | 2018 | 主要增强了http客户端,启动时间,容器支持,嵌套访问控制等方面的功能,并删除了一些过时的api |
java 12 | 2019 | 引入了Switch表达式,Jvm常量api等新特性 |
java 13 | 2019 | 引入了文本块,动态CDS、ZGC等新特性 |
java 14 | 2020 | 主要更新包括垃圾回收器的改进、模式匹配语法的使用、依赖封闭/接口封闭以及record紧凑语法。 |
java 15 | 2021 | 主要更新是关于垃圾回收器的改进,CMS垃圾回收器被剔除,ParallelScavenge + SerialOld GC 的组合模式被弃用,ZGC垃圾回收器转正,G1回收器得到优化后依然是默认的垃圾回收器 |
java 16 | 2021 | 主要更新是关于垃圾回收器的改进,ZGC可以将未使用的堆内存返回给操作系统,Records 的扩展,包括 sealed 和 non-sealed 标注;新增了 Vector API,用于并行计算。 服务提供者接口(SPAPI),取代了旧的类路径扩展机制; |
java 17 | 2023 | 基于OpenJDK 11 的长期支持(LTS)版本;主要更新是关于垃圾回收器的改进,ZGC再次得到增强,可以更好地处理高吞吐量的场景。 |
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello Java");
}
}
????????javac命令是将java源文件编译成字节码文件,输入完javac HelloWord.java后便会在该文件的相同目录下生成HelloWord.class文件,然后使用java命令运行该字节码文件得到输出信息。需要注意的是该java源文件的命名需要和文件里的类名相同否则会找不到主类而报错。
????????Java 所有的组成部分都需要名字。类名、变量名以及方法名都被称为标识符。关于 Java 标识符,有以下几点需要注意:
访问控制修饰符:public,default,private,protected
非访问控制修饰符:final,abstract,static,synchronized
????????在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。
????????成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。 通常,实例变量应该被设为私有,以防止外部代码随意修改其值。
????????常量也是与类相关的,但它是用 final 关键字修饰的变量,一旦被赋值就不能再修改。与静态变量不同的是,常量在编译时就已经确定了它的值,而静态变量的值可以在运行时改变。
????????另外,常量通常用于存储一些固定的值,如数学常数、配置信息等,而静态变量通常用于存储可变的数据,如计数器、全局状态等。静态变量是与类相关的变量,具有唯一性和共享性,可以用于存储整个程序都需要使用的数据,但需要注意初始化时机和与常量的区别。
????????Java 中的静态变量是属于类的,而不是对象的实例。因此,当多个线程同时访问一个包含静态变量的类时,需要考虑其线程安全性。静态变量在内存中只有一份拷贝,被所有实例共享。因此,如果一个线程修改了静态变量的值,那么其他线程在访问该静态变量时也会看到修改后的值。这可能会导致并发访问的问题,因为多个线程可能同时修改静态变量,导致不确定的结果或数据一致性问题。为了确保静态变量的线程安全性,需要采取适当的同步措施,如同步机制、原子类或 volatile 关键字,以便在多线程环境中正确地读取和修改静态变量的值。
基本数据类型分为四大类,八小种
整数型 | 占用字节 | 表示范围 |
byte | 1 | -128~127 |
short | 2 | -32768~32767 |
int | 4 | -2^31~(2^31-1) |
long | 8 | -2^63~(2^63-1) |
浮点型 | 占用字节 | 表示范围 |
float | 4 | 大约 ± 3.402 823 47E+38F (有效位数为 6 ~ 7 位) |
double | 8 | 大约 ± 1.797 693 134 862 315 70E+308 (有效位数为 15 位) |
????????在Java开发中,计算金额时通常使用`BigDecimal`数据类型。`BigDecimal`类提供了高精度的浮点数计算,适合用于精确计算货币金额,避免了使用基本数据类型(如`float`和`double`)可能产生的精度问题。
了解char之前必须要弄清楚Unicode编码机制
ascii字符集:只有英文,数字,符号等,占1个字节。
gbk字符集:汉字占2个字节,英文,数字占1个字节。
utf-8字符集:汉字占3个字节,英文,数字占1个字节。
????????编码和解码时需要使用同一种编码方式,英文数字一般不会出现乱码。英文很多字符集都兼容ASCII字符集
????????美国ASCII(美国信息交换标准代码)字符集,使用一个字节来存储,这个字节首位都是0,表示128个字符,对美国人够用了。
????????世界上编码过多,容易出现问题,此时Unicode(万国码)应运而生,一统天下。Unicode是国际组织制定的,可以容纳世界上所有的文字,符号和字符集。UTF-8是一直重要的编码方案
汉字占三个字节,英文数字占1个字节
????????boolean (布尔)类型有两个值:false 和 true, 用来判定逻辑条件 整型值和布尔值之间 不能进行相互转换
????????在Java中,引用类型的变量非常类似于C/C++的指针。引用类型指向一个对象,指向对象的变量是引用变量。这些变量在声明时被指定为一个特定的类型,比如 Employee、Puppy 等。变量一旦声明后,类型就不能被改变了。
数据类型转换满足以下要求:
int i =128; byte b = (byte)i;
(int)23.7 == 23; (int)-45.89f == -45
如果你尝试将一个字节小的数据类型赋值给一个字节更大的数据类型,那么会自动进行数据类型的提升。
byte
-> short
-> int
-> long
byte
-> short
short
-> int
int
-> long
例如,你可以这样做而不会有错误:
byte b = 10;
int i = b;
如果你尝试将一个字节大的数据类型赋值给一个字节小的数据类型,那么你需要进行强制类型转换,否则可能会丢失数据。
long
-> int
int
-> short
short
-> byte
例如:
int i = 130;
byte b = (byte) i;
????????我们来分析一下为什么这样转换 int i = 130;
到 byte b = (byte) i;
结果为 -126
。首先,我们知道 byte
的范围是从 -128
到 127
。当超出这个范围时,它会进行溢出并从另一端重新开始计数。这是因为计算机中的数是基于补码进行存储的。对于正数,补码和原码是相同的。但对于负数,补码是其二进制原码的反码(取反)再加1。那么,int
值 130
的二进制表示形式(我们只看最后8位,因为 byte
只有8位)是 10000010
。当我们尝试将这个二进制数作为 byte
解释时,最高位(即最左边的位)是1,代表这是一个负数。为了知道这个数的真实值,我们需要计算其补码:
取反(取反码):01111101
加1(得到补码):01111110
这个二进制数 01111110
是十进制的 126
。但由于最高位代表这是一个负数,所以实际值是 -126
。
因此,当你执行 byte b = (byte) 130;
,b
的值会变为 -126
。
A = 0011 1100
B = 0000 1101
-----------------
A&B = 0000 1100 // 全1则1 与运算
A | B = 0011 1101 // 有1则1 或运算
A ^ B = 0011 0001 // 不同1相同0 异或运算
~A= 1100 0011 // 按位取反 取反运算
A<<2=1111 0000 // 按位左移运算符。左操作数按位左移右操作数指定的位数。
A>>2=0000 1111 // 按位右移运算符。左操作数按位右移右操作数指定的位数。
????????该运算符用于操作对象实例,检查该对象是否是一个特定类型(类类型或接口类型)。instanceof运算符使用格式如下:
( Object reference variable ) instanceof (class/interface type)
String name = "James";
boolean result = name instanceof String; // 由于 name 是 String 类型,所以返回真
Java 语言中提供的数组是用来存储固定大小的同类型元素。 声明语法 dataType[]arrayRefVar;
// 创建数组
dataType[] arrayRefVar = new dataType[arraySize];
dataType[] arrayRefVar = {value0, value1, ..., valuek};
// 数组的元素是通过索引访问的。数组索引从 0 开始,所以索引值从 0 到 arrayRefVar.length-1
// JDK 1.5 引进了一种新的循环类型,被称为 For-Each 循环或者加强型循环,
// 它能在不使用下标的情况下遍历数组。
// 语法格式如下:
for(type element: array)
{
System.out.println(element);
}
???? 优点:查询/查找/检索某个下标上的元素时效率极高。可以说是查询效率最高的一个数据结构。为什么检索效率高?
缺点:删除和增加元素时效率低
????????在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。 封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。要访问该类的代码和数据,必须通过严格的接口控制。封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。
????????接口是一种完全抽象的类,它定义了一组方法(没有实现),这些方法可以被任何类实现。一个类可以实现多个接口。类描述对象的属性和方法。接口则包含类要实现的方法。除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
????????接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。
接口可以多继承。
注:JDK 1.8 以后,接口里可以有静态方法和方法体了。
注:JDK 1.8 以后,接口允许包含具体实现的方法,该方法称为"默认方法",默认方法使用 default 关键字修饰。更多内容可参考 Java 8 默认方法。
注:JDK 1.9 以后,允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。更多内容可参考 Java 9 私有接口方法
????????抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类 Airplane,将鸟设计为一个类 Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行 设计为一个接口Fly,包含方法fly( ),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。从这里可以看出,继承是一个 "是不是"的关系,而 接口 实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。
????????设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。什么是模板式设计?最简单例子,大家都用过 ppt 里面的模板,如果用模板 A 设计了 ppt B 和 ppt C,ppt B 和 ppt C 公共的部分就是模板 A 了,如果它们的公共部分需要改动,则只需要改动模板 A 就可以了,不需要重新对 ppt B 和 ppt C 进行改动。而辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。
下面看一个网上流传最广泛的例子:门和警报的例子:门都有 open() 和 close() 两个动作,此时我们可以定义通过抽象类和接口来定义这个抽象概念:
abstract class Door {
public abstract void open();
public abstract void close();
}
但是现在如果我们需要门具有报警 的功能,那么该如何实现?下面提供两种思路:
????????从这里可以看出, Door 的 open() 、close() 和 alarm() 根本就属于两个不同范畴内的行为,open() 和 close() 属于门本身固有的行为特性,而 alarm() 属于延伸的附加行为。因此最好的解决办法是单独将报警设计为一个接口,包含 alarm() 行为,Door 设计为单独的一个抽象类,包含 open 和 close 两种行为。再设计一个报警门继承 Door 类和实现 Alarm 接口。
interface Alram {
void alarm();
}
abstract class Door {
void open();
void close();
}
class AlarmDoor extends Door implements Alarm {
void oepn() {
//....
}
void close() {
//....
}
void alarm() {
//....
}
}
????????重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
最常用的地方就是构造器的重载。
重载规则:
对象多态,行为多态,Animal cat = new Cat( );cat.move();编译看左边,运行看右边
????????java程序永远都分为编译阶段和运行阶段。先分析编译阶段,再分析运行阶段,编译无法通过,根本是无法运行的。编译阶段编译器检查cat个引用的数据类型为animal,由于animal.class字节码当中有move()方法,所以编译通过了。这个过程我们称为静态绑定 编译阶段绑定。只有静态绑定成功之后才有后续的运行。
????????在程序运行阶段,jvm内存当中真实创建的对象是cat对象,那么以下程序在运行阶段一定会调用cat对象的move()方法,此时发生了程序的动态绑定 运行阶段绑定。无论是cat类有没有重写move方法,运行阶段一定调用的是cat对象的move方法,因为底层真实对象就是cat对象。
父类型引用指向子类型对象这种机制导致程序存在编译阶段绑定和运行阶段绑定两种不同的形态/状态,
这种机制可以成为一种多态语法机制。
多态就是父类型的引用可以指向子类型的对象,程序产生了一种多种形态的概念第一种是编译期形态,第二种为运行期形态,两种不同的形态可以称为多态语法机制
多态的实现方式:重写,接口,抽象类和抽象方法;
Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。 String类有50多个方法,在后面单独开一篇讲解Java常用类及其常用方法。String 类是不可改变的,所以你一旦创建了 String 对象,那它的值就无法改变了 。
String存在于字符串常量池(StringTable)中,字符串常量池在不同版本的jdk里面存在于jvm里不同的区域。jdk1.6中,字符串常量池在方法区中,方法区的实现叫永久代,永久代存在于jvm内存。
在jdk1.8中方法区的实现叫元空间,元空间存在于本地内存。此时字符串常量池(StringTable)存在于堆里面。
相同的字符串在字符串常量池中只有一份,作用自然是为了节约内存。
当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。 StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
StringBuffer
的实例时,不会出现数据不一致的问题。StringBuffer
快,但如果在多线程环境中需要修改字符串,应特别小心使用它,以避免出现数据不一致或其他问题。从 JDK 5 开始,StringBuffer 已经补充了一个为单线程使用而设计的等效类,StringBuffer
的实例时,不会出现数据不一致的问题。StringBuffer
快,但如果在多线程环境中需要修改字符串,应特别小心使用它,以避免出现数据不一致或其他问题。从 JDK 5 开始,StringBuffer 已经补充了一个为单线程使用而设计的等效类,StringBuilder
。通常应优先使用 StringBuilder
类,因为它支持所有相同的操作,但速度更快,因为它不执行同步。StringBuffer
的方法大部分是同步的,因此在单线程环境中,它通常比 StringBuilder
慢。而 StringBuilder
,由于其方法不是同步的,因此在许多操作中表现得更快。StringBuilder
类,因为它支持所有相同的操作,但速度更快,因为它不执行同步。StringBuffer
的方法大部分是同步的,因此在单线程环境中,它通常比 StringBuilder
慢。而 StringBuilder
,由于其方法不是同步的,因此在许多操作中表现得更快。