黑马程序员java学习笔记——面向对象的特征封装、继承和多态

时间:2022-02-23 13:13:41
------- android培训java培训、期待与您交流! ----------

    感觉面向对象是在java中比较难的内容,但也是核心内容,毕竟写代码思想是很重要的,面向对象就是一种思想,精通这种思想,编程就会变的简单。下面就是我学完面向对象这一部分做的笔记。

    面向对象

    理解面向对象:面向对象其实就是站在事物本身的角度去看待事物,是将功能封装进对象,强调的是具备了功能的对象,这种思想能让复杂的问题简单化,能让我们的角色转变,从执行者变成指挥者。比如我们平时看电视,需要我进行开机-->换台-->关机的动作,这就是典型的面向过程,每次都需要我们自己去执行这些动作;其实,可以尝试换一种思考方式,可以把这些动作看成是电视自己的动作,怎么开机,怎么换台,最清楚的是电视机自己,当我要看电视的时候,只要给电视一个命令说电视开机,电视换台,电视关机就可以了,至于电视机怎么去执行这些操作,不用我管,也就是说把这些功能封装进电视机中,我们只需要指挥电视机完成它自己所具备的功能就可以了。

    在我们以后的开发中适用对象的原则:先去找有没有我们要实现的功能所需的对象,找到了,就使用这个对象去做事情,没找到就自己创建一个对象,然后在使用自己做的这个对象,同时也需要我们维护对象之间的关系。 

    类与对象的关系: 对象的抽象就是类,类的具体化就是对象,也就是说类是我们不断的从对象中抽取它们之间的共性内容所得到的,而对象就是由这个类所衍生出来的实体。简单的说就是:类是对现实生活中事物的描述,而对象就是这类实物,是实实在在存在的个体。

    就比如说我们造汽车,首先要有图纸,然后我们才能根据图纸来制造汽车,这个图纸就可以看做是一个类,而每个汽车就可以看作是一个对象。   

    我们来通过代码描述一辆汽车:

class Car
{
//描述颜色
String color = "红色";
//描述轮胎数
int num = 4;

//运行行为。
void run()
{

System.out.println(color+".."+num);
}

}
    上面就是对一个汽车的描述,有颜色,轮胎数,还有run的行为,对事物的描述包括描述事物的属性和事物的行为,属性对应类中的变量,行为对应类中的函数。其实定义类,就是在描述事物,就是在定义属性和行为,属性和行为共同成为类中的成员(成员变量和成员函数)。

    生产汽车,我们可以通过一个new操作符来完成,其实就是在堆内存中生产一个实体。如Car c = new Car();c就是一个类类型的变量,类类型的变量指向对象。

    成员变量与局部变量

    成员变量:作用于整个类中,在堆内存中,因为对象的存在,才在内存中存在。

    局部变量:作用于函数中,或者语句中,在栈内存中,随着方法或者语句的结束,在内存中被释放。

    例如:对象产生后,才会有颜色和轮胎数,成员变量在整个类中有效,在类中的其他函数可以直接访问。

    匿名对象:书写对象的简化格式。

    匿名对象的特点:1,匿名对象调用属性没有意义;

                    2,对于某个方法只调用一次,可以使用匿名对象;

                    3,匿名对象可以作为参数进行传递;

                    4,当一个对象要对多个成员进行调用时,必须要给这个对象取个名字。

    面向对象的特征:封装、继承和多态。

         封装          a:什么是封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式。函数是最小的封装体,类也是封装体,隐藏对象的属性通过一个修饰符private来完成,将成员私有后,本类以外即使建立了对象也不能直接访问该成员,要注意,私有只是封装的一种表现形式,不能说私有就是封装,之所以对外提供公共访问方式,就是因为可以在访问方式加入逻辑等判断语句,提高代码的健壮性。     b:封装的好处:1,隔离了变化;                    2,提高了重用性;                    3,便于使用;                    4,提高了安全性。     c:封装的原则:1,将不需要对外提供的内容都隐藏起来;

                   2,把属性都隐藏,提供公共方法对其访问。

    对外提供的方法一般都是set,get开头的,set开头,返回值类型是void,是设置变量的值,get方法的返回值类型和它获取的变量一致。

    下面通过一个Person类来提现:

class Person//首先我们要建立一个描述人这种事物的Person类
{
private int age;//将年龄进行私有;
public void setAge(int a)//对外提供一个设置年龄的方法
{
if(a>0 && a<130)//在方法中进行判断
{
age = a;
speak();
}
else
System.out.println("feifa age");
}

public int getAge()//对外提供一个获取年龄的方法
{
return age;
}
private void speak()
{
System.out.println("age="+age);
}
}

class PersonDemo
{
public static void main(String[] args)
{
Person p = new Person();//创建人对象;
p.setAge(-40);//通过set方法设置这个人的年龄;
p.speak();//调用speak方法;
}
}
    构造函数:给对象进行初始化的函数。

    特点:1,函数名与类名相同;

          2,不需要返回值类型;

          3,不可以有return语句。

    小细节:当类中没有定义构造函数时,那么系统会默认给该类添加一个空参数的构造函数;当类中定义了之后,默认的空参数构造函数就没有了。

    构造函数和一般函数的区别:构造函数和一般函数首先在写法上不同,而且构造函数是在对象一建立就运行,给对象进行初始化,只运行一次;而一般方法是在对象调用时才运行,可以被对象调用多次。

    什么时候定义构造函数:当分析事物时,该事物具备一些特性或行为,那么将这些内容定义在构造函数中。

    构造代码快:构造代码快是给所有对象进行初始化。

    特点:对象一建立就运行,而且优先于构造函数执行,和构造函数的区别就是,构造代码快是给所有对象进行初始化,构造代码快中定义的是不同对象的共性的初始化内容。

    this关键字:this代表它所在对象所属函数的引用,那个对象再调用this所在的函数,this就代表哪个对象。

    this语句:this(实际参数),用于构造函数间的调用,只能放在构造函数的第一句。

    静态static:是一个修饰符,用于修饰成员变量,不能修饰局部变量,静态所修饰的内容被对象共享,被静态修饰后,可以直接被类名调用,格式是:类名.静态成员。

    静态的特点:1,随着类的加载而存在于方法区中;

                2,优先于对象存在;

                3,被对象所共享;

                4,可以直接被类名调用。

    也就是说静态随着类的加载而加载,也随着类的消失而消失,生命周期比较长,而且静态先存在,对象后存在。

    实例变量和类变量的区别:

    1,存放位置不同:

       类变量随着类的加载存在于方法区中;

       实例变量随着对象的建立存在于堆内存中。

    2,生命周期不同:

       类变量随着类的消失而消失,生命周期较长;

       实例变量随着对象的消失而消失,生命周期较短。

    使用静态要注意的事项:

       静态方法只能访问静态成员,非静态方法既能访问静态成员,也能访问非静态成

       员。

       静态方法中不能定义this,super关键字。因为静态优先于对象存在。

    静态的利弊:

       对对象的共享数据进行单独空间的存储,节省空间,而且可以直接被类名调用。

       生命周期过长,访问有局限性。

    主函数是静态的:

       主函数是一个特殊的函数,作为程序的入口,可以被jvm调用。

       public:代表该函数的访问权限是最大的;

       static:代表该函数随着类的加载就已经存在了;

       void:主函数中没有具体的返回值,它被jvm调用,不需要返回值;

       main:不是关键字,是一个特殊单词,被虚拟机所识别;

       函数的参数(String[] args):参数类型是一个String类型的数组,名为args。

    什么时候使用静态:

    要从两方面下手,因为静态修饰的内容有成员变量和成员函数;

    当成员中出现共享数据时(注意:是共享数据),该数据被静态所修饰,对象中的特有数据要定义成非静态,存在于对内存中。

    当功能内部没有访问到静态成员是,该功能可以定义成静态的。

    静态代码快

    格式:

    static

    {

         执行语句;

    }

    特点:随着类的加载而加载,只执行一次,并优先于主函数执行,用于给类进行初始化。

    对象的初始化过程:Person p = new Person();

    接下来分析一下这句话都做了什么;

    1,应为new用到了Person.class,所以会找到Person.class并加载到内存中;

    2,执行该语句中的静态代码快,对类进行初始化;

    3,在堆内存中开辟空间,分配地址值;

    4,在堆内存中建立对象的特有属性,并进行默认初始化;

    5,对属性进行显示初始化;

    6,对属性进行构造代码快初始化;

    7,对属性进行构造函数初始化;

    8,将地址值赋给占内存中的变量p;

    设计模式

    解决一类问题最行之有效的方式,java有23中设计模式。

    单例设计模式:一个类在内存中只存在一个对象。

    为什么要保证对象的唯一?

    1,为了避免其他程序过多的建立该类对象,先禁止其他程序建立该类对象;

    2,为了让其他程序访问到该类对象,可以在该类中自定义一个对象;

    3,为了方便其他程序访问到该类对象,可以对外提供一些访问方式。

    这三步用代码体现就是:

    1,将构造函数私有;

    2,在本类中建立本类对象;

    3,提供一个方法可以获取这个对象。

    对于事物,该怎么描述就怎么描述,当事物的对象需要在内存中保证唯一时,就将以上的三步加上即可。

    单例设计模式有两种写法:懒汉式,饿汉式。

    饿汉式:类一进内存就创建好了对象。

class Single
{
private static Single s = new Single();//在类中建立本类对象;
private Single(){}//将构造函数私有
public static Single getInstance()
{
return s;
}
}
    懒汉式:类进内存,对象还没有存在,只有调用了返回对象的那个方法时,才建立对象,这也叫对象的延时加载。

class Single
{
private static Single s = null;//先不给对象初始化;
private Single(){}
public static Single getInstance()
{
if(s==null)
{
synchronized(Single.class)//加同步锁,是为了防止多线程时发生安全问题;
{
if(s==null)
s = new Single();//当调用了getInstance方法时才给对象进行初始化。
}
}
return s;
}
}
    开发时,一般使用饿汉式

    继承

    概述:多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承单独的那个类即可。多个类可以称为子类,单独这个类称为父类或者超类。子类可以直接访问父类中的非私有的属性和行为。通过 extends关键字让类与类之间产生继承关系。

    举个例子来说明,动物有很多种,比如老虎,狮子,猎豹,这三个都是动物,所以我们可以说狮子是动物的子类,动物是狮子的父类。

    继承的好处:提高了代码的复用性;让类与类之间产生了关系,有了这个关系,才有了多态的特性。

    注意:千万不要为了获取其他类的功能,简化代码而继承,必须是类与类之间有所属关系才能继承,所属关系为is a(谁是谁中的一员)。

    在java语言中,只支持单继承,不支持多继承。

    单继承:一个类继承一个类;

    多继承:一个类可以继承多个类。

    因为多继承容易带来安全隐患,当多个父类中定义了相同功能,但功能内容不同时,子类不知道该调用哪一个,但java支持多层继承。

    但是java保留了这种机制,并用另一种体现形式来完成表示,就是多实现。

    如何使用一个体系中的功能?

    想要使用体系,现查阅体系父类中的描述,因为父类式体系的共性功能,通过了解共性功能,就能了解该体系的基本功能,这个体系已经可以基本使用了。

    在具体调用时,要创建最子类的对象,原因有两个:

    1,有可能父类不能创建对象;

    2,创建子类对象可以使用更多功能,包括共有的,也包括特有的。

    简单一句话,查阅父类功能,创建子类对象。

    子父类出现后类中成员的特点:

    变量如果子类中出现非私有的同名成员变量时,子类要访问本类中的变量,用this。子类要访问父类中的同名变量,用supersuper的使用和this的使用几乎一致,且两者都存在于方法区中。 this表示本类对象的引用;super表示父类对象的引用。

    函数:当子类出现和父类一模一样的函数时,子类对象如果调用到了该函数,会运行子类的内容;如同父类函数被调用一样,这种情况是函数的一个特性,覆盖,其实父类方法还在内存中,只是没运行。

    当子类继承父类,沿袭了父类的功能,到子类中。但是子类虽具备该功能,但是功能的内容却和父类不一致,这时,没有必要定义新功能,而是使用覆盖特性,保留父类的功能定义,并重写功能内容。

    注意:1,子类覆盖父类,必须保证子类权限大于等于父类权限,才可以覆盖,否则

          编译失败。

          2,静态只能覆盖静态。

    重载和覆盖的区别:重载只看同名函数的参数列表,覆盖要求子父类方法要一模一样,包括返回值类型。

   子父类中构造函数的特点——子类的实例化过程

    在对子类对象进行初始化时,父类的构造函数也会运行,那是因为子类的构造函数的第一行有一条默认的隐式super()语句,它会访问父类中空参数的构造函数,而且子类所有的构造函数第一行都有super();

    访问父类中构造函数的原因:因为父类中的数据子类可以直接获取,所以子类对象在建立时,要查看父类是如何对这些数据进行初始化的,所以子类在对象的初始化时,要先访问一下父类的构造函数。

    如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式来访问,super()括号中传什么参数就调用对应的父类中的构造函数。

    子父类中的构造函数是不能覆盖的,覆盖要求子父类的构造函数一模一样,而构造函数随着类名走,子父类构造函数的类名都不一致。

    结论:子类的所有构造函数,默认都会访问父类中空参数的构造函数。因为子类每一个构造函数内的第一行都有一句隐式super();当父类中没有空参数的构造函数时,子类必须手动通过supe语句或者this语句形式来指定要访问的构造函数。当然子类的构造函数第一行也可以手动指定this语句来访问本类中的构造函数。子类中至少会有一个构造函数会访问父类中的构造函数。

    final关键字

    final:最终,作为一个修饰符。

    特点:

    1、可以修饰类、函数、变量。

    2、被final修饰的类不可以被继承。这样就可以避免被继承、被子类复写功能。

    3、被final修饰的方法不可以被复写。

    4、被final修饰的变量是一个常量只能赋值一次,既可以修饰成员变量,又可以修饰

    局部变量。

       当在描述事物时,一些数据的出现值是固定的,那么这时为了增强阅读性,都给

    这些值起个名字。方便于阅读。而这个值不需要改变,所以加上final修饰。作为常

    量:常量的书写规范所有字母都大写,如果由多个单词组成,单词间通过_连接。

    5、内部类定义在类中的局部位置上时,只能访问该局部被final修饰的局部变量。

    抽象类

    当多个类中出现了相同功能,但功能主体不同,这时也可以向上抽去,只至抽取功能定义,不抽取功能主体。

    抽象类的特点:

    1,抽象方法一定定义在抽象类中;

    2,抽象类和抽象方法必须用abstract所修饰;

    3,抽象类不能创建对象,因为调用抽象方法没意义;

    4,抽象类中的方法如果要被使用,就用一个类来继承这个抽象类,并覆盖抽象类中的所有抽象方法,建立子类对象调用,如果没有全部覆盖,那这个子类还是一个抽象类。

    抽象类和一般类没有太大的不同,该怎么描述事物就怎么描述,只不过该事物中出现了一些看不懂的东西,这些不确定的部分,也就是事物的功能,需要明确出现,但是无法定义主体,通过抽象方法来表示。凡是不确定的都沿袭到子类去做。

    抽象类比一般类多了抽象函数,就是在类中可以定义抽象方法,其实抽象类中也可以没有抽象方法。抽象类不能实例化。

    一个抽象类不定义抽象方法,目的就是不让该类建立对象。

    根据抽象类引入一个模版方法模式:在定义功能时,功能的一部分是确定的,而另一部分是不确定的,确定的部分在使用不确定的部分,这时候我们可以把不确定的部分暴露出去,由该类的子类来完成。

/*
需求:获取一段程序运行的时间。
原理:获取程序开始和结束的时间并相减即可。
*/
abstract class GetTime
{
public final void getTime()
{
long start = System.currentTimeMillis();//获取程序开始运行前的时间

runcode();//功能主体不确定的部分;

long end = System.currentTimeMillis();//获取程序运行结束后的时间

System.out.println("毫秒:"+(end-start));//获取程序的运行时间。
}
public abstract void runcode();//这就是被暴露出去的部分。

}


class SubTime extends GetTime
{

public void runcode()//定义的子类,实现不确定的这一部分。
{

for(int x=0; x<2000; x++)
{
System.out.print(x);
}
}
}


class TemplateDemo
{
public static void main(String[] args)
{
SubTime gt = new SubTime();
gt.getTime();
}
}
    接口:可以认为是一个特殊的抽象类,这个抽象类中所有的方法都是抽象的,那么这个类就可以用接口的形式来表示,这个关键字是interface。

    接口定义时格式的特点:

    1、接口中常见定义:常量,抽象方法。

    2、接口中的成员都有固定修饰符。

       常量:public static final;

       方法:public abstract;

    其实也可以不写,只要是interface系统会自动给加上。

    接口中的成员都是public的。

    类继承类是因为里边有非抽象的内容可以直接拿来用,而到了接口,里边全抽象了,这时我们要用一个更确切的方式来表示,就是子类要将接口中的内容是先后才能实例化。接口是不可以被创建对象的,因为有抽象方法,需要被子类实现,子类对接口中的方法全都覆盖后,才能实例化,否则还是一个抽象类。

    接口可以被类多实现,也是对多继承不支持的转换形式,java支持多实现。

    接口的特点:

    1,接口是对外暴露的规则;

    2,接口是程序的功能扩展;

    3,接口降低了偶合性;

    4,类与接口之间是实现关系,而且类可以集成一个类的同时实现多个接口;

    5,接口与接口之间有继承关系。


    多态


    可以理解为事物存在的多种形态。    

    多态的表现形式:1:父类引用指向了自己的子类对象;

                    2:父类的引用也可以接受自己的子类对象。

    多态的前提:1,必须是类与类之间有关系,要么继承,要么实现;

                2,必须要存在覆盖。

    多态的好处:多态的出现大大的提高了程序的扩展性。

    多态的弊端:只能使用父类的引用访问父类中的成员,举个例子来说:Animal a = new Cat();父类的引用指向子类对象,这就是多态的表现形式相当于Cat进行了自动类型提升,但是a这个引用只能调用Animal类中所具有的方法,如果要调用cat特有的方法,那么就只能通过对a进行强转,也就是Cat c = (Cat)a;注意:千万不要这么做:Animal a = new Animal();Cat c = (Cat)a;这句话是将父类对象转成子类类型,多态中,我们转的只是父类的引用,自始至终都是子类在做着变化。

    多态中成员的特点

    成员函数:

    在编译时期:参阅引用型变量所属的类中是否有调用的方法。如果有,编译通过,如果没有,编译失败。

    在运行时期:参阅对象所属的类中是否有调用的方法。

    简单总结:成员函数在多态调用时,编译看左边,运行看右边。

    涉及到面试:

    在多态中成员变量的特点:无论编译和运行都参考左边,也就是引用型变量所属的类。

    在多态中静态成员函数的特点:无论编译和运行都参考左边。

    多态的应用:多态的主板事例

/*
需求:电脑运行事例
思路:
电脑运行基于主板,如果说只是给主板定义一个让主板运行的功能,它的后期扩展性就极差,
因为后期我们可能还需要电脑有其他的功能,比如说听歌,上网,打游戏等,而听歌上网打
游戏这些都需要依赖主板才能运行,并不具备独立运行的功能,所以说,我们可以定义一个
接口,当后期要加入的元素满足我这个接口的规则,那么你就能在主板上使用。
*/
interface PCI//建立PCI接口
{
public abstract void open();
public abstract void close();
}
class MainBoard//这是主板
{
public void run()
{
System.out.println("MainBoard run");
}
public void usePCI(PCI p)//定义了接口的好处就在于不用每增加一个元素,就要在这;
//里新增一个方法,建立接口的引用,每增加一个元素只需要在该;
//元素实现了PCI接口后,将接口的引用指向这个类的对象就可以了;
//这里可以看作是:PCI p = new NetCard();PCI p = new SoundCard();

{
if(p!=null)
{
p.open();
p.close();
}
}
}
class NetCard implements PCI
{
public void open()//网卡实现PCI接口,并复写接口中的方法;
{
System.out.println("NetCard open");
}
public void close()
{
System.out.println("NetCard close");
}
}
class SoundCard implements PCI
{
public void open()//声卡实现PCI接口,并复写接口中的方法;
{
System.out.println("SoundCard open");
}
public void close()
{
System.out.println("SoundCard close");
}
}
class DuoTaiDemo4
{
public static void main(String[] args)
{
MainBoard mb = new MainBoard();
mb.run();
mb.usePCI(new NetCard());//将网卡的对象传递给usePCI方法;
mb.usePCI(new SoundCard());//将声卡的对象传递给usePCI方法;
}
}

    

    Object类:是类层次结构的根类,每个类都是用Object作为超类,所有对象都实现这个类的方法,该类中定义了所有对象都具备的功能。

    Object类中的equals方法:比较的是两个对象是否相同,其实比较的是地址值。

    Object类中的toString方法:将对象转换成字符串。

    Object类中已经提供了对象是否相同的比较方法,如果自定义的类中也有比较是否相同的功能,没有必要重新定义,只要沿袭父类的功能,建立自己特有的比较内容即可。