javaSE基础知识以及面向对象

javaSE基础知识以及面向对象

1,java开发环境知识

1.1 jdk,jre,jvm

① JDK:JDK(Java Development Kit) 是 Java 语言的软件开发工具包(SDK)。指的是Java Development Kit,它是一个开发Java应用程序所需的软件开发工具包。JDK包括Java编译器、Java虚拟机、Java类库等必要组件,以及用于开发、调试和运行Java应用程序的其他工具

② JRE:JRE(Java Runtime Environment,Java 运行环境),运行 JAVA程序所必须的环境的集合,包含 JVM 标准实现及 Java 核心类库。

③ JVM:JVM 是 Java Virtual Machine(Java 虚拟机)的缩写,JVM 是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM 是实现 Java 语言跨平台的法宝。

在这里我需要重点强调一下,不同的操作系统需要安装不同版本的 JDK,有专门的 windows版 JDK,Linux 版 JDK,Mac 版 JDK 等,当然不同版本的 JDK安装之后会对应不同版本的 JRE和 JVM。

JDK 的安装目录。在目录下面有六个文件夹、一个 src 类库源码压缩包、和其他几个声明文件。其中,真正在运行 java 时起作用的是以下四个文件夹:bin、include、lib、jre。bin : 最主要的是编译器(javac.exe),include : java 和 JVM 交互用的头文件,lib:类库,jre:java 运行环境。注意:这里的 bin、lib 文件夹和 jre 里的 bin、lib 是不同的)总的来说 JDK是用于 java 程序的开发,而 jre 则是只能运行 class 而没有编译的功能。eclipse、idea 等其他 IDE 有自己的编译器而不是用 JDK bin 目录中自带的,所以在安装时你会发现他们只要求你选 jre 路径就 ok 了。jdk,jre内部都包含 JAVA 虚拟机 JVM,JAVA 虚拟机内部包含许多应用程序的类的解释器和类加载器等等。

1.2 初步了解 Java的加载与执行

通过上图,我们可以看到 Java 程序从开发到最终运行是这样进行的:

第一步(写代码):在任意位置创建一个.java 结尾的文件,程序员在该文件中编写符合 Java语法的源代码,这个文件被称为Java 源文件。

第二步(编译):使用“javac”命令对 java 源文件进行编译,如果 Java 源文件符合 Java语法规则,则编译生成 1 个或者多个以“.class”结尾的文件。“.class”结尾的文件我们称之为字节码文件,注意字节码文件不是普通文本文件,使用记事本等文本编辑器是无法打开的,但该文件内容也不是二进制,如果是二进制形式的话,操作系统是可以直接执行的,这个文件的内容我们称之为字节码。注意:这里有一个“类名”的概念,假设生成的文件是 A.class,则表示类名为 A,B.class 则表示类名为 B。

第三步(运行):使用“java”命令运行已编译的 Java 程序,假设编译之后的字节码文件是 A.class,则在 dos 命令窗口中执行 java A命令,这个时候 Java 虚拟机(JVM)就启动了,Java 虚拟机通过“类装载器 ClassLoader”从硬盘中找到 A.class 文件并装载,字节码文件被装载到 Java 虚拟机之后,Java 虚拟机解释器对字节码进行解释,最终解释为二进制码,然后操作系统通过执行二进制指令来和硬件平台完成交互。以上则是 Java 程序加载与执行的过程,接下来我给大家总结 7 条规则,大家可以理解并记忆一下:

① Java 程序从开发到运行包括编译和运行两个阶段,这两个阶段可以在不同的操作系统中完成,例如在 windows 环境下进行编译,在 Linux 环境下运行,这是因为有 JVM 机制的存在,做到了一次编译到处运行(跨平台/可移植)。

② 编译阶段需要使用 javac.exe(安装 JDK 之后该命令就存在了)命令,运行阶段需要使用 java.exe(安装 JRE 之后该命令就存在了)命令。

③ 一个 Java 源文件可能会编译生成多个 class 文件。

④ Java 源文件中的源代码如果不符合 Java 的语法机制则编译时编译器会提示错误信息,并且无法生成 class 文件。反之则生成 class 文件,而 class 文件才是最终要执行的程序,此时将 Java 源文件删除是不会影响 Java 程序运行的(当然,我们也不必删除 java 源文件,因为在运行 class 文件之后,如果没有达到预期的运行效果,这个时候还需要将 Java 源代码修改,重新编译,以达到最终的运行效果)。

⑤ 若生成的字节码文件名为 A.class,那么我们称 A为一个类的名字(这个先记住就行,后面的内容会讲)。

⑥ 当编译阶段完成之后,我们可以使用 JRE 中的 java.exe 命令运行程序,例如执行命令“java A”,该命令执行后会启动类加载器,类加载器去硬盘上搜索 A.class 文件,找到该字节码文件之后,将其加载到 JVM 当中,JVM 中的解释器会将 A.class 字节码文件解释为操作系统可以执行的二进制码,然后操作系统通过执行二进制码和硬件平台交互。

⑦ 运行 Java 程序的前提是当前操作系统上已经安装了对应版本的 JVM(JVM 不是单独安装的,安装 JRE 即可,不同的操作系统需要安装不同版本的 JRE,不同版本的 JRE 对应不同版本的 JVM。

1.3 配置环境变量

java的编译运行命令都在jdk的bin目录下,因此只能在bin目录下执行相关命令。为了在各个目录下都能执行命令,因此需要配置环境变量path,path分为系统变量和用户变量,用户变量只对当前用户有效,系统变量对所有用户有效。

windows 操作系统到底是如何搜索命令文件的呢?实际上它会先在当前路径下找,找不到的时候会自动去环境变量 path 的路径中查找,找到则执行该命令,找不到则在 DOS 窗口中提示错误信息。希望大家以后遇到类似的“在 DOS 命令窗口中输入某个命令时出现找不到命令”这样的问题都能够独立的解决。

1.4 java注释

Java 语言的注释包括三种方式,它们分别是:

第一种:单行注释,语法格式如下:

// 单行注释,两个正斜杠后面的内容被注释

第二种:多行注释,语法格式如下:

/* 这里的注释信息为多行注释: 
* 第 1 行注释信息
* 第 2 行注释信息
*/

第三种:javadoc 注释。

/**
* 这里的信息是 javadoc 注释
* @author 作者名字
* @version 版本号
* @since 自从哪个版本号开始就存在了
*/

注意:对于 javadoc 注释来说,这里的注释会被 JDK bin 目录下的 javadoc.exe 命令解析并生成帮助文档(生成帮助文档后期做项目的时候大家会接触到的)。

1.5 public class和class区别

我们可以看到创建了一个 A.java 源文件,在该文件中定义了三个类,分别是 B 类、C 类和D 类,使用 javac 命令编译之后生成了三个字节码,分别是B.class、C.class、D.class。通过以上的测试可以得出:一个 java 源文件中可以定义多个 class,并且在编译的时候一个 class 会对应编译生成一个 class 字节码文件。还有,public 的 class 可以没有。

接下来,我们在 A.java 源代码中继续定义一个“公开的类 E”,请看下图:

我们可以看到,定义公开的类 E 之后,再次编译,编译器报错了。并且提示的错误信息是:类 E 是公共的,应在名为 E.java 的文件中声明。换句话说在 A.java 文件中定义的公共的类的名字必须是 A,不能是其它名称。也间接说明在同一个 java 文件中公共的类只能有一个(注意:在同一个 java 文件中类名不能重名)。通过以上的测试可以得出:如果定义 public class 的类,只能定义一个,并且要求此类名必须和 java 源文件名保持一致。(这是规则记住就行,学计算机编程语言有很多知识点在学习的时候很难理解,只能靠记忆,随着后面内容的学习,大家会对以前困惑的知识点有所理解)。接下来,我们在每一个类的类体当中都定义 main 方法,都写上程序的入口,看看是否可以编译和运行:

在这里插入图片描述

我们可以看到,在每一个 class 中都可以编写 main 方法,想让程序从哪个入口进去执行则加载该类即可。

2 javaSE

2.1 基础语法

2.1.1 标识符

标识符可以标识类名,变量名,接口名,方法名

1. Java 标识符的命名规则
  a) 标识符是由,数字,字母,下划线和美元符号构成,其他符号不可以
  b) 必须以字母、下划线或美元符号开头,不能以数字开头
2. 关键字不能作为标识符
3. 标识符区分大小写
4. 标识符理论上没有长度限制

命名 Java 标识符,最好见名知意,可以使用驼峰标示

2.1.2 关键字

在 Java 中关键字都是小写的

2.1.3数据类型

数据类型

Java 总共有两种数据类型,主要有基本类型和引用类型,基本类型有 8 种,引用数据类型有3 种

数据类型
 基本数据类型:数值类型,整数型(byte,short,int,long),浮点型(float,double),字符类型(char),布尔类型(boolean,只能取值 true 和 false)
 
 引用数据类型:数组,类,接口

注意:String是Java中的字符串,字符串是一个特殊的对象,属于引用类型。

引用类型和基本数据类型的不同:

存储方式:基本数据类型数据存储发生在栈内存中;引用类型数据存储,分两步,在堆中保存数据,在栈中保存数据的地址(堆地址)

赋值:引用类型赋值,传递的是地址;基本类型赋值,传递的是值。

八种数据类型的取值范围

1个byte字节=8个bit比特,数据以字节为单位存储,以bit位进行传输

字符编码

对于以上的八种基本数据类型来说,其中七种类型 byte,short,int,long,float,double,boolean计算机表示起来是很容易的,因为这七种类型底层直接就是数字,十进制的数字和二进制之间有固定的转换规则,所以计算机可直接表示和处理。但是大家别忘了,除了以上的七种数据类型之外,还有一种类型叫做字符型 char,这个对于计算机来说表示起来就不是那么容易了,因为字符毕竟是现实世界当中的文字,而文字每个国家又是不同的,计算机是如何表示文字的呢?

实际上,起初的时候计算机只支持数字,因为计算机最初就是为了科学计算,随着计算机的发展,为了让计算机起到更大的作用,因此我们需要让计算机支持现实世界当中的文字,一些标准制定的协会就制定了字符编码(字符集),字符编码其实就是一张对照表,在这个对照表上描述了某个文字与二进制之间的对应关系。最初的时候美国标准协会制定了 ASCII 码,ASCII(American Standard Code for Information Interchange:美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是现今最通用的信息交换标准,并等同于国际标准 ISO/IEC 646。

ASCII 码采用 1 个字节编码,1 个字节可以表示 256 种不同的形式(前面说过了),对于英文来说这个足够了,因为英文单词就是由 26 个英文字母拼凑而成,大小写全部才 52 个,再加上数字和标点符号也不会超过 256 个。但 ASCII 码对于中文来说那就不够了,因为汉字不止 256个。

常见的 ASCII 码需要大家能够记住几个,在 ASCII 码中规定’a’对应 97,'b’对应 98,以此类推,'A’对应 65,'B’对应 66,以此类推,'0’字符对应 48,'1’字符对应 49,以此类推,这些常见的编码还是需要大家记住的。

在字符编码当中,有这样两个常见的术语需要大家了解一下:编码和解码,它们都是什么,我们拿字符’a’来解释一下:'a’是 97,97 对应的二进制是 01100001,那么从’a’到二进制 01100001的转换过程称为编码,从二进制 01100001 到’a’的转换过程称为解码。大家一定要注意:编码和解码要采用同一种字符编码方式(要采用同一个对照表),不然会出现乱码。这也是乱码出现的本质原因。

随着计算机的不断发展,为了让计算机支持更多国家的语言,国际标准组织又制定了ISO-8859-1 字符集,又被称为 latin-1,向上兼容 ASCII 码,仍不支持中文,主要支持西欧语言。再后来,计算机慢慢的开始支持简体中文、繁体中文、日本语、朝鲜语等,其中支持简体中文的字符集包括:GB2312 、GBK 、GB18030,它们的容量大小不同,其中 GB2312 < GBK < GB18030。支持繁体中文的是大五码 Big5 等。后来,在上世纪 90 年代初,国际组织制定了一种字符编码方式,叫做 Unicode 编码,这种编码方式统一了全球所有国家的文字,具体的实现包括:UTF-8,UTF-16,UTF-32 等。

Java 为了国际化,为了支持所有国家的语言,所以 Java 采用的编码方式为 Unicode 编码。例如字符’中’对应的 Unicode 码是’\u4e2d’。在实际开发中几乎所有的团队都会使用 Unicode 编码方式,因为这种方式更通用,兼容性更好。

通过本小节的学习,大家需要理解字符编码是什么,有什么作用,常见的 ASCII 码要知道一些,另外要理解什么是编码,什么是解码,要知道编码和解码采用的字符编码方式不同时会出现乱码,还要知道国际通用的编码方式为 ISO-8859-1,支持简体中文的编码方式包括GB2312、GBK、GB18030,而 Java 采用 unicode 编码,目前在实际的开发中大部分团队都会选择 UTF-8 的编码方式。

2.1.4 运算符

2.1.5 控制语句

2.1.6 常量

在程序运行期间,固定不变的量

分类

  1. 整数常量:直接写上的数字,没有小数点。例如:-100、200、0、300
  2. 浮点数常量:直接写上的数字,有小数点。例如:-2.3、3.14、0.0
  3. 字符常量:用单引号引起来的单个字符。例如:‘J’、‘A’、‘8’、‘中’
  4. 字符串常量:用双引号引起来,叫做字符串常量。例如:“Java”、“Hello”、“你我他”
  5. 布尔常量:只有两个取值。True(真)、False(假)。
  6. 空常量:代表没有任何数据。Null(空)
练习
public class Demo01Const {
 
 
    public static void main(String[] args) {
        // 字符串常量
        System.out.println("ABC");
        System.out.println("");                  // 字符串两个双引号中间的内容为空
        System.out.println("JAVA");
        
        // 整数常量
        System.out.println(10);
        System.out.println(-10);
        
        // 浮点数常量
        System.out.println(3.14);
        System.out.println(-2.3);
        
        // 字符常量
        System.out.println('A');
        System.out.println('4');
 
 
 
        // 错误写法
        // System.out.println('CB');    // 两个单引号中间必须有且仅有一个字符。
 
        // System.out.println('');         // 两个单引号中间必须有且仅有一个字符。
 
 
        // 布尔常量
        System.out.println(true);
        System.out.println(false);
        
        // 空常量。不能直接用来打印。
        // System.out.println(null);
    }
}

2.1.6 变量初步

java变量

局部变量
成员变量
	静态变量
	实例变量

java 中的变量包括:局部变量和成员变量,在方法体中声明的变量为局部变量,有效范围很小,只能在方法体中访问,方法结束之后局部变量内存就释放了,在内存方面局部变量存储在栈当中。在类体中定义的变量为成员变量,而成员变量又包括实例变量和静态变量,当成员变量声明时使用了 static 关键字,那么这种变量称为静态变量,没有使用 static 关键字称为实例变量,实例变量是对象级别的,每个对象的实例变量值可能不同,所以实例变量必须先创建对象,通过“引用”去访问,而静态变量访问时不需要创建对象,直接通过“类名”访问。实例变量存储在堆内存当中,静态变量存储在方法区当中。实例变量在构造方法执行过程中初始化,静态变量在类加载时初始化。

当一个类的所有对象的某个“属性值”不会随着对象的改变而变化的时候,建议将该属性定义为静态属性(或者说把这个变量定义为静态变量),静态变量在类加载的时候初始化,存储在方法区当中,不需要创建对象,直接通过“类名”来访问。

变量其实是 java 中的一个最基本的单元,也就是内存中的一块区域,Java 中的变量有四个基本属性:变量名,数据类型,存储单元和变量值

	变量名:合法的标识符
	变量的数据类型:可以是基本类型和引用类型(**必须包含类型**) 
	存储单元:存储单元大小是由数据类型决定的,如:int 为 4 个字节 32 位 
	变量值:在存储单元中放的就是变量值(如果是基本类型放的就是具体值,如果是引用类型放的是内存地址,如果 null,表示不指向任何对象)

变量的声明格式:
	类型 变量名;

【示例代码】

public class VarTest01 {
public static void main(String[] args) {
//定义变量
int age;
//输出变量
System.out.println(age);
} 
}

采用 javac 编译 VarTest01.java,会出现错误。出现错误的原因是:变量没有初始化,变量初始化的过程就是赋值,变量使用前必须初始化,

以下示例是正确的:

public class VarTest02 {
public static void main(String[] args) {
//定义变量,赋值为 100
int age = 100;
//输出变量
System.out.println(age);
} 
}

变量与属性的关系:变量是内存单元,SUN官方定义为属性是指get或者set方法名 去掉get或者set后,把剩余的部分首字母改为小写后,即为这个类的属性。

2.1.7 方法初步

[修饰符列表] 返回值类型 方法名(形式参数列表){

方法体;

}

2.2 面向对象

OOA/OOD/OOP:

① OOA:面向对象分析(Object-Oriented Analysis)

② OOD:面向对象设计(Object-Oriented Design)

③ OOP:面向对象编程(Object-Oriented Programming)

面向对象具有三大特征,这三大特征目前大家只需要记住,后面我会进行一一讲解:

① 封装(Encapsulation)

② 继承(Inheritance)

③ 多态(Polymorphism)

任何一门面向对象的编程语言都具备以上三大特征,例如:python、C#、java 等。

2.2.1 类和对象

[修饰符] class 类名 {
类体 = 属性 + 方法
}
对象

在 java 语言当中,当实例变量没有手动赋值,在创建对象的时候,也就是说在 new 的时候,系统会对实例变量默认赋值,

它们的默认值请参考下表:

java虚拟机内存管理过程

① 程序计数器:

  1. 概念:可以看做当前线程所执行的字节码的行号指示器。
  2. 特点:线程私有的内存

② java 虚拟机栈(重点):

  1. 概念:描述的是 java 方法执行的内存模型。(每个方法在执行的时候会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每个方法从调用直至完成的过程,就对应一个栈帧从入栈到出栈的过程。)
  2. 特 点 : 线 程 私 有, 生 命 周期 和 线 程 相同 。 这 个 区域 会 出 现 两种 异 常 :StackOverflowError 异常: 若线 程请求 的深 度大于 虚拟 机所允 许的 深度 ;OutOfMemoryError 异常:若虚拟机可以动态扩展,如果扩展是无法申请到足够的内存。

③ 本地方法栈:

  1. 概念:它与虚拟机栈所发挥的作用是相似的,区别是 java 虚拟机栈为执行 java 方

法服务,而本地方法栈是为本地方法服务。

  1. 特点:线程私有,也会抛出两类异常:StackOverflowError 和 OutOfMemoryError。

④ java 堆(重点):

  1. 概念:是被所有线程共享的一块区域,在虚拟机启动时创建。
  2. 特点:线程共享,存放的是对象实例(所有的对象实例和数组),GC 管理的主要区域。可以处于物理上不连续的内存空间。

⑤ 方法区(重点):

  1. 概念:存储已被虚拟机加载的类信息、常量、静态变量,即时编译器编译后的代码等数据。
  2. 特点:线程共享的区域,抛出异常 OutOfMemory 异常:当方法区无法满足内存分配需求的时候。

以上所描述内容,有看得懂的,也有看不懂的,例如:线程、本地方法等,这个需要大家在学习后面内容之后,返回来再看一看,那个时候你就全部明白了。针对于目前来说,大家必须要知道 java 虚拟机有三块主要的内存空间,分别是“虚拟机栈(后面简称栈)”、“方法区”、“堆区”,方法区存储类的信息,栈中存储方法执行时的栈帧以及局部变量,堆区中主要存储 new 出来的对象,以及对象内部的实例变量。其中垃圾回收器主要针对的是堆内存,方法区中最先有数据,因为程序执行之前会先进行类加载。栈内存活动最频繁,因为方法不断的执行并结束,不断的进行压栈弹栈操作。将目前阶段需要掌握的内存空间使用一张简单的图表示出来,这个图是大家需要掌握的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MeKJ3mGz-1688608650694)(https://gitee.com/yeminglan/typroa_images2/raw/master/img2/image-20230623223805291.png#id=zrHyx&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]

大概了解了 java 虚拟机内存分配之后,来看看以下代码在执行过程中,内存是如何变化的:

public class StudentTest {
public static void main(String[] args) {
    int i = 10;
    Student s1 = new Student();
} }

第一步:类加载

第二步:main 方法调用,给 main 方法分配栈帧(压栈)

第三步:执行 int i = 10,局部变量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NEEbjDbE-1688608650694)(https://gitee.com/yeminglan/typroa_images2/raw/master/img2/image-20230623224349416.png#id=yOLBb&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]

第四步:执行 new Student(),在堆中创建对象,同时初始化实例变量

第五步:将堆区中学生对象的内存地址赋值给局部变量 s1

注意:上图所描述内存图有些地方为了帮助大家更好的理解,有些位置画的不是很精确,随着后面内容的学习我们再进一步修改,目前上图已经够大家用了。上图中 i 变量和 s1 变量都是局部变量,都在栈内存当中,只不过 i 变量是基本数据类型 int, 而 s1 变量是引用数据类型 Student。上图中堆区当中的称为“对象”,该“对象”内部 no、name、age、sex 都是实例变量/属性,这些变量在 new对象的时候初始化,如果没有手动赋值,系统会赋默认值。上图堆区中“对象”创建完成之后,该对象在堆区当中的内存地址是:0x1111,程序中的“=”将 0x1111 这个堆内存地址赋值给 s1 变量,也就是说 s1 变量保存了堆内存对象的内存地址,我们对于这种变量有一种特殊的称呼,叫做“引用”。也就是说对于 Student s1 = new Student()代码来说,s1 不是对象,是一个引用,对象实际上是在堆区当中,s1 变量持有这个对象的内存地址。

java 中没有指针的概念(指针是 C 语言当中的机制),所以 java 程序员没有权利直接操作堆内存,只能通过“引用”去访问堆内存中的对象,例如:s1.no、s1.name、s1.sex、s1.age。

访问一个对象的内存,其实就是访问该对象的实例变量,而访问实例变量通常包括两种形式,要么就是读取数据,要么就是修改数据,例如:System.out.println(s1.no)这就是读取数据,s1.no = 100 这就是修改数据。

构造方法

构造方法是类中特殊的方法,通过调用构造方法来完成对象的创建,以及对象属性的初始

化操作。

构造方法怎么定义,请看以下的语法格式:

[修饰符列表] 构造方法名(形式参数列表){
构造方法体; 
}

① 构造方法名和类名一致。

② 构造方法用来创建对象,以及完成属性初始化操作。

③ 构造方法返回值类型不需要写,写上就报错,包括 void 也不能写。

④ 构造方法的返回值类型实际上是当前类的类型。

⑤ 一个类中可以定义多个构造方法,这些构造方法构成方法重载。

怎么调用构造方法呢,语法格式是:new 构造方法名(实际参数列表);接下来,看以下代码:

一个类当中可以定义多个构造方法,构造方法是支持重载机制的,具体调用哪个构造方法,那要看调用的时候传递的实际参数列表符合哪个构造方法了。构造方法虽然在返回值类型方面不写任何类型,但它执行结束之后实际上会返回该对象在堆内存当中的内存地址,这个时候可以定义变量接收对象的内存地址,这个变量就是之前所学的“引用”,请看以下代码:

public class DateTest {
public static void main(String[] args) {
    System.out.println("main begin");
    Date time1 = new Date();
    System.out.println(time1);
    Date time2 = new Date(2008);
    System.out.println(time2);
    Date time3 = new Date(2008 , 8);
    System.out.println(time3);
    Date time4 = new Date(2008 , 8 , 8);
    System.out.println(time4);
    System.out.println("main over");
} }

public class Date {
    int year; //年
    int month; //月
    int day; //日
    //构造方法(无参数构造方法)
    public Date(){
    System.out.println("Date 类无参数构造方法执行");
    }
    //构造方法(有参数构造方法)
    public Date(int year1){
    System.out.println("带有参数 year 的构造方法");
    }
    //构造方法(有参数构造方法)
    public Date(int year1 , int month1){
    System.out.println("带有参数 year,month 的构造方法");
    }
    //构造方法(有参数构造方法)
    public Date(int year1 , int month1 , int day1){
    System.out.println("带有参数 year,month,day 的构造方法");
} }



public class DateTest {
public static void main(String[] args) {
    System.out.println("main begin");
    Date time1 = new Date();
    System.out.println(time1.year + "年" + time1.month + "月" + time1.day + 
    "日");
    Date time2 = new Date(2008);
    System.out.println(time2.year + "年" + time2.month + "月" + time2.day + 
    "日");
    Date time3 = new Date(2008 , 8);
    System.out.println(time3.year + "年" + time3.month + "月" + time3.day +
                       "日");
    Date time4 = new Date(2008 , 8 , 8);
    System.out.println(time4.year + "年" + time4.month + "月" + time4.day + 
    "日");
    System.out.println("main over");
} }

为什么无论通过哪个构造方法创建 Date 对象,最终的结果都是“0 年 0 月 0 日”呢?这是因为所有的构造方法在执行过程中没有给对象的属性手动赋值,系统则自动赋默认值,实际上大部分情况下我们需要在构造方法中手动的给属性赋值,这本来就是构造方法的主要的职责,要不然重载多次构造方法就没有意义了,以上的代码应该这样写,请看:

public class Date {
    int year; //年
    int month; //月
    int day; //日
    public Date(){
    }
    public Date(int year1){
    year = year1;
    }
    public Date(int year1 , int month1){
    year = year1;
    month = month1;
    }
    public Date(int year1 , int month1 , int day1){
    year = year1;
    month = month1;
    day = day1;
} }

通过以上内容的学习得知,构造方法的作用是专门用来创建对象同时给属性赋值的,它的语法很简单,比普通方法还要简单,因为构造方法名和类名一致,还不需要写返回值类型,使用 new 就可以调用了。在一个类当中可以同时定义多个构造方法,它们之间构成重载关系。这样就做到了在 java 中你想要什么就 new什么,每一次 new都会在堆内存中创建对象,并且对象内部的实例变量被初始化了。一定要注意,实例变量没有手动赋值的时候系统会默认赋值,但不管是手动赋值还是系统赋默认值,都是在构造方法执行的时候才会进行赋值操作,类加载的时候并不会初始化实例变量的空间,那是因为实例变量是对象级别的变量,没有对象,哪来实例变量,这也是为什么实例变量不能采用“类名”去访问的原因。

public class DateTest {
    public static void main(String[] args) {
    System.out.println(Date.year);
} }

实例变量为引用

String 是一个 class,和我们定义的类没有区别,它和基本数据类型还是不一样的(int i = 10,i 变量是基本数据类型,i 变量中存储的就是 10),也就是说String name = “zhangsan”,实际上 name 变量中存储的并不是”zhangsan”这个字符串,因为 name是一个引用,那 name 中必然存储的是”zhangsan”字符串对象的内存地址。因为之前我们说过引用的概念,什么是引用:引用就是一个变量,只不过该变量中存储的是 java 对象的内存地址。也就是说,以前所讲内容中使用内存图描述字符串的时候都是有偏差的。我们来修正一下,

请看代码:

public class Student {
    int no;
    String name;
    int age;
    boolean sex;
    public Student(int _no,String _name,int _age,boolean _sex){
    no = _no;
    name = _name;
    age = _age;
    sex = _sex;
} }


public class StudentTest {
public static void main(String[] args) {
    Student s1 = new Student(110 , "zhangsan" , 20 , true);
    System.out.println("学号:" + s1.no);
    System.out.println("姓名:" + s1.name);
    System.out.println("年龄:" + s1.age);
    System.out.println("性别:" + (s1.sex ? "男" : "女"));
} }

2.2.2 封装

一般是实体类的封装,属性私有

//封装的使用
public class Student{//类将属性和方法进行了封装
  private String name;
  //将成员变量设置为私有,达到封装属性的目的
  public String getName()
  {
    return name;//为私有属性提供getter方法,用于获取私有属性的值
  }
   public String setName()
  {
    this.name =name;//为私有属性提供setter方法,用于修改私有属性的值
  }
  
}
不封装存在的问题
public class MobilePhone {
    //电压:手机正常电压在 3~5V
    double voltage;
}


public class MobilePhoneTest {
public static void main(String[] args) {
    MobilePhone phone = new MobilePhone();
    phone.voltage = 3.7;
    System.out.println("手机电压 = " + phone.voltage);
    phone.voltage = 100;
    System.out.println("手机电压 = " + phone.voltage);
} }

以上程序 MobilePhone 类未进行封装,其中的电压属性 voltage 对外暴露,在外部程序当中可以对 MobilePhone 对象的电压 voltage 属性进行随意访问,导致了它的不安全,例如手机的正常电压是 3~5V,但是以上程序已经将手机电压设置为 100V,这个时候显然是要出问题的,但这个程序编译以及运行仍然是正常的,没有出现任何问题,这是不对的。

怎么封装

为了保证内部数据的安全,这个时候就需要进行封装了,封装的第一步就是将应该隐藏的数据隐藏起来,起码在外部是无法随意访问这些数据的,怎么隐藏呢?我们可以使用 java 语言中的 private 修饰符,private 修饰的数据表示私有的,私有的数据只能在本类当中访问。请看程序:

public class MobilePhone {
    //电压:手机正常电压在 3~5V
    private double voltage;
}


public class MobilePhoneTest {
public static void main(String[] args) {
    MobilePhone phone = new MobilePhone();
    phone.voltage = 3.7;
    System.out.println("手机电压 = " + phone.voltage);
    phone.voltage = 100;
    System.out.println("手机电压 = " + phone.voltage);
} }

通过以上的测试,手机对象的电压属性确实受到了保护,在外部程序中无法访问了。但从当前情况来看,voltage 属性有点儿太安全了,一个对象的属性无法被外部程序访问,自然这个数据就没有存在的价值了。所以这个时候就需要进入封装的第二步了:对外提供公开的访问入口,让外部程序统一通过这个入口去访问数据,我们可以在这个入口处设立关卡,进行安全控制,这样对象内部的数据就安全了。对于“一个”属性来说,我们对外应该提供几个访问入口呢?通常情况下我们访问对象的某个属性,不外乎读取(get)和修改(set),所以对外提供的访问入口应该有两个,这两个方法通常被称为 set 方法和 get 方法(请注意:set 和 get 方法访问的都是某个具体对象的属性,不同的对象调用 get 方法获取的属性值不同,所以 set 和 get 方法必须有对象的存在才能调用,这样的方法定义的时候不能使用 static 关键字修饰,被称为实例方法。实例方法必须使用“引用”的方式调用。还记得之前我们接触的方法都是被 static 修饰的,这些方法直接采用“类名”的方式调用,而不需要创建对象,在这里显然是不行的)。

有的读者可能会有这样的疑问:构造方法中已经给属性赋值了,为什么还要提供 set 方法呢?注意了同学们,这是两个完全不同的时刻,构造方法中给属性赋值是在创建对象的时候完成的,当对象创建完毕之后,属性可能还是会被修改的,后期要想修改属性的值,这个时候就必须调用 set 方法了。

2.2.3 继承

特性

① B类继承 A类,则称 A类为超类(superclass)、父类、基类,B类则称为子类(subclass)、派生类、扩展类。

② java 中的继承只支持单继承,不支持多继承,C++中支持多继承,这也是 java 体现简单性的一点,换句话说,java 中不允许这样写代码:class B extends A,C{ }。

③ 虽然 java 中不支持多继承,但有的时候会产生间接继承的效果,例如:class C extends B,class B extends A,也就是说,C 直接继承 B,其实 C 还间接继承 A。

④ java 中规定,子类继承父类,除构造方法和被 private 修饰的数据不能继承外,剩下都可以继承。

⑤ java 中的类没有显示的继承任何类,则默认继承 Object 类,Object 类是 java 语言提供的根类(老祖宗类),也就是说,一个对象与生俱来就有 Object 类型中所有的特征。

⑥ 继承也存在一些缺点,例如:CreditAccount 类继承 Account 类会导致它们之间的耦合度非常高,Account 类发生改变之后会马上影响到 CreditAccount 类。

作用

继承解决了代码复用的问题(代码复用就是代码的重复利用),这是继承机制最基本的作用。

继承的作用中除了可以让代码复用之外,还有非常重要的两个作用,那就是有了继承之后才会衍生出方法的覆盖和多态机制。

2.2.4 多态

多态(Polymorphism)属于面向对象三大特征之一,它的前提是封装形成独立体,独立体之间存在继承关系,从而产生多态机制。多态是同一个行为具有多个不同表现形式或形态的能力。现实中,比如我们按下 F1 键这个动作:

如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;
如果当前在 Word 下弹出的就是 Word 帮助;
如果当前在 Windows 下弹出的就是 Windows 帮助和支持。

多态就是“同一个行为”发生在“不同的对象上”会产生不同的效果。那么在java 中多态是如何体现的呢?

在 java 中允许这样的两种语法出现,一种是向上转型(Upcasting),一种是向下转型(Downcasting),向上转型是指子类型转换为父类型,又被称为自动类型转换,向下转型是指父类型转换为子类型,又被称为强制类型转换。

向上转型

在 java 语言中有这样的一个规定,无论是向上转型还是向下转型,两种类型之间必须要有继承关系,没有继承关系情况下进行向上转型或向下转型的时候编译器都会报错。

public class Animal {
public void move(){
    System.out.println("Animal move!");
} }


public class Cat extends Animal{
    //方法覆盖
    public void move(){
    System.out.println("走猫步!");
    }
    //子类特有
    public void catchMouse(){
    System.out.println("抓老鼠!");
} }


public class Bird extends Animal{
    //方法覆盖
    public void move(){
    System.out.println("鸟儿在飞翔!");
    }
    //子类特有
    public void sing(){
    System.out.println("鸟儿在歌唱!");
} }


public class Test01 {
public static void main(String[] args) {
    //创建 Animal 对象
    Animal a = new Animal();
    a.move();
    //创建 Cat 对象
    Cat c = new Cat();
    c.move();
    //创建鸟儿对象
    Bird b = new Bird();
    b.move();
} }

其实在 java 中还允许这样写代码,请看:

public class Test02 {
public static void main(String[] args) {
    Animal a1 = new Cat();
    a1.move();
    Animal a2 = new Bird();
    a2.move();
} }

以上程序演示的就是多态,多态就是“同一个行为(move)”作用在“不同的对象上”会有不同的表现结果。java 中之所以有多态机制,是因为 java 允许一个父类型的引用指向一个子类型的对象。也就是说允许这种写法:Animal a2 = new Bird(),因为 Bird is a Animal 是能够说通的。其中 Animal a1 = new Cat()或者 Animal a2 = new Bird()都是父类型引用指向了子类型对象,都属于向上转型(Upcasting),或者叫做自动类型转换。

我来解释一下这段代码片段【Animal a1 = new Cat();a1.move(); 】:java 程序包括编译和运行两个阶段,分析 java 程序一定要先分析编译阶段,然后再分析运行阶段,在编译阶段编译器只知道 a1 变量的数据类型是 Animal,那么此时编译器会去 Animal.class字节码中查找 move()方法,发现 Animal.class 字节码中存在 move()方法,然后将该 move()方法绑定到 a1 引用上,编译通过了,这个过程我们可以理解为“静态绑定”阶段完成了。紧接着程序开始运行,进入运行阶段,在运行的时候实际上在堆内存中 new的对象是 Cat 类型,也就是说真正在 move 移动的时候,是 Cat 猫对象在移动,所以运行的时候就会自动执行 Cat 类当中的 move()方法,这个过程可以称为“动态绑定”。但无论是什么时候,必须先“静态绑定”成功之后才能进入“动态绑定”阶段。

向下转型
public class Test03 {
public static void main(String[] args) {
    Animal a = new Cat();
    a.catchMouse();
} }

有人认为 Cat 猫是可以抓老鼠的呀,为什么会编译报错呢?那是因为“Animal a = new Cat();”在编译的时候,编译器只知道 a 变量的数据类型是 Animal,也就是说它只会去Animal.class 字节码中查找 catchMouse()方法,结果没找到,自然“静态绑定”就失败了,编译没有通过。就像以上描述的错误信息一样:在类型为 Animal 的变量 a 中找不到方法catchMouse()。

那么,假如说我就是想让这只猫去抓老鼠,以上代码应该如何修改呢?请看以下代码:

public class Test04 {
public static void main(String[] args) {
    //向上转型
    Animal a = new Cat();
    //向下转型:为了调用子类对象特有的方法
    Cat c = (Cat)a;
    c.catchMouse();
} }

我们可以看到直接使用 a 引用是无法调用 catchMouse()方法的,因为这个方法属于子类 Cat中特有的行为,不是所有 Animal 动物都可以抓老鼠的,要想让它去抓老鼠,就必须做向下转型(Downcasting),也就是使用强制类型转换将 Animal 类型的 a 引用转换成 Cat 类型的引用c(Cat c = (Cat)a;),使用 Cat 类型的 c 引用调用 catchMouse()方法。通过这个案例,可以得出:只有在访问子类型中特有数据的时候,需要先进行向下转型。其实向下转型就是用在这种情形之下。那么向下转型会存在什么风险吗?请看以下代码:

public class Test05 {
public static void main(String[] args) {
    Animal a = new Bird();
    Cat c = (Cat)a;
} }

以上代码可以编译通过吗?答案是可以的,为什么呢?那是因为编译器只知道 a 变量是Animal 类型,Animal 类和 Cat 类之间存在继承关系,所以可以进行向下转型(前面提到过,只要两种类型之间存在继承关系,就可以进行向上或向下转型),语法上没有错误,所以编译通过了。但是运行的时候会出问题吗,因为毕竟 a 引用指向的真实对象是一只小鸟。来看运行结果:

以上的异常是很常见的 ClassCastException,翻译为类型转换异常,这种异常通常出现在向下转型的操作过程当中,当类型不兼容的情况下进行转型出现的异常,之所以出现此异常是因为在程序运行阶段 a 引用指向的对象是一只小鸟,然后我们要将一只小鸟转换成一只猫,这显然是不合理的,因为小鸟和猫之间是没有继承关系的。为了避免这种异常的发生,建议在进行向下转型之前进行运行期类型判断,这就需要我们学习一个运算符了,它就是 instanceof。

instanceof 运算符的语法格式是这样的:

(引用 instanceof 类型)

instanceof 运算符的运算结果是布尔类型,可能是 true,也可能是 false,假设(c instanceof Cat)结果是 true 则表示在运行阶段 c 引用指向的对象是 Cat 类型,如果结果是 false 则表示在运行阶段 c 引用指向的对象不是 Cat 类型。有了 instanceof 运算符,向下转型就可以这样写了:

public class Test05 {
public static void main(String[] args) {
    Animal a = new Bird();
    if(a instanceof Cat){
    Cat c = (Cat)a;
    c.catchMouse();
} } }

以上程序运行之后不再发生异常,并且什么也没有输出,那是因为 if 语句的条件并没有成立,因为在运行阶段 a 引用指向的对象不是 Cat 类型,所以(a instanceof Cat)是 false,自然就不会进行向下转型了,也不会出现 ClassCastException 异常了。在实际开发中,java 中有这样一条默认的规范需要大家记住:在进行任何向下转型的操作之前,要使用 instanceof 进行判断,这是一个很好的编程习惯。就像下面的代码:

public class Test05 {
public static void main(String[] args) {
    Animal a = new Bird();
    if(a instanceof Cat){
    Cat c = (Cat)a;
    c.catchMouse();
    }else if(a instanceof Bird){
    Bird b = (Bird)a;
    b.sing();
} } }

到这里大家理解什么是多态了吗?其实多态存在的三个必要条件分别是:

① 继承

② 方法覆盖

③ 父类型引用指向子类型对象

多态显然是离不开方法覆盖机制的,多态就是因为编译阶段绑定父类当中的方法,程序运行阶段自动调用子类对象上的方法,如果子类对象上的方法没有进行重写,这个时候创建子类对象就没有意义了,自然多态也就没有意义了,只有子类将方法重写之后调用到子类对象上的方法产生不同效果时,多态就形成了。实际上方法覆盖机制和多态机制是捆绑的,谁也离不开谁,多态离不开方法覆盖,方法覆盖离开了多态也就没有意义了。

接下里就来看看之前没有解决的问题:方法覆盖主要是说实例方法,静态方法为什么不谈方法覆盖?——静态方法的执行压根和对象无关,既然和对象无关那就表示和多态无关,既然和多态无关,也就是说静态方法的“覆盖”是没有意义的,所以通常我们不谈静态方法的覆盖。

2.2.5 重载与重写(Override)

重载

只要在同一个类当中,方法名相同,参数列表不同(类型、个数、顺序),即构成方法重载。

重写

从父类中继承过来的方法已经不够子类使用了,此时就需要使用方法覆盖机制。

构成方法覆盖的条件:

① 方法覆盖发生在具有继承关系的父子类之间,这是首要条件;

② 覆盖之后的方法与原方法具有相同的返回值类型、相同的方法名、相同的形式参数列表;

另外,在使用方法覆盖的时候,需要有哪些注意事项:

① 由于覆盖之后的方法与原方法一模一样,建议在开发的时候采用复制粘贴的方式,不建议手写,因为手写的时候非常容易出错,比如在 Object 类当中有 toString()方法,该方法中的 S 是大写的,在手写的时候很容易写成小写 tostring(),这个时候你会认为 toString()方法已经被覆盖了,但由于方法名不一致,导致最终没有覆盖,这样就尴尬了;

② 私有的方法不能被继承,所以不能被覆盖;

③ 构造方法不能被继承,所以也不能被覆盖;

④ 覆盖之后的方法不能比原方法拥有更低的访问权限,可以更高(学习了访问控制权限修饰符之后你就明白了);

⑤ 覆盖之后的方法不能比原方法抛出更多的异常,可以相同或更少(学习了异常之后就明白了);

⑥ 方法覆盖只是和方法有关,和属性无关;

⑦ 静态方法不存在覆盖(不是静态方法不能覆盖,是静态方法覆盖意义不大,学习了多态机制之后就明白了);

2.2.6 关键字

static,super,this,final

1)static

static 是 java 语言中的关键字,表示“静态的”,它可以用来修饰变量、方法、代码块等,修饰的变量叫做静态变量,修饰的方法叫做静态方法,修饰的代码块叫做静态代码块。在 java语言中凡是用 static 修饰的都是类相关的,不需要创建对象,直接通过“类名”即可访问,即使使用“引用”去访问,在运行的时候也和堆内存当中的对象无关。

静态变量
java变量

局部变量
成员变量
	静态变量
	实例变量

java 中的变量包括:局部变量和成员变量,在方法体中声明的变量为局部变量,有效范围很小,只能在方法体中访问,方法结束之后局部变量内存就释放了,在内存方面局部变量存储在栈当中。在类体中定义的变量为成员变量,而成员变量又包括实例变量和静态变量,当成员变量声明时使用了 static 关键字,那么这种变量称为静态变量,没有使用 static 关键字称为实例变量,实例变量是对象级别的,每个对象的实例变量值可能不同,所以实例变量必须先创建对象,通过“引用”去访问,而静态变量访问时不需要创建对象,直接通过“类名”访问。实例变量存储在堆内存当中,静态变量存储在方法区当中。实例变量在构造方法执行过程中初始化,静态变量在类加载时初始化。

当一个类的所有对象的某个“属性值”不会随着对象的改变而变化的时候,建议将该属性定义为静态属性(或者说把这个变量定义为静态变量),静态变量在类加载的时候初始化,存储在方法区当中,不需要创建对象,直接通过“类名”来访问。

静态代码块

静态代码块的语法格式是这样的:

类{
    //静态代码块
    static{
    java 语句; 
} 
}

静态代码块在类加载时执行,并且只执行一次。开发中使用不多,但离了它有的时候还真是没法写代码。静态代码块实际上是 java 语言为程序员准备的一个特殊的时刻,这个时刻就是类加载时刻,如果你想在类加载的时候执行一段代码,那么这段代码就有的放矢了。例如我们要在类加载的时候解析某个文件,并且要求该文件只解析一次,那么此时就可以把解析该文件的代码写到静态代码块当中了。

一个类当中可以编写多个静态代码块(尽管大部分情况下只编写一个),并且静态代码块遵循自上而下的顺序依次执行,所以有的时候放在类体当中的代码是有执行顺序的(大部分情况下类体当中的代码没有顺序要求,方法体当中的代码是有顺序要求的,方法体当中的代码必须遵守自上而下的顺序依次逐行执行),另外静态代码块当中的代码在 main 方法执行之前执行,这是因为静态代码块在类加载时执行,并且只执行一次。

静态方法

方法在什么情况下会声明为静态的呢?方法实际上描述的是行为动作,我认为当某个动作在触发的时候需要对象的参与,这个方法应该定义为实例方法,例如:每个玩篮球的人都会打篮球,但是你打篮球和科比打篮球最终的效果是不一样的,显然打篮球这个动作存在对象差异化,该方法应该定义为实例方法。再如:每个高中生都有考试的行为,但是你考试和学霸考试最终的结果是不一样的,一个上了“家里蹲大学”,一个上了“清华大学”,显然这个动作也是需要对象参与才能完成的,所以考试这个方法应该定义为实例方法。在实际的开发中,“工具类”当中的方法一般定义为静态方法,因为工具类就是为了方便大家的使用,将方法定义为静态方法,比较方便调用,不需要创建对象,直接使用类名就可以访问。

2)this

this 是 java 语言中的一个关键字,它存储在内存的什么地方呢,一起来看一段程序:

public class Customer {
    private String name;
    public Customer(){
    }
    public Customer(String _name){
    name = _name;
    }
    public void setName(String _name){
    name = _name;
    }
    public String getName(){
    return name;
} }


public class CustomerTest {
public static void main(String[] args) {
    Customer jack = new Customer("jack");
    Customer rose = new Customer("rose");
} }

this 可以看做一个变量,它是一个引用,存储在 Java 虚拟机堆内存的对象内部,this 这个引用保存了当前对象的内存地址指向自身,任何一个堆内存的 java 对象都有一个 this,也就是说创建 100 个 java 对象则分别对应 100 个 this。通过以上的内存图,可以看出“jack 引用”保存的内存地址是 0x1111,对应的“this 引用”保存的内存地址也是 0x1111,所以“jack 引用”和“this 引用”是可以划等号的。也就是说访问对象的时候 jack.name 和 this.name 是一样的,都是访问该引用所指向对象的 name 属性。this 指向“当前对象”,也可以说 this 代表“当前对象”,this 可以使用在实例方法中以及构造方法中,语法格式分别为“this.”和“this(…)”。this 不能出现在带有 static 的方法当中。

this不能出现在 static的方法当中,这是为什么呢?首先static 的方法,在调用的时候是不需要创建对象的,直接采用“类名”的方式调用,也就是说static 方法执行的过程中是不需要“当前对象”参与的,所以 static 的方法中不能使用 this,因为 this 代表的就是“当前对象”。

this在实例方法中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1532NtDV-1688608650697)(https://gitee.com/yeminglan/typroa_images2/raw/master/img2/image-20230624004439925.png#id=E4c53&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]

最终的结论是,this 不能出现在 static 的方法中,可以出现在实例方法中,代表当前对象,大部分情况下 this 都是可以省略的,只有当在实例方法中区分局部变量和实例变量的时候不能省略。

this在构造方法中

this 还有另外一种用法,使用在构造方法第一行(只能出现在第一行,这是规定,记住就行),通过当前构造方法调用本类当中其它的构造方法,其目的是为了代码复用。调用时的语法格式是:this(实际参数列表),请看以下代码:

public class Date {
  private int year;
  private int month;
  private int day;
  //业务要求,默认创建的日期为 1970 年 1 月 1 日
  public Date(){
  this(1970 , 1, 1);
  }
  public Date(int year,int month,int day){
  this.year = year;
  this.month = month;
  this.day = day;
  }
  public int getYear() {
  return year;
  }
  public void setYear(int year) {
  this.year = year;
  }
  public int getMonth() {
  return month;
  }
  public void setMonth(int month) {
  this.month = month;
  }
  public int getDay() {
  return day;
  }
  public void setDay(int day) {
  this.day = day;
} }

3)super

严格来说,super 其实并不是一个引用,它只是一个关键字,super 代表了当前对象中从父类继承过来的那部分特征。this 指向一个独立的对象,super 并不是指向某个“独立”的对象,假设张大明是父亲,张小明是儿子,有这样一句话:大家都说张小明的眼睛、鼻子和父亲的很像。那么也就是说儿子继承了父亲的眼睛和鼻子特征,那么眼睛和鼻子肯定最终还是长在儿子的身上。假设this指向张小明,那么 super 就代表张小明身上的眼睛和鼻子。换句话说 super 其实是 this 的一部分。如下图所示:张大明和张小明其实是两个独立的对象,两个对象内存方面没有联系,super 只是代表张小明对象身上的眼睛和鼻子,因为这个是从父类中继承过来的,在内存方面使用了 super 关键字进行了标记

public class SuperTest01 extends Object{
    //实例方法
    public void doSome(){
    System.out.println(this);
    System.out.println(super);
} }

通过以上的测试,可以看出 this 是可以单独使用的引用,但 super 无法输出,编译器提示super 要使用必须是“super.xxx”,显然 super 并不指向独立的对象,并不是保存某个对象的内存地址。

this 和 super 都是无法使用在静态方法当中的。

super在实例方法中

父类和子类中有同名实例变量或者有同名的实例方法,想在子类中访问父类中的实例变量或实例方法,则super 是不能省略的,其它情况都可以省略。

super在构造方法中

super 使用在构造方法中,语法格式为:super(实际参数列表),这行代码和“this(实际参数列表)”都是只允许出现在构造方法第一行。

4)final
final关键字的意思:

是不能改变的意思,用来修饰类、方法、变量。

用法:

Java提供了final关键字主要用来修饰一些不可改变的内容。

final修饰类,类不能被继承

final修辞方法,方法不能被重写

被final修饰的变量只能赋值一次

其余含义:

 final 修饰的变量必须显示初始化

 如果修饰的引用,那么这个引用只能指向一个对象,也就是说这个引用不能再次赋值,但被指向的对象是可以修改的

 构造方法不能被 final 修饰

 会影响 JAVA类的初始化:final 定义的静态常量调用时不会执行 java 的类初始化方法,也就是说不会执行 static 代码块等相关语句,这是由 java 虚拟机规定的。我们不需要了解的很深,有个概念就可以了。

final static

final static 和static final没有什么区别都可以使用。

static修饰的属性强调它们只有一个,final修饰的属性表明是一个常数,final static 修饰的属性表示一旦给值,就不可以修改,并且可以通过类名访问。

3,基础知识延伸

1,字节与字符

1个byte字节=8个bit比特,数据以字节为单位存储,以bit位进行传输
ASCII码:一个英文字母(a,A)占1个字节,一个中文汉字占2个字节,
UTF-8码:一个英文字母或者英文标点占1个字节,一个中文汉字或中文标点占3个字节
Unicode:一个英文字母或者英文标点、一个中文汉字或者中文标点占4个字节,(根据所有字符中单个字符占字节的最大值确定的,如果一个英文字母占不到4个字节也会分配4个字节,所有会出现浪费的情况,导致文件过大)

证明如下

    System.out.println("a".getBytes("ASCII").length);//1
    System.out.println("A".getBytes("ASCII").length);//1
    System.out.println("中".getBytes("ASCII").length);//2
 
    System.out.println("a".getBytes("UTF-8").length);//1
    System.out.println("A".getBytes("UTF-8").length);//1
    System.out.println(",".getBytes("UTF-8").length);//1
    System.out.println("发".getBytes("UTF-8").length);//3
    System.out.println(",".getBytes("UTF-8").length);//3
 
    System.out.println("a".getBytes("Unicode").length);//4
    System.out.println("A".getBytes("Unicode").length);//4
    System.out.println(",".getBytes("Unicode").length);//4
    System.out.println("发".getBytes("Unicode").length);//4
    System.out.println(",".getBytes("Unicode").length);//4

2,类型转换

  • 自动类型转换,也叫隐式转换
  • 强制类型转换,也叫显式转换

3,三元运算符

条件表达式b?x:y;,先计算条件b,然后进行判断。如果b的值为true,计算x的值,运算结果为x的值;否则,计算y的值,运算结果为y的值。

4,关键字

null

null本身不是对象
null是java的一个关键字,并不是一个对象,下面通过代码进行说明

public class Demo
{
    public static void main(String[] args)
    {
        if(null instanceof java.lang.Object)
            System.out.println("yes");
        else
            System.out.println("no");
    }
}

测试结果为no
null本身所具有的二义性
null本身拥有两层意思:
null代表值不存在
null本身表示值
举例说明。以下代码可以说明两种情况:第一种是Map中key对应的值为null;第二种是Map中不存在与key对应的value

   Map.get(key) == null

null的使用情况
通常在不确定变量的值的时候使用null,通过以下代码说明:

People student = null;
if (flag == true)
    student = new Student();
else
    student = new Teacher();
System.out.println("the name is : " + student.getName());

此处为什么要用null:java变量使用原则是先声明,然后初始化,再使用。如果不使用null进行初始化,这几句代码就不会通过编译。因为变量的初始化是在if…else…当中的
容器类型与null
List:允许重复元素,可以加入任意多个null。
Set:不允许重复元素,最多可以加入一个null。
Map:Map的key最多可以加入一个null,value字段没有限制。
数组:基本类型数组,定义后,如果不给定初始值,则java运行时会自动给定值。引用类型数组,不给定初始值,则所有的元素值为null。
null的其它作用
判断一个引用类型是否为null,用“==”
释放内存:让一个非null的引用变量指向null。这样之前引用指向的对象就不再被使用,此时等待JVM启动垃圾回收机制,回收内存

5,jdk常用开发包

java.lang,此包 Java 语言标准包,使用此包中的内容无需 import 引入

java.sql,提供了 JDBC 接口类

java.util,提供了常用工具类

java.io,提供了各种输入输出流

6,包

包其实就是目录,特别是项目比较大,java 文件特别多的情况下,我们应该分目录管理,在 java中称为分包管理,包名称通常采用小写

/*
1、包最好采用小写字母
2、包的命名应该有规则,不能重复,一般采用公司网站逆序,
如:com.bjpowernode.项目名称.模块名称
com.bjpowernode.exam
*/
//package 必须放到 所有语句的第一行,注释除外
package com.bjpowernode.exam;
public class PackageTest01 {
    public static void main(String[] args) {
    System.out.println("Hello Package!!!");
} }

7,访问控制权限

java 访问级别修饰符主要包括:private protected 和 public,可以限定其他类对该类、属性和方法的使用权限

注意以上对类的修饰只有:public 和 default,内部类除外

8,内部类

在一个类的内部定义的类,称为内部类,内部类主要分类:

 实例内部类

 局部内部类

 静态内部类

实例内部类

 创建实例内部类,外部类的实例必须已经创建

 实例内部类会持有外部类的引用

 实例内部不能定义 static 成员,只能定义实例成员

public class InnerClassTest01 {
    private int a;
    private int b;
    InnerClassTest01(int a, int b) {
    this.a = a;
    this.b = b;
    }
  
  
    //内部类可以使用 private 和 protected 修饰
    private class Inner1 {
    int i1 = 0;
    int i2 = 1;
    int i3 = a;
    int i4 = b;
    //实例内部类不能采用 static 声明
    //static int i5 = 20;
    }
  
  
    public static void main(String[] args) {
    InnerClassTest01.Inner1 inner1 = new InnerClassTest01(100, 200).new Inner1();
    System.out.println(inner1.i1);
    System.out.println(inner1.i2);
    System.out.println(inner1.i3);
    System.out.println(inner1.i4);
} }

静态内部类

 静态内部类不会持有外部的类的引用,创建时可以不用创建外部类

 静态内部类可以访问外部的静态变量,如果访问外部类的成员变量必须通过外部类的实例访问

public class InnerClassTest02 {
    static int a = 200;
    int b = 300;
  
    static class Inner2 {
    //在静态内部类中可以定义实例变量
    int i1 = 10;
    int i2 = 20;
    //可以定义静态变量
    static int i3 = 100;
    //可以直接使用外部类的静态变量
    static int i4 = a;
    //不能直接引用外部类的实例变量
    //int i5 = b;
    //采用外部类的引用可以取得成员变量的值
    int i5 = new InnerClassTest02().b;
    }
  
    public static void main(String[] args) {
    InnerClassTest02.Inner2 inner = new InnerClassTest02.Inner2();
    System.out.println(inner.i1);
} }

局部内部类

局部内部类是在方法中定义的,它只能在当前方法中使用。和局部变量的作用一样局部内部类和实例内部类一致,不能包含静态成员

public class InnerClassTest03 {
    private int a = 100;
    //局部变量,在内部类中使用必须采用 final 修饰
    public void method1(final int temp) {
      
    class Inner3 {
    int i1 = 10;
    //可以访问外部类的成员变量
    int i2 = a;
    int i3 = temp;
    } 
      
    //使用内部类
    Inner3 inner3 = new Inner3();
    System.out.println(inner3.i1);
    System.out.println(inner3.i3);
    }
  
  
    public static void main(String[] args) {
    InnerClassTest03 innerClassTest03 = new InnerClassTest03();
		innerClassTest03.method1(300);
} }

匿名内部类

是一种特殊的内部类,该类没有名字

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值