是一种职位的名称。
通常是通过计算机的“某种编程语言”完成软件的开发。
眼前的笔记本电脑就是一台计算机。
计算机包括两部分:
硬件:鼠标、键盘、显示器、主机箱内部的CPU、内存条……
注意:计算机只有硬件是无法工作的,需要软件驱动硬件才能工作。
软件:
直接和硬件交互的软件,例如win7、win8、win10……
应用软件:
应用软件通常运行在系统软件中,例如:QQ运行在Wind7中。
QQ就是应用软件。
win7就是操作系统软件。
我们通常所说的软件开发一般都是指使用计算机编程语言完成“应用软件”的开发。
软件开发协议计算编程语言,计算机编程语言很多,例如:c语言、c++、Java、.net、C#、PHP……
通过协议java系列计算完成应用软件开发。
Java软件工程师:通过Java编程语言完成应用软件的开发,我们不是弄硬件,我们是弄软件开发的。
学习之前先安装一个好一点的文本编辑器:我们这里使用安装的是EditPlus3
cd命令
cd \ 直接回到根目录
切换盘符:
c:回车
d:回车
g:回车
什么是计算机“编程”语言?即-交流的规则
第一代语言:机器语言
主要辨析二进制码,直接编写10011这样的二进制。
以打孔机为代表。
第二代语言:低级语言
主要以汇编语言为代表
在低级语言当中已经引入了一些英语单词,例如变量赋值。
第三代语言:高级语言
几乎和人类的语言完全相同。
如Java、C++……
Java语言诞生于1995年。
其实在1995年之前SUN公司(太阳微电子公司:该公司目前给oracle(甲骨文:做数据库的)收购了),为了占领只能电子消费产品市场,派James Gosling领导团队开发了一个oak(橡树)语言。
1996年:JDK1.0诞生
什么是JDK?
java开发工具包
做java开发必须安装的一个工具包,该工具包需要从官网下载。
目前SUN被oracle收购了,所有下载需要去oracle下载。http://www.oracle.com
目前jdk最高版本Java8/JDK1.8/JDK8
java包括三大块
JavaSE(java标准版)
JavaEE(Java企业版)
JavaME(Java微型版)
其中JavaSE是基础,JavaEE是以后的主攻方向。
Java语言特性【开源、免费、纯面向对象、跨平台】
Windows操作系统内核与Linux系统的内核肯定不相同,他们这两个操作系统执行指令的方式也是不一样的。
结论:显然java程序不能直接和操作系统打交道,因为Java程序只有一份。操作系统执行原理都不同。
SUN的团队很聪明,他们想了一个办法,他们让java程序运行在一台虚拟的计算机当中,这个虚拟的计算机叫做java虚拟机,简称JVM。Java虚拟机在和底层的操作系统打交道。
JVM一般是在安装jdk的时候就自动安装了。
多线程
健壮性
和自动垃圾回收机制有关,自动垃圾回收机制简称为GC机制。
Java语言运行工程中产生的垃圾是自动回收的,不需要程序关心。
安全性
……
编译阶段主要任务是检查Java源程序是否符合Java语法,符合Java语法规则则生成长长的字节码文件(xxx.class),不符合则不会生成。
字节码文件中不是纯粹的二进制,这种文件无法在操作系统当中直接运行。
编译过程:
1. 程序员需要在任意位置新建一个.java扩展名的文件,这个文件成为Java源程序文件,源文件当中编写的是Java源代码。
2. Java程序员需要使用jdk当中自带的javac.exe命令进行java程序的编译。使用方法为:javac java源文件的路径。
3. 一个java源文件可以编译生成多个.class文件。
jdk安装之后,除了自带javac.exe之外,还有另一个工具,叫做java.exe,我们主要是使用它来负责运行阶段。
用法:java 字节码文件/class文件(注意:不要跟后缀名.class,也不能是路径,否则会报错),如:java a 能够正常运行。java a.class 报错
运行阶段的过程:
jdk下载安装并配置环境变量:https://www.cnblogs.com/miaomiaoka/p/11198344.html
注意以下的名词:
jdk - Java开发工具包,用来开发的时候安装,一般会自带jre,有单独软件安装
jre - Java运行时环境,用来部署项目的时候安装,jre包括jvm,有单独软件安装
jvm - Java虚拟机,没有单独软件安装
三者关系:
JDK目录的介绍:
JDK/bin:该目录下存放了很多命令,例如:javac.exe和java.exe
javac.exe 负责编译
java.exe负责运行
编写源代码:
public class HelloWorld{
public static void main(String[] args) {
System.out.println("hello world");
}
}
编译源文件
运行字节码文件
Java源代码中的注释不会被编译到字节码文件中。
注:类体中不允许直接编写java语句【除声明变量之外】
如:一下语句为错误的定义方式:
public class Test{
System.out.println("hello world!"); // 错误定义方式,语句必须放在函数体内。
public static void main(String[] args) {
System.out.println("hello world"); // 正确定义方法
}
}
class A{
public static void main(String[] arg){
System.out.println("A");
}
}
class B{
public static void main(String[] arg){
System.out.println("B");
}
}
class C{
public static void main(String[] arg){
System.out.println("C");
}
}
public class ClazzTest{
public static void main(String[] arg){
System.out.println("ClazzTest");
}
}
1. 一种非常经典的数据结构
栈数据结构:stack
2. 什么是数据结构?
数据结构通常是:存储数据的容器。而该容器可能存在不同的结构,数据结构和java语言实际上是没有关系的,数据结构是一门独立的学科,在大学计算机专业中,数据结构是必修的一门课程。
3. 常见的数据结构:
数组、链表、图、二叉树、栈、队列……,java语言把常用的数据结构都已经写好了,对于java程序员来说我们直接使用就好了。所以当前阶段是不需要精通数据结构的。如果期望有更高的造诣,建议数据结构还是需要精通的。
4. 和数据结构通常出现在一块的是:算法
算法:排序算法、查找算法、……算法
/*
局部变量:只在方法体重有效,方法结束,局部变量的内存就释放了。
JVM三块主要的内存:栈内存、堆内存、方法区内存
方法区中最先有数据:方法区中方代码片段,存放class字节码。
堆内存:暂时不涉及
对内存:方法调用的时候,方法需要的内存空间在栈中分配。方法不调用时不会在栈中分配空间的。
方法只有在调用的时候才会在栈中分配空间,并且调用时就是压栈。
方法调用结束后该方法所需要的空间就会被释放,此时发生弹栈动作。
*/
public class MethodTest08{
public static void main(String[] args){
System.out.println("main begin!");
int x = 100;
m1(x);
System.out.println("main end!");
}
public static void m1(int i){
System.out.println("m1 begin!");
m2(i);
System.out.println("m1 end!");
}
public static void m2(int i){
System.out.println("m2 begin!");
m3(i);
System.out.println("m2 end!");
}
public static void m3(int i){
System.out.println("m3 begin!");
System.out.println(i);
System.out.println("m3 end!");
}
}
从语言的角度出发
对于c语言来说,是完全面向过程的。
对于C++来说,是一半面向过程,一半面向对象的。
对于java来说,是完全面向对象的。
什么是面向过程的开发方式?
主要特点有:注重步骤,注重的是实现这个功能的步骤,第一步是干什么,第二部是干什么,……另外面向过程也注重实现功能的因果关系。
缺点:耦合度太高,导致扩展性差。
优点:对于小型项目(功能),采用面向对象过程的方式进行开发,效率较高。不需要前期进行对象的提取,模型的建立,采用面向过程方式可以直接写代码,编写因果关系。
什么是面向对象的开发方式?
采用面向对象的方式进行开发,更符合人类的思维方式。人类就是以”对象”的方式去认识世界的,所有面向对象更容易让我们接受。
任何一个面向对象的编程语言都包括这三个特征。
问题:为什么属性是以变量的形式存在?
答案:因为属性对应的是“数据”,数据在程序只能放在变量中。
变量的分类:变量根据出现的位置进行划分。
方法体当中声明的变量:局部变量
方法体外声明的变量:成员变量(也称为”属性“)。
数据类型包括两种:
基本数据类型:byte、short、int、long、float、double、boolean、char
引用数据类型:String、class……
public class this_and_static {
// 以下实例的,都是对象相关的,访问时采用“引用.”的方式访问。需要先new对象。
// 实例相关的,必须先有对象,才能访问,可以能会出现空指针异常。
// 成员变量中的实例变量
int i;
// 实例方法
public void m2(){}
// 以下静态的,都是类相关的,访问时采用“类名.”的方式访问,不需要new对象。
// 不需要对象的参与即可访问。没有空指针异常的发生。
// 成员变量中的静态变量
static int k;
// 静态方法
public static void m1(){}
}
变量什么时候声明为实例的,什么时候声明为静态的?
如果这个类型的所有对象的某个属性值都是一样的,不建议定义为实例变量,浪费内存空间,建议定义为类级别特征,即静态变量,在方法区中只保留一份,节省内存开销。
public class StaticTest02 {
public static void main(String[] args) {
Chinese c1 = new Chinese("522258199612031819", "张三");
System.out.println("idCard: "+c1.idCard);
System.out.println("name: "+c1.name);
System.out.println("country: "+Chinese.country); // country是静态变量,直接使用“类名.属性名”的方式访问
Chinese c2 = new Chinese("622258199012035689", "李四");
System.out.println("idCard: "+c2.idCard);
System.out.println("name: "+c2.name);
System.out.println("country: "+Chinese.country);
}
}
// 定义一个类:中国人
class Chinese{
// 身份证号
// 每一个人的身份证号不同,所有身份证号应该是实例变量
String idCard;
// 姓名
// 每一个人的名字可能都是不同的,也定义为实例变量
String name;
// 国籍
// 只要你是中国人,每个人的国籍都是一样,定义为静态变量
static String country = "Chinese";
public Chinese(){}
public Chinese(String idCard, String name){
this.idCard = idCard;
this.name = name;
}
}
实例变量内存分布图
静态变量内存分布图
package com.zh0u.static关键字;
public class StaticTest03 {
public static void main(String[] args) {
// 通过“类名.静态变量名”的方式访问静态变量
System.out.println(ChinaMobile.maker); // China Mobile
// 创建对象
ChinaMobile mobile = new ChinaMobile("15385895650");
// 通过“引用.静态变量名”也可以正常访问,实际上是编译器自动将前面的“引用”转换为“类名”
/* 总结:
实例的变量:一定需要使用“引用.”来访问
静态的变量:建议使用“类名.”来访问,但使用“引用.”也可以访问,但是不建议,容易使人困惑。
*/
System.out.println(mobile.cardNo); // 15385895650
System.out.println(mobile.maker); // China Mobile
// 将mobile引用变成null引用
mobile = null;
// 分析这里会不会出现空指针异常?不会出现空指针异常,因为静态变量不需要对象的存在。
// 实际上以下代码在运行的时候,还是:System.out.println(ChinaMobile.maker);
System.out.println(mobile.maker);
// 以下这个会出现空指针异常,因为cardNo是实例变量
// 结论:空指针异常只有在“空引用”访问“实例”相关的时候,才会出现空指针异常。
// System.out.println(mobile.cardNo);
}
}
class ChinaMobile{
// 卡号码,实例变量
String cardNo;
// 制造商,静态变量
static String maker = "China Mobile";
public ChinaMobile(){}
public ChinaMobile(String cardNo){
this.cardNo = cardNo;
}
}
package com.zh0u.static关键字;
public class StaticTest04 {
public static void main(String[] args) {
// 静态方法既可以使用“类名.”的方式来访问,也可以使用“引用.”的方式来访问,但是不建议使用后者。
// 如果是静态方法,也可以直接写方法名即可。-- doSome();
StaticTest04.doSome();
// 实例方法只能通过“引用.”的方式来访问,而且当引用的对象为null时,此时引用会出现空指针异常。
new StaticTest04().doOther();
}
public static void doSome(){
System.out.println("静态方法doSome()执行了!");
}
public void doOther(){
System.out.println("实例方法doOther()执行了!");
}
}
对于方法来说,什么时候为实例方法?什么时候定义为静态方法?
参考标准:当这个方法体当中,直接访问了实例变量,这个方法一定是实例方法。
在今后的开发中,大部分情况下,如果是工具类的话,工具类当中的方法一般都是静态的(因为不需要new对象,直接采用类名调用,极其方便)。
使用static关键字可以定义“静态代码块”,语法如下:
static{
java语句1;
java语句2;
……
}
关键点:static定义的静态代码块在“类加载的时候执行,在main方法之前执行,并且只执行一次”。
public class StaticTest06 {
static {
System.out.println("a");
}
static {
System.out.println("b");
}
public static void main(String[] args) {
System.out.println("d");
}
static {
System.out.println("c");
}
}
以上程序运行结果:
a
b
c
d
静态代码块的作用:
第一:静态代码块不是很常用(不是每一个类都需要的)。
第二:可以放在具体业务中,例如:可以记录类加载的的时间。
代码执行顺序要求:
this是一个关键字,全部小写。
一个对象一个this,this是一个变量(引用)。this保存当前对象的内存地址,指向的是对象本身。所以也可以说this就是“当前对象”。
this存在堆内存当中对象的内部。它只能使用在实例方法中而不能出现在静态方法中。谁调用这个实例方法,this就是谁,所以this代表的“当前对象”。
package com.zh0u.this关键字;
public class ThisTest01 {
public static void main(String[] args) {
Customer c1 = new Customer("张三");
Customer c2 = new Customer("李四");
}
}
class Customer{
// 属性
// 实例变量
String name;
// 构造方法
public Customer(){}
public Customer(String name){this.name = name;}
// 顾客购物的方法
// 实例方法
public void shopping(){
// c1调用shopping(),this是c1
// c2调用shopping(),this是c2
// 这里的this.name中,this可以省略,省略了默认访问“当前对象”的name。“this.”在大部分情况下都是可以省略的。
System.out.println(this.name +"正在购物!");
}
public static void doSome(){
// this代表的是当前对象,而静态方法的调用不需要对象。这里就会产生矛盾。
// System.out.println(this); // 错误:java: 无法从静态上下文中引用非静态 变量 this
}
}
class Student{
// 实例变量的访问,必须先new对象,通过“引用.”来访问。
String name;
// 静态方法
public static void m1(){
// 以下的两个输出语句都会报错
// System.out.println(name);
// this代表的是当前对象
// System.out.println(this.name);
}
}
this可以使用在实例方法中,不能使用在静态方法中。this关键字大部分情况下可以省略,但有些时候却不能省略。
在实例方法或构造方法中,为了区分局部变量和实例变量,this是不能省略的,这是因为Java中变量存在一个“就近原则”问题。
class Robots{
// 编号
private int no;
// 姓名
private String name;
// 构造方法
public Robots(){}
// 以下这些凡是有this的都是不能够省略的,因为Java变量赋值存在一个就近原则问题。
public Robots(int no, String name){
// 如果这里的this省略了,代表变成了:no = no;由于存在就近原则问题,这里的两个no都是局部变量,这样一来就没法给“属性变量no”赋值了。
// 同样name也是一样的。
this.no = no;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
}
通过当前的构造方法去调用本类的另一个构造方法,可以使用一下的语法格式:
this(实际参数列表);
使用以上的方式可以提高代码的复用,但是需要注意的是,构造方法1和构造方法2都是在同一个类当中,this()方法在构造器中只能出现一次且this()方法之前不能有其它的语句。
class DefinedDate{
// 年
private int year;
// 月
private int month;
// 日
private int day;
// 构造函数
public DefinedDate(){
// 这里的三行代码和有参构造函数中的代码在一定程度上是重复的,为了提高代码的复用,可以使用this()方法来解决。
// this.year = 1970;
// this.month = 1;
// this.day = 1;
// this()方法解决以上代码重复问题
this(1970, 1, 1);
}
public DefinedDate(int year, int month, int day){
this.year = year;
this.month = month;
this.day = day;
}
}
继承的本质: 子类继承父类后,是将父类继承过来的方法归为自己所有。子类调用父类的方法时,实际上是调用继承过来的方法。
继承的时候应该遵循 “is a” 的规范。如Cat is a Animal。
基本作用:子类继承父类,代码可以得到复用。
主要作用:因为有了继承关系,才有了后面的方法覆盖和多态机制。
package com.zh0u.extends关键字;
public class ExtendsTest01 {
public static void main(String[] args) {
Account account = new Account("65245892144456266", "400000");
System.out.println("账号: "+account.getActNo()+", 余额: "+account.getBalance());
CreditAccount creditAccount = new CreditAccount("65245892144456266","600000",0.9999);
System.out.println("账号: "+creditAccount.getActNo()+", 余额: "+creditAccount.getBalance()+", 信誉度: "+creditAccount.getCredit());
}
}
class Account{
// 账号
private String actNo;
// 余额
private String balance;
// 构造方法
public Account(){}
public Account(String actNo, String balance){
this.actNo = actNo;
this.balance = balance;
}
public String getActNo() {
return actNo;
}
public void setActNo(String actNo) {
this.actNo = actNo;
}
public String getBalance() {
return balance;
}
public void setBalance(String balance) {
this.balance = balance;
}
}
class CreditAccount extends Account{
// 信誉度
private double credit;
public CreditAccount(){}
public CreditAccount(String actNo, String balance, double credit){
this.setActNo(actNo);
this.setBalance(balance);
this.credit = credit;
}
public double getCredit() {
return credit;
}
public void setCredit(double credit) {
this.credit = credit;
}
}
B类继承A类,则A类成为超类(superclass)、父类、基类。B类称为子类(subclass)、派生类、扩展类。
Java中的继承制仅支持单继承,不支持多继承,C++中支持多继承。
虽然Java总不支持多继承,但有时候会产生间接继承的效果。
class A{}
class B extends A{}
class C extends B{} // 这种方法在某种程度上来说也是多继承
Java中规定,子类继承父类,除构造方法不能继承,剩余的都可以继承。但是私有(private)的属性无法在子类中直接访问。可以通过间接方式(setter与getter)访问。
如果Java中的类没有显示的继承任何类,但会默认继承Object类,Object类时java语言提供的根类,也就是说,一个对象与生俱来就有Object类型总所有的特性。
继承也存在一些缺点,如父类中发生改变之后便会影响到子类,即耦合度较高。
要想成为牛人,先看看牛人写的源代码。多模仿,后超越。
注意: 当源码当当中一个方法以“;”结尾,并且修饰符列表中有“native”关键字,表示底层调用C++写的dll(动态链接库)程序。
如:
private static native void registerNatives();
当System.out.println()打印对象而没有指定使用“toString()”方法的时候,println()会自动的调用toString()并将其结果作为输出结果。
public class ObjectTest02 {
public static void main(String[] args) {
// 分析下面的第一行代码能不能正常执行?
// 答案是不能正常执行的,我们通过分析源代码可以知道toString()方法在Object类中不是静态方法,只有静态的才使用“类名.”的方式进行访问。
// 非静态方法只能使用“引用(也称为对象).”的形式访问
// ObjectTest02.toString();
// 下面使用“引用.”的方式进行访问
ObjectTest02 test02 = new ObjectTest02();
// 在以下的代码中输出为:com.zh0u.Object类.ObjectTest02@6d6f6e28。在这个返回结果中,com.zh0u.Object类是包名,ObjectTest02为类名。
// 而6d6f6e28是该对象在堆内存中的地址经过“哈希算法”返回的十六进制结果。
System.out.println(test02.toString()); // com.zh0u.Object类.ObjectTest02@6d6f6e28
System.out.println(test02); // com.zh0u.Object类.ObjectTest02@6d6f6e28
}
}
子类继承父类,有一些“行为”可能不需要改进,有一些“行为”可能面临着必须改进,因为父类中继承过来的方法已经无法满足子类的业务需求。
什么时候我们会考虑使用“方法覆盖”呢?
子类继承父类之后,当继承过来的方法无法满足当前子类的业务需求时,子类有权利对这个方法进行重新编写,有必要进行“方法的覆盖”。方法覆盖又叫做:方法重写,Override,Overrite。
需要注意的是应该要将“方法覆盖(Override/Overrite)”与“方法重载(Overload)”正确的区分开来。
方法覆盖满足一下条件:
那么什么是时候使用方法重载?
当在一个类当中,如果功能相似的话,建议将名字定义一样,这样代码美观,并且方便编程,满足一下条件之后便能构成方法重载:
重要结论:当子类对父类继承过来的方法进行“方法覆盖”之后,子类对象调用该方法的时候,一定执行覆盖之后的方法。
package com.zh0u.Overwride;
public class OverwriteTest01 {
public static void main(String[] args) {
new Bird().move(); // 输出: 鸟儿在飞翔!!!
}
}
class Animal{
public void move(){
System.out.println("动物在移动!!!");
}
}
class Bird extends Animal{
// 方法覆盖
public void move(){
System.out.println("鸟儿在飞翔!!!");
}
}
注意点:
方法覆盖只是针对于方法,和属性无关。
私有方法无法覆盖。(可以理解为子类无法继承,所以没法覆盖)
构造方法不能被继承,所以构造方法也不能被覆盖。
方法覆盖知识针对于实例方法,静态方法覆盖没有意义。
package com.zh0u.多态.静态方法不存在Overrite;
/*
* 1. 方法覆盖需要和多态进制联合起来使用才有意义。
* Animal a = new Cat();
* a.move();
* 要的是什么效果?
* 编译的时候move()方法是Animal的。运行的时候自动调用到子类重写move()的方法上去。
*
* 假设没有多态机制,只有方法覆盖机制,这样是没有意义的。
* 没有多态机制,方法覆盖可有可无。方法覆盖和多态不能分开。
*
* 静态方法存在方法覆盖吗?
* 多态自然和对象有关,而静态方法的执行不需要对象,所有,一般情况下,我们会说静态方法“不存在”方法覆盖。
*
*/
public class OverwriteTest {
public static void main(String[] args) {
// 静态方法可以使用“引用.”来调用嘛?答案是:可以的。
// 虽然使用“引用.”来调用,但是实际运行的时候还是:Animal.doSome()
Animal a = new Cat();
// 静态方法和对象无关
// 虽然使用“引用.”来调用。但是实际运行的时候还是:Animal.doSome()
a.doSome(); // Animal的doSome()方法执行!!!
Animal.doSome(); // Animal的doSome()方法执行!!!
Cat.doSome(); // Cat的doSome()方法执行!!!
}
}
class Animal{
// 父类的静态方法
public static void doSome(){
System.out.println("Animal的doSome()方法执行!!!");
}
}
class Cat extends Animal{
// 尝试在子类当中对父类的静态方法进行重写
public static void doSome(){
System.out.println("Cat的doSome()方法执行!!!");
}
}
父类型引用指向子类对象。分析程序的时候包括编译阶段和运行阶段。
编译阶段:绑定父类型的方法。
运行阶段:动态绑定子类型对象的方法
这个过程被称为“多态”。
两个重要的概念:
第一:向上转型,子 ----> 父,即父类型指向子对象。
第二:向下转型,父 ----> 子,需要加强制类型转换符。
注意点:
Java中允许向上转型,也允许向下转型。无论是向上转型还是向下转型,两种类型之间必须有继承关系,没有继承关系编译器报错。
什么时候必须进行向下转型?
当父类引用执行子类中“特有的方法”时,这个时候必须使用向下转型。
Animal
package com.zh0u.多态;
public class Animal {
public void move(){
System.out.println("动物在移动!!!");
}
}
Bird
package com.zh0u.多态;
public class Bird extends Animal{
public void move(){
System.out.println("鸟儿在飞翔!!!");
}
}
Cat
package com.zh0u.多态;
public class Cat extends Animal{
public void move(){
System.out.println("Cat走猫步!!!");
}
// 猫抓老鼠,应该是猫特有的属性
public void catchMouse(){
System.out.println("猫正在抓老鼠!!!");
}
}
Test01
package com.zh0u.多态;
public class Test01 {
public static void main(String[] args) {
Animal animal = new Animal();
animal.move();
// 鸟
Bird bird = new Bird();
bird.move();
// 猫
Cat cat = new Cat();
cat.move();
// 多态,父引用指向子对象
Animal a1 = new Bird();
Animal a2 = new Cat();
/*
什么是多态?
多种形态,多种状态。
分析a2.move();
Java程序分为编译阶段和运行阶段。
先来编译阶段:
对于编译器来说,编译器只知道a2的类型是Animal,所以编译器再检查语法的时候。
会去Animal.class字节码文件中找move()方法,找到了,绑定move()方法,编译通过,静态绑定成功。编译阶段属于“静态绑定”。
在来分析运行阶段:
运行阶段的时候,实际上在堆内存中创建的Java对象是Cat对象,所以move的时候,真正参与move的对象是猫对象。所以运行阶段
对动态执行Cat对象的move()方法,这个过程属于运行阶段。(运行阶段绑定输入动态绑定)
多态表示多种形态:
编译的时候是一种形态。
运行的时候是另一种形态。
即 “多态”
*/
a1.move(); // 鸟儿在飞翔!!!
a2.move(); // Cat走猫步!!!
// =================================
Animal a3 = new Cat();
/*
分析下面的语句是否能编译和运行?
分析程序一定要分析编译阶段的静态绑定和运行阶段的动态绑定。
只有编译通过的代码才能运行,以下代码编译是不能通过的。因为编译器只知道a3的类型是Animal,去Animal.class文件中找catchMouse()
方法,结果没有找到,所以静态绑定失败,编译报错。无法运行。
*/
// a3.catchMouse();
/*
假设代码写到了这里,我非要让a3调用catchMouse()方法咋办?
这个时候就必须使用“向下转型”了,即“强制类型转换”,即下面的代码。
分析以下代码:为啥能够正常运行?
因为a3是Animal类型,转成Cat,Animal和Cat之间存在继承关系,所有没有报错!!!
*/
Cat c4 = (Cat) a3; // 这里也可以称为“向下转型”
c4.catchMouse(); // 猫正在抓老鼠!!!
}
}
在进行强制类型转换时存在着一定的风险,例如:将一个类强制转换为另外一个类的类型的时候,如果这两个类没有继承关系,则会抛出“ java.lang.ClassCastException”异常。
Animal b = new Bird();
Cat cat1 = (Cat) b; // 没有继承关系的两个类之间是不能进行强制类型的转换的。
cat1.catchMouse();
为了解决在强制类型转化是的风险,可以使用instanceof来解决这个问题。
在对类型进行向下转型(强制类型转换)时一定要使用instanceof运算符进行判断。这样可以很好的避免:ClassCastException
instanceof - 运行阶段动态判断
instanceof可以在运行阶段动态判断引用指向的对象的类型。
instanceof 语法: (引用 instanceof 类型)
instanceof运算符的运算结果只能是true/false。
假设c是一个引用,c变量保存了内存地址指向对中的对象。
如果(c instanceof Cat) 为true,则表示:c引用指向的堆内存中的java对象是一个Cat,反之则不是。
则存在以下代码:
Animal b = new Bird();
if (b instanceof Cat){ // 通过返回值来判断是否能强制类型转换
Cat cat1 = (Cat) b;
cat1.catchMouse();
}
尝试完成以下练习:
编写程序模拟“主人”喂养“宠物”的场景:
提示1:
主人类:Master
宠物类:Pet
宠物子类:Dog、Cat、YingWu
提示2:
主人应该有喂养的方法:feed()
宠物应该有吃的方法:eat()
只要主人喂养宠物,宠物就吃。
要求:主人类中提供一个喂养方法feed(),要求达到可以喂养各种类型的宠物。
编写测试程序:
创建主人对象
创建各种宠物对象
调用主人的喂养方法feed(),喂养不同的宠物,观察执行结果。
通过该案例,理解多态在开发中的作用。
重要提示:feed()方法是否需要一个参数,参数选什么类型!!!
Master:
package com.zh0u.多态在开发中的作用;
// 主人类
public class Master {
/*
// 假设主人起初的时候知识喜欢养宠物狗狗
// 喂养宠物狗狗
public void feed(Dog dog){
dog.eat();
}
// 由于心得需求产生,导致我们“不得不”去修改Master这个类的代码
public void feed(Cat cat){
cat.eat();
}
*/
// 能不能让Master主人这个类以后不再修改了,即使主人有喜欢养其他宠物了,Master也不需要修改。
// 这个时候就需要使用“多态机制”。
// 最好不要写具体的宠物类型,这样会影响程序的扩展性。可以编写一个更为抽象的父类,让这些具体的类去继承这个抽象的父类,实现多态机制。
public void feed(Pet pet){
// 编译的时候,编译器发现pet是Pet类,回去Pet类中找eat()方法,结果找到了,编译通过。
// 运行的时候,底层实际的对象是什么,就会自动调用到该实际对象对应的eat()方法上去。
// 这就是多态的使用。
pet.eat();
}
/*
注意这里的分析:
主人起初的时候只喜欢养宠物狗狗,随着时间的推移,主人有喜欢上养“猫咪”。
在实际的开发中这就是表示客户产生了新的需求,作为软件开发人员来说,必须满足客户的需求。
我们怎么去满足客户的需求呢?
在不使用多态进制的前提下,目前我们只能在Master类中添加一个新的方法。
思考:软件在扩展新需求过程当中,修改Master这个类有什么问题?
一定要记住:软件在扩展过程当中,修改的越少越好,修改的越多,你系统当前的稳定性就越差,未知的风险就越多。
其实这里涉及到一个软件的开发原则:
软件开发有七大原则,其中有一条最基本的原则:OCP(开闭原则)
什么是开闭原则?
对扩展开放(你可以额外添加,没问题),对修改关闭(尽量很少的修改现有程序)。在软件的扩展过程当中,修改的越少越好。
高手开发项目不是仅仅为了实现客户的需求,还需要考虑软件的扩展性。
面向父类型编程,面向更加抽象进行编程,不建议面向具体编程。因为面向具体编程会让软件的扩展力很差。
*/
}
Pet
package com.zh0u.多态在开发中的作用;
public class Pet {
public void eat(){
System.out.println("宠物能吃东西!");
}
}
Dog
package com.zh0u.多态在开发中的作用;
public class Dog extends Pet{
public void eat(){
System.out.println("宠物狗狗喜欢啃骨头,啃的很香!!!");
}
}
Cat
package com.zh0u.多态在开发中的作用;
public class Cat extends Pet{
public void eat(){
System.out.println("宠物猫猫喜欢吃鱼,吃的很香!!!");
}
}
Test
package com.zh0u.多态在开发中的作用;
public class Test {
public static void main(String[] args) {
// 创建一个主人
Master master = new Master();
// 创建一个主人喜欢的宠物狗狗
Dog dog = new Dog();
// 再创建一个主人喜欢的宠物猫猫
Cat cat = new Cat();
// 主人喂
master.feed(dog);
master.feed(cat);
}
}
多态在开发中的作用:降低程序的耦合度,提高程序的扩展力。
如以下代码:
public class Master{
public void feed(Dog d){}
public void feed(Cat c){}
}
以上的代码中表示:Master和Dog以及Cat的关系很紧密(耦合度高)。导致扩展力很差。
public class Master{
public void feed(Pet pet){}
}
以上的代表中表示:Master和Dog以及Cat的关系就脱离了,Master关心的是Pet类,这样Master和Dog以及Cat的耦合度就降低了,提高了软件的扩展性。
面向抽象编程,不建议面向具体编程。
super是一个关键字,全部小写。可以将super和this对比着学习。
this:
super:
重要结论:
当一个构造方法第一行,既没有this()有没有super()的话,默认会有一个super(),表示通过当前子类的构造方法调用父类的参数构造方法。所以必须保证父类的无参数构造方法是存在的。
super和this都不能出现在static修饰的代码中。
package com.zh0u.super关键字;
/*
* 1. 在恰当的时间使用:super(实际参数列表);
* 2. 注意:在构造方法执行过程中一连串调用了父类的构造方法,父类的构造方法有继续向上调用它父类的构造方法。但是实际上对象只创建了一个。
*
* 3. 思考:“super(实参)”到底是干啥的?
* super(实参)的作用是:初始化当前对象的父类型特征,并不是创建新对象,实际上对象只创建1个。
*
* 4. super关键字代表什么?
* super关键字代表的就是“当前对象”的那部分父类型特征。
*/
public class SuperTest03 {
public static void main(String[] args) {
Account account = new Account("65245892144456266", "400000");
System.out.println("账号: "+account.getActNo()+", 余额: "+account.getBalance());
CreditAccount creditAccount = new CreditAccount("65245892144456266","600000",0.9999);
System.out.println("账号: "+creditAccount.getActNo()+", 余额: "+creditAccount.getBalance()+", 信誉度: "+creditAccount.getCredit());
}
}
class Account{
// 账号
private String actNo;
// 余额
private String balance;
// 构造方法
public Account(){}
public Account(String actNo, String balance){
this.actNo = actNo;
this.balance = balance;
}
public String getActNo() {
return actNo;
}
public void setActNo(String actNo) {
this.actNo = actNo;
}
public String getBalance() {
return balance;
}
public void setBalance(String balance) {
this.balance = balance;
}
}
class CreditAccount extends Account {
// 信誉度
private double credit;
public CreditAccount(){}
public CreditAccount(String actNo, String balance, double credit){
// 私有属性只能在本类中使用
/*
this.actNo = actNo;
this.balance = balance;
*/
// 通过以上两行代码,得出在恰当的位置使用super(),即通过父类构造方法调用父类构造方法
super(actNo, balance);
this.setBalance(balance);
this.credit = credit;
}
public double getCredit() {
return credit;
}
public void setCredit(double credit) {
this.credit = credit;
}
}
this、super
package com.zh0u.super关键字;
public class SuperTest04 {
}
class Customer{
String name;
public Customer(){};
public Customer(String name){
// super(); 默认存在
this.name = name;
}
}
class Vip extends Customer{
public Vip(){};
public Vip(String name){
super(name);
}
public void shopping(){
// this表示当前对象
System.out.println(this.name +"正在购物!"); // 张三正在购物!
// super表示当前对象的父类型特征
System.out.println(super.name +"正在购物!"); // 张三正在购物!
System.out.println(name +"正在购物!"); // 张三正在购物!
}
}
当子类中的属性和父类中的属性(方法名)相同时,访问对应属性(方法)的时候应该加上this.或super.,即这个时候的super是不能省略的。
package com.zh0u.super关键字;
public class SuperTest04 {
}
class Customer{
String name;
public Customer(){};
public Customer(String name){
// super(); 默认存在
this.name = name;
}
}
class Vip extends Customer{
public Vip(){};
public Vip(String name){
super(name);
}
public void shopping(){
// this表示当前对象
System.out.println(this.name +"正在购物!"); // NULL正在购物!
// super表示当前对象的父类型特征
System.out.println(super.name +"正在购物!"); // 张三正在购物!
System.out.println(name +"正在购物!"); // NULL正在购物!
}
}
package com.zh0u.super关键字;
public class SuperTest06 {
public void doSome(){
// com.zh0u.super关键字.SuperTest06@6d6f6e28
System.out.println(this);
// 输出引用的时候会自动调用引用的toString()方法,System.out.println(this.toString());
// 以下语句会出现“需要'.'”的错误
//System.out.println(super);
/*
通过上面的语句得出结论:
super不是引用。super也不保存内存地址,super也不指向任何任何对象。
super只是代表当前对象内部的那一块父类型的特征。
this和super都不能出现在static静态方法中。
*/
}
public static void main(String[] args) {
SuperTest06 test06 = new SuperTest06();
test06.doSome();
}
}