抽象类
含有抽象(abstract关键字)方法的类即为抽象类。(抽象类可以不含抽象方法,但是这样抽象类本身意义就很小了)
抽象类和普通类的区别:
1. 抽象方法必须为public,protected(private不能被继承)2. 抽象类不能创建对象
3. 如果一个类为抽象类的子类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则子类也必须为抽象类。
接口
Java设计的接口(使用interface关键字)初衷:对行为的抽象。
接口中可以含有方法和变量
a. 接口中的变量:缺省且必须为public static final
b. 接口中的方法:缺省且必须为public abstract
抽象类和接口理解和应用
定义一个Person抽象类
1. 子类Boy
2. 子类Girl
public abstract class Person {
private boolean sex; //性别
abstract public void run(); //跑方法
}
class Boy extends Person{
@Override
public void run() {
System.out.println("Boy run");
}
public boolean getSex(){
return true;
}
}
class Girl extends Person{
@Override
public void run() {
System.out.println("Girl run");
}
public boolean getSex(){
return false;
}
}
定义一个Dog抽象类
1. 子类Bomex
2. 子类Shiba
public abstract class Dog {
public String type; //狗的种类
abstract public void run(); //跑方法
}
class Bomex extends Dog{
public void run() {
System.out.println("Bomex run");
}
public String getType(){
return "Bomex";
}
}
class Shiba extends Dog{
public void run() {
System.out.println("Shiba run");
}
public String getType(){
return "Shiba";
}
}
我们注意到:
在属性上,我们把男孩类和女孩类做了一个再抽象形成了Person抽象类。
把博美类和柴犬类做了一个再抽象形成了Dog抽象类。
在方法上,这几个类都拥有run()这个属性。即无论是是人还是小狗都会跑,这样就会存在大量的代码复写。
我们可以这样理解: 抽象类是对类的再抽象,决定一个对象“是不是”。
1. 即小明是不是男孩,男孩是不是人。
2. 我家旺旺是不是博美,博美是不是狗。
接口是对行为,对能力的抽象,决定一个对象"会不会"。
1. 即小明会跑
2. 旺旺也会跑
这样的话,可以建立一个run接口,无论是Person类、还是Dog类都实现该接口,从而实现其中run方法。
将代码重写:
新建一个Run接口,建立抽象方法run()
public interface Run {
abstract public void run();
}
Person改写为:
public abstract class Person implements Run{
private boolean sex; //性别
}
class Boy extends Person{
@Override
public void run() {
System.out.println("Boy run");
}
public boolean getSex(){
return true;
}
}
class Girl extends Person{
@Override
public void run() {
System.out.println("Girl run");
}
public boolean getSex(){
return false;
}
}
Dog类改写为:
public abstract class Dog implements Run{
public String type; //狗的种类
}
class Bomex extends Dog{
@Override
public void run() {
System.out.println("Bomex run");
}
public String getType(){
return "Bomex";
}
}
class Shiba extends Dog{
public void run() {
System.out.println("Shiba run");
}
public String getType(){
return "Shiba";
}
}
可以看出来,这样的改写,方便了对行为的管理。
接口和抽象类相同和区别
相同:
1. 接口和抽象类都不能被实例化
2. 接口和抽象类都可以包含抽象方法
区别:
在设计目的上: 接口作为系统与外界交互的窗口,体现的是一种规范。
对于设计实现者,接口规定了实现者必须向外提供哪些服务。
对于接口的调用者,接口规定了调用者可以调用那些服务。
对于多个程序而且,接口是多个程序之间的通信标准。
抽象类为多个子类的父类,体现的是一种模板式设计。
对于抽象类中的方法,是系统实现过程中的中间产品,这个中间产品需要经过子类的再完善,才能成为最终产品。
在使用上:
接口只能有抽象方法和静态变量
抽象类可以作为普通类使用,只是不能生成对象
类和对象
1. 如果用户没有自定义构造器,则系统会自动创建一个static属性的无参构造器。
2. 在实例化一个对象的过程中
a. 首先Java执行引擎会寻找是否已加载这个类,如果未加载,则先加载再生成对象。已加载,则直接生成对象
b. 在类加载的过程中,如果有static变量和static静态代码块,会先加载static属性。(按上下顺序)
c. 在生成类对象过程中,会先初始化类对象的所有成员变量(在程序的任何位置),再执行构造器
构造器完全负责对象的创建?
在实例化一个对象的过程中:
1. 首先系统为该对象分配内存空间,并为对象执行默认的初始化操作。此时对象已经产生了,只是外部还不能使用
2. 再调用构造器方法(注意我们常在构造器内使用this关键字,说明此时对象已经建立),调用完构造器后return出该类的实例化对象
3. 对于this关键字可以简单的理解和super关键字用法类似,this总是指向到调用该方法的对象,而super指向this指向对象的父对象。(java在创建一个子类对象,会隐式的创建该类的父类的对象,this指向子类对象,super指向父类对象,注意在子类构造器中调用父类构造器,使用super关键字需要放在第一句,同理,在一个子类构造器中调用另一个构造器使用this关键字也需要放在第一句)
继承
1. 一个子类只能继承一个父类,一个父类可以有多个子类
2. 子类能够访问父类的非pirvate变量和方法
3. 子类和父类之间变量和方法存在隐藏和覆盖的关系
a. 如果子类中出现和父类同名的成员变量,则子类成员变量会屏蔽父类的成员变量(隐藏现象), 如果要调用父类的成员变量,则需要使用super关键字。
b. 如果子类中出现和父类同名的成员方法,则子类成员方法会覆盖父类的成员方法的,使用super调用
c. 如果子类和父类有同名的静态方法,则会产生隐藏现象
(子类和父类同名现象会在多态上体现的更明显)
实际代码应用
1. 我们假设系统没有载入子类和父类,需要实例化一个子类对象
2. 理论上过程应该如下:
a.在加载子类前,会先加载父类,故先考虑父类
1) 先初始化父类里的static变量和方法
2) 初始化父类里的成员变量
3) 调用父类的构造方法
b. 父类加载完毕后,会加载子类,考虑子类
1) 先初始化子类里的static变量和方法
2) 初始化子类里的成员变量
3) 调用子类的构造方法
示例代码
package org.delphi.fan;
public class test {
public static void main(String[] args) {
new Student();
}
}
class Eat{
public Eat(String person){
System.out.println(person+" eat constructer.");
}
}
class Student extends Person{
Eat eat = new Eat("student");
public Student(){
System.out.println("student constructer.");
}
static{
System.out.println("student static code.");
}
}
class Person{
Eat eat = new Eat("person");
public Person(){
System.out.println("person constructer.");
}
static{
System.out.println("person static code.");
}
}
理论输出为:
person static code.
person eat constructer.
person constructer.
student static code.
student eat constructer.
student constructer.
实际输出为:
person static code.
student static code.
person eat constructer.
person constructer.
student eat constructer.
student constructer.
为什么会出现这个结果,是因为JVM的加载过程中,先考虑静态变量(代码块),再考虑继承关系.
对于有static属性的继承关系内,Java创建对象的完整过程为
1. 分配父类静态变量(静态代码块)
2. 分配子类静态变量(静态代码块)
3. 加载父类成员变量 --> 调用父类构造器 (父类对象创建完毕)
4. 加载子类成员变量 --> 调用子类构造器 (子类对象创建完毕)
对于子类和父类隐藏和覆盖的解释
1. 我们假设系统使用子类构造方法实例化一个父类对象,并调用父类和子类中同名的静态方法、成员方法和变量
2. 理论上过程应该如下:
1) 调用父类的构造方法
2) 调用子类的构造方法
3) 调用父类对象同名成员属性和静态成员方法时:存在隐藏现象(使用父类属性)
4) 调用父类对象同名方法时:存在覆盖现象(使用父类属性)
示例代码
package org.delphi.fan;
public class test {
public static void main(String[] args) {
Person person = new Student();
System.out.println("name:"+person.name);
person.eat();
person.fight();
}
}
class Student extends Person{
public String name="STUDENT";
public Student(){
System.out.println("student constructer.");
}
public void eat(){
System.out.println("student eat.");
}
public static void fight(){
System.out.println("student static fight.");
}
}
class Person{
public String name="PERSON";
public Person(){
System.out.println("person constructer.");
}
public void eat(){
System.out.println("person eat.");
}
public static void fight(){
System.out.println("person static fight.");
}
}
理论输出为:
person constructer.
student constructer.
name:PERSON
student eat.
person static fight.
实际输出为:
person constructer.
student constructer.
name:PERSON
student eat.
person static fight.
多态
Java引用变量有两个类型:一个是编译时的类型(声明该变量时使用的类型),一个是运行时的类型(实例化赋给该变量的对象类型)。
对于向上转型:
1. 引用变量只能调用声明该变量时所用类里包含的方法。
2. 引用方法会被子类(实例声明)覆盖,即方法行为总是子类方法行为
3. 而对象的属性则不具备多态性,即保持父类(编译声明)的变量属性
对于向下转型:
保证程序不会报错,使用
if(对象 instanceof 类名){//对象必须为子类或者父类的对象 否则返回false
子类名 子对象 = (子类名)父对象 ;
}
对于多态的作用,可以这么理解:
问题:现在我们需要喝水,对此实现方案,是编写一个水类,含有喝水drink方法。
class Water{
public void drink();
}
class NongFuSQ extends Water{
public void drink(){
yaoyiyao(); //NongFuSQ需要摇一摇
}
public void yaoyiyao(){
System.out.println("yaoyiyao!");
}
}
此时我们要喝水可以用这样代码实现 :
NongFuSQ water = new NongFuSQ();
water.drink();
class WaHaHa extends Water{
public void drink(){
addSuger(); //WaHaHa需要加糖操作
}
public void addSuger(){
System.out.println("addSuger!");
}
}
calss KangShiFu extends Water{
public void drink(){
addNode(); //KangShiFu需要和面条一起
}
public void addNode(){
System.out.println("addNode!");
}
}
//如果是哇哈哈
WaHaHa water = new WaHaHa();
water.drink();
</pre><pre name="code" class="java" style="font-size: 14px; font-weight: bold;">//如果是康师傅
KangShiFu water = new KangShiFu();
water.drink();
只要我一换水, 就需要改写代码,如果我在多处使用water对象,且使用了大量water包含的方法(方法中可能还包括一些子类特有的方法)。
Water water = new WaHaHa();
water.drink(); <span> </span>//喝水只需要调用一个water就可以
面向对象三大特征:
封装
继承
提高了代码复用,有利于代码的维护。
多态
==和equals比较运算符
public class Person {
private String id;
public String getID(){
return id;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
//
if (obj!=null && obj.getClass()==Person.class) {
Person per = (Person)obj;
if (this.getID().equals(per.getID())) { //判断对象ID属性是否相同
return true;
}
}
return false;
}
}