本文共 31463 字,大约阅读时间需要 104 分钟。
Java核心思想就是OOP
对于C语言来说,是完全面向过程的
对于C++语言来说,是一半面向过程,一半面向对象。 对于Java语言来说,是完全面向对象的面向过程思想(线性思维)
特点:
缺点:
优点:(快速开发)
采用面向对象的方式进行开发,更符合人类的思维方式
人类就是以对象的方式认识世界的
面向对象思想(分类思维)
对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但是,具体到微观操作,任然需要面向过程的思路去处理。
当我们采用面向对象的方式贯穿整个系统的话,设计以上三个术语
实现一个软件的过程:分析——>设计——>编码
面向对象编程的本质就是:以类的方式组织代码,以对象的形式(封装)数据
抽象
抽取现实中实体的共同点
三大特征
封装
将数据包装起来
继承
子类可继承父类,子类有了父类的所有东西
多态
同一个事物表现出不同的形态
从认识论角度考虑是先有对象后有类。对象,是具体的事物。类,是抽象的,是对对象的抽象
从代码运行角度考虑是先有类后有对象。类是对象的模板
对象还有另一个名字:实例
类是一种抽象的数据类型,实际上在现实世界中是不存在的,它是对某一类事物整体描述/定义,但是并不能代表某一个具体的事物。它表示一个模板。使我们人类大脑进行思考、总结、抽象的一个结果
类本质:是现实世界当中某些事物具有共同特征,将这些特征提取出来形成的概念就是一个类,类就是一个模板
对象是抽象概念的具体实例,是实际存在的个体
在java语言中,要想得到对象,必须先定义类,对象是通过类这个模板创造出来的
软件开发是为了解决现实生活中的问题。
首先java软件必须能够模拟现实世界
而软件是一个虚拟的世界,这个虚拟的世界需要和现实世界一一对应,这才叫做模拟。
[修饰符列表] class 类名{ //类体 = 属性 + 方法}
属性:在代码上以变量的形式存在(描述状态)
方法:描述动作/行为因为属性对应的是数据,数据在程序中只能方法变量中
结论:属性其实就是变量
使用new关键字创建对象
创建对象语法:
new 类名(); 类是模板,通过一个类,是可以创建N多个对象的 new是一个运算符。专门负责创建对象
使用new关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用。
类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下两个特点:
访问创建的对象语法:
类型 变量名 = new 类名(); Student s1 = new Student(); 和 int i =100; 解释一下: i是变量名 int是变量的数据类型 100是具体的数据 s1是变量名(s1不能叫对象,s1只是一个变量名字) Student是变量s1的数据类型(引用数据类型) new Student()这是一个对象(学生类创建出来的学生对象)
例如:通过Person pudding = new Person();
题一:创建两个用户对象
题二:用户类和地址类和测试类的关系 题三:宠物类和主类之间关系 栈里面:里面存放方法+变量的引用堆里面:存放我们创建的对象
对象是啥:堆里面new出来的
引用是啥:是存储对象内存地址的一个变量
package com.object;public class T { A o1; //成员变量中的实例变量。必须先创建对象,通过引用来访问 public static void main(String[] args) { D d = new D(); C c = new C(); B b = new B(); A a = new A(); T t = new T(); //这里不写代码会出现空指针异常 c.o4 = d; b.o3 = c; a.o2 = b; t.o1 = a; //编写代码通过t来访问d中的i System.out.println(t.o1.o2.o3.o4.i); }}class A{ B o2;}class B{ C o3;}class C{ D o4;}class D{ int i;}结果为:0
出现空指针异常的前提条件是?(本质)
“空引用”访问实例相关【对象相关】的数据时,都会出现空指针异常package com.object;/*关于垃圾回收器GC 在java语言中,垃圾回收器主要针对的是堆内存 当一个java对象没有任何引用指向该对象的时候 GC会考虑将该垃圾数据释放回收掉 */public class Test { public static void main(String[] args) { //创建客户对象 Customer c = new Customer(); System.out.println(c.id); c.id = 9479; System.out.println("客户的id是=" + c.id); c=null; //NullPointerException //编译器没问题,因为编译器只检查语法,编译器发现c是Customer类型 System.out.println(c.id); }}class Customer{ int id; //成员变量中的实例变量,应该先创建对象,通过引用.的方式访问}空指针异常对实例变量和实例方法都有关系
package com.Method;public class NullPointerTest { public static void main(String[] args) { User u = new User(); System.out.println(u.id); //0 u.doSome(); //引用变成空null u = null; //id的访问,需要对象的存在 //System.out.println(u.id); 空指针异常 //一个实例方法的调用也必须有对象的存在 u.doSome(); }}//类 = 属性 + 方法//属性描述状态//方法描述行为动作class User{ //实例变量 int id; //实例方法(对象相关的方法,对象级别的方法,应该是一个对象级别的行为) //方法模拟的是对象的行为动作 public void doSome(){ System.out.println("do some!"); }}
封装、继承、多态
有了封装,才有继承,有了继承,才能说多态
现实生活中有很多的例子都是封装的
例如:手机、电视、电脑、照相机这些都是外部有一个坚硬的壳儿。封装起来,保护内部的部件。保证内部的部件是安全的。另外封装了之后,对于我们使用者来说,我们看不见内部的复杂结构,我们也不需要关系内部有多么复杂,我们只需要操作外部壳儿上的几个按钮即可
封装的作用:
在代码级别上,封装有什么用?
总结:该露的露,该藏的藏
封装(数据的隐藏)
属性私有,get/set
我们如下不使用封装机制,Person类的age属性对外暴露,可以在外部程序中随意访问,导致不安全。
怎么解决这个问题?
封装不再对外暴露复杂的数据,封装起来
代码的实现步骤:
java开发规范注意事项:
get方法要求:
public 返回值类型 get+属性名首字母大写(无参){ }
set方法的要求:
public void set+属性名首字母大写(有一个参数){ xxx = 参数; }
package com.oop.demo04;//类 private:私有public class Student { //属性私有 private String name;// 名字 private int id;//学号 private char sex;//性别 private int age; //提供一些可以操作这个属性的方法! //提供一些的public 的get、set方法 //get获得这个数据 public String getName(){ return this.name; } //set给这个数据设置值 public void setName(String name){ this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public char getSex() { return sex; } public void setSex(char sex) { this.sex = sex; } public int getAge() { return age; } public void setAge(int age) { if (age>120 || age<0){ //不合法 this.age = 3; }else { this.age = age; } }}主类package com.oop;import com.oop.demo04.Student;/*封装的意义1. 提高程序的安全性,保护数据2. 隐藏代码的实现细节3. 统一接口4. 系统可维护性增加了 */public class Application { public static void main(String[] args) { Student s1 = new Student(); s1.setName("pudding"); System.out.println(s1.getName()); s1.setAge(-1);//不合法的 System.out.println(s1.getAge()); }}
继承:在现实世界当中也是存在的
例如:父亲很有钱,儿子不用努力也很有钱
继承的作用:
继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模
没有使用继承的时候代码如下:package com.extend;//分析以下代码存在什么问题,// 代码臃肿。代码没有得到重复利用public class ExtendsTest01 { public static void main(String[] args) { Account act = new Account(); act.setActno("111111"); act.setBalance(10000); System.out.println(act.getActno()+"余额"+act.getBalance()); CreditAccount ca = new CreditAccount(); ca.setActno("222222"); ca.setBalance(-10000); ca.setCredit(0.99); System.out.println(ca.getActno()+"余额"+ca.getBalance()+",信誉度"+ca.getCredit()); }}//银行账户类//账户的属性:账户、余额class Account{ //属性 private String actno; private double balance; //构造方法 public Account() { } public Account(String actno, double balance) { this.actno = actno; this.balance = balance; } //setter and getter方法 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; }}//其他类型的账户:信用卡账户//账号、余额、信誉度class CreditAccount{ //属性 private String actno; private double balance; private double credit; //构造方法 public CreditAccount() { } //setter and getter方法 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 double getCredit() { return credit; } public void setCredit(double credit) { this.credit = credit; }}
使用继承之后的代码如下:package com.Extends;//使用继承机制来解决代码复用问题public class ExtendsTest02 { public static void main(String[] args) { Account act = new Account(); act.setActno("111111"); act.setBalance(10000); System.out.println(act.getActno()+"余额"+act.getBalance()); CreditAccount ca = new CreditAccount(); ca.setActno("222222"); ca.setBalance(-10000); ca.setCredit(0.99); System.out.println(ca.getActno()+"余额"+ca.getBalance()+",信誉度"+ca.getCredit()); }}//银行账户类//账户的属性:账户、余额class Account{ //父类 //属性 private String actno; private double balance; //构造方法 public Account() { } public Account(String actno, double balance) { this.actno = actno; this.balance = balance; } //setter and getter方法 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; }}//其他类型的账户:信用卡账户//账号、余额、信誉度class CreditAccount extends Account{ //子类 //属性 private double credit; //构造方法 public CreditAccount() { } //setter and getter方法 public double getCredit() { return credit; } public void setCredit(double credit) { this.credit = credit; }}
① B类继承A类,则称A类为超类(superclass)、父类、基类,B类则称为子类(subclass)、派生类、扩展类
class A{ } class B extends A{ }
② java中的继承只支持单继承,不支持多继承,C++中支持多继承,这也是java体现简单性的一点,换句话说,java中不允许这样写代码:
class B extends A,C{ } 这是错误的
③ 虽然java中不支持多继承,但有的时候会产生间接继承的效果,
例如:class C extends B,class B extends A 也就是说,C直接继承B,C间接继承了A
package com.Extends;public class ExtendsTest03 { }class A{ }class B{ }class C extends A{ }class D extends B{ }/*语法错误java中只允许单继承,不允许多继承。java是简单的。C++支持多重继承C++更接近显示一些,因为在现实世界中儿子同时继承父母两方的特性class E extends A,B{}*/class X{ //X会默认继承Object类}class Y extends X{ }//其实这也说明了Z是继承X和Y的//这样描述:Z直接继承了Y,Z间接继承了Xclass Z extends Y{ }/* Z继承了Y Y继承了X X继承了Object Z对象具有Object对象的特征(基因) Object是所有类的超类,老祖宗,类体系结构的根 java这么庞大的一个继承结构,最顶点是:Object */
④ java中规定,子类继承父类,除构造方法不能继承之外,剩下的都可以继承。但是私有的属性无法在子类中直接访问。(父类中private修饰的不能在子类中直接访问。可以通过直接的手段来访问。即通过get和set方法)
⑤ java中的类没有显示的继承任何类,则默认继承Object类,Object类是java语言提供的根类(老祖宗类),也就是说,一个对象与生俱来就有Object类型中所有的特征
⑥ 继承也有一些缺点,例如:CreditAccount类继承Account类会导致它们之间的耦合度非常高,Account类发生改变之后会马上影响到CreditAccount类
extends的意思是扩展。子类是父类的扩展
Java中类只有单继承,没有多继承
测试:子类继承父类之后,能使用子类对象调用父类方法吗?
package com.Extends;public class ExtendsTest04 { public static void main(String[] args) { Cat c = new Cat(); c.move(); }}//父类//class Animal extends Objectclass Animal{ //名字(先不封装) String name = "pudding"; //提供一个动物移动的方法 public void move(){ System.out.println(name + "正在移动!"); }}// Cat子类// Cat继承Animal,会将Animal中所有的全部继承过来class Cat extends Animal{ }
理解子类继承父类的过程
package com.oop.demo05;//在Java中所有的类,都默认直接或间接继承Object类//person 人 父类public class Person /*extends Object*/{ public Person() { System.out.println("Person无参执行"); } protected String name = "pudding"; public void print(){ System.out.println("Person"); }}package com.oop.demo05;//学生 is 人 派生类/子类//子类继承了父类,就会拥有父类的全部方法!public class Student extends Person{ public Student() { //隐藏代码:调用了父类的无参构造super(); super(); //默认调用父类的构造器,必须要在子类构造器的第一行 System.out.println("Student无参执行"); } private String name = "张三"; public void print(){ System.out.println("Student"); } public void test(String name){ System.out.println(name); System.out.println(this.name); System.out.println(super.name); } public void test1(){ print(); this.print(); super.print(); }}main方法package com.oop;import com.oop.demo05.Person;import com.oop.demo05.Student;public class Application { public static void main(String[] args) { Student student = new Student(); student.test("李四"); student.test1(); }}
凡是采用"is a"能描述的,都可以继承 IS-A 就是说:一个对象是另一个对象的一个分类。
例如:
Cat is a AnimalDog is a Animal
假设以后的开发中有一个A类,有一个B类,A类和B类确实也有重复的代码,那么他们两个之间就可以继承吗?不一定,还是要看一看它们之间是否能够使用is a来描述。
任何一个类,没有显示继承任何类,默认继承Object,那么Object类当中有哪些方法呢?老祖宗为我们提供了哪些方法?
先模仿后超越
java为什么比较好学呢?
是因为java内置了一套庞大的类库,程序员不需要从0开始写代码,程序员可以基于这套庞大的类库进行"二次开发"。(开发速度较快,是因为JDK内置的这套库实现了很多基础的功能)例如:String是SUN用来编写字符串的类System是SUN用来编写系统类。这些类都可以直接拿来使用
理解如下代码:
package com.多态的基础语法;public class Test { //静态变量 static Student stu = new Student(); public static void main(String[] args) { //拆分为两行 Student s = Test.stu; //Test.stu返回的是一个学生对象 s.exam(); //学生对象里面有一个exam考试的方法 //合并代码 Test.stu.exam(); System.out.println("Hello World!"); }}class Student{ //实例方法 public void exam(){ System.out.println("考试...."); }}
研究了Object类中,其中toString()方法我们进行测试。
package com.Extends;public class ExtendsTest05 { //ExtendsTest05默认继承Obeject //ExtendsTest05类当中是有toString()方法 //不过toString()方法是一个实例方法,需要创建对象才能调用 /* public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } */ public static void main(String[] args) { ExtendsTest05 et = new ExtendsTest05(); String retValue = et.toString(); //154617c可以等同看做对象在堆内存当中的内存地址 //实际上是内存地址经过"哈希算法"得出的十六进制结果 System.out.println(retValue); //结果:com.Extends.ExtendsTest05@154617c //创建对象 Product pro = new Product(); System.out.println(pro.toString()); //com.Extends.Product@a14482 System.out.println(pro); //默认调用了toString方法了,结果:com.Extends.Product@a14482 }}class Product{ /* public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } */}
toString()方法的作用是什么
Object类中toString()方法默认实现是什么?
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode());}
toString:方法名的意思是转换成String
注意:显示调用父类构造器,即写super();方法的话,必须写在子类构造方法第一行,调用自己的构造器也是一样的,this();
如果写了有参构造之后,无参构造就没有了,想要使用的话,必须显示定于,所以重新写了有参构造最好加上无参构造
super总结:
VS this总结
代表的对象不同:
this:代表本身调用者这个对象super:代表父类对象的引用
前提不同:
this:没有继承也可以使用 super:只能在继承条件下才可以使用
构造方法区别:
this(); 本类的构造方法 super(); 父类的构造
子类继承父类之后,当继承过来的方法无法满足当前子类的业务需求时,子类有权对这个方法进行重写编写,有必要进行"方法的覆盖"。
父类的功能,子类不一定需要,或者不一定满足
package com.Override;/*当前程序的问题 鸟儿在执行move()方法的时候,最好输出结果是"鸟儿在飞翔" 但是当前的程序在执行move()方法的时候输出的结果是:动物在移动 很显然Bird子类从Animal父类中继承过来的move()方法已经无法满足子类的业务需求 */public class OverrideTest01 { public static void main(String[] args) { //创建鸟对象 Bird b = new Bird(); //让鸟移动 b.move(); }}//父类class Animal{ //移动 public void move(){ System.out.println("动物在移动"); }}//子类class Bird extends Animal{ //子类继承父类中,有一些行为可能不需要改进,有一些行为必须改进 //因为父类中继承过来的方法已经无法满足子类业务的需求 //鸟儿在移动的时候希望输出鸟儿在飞翔}
更深的理解一下
public class Application { //静态的方法和非静态的方法区别很大 //静态方法 //方法的调用只和左边,定义的数据类型有关 //非静态方法 //重写 public static void main(String[] args) { A a = new A(); a.test(); //A类 //父类的引用指向了子类 B b = new A(); //子类重写了父类的方法 b.test(); //B类 /* b是A new出来的对象,因此调用了A的方法 没有static时,b调用的是对象的方法,而b是A类new出来的 有static时,b调用了B类的方法,因为b是用b类定义的 因为静态方法是类的方法,而非静态是对象的方法 */ }}
对比一下方法重载。
什么时候考虑使用方法重载overload?
什么条件满足之后能够构成方法重载overload?
方法重载和方法覆盖的区别?
方法覆盖,就是将继承过来的那个方法给覆盖掉了。继承过来的方法没了
结论:
package com.Override;public class OverrideTest01 { public static void main(String[] args) { //创建鸟对象 Bird b = new Bird(); //让鸟移动 b.move(); //鸟儿在飞翔 Cat c = new Cat(); c.move(); //猫在走猫步 }}//父类class Animal{ //移动 public void move(){ System.out.println("动物在移动"); }}//子类class Bird extends Animal{ //对move方法进行方法覆盖,方法重写,override //最好将父类中的方法原封不动的复制过来 public void move(){ System.out.println("鸟儿在飞翔"); }}class Cat extends Animal{ //方法重写 public void move(){ System.out.println("猫在走猫步"); }}
注意二举例理解:
//私有方法不能覆盖public class OverrideTest05 { //私有方法 private void doSome(){ System.out.println("OverrideTest05's private method"); } public static void main(String[] args) { OverrideTest05 ot = new OverrideTest05(); ot.doSome(); //OverrideTest05's private method }}//子类class T extends OverrideTest05{ //尝试重写父类中的dosome方法 //访问权限不能更低,可以更高 public void doSome(){ System.out.println("T's public dosome method"); }}
注意三举例理解:
/* 1. 方法覆盖需要和多态机制联合起来使用才有意义 Animal a = new Cat(); a.move(); 要的是什么效果? 编译的时候move()方法是Animal的 运行的时候自动调用到子类重写move()方法上 方法覆盖和多态不能分开 2. 静态方法存在方法覆盖吗? 多态自然和对象有关系了 而静态方法的执行不需要对象 所以一般情况下,我们会说静态方法不存在方法覆盖 */public class OverrideTest04 { public static void main(String[] args) { //静态方法可以使用"引用."来调用 //虽然使用"引用."来调用,但是和对象无关 Animals a = new Dog(); a.dosome(); //Animal的dosome方法执行 Dog.dosome();//Dog的dosome方法执行 //静态方法和对象无关 //虽然使用"引用."来调用。但是实际运行的时候还是:Animal.dosome() }}class Animals{ //父类的静态方法 public static void dosome(){ System.out.println("Animal的dosome方法执行"); }}class Dog extends Animals{ //尝试在子类当中对父类的静态方法进行重写 public static void dosome(){ System.out.println("Dog的dosome方法执行"); }}
在方法覆盖中,关于方法的返回值类型。什么条件满足之后,会构成方法的覆盖呢?
学习了多态机制之后:"相同的返回值类型"可以修改一下吗?
package com.Override;public class OverrideTest02 { public static void main(String[] args) { // Chinese p1 = new Chinese("张三"); 错误原因,构造方法无法继承,子类没有带参数的构造方法 Chinese p1 = new Chinese(); p1.setName("张三"); p1.speak(); American p2 = new American(); p2.setName("Jack"); p2.speak(); }}class Person{ //属性 private String name; //构造 public Person() { } public Person(String name) { this.name = name; } //setter and gett public String getName() { return name; } public void setName(String name) { this.name = name; } //人都会说话 public void speak(){ System.out.println(name + "正在说话"); }}//中国人class Chinese extends Person{ //中国人说汉语,需要对speak()方法进行重写 public void speak(){ System.out.println(this.getName() + "说中文"); }}//美国人class American extends Person{ //美国人说英语,需要对父类speak重写 @Override public void speak() { System.out.println(getName() + "说英语");; }}
toString()方法存在的作用就是:将java对象转换成字符串形式
toString()方法给的默认实现不够用,我们就重新覆盖toString()方法
大多数的java类toString()方法都需要覆盖的,因为Object类中提供的toString()方法输出的是一个java对象的内存地址
至于toString()方法具体怎么进行覆盖?
package com.Override;public class overrideTest03 { public static void main(String[] args) { //创建一个日期对象 MyDate t1 = new MyDate(); //调用toString()方法(将对象转换成字符串形式) //重写MyDate中toString()方法之前的结果 //System.out.println(t1.toString()); //com.Override.MyDate@154617c //重写MyDate中toString()方法之后的结果 System.out.println(t1.toString()); //1970年1月1日 //当输出一个引用的时候,println方法会自动调用引用的toString方法 System.out.println(t1); //1970年1月1日 }}//日期类class MyDate{ private int year; private int month; private int day; public MyDate() { this(1970,1,1); } public MyDate(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } public int getYear() { return year; } public void setYear(int year) { this.year = year; } public int getMonth() { return month; } public void setMonth(int month) { this.month = month; } public int getDay() { return day; } public void setDay(int day) { this.day = day; } //从Object类中继承的那个toString()方法以及无法满足我业务需求了 //我在子类MyDate中有必要对父类的toString()方法进行覆盖/重写 //我的业务要求是:调用toString()方法转换时候,希望出现结果:xxxx年xx月xx日格式 public String toString(){ return year + "年" + month + "月" + day + "日"; }}
学习多态基础语法之前,我们需要普及两个概念
第一个:向上转型(upcasting):子—>父(自动类型转换)
Animal a = new Cat();
第二个:向下转型(downcasting):父—>子(强制类型转换,需要加强制类型转换符)
Cat c = (Cat)a;
注意:java中允许向上转型,也允许向下转型
无论是向上转型,还是向下转型,两种类型之间必须有继承关系,没有继承关系编译器报错注意:在以后的工作中,要专业一点,说向上转型和向下转型,不要说自动类型转换,也不要说强制类型转换,因为自动类型转换和强制类型转换是使用在基本数据类型方面的,在引用类型转换这里只有向上和向下转型
总结:
即同一个方法可以根据发送对象的不同而采用多种不同的行为方式
一个对象的实际类型是确定的,但可以指向对象的引用的类型有很多(父类、有关系的类)
多态存在条件
注意:多态是方法的多态,属性没有多态性
/*多态注意事项: 1. 多态是方法的多态,属性没有多态 2. 父类和子类,有联系 3. 多态存在的条件:继承关系,方法需要重写,父类引用指向子类对象! Father f1 = new Son(); 不能被重写的方法 1. static静态的方法,属于类,不属于实例 2. final 常量 3. private方法 */
多态是同一个行为具有多个不同表现形式或形态的能力。
多态性是对象多种表现形式的体现。
比如我们说"宠物"这个对象,它就有很多不同的表达或实现,比如有小猫、小狗、蜥蜴等等。那么我到宠物店说"请给我一只宠物",服务员给我小猫、小狗或者蜥蜴都可以,我们就说"宠物"这个对象就具备多态性。
多态指的是:多种形态,多种状态
多态的典型代码:父类型引用指向子类型对象(java中允许这样写代码)
Animal a = new Cat();a.move();编译的时候编译器发现a的类型是Animal,所以编译器会去Animal类中找move()方法找到了,绑定,编译通过。但是运行的时候和底层堆内存当中实际对象有关真正执行的时候会自动调用"堆内存中真实对象"的相关方法。
注意:
java中只有"类名"或者"引用"才能去"点"以下的类都用于举例理解多态,而创建的类
多态的向上转型理解:public class Test01 { public static void main(String[] args) { Animal a1 = new Animal(); a1.move(); Cat c1 = new Cat(); c1.move(); Bird b1 = new Bird(); b1.move(); /* 代码可以如下这样写吗? 1. Animal和Cat之间有继承关系吗? 有的 2. Animal是父类,Cat是子类 3. Cat is a Animal 这句话能不能说通? 能 4. 经过测试得知java中支持这样的一个语法 父类型的引用允许指向子类型的对象 Animal a2 = new Cat() a2就是父类型的引用 new Cat()是一个子类型的对象 允许a2这个父类型引用指向子类型的对象 */ Animal a2 = new Cat(); Animal a3 = new Bird(); /* 什么是多态? 多种形态,多种状态 分析:a2.move(); java程序分为编译阶段和运行阶段 先来分析编译阶段: 对于编译器来说,编译器只知道a2的类型是Animal, 所以编译器在检查语法的时候,会去Animal.class字节码文件去找 move()方法,找到了,绑定上move()方法, 编译通过,静态绑定成功。(编译阶段属于静态绑定) 再来分析运行阶段: 运行阶段的时候,实际上在堆内存中创建的java对象是Cat对象, 所以move的时候,真正参与move的对象是一只猫, 所以运行阶段会动态执行Cat对象的move()方法。 这个过程属于运行阶段绑定(运行阶段绑定属于动态绑定) 多态表示多种形态: 编译的时候一种形态 运行的时候一种形态 */ //调用a2的move()方法 a2.move(); //cat在走猫步 //调用a3的move()方法 a3.move(); //鸟儿在飞翔 }}
多态的向下转型理解
public class Test01 { public static void main(String[] args) { Animal a5 = new Cat(); //底层对象是一只猫 //a5.catchMouse(); 错误 //因为:编译器只知道a5的类型是Animal,去Animal.class文件夹中找catchMouse()方法 //结果没有找到,所以静态绑定失败,编译报错,无法运行(语法不合法) //当我非要调用catchMouse()方法怎么办 //这个时候必须使用"向下转型了"(强制类型转换) Cat x = (Cat)a5; //因为a5是Animal类型,转成Cat,Animal和Cat之间存在继承关系。所以没报错 x.catchMouse(); //猫正在抓老鼠 //向下转型有风险吗? Animal a6 = new Bird(); //表面上a6是一个Animal,运行的时候实际上是一个鸟儿 /* 分析以下程序 编译器检测到a6这个引用是Animal类型, 而Animal和Cat之间存在继承关系,所以可以向下转型 编译没毛病 运行阶段,堆内存实际创建的对象是:Bird对象 在实际运行过程中,拿着Bird对象转换成Cat对象就不行了 因为Bird和Cat之间没有继承关系 运行出现了异常,这个异常非常的重要 java.lang.ClassCastException:类型转换异常 java.lang.ClassCastException:空指针异常 */ System.out.println(a6 instanceof Cat); //false if (a6 instanceof Cat){ //如果a6是一只Cat Cat y = (Cat)a6; //再进行强制类型转换 y.catchMouse(); } }}
主方法public class Application { public static void main(String[] args) { //一个对象的实际类是确定的 //new Student() //new Person //可以指向的引用类型就不确定了 //Student能调用的方法都是自己的或者继承父类的 Student s1 = new Student(); //父类的引用指向了子类 //Person父类型,可以指向子类,但是不能调用子类独有的方法 Person s2 = new Student(); Object s3 = new Student(); //对象能执行哪些方法,主要看对象左边的类型,和右边关系不大 //如果对象左边类型里面没有的方法,这个对象是无法调用的 s2.run();//子类重写了父类的方法,执行了子类的方法 s1.run(); }}
需要调用或执行子类对象特有的方法,才需要强制类型转换(向下转型)
向下转型有风险吗?
容易出现ClassCastException(类型转换异常)怎么避免这个风险?
instanceof运算符,可以在程序运行阶段动态的判断某个引用指向的对象是否为某一种类型养成好习惯,向下转型之前一定要使用instanceof运算符进行判断
作用:判断两个类之间是否存在父子关系
第一:instanceof可以在运行阶段动态判断引用执行的对象的类型
第二:instanceof的语法:(引用 instanceof 类型)
第三:instanceof运算符的运算结果只能是:true/false
第四:c是一个引用,c变量保存了内存地址指向了堆中的对象。
假设(c instanceof Cat)为true表示: c引用指向的堆内存中的java对象是一个Cat 假设(c instanceof Cat)为false表示: c引用指向的堆内存中的java对象不是一个Cat
程序员要养成一个好习惯:
/*这个代码的疑问? 肉眼可以观察到底层是new Bird()还是new Cat() 我们为什么还要进行instanceof的判断呢 原因: 你以后可能肉眼看不到 */public class Test02 { public static void main(String[] args) { Animal x = new Bird(); Animal y = new Cat(); if (x instanceof Bird){ Bird b = (Bird)x; b.sing(); //鸟儿在歌唱 } else if (x instanceof Cat){ Cat c = (Cat)x; c.catchMouse(); } if (y instanceof Bird){ Bird b = (Bird)y; b.sing(); } else if (y instanceof Cat){ Cat c = (Cat)y; c.catchMouse(); //猫正在抓老鼠 } }}
为什么需要上述这样写能,不麻烦吗?
解释如下:还有一个例子看一看:
public class Application { public static void main(String[] args) { //Object > String //Object > Person > Teacher //Object > Person > Student Object object = new Student();// System.out.println(X instanceof Y); //编译能不能通过,看X和Y有没有父子关系 System.out.println(object instanceof Student); //true System.out.println(object instanceof Person); //true System.out.println(object instanceof Object); //true System.out.println(object instanceof Teacher); //false System.out.println(object instanceof String); //false System.out.println("============================="); Person person = new Student(); System.out.println(person instanceof Student); //true System.out.println(person instanceof Person); //true System.out.println(person instanceof Object); //true System.out.println(person instanceof Teacher); //false// System.out.println(person instanceof String); //编译报错 System.out.println("============================="); Student student = new Student(); System.out.println(student instanceof Student); //true System.out.println(student instanceof Person); //true System.out.println(student instanceof Object); //true// System.out.println(student instanceof Teacher); //编译报错// System.out.println(person instanceof String); //编译报错 }}
编写程序模拟"主人"喂养"宠物"的场景 主人类:Master 宠物类:Pet 宠物类子类:Dog、Cat、YingWu 主人应该有的喂养方法:feed() 宠物应该有吃的方法:eat() 只要主人喂宠物,宠物就吃要求:主人类中只提供一个喂养方法feed(),要求达到可以喂养各种类型的宠物编写测试程序: 创建主人对象 创建各种宠物对象 调用主人的喂养方法feed(),喂养不同的宠物,观察执行结果
没有使用多态之前的版本
改进之后的多态版本package com.多态在开发中的作用;//主人类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 = new Dog() //编译的时候,编译器发现pet是Pet类,会去Pet类中找eat()方法,结果找打了,编译通过 //运行的时候,底层实际对象是什么,就会自动调用该实际对象对应的eat()方法上 //这就是多态的使用 pet.eat(); 注:这里不能使用Object,虽然Object是所有对象的父类, 但是Object中没有eat方法 }}/* 1. 注意这里的分析: 主人起初的时候只喜欢养宠物狗狗 随着时间的推移,主人又喜欢上养"猫咪" 这就表示在实际开发中这就表示产生了新的需求 作为软件开发人员来说,必须满足客户的需求 2. 在不使用多态的机制下,目前我们只能在Master类中添加一个新的方法 3. 思考:软件在扩展新需求过程当中,修改Master这个类有什么问题? 一定要记住:软件在扩展过程当中,修改的越少越好 修改的越多,你的系统当前的稳定性就越差,为止的风险就会越多 其实这里涉及到一个软件的开发原则: 软件开发原则有七大原则(不属于java,这个开发原则属于整个软件业): 其中一条最基本的原则:OCP(开闭原则) 什么是开闭原则? 对扩展开放(你可以添加),对修改关闭(最好很少的修改现有程序) 在软件的扩展过程当中,修改的越少越好 4. 高手开发项目不仅仅为了实现客户的需求,还需要考虑软件的扩展性 软件的扩展性:例如电脑的内存条坏了,买一个新的插上,直接使用。 这个电脑设计就考虑了扩展性 5. 面向父类型编程,面向更加抽象进行编程,不建议面向具体编程 因为面向具体编程会让软件的扩展力很差 */
降低程序的耦合度,提高程序的扩展力
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的耦合度就降低了,提高了软件的扩展性
面向对象的三大特征:
abstract修饰符可以用来修饰方法也可以用来修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类,那么该类就是抽象类。
特点
作用:
提高开发效率
普通类:只有具体实现
抽象类:具体实现和规范(抽象方法)都有! 接口:只有规范!(实现了了约束和实现分离)接口就是规范,定义的一组规则,体现了现实世界中"如果你是…则必须能…"的思想。
接口的本质就是契约,就像我们人间的法律一样,制定好大家都去遵守
OO的精髓,是对对象的抽象,最能体现这一点的就是接口。为什么我们讨论设计模式都只针对具备了抽象能力的语言(如Java、C++、C#等)就是因为设计模式所研究的,实际上就是如何合理的去抽象。
声明类的关键字是class,声明接口的关键字是interface
接口的作用:转载地址:http://hqxzi.baihongyu.com/