当前位置:   article > 正文

java零基础从入门到精通(全)

java

前言

这部分知识一共有4个文档
第一个是当前这个文档

  1. java零基础从入门到精通(全)
  2. javaSE从入门到精通的二十万字总结(一)
  3. javaSE从入门到精通的二十万字总结(二)
  4. javaSE从入门到精通的二十万字总结(三)

java优势

  • 健壮性(JVM(C++语言写的一个虚拟的计算机)、GC(垃圾回收机制))
  • 多线程并发
  • 可移植性/跨平台

java体系

  • JavaSE:标准版
  • JavaEE:企业版
  • JavaME:微型版

1. 入门知识

1.1 JDK/JRE/JVM区别

JVM是不能独立安装的。
JRE和JDK都是可以独立安装的

JDK = JRE + 开发工具集(例如Javac编译工具等)
JRE = JVM + Java SE 标准类库

  • JRE:java运行环境
  • JDK:Java开发工具箱
  • JVM:java虚拟机

JVM的内存结构中三块比较重要的内存空间

  1. 方法区:存储代码片段,存储xxx.class字节码文件,类加载器将代码加载到这
  2. 堆内存:面向对象
  3. 栈内存:所需要的内存空间(局部变量)

此处的jdkbin目录下有

  • javac.exe 负责编译
  • java.exe 负责运行

1.2 编译与运行理解

编译操作(命令行javac进行编译)

  1. java代码(源代码,扩展名为XX.java)无法被jvm所识别,必须要先经过编译,将普通的文本代码变成字节码
  2. 单个java源文件可以生成多个class文件,最终执行的是class文件

运行操作(命令行java进行运行)

  1. 将字节码(扩展名为XX.class)可以放到任何平台执行,实现跨平台的运行
  2. 字节码不是二进制码

程序运行原理

  1. 启动JVM(java虚拟机)
  2. JVM启动“类加载器classloader”寻找class文件(相应的字节码文件,默认当前路径)
  3. 将其加载到 java 虚拟机的方法区当中,开始调用 main 方法,main 方法被调用的瞬间,会给 main 方法在“栈”内存中分配所属的活动空间,此时发生压栈动作,main 方法的活动空间处于栈底
  4. 方法只定义不去调用的话,只是把它的代码片段存储在方法区当中,java 虚拟
    机是不会在栈内存当中给该方法分配活动空间的,只有在调用的瞬间,java 虚拟机才会在“栈内存”当中给该方法分配活动空间
  5. main先调用,故结束的时候main也是最后一个结束,故程序也就结束了

1.3 类体函数细节

  1. 任何一个程序都必须要有一个主函数入口main
  2. main函数的public static void main(String[] args) 除了args可以改名字外,其他都不能动
  3. 类体中应该是方法函数,而不是直接的java语句
  4. 方法应该放在类体中而不是类体外
  5. 一个java源文件可以多个class文件源文件中,只要有一个class类,一定会生成class文件
  6. public类不是必须要有,但有的话,必须要和源文件名保持一致才可以是public,也就是public类只能有一个
class A{
    public static void main(String[] args) {
        System.out.println("a");
    }
}

class B{
    public static void main(String[] args) {
        System.out.println("B");
    }
}

class C{
    public static void main(String[] args) {
        System.out.println("C");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 可以有多个class,但如果要定义一个public class ,只能有一个,而且必须与文件名相同
  • 任何一个 class 中都可以设定程序入口
  • 虽然一个 java 源文件可以定义多个 class,实际上这是不规范的,比较规范的写法是一个 java 源文件中只定义一个 class

2. 语法

2.1 标识符与关键字

有权利命名的单词都是标识符

  1. 标识符可以为类名,方法名,变量名,接口名,常量名等
  2. 只能由数字、字母(包括中文)、下划线、美元符号组成,不能有其他符号
  3. 标识符不能以数字开头,不能有空格
  4. 标识符不可以用关键字
  5. 标识符区分大小写

关键字具有特殊含义,不能用作标识符

byteshortintlongfloat
doublebooleanchariffor
elsewhiledocontinuebreak
publicdefaultprotectedprivatethrow
throwstrycatchfinalfinally
caseswitchtransientpackagenew
staticthisabstractstrictfpnative
gotosuperextendsimplementsimport
instanceofreturnsynchronizedvoidconst
classenumassertinterfacevolatile

具体每个关键字的定义

关键字大致含义
abstract表明类或者成员方法具有抽象属性
assert断言,用来进行程序调试
boolean基本数据类型之一,声明布尔类型的关键字
break提前跳出一个块
byte基本数据类型之一,字节类型
case用在 switch 语句之中,表示其中的一个分支
catch用在异常处理中,用来捕捉异常
char基本数据类型之一,字符类型
class声明一个类
const保留关键字,没有具体含义
continue回到一个块的开始处
default默认,例如,用在 switch 语句中,表明一个默认的分支
do用在 do-while 循环结构中
double基本数据类型之一,双精度浮点数类型
else用在条件语句中,表明当条件不成立时的分支
enum枚举
extends表明一个类型是另一个类型的子类型,这里常见的类型有类和接口
final表示不可变,最终的
finally用于处理异常情况,用来声明一个基本肯定会被执行到的语句块
float基本数据类型之一,单精度浮点数类型
for一种循环结构的引导词
goto保留关键字,没有具体含义
if条件语句的引导词
implements表明一个类实现了给定的接口
import表明要访问指定的类或包
instanceof用来测试一个对象是否是指定类型的实例对象
int基本数据类型之一,整数类型
interface接口
long基本数据类型之一,长整数类型
native用来声明一个方法是由与计算机相关的语言(如 C/C++语言)实现的
new用来创建新实例对象
package
private一种访问控制方式:私用模式
protected一种访问控制方式:保护模式
public一种访问控制方式:共用模式
return从成员方法中返回数据
short基本数据类型之一,短整数类型
static表明具有静态属性
strictfp用来声明 FP_strict(单精度或双精度浮点数)表达式遵循IEEE 754 算术规范
super表明当前对象的父类型的引用或者父类型的构造方法
switch分支语句结构的引导词
synchronized表明一段代码需要同步执行
this指向当前实例对象的引用
throw抛出一个异常
throws声明在当前定义的成员方法中所有需要抛出的异常
transient声明不用序列化的成员域
try尝试一个可能抛出异常的程序块
void声明当前成员方法没有返回值
volatile表明两个或者多个变量必须同步地发生变化
while用在循环结构中

2.2 变量与数据类型

内存中的最基本的存储单元,存数据用,且数据可变

1.变量的三要素:数据类型、变量名、值
2.变量注意事项

  1. 同一个作用域内不可重名
  2. 声明需要赋值,但声明和赋值可以不同步
  3. 注意变量的作用域

3.变量的声明:数据类型 变量名;
4.变量的分类:在方法体当中声明的就是局部变量,在方法体外面声明的就是成员变量
5.变量的作用域:出了大括号就不是

用来声明变量,且不同的数据类型分配不同大小的空间
根据数据类型可以分为基本数据类型引用型数据类型

  1. 基本数据类型包括整数型(byte,short,int,long),浮点型(float,double
    ),布尔型(boolean),字符型(char)
  2. 引用型数据类型在java中除了基本数据类型之外,剩下的都是引用数据类型,比如string字符串

此处主要介绍主要介绍基本数据类型的不同,是占用空间的不同

类型占用字节数量(byte)
byte1
short2
int4
long8
float4
double8
boolean1 (1byte的1或0,00000001(true)或00000000(false))
char2

类型转换的时候需要遵循的规则

  • 除 boolean 类型不能转换,剩下七种类型之间都可以进行转换
  • 小容量向大容量转换,从小到大的排序为:byte < short(char) < int < long < float < double
  • 大容量转换成小容量,为强制性转换,可能会丢失精度
  • 多种数据类型混合运算,先转换为大的类型格式

补充运算符

类型说明
算术运算符+ - * / % ++ –
关系运算符> >= < <= == !=
逻辑运算符&| ! &&||
赋值运算符= += -= *= /= %=
三目运算符布尔表达式 ? 表达式1 : 表达式2
字符串连接运算符+

补充:如何接收用户的输入

Scanner s = new Scanner(System.in);
// 接收整数
int i = s.nextInt()
// 接收字符串
String str = s.next();
  • 1
  • 2
  • 3
  • 4
  • 5

2.3 控制语句

控制语句有选择、循环以及转向

  • 选择语句有if和switch结构
  • 循环语句有for、while和do while结构
  • 转向语句有break、continue和return结构

1.if结构语句的四种写法

  1. 对于一个if语句来说,只要有1个分支执行,整个if语句结束
  2. 结果为true时,分支才会执行
if(布尔表达式){
}
				
if(布尔表达式){
}else{
}

if(布尔表达式){
}else if(布尔表达式){
}else if(布尔表达式){
}else if(布尔表达式){
}else if(布尔表达式){
}

if(布尔表达式){
}else if(布尔表达式){
}else if(布尔表达式){
}else if(布尔表达式){
}else if(布尔表达式){
}else{
}

if语句嵌套:
if(布尔表达式){ //前提条件
	if(布尔表达式){
		if(布尔表达式){
							
		}else{
							
	}
}
}else{
					
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

2。switch结构语句

  • switch 虽然只能探测 int 类型,但是也可以将 byte,short,char 类型放到小括号当中,因 为这些类型会自动转换成 int 类型
  • switch 语句当中 case 是可以进行合并的
switch(){ //值允许是String、int,(byte,short,char可以自动转换int)
case1: case 值x:
	java语句;
	break;
case2:
	java语句;
	break;
case3:
	java语句;
	break;
default:
	java语句;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

3.for结构语句

for(初始化表达式;条件表达式;更新表达式){
	循环体;
}
for(int i = 0; i < 10; i++){
	System.out.println(i);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

4.while结构语句
执行次数:0~N次。

while(布尔表达式){
循环体;
}		
  • 1
  • 2
  • 3

5.do…while结构语句
执行次数:1~N次

do{
循环体;
}while(布尔表达式);
  • 1
  • 2
  • 3

6.break结构语句
默认情况下,终止离它最近的循环
也可以通过标识符的方式,终止指定的循环

for(int i = 0; i < 10; i++){
				if(i == 5){
					break;
				}
				code1;
				code2;
				code3;
				code4;
				....
			}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

7.continue结构语句
终止当前“本次”循环,直接跳入下一次循环继续执行

for(int i = 0; i < 10; i++){
				if(i == 5){
					continue;
				}
				code1;
				code2;
				code3;
				code4;
				....
			}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

8.return结构语句
用来终止一个方法的执行,区分于break,break 用来终止循环,

3. 方法

3.1 定义

函数方法的封装
完成某个特定功能的并且可以被重复利用的代码片段

[修饰符列表] 返回值类型 方法名(形式参数列表){
		方法体; 
	}
  • 1
  • 2
  • 3
  • 修饰符列表:可选项,public static 等
  • 返回值类型:基本数据类型和引用数据类型,如果一个方法执行结束之后不准备返回任何数据,则返回值类型必须写 void
  • 方法名:合法的标识符
  • 方法体:java语句

关于返回值类型的一一对应注意事项

  • 返回值类型与返回值要匹配
  • 在同一个“域”中,“return”语句后面是不能写任何代码的,因为它无法执行到
//示列1:执行错误结果,因为必须与返回值类型匹配,缺少返回值return
public static int method1(){
}

//示列2:执行错误结果,只要碰到return,方法函数就会结束
public static int method1(){
	return 1;
	System.out.println("hello world!");
}

//示列3:执行错误结果,缺少返回值信息,因为if语句可能执行也可能不执行
public static int method1(){
	boolean flag = true;
	if(flag){
		return 1;
	}
}

//示列4:执行成功,if与else必定有一个分支可以执行成立
public static int method1(){
	boolean flag = true;
	if(flag){
		return 1;
	}else{
		return 0;
	} 
}

//以上示列总结:执行成功,以上代码也可这样执行
public static int method1(){
	boolean flag = true;
	if(flag){
		return 1;
	}
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

如何调用该方法
如果在同一个类中可以省略,不在同一个类中不能省略类名

//第一个类
public class MethodTest03 {
	public static void main(String[] args) {
		sumInt(100, 200); //“类名.”可以省略
		sumDouble(1.0, 2.0);//“类名.”可以省略
		//doOther(); //编译报错
		Other.doOther(); //“类名.”不能省略
	}
	public static void sumInt(int x , int y){
	System.out.println(x + "+" + y + "=" + (x + y));
	}
	public static void sumDouble(double a , double b){
	System.out.println(a + "+" + b + "=" + (a + b));
	} 
}

//第二个类
public class Other{
	public static void doOther(){
		System.out.println("Other doOther...");
	} 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

3.2 方法重载

在同一个类当中,如果多个功能是相似的,可以考虑将它们的方法名定义的一致,使用方法重载机制,所谓重载机制是一个类中定义多个同名的方法,但要求每个方法具有不同的参数的类型或参数的个数

需要满足的条件有
和返回值类型(void/int)无关,和修饰符列表(public static)无关

public static void dosome(int i){
}

//不构成重载
public static int dosome(int i){
}

//不构成重载
void dosome(int i){
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

即应该满足条件如下

  • 在同一个类当中
  • 方法名相同
  • 参数列表不同:个数不同算不同,顺序不同算不同,类型不同也算不同

不同参数类型的重载代码示例

public static void main(String[] args) {
	int x1 = 10;
	int x2 = 20;
	int retValue1 = sum(x1 , x2);
	System.out.println(x1 + "+" + x2 + "=" + retValue1);
	long y1 = 10L;
	long y2 = 20L;
	long retValue2 = sum(y1 , y2);
	System.out.println(y1 + "+" + y2 + "=" + retValue2);
	double z1 = 10.0;
	double z2 = 20.0;
	double retValue3 = sum(z1, z2);
	System.out.println(z1 + "+" + z2 + "=" + retValue3);
}
public static int sum(int a , int b){
	return a + b;
}
public static long sum(long a , long b){
	return a + b;
}
public static double sum(double a , double b){
	return a + b;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

同功能不同参数的代码示列(比较两个数和比较三个数的重载)

public class Test {
	public static void main(String[] args) {
		System.out.println(getBiger(10, 20));
		System.out.println(getBiger(5,6,4));
}
public static int getBiger(int a , int b){
	return a > b ? a : b;
}
public static int getBiger(int a , int b , int c){
	return getBiger(a , b) > c ? getBiger(a , b) : c; 
} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

3.3 方法递归

递归的使用要注意结束条件是否正确

  • 递归其实就是方法在执行的过程中调用了另一个方法,而另一个方法则是自己本身
  • 使用递归的时候,必须添加结束条件,没有结束条件,会发生栈内存溢出错误

示列程序累加1到5

public class RecursionTest03 {
	public static void main(String[] args) {
		int n = 5;
		int result = accumulate(n);
		System.out.println("1 到" + n + "的和是:" + result);
	}
	public static int accumulate(int n){
		if(n == 1){
		return 1;
		}
	return n + accumulate(n - 1);
	} 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在这里插入图片描述

4. 类和对象

  1. 类是现实世界当中具有共同特征的事物进行抽象形成的模板或概念。而对象是实际存在的个体
  2. 通过类可以创建对象,对象又被称为实例(instance),这个过程也可以称为实例化
  3. 类 = 属性 + 方法,而属性描述的是状态,方法描述的是行为动作

4.1 类的定义

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

//例如
public class Student {
//学号
int no;
//姓名
String name;
//年龄
int age;
//性别
boolean sex;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

以上程序当中 no、name、age、sex 都是属性,它们都是成员变量中的实例变量,所谓实例变量就是对象级别的变量,这些属性要想访问,必须先创建对象才能访问,不能直接通过类去访问,因为每一个学生的学号都是不一样的。没有学生对象,谈何学号!

4.2 对象的创建

类定义之后,就可以使用类这个“模板”来创造“对象”

public class StudentTest {
	public static void main(String[] args) {
		//创建一个学生对象
		Student s1 = new Student();
		//再创建一个学生对象
		Student s2 = new Student();
		//以上代码其实和这行代码差不多	
	} 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

4.3 对象的使用

Student 类可以创建很多学生对象,通过类实例化两个对象,那必然会有不同的学号,并且通过类实列的对象使用,没有初始化,默认系统赋值

数据类型默认值
byte0
short0
int0
long0L
float0.0f
double0.0
booleanfalse
char\u0000
引用类型null
public class StudentTest {
	public static void main(String[] args) {
		//创建一个学生对象
		Student s1 = new Student();
		//再创建一个学生对象
		Student s2 = new Student();
		//以上代码其实和这行代码差不多
		int i = 10;
		int no1 = s1.no;
		System.out.println("学号:" + no1);
		String name1 = s1.name;
		System.out.println("姓名:" + name1);
		int age1 = s1.age;
		System.out.println("年龄:" + age1);
		boolean sex1 = s1.sex;
		System.out.println("性别:"+ sex1);
		int no2 = s2.no;
		System.out.println("学号:" + no2);
		String name2 = s2.name;
		System.out.println("姓名:" + name2);
		int age2 = s2.age;
		System.out.println("年龄:" + age2);
		boolean sex2 = s2.sex;
		System.out.println("性别:" + sex2);
		
		//当然,也可以不使用 no1,no2 这样的变量接收
		System.out.println("学号 = " + s1.no);
		System.out.println("姓名 = " + s1.name);
		System.out.println("年龄 = " + s1.age);
		System.out.println("性别 = " + s1.sex);
		System.out.println("学号 = " + s2.no);
		System.out.println("姓名 = " + s2.name);
		System.out.println("年龄 = " + s2.age);
		System.out.println("性别 = " + s2.sex);
		} 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

4.4 构造方法

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

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

需要注意的事项

  1. 构造方法名和类名一致
  2. 构造方法用来创建对象,以及完成属性初始化操作
  3. 构造方法返回值类型不需要写,写上就报错,包括 void 也不能写
  4. 构造方法的返回值类型实际上是当前类的类型
  5. 一个类中可以定义多个构造方法,这些构造方法构成方法重载

//示列1:构造函数
package com.gaokaoli;
public class Date {

        int year; //年
        int month; //月
        int day; //日
        //构造方法(无参数构造方法)
        public Date(){
        System.out.println("Date 类无参数构造方法执行");
    } 
}
package com.gaokaoli;
public class student {
    public static void main(String[] args) {
        System.out.println("main begin");
        new Date();
        System.out.println("main over");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

  • 当一个类没有显示的定义任何构造方法的时候,系统默认提供无参数构造方法,当显示的定义构造方法之后,系统则不再提供无参数构造方法
//示列2:错误代码,没有默认构造函数
public class Date {
    int year; //年
    int month; //月
    int day; //日
    //构造方法(有参数构造方法)
    public Date(int year){
    System.out.println("带有参数 year 的构造方法");
} }

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

//示列3:结合无参构造函数和有参构造函数
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");
        new Date();
        new Date(2008);
        new Date(2008 , 8);
        new Date(2008 , 8 , 8);
        System.out.println("main over");
        }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 一个类当中可以定义多个构造方法,构造方法是支持重载机制 的,具体调用哪个构造方法,那要看调用的时候传递的实际参数列表符合哪个构造方法,构造方法虽然在返回值类型方面不写任何类型,但它执行结束之后实际上会返回该对象在堆内存当中的内存地址,这个时候可以定义变量接收对象的内存地址
//示列4:引用地址内存的意思
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");
		}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

输出这些引用的结果是“Date@xxxxx”,对于这个结果目前可以把它等同看做是对象的内存地址(严格来说不是真实的对象内存地址)。通过这个引用就可以访问对象的内存了
在这里插入图片描述


通过引用地址调用对象的示列:

//示列5:通过引用地址访问对象
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");
    } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

因为所有的构造方法在执行过程中没有给对象的属性手动赋值,系统则自动赋默认值,实际上大部分情况下我们需要在构造方法中手动的给属性赋值
在这里插入图片描述


通过构造函数初始化对象,给属性值赋值:
没有赋值的对象都是默认初始化为系统值

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;
    } }


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");
        }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

截图如下:
在这里插入图片描述
注意事项:
在这里插入图片描述


任何“引用”当中存储一定是对象的内存地址,“引用”不一定只是以局部变量的形式存在

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

public class Vip {
    int id;
    String name;
    Date birth;
    public Vip(){
    }
    public Vip(int _id,String _name,Date _birth){
        id = _id;
        name = _name;
        birth = _birth;
    } }
public class VipTest {
    public static void main(String[] args) {
        Date d = new Date(1983 , 5 , 6);
        Vip v = new Vip(123 , "jack" , d);
        System.out.println("编号=" + v.id);
        System.out.println("姓名=" + v.name);
        System.out.println("生日=" + v.birth.year + "年" + v.birth.month + "月" +
                v.birth.day + "日");
    } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

结果截图:
在这里插入图片描述


此处比较难
具体调用传递的是什么值?
基本变量与引用型变量的内存传递
基本变量是值,引用型变量是内存地址值
引用数据类型由于传递的这个值是 java 对象的内存地址,所以会导致两个引用指向同一个堆内存中的 java 对象,通过任何一个引用去访问堆内存当中的对象

public class AssignmentTest {
    public static void main(String[] args) {
//基本数据类型
        int a = 10;
        int b = a; //a 赋值给 b,a 把什么给了 b?
        a++;
        System.out.println("a = " + a);
        System.out.println("b = " + b);
//引用数据类型
        Bird bird1 = new Bird("polly");
        //bird1 赋值给 bird2,bird1 把什么给了 bird2?
        Bird bird2 = bird1;
        System.out.println("bird1's name = " + bird1.name);
        System.out.println("bird2's name = " + bird2.name);
        bird1.name = "波利";
        System.out.println("bird1's name = " + bird1.name);
        System.out.println("bird2's name = " + bird2.name);
    } }

class Bird {
    String name;
    public Bird(){}
    public Bird(String _name){
        name = _name;
    } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

具体的代码截图:
在这里插入图片描述

5. 面向对象

在讲解面向对象的前提中,先讲解面向过程,面向过程是一步一步按着步骤执行,一般的面向过程是从上往下步步求精。面向对象主要是把事物给对象化,对象包括属
性与行为

面向对象能有效提高编程的效率,通过封装技术,可以像搭积木的一样快速开发出一个全新的系统。面向对象将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性

面向对象具有三大特征

  • 封装
  • 继承
  • 多态

5.1 封装

指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系

封装的作用:

  • 保证内部结构的安全(并非人人可调用)
  • 屏蔽复杂,暴露简单

封装的步骤:

  1. 属性私有化(使用private关键字进行修饰。)
  2. 对外提供简单的操作入口
    (1个属性对外提供两个set和get方法。外部程序只能通过set方法修改,只能通过get方法读取,可以在set方法中设立关卡来保证数据的安全性)

规范中要求 set 方法名是 set + 属性名(属性名首字母大写),get 方法名是 get + 属性名(属性名首字母大写)。其中 set 方法有一个参数,用来给属性赋值,set 方法没有返回值。而 get 方法不需要参数,返回值类型是该属性所属类型

set 方法和 get 方法都不带 static 关键字,不带 static 关键字的方法称为实例方法,这些方法调用的时候需要先创建对象,然后通过“引用”去调用这些方法,实例方法不能直接采用类名的方式调用
因为 set 和 get 方法操作的是实例变量,“不同的对象”调用 get 方法最终得到的数据是不同的,方法是一个对象级别的方法,不能直接采用“类名”调用,必须先创建对象,再通过“引用”去访问

public void setYear(int year) {
        this.year = year;
    }
public int getYear() {
      return year;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

例如完整程序

public class Product {
    private int no;
    private String name;
    private double price;
    public Product(){
    }
    public Product(int _no , String _name , double _price){
        no = _no;
        name = _name;
        price = _price;
    }
    public int getNo() {
        return no;
    }
    public void setNo(int _no) {
        no = _no;
    }
    public String getName() {
        return name;
    }
    public void setName(String _name) {
        name = _name;
    }
    public double getPrice() {
        return price;
    }
    public void setPrice(double _price) {
        price = _price;
    } }

public class ProductTest {
    public static void main(String[] args) {
        Product p1 = new Product(10000 , "小米 5S" , 2000.0);
        System.out.println("商品编号:" + p1.getNo());
        System.out.println("商品名称:" + p1.getName());
        System.out.println("商品单价:" + p1.getPrice());
        p1.setNo(70000);
        p1.setName("小米 6");
        p1.setPrice(2100.0);
        System.out.println("商品编号:" + p1.getNo());
        System.out.println("商品名称:" + p1.getName());
        System.out.println("商品单价:" + p1.getPrice());
    } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

补充static关键字

带有 static 关键字的方法是静态方法,不需要创建对象,直接通过“类”来调用。对于没有 static 关键字的方法被称为实例方法,这些方法执行时要求必须先创建对象,然后通过“引用”的方式来调用。而对于封装来说,setter 和 getter 方法都是访问对象内部的属性,所setter 和 getter 方法在定义时不能添加 static 关键字

5.2 继承

  • 继承时子类继承父类的特征和行为,使得子类对象(实例)具有父类的属性,或子类从父类继承方法,使得子类具有与父类相同的行为
  • 父类更通用,子类更具体
  • 子类会具有父类的一般特性也会具有自身的特性
class 类名 extends 父类名{
类体; }
  • 1
  • 2

如果两个函数相同,定义相同的功能会十分冗余

//第一个函数
public class Account { //银行账户类
    //账号
    private String actno;
    //余额
    private double balance;
    //账号和余额的 set 和 get 方法
    public String getActno() {
        return actno;
    }
    public void setActno(String actno) {
        this.actno = actno;
    }
    public double getBalance() {
        return balance;
    }
    public void setBalance(double balance) {
        this.balance = balance;
    } }

//第二个函数
public class CreditAccount { //信用账户类
    //账号
    private String actno;
    //余额
    private double balance;
    //账号和余额的 set 和 get 方法
    public String getActno() {
        return actno;
    }
    public void setActno(String actno) {
        this.actno = actno;
    }
    public double getBalance() {
        return balance;
    }
    public void setBalance(double balance) {
        this.balance = balance;
    }
    //信誉度(特有属性)
    private double credit;
    //信誉度的 set 和 get 方法
    public double getCredit() {
        return credit;
    }
    public void setCredit(double credit) {
        this.credit = credit;
    } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

将程序修改为继承的方式

public class Account { //银行账户类
    //账号
    private String actno;
    //余额
    private double balance;
    //账号和余额的 set 和 get 方法
    public String getActno() {
        return actno;
    }
    public void setActno(String actno) {
        this.actno = actno;
    }
    public double getBalance() {
        return balance;
    }
    public void setBalance(double balance) {
        this.balance = balance;
    } }


public class CreditAccount extends Account{ //信用账户类
    //信誉度(特有属性)
    private double credit;
    //信誉度的 set 和 get 方法
    public double getCredit() {
        return credit;
    }
    public void setCredit(double credit) {
        this.credit = credit;
    } }


public class AccountTest {
    public static void main(String[] args) {
        CreditAccount act = new CreditAccount();
        act.setActno("111111111");
        act.setBalance(9000.0);
        System.out.println(act.getActno() + "信用账户,余额" + act.getBalance()
                + "元");
    } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

对继承的理解

  1. 有了继承之后才会衍生出方法的覆盖和多态机制
  2. java 中的继承只支持单继承,不支持多继承,但支持间接继承,例如:class C extends B,class B extends A。C++中支持多继承
  3. 子类继承父类,除构造方法和被 private 修饰的数据不能继承外,剩下都可以继承
  4. 继承也有缺点,耦合度比较高,如果父类改变会影响到子类

扩充:父类子类的执行顺序理解

public class Test {
    public static void main(String[] args) {
        new H2();
    } }
class H1{
    {
        System.out.println("父类代码块");
    }
    public H1(){
        System.out.println("父类构造");
    }
    static{
        System.out.println("父类静态代码块");
    } }
class H2 extends H1{
    static{
        System.out.println("子类静态代码块");
    }
    {
        System.out.println("子类代码块");
    }
    public H2(){
        System.out.println("子类构造");
    } }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

执行的结果显示为:
先调用父类的静态代码块,子类静态代码块
在执行父类的代码以及构造函数从上往下,最后执行子类的代码块和构造函数
在这里插入图片描述

5.3 覆盖和多态

  1. 只要在同一个类当中,方法名相同,参数列表不同(类型、个数、顺序),即构成方法重载
  2. 而覆盖是只有当从父类中继承过来的方法无法满足当前子类业务需求的时候,需要将父类中继承过来的方法进行覆盖
  3. 多态是同一个行为具有多个不同表现形式或形态的能力

覆盖的条件以及注意事项

  • 方法覆盖发生在具有继承关系的父子类之间
  • 覆盖之后的方法与原方法具有相同的返回值类型、相同的方法名、相同的形式参数列
  • 私有的方法、构造函数不能被继承
  • 覆盖之后的方法不能比原方法拥有更低的访问权限,不能比原方法抛出更多的异常
  • 静态方法不存在覆盖

覆盖的程序代码示列:
ChinaPeople 和 AmericaPeople 将从 People 类中继承过来的 speakHi()方法进行
了覆盖,我们也看到了当 speakHi()方法发生覆盖之后,子类对象会调用覆盖之后的方法,不会再去调用之前从父类中继承过来的方法

public class People {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void speakHi(){
        System.out.println(this.name + "和别人打招呼!");
    } }


public class ChinaPeople extends People {
    public void speakHi(){
        System.out.println("你好,我叫"+this.getName()+",很高兴认识你!");
    } }

public class AmericaPeople extends People {
    public void speakHi(){
        System.out.println("Hi,My name is "+this.getName()+",Nice to meet
                you!");
    } }
    }

public class PeopleTest {
    public static void main(String[] args) {
        ChinaPeople cp = new ChinaPeople();
        cp.setName("张三");
        cp.speakHi();
        AmericaPeople ap = new AmericaPeople();
        ap.setName("jackson");
        ap.speakHi();
    } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

无论是向上转型还是向下转型,两种类型之间必须要有继承关系,没有继承关系情况下进行向上转型或向下转型的时候编译器都会报错。向上转型是指子类型转换为父类型,又被称为自动类型转换,向下转型是指父类型转换为子类型,又被称为强制类型转换

多态存在的三个必要条件

  • 继承
  • 方法覆盖
  • 父类型引用指向子类型对象
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();
        //等同于Animal c = new Cat();
        c.move();
//创建鸟儿对象
        Bird b = new Bird();
        等同于Animal b = new Bird();
        b.move();
    } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

以上程序演示的就是多态,多态就是“同一个行为(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();
} }

/*因为“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()方法
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

只有在访问子类型中特有数据的时候,需要先进行向下转型。

//编译没错,执行报错
public class Test05 {
	public static void main(String[] args) {
		Animal a = new Bird();
		Cat c = (Cat)a;
	} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

没有继承关系的函数,为了避免这种异常的发生,建议在进行向下转型之前进行运行期类型判断,引入instanceof运算符。

instanceof 运算符的运算结果是布尔类型,可能是 true,也可能是 false,假设(c instanceof Cat)结果是 true 则表示在运行阶段 c 引用指向的对象是 Cat 类型,如果结果是 false 则表示在运行阶段 c 引用指向的对象不是 Cat 类型

//正确案例
public class Test05 {
    public static void main(String[] args) {
        Animal a = new Bird();
        if(a instanceof Cat){
            Cat c = (Cat)a;
            c.catchMouse();
        } } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在进行任何向下转型的操作之前,要使用 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();
        } } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

同样的示列:以多态为主
请设计一个系统,描述主人喂养宠物的场景,首先在这个场景当中应该有“宠物对象”,宠物对象应该有一个吃的行为,另外还需要一个“主人对象”,主人对象应该有一个喂的行为

先展示不是多态的代码结构,做以区分对比

//宠物狗
public class Dog {
    String name;
    public Dog(String name){
        this.name = name;
    }
//吃的行为
public void eat(){
    System.out.println(this.name + "在啃肉骨头!");
} }

//主人
public class Master {
    //喂养行为
    public void feed(Dog dog){
//主人喂养宠物,宠物就吃
        System.out.println("主人开始喂食儿");
        dog.eat();
        System.out.println("主人喂食儿完毕");
    } }

public class Test {
    public static void main(String[] args) {
//创建狗对象
        Dog dog = new Dog("二哈");
//创建主人对象
        Master master = new Master();
//喂养
        master.feed(dog);
    } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

代码编译执行可以通过,但是之后如果添加额外的功能或者是额外的对象,需要全部修改
不符合多态的定义以及结构
下面展示多态的结构

//宠物类
public class Pet {
    String name;
    //吃的行为
    public void eat(){
    } }

//宠物猫
public class Cat extends Pet{
    public Cat(String name){
        this.name = name;
    }
    //吃的行为
    public void eat(){
        System.out.println(this.name + "在吃鱼!");
    } }

//宠物狗
public class Dog extends Pet{
    public Dog(String name){
        this.name = name;
    }
    //吃的行为
    public void eat(){
        System.out.println(this.name + "在啃肉骨头!");
    } }

//主人
public class Master {
    //喂养行为
    public void feed(Pet pet){
//主人喂养宠物,宠物就吃
        System.out.println("主人开始喂食儿");
        pet.eat();
        System.out.println("主人喂食儿完毕");
    } }

public class Test {
    public static void main(String[] args) {
//创建狗对象
        Dog dog = new Dog("二哈");
//创建主人对象
        Master master = new Master();
//喂养
        master.feed(dog);
//创建猫对象
        Cat cat = new Cat("汤姆");
//喂养
        master.feed(cat);
    } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

结果如图所示
在这里插入图片描述

总结

本章节的难点就是对多态机制的理解,多态的代码表现是父类型引用指向子类型对象,对于多态的理解一定要分为编译阶段和运行阶段来进行分析,编译阶段只是看父类型中是否存在要调用的方法,如果父类中不存在,则编译器会报错,编译阶段和具体 new 的对象无关。但是在运行阶段就要看底层具体 new的是哪个类型的子对象了,new的这个子类型对象可以看做“真实对象”,自然在运行阶段就会调用真实对象的相关方法。例如代码:Animal a = new Cat(); a.move();,在编译阶段编译器只能检测到a的类型是Animal,所以一定会去Animal类中找move()方法,如果 Animal 中没有 move()方法,则编译器会报错,即使 Cat 中有 move()方法,也会报错,因为编译器只知道 a 的类型是 Animal 类,只有在运行的时候,实际创建的真实对象是 Cat,那么在运行的时候就会自动调用 Cat 对象的 move()方法。这样就可以达到多种形态,也就是说编译阶段一种形态,运行阶段的时候是另一种形态

6. 关键字

挑选容易混淆的关键字进行讲解
可查看我上一篇文章
super关键字与this关键字区别

6.1 static

  • static 是 java语言中的关键字,表示“静态的”,它可以用来修饰变量、方法、代码块等,修饰的变量叫做静态变量,修饰的方法叫做静态方法,修饰的代码块叫做静态代码块。java语言中凡是用 static 修饰的都是类相关的,不需要创建对象,直接通过“类名”即可访问,即使使用“引用”去访问,在运行的时候也和堆内存当中的对象无关
  • 实例变量是对象级别的,每个对象的实例变量值可能不同,所以实例变量必须先创建对象,通过“引用”去访问,而静态变量访问时不需要创建对象,直接通过“类名”访问。实例变量存储在堆内存当中,静态变量存储在方法区当中。实例变量在构造方法执行过程中初始化,静态变量在类加载时初始化
  • 当一个类的所有对象的某个“属性值”不会随着对象的改变而变化的时候,建议将该属性定义为静态属性(或者说把这个变量定义为静态变量),静态变量在类加载的时候初始化,存储在方法区当中,不需要创建对象,直接通过“类名”来访问
{
	//静态代码块
	static{
	java 语句; 
	} 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

静态代码块从上往下执行

public class StaticTest01 {
    //静态代码块
    static{
        System.out.println(2);
    }
//静态代码块
static{
    System.out.println(1);
}
    //main 方法
    public static void main(String[] args) {
        System.out.println("main execute!");
    }
    //静态代码块
    static{
        System.out.println(0);
    } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

此代码块输出结果为210


错误代码示列
i 变量是实例变量,实例变量必须先创建对象才能访问,静态代码块在类加载时执行,这个时候对象还没有创建呢,所以 i 变量在这里是不能这样访问的。可以考虑在 i 变量前添加static,这样 i 变量就变成静态变量了,静态变量访问时不需要创建对象,直接通过“类”即可访问

public class StaticTest02 {
	int i = 100;
	static{
		System.out.println(i);
	} }
  • 1
  • 2
  • 3
  • 4
  • 5

正确代码示列,而且变量和方法必须有先后顺序

public class StaticTest02 {
	static int i = 100;
	static{
	System.out.println("静态变量 i = " + i);
	}
	public static void main(String[] args) {
	} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

不谈静态方法的覆盖

public class OverrideTest {
    public static void main(String[] args) {
        Math.sum();
        MathSubClass.sum();
    }
}

public class Math{
    public static void sum(){
        System.out.println("Math's sum execute!");
    } }

public class MathSubClass extends Math{
    //尝试覆盖从父类中继承过来的静态方法
    public static void sum(){
        System.out.println("MathSubClass's sum execute!");
    } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

执行结果截图
在这里插入图片描述
但是方法覆盖和多态机制联合一起才有意义

public class OverrideTest {
	public static void main(String[] args) {
		Math m = new MathSubClass();
		m.sum();
		m = null;
		m.sum();
	} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

两个函数的执行结果截图都如上,甚至 m = null 的时候再去调用 m.sum()也没有出现空指针异常,这说明静态方法的执行压根和对象无关,既然和对象无关那就表示和多态无关,既然和多态无关,也就是说静态方法的“覆盖”是没有意义的,所以通常我们不谈静态方法的覆盖

6.2 this

  • this 是 java 语言中的一个关键字
  • this 可以看做一个变量,它是一个引用,存储在 Java 虚拟机堆内存的对象内部,this 引用保存了当前对象的内存地址指向自身,任何一个堆内存的 java 对象都有一个 this,也就是说创建 100 个 java对象则分别对应 100 个 this
  • this 可以出现在实例方法当中,因为实例方法在执行的时候一定是对象去触发的,实例方法一定是对象才能去调用的,而 this 恰巧又代表“当前对象”
  • 在实例方法中可以直接访问当前对象的实例变量,而“this.”是可以省略的

this 不能出现在带有 static 的方法当中

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

public class ThisInStaticMethod {
    public static void main(String[] args) {
        ThisInStaticMethod.method();
    }
    public static void method(){
        System.out.println(this);
    } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

会出现报错
在这里插入图片描述

换成this的写法

这样很丑陋

public Customer(String _name){
name = _name;
}
  • 1
  • 2
  • 3

变换成this的指针引用

public class Customer {
    private String name;
    public Customer(){
    }
    public Customer(String name){
        this.name = name;//这里的“this.”不能省略
    }
    public void setName(String name){
        this.name = name;//这里的“this.”不能省略
    }
    public String getName(){
        return name; //这里的“this.”可以省略
    }
    public void shopping(){
//这里的“this.”可以省略
        System.out.println(name + " is shopping!");
    } }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

以上代码当中 this.name = name,其中 this.name 表示实例变量 name,等号右边的 name 是局部变量 name,此时如果省略“this.”,则变成 name = name,这两个 name 都是局部变量(java 遵守就近原则),和实例变量 name 无关了,显然是不可以省略“this.”的


扩展补充this指针的代码
在一个实例方法当中可以直接去访问其它的实例方法,方法是对象的一种行为描述,实例方法中直接调用其它的实例方法,就表示“当前对象”完成了一系列行为动作


public class Customer {
    private String name;
    public Customer(){
    }
    public Customer(String name){
        this.name = name;
    }
    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
    //实例方法
    public void shopping(){
        System.out.println(name + " is shopping!");
        System.out.println(name + " 选好商品了!");
//pay()支付方法是实例方法,实例方法需要使用“引用”调用
//那么这个“引用”是谁呢?
//当前对象在购物,肯定是当前对象在支付,所以引用是this
        this.pay();
//同样“this.”可以省略
        pay();
    }
    //实例方法
    public void pay(){
        System.out.println(name + "支付成功!");
    } }

public class CustomerTest {
    public static void main(String[] args) {
        Customer jack = new Customer("jack");
        jack.shopping();
        System.out.println("=======================");
        Customer rose = new Customer("rose");
        rose.shopping();
    } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

代码截图如下
在这里插入图片描述

6.2.1 static与this对比

static和this指针的结合代码演示
static 的方法中不能直接访问实例变量,要访问实例变量必须先自己创建一个对象,通过“引用”可以去访问,不能通过 this 访问,因为在 static 方法中是不能存在 this 的

public class ThisTest {
    int i = 10;

    public static void main(String[] args) {
//这肯定是不行的,因为 main 方法带有 static,不能用 this
//System.out.println(this.i);
//可以自己创建一个对象
        ThisTest tt = new ThisTest();
//通过引用访问
        System.out.println(tt.i);
    }
}    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

方法也同理,需要用对象去调用
实例方法必须先创建对象,通过引用去调用,在以上的 main 方法中并没有创建对象,更没有 this。

//错误代码示列:
public class ThisTest {
    public static void main(String[] args) {
        doSome();
    }
    public void doSome(){
        System.out.println("do some...");
    } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

正确代码示列:

public class ThisTest {
    public static void main(String[] args) {
        ThisTest tt = new ThisTest();
        tt.doSome();
    }
    public void doSome(){
        System.out.println("do some...");
    } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

总结:

this 不能使用在 static 的方法中,可以使用在实例方法中,代表当前对象,多数情况下 this 是可以省略不写的,但是在区分局部变量和实例变量的时候不能省略,在实例方法中可以直接访问当前对象实例变量以及实例方法,在 static 方法中无法直接访问实例变量和实例方法

6.2.2 this与构造函数

this 还有另外一种用法,使用在构造方法第一行(只能出现在第一行)
通过当前构造方法调用本类当中其它的构造方法,其目的是为了代码复用
在这里插入图片描述
应该这样复用

 //业务要求,默认创建的日期为 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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

构造函数中this必须出现在第一行否则会出错,以下代码就是示列

//业务要求,默认创建的日期为 1970 年 1 月 1 日
//错误示列
public Date(){
System.out.println("...");
this(1970 , 1, 1);
}
public Date(int year,int month,int day){
this.year = year;
this.month = month;
this.day = day;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

代码截图
在这里插入图片描述

6.3 super

super 使用在构造方法中,语法格式为:super(实际参数列表),这行代码和“this(实际参数
列表)”都是只允许出现在构造方法第一行(这一点记住就行了),所以这两行代码是无法共
存的。“super(实际参数列表)”这种语法表示子类构造方法执行过程中调用父类的构造方法

代码示例如下:

public class People {
    String idCard;
    String name;
    boolean sex;
    public People(){
    }
    public People(String idCard,String name,boolean sex){
        this.idCard = idCard;
        this.name = name;
        this.sex = sex;
    } }

public class Student extends People{//学号是子类特有的
    int sno;
    public Student(){
    }
    public Student(String idCard,String name,boolean sex,int sno){
        this.idCard = idCard;
        this.name = name;
        this.sex = sex;
        this.sno = sno;
    } }

public class StudentTest {
    public static void main(String[] args) {
        Student s = new Student("12345x","jack",true,100);
        System.out.println("身份证号" + s.idCard);
        System.out.println("姓名" + s.name);
        System.out.println("性别" + s.sex);
        System.out.println("学号" + s.sno);
    } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

发现继承过后代码冗长且只是添加某一部分变量而已

public Student(String idCard,String name,boolean sex,int sno){
	super(idCard,name,sex);
	this.sno = sno;
}
  • 1
  • 2
  • 3
  • 4

“super(实际参数列表);”语法表示调用父类的构造方法,代码复用性增强了

6.3.1 super和this对比

this:

  • this 是一个引用,保存内存地址指向自己
  • this 不能出现在静态方法中
  • this 大部分情况下可以省略,在方法中区分实例变量和局部变量的时候不能省略
  • “this(实际参数列表)”出现在构造方法第一行,通过当前的构造方法去调用
    本类当中其它的构造方法

super:
super 代表了当前对象中从父类继承过来的那部分特征。this 指向一个独立的对象

super与this共有的

  • super 和 this 都可以使用在实例方法当中
  • super 不能使用在静态方法当中,因为 super 代表了当前对象上的父类型特征,静态方法中没有 this,肯定也是不能使用 super 的
  • super 也有这种用法:“super(实际参数列表);”,这种用法是通过当前的构造方法调用父类的构造方法

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


完结,撒花


学完java的基础内容,相信你已经差不多有所了解
接下来是新的篇章继续深入java
可查看我写的另一篇文章

再次强调
这部分知识一共有3个文档
第一个是当前这个文档

  1. java零基础从入门到精通(全)
  2. javaSE从入门到精通的十万字总结(一)
  3. javaSE从入门到精通的十万字总结(完结)

彩蛋

以下来源于评论区疑问

要调用抽象类中的具体方法,通常有以下两种方法:

  • 通过子类实例调用:创建一个继承该抽象类的子类的实例,通过该实例调用具体方法
abstract class AbstractClass {
    // 具体方法
    public void concreteMethod() {
        System.out.println("AAA");
    }
    
    // 抽象方法
    public abstract void abstractMethod();
}

// 子类继承抽象类
class SubClass extends AbstractClass {
    // 实现抽象方法
    @Override
    public void abstractMethod() {
        System.out.println("BBB.");
    }
}

public class Main {
    public static void main(String[] args) {
        // 通过子类实例调用具体方法
        SubClass subClassInstance = new SubClass();
        subClassInstance.concreteMethod();  // 调用具体方法
        subClassInstance.abstractMethod();  // 调用实现的抽象方法
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 静态方法调用:如果抽象类中的具体方法是静态方法,可以直接通过抽象类类名调用
abstract class AbstractClass {
    // 静态具体方法
    public static void staticConcreteMethod() {
        System.out.println("CCC");
    }
    
    // 抽象方法
    public abstract void abstractMethod();
}

public class Main {
    public static void main(String[] args) {
        // 直接通过抽象类类名调用静态方法
        AbstractClass.staticConcreteMethod();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/一键难忘520/article/detail/923980
推荐阅读
相关标签
  

闽ICP备14008679号