Java基础
面向对象的特征:
- 抽象:
抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。 - 继承:
继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。通过继承,子类可以使用父类中的一些成员变量与方法,从而提高代码的复用性,提高开发效率。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。 - 封装:
封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。 - 多态性:
多态性是指允许不同类的对象对同一消息作出响应【因为这些类都实现了同一个接口或都继承自同一个父类】。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。
面向对象:是将数据及对数据的操作放在一起,作为一个相互依存的整体,即对象。对同类对象抽象出共性,即类。类中的属性只能被本类的方法处理。类通过暴露接口来对外提供服务,对象之间通过消息进行通信。
面向过程:是一种以事件为中心的开发方法,自顶向下顺序执行,其可以按功能划分为若干个模块,各个模块是相互独立的,且每一个模块一般是由顺序、选择和循环这三种基本结构组成。
0、Java简介
Java 介于编译型语言和解释型语言之间。编译型语言如C、C++,代码是直接编译成机器码执行,但是不同的平台(x86、ARM等)CPU的指令集不同,因此,需要编译出每一种平台的对应机器码。解释型语言如Python、Ruby没有这个问题,可以由解释器直接加载源码然后运行,代价是运行效率太低。而 Java 是将代码编译成一种“字节码”,它类似于抽象的CPU指令,然后,针对不同平台编写虚拟机,不同平台的虚拟机负责加载字节码并执行,这样就实现了“一次编写,到处运行”的效果。当然,这是针对 Java 开发者而言。对于虚拟机,需要为每个平台分别开发。为了保证不同平台、不同公司开发的虚拟机都能正确执行 Java 字节码,SUN公司制定了一系列的 Java 虚拟机规范。从实践的角度看,JVM的兼容性做得非常好,低版本的 Java 字节码完全可以正常运行在高版本的JVM上。
随着 Java 的发展,SUN给 Java 又分出了三个不同版本:
- Java SE: Standard Edition
- Java EE: Enterprise Edition
- Java ME: Micro Edition
这三者之间有啥关系呢?
┌───────────────────────────┐
│Java EE │
│ ┌────────────────────┐ │
│ │Java SE │ │
│ │ ┌─────────────┐ │ │
│ │ │ Java ME │ │ │
│ │ └─────────────┘ │ │
│ └────────────────────┘ │
└───────────────────────────┘
简单来说,Java SE就是标准版,包含标准的JVM和标准库,而Java EE是企业版,它只是在Java SE的基础上加上了大量的API和库,以便方便开发Web应用、数据库、消息服务等,Java EE的应用使用的虚拟机和Java SE完全相同。
Java ME就和Java SE不同,它是一个针对嵌入式设备的“瘦身版”,Java SE的标准库无法在Java ME上使用,Java ME的虚拟机也是“瘦身版”。
0.1 JRE or JDK
- JRE: Java Runtime Environment,Java 运行环境的简称,为 Java 的运行提供了所需的环境。它是一个 JVM 程序,主要包括了 JVM 的标准实现和一些 Java 基本类库。是使用 Java 语言编写的程序运行所需要的软件环境,是提供给想运行 Java 程序的用户使用的。
- JDK: Java Development Kit,Java 开发工具包,是程序员使用 Java 语言编写 Java 程序所需的开发工具包,是提供给程序员使用的,提供了 Java 的开发及运行环境。JDK 是 Java 开发的核心,集成了 JRE 以及一些其它的工具,比如包含了编译 Java 源码的编译器javac,还包含了很多 Java 程序调试和分析的工具工具,还包含了 Java 程序编写所需的文档和demo例子程序。
简单地说,JRE就是运行 Java 字节码的虚拟机。但是,如果只有 Java 源码,要编译成 Java 字节码,就需要JDK,因为JDK除了包含JRE,还提供了编译器、调试器等开发工具。
二者关系如下:
┌─ ┌──────────────────────────────────┐
│ │ Compiler, debugger, etc. │
│ └──────────────────────────────────┘
JDK ┌─ ┌──────────────────────────────────┐
│ │ │ │
│ JRE │ JVM + Runtime Library │
│ │ │ │
└─ └─ └──────────────────────────────────┘
┌───────┐┌───────┐┌───────┐┌───────┐
│Windows││ Linux ││ macOS ││others │
└───────┘└───────┘└───────┘└───────┘
如果你需要运行 Java 程序,只需安装JRE就可以了。如果你需要编写 Java 程序,需要安装JDK。
JRE根据不同操作系统(如:windows,Linux等)和不同JRE提供商(IBM,ORACLE等)有很多版本 。
0.1.1 Java的跨平台
Java 源程序先经过javac编译器编译成二进制的.class字节码文件(Java 的跨平台指的就是.class字节码文件的跨平台,.class字节码文件是与平台无关的)。.class文件运行在jvm上, Java 解释器(jvm的一部分)会将其解释成对应平台的机器码执行,所以 Java 所谓的跨平台就是在不同平台上安装了不同的jvm,而在不同平台上生成的.class文件都是一样的,而.class文件再由对应平台的jvm解释成对应平台的机器码执行。
机器码:完全依附硬件而存在,即不同的硬件由于内部的指令集不同,对同一指令的解析可能不同,即不存在真正的跨平台性。JAVA的跨平台性是因为有JVM,不同版本的JVM知晓不同的硬件指令,它会把字节码翻译为各个机器认识的机器码。
0.2 基本术语
- 类:构造对象的模板或蓝图
- 对象:类构造的过程,即创建的类实例
- 实例域:对象中的数据,操纵数据的过程称为方法,对于每个类实例(对象)都有一组特定的实例域值
- 封装:将数据和行为组合在一个包中,并对对象的使用者隐藏了数据的实现方式。实现封装的关键在于绝对不能让类中的方法直接地访问其他类的实例域,程序仅通过对象的方法与对象数据进行交互。封装给对象赋予了“黑盒”特征,提高了重用性与可靠性
- 继承:通过扩展一个类来建立另外一个类的过程,这个新类具有所扩展类的全部属性与方法,并且可以设置新类自己的新方法与数据域
- 派生: 从已有类产生一个新的子类,成为类的派生,派生类即子类,继承了基类的所有数据成员和函数,并可以对成员作必要的增加或调整
- 派生类是基类的具体化,基类是派生类的抽象
0.3 Java和JavaSciprt
JavaScript 与 Java 是两个公司开发的不同的两个产品。Java 是原Sun Microsystems公司推出的面向对象的程序设计语言,特别适合于互联网应用程序开发;而JavaScript是Netscape公司的产品,为了扩展Netscape浏览器的功能而开发的一种可以嵌入Web页面中运行的基于对象和事件驱动的解释性语言。JavaScript的前身是LiveScript;而 Java 的前身是Oak语言。
下面对两种语言间的异同作如下比较:
-
基于对象和面向对象:
Java 是一种真正的面向对象的语言,即使是开发简单的程序,必须设计对象;JavaScript是种脚本语言,它可以用来制作与网络无关的,与用户交互作用的复杂软件。它是一种基于对象(Object-Based)和事件驱动(Event-Driven)的编程语言,因而它本身提供了非常丰富的内部对象供设计人员使用。
-
解释和编译:Java 的源代码在执行之前,必须经过编译。JavaScript是一种解释性编程语言,其源代码不需经过编译,由浏览器解释执行(目前的浏览器几乎都使用了JIT(即时编译)技术来提升JavaScript的运行效率)
-
强类型变量和类型弱变量: Java 采用强类型变量检查,即所有变量在编译之前必须作声明;JavaScript中变量是弱类型的,甚至在使用变量前可以不作声明,JavaScript的解释器在运行时检查推断其数据类型。
-
代码格式不一样。
脚本语言:
脚本语言是为了缩短传统的编写-编译-链接-运行(edit-compile-link-run)过程而创建的计算机编程语言。一个脚本通常是解释执行而非编译。脚本语言通常都有简单、易学、易用的特性,目的就是希望能让程序员快速完成程序的编写工作。脚本也是一种语言,其同样由程序代码组成。脚本语言是一种解释性的语言,例如Python、JavaScript等等,它不像c\c++等可以编译成二进制代码,以可执行文件的形式存在,脚本语言不需要编译,可以直接用,由解释器来负责解释。
==脚本语言是被解释而系统程序设计语言是被编译。==被解释的语言由于没有编译时间而提供快速的转换,通过允许用户运行时编写应用程序,而不需要耗时的编译/打包过程。解释器使应用程序更加灵活,脚本语言的代码能够被实时生成和执行。脚本语言通常都有简单、易学、易用的特性,目的就是希望能让程序设计师快速完成程序的编写工作。
一、概述
-
值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量
-
引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。 所以对引用对象进行操作会同时改变原对象
一般认为, Java 内的传递都是值传递.
1.0 基本介绍
可以由四部分组成:数字、字母、下划线和美元符号$;
注:不可以使用数字开头;不可以使用 Java 中的关键字。
计算机的数据长度:
-
位:一个二进制数据0或1,是1bit(位);
-
字节:存储空间的计量单位,是1byte = 8bit;
# 基本数据类型
boolean 类型变量的取值有:ture、false,1位
char 数据类型有:unicode字符,16位
byte:一个字节(8位)(-128~127)
short:两个字节(16位)(-32768~32767)
int:四个字节(32位)(一个字长)(-2147483648~2147483647)
long:八个字节(64位)(-9223372036854774808~9223372036854774807)
float:四个字节(32位)(3.402823e+38 ~ 1.401298e-45)
double:八个字节(64位)(1.797693e+308~ 4.9000000e-324)
对应 Java 的基本数据类类型,即包装类:Integer、Float、Boolean、Character、Double、Short、Byte、Long
1.0.1 运算符
算数运算符、关系运算符、逻辑运算符、位运算符
-
算数运算符(9):+ - * / % ++ --。
-
关系运算符(6):== != > >= < <=。
-
逻辑运算符(6):&& || ! ^ & |
-
位运算符(7):& | ~ ^ >> << >>>
(1)算数运算符
算数运算符操作数必须是数值类型。
转换原则:从低精度向高精度转换byte 、short、int、long、float、double。低精度到高精度会自动转换,而高精度到低精度则要类型强制转换。
注意:
- 数值计算中语法现象——“晋升”,即:byte、short和char 低于int的数据类型)进行算术运算后,结果会自动提升成int类型;
- 两个char型运算时,自动转换为int型;当char与别的类型运算时,也会先自动转换为int型的,再做其它类型的自动转换;
- 算数运算可以加入小括号"()"提高优先级,优先小括号内运算,再其他运算符运算;
- 算数运算前操作数变量必须赋值,反之,报语法错误。
(2)关系运算符
关系运算符用于比较两个数值之间的大小,其运算结果为一个逻辑类型(boolean布尔类型)的数值。
等于’==’,不等于’!=’,大于’>’,大于等于’>=’,小于’<’,小于等于’<='
== 等于操作符:
如果两边是对象类型则基于内存引用,若他们的引用完全相同(指向同一个对象地址)时,操作将返回true,否则返回false。
如果两边是基本类型,就是比较数值是否相等。
(3)逻辑运算符
逻辑运算符要求操作数的数据类型为boolean逻辑型,其运算结果也是boolean逻辑型值。
逻辑运算的数据和逻辑运算符的运算结果是boolean类型。
逻辑与’&&’,逻辑或’||’,逻辑非’!'
两种与(逻辑与&&和位与&)的运算规则基本相同,两种或(逻辑或||和位或|)的运算规则也基本相同。
&和|运算是把表达式中每一位二进制位都比对完,而&&和||运算具有短路计算功能。
- 对于&来说,如果左侧条件为false,也会计算右侧条件的值,而对于&&来说,如果左侧的条件为false,则不计算右侧的条件,这种现象被称作短路现象。所谓短路计算,是指系统从左至右进行逻辑表达式的计算,一旦出现计算结果已经确定的情况,则计算过程即被终止。对于&&运算来说,只要运算符左端的值为false,则因无论运算符右端的值为true或为false,其最终结果都为false。所以,系统一旦判断出&&运算符左端的值为false,则系统将终止其后的计算过程;
- 对于 || 运算来说,只要运算符左端的值为true,则因无论运算符右端的值为true或为false,其最终结果都为true。所以,系统一旦判断出|| 运算符左端的值为true,则系统将终止其后的计算过程。
(4)位运算符
位运算是以二进制位为单位进行的运算,其操作数和运算结果都是二进制整型值,其实际上操作的是数的补码,但是在最后输出时会将原码以十进制的形式输出。
位与’&’,位或’|’,位非’~’,位异或’^’,右移’>>’,左移’<<’,0填充的右移’>>>'
位运算的位与’&’,位或’|’,位非’~’,位异或’^'与逻辑运算的相应操作的真值表完全相同,其差别只是位运算操作的操作数和运算结果都是二进制整数,而逻辑运算相应操作的操作数和运算结果都是逻辑值boolean型。
右移是将一个二进制数按指定移动的位数向右移位,移掉的被丢弃,左边移进的部分或者补0(当该数为正时),或者补1(当该数为负时)。这是因为整数在机器内部采用补码表示法,正数的符号位为0,负数的符号位为1。
- 将一个数左移"<<"会使该值乘以2的幂
- 将一个数右移“>>"会使该值除以2的幂
- 右移(补零)运算符">>>",即无符号右移,其永远不会产生负号,因为其符号位总是被补零。 不论被移动数是正数还是负数,左边移进的部分一律补0
// 右移和左移操作,是整数机器数的补码表示法。
//1 正数
int x = 70; // x等于二进制数的01000110——>补码为01000110
int y = 2;
int z = x>>y // z等于二进制数的00010001
// 即运算结果为z等于二进制数00010001,即z等于十进制数17【70/2/2】
//2 负数
int x = -70; //x等于二进制数的11000110——>补码为:10111010
int y = 2;
int z = x>>y //在补码上进行右移,左边补1,z等于二进制数11101110(这是补码)
// 即运算结果为z等于二进制数11101110,将补码转为原码,得其真值,即z等于十进制数-18
1.0.2 关键字
关键字 | 含义 |
---|---|
private | 一种访问控制方式:私用模式 |
protected | 一种访问控制方式:保护模式 |
public | 一种访问控制方式:共用模式 |
abstract | 表明类或者成员方法具有抽象属性 |
class | 类 |
extends | 表明一个类型是另一个类型的子类型,这里常见的类型有类和接口 |
final | 用来说明最终属性,表明一个类不能派生出子类,或者成员方法不能被覆盖,或者成员域的值不能被改变 |
implements | 表明一个类实现了给定的接口 |
interface | 接口 |
native | 用来声明一个方法是由与计算机相关的语言(如C/C++/FORTRAN语言)实现的 |
new | 用来创建新实例对象 |
static | 表明具有静态属性 |
strictfp | 用来声明FP_strict(单精度或双精度浮点数)表达式遵循IEEE 754算术规范 |
synchronized | 表明一段代码需要同步执行 |
transient | 声明不用序列化的成员域 |
volatile | 表明两个或者多个变量必须同步地发生变化 |
break | 提前跳出一个块 |
continue | 回到一个块的开始处 |
return | 从成员方法中返回数据 |
do | 用在do-while循环结构中 |
while | 用在循环结构中 |
if | 条件语句的引导词 |
else | 用在条件语句中,表明当条件不成立时的分支 |
for | 一种循环结构的引导词 |
instanceof | 用来测试一个对象是否是指定类型的实例对象 |
switch | 分支语句结构的引导词 |
case | 用在switch语句之中,表示其中的一个分支 |
default | 默认,例如,用在switch语句中,表明一个默认的分支 |
try | 尝试一个可能抛出异常的程序块 |
catch | 用在异常处理中,用来捕捉异常 |
throw | 抛出一个异常 |
throws | 声明在当前定义的成员方法中所有需要抛出的异常 |
import | 表明要访问指定的类或包 |
package | 包 |
boolean | 基本数据类型之一,布尔类型 |
byte | 基本数据类型之一,字节类型 |
char | 基本数据类型之一,字符类型 |
double | 基本数据类型之一,双精度浮点数类型 |
float | 基本数据类型之一,单精度浮点数类型 |
int | 基本数据类型之一,整数类型 |
long | 基本数据类型之一,长整数类型 |
short | 基本数据类型之一,短整数类型 |
null | 空 |
true | 正确 |
false | 错误 |
super | 表明当前对象的父类型的引用或者父类型的构造方法 |
this | 指向当前实例对象的引用 |
void | 声明当前成员方法没有返回值 |
goto | 保留关键字,没有具体含义 |
const | 保留关键字,没有具体含义 |
(1)synchronized
synchronized 关键字可以应用于方法或语句块,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
synchronized 关键字可防止代码的关键代码段一次被多个线程执行。
-
如果应用于静态方法,那么,当该方法一次由一个线程执行时,整个类将被锁定。
-
如果应用于实例方法,那么,当该方法一次由一个线程访问时,该实例将被锁定。
-
如果应用于对象或数组,当关联的代码块一次由一个线程执行时,对象或数组将被锁定。
(2)transient不被序列化
transient 关键字可以应用于类的成员变量,以便指出该成员变量不应在包含它的类实例序列化时被序列化。当一个对象被串行化的时候,transient 型变量的值不包括在串行化的表示中,然而非 transient 型的变量是被包括进去的。
class Student1 implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
// 该变量不会被序列化
private transient String password;
private static int count = 0;
public Student1(String name, String password) {
System.out.println("调用Student的带参的构造方法");
this.name = name;
this.password = password;
count++;
}
public String toString() {
return "人数: " + count + " 姓名: " + name + " 密码: " + password;
}
}
Java 的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。
当两个进程进行远程通信(RMI, 远程方法调用(Remote Method Invocation))时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。此时就需要使用序列化与反序列化实现 Java 对象的传送:
- 序列化:将 Java 对象转化为字节序列的过程,是对象持久化的一种实现方法,它是将对象的属性和方法转化为一种序列化的形式用于存储和传输。
- 反序列化:将字节序列转化为 Java 对象的过程,根据字节序列保存的信息重建对象。
(3)volatile保证值最新
volatile 用以声明变量的值可能随时会别的线程修改的变量,使用volatile修饰的变量会强制将某个线程修改的新值立即写入主存,主存中值的更新会使缓存中的值失效【非volatile变量不具备这样的特性,非volatile变量的值会被缓存,线程A更新了这个值,线程B读取这个变量的值时可能读到的并不是是线程A更新后的值】。同时,volatile会禁止指令重排。
volatile 是 Java 提供的一种轻量级的同步机制【与synchronized机制相比较】。volatile 的目标用途是为了确保在多线程环境下,所有线程所看到的指定变量的值都是相同的,最新的。volatile 可保证可见性与有序性,但是不保证原子性。【参见多线程高级】
并发编程的三个基本概念:
-
原子性:即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
原子性是拒绝多线程操作的。不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。简而言之,在整个操作过程中不会被线程调度器中断的操作,都可认为是原子性。
-
可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能立即看到修改后的值。
在多线程环境下,一个线程对共享变量的操作对其他线程是不可见的。 Java 提供了volatile来保证可见性,当一个变量被volatile修饰后,表示着线程本地内存无效,当一个线程修改共享变量后他会立即被更新到主内存中,其他线程读取共享变量时,会直接从主内存中读取。因此可以保证可见性。【JMM——java内存模型】
-
有序性:即程序执行的顺序按照代码的先后顺序执行。
在 Java 内存模型中,为了效率是允许编译器和处理器对指令进行重排序,当然重排序不会影响单线程的运行结果,但是对多线程会有影响。 Java 提供volatile来保证一定的有序性。
指令重排:一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
1.0.3 Java的特性
(1)封装
隐藏对象内部数据的复杂性,只对外公开简单的接口,便于外界调用,从而提高系统的可扩展性、可维护性。
Java中通过将数据声明为私有的(private), 再提供公共的( public)方法:getXxx()和setXxx()实现对该属性的操作, 以实现下述目的:
- 隐藏一个类中不需要对外提供的实现细节;
- 使用者只能通过事先定制好的方法来访问数据, 可以方便地加入控制逻辑,限制对属性的不合理操作;
- 便于修改, 增强代码的可维护性
(2)继承
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
作用:
- 继承的出现减少了代码冗余,提高了代码的复用性
- 继承的出现,更有利于功能的扩展
- 继承的出现让类与类之间产生了关系,提供了多态的前提
(3)多态
对象的多态性:父类的引用指向子类的对象。可以直接应用在抽象类和接口上,提高了代码的通用性。前提是需要存在继承或者实现关系,有方法的重写。
多态性有两种表现形式:
- 方法的重载
- 方法的覆盖
1.1 变量与常量
1.1.1 变量
(1)变量
在计算机程序中,声明在不同地方的变量具有不同的作用域,例如局部变量、全局变量等。在 Java 语言中,作用域是由花括号的位置决定的,它决定了其定义的变量名的可见性与生命周期。
声明一个局部变量之后,必须使用赋值语句对变量进行显式的初始化,不能使用未初始化的变量。
TreeNode temp; // 只定义了一个变量temp,没有给其赋初值
TreeNode temp1 = null; // 定义了一个变量,并且给其赋值为null,但是没有给其分配内存空间
TreeNode temp1 = new TreeNode(1); // 在堆中创建对象,并为其分配内存空间
在 Java 语言中,变量的类型主要有3种:成员变量、静态变量和局部变量。
- 类的成员变量的作用范围与类的实例化对象的作用范围相同,当类被实例化时,成员变量就会在内存中分配空间并初始化,直到这个被实例化对象的生命周期结束时,成员变量的生命周期才结束。【方法外未被static修饰的变量】
- 被static修饰的成员变量称为静态变量或全局变量,与成员变量不同的是,静态变量不依赖于特定的实例,而是被所有实例所共享,也就是说,只要一个类被加载,JVM就会给类的静态变量分配存储空间并对其进行初始化,对其赋零值。因此,就可以通过类名和变量名来访问静态变量,同时不需要必须为静态变量进行初始化操作。【方法外被static修饰的变量】
- 局部变量的作用域与可见性为它所在的花括号内。
(2)访问修饰符
修饰符 | 说明 | 当前类 | 同一package | 子类 | 其他package |
---|---|---|---|---|---|
public | 被public修饰的class/interface或field/method可以被其他任何类访问 | √ | √ | √ | √ |
private | private修饰符只限制在class内部,被private修饰的field/method无法被其他类访问,嵌套类除外(如静态内部类) | √ | × | × | × |
protected | protected修饰符作用于继承关系。被其修饰的字段和方法可以被它的子类访问 | √ | √ | √ | × |
(3)变量类型
- 局部变量:定义在方法内部,各方法内部的局部变量的名称可以重复,但是不可重定义成员变量
- 成员变量:定义在类中,但是在方法外,不可重定义
- 静态变量/类变量:使用static修饰的变量
- 实例变量:不使用static修饰的变量
注:(2)中的修饰符只能用来修饰成员变量,不能被用来修饰局部变量。
1.1.2 常量
(1)普通常量
在 Java 中利用final指示常量。
public class Fourth_Constant {
public static void main(String[] args) {
// 普通常量
// 只能对其进行一次赋值,可以直接对其赋值,也可以使用构造器对其进行赋值
// 参见:2.2.2 final实例域
final double ZDP = 123456;
// 报错:不允许再次赋值
// ZDP = 123;
System.out.println(ZDP);
}
}
final 表示这个变量只能被赋值一次。一旦被赋值之后,就不能再更改了。习惯上,常量名使用大写。
final 指的是引用的不可变性,即它只能指向初始时指向的那个对象,而不关心指向对象内容的变化。所以,被final修饰的变量必须初始化。
这种情况视为不使用 static 的情况。如果建立常量时直接赋一个固定值,那么这个常量的值是固定不变的,即多个对象中的值也是相同的。如果建立常量时是采用函数或对象,那么每次建立对象时给其常量的初始化值就有可能不同。所以,只使用 final 的 Java 常量定义并不是恒定的。
(2)类常量/静态常量
类常量可以在一个类中被类中的多个方法使用,可以使用关键字 static final 设置一个类常量。
public class Fourth_Constant {
// 类常量/静态常量
public static final double ZDP = 123456;
// 需要对静态常量进行立即初始化,否则会报错
public static void main(String[] args) {
// 报错:不允许再次赋值
// ZDP = 123;
System.out.println(ZDP);
}
// 本类中的其他方法可以访问类常量
@Test
public void printZdp(){
System.out.println(ZDP);
}
}
需要注意的是,类常量的定义位于main方法的外部。因此,在同一个类的其他方法也可使用这个常量,如果这个常量被声明为public,那么其他类的方法也可以访问这个类常量。此为使用static修饰的情况。在创建对象之前就会为这个变量在内存中创建一个存储空间,以后创建对象如果需要用到这个静态变量,那么就会共享这一个变量的存储空间。
1.2 字符串
Java 字符串是Unicode字符序列, Java 字符串由char值序列组成。每个用双引号括起来的字符串都是string类的一个实例。
-
空串(“”)是一个长度为0的字符串,空串是一个 Java 对象,有自己的长度(0)和内容(空)。可以使用以下代码来检查一个字符串是否为空:
if(str.length()==0) ====================== if(str.equals(""))
-
null串表示没有任何对象与该变量关联。可以使用以下字符串是否为null:
if(str==null)
1.2.1 概览
-
String 类被声明为 final,因此它不可被继承。(Integer 等包装类也不能被继承)
-
String 是不可变的字符序列,即==不可变性==。体现在:
- 当对字符串重新赋值时,需要重写指定内存区域【字符串常量池】新建字符串对其进行赋值,不能使用原有的value进行赋值
- 当对现有的字符串进行连接操作时,也需要重新指定内存区域对其赋值,不能使用原有的value进行赋值
-
String 不可变性天生具备线程安全,可以在多个线程中安全地使用。
在 Java 8 中,String 内部使用 char 数组存储数据。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。
1.2.2 定义方式
- 使用字面量定义。虽然String不是基本数据类型,但是它也可以使用给基本数据类型赋值的方式赋值,即使用字面量的形式,如
String a = “abc”;
- 使用new创建一个String类的对象,并对其进行赋值。如
String a = new String(“ABC”);
(1)字面量方式
通过字面量方式给一个字符串赋值,此时的字符串值会声明在字符串常量池中。字符串常量池中是不会存储相同内容的字符串的。
采用字面量的形式创建字符串,会自动地将字符串“abc”放入 String Pool 常量池中。
// 对于字面量形式的字符串,会被自动的加入常量池中
String s1 = "abc";
// 由于字符串常量池内存储的内容唯一,所以s2不再创建,直接将s2指向“abc”串
String s2 = "abc";
System.out.println(s1 == s2); // true
当给 s1 赋新值时,此时 s1 的地址会指向新的地址:
// 对于字面量形式的字符串,会被自动的加入常量池中
String s1 = "abc";
// 由于字符串常量池内存储的内容唯一,所以s2不再创建,直接将s2指向“abc”串
String s2 = "abc";
System.out.println(s1 == s2); // true
// 赋新值
s1 = "abcde";
replace()操作:
// 调用replace函数对s1进行修改,生成一个新的字符串s3
String s1 = "abc";
String s3 = s1.replace('a','m');
System.out.println(s1); // abc 未变
System.out.println(s3); // mbc 变了,本质上还是新建了一个字符串,s1指向的abc仍然存在
以上是String不可变性的体现。只要有对字符串重新赋值的操作,都会开辟新的内存区域来进行赋值,不会改变原有的value。【字符串常量池的特性决定】
(2)new方式
String基本的构造器有:
//本质上this.value = new char[0];
String s1 = new String();
//this.value = original.value;
String s2 = new String(String original);
//this.value = Arrays.copyOf(value, value.length);
String s3 = new String(char[] a);
String s4 = new String(char[] a,int startIndex,int count);
字符串对象的存储方式:
// 未初始化,没有开辟存储空间
String s1;
// 初始化为null,没有开辟存储空间
String s2 = null;
// 字面量形式定义为空字符串,此时会直接存储在字符串常量池中,不会在堆中开辟内存
String s3 = "";
// 输出内容即为字面量:空串
System.out.println(s3);
System.out.println("*********************");
// new构造器形式定义两个对象,此时会在堆中开辟两个不同的内存空间
String s4 = new String();
String s5 = new String();
System.out.println(s4); // 输出内容为:空
System.out.println(s3 == s4); // 字面量与对象的比较:false
System.out.println(s4 == s5); // 两对象的比较:false,此时为堆中的地址
// 针对一般的类,也是如此
Person p1 = new Person();
p1.name = "zdp"; // 使用字面量形式赋值
Person p2 = new Person();
p2.name = "zdp"; // 使用字面量形式赋值
// 因为使用字面量形式赋值,所以两个对象的name属性都指向常量池中的同一个串"zdp"
System.out.println(p1.name.equals(p2.name)); // true
System.out.println(p1.name == p2.name); // true
p1.name = "zjx"; // 使用字面量形式给p1重新赋值,会开辟一个新的区域,p1指向这个新的串
System.out.println(p2.name); // 但不会改变p2的值,因为不可变性
注意:当使用new方式创建含有String属性的对象时,在内存中会创建两个对象
- 一个是在堆空间中new的本类对象
- 一个是在字符串常量池中创建的字符串内容
(3)字符串拼接
-
常量与常量的拼接结果在字符串常量池,且字符串常量池中不会存在相同内容的常量
-
两个字符串常量进行拼接时,会在编译期进行优化,如 s3 将会直接被优化为 “helloworld” 一个串
-
只要其中有一个是变量,就会在堆空间中new一个String()实例对象,其内容为“helloworld”
-
如果拼接的结果调用intern()方法,返回值就在常量池中
intern()方法:
当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定,只比较内容),那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。
@Test
public void test1() {
String str1 = new StringBuilder("mei").append("tuan").toString();
System.out.println(str1);
System.out.println(str1.intern());
System.out.println(str1 == str1.intern()); // true
System.out.println();
String str2 = new StringBuilder("ali").append("baba").toString();
System.out.println(str2);
System.out.println(str2.intern());
System.out.println(str2 == str2.intern()); // true
System.out.println();
String str3 = new StringBuilder("ja").append("va").toString();
System.out.println(str3); // 自定义的字符串:java
System.out.println(str3.intern()); // sun.misc.Version 类中定义的static字符串变量:java
System.out.println(str3 == str3.intern()); // false
}
注:其中第三个字符串“java”,在JVM类加载时就会首先加载一个字符串“java”进字符串常量池,所以在调用intern()方法时,将会报“false”。
1.2.3 String相关类
(1)StringBuffer
StringBuffer 是一个可变对象,当对他进行修改的时候不会像String那样重新建立对象,可以对字符串内容进行增删,此时不会产生新的对象。
它只能通过构造函数来建立:StringBuffer sb = new StringBuffer();
StringBuffer对象实例化后,只对这一个对象操作。
(2)StringBuilder
StringBuilder 和 StringBuffer 非常类似,均代表可变的字符序列, 而且提供相关功能的方法也一样。两个类的构造器和方法也基本相同。不同的是:StringBuffer是线程安全的,而StringBuilder则没有实现线程安全功能,所以性能略高。
(3)区别
- 运算速度比较:StringBuilder > StringBuffer > String
- String(JDK1.0): 不可变字符序列
- StringBuffer(JDK1.0): 可变字符序列、效率低、线程安全,初始化容量为16
- StringBuilder(JDK 5.0):可变字符序列、效率高、 线程不安全,初始化容量为16
String是不可变类,String对象一旦被创建,其值将不会被改变,而StringBuffer与StringBuilder是可变类,当对象被创建之后仍然可以对其值进行修改。StringBuffer与StringBuilder只能使用构造器来进行初始化,不能使用赋值的方式来初始化。StringBuffer与StringBuilder的区别在于StringBuilder不是线程安全的,如果只是在单线程环境下使用字符串缓冲区,那么StringBuilder的效率会更高些。
注意:作为参数传递的话,方法内部String不会改变其值, StringBuffer和StringBuilder会改变其值(可变与不可变的差异)
1.3 基本数据类型
Java 语言支持的8种基本数据类型是:
1.3.1 数值类型
(1)整型
java中各进制数的表示:
- 0b:二进制数
- 0:八进制数
- 默认:十进制数
- 0x:十六进制数
整型用于表示没有小数部分的数值,它允许是负数。在JAVA中,没有任何无符号unsigned形式的int,long,short和byte类型。
int a = 0b10000000000000000000000000000000; // 32位二进制数
System.out.println(a<<1); // 移位操作,左移一位===>a=0
注:在给Long类型的对象赋值的时候,若整数类型的数据没有超出int的表示范围,则不需要在该数据之后加上L。如long age = 16;
(2)浮点类型
浮点类型用于表示小数部分的数值。默认情况下,默认使用双精度浮点数double,所以要定义一个 float 类型的变量,需要在数值后面加上F。
注:
- 有效位数指的是从左边第一个不为零的数开始到最后一个数
- 2进制的小数无法精确的表达10进制小数,所以在计算小数时,会出现误差
浮点数的底层表示分为:
- float:符号位(1)+指数位(8)+尾数位(23)
- double:符号位(1)+指数位(11)+尾数位(52)
将double类型的数据转换为float类型的数据,会造成数据精度的降低
注:但是此时的数据精度会降低:1.1234567894562F——>1.1234568
在实际应用中,浮点类型不适合用于无法接受舍入误差的金融计算中。在数值计算中不允许有任何舍入误差时,可以使用BigDecimal类,进行精确计算。
1.3.2 字符类型
char原本是用来表示单个字符。char的字面量值要使用单引号括起来。
(1)编码
要想弄清楚char类型,就必须了解Unicode编码机制(国际通用字符集)。Unicode编码打破了传统字符编码机制的限制。即它是想解决在先前已经出现的各种编码机制针对不同的字母可能对应不同的数值问题。在最初设计 Java 时,采用了16位的Unicode字符集,但是现在16位已经远远无法满足需求了。
随后,国际上推出了UTF编码标准,采用不同长度的编码表示所有的Unicode码点:
- UTF-8
- UTF-16
- UTF-32
(2)码点与代码单元
码点是指与一个编码表中的某个字符对应的代码值。在Unicode编码标准中,码点采用十六进制书写,并加上U+前缀,例如U+0041(65)就是A的码点;
UTF-16编码采用不同长度的编码表示所有的Unicode码点。Unicode的码点分为17个代码级别,第一个级别为基本的多语言级别,码点从U+0000到U+FFFF,包含经典的Unicode代码;其余16个级别的码点从U+10000到U+10FFFF,包含一些辅助字符。
- 在基本的多语言级别(经典字符)中,每个字符用16位表示,通常被成为代码单元;
- 在辅助字符(特殊字符)中,则采用一对连续的代码单元进行编码。
char数据类型是一个采用UTF-16编码表示Unicode码点的代码单元。length()方法返回的是采用UTF-16编码表示的给定字符串所需要的代码单元数量,即只关心经典字符的数量;而若要得到实际的长度,就要返回码点的数量。
在 Java 中,char本质上是UTF-16编码,且只能存放UTF-16编码下的只占2字节的那些字符。所以强烈建议不要在程序中使用char类型,最好使用字符串作为抽象数据类型。
(3)底层实现
char类型表现出来的是字面常量,但是在底层进行计算的时候,实际上是按照一个码值进行的,这个码值为ASCII码。但是之前介绍说char类型是按照Unicode码进行存储的,这是因为Unicode表本身的前128位是兼容ASCII码表的。
// char与整型的转换
int num = '中'; // 字符'中'的Unicode的码值
char zhong = 20013; // 将'中'的码值赋值给char
System.out.println(num);
System.out.println(zhong);
1.3.3 布尔类型
boolean 用于判定逻辑条件。整型与布尔值之间不能进行相互转换。
虽然定义了 boolean 这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何供 boolean 值专用的字节码指令,Java 语言表达式所操作的 boolean 值,在编译之后都使用 Java 虚拟机中的 int 数据类型来代替,而 boolean 数组将会被编码成 Java 虚拟机的 byte 数组,每个元素 boolean 元素占8位。这样我们可以得出 boolean 类型占了单独使用是4个字节,在数组中又是1个字节。使用 int 的原因是,对于当下32位的处理器(CPU)来说,一次处理数据是32位(这里不是指的是32/64位系统,而是指CPU硬件层面),具有高效存取的特点。
1.4 引用数据类型
1.4.1 数组
数组是引用数据类型,是对象。因为每个数组类型都有其对应的类型,同时数组不仅有自己的属性【如length】,也有一些方法可以调用。
(1)特点
数组是同一数据类型的集合,数组一旦创建后,大小就不可变;可以通过索引访问数组元素,但索引超出范围将报错;数组元素可以是值类型(如int)或引用类型(如String),但数组本身是引用类型。
Java 的数组有几个特点:
- 数组所有元素初始化为默认值,整型都是
0
,浮点型是0.0
,布尔型是false
- 长度是确定的。数组一旦被创建,它的大小就是不可以改变的,数组元素可以重复
- 其元素必须是相同类型,不允许出现混合类型
- 数组类型可以是任何数据类型,包括基本数据类型和引用类型
- 如果对数组进行填充时,没有填充满,那么其剩余的空间仍会保留空值null
(2)数组初始化
静态初始化:静态初始化是指由程序员在初始化数组时为数组每个元素赋值,由系统决定数组的长度。
// 静态初始化中,不指定数目,数目由系统解析获得【若指定数目,将会报错】
// 一维数组
int[] array = new int[]{1,2,3};
int[] array = {1,2,3};
// 二维数组
int[][] array = new int[][]{{5, 4}, {6, 4}, {6, 7}, {2, 3}};
“动态”初始化:由程序员在初始化数组时指定数组的长度,由系统为数组元素分配初始值。
// 动态初始化在数组声明的同时会分配一块内存空间供该数组使用,其长度为10
// 同时,动态初始化时,其元素会根据它的数据类型被设置为默认的初始值
// 一维数组
int[] array = new int[10]; // 动态初始化数组,必须指定数目
// 二维数组
int[][] array = new int[4][]; // 二维数组在动态初始化时,必须指定行数,可不设定列数
// 给动态初始化的数组进行统一赋值为1,不使用int的默认初始值0
Arrays.fill(array, 1);
(3)二维数组
多维数组可以看成是数组的数组。
二维数组必须指定行数,而无需指定列数:int [][]arr=new int[3][];
a> 特殊二维数组
有一种特殊的二维数组,它的行数确定,但是每行的列数不确定。这样的的数组实现方法:先创建制定行数,列数缺省的二维数组,然后对数组的每一行重新初始化。
//第二种定义二维数组的方式,它的行数确定,但是每行的列数不确定。这样的的数组实现方法:先创建制定行数,列数缺省的二维数组,然后对数组的每一行重新初始化。
public class TwoArrayDemo_3 {
public static void main(String[] args) {
int[][] arr = new int[3][];
//第一行
arr[0] = new int[7];//相当于int[] a=new int[7]
for (int i = 0; i < arr[0].length; i++) {
arr[0][i] = i + 1;
}
//第二行
arr[1] = new int[4];
for (int i = 0; i < arr[1].length; i++) {
arr[1][i] = i + 1;
}
//第三行
arr[2] = new int[8];
for (int i = 0; i < arr[2].length; i++) {
arr[2][i] = i + 1;
}
//遍历数组
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
System.out.print(arr[i][j]);
}
System.out.println();
}
}
}
b> 获取二维数组的行列数
二维数组就是一个特殊的一维数组,其每一个元素都是一个一维数组。
-
行数:即为该二维数组的总长度
-
列数:为各元素(行)的长度
int[][] arr = {
{2,3,4},
{4,5,6},
{7,8,9}
};
int rows = arr.length;//行数
int columns = arr[0].length;//列数
1.4.2 类
…
1.4.3 接口
…
1.5 值传递机制
1.5.1 变量的赋值
- 如果变量是基本数据类型,此时赋值的是变量所保存的数据值
- 如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值
1.5.2 值传递机制
(1)形参与实参
- 形参:方法定义时,声明的小括号内的参数
- 实参:方法调用时,实际传递的数据
(2)形参值传递机制
- 如果参数是基本数据类型,此时实参赋给形参的是实参的真实存储的数据值
- 如果参数是引用数据类型,此时实参赋给形参的是实参的真实存储数据的地址值
/**
* 值传递机制
*/
public class TransferValue {
// 值传递
public void test1(int age) {
age = 30;
}
// 引用数据传递
public void test2(Person person){
person.setAge(30);
}
// 引用数据传递
public void test3(String str) {
str = "xxx";
}
public static void main(String[] args) {
TransferValue transferValue = new TransferValue();
//1 传递基本数据类型
int age = 10;
// 值传递
transferValue.test1(age);
System.out.println(age); // 输出的age还是主方法中的age
//2 传递引用数据类型
Person person = new Person(20);
// 引用传递
transferValue.test2(person);
System.out.println(person.getAge()); // 输出的person是堆中的引用
//3 传递引用数据类型
String str = "abc";
transferValue.test3(str);
System.out.println(str); // 输出的String是方法区中的字符串常量
}
}
1.6 static关键字
static关键字可以修饰变量、方法、代码块。
1.6.1 静态变量
static修饰的变量也称为静态变量,静态变量和非静态变量的区别是:静态变量被所有对象共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在new创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
static成员变量的初始化顺序按照定义的顺序进行初始化。
- 可以使用类实例对象访问静态变量
- 也可以使用类直接访问静态变量
1.6.2 静态代码块
static关键字还有一个比较重要的作用就是用来形成静态代码块以优化程序性能。静态代码块只能操作静态变量,不可以访问非静态变量。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来依次执行每个static块,并且只会执行一次。
static块可以优化程序性能,是因为它的特性:
-
静态代码块的执行要先于非静态代码块。
-
静态代码块随着类的加载而加载,且只执行一次。
1.6.3 静态方法
(1)简介
Java 中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。静态方法只能访问静态变量。
静态方法是一种不能操作对象的方法,即不能访问类中的实例域,但是可以访问自身类中的静态域。
实例方法和静态方法:
Java 中的方法分为类方法和实例方法,区别是类方法中有static修饰,为静态方法,是类的方法。所以在类文件加载到内存时就已经创建,但是实例方法是对象的方法,只有对象创建后才起作用,所以在类方法中不能调用实例方法,但实例方法中可以调用类方法,且实例方法可以互相调用。
(2)main()方法
- 由于 Java 虚拟机需要调用类的main()方法,所以该方法的访问权限必须是 public,又因为 Java 虚拟机在执行main()方法时不必创建对象,所以该方法必须 是static的,该方法接收一个String类型的数组参数,该数组中保存执行 Java 命令时传递给所运行的类的参数。
- 又因为main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员,这种情况,我们在之前的例子中多次碰到。
1.6.4 成员变量赋值顺序
当实例化对象时,对象所在类的所有成员变量首先要进行初始化,只有当类的所有成员变量完成初始化后,才会调用对象所在类的构造函数创建对象。
(1)初始化的原则
JAVA程序的初始化一般遵循3个原则:
- 静态对象优先于非静态对象。其中,静态对象只初始化一次,而非静态对象可能会被初始化多次。
- 父类优先于子类。
- 按照成员变量定义的先后顺序进行初始化。
(2)初始化的顺序
public class LoadOrder {
//1 静态变量
public static String staticField = "静态变量";
//2 静态初始化块
static {
System.out.println(staticField);
System.out.println("静态初始化块");
}
//3 变量
public String field = "变量";
//4 初始化块
{
System.out.println(field);
System.out.println("初始化块");
}
//5 构造器
public LoadOrder() {
System.out.println("构造器");
}
public static void main(String[] args) {
// 调用构造器,创建一个对象
new LoadOrder();
}
}
1.6.5 总结
“static”关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。
static可以用来修饰属性、方法和代码块。static修饰的变量属于这个类所有,既由这个类创建的所有对象共用一个static变量。通常把static修饰的属性和方法称为类属性(类变量)、类方法。不使用static修饰的属性和方法属于单个对象,通常称为实例属性(实例变量)、实例方法。
- static的变量在定义赋初值后,在以后的代码中可以被修改,只要是改一次就变一次
- 静态方法中不能使用this(因为被static修饰的属于类)和super关键字(super与继承有关)
- 在 Java 中不允许使用static修饰局部变量,只能修饰全局变量
- 静态方法只能访问本类中的静态变量,且static方法不能被覆盖
(1)优点
可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。
(2)static与final的区别
static变量跟final变量在内存中分布不同,final变量在堆内存中,两个对象各有一块且有自己的初值,而static在方法区只存在一份值(共享),只要一个类被加载,JVM就会给类的静态变量分配存储空间。
static修饰的属性强调它们只有一个,final修饰的属性表明是一个常数(创建后不能被修改)。static final 修饰的属性表示一旦给值,就不可修改,并且可以通过类名访问。
static final也可以修饰方法,表示该方法不能被重写,但是可以在不new对象的情况下调用。如主方法main().
1.7 final关键字
final标识的实例域就是将类中的某些变量设置为常量。
将实例域定义为final时,构建对象时必须初始化这样的域,即在每一个构造器执行后,这个域的值必须要被设置,并且在后面的操作中,不能够再对它进行修改,即不能再使用set()方法。例如,可以将Employee类中的name域声明为final,因为在构建之后,这个值不会再被修改,即没有setName()方法:
final修饰符大都应用于基本类型域,或不可变类的域。
1.7.1 final类
用final修饰的类不能被继承 ,要注意的是被final修饰的类,它的成员方法也都会被隐式的指定为final方法。
一般来说我们往往会把工具类设计成为一个final类。在JDK中,被设计为final类的有String、System等。
1.7.2 final方法
被final修饰的方法代表不能被重写,但是可以被重载.其目的是把方法锁定,以防任何继承类修改它的含义。【因为无法被继承,所以就无法被重写——发生在子类与父类之间】
1.7.3 final变量
即常量。被final修饰的变量必须要初始化,赋值后不能再重新赋值。
- 如果修饰的变量是基本数据类型,值不能改变
- 如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
为被final修饰的成员变量初始化有两种方式:
-
直接赋值 :
public final int id = 1;
【显式初始化】 -
在构造方法中赋初值:【构造器初始化】
private final String name; // 构造方法 public Employee(String name) { // 使用final修饰的变量只能在构造时进行一次赋值 this.name = name; }
1.8 switch-case
-
JDK1.7之前,case只能支持 int、byte、short、char这几个基本数据类型和其对应的封装类型,switch后面的括号里面只能放int类型的值,但由于byte,short,char类型,它们会自动转换为int类型(精度小的向大的转化)。
-
JDK1.7后,整型,枚举类型,boolean,字符串都可以。之所以JDK1.7之后case可以支持String,是通过调用switch中 String.hashCode 将String 转换为 int 从而进行判断。
注意,对于精度比int大的类型,比如long、float,doulble,不会自动转换为int,如果想使用,就必须强转为int,如(int)float