第三章 面向对象

3.1面向对象的概念

3.3.1理解面向对象

    1.面向对象是相对面向过程而言

    2.面向对象和面向过程的区别

       面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。

       面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

3.3.2面向对象的特点

1、是一种符合人们思考习惯的思想

2、可以将复杂的事情简单化

3、将程序员从执行者转换成了指挥者

4、完成需求时:

l先要去找具体所需的功能的对象来用

l如果该对象不存在,那么创建一个具有所需功能的对象

l这样简化开发并提高了复用性

3.3.3面向对象开发,设计,特征

    1、开发的过程:其实就是不断创建对象,使用对象,指挥对象做事情

    2、设计的过程:其实就是在管理和维护对象之间的关系

    3、面向对象的特征:

    封装(encapsulation):封装是面向对象的核心思想,将对象的属性和行为封装起来,而它的载体是类,类通常对客户隐藏细节。

    继承(inheritance):使用已存在的类的定义作为建立新技术的基础,新类的定义可以增加新的数据或功能,也可以使用父类的功能。

    多态(polymorphism):动态绑定机制,用来决定会使用被重写的方法名的那个方法的行为。

3.2类与对象的关系

3.2.1类与对象的关系

    1、对象——类的实例

    它是实际存在的该类事物的个体。面向对象程序设计思想是以对象来思考问题,首先将现实世界的尸体抽离为对象,然后考虑对象具备的行为和属性。

    2、类:类是同一类事物的统称,是构造对象时依赖的规范, 

3.2.2类的定义

1、类定义的内容:

⑴、成员变量:也叫成员域,是对象的属性

                   ①、类变量:被该类全部共享的变量,也称静态成员域,常用于保存所有对象共有的常数值。

②、实例变量:也称非静态成员域

             ⑵、方法:也叫成员函数,是对象的行为。

                 ①、类方法:即使该类对象没有对象存在时,也可以执行类方法。它也用static进行声明,因此也被称为静态方法

②、实例方法

3.2.3成员变量和局部变量的区别?

    1.作用范围:成员变量可以被public,protect,private,static等修饰符修饰,局部变量不能被控制修饰符及static修饰;两者都可以定义成final型

 2.是否有默认值:成员变量有,局部变量木有

 3.生命周期:成员变量随对象的建立而存在,随其消失而消失;局部变量随所属区域的执行而产生,随其释放而释放

 4.存储位置:成员变量存储在堆,局部变量存储在栈

3.2.4匿名对象

    1、其实就是定义对象的简写格式:new 对象名();

    2、使用场景:①、当对象方法仅进行一次调用时,就可以简化成匿名对象

 ②、可以作为实际参数进行传递

3.2.5参数传递——值调用

     1、值调用将实际参数的内容拷贝到被调用方法

 2、基本数据类型:创建时存储于栈中。在调用时,会将将其copy拷贝到被调用方法的栈中。然后方法method根据传过来的参数进行方法的执行。

 3、引用数据类型:引用类型的引用存储于栈中,而对象则是存储与堆中在调用时,会对象的地址拷贝到被调用方法的栈中。然后方法method根据传过来的参数进行方法的执行。

3.3 封装

 1、定义:隐藏对象的属性和实现细节,对外提供公共的访问方式

class Person{
//隐藏age这个属性,一旦有属性,几乎都封装,
private int age;
//提供对外访问方式,是为了可控
public void setAge(int a) {
age = a;
}
public int getAge(){
return age;
}

   2、好处

n将变化隔离。

n便于使用

n提高重用性

n提高安全性

原则

l将不需要对外提供的内容都隐藏起来。

l把属性都隐藏,提供对外访问方式。

4、private关键字

n作用:利用private关键字,可以修饰成员,私有的内容只在本类中有效。

n封装与私有的关系:私有是封装的子集,私有仅仅是封装的一种体现而已

n在java中最小的封装体是函数。

3.4 构造函数

 1、特点:⑴函数名与类名相同

          ⑵不定义返回值类型,没有具体的返回值

 2、作用:给对象进行初始化,创建对象都必须要通过构造函数初始化

 3、默认构造函数:一个类中如果没有定义过构造函数,那么该类会有一个默认的空参构造函数,如果定义了,类中就没有默认的啦。

 4、与一般函数的区别:⑴构造函数:对象创建时,就会调用与之对应的构造函数,对对象进行初始化,一般函数:对象创建后,需要函数功能时才调用。

⑵构造函数在对象创建时会调用,而且只调用一次;一般函数在对象创建后,可调用多次。

 5、重载:

       什么时候使用构造函数?在描述事物的时候,该事物一创建,就具备的一些内容,这些内容定义在构造函数中。

 如果构造函数有多个,用于对不同对象进行针对性的初始化,多个函数在类中是以重载的形式来体现的。

 6、注意:⑴、方法的名称、形式参数的类型及其顺序形成了方法的签名,签名是唯一

   ⑵、在构造函数前,不能加返回类型。

   ⑶、一般函数不可以调用构造函数,原因是构造函数是对对象初始化函数,不能被调用,非得调用就得用:new 构造函数();但这是废话、

   ⑷、构造函数中也有return语句。

3.5 this关键字

    1、使用场景

      ⑴、当定义类中功能时,该类函数内部要用到调用该函数的对象时,这是用this来表示这个对象,即本类功能内部用到本类对象的时候,用this。 

        ①、区分同名的局部变量与成员变量,this代表本类的对象,到底代表哪一个?this代表它所在函数所属对象的引用。简单说:哪个对象在调用this所在的函数,this就代表哪个对象

        ②、在本类方法中使用对象,用this。

        ③、凡是被对象调用的方法,在内存中都有this所属

     ⑵用于构造函数间的相互调用

  2、注意:this只能放在构造函数中第一个,因为初始化动作要先执行

    3、代码示例

class Person{
private int age;
private String name;
/*public Person(int age) {
this.age = age;
}*/
Person(String name){
this.name = name;//将局部的赋给对象的变量
}
Person(String name, int age){
//Person(name);这是一般函数间的调用
this(name);//这是构造函数间的互相调用,在这里,this(name)相当于p(name),也就是new Person(name);
this.age = age;
}
public void shout(){
System.out.println("My age is "+age+"\nMy name is "+name);
}
/*
* 需求:给人定义一个用于比较年龄是否相同的功能
*
public boolean compare(Person p){
return this.age == p.age;
}*/
}
public class AboutThisKey {
public static void main(String[] args) {
/*Person p1 = new Person(20);
Person p2 = new Person(25);
boolean b = p1.compare(p2);
System.out.println(b);
*/
Person p = new Person("lisi",30);
// Person p1 = new Person("zhangsan");
p.shout();
// p1.shout();
//为什么p1不打印My age is 0 My name is lisi,而p不打印My age is 0 My name is zhangsan
//在一个类里面,成员之间互相调用时都是对象完成的,所以shout放完的是某一个对象中的name和age,即name和age之前省略了this
}
}

3.6 static关键字

1、共享数据:static用于修饰成员(成员函数,成员变量),这些成员都是被对象共享的成员。

2、特点

       ①、随类的加载而加载,消失而消失。

       ②、优先于对象而存在

       ③、被所有对象所共享

       ④、可以直接被类名所调用

       代码示例

class Chinese
{
static String country = "中国";//共享数据
String name;
int age;
void singOurCountry(){
System.out.println("啊,亲爱的"+country);
}
}
public class AboutStaticKey {
public static void main(String[] args) {
System.out.println(Chinese.country);
//当成员被静态修饰后,除了可以被对象调用之外,还可以被类名调用
Chinese ch = new Chinese();
System.out.println("Chinese country is " + ch.country);
ch.singOurCountry();
}
}

    3、实例变量与类变量的区别

       ①、存储位置:前者随对象的建立而存在于堆内存中;后者随着类的加载而存在于方法区中

       ②、生命周期;前者随着对象的消失而消失;后者随着类的的消失而消失

       ③、调用方式:前者可以被对象及类名调用,但建议用类名调用;后者只能被对象调用

   4、静态的使用注意事项:

      ①、静态的方法只能访问静态成员

      ②、静态方法中不可以使用this或super关键字

      ③、静态省略的是类名,成员变量省略的是this

      ④、主函数是静态的

        public static void main(String[] args)

        1)、public:代表着该函数的权限最大

        2)、static:代表主函数岁类的加载而加载就已经存在

        3)、void:主函数没有返回值

        4)、main:不是关键字,是一个特殊的单词,可以被jvm识别。

        5)、String[] args:函数的参数,参数类型是一个数组,该数组中的元素师字符串,字符串类型的数组。

    5 、静态的利弊

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

       ②、弊:生命周期过长,而且访问有局限性

    6、什么时候使用静态?

       ①、静态变量:如果对象中出现共享数据时,该数据被静态修饰,对象中的特有数据定义在堆内存中

       ②、静态函数:函数是否用静态修饰,就看该函数功能是否需要访问对象中的特有数据,需要,该功能为非静态;不需要,就定义成静态

   7、静态代码块

      ①、格式:static{

                   code

               }

      ②、特点:随类的加载而执行,且只执行一次,并优先于主函数而执行

      ③、作用:给类进行初始化

   8、面试题

class StaticCode {
StaticCode(){
//因为没有创建过与之相关的对象,从而没有被执行过,所以不打印b
System.out.println("b");
}
static{//静态代码块给类初始化
System.out.println("a");
}
{//构造代码块给对象初始化,其优先级比构造函数高,因为构造函数有针对性,而构造代码块可以初始化所有对象
System.out.println("c");
}
StaticCode(int x){//构造函数给对应的对象初始化
System.out.println("d");
}
public static void show(){
System.out.println("show run");
}
}
class StaticCodeDemo {
public static void main(String[] args) {
new StaticCode(4);
}
}
问打印结果是?为什么?
结果:a c d,

3.7 单例设计模式

    1、设计模式:解决一类问题最行之有效的方法

    2、单例设计模式:保证内存中只有一个对象

    3、原因:以配置文件为例,

    4、如何保证对象的唯一性?

       1)、不允许其他程序用new建立该类对象

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

       3)、为了让其他程序对自定义对象可以访问,可以对外提供一些访问方式

    5、以上3部怎么用代码体现?

       1)、将构造函数私有化

       2)、在类中创建一个本类对象

       3)、提供一个方法可以获得该对象

 6、模式:

①饿汉式:定义单例时,建议使用饿汉式

class Singleton{
private int num;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
private Singleton(){}
private static Singleton s = new Singleton();
public static Singleton getInstance(){
return s;
}
}

 ②懒汉式:对象的延时加载,懒汉式在面试的时候考的最多

class Singleton{
private int num;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
private Singleton(){}
private static Singleton s = null;
public static  Singleton getInstance(){
if(s==null){
synchronized(Singleton.class){
if(s==null)
s = new Singleton();
}
}
return s;
}
}
public class SingletonPattern {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
s1.setNum(30);
System.out.println(s2.getNum());
}
}

3.8 继承

3.8.1继承的概述

 1、继承:Java继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。

 2、继承的优点

        ①、提高了代码的复用性

        ②、让类与类之间产生了关系,有了这关系,才有了多态的特性

       注意:千万别因为要获取其他类 的功能,简化代码而继承,必须是类与类之间有所属关系才可以继承。

 3、父类,也称为基类,或超类

 4、 聚集关系:关联的一种形式,代表两个类之间的整体 / 局部关系。聚集暗示着整体在概念上处于比局部更高的一个级别,而关联暗示两个类在概念上位于相同的级别。

    有两种特殊聚集:共享聚集和组合聚集。

l共享聚集:它的“部分”对象可以是多个任意“整体”对象的一部分

l组合聚集:整体拥有各部分,部分与整体共存。

3.8.2继承的特点

 1、java语言:只支持单继承,不支持多继承,因为多继承容易带来安全隐患:当多个父类中定义相同功能,而功能的内容不同,子类不确定要运行哪一个但是java保留了这种机制,并以另一种体现形式来完成表示,多实现

 2、java支持多层继承,也就是一个继承体系。如何使用一个继承体系中的功能?想要使用体系,先查阅体系父类的描述,因为父类定义了该体系的共×××,通过了解共×××,就可以知道该体系的基本功能。在具体调用时,要创建最子类对象,其原因是父类有可能创建不了对象,另外创建子类可以使用更多的功能。

 3、子父类出现,类成员的特点:

        ⑴、变量:如果子父类中出现非私有的同名成员变量时,子类要访问本类中的变量时用this,子类要访问父类中的变量时用super。他们俩的用法几乎一致,只是俩代表的引用不同

        ⑵、函数:当子类出现和父类一模一样的函数时,子类对象调用该函数会运行子类函数的内容,如同父类的函数被覆盖了一样,也叫重写或覆盖。

    当子类继承了父类,沿袭了父类的功能,到子类中,但是子类虽具备该功能,但是功能的内容和父类不一致,这时,只需覆盖父类功能,保留父类的功能定义,并重写功能内容。

    通过函数的复写可以提高扩展性

    覆盖注意事项:

      1)、子类的权限必须大于等于父类的权限,才可以覆盖。

      2)、静态只能覆盖静态

      3)、重载只看同名函数的参数列表,而重写的子父类方法必须一模一样

    ⑶、构造函数:

子父类的构造函数是不可能一样的,因为构造函数的函数名与类名一致。在对子类对象进行初始化时,父类的构造函数也会运行。其原因是在子类构造函数默认的第一行有一条隐式的语句super(注意当第一行是this(),就没有super()),super会访问父类中空参数的构造函数。

    为什么子类一定要访问父类的构造函数?因为父类中的数据子类可以直接获取,所以子类对象在建立时,需要先查看父类是如何对这些数据进行初始化的的,所以子类在对象初始化时,要先访问父类的构造函数。如果要访问父类中指定构造函数,可以通过手动定义super语句来。

3.8.3 super关键字

    1、应用

          ⑴、在子类的构造方法内部引用父类的构造方法,如果要引用super的话,必须把super放在函数的首位.

class Base {
Base() {
System.out.println("Base");
}
}
public class Checket extends Base {
Checket() {
super();//调用父类的构造方法,一定要放在方法的首个语句
System.out.println("Checket");
}
public static void main(String argv[]) {
Checket c = new Checket();
}
}

          ⑵、在子类中调用父类中的成员方法或成员变量

class Country {
String name;
void value() {
name = "China";
}
}
class City extends Country {
String name;
void value() {
name = "Hufei";
super.value();//不调用此方法时,super.name返回的是父类的成员变量的值null
System.out.println(name);
System.out.println(super.name);
}
public static void main(String[] args) {
City c=new City();
c.value();
}
}

          ⑶、用super直接传递参数

class Fu{
int num;
Fu(int num){
System.out.println("fu run"+"  "+num);
}
void show(){
System.out.println("fu show");
}
}
class Zi extends Fu{
Zi(){
//super();
super(4);// 传递参数
System.out.println("zi run");
}
void show(){
System.out.println("zi show");
}
}

        2、注意:

         ⑴、super语句必须放在子类构造函数的第一行

⑵、super和this都只能定义在第一行,所以只能定义一个。

⑶、通过super初始化父类内容时,子类的成员变量并未显示初始化,等super()父类完毕后,才进行成员变量显示初始化

3.8.4实例化过程

1、子类的实例化过程:

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

2、对象实例化过程——以Person p = new Person();为例

lJvm会读取指定路径下的Person.class文件,并加载进内存,并会先加载Person的父类

l在堆内存中开辟空间,分配地址。

l在对象空间中,对对象中的属性进行默认初始化

l调用对应的构造函数进行初始化

l在构造函数中,第一行会先到调用父类中构造函数进行初始化

l父类初始化完毕后,再对子类的属性进行显示初始化

l在进行子类构造函数的特定初始化

l完毕后,将地址值赋给引用变量

3.8.6 final关键字

     1、final修饰的类不可以继承

     2、final修饰的方法不可以覆盖

     3、final修饰的变量是一个常量,只能赋值一次,且只固定显示化赋值

     4、注意:类变量只有在final的修饰下,值才会固定,否则可以修改,static只能说明该值被共享,但不规定是固定的。