赞
踩
这部分知识一共有4个文档
第一个是当前这个文档
java优势
java体系
JVM是不能独立安装的。
JRE和JDK都是可以独立安装的
JDK = JRE + 开发工具集(例如Javac编译工具等)
JRE = JVM + Java SE 标准类库
JVM的内存结构中三块比较重要的内存空间
此处的jdkbin目录下有
编译操作(命令行javac进行编译)
运行操作(命令行java进行运行)
程序运行原理
public static void main(String[] args)
除了args可以改名字外,其他都不能动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"); } }
有权利命名的单词都是标识符
关键字具有特殊含义,不能用作标识符
byte | short | int | long | float |
---|---|---|---|---|
double | boolean | char | if | for |
else | while | do | continue | break |
public | default | protected | private | throw |
throws | try | catch | final | finally |
case | switch | transient | package | new |
static | this | abstract | strictfp | native |
goto | super | extends | implements | import |
instanceof | return | synchronized | void | const |
class | enum | assert | interface | volatile |
具体每个关键字的定义
关键字 | 大致含义 |
---|---|
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 | 用在循环结构中 |
内存中的最基本的存储单元,存数据用,且数据可变
1.变量的三要素:数据类型、变量名、值
2.变量注意事项
3.变量的声明:数据类型 变量名;
4.变量的分类:在方法体当中声明的就是局部变量,在方法体外面声明的就是成员变量
5.变量的作用域:出了大括号就不是
用来声明变量,且不同的数据类型分配不同大小的空间
根据数据类型可以分为基本数据类型和引用型数据类型
此处主要介绍主要介绍基本数据类型的不同,是占用空间的不同
类型 | 占用字节数量(byte) |
---|---|
byte | 1 |
short | 2 |
int | 4 |
long | 8 |
float | 4 |
double | 8 |
boolean | 1 (1byte的1或0,00000001(true)或00000000(false)) |
char | 2 |
类型转换的时候需要遵循的规则
补充运算符
类型 | 说明 |
---|---|
算术运算符 | + - * / % ++ – |
关系运算符 | > >= < <= == != |
逻辑运算符 | &| ! &&|| |
赋值运算符 | = += -= *= /= %= |
三目运算符 | 布尔表达式 ? 表达式1 : 表达式2 |
字符串连接运算符 | + |
补充:如何接收用户的输入
Scanner s = new Scanner(System.in);
// 接收整数
int i = s.nextInt()
// 接收字符串
String str = s.next();
控制语句有选择、循环以及转向
1.if结构语句的四种写法
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{ }
2。switch结构语句
switch(值){ //值允许是String、int,(byte,short,char可以自动转换int)
case 值1: case 值x:
java语句;
break;
case 值2:
java语句;
break;
case 值3:
java语句;
break;
default:
java语句;
}
3.for结构语句
for(初始化表达式;条件表达式;更新表达式){
循环体;
}
for(int i = 0; i < 10; i++){
System.out.println(i);
}
4.while结构语句
执行次数:0~N次。
while(布尔表达式){
循环体;
}
5.do…while结构语句
执行次数:1~N次
do{
循环体;
}while(布尔表达式);
6.break结构语句
默认情况下,终止离它最近的循环
也可以通过标识符的方式,终止指定的循环
for(int i = 0; i < 10; i++){
if(i == 5){
break;
}
code1;
code2;
code3;
code4;
....
}
7.continue结构语句
终止当前“本次”循环,直接跳入下一次循环继续执行
for(int i = 0; i < 10; i++){
if(i == 5){
continue;
}
code1;
code2;
code3;
code4;
....
}
8.return结构语句
用来终止一个方法的执行,区分于break,break 用来终止循环,
函数方法的封装
完成某个特定功能的并且可以被重复利用的代码片段
[修饰符列表] 返回值类型 方法名(形式参数列表){
方法体;
}
关于返回值类型的一一对应注意事项
//示列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; }
如何调用该方法
如果在同一个类中可以省略,不在同一个类中不能省略类名
//第一个类 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..."); } }
在同一个类当中,如果多个功能是相似的,可以考虑将它们的方法名定义的一致,使用方法重载机制,所谓重载机制是一个类中定义多个同名的方法,但要求每个方法具有不同的参数的类型或参数的个数
需要满足的条件有
和返回值类型(void/int)无关,和修饰符列表(public static)无关
public static void dosome(int i){
}
//不构成重载
public static int dosome(int i){
}
//不构成重载
void dosome(int i){
}
即应该满足条件如下
不同参数类型的重载代码示例
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; }
同功能不同参数的代码示列(比较两个数和比较三个数的重载)
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到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);
}
}
[修饰符] class 类名 {
类体 = 属性 + 方法
}
//例如
public class Student {
//学号
int no;
//姓名
String name;
//年龄
int age;
//性别
boolean sex;
}
以上程序当中 no、name、age、sex 都是属性,它们都是成员变量中的实例变量,所谓实例变量就是对象级别的变量,这些属性要想访问,必须先创建对象才能访问,不能直接通过类去访问,因为每一个学生的学号都是不一样的。没有学生对象,谈何学号!
类定义之后,就可以使用类这个“模板”来创造“对象”
public class StudentTest {
public static void main(String[] args) {
//创建一个学生对象
Student s1 = new Student();
//再创建一个学生对象
Student s2 = new Student();
//以上代码其实和这行代码差不多
}
}
Student 类可以创建很多学生对象,通过类实例化两个对象,那必然会有不同的学号,并且通过类实列的对象使用,没有初始化,默认系统赋值
数据类型 | 默认值 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
float | 0.0f |
double | 0.0 |
boolean | false |
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:构造函数 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"); } }
//示列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"); } }
//示列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");
}
}
输出这些引用的结果是“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"); } }
因为所有的构造方法在执行过程中没有给对象的属性手动赋值,系统则自动赋默认值,实际上大部分情况下我们需要在构造方法中手动的给属性赋值
通过构造函数初始化对象,给属性值赋值:
没有赋值的对象都是默认初始化为系统值
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"); } }
截图如下:
注意事项:
任何“引用”当中存储一定是对象的内存地址,“引用”不一定只是以局部变量的形式存在
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 + "日"); } }
结果截图:
此处比较难
具体调用传递的是什么值?
基本变量与引用型变量的内存传递
基本变量是值,引用型变量是内存地址值
引用数据类型由于传递的这个值是 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; } }
具体的代码截图:
在讲解面向对象的前提中,先讲解面向过程,面向过程是一步一步按着步骤执行,一般的面向过程是从上往下步步求精。面向对象主要是把事物给对象化,对象包括属
性与行为
面向对象能有效提高编程的效率,通过封装技术,可以像搭积木的一样快速开发出一个全新的系统。面向对象将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性
面向对象具有三大特征
指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系
封装的作用:
封装的步骤:
规范中要求 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;
}
例如完整程序
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()); } }
补充static关键字
带有 static 关键字的方法是静态方法,不需要创建对象,直接通过“类”来调用。对于没有 static 关键字的方法被称为实例方法,这些方法执行时要求必须先创建对象,然后通过“引用”的方式来调用。而对于封装来说,setter 和 getter 方法都是访问对象内部的属性,所setter 和 getter 方法在定义时不能添加 static 关键字
class 类名 extends 父类名{
类体; }
如果两个函数相同,定义相同的功能会十分冗余
//第一个函数 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; } }
将程序修改为继承的方式
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() + "元"); } }
对继承的理解
扩充:父类子类的执行顺序理解
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("子类构造"); } } }
执行的结果显示为:
先调用父类的静态代码块,子类静态代码块
在执行父类的代码以及构造函数从上往下,最后执行子类的代码块和构造函数
覆盖的条件以及注意事项
覆盖的程序代码示列:
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(); } }
无论是向上转型还是向下转型,两种类型之间必须要有继承关系,没有继承关系情况下进行向上转型或向下转型的时候编译器都会报错。向上转型是指子类型转换为父类型,又被称为自动类型转换,向下转型是指父类型转换为子类型,又被称为强制类型转换
多态存在的三个必要条件
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(); } }
以上程序演示的就是多态,多态就是“同一个行为(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()方法 */
只有在访问子类型中特有数据的时候,需要先进行向下转型。
//编译没错,执行报错
public class Test05 {
public static void main(String[] args) {
Animal a = new Bird();
Cat c = (Cat)a;
} }
没有继承关系的函数,为了避免这种异常的发生,建议在进行向下转型之前进行运行期类型判断,引入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();
} } }
在进行任何向下转型的操作之前,要使用 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();
} } }
同样的示列:以多态为主
请设计一个系统,描述主人喂养宠物的场景,首先在这个场景当中应该有“宠物对象”,宠物对象应该有一个吃的行为,另外还需要一个“主人对象”,主人对象应该有一个喂的行为
先展示不是多态的代码结构,做以区分对比
//宠物狗 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); } }
代码编译执行可以通过,但是之后如果添加额外的功能或者是额外的对象,需要全部修改
不符合多态的定义以及结构
下面展示多态的结构
//宠物类 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); } }
结果如图所示
总结
本章节的难点就是对多态机制的理解,多态的代码表现是父类型引用指向子类型对象,对于多态的理解一定要分为编译阶段和运行阶段来进行分析,编译阶段只是看父类型中是否存在要调用的方法,如果父类中不存在,则编译器会报错,编译阶段和具体 new 的对象无关。但是在运行阶段就要看底层具体 new的是哪个类型的子对象了,new的这个子类型对象可以看做“真实对象”,自然在运行阶段就会调用真实对象的相关方法。例如代码:Animal a = new Cat(); a.move();,在编译阶段编译器只能检测到a的类型是Animal,所以一定会去Animal类中找move()方法,如果 Animal 中没有 move()方法,则编译器会报错,即使 Cat 中有 move()方法,也会报错,因为编译器只知道 a 的类型是 Animal 类,只有在运行的时候,实际创建的真实对象是 Cat,那么在运行的时候就会自动调用 Cat 对象的 move()方法。这样就可以达到多种形态,也就是说编译阶段一种形态,运行阶段的时候是另一种形态
挑选容易混淆的关键字进行讲解
可查看我上一篇文章
super关键字与this关键字区别
类{
//静态代码块
static{
java 语句;
}
}
静态代码块从上往下执行
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); } }
此代码块输出结果为210
错误代码示列
i 变量是实例变量,实例变量必须先创建对象才能访问,静态代码块在类加载时执行,这个时候对象还没有创建呢,所以 i 变量在这里是不能这样访问的。可以考虑在 i 变量前添加static,这样 i 变量就变成静态变量了,静态变量访问时不需要创建对象,直接通过“类”即可访问
public class StaticTest02 {
int i = 100;
static{
System.out.println(i);
} }
正确代码示列,而且变量和方法必须有先后顺序
public class StaticTest02 {
static int i = 100;
static{
System.out.println("静态变量 i = " + i);
}
public static void main(String[] args) {
} }
不谈静态方法的覆盖
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!"); } }
执行结果截图
但是方法覆盖和多态机制联合一起才有意义
public class OverrideTest {
public static void main(String[] args) {
Math m = new MathSubClass();
m.sum();
m = null;
m.sum();
} }
两个函数的执行结果截图都如上,甚至 m = null 的时候再去调用 m.sum()也没有出现空指针异常,这说明静态方法的执行压根和对象无关,既然和对象无关那就表示和多态无关,既然和多态无关,也就是说静态方法的“覆盖”是没有意义的,所以通常我们不谈静态方法的覆盖
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);
} }
会出现报错
换成this的写法
这样很丑陋
public Customer(String _name){
name = _name;
}
变换成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!"); } }
以上代码当中 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(); } }
代码截图如下
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);
}
}
方法也同理,需要用对象去调用
实例方法必须先创建对象,通过引用去调用,在以上的 main 方法中并没有创建对象,更没有 this。
//错误代码示列:
public class ThisTest {
public static void main(String[] args) {
doSome();
}
public void doSome(){
System.out.println("do some...");
} }
正确代码示列:
public class ThisTest {
public static void main(String[] args) {
ThisTest tt = new ThisTest();
tt.doSome();
}
public void doSome(){
System.out.println("do some...");
} }
总结:
this 不能使用在 static 的方法中,可以使用在实例方法中,代表当前对象,多数情况下 this 是可以省略不写的,但是在区分局部变量和实例变量的时候不能省略,在实例方法中可以直接访问当前对象实例变量以及实例方法,在 static 方法中无法直接访问实例变量和实例方法
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;
}
构造函数中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;
}
代码截图
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); } }
发现继承过后代码冗长且只是添加某一部分变量而已
public Student(String idCard,String name,boolean sex,int sno){
super(idCard,name,sex);
this.sno = sno;
}
“super(实际参数列表);”语法表示调用父类的构造方法,代码复用性增强了
this:
super:
super 代表了当前对象中从父类继承过来的那部分特征。this 指向一个独立的对象
super与this共有的
this 和 super 都是无法使用在静态方法当中的
完结,撒花
学完java的基础内容,相信你已经差不多有所了解
接下来是新的篇章继续深入java
可查看我写的另一篇文章
再次强调
这部分知识一共有3个文档
第一个是当前这个文档
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。