首页 > 代码库 > 黑马程序员——面向对象(一)概述、类与对象、继承、抽象类、接口、多态、内部类
黑马程序员——面向对象(一)概述、类与对象、继承、抽象类、接口、多态、内部类
一、面向对象概述
1、面向对象:是一个很抽象的概念,它相对面向过程而言,是一种程序设计的思想。
2、面向对象的特点:
* 符合人们思考习惯的思想
* 可以将复杂的事情简单化
* 将程序员从执行者转换成了指挥者
* 完成需求时:只需要寻找具备所需功能的对象或者创建所需功能的对象即可,简化开发效率并提高对象的复用性。
3、面向对象的三个特征:封装(Encapsulation)、继承(Inheritance)、多态(Polymorphism)。
二、类与对象
(一)类与对象之间的关系
1、类的定义:类是一组事物共有特征和功能的描述。类是对于一组事物的总体描述。Java用类class来描述对象的属性和行为。
2、对象:对象是类的实例,
3、类的定义格式:
[修饰符] class类名{
属性;
构造方法;
一般方法;
}
(二)成员变量和局部变量的区别
成员变量:
* 成员变量定义在类中,在整个类中都可以被访问。
* 成员变量随着对象的建立而建立,存在于对象所在的堆内存中。
* 成员变量有默认初始化值。
局部变量:
* 局部变量只定义在局部范围内,如:函数内,语句内等。
* 局部变量存在于栈内存中。
* 作用的范围结束,变量空间会自动释放。
* 局部变量没有默认初始化值。
(三)构造函数
1、特点:
(1)函数名与类名相同。
(2)无返回值。
2、作用:给对象进行初始化。对象一建立就会调用与之对应的构造函数。
3、构造函数的细节:
当一个类中没有定义构造函数时,系统就会默认给该类加入一个空参数的构造函数。当在类中自定义了构造函数后,默认的构造函数就没有了。
4、构造函数和一般函数的区别?
(1)写法上不同。
(2)在运行上也有不同:构造函数式在对象一建立就运行,给对象初始化。而一般方法是对象调用时才执行,是给对象添加对象具备的功能。
(3)一个对象建立,构造函数只运行一次。而一般方法可以被该对象调用多次。
5、什么时候定义构造函数?
当分析事物时,该事物存在具备一些特性或者行为,那么将这些内容定义在构造函数中。
6、构造代码块
{ System.out.println(“person code run”); }
作用:给对象进行初始化。对象一建立就运行,而且优先于构造函数执行。
“构造函数”与“构造代码块”的区别总结:(1)优先级不同,构造代码块优先于构造函数;(2)初始化对象不同。构造代码块中定义的是不同对象共性的初始化内容,是给所有对象进行统一初始化。而构造函数是给对应的对象初始化。
(四)this关键字
1、作用:指向当前类的属性和方法。
2、说明:在类定义中,如果“局部变量”和“成员变量”同名,则在将“局部变量”赋给“成员变量”时,可以使用this关键字,例如使用方式可以使:this.name = name 。
3、问题:this看上去好像是为了区分变量同名,即区分局部变量和成员变量。this为什么可以解决这种区分?它到底代表什么呢?
解答:this代表本类的对象,代表它所在函数所属的对象的引用。因为this持有本类对象的引用,所以this单独使用代表当前对象。
4、补充:this也可以使用在构造函数中调用其他的构造函数。在构造函数中,用this调用其他构造函数,必须写在第一行,因为初始化要先执行。。
5、this关键字的基本应用(Java代码)
需求:this关键字用于“构造函数”间调用
1 class Person{2 Person (String name){3 this.name = name;4 }5 Person( String name , int age ){6 this( name ); //等价于 this.name = name,即调用上面的构造函数7 this.age = age;8 }9 }
6、关于this及构造函数的总结:
(1)用于区分同名变量的情况,说的成员和局部同名的时候;
(2)用于构造函数间调用,this语句只能定义在构造函数的第一行。
(3)构造函数之间相互调用,初始化动作先执行。初始化动作中的初始化要先执行;构造函数自身的初始化,最后执行。
(4)注意:构造杉树之间的相互调用初始化,会陷入死循环。
(五)static关键字
1、static关键字说明
static是一个修饰符,用于修饰成员(成员变量和成员函数)。当类成员被static修饰后,就多了一个调用方式,除了可以被对象调用外,还可以直接通过类名调用。格式:类名.静态成员。
2、static的特点
(1)随着类的加载而加载,随着类的消失而消失,其生命周期最长。
(2)优先于对象存在。(静态是先存在。对象是后存在)
(3)被所有对象所共享。
(4)可以直接通过类名所调用。
(5)static修饰的数据是共享数据。由static修饰的变量称为“静态变量”或“类变量”。可被对象调用的非静态变量称为“成员变量”或“实例变量”。
3、实例变量和类变量(静态变量)的区别
(1)存放位置:类变量随着类的加载而存在于“方法区”中。实例变量随着对象的建立而存在“堆内存”中。
(2)生命周期:类变量生命周期最长,随着类的消失而消失。实例变量生命周期随着对象的消失而消失。
4、static使用注意事项
(1)static方法只能访问“静态成员”。非static方法既可以访问static成员,也可以访问非静态成员。
(2)static方法中,不可以定义this,super等关键字。因为静态优先于对象存在。
(3)main函数是静态的。
5、static的利弊
利:对对象共享数据进行单独空间的存储,节省空间;可以直接被类名调用。
弊:生命周期过长。访问出现局限性。(静态虽好,只能访问静态)。
6、main函数是静态的
(1)主函数是一个特殊的函数,是程序的入口,可以被JVM调用。
(2)主函数定义:public:代表着该函数访问权限是最大的。static:代表主函数随着类的加载就已经存在了。void:主函数没有具体的返回值。main:不是关键字,但是一个特殊的单词,可以被JVM识别。String[] arr:函数的参数,是一个字符串数组。
(3)主函数是固定格式的:被JVM识别。JVM在调用主函数时,传入的是new String[0]。可以将main函数的参数args输出查看。
7、什么时候使用static?
(1)从两方面下手: 即“静态变量”和“静态函数”。
(2)什么时候定义静态变量(类变量)呢?当对象中出现共享数据时,该数据被静态所修饰。对象中的特有数据要定义成非静态,存在于堆内存中。
(3)什么时候定义静态函数呢?当功能内部没有访问到非静态数据(对象的特有数据),那么该功能可以定义成静态的。
8、Java帮助文档的制作(Javadoc)
在类文件ArrayToolDemo.java文件中,有文档注释如下:
/**
这是一个可以对数组进行操作的工具类,提供了……功能。
@ author xxx
@ version v1.0
@ param xxx
@ return xxx
*/
在dos命令行中输入命令:javadoc -d myhelp -author-version ArrayToolDemo.java,则Java中的javadoc工具就会帮我们完成帮助文档(也称API文档)的制作。
9、静态代码块
(1)定义格式:static { 静态代码块中的语句 }
(2)特点:随着类的加载而执行,只执行一次。并优先先于main函数。用于对类进行初始化。
10、对象的初始化过程
定义一个对象,都做了什么事情?
(1)把类名.class文件加载到内存中。
(2)执行该类中的static代码块,如果有得话,给该类进行初始化。
(3)在堆内存中开辟空间,分配内存地址给对象。
(4)在堆内存中建立对象的特有属性,并进行默认初始化。
(5)对属性进行显示初始化。
(6)对对象进行构造代码块初始化。
(7)对对象进行对应构造函数初始化。
(8)将内存地址赋给栈内存中的对象名变量。
(六)单例设计模式
1、设计模式:解决某一问题最行之有效的方法。java有23种设计模式。
2、单例设计模式:解决一个类在内存中只存在一个对象的问题。
3、问题:如何保证对象的唯一呢?
(1)为避免其他程序过多建立该类对象,先禁止其他程序建立该类对象。
(2)为了让其他的程序可以访问到该类对象,只好在本类中自定义一个对象。
(3)为方便其他程序对自定义对象的访问,可以对外提供一些访问方式。
上述三步如何通过代码实现来体现呢?
(1)将构造函数用private私有化。
(2)在类中创建一个本类对象。
(3)提供一个方法可以获取该类对象。
注解:对事物该怎么描述,还怎么描述。当需要将该事物的对象保证在内存中唯一时,就加上以上三步。
4、饿汉式、懒汉式
(1)单例设计模式一:饿汉式
class Single{ private Single(){} private static Single s = new Single(); public static Single getInstance(){ return s; }}
注释说明:饿汉式的特点是先初始化对象。如:Single类一进内存,就已经创建好了对象。在实际开发中,出于安全考虑,建议使用此方式。
(2)单例设计模式二:懒汉式(又称:延时加载)
1 class Single 2 { 3 private static Single s=null; 4 private Single(){} 5 public static Single getInstance() 6 { 7 If(s==null) 8 s=new Single(); 9 return s;10 }11 }
注释说明:是方法被调用时,才进行初始化。如:以上代码中,Single类进内存,对象还没有存在,只有调用了getInstance()方法时,才创建对象。在多线程同时调用时,懒汉式易出问题。
三、继承(extends)
(一)继承的概述
继承是面向对象的一个重要特征。当多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承抽取共同属性的类即可。这时,多个类可以称为子类,单独的这个类被称为父类或超类。
当类与类之间就存在了继承的关系,子类可以直接访问父类中的非私有的属性和行为。在代码中通过extends关键字表示继承关系。
继承的定义格式:格式:[修饰符] class SubClass extends SuperClass{}
(二)继承特点
1、提高了代码的复用性。
2、让类之间产生关系,催生了类的多态特性。
3、类与类之间的所属关系是is-a
4、java语言只支持单继承,不支持多继承(因为多继承带来安全隐患,当多个父类中定义了相同功能,当功能内容不同时,子类对象不确定要运行哪一个)
5、Java保留了“多继承”机制,用“接口(interface)”完成“多实现”,实现接口的关键字implements
6、Java支持“多层继承”,也就是一个继承体系。
(三)子父类中成员变量
继承关系出现后,类成员包括:变量,函数,构造函数。
1、变量
如果子类中出现非私有的同名成员变量时,子类要访问本类中的变量,用this。子类要访问父类中的同名变量,用super。
super的使用和this的使用几乎一致,且两者都存在于方法区中。this表示本来对象的引用。super表示父类对象的引用。
2、子父类中函数的特点——覆盖(重写)
当子父类出现同名函数时,当子类对象调用该函数,会运行子类函数的内容。如同父类的函数被覆盖一样。这种情况是函数的另一个特性:重写(覆盖)。
当子类继承父类,沿袭了父类的功能到子类中。但子类虽具备该功能与父类不同,这时,没有必要定义新功能,而是使用覆盖特性,保留父类的功能定义,并重写功能内容。子类同时具有父类方法中的内容时,可以用super.方法()。
注意:(1)子类覆盖父类,必须保证子类权限大于父类权限,才可以覆盖,否则编译失败。(2)静态只能覆盖静态。(3)父类中的私有方法不能被覆盖。
重载(Overload):只看同名函数的参数列表。
重写(Override):子父类方法要一模一样。
3、构造函数
在对子类对象进行初始化时,父类构造函数也会运行。那是因为子类的每一个构造函数默认第一行有一条隐式的语句super()。super():会访问父类中空参数的构造函数。而且子类中所有的构造函数默认第一行都是super()。
问题:为什么子类实例化一定要访问父类中的构造函数?
因为父类中的数据子类可以直接获取,所以子类在建立对象时,需要先查看父类是如何对这些数据进行初始化的。所以子类在对象初始化时,要先访问一下父类中的构造函数。
如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式来指定。在上面的示例中已经有了很好的体现。
注:super语句一定定义在子类构造函数中的第一行。
构造函数结论:
① 子类的所有构造函数,默认都会访问父类中空参数的构造函数。因为子类的每一个构造函数内的第一行都有一句隐式super()。
② 当父类中没有空参数的构造函数时,子类必须手动通过supe语句形式来指定要访问父类中的构造函数。
③ 当然,子类的构造函数第一行也可以手动指定this语句形式来访问本类中的构造函数。子类中至少会有一个构造函数会访问父类中的构造函数。
知识点:为什么this()和super()不能在同一个构造函数中?因为它们都位于够咱函数的第一行,且两不能在同一行。为什么不能再同一行?因为初始化动作要先做,而在子类构造函数中必有一个this语句或者super语句。
(四)final关键字
final作为一个修饰符。具有以下特点:
1、可以修饰类、函数、变量。
2、被final修饰的类不可以被继承,这样就可以避免被继承、被子类复写功能,打破“封装性”。
3、被final修饰的方法不可以被覆写。
4、被final修饰的变量是一个常量,只能赋值一次,既可以修饰“成员变量”,又可以修饰“局部变量”。(作为常量:所有的字母都大写;多个单词,用下划线连接)。
5、内部类定义在类中的局部位置上时,只能访问该局部被final修饰的局部变量。
四、抽象类(abstract class)
(一)定义
1、Java中可以定义没有方法体的方法,该方法的具体实现由子类完成,该方法称为抽象方法,包含抽象方法的类就是抽象类。
2、抽象类的由来
多个对象都具备相同的功能,但是功能具体内容有所不同,那么在抽取过程中,只抽取了功能定义,并未抽取功能主体,那么只有功能声明,没有功能主体的方法称为抽象方法。
(二)抽象的特点
1、抽象方法一定定义在抽象类中。
2、抽象类和抽象方法必须用abstract关键字来修饰。
3、 抽象类不可以被实例化,即不可以用new创建对象。
4、抽象类中的抽象方法被使用,必须有子类覆写后,建立子类对象进行调用。如果子类只覆盖了部分抽象方法,那么子类还是一个抽象类。
5、抽象方法只有方法声明,没有方法体,定义在抽象类中。
注:抽象类中可以有非抽象的方法。
6、抽象类及抽象方法的定义格式,示例代码如下:
1 abstract class Demo{2 abstract void show(); //抽象方法3 abstract void inof(); //抽象方法4 void turn(){} //一般方法5 }
(三)抽象类的一些细节问题
1、抽象类中有构造函数吗?
答:有,用于给子类对象进行初始化。
2、抽象类可以不定义抽象方法吗?
答:可以,但很少见,目的是不让该类创建对象。AWT的适配器对象就是这种类。
3、抽象关键字不可以和那些关键字共存?
答:private(因为方法要被覆盖)、static(不用创建对象)、final(覆盖还是不覆盖?)
4、抽象类和一般类的异同点:
相同点:抽象类和一般类都是用来描述事物的,都在内部定义成员。
不同点:(1)一般类有足够的信息描述事物。抽象了描述事物的信息有可能不足。(2)一般类中不能定义抽象方法,只能定义非抽象方法。抽象类中既可以定义抽象方法,又可以定义非抽象方法。(3)一般类可以被实例化。抽象方法不可以被实例化。
5、抽象类一定是个父类吗?是的。
(四)抽象类应用示例代码
/* 需求:假如我们在开发一个系统时需要对员工进行建模,员工包含 3 个属性:姓名、工号以及工资。经理也是员工,除了含有员工的属性外,另为还有一个奖金属性。请使用继承的思想设计出员工类和经理类。要求类中提供必要的方法进行属性访问。
分析:员工类:name id pay。经理类:继承了员工,并有自己特有的bonus。*/
1 //员工类,也是父类 2 abstract class Employee{ 3 private String name;//姓名 4 private String id; //工号 5 private double pay; //工资 6 7 //自定义构造函数初始化 8 Employee(String name,String id,double pay){ 9 this.name = name;10 this.id = id; 11 this.pay = pay;12 }13 public abstract void work();//抽象的工作方法14 }15 16 //经理类,继承员工类17 class Manager extends Employee{18 private int bonus;//特有的奖金属性19 Manager(String name,String id,double pay,int bonus) {//子类的构造方法20 super(name,id,pay);//调用超类中的构造器21 this.bonus = bonus;22 }23 public void work(){//经理类的工作方法内容24 System.out.println("manager work");25 }26 }27 28 //普通员工类,继承员工类29 class Pro extends Employee{30 Pro(String name,String id,double pay){31 super(name,id,pay);32 }33 public void work(){ //普通员工类的工作方法内容34 System.out.println("pro work");35 }36 }37 38 class AbstractDemo{39 public static void main(String[] args){40 new Manager("manager","001",10000,2000).work();41 new Pro("pro","020",5000).work(); 42 }43 }
五、接口(interface)
(一)接口的概述
1、接口定义
接口可以被认为是一个特殊的抽象类。当抽象类中的方法都是抽象的,那么该类可以通过接口的形式来表示。接口使用interface来表示,子类中用implements实现。格式为:
interface 接口名{}
子类名 implements接口名{}
2、格式特点
(1)接口中常见定义:常量,抽象方法。
(2)接口中的成员都有固定修饰符。常量:public static final。方法:public abstract
(3)接口中的成员都是public的。
在使用中,常量可以不写publicstatic final,方法可以不写public abstract,编译时Java会自动添加这些修饰符,因为这是固定的格式修饰符。但为了方便阅读,通常我们都写上。
(二)接口的特点
1、接口是对外暴露的规则。
2、接口是程序的功能扩展。
3、接口的出现降低耦合性。
4、接口可以由类多实现。这也是对多继承不支持的转换形式。java支持多实现。
5、类与接口之间是“实现关系”,而且类可以继承一个类的同时实现多个接口。
6、接口之间可以有继承关系,而且可以多继承,因为接口没有方法体。
注:(1)接口不可以创建对象的,因为有抽象方法。需要被子类实现(implements),子类对接口中的抽象方法全都覆盖后,子类才可以进行实例化。否则子类是一个抽象类。(2)实现多个接口时,接口中不可以有返回不同类型的同名抽象函数。这样子类实现时将不能复写。
(三)接口与抽象类的
相同点:都是不断向上抽取而来。
不同点:(1)抽象类需要被继承,只能是单继承。接口需要被实现,可以使多实现。(2)抽象类中可以定义抽象方法和非抽象方法,子类继承后可以直接使用非抽象方法。接口只能是定义抽象方法,必须由子类去实现。(3)继承关系是is-a。实现关系是like。继承体系是共性内容;实现关系是额外功能。(4)抽象类中可以私有变量或方法。接口中的常量和方法都是public修饰的权限。
(四)接口的应用示例
/*需求:笔记本电脑使用。为了扩展笔记本的功能,但日后出现什么功能设备不知道。
分析:定义一个规则,只要日后出现的设备都符合这个规则就可以了。规则在java中就是接口。*/
1 interface USB// 暴露的规则。 2 { 3 public void open(); 4 public void close(); 5 } 6 class BookPC 7 { 8 public static void main(String[] args) 9 {10 useUSB(new UPan());//功能扩展了。11 useUSB(new UsbMouse());12 }13 //使用规则。14 public static void useUSB(USB u)//接口类型的引用,用于接收(指向)接口的子类对象。//USB u= new UPan();15 {16 if(u!=null)17 {18 u.open();19 u.close();20 }21 }22 }23 //一年后。------------------------------24 //实现规则。25 //这些设备和电脑的耦合性降低了。26 class UPan implements USB27 {28 public void open()29 {30 System.out.println("upan open");31 }32 public void close()33 {34 System.out.println("upan close");35 }36 }37 class UsbMouse implements USB38 {39 public void open()40 {41 System.out.println("UsbMouse open");42 }43 public void close()44 {45 System.out.println("UsbMouse close");46 }47 }
六、多态
(一)多态的概述
1、对多态的理解:指同一个实体同时具有多种形式,也可以理解为事物存在的多种体现形态。例如动物中猫和狗。猫这个对象对应的类型是猫类型,如:猫 cat1 = new 猫()。同时猫也是动物的一种,因此也可以把猫称为动物。动物 cat1 = new猫()。那么动物就是从猫和狗等具体事物中抽取出来的父类型。
2、多态在代码中的体现:父类或接口的引用指向其子类对象。如Animal a = new Dog()。
3、多态的好处:提高代码的扩展性,前期定义的代码可以使用后期的内容。
4、多态的弊端
(1)前期定义的内容,不能使用后期特有内容。
(2)只能使用父类中的引用访问父类中的成员。也就是说使用了多态,父类型的引用在使用功能时,不能直接调用子类中的特有方法。如Animal a = new Cat(); 这代码就是多态的体现,假设子类Cat中有特有的抓老鼠功能,父类型的 a就不能直接调用。这上面的代码中,可以理解为Cat类型提升了,向上转型。如果此时父类的引用想要调用Cat中特有的方法,就需要强制将父类的引用转成子类类型,向下转型。如:Catc = (Cat)a;
注:如果父类可以创建对象,如Animal a = new Animal()。此时就不能向下转型,Cat c = (Cat)a这样的代码就变得不容许,编译时会报错。所以千万不能出现这样的操作,就是将父类对象转成子类类型。我们所说的能够转换指的是父类引用指向自己的子类对象时,该引用可以被提升,也可以被强制转换。多态始终都是子类对象在做着变化。
5、多态的前提
(1)类与类之间必须有关系,要么继承,要么实现。
(2)存在覆盖。父类中有方法被子类重写。
6、多态的类型判断
instanceof用于判断对象的具体类型。
(二)多态中成员的特点
1、多态中成员变量的特点
无论编译和运行,都参考左边(引用变量所属的类)。如:多态中的父类引用调用成员变量时,如果父类和子类有同名的成员变量,那么被调用的是父类中的成员变量。
2、多态中非静态成员函数的特点
在编译时期:参考引用型变量所属的类中是否有调用成员函数。如果有,编译通过,如果没有编译失败。
在运行时期:参阅对象所属的类中是否有调用的方法。这就是说,如果父类中有一个非抽象的方法,而子类继承后又将其覆写,在多态运行时,父类的引用调用这个同名函数时,被运行的将是父类中的方法。
简单总结就是:成员函数在多态调用时,编译时看左边,运行时看右边。
3、多态中静态成员函数的特点
无论编译和运行,都参考左边。也就是父类引用在调用静态同名函数时,被调用的是父类中的静态函数。这是因为,当类一被加载,静态函数就随类绑定在了内存中。此时,不需要创建对象,就可以使用类名直接调用。
注:类在方法区中的分配,分为静态区和非静态区,而关键字this和super在非静态区。
(三)多态应用的代码示例
/* 电脑的运行实例。电脑的运行由主板控制,假设主板只是提供电脑运行,但是没有上网,听歌等功能。而上网、听歌需要硬件的支持。而现在主板上没有网卡和声卡,这时可以定义一个规则,叫PCI,只要符合这个规则的网卡和声卡都可以在主板上使用,这样就降低了主板和网卡、声卡之间的耦合性。用程序体现。*/
1 // 接口PCI 2 interface PCI{ 3 void open(); 4 void close(); 5 } 6 7 //网卡实现接口 8 class NetCard implements PCI{ 9 public void open(){10 System.out.println("NetCard_open");11 }12 public void close(){13 System.out.println("NetCard_close");14 }15 }16 17 //声卡实现接口18 class SoundCard implements PCI{19 public void open(){20 System.out.println("SoundCard_open");21 }22 public void close(){23 System.out.println("SoundCard_close");24 }25 }26 27 class Mainboard{28 //电脑运行29 public static void run(){30 System.out.println("Mainboard_run");31 }32 //使用扩展功能33 public static void usePCI(PCI p) {//接口型引用指向自己的子类对象。//PCI p = new NetCard()34 if(!(p==null)){35 p.open();36 p.close();37 }38 }39 }40 41 class Demo{42 public static void main(String[] args) {43 Mainboard m =new Mainboard();44 //电脑运行45 m.run();46 //m.usePCI(null);47 //电脑上网48 m.usePCI(new NetCard());49 //电脑听歌50 m.usePCI(new SoundCard());51 }52 }
七、内部类
黑马程序员——面向对象(一)概述、类与对象、继承、抽象类、接口、多态、内部类