美文网首页
java面向对象

java面向对象

作者: 上炼致知 | 来源:发表于2020-04-01 17:20 被阅读0次

    面向对象和面向过程的思想

    • 面向过程的程序设计方式:遇到一件事,思考“我该怎么做”,然后一步步实现过程;
    • 面向对象的程序设计方式:遇到一件事,思考“我该让谁做”,“谁”就是对象,要怎么做是对象的事;

    创建对象的格式

    类名 对象名 = new 类名();
    

    类是对一类事物的抽象描述,而对象用于表现现实中该类事物的个体,类与对象的关系如下图所示:

    类与对象的关系.png
    类用于描述多个对象的共同特征,,它是对象的模版,对象用于描述现实中的个体,它是类的实例。对象是根据类创建的,并且一个类可以对应多个对象

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

    • 定义的位置不同

      • 定义在类中的变量是成员变量;
      • 定义在方法中或者{}语句里面的变量是局部变量;
    • 在内存中的位置不同

      • 成员变量存储在堆内存的对象中;
      • 局部变量存储在栈内存的方法中;
    • 生命周期不同

      • 成员变量随着对象的出现而出现在堆中,随着对象的消失而从堆中消失;
      • 局部变量随着方法的出现而出现在栈中,随着方法的弹栈而消失;
    • 初始化不同

      • 成员变量因为在堆内存中,所有默认的初始化值;

      • 局部变量没有默认的初始化值,必须手动的给其赋值才可以使用;

    基本类型和引用类型作为参数传递

    • 基本类型作为参数传递时,是将基本类型变量x在空间中的值复制了一份传递给调用的方法show(),show()方法中接受到了复制的值,再在show()方法中对x变量进行操作,这时只会影响到show中的x,当show方法执行完成,弹栈后,程序又回到main方法执行,main方法中的x值还是原来的值;图解如下: 基本类型作为参数传递.png
    • 当引用变量作为参数传递时,其实是将引用变量空间中的内存地址(引用)复制了一份传递给了show()方法 的d引用变量,这时会有两个引用同时指向堆中的同一个对象,当执行show()方法中的d.x=6时,会根据d所持有的引用找到堆中的对象,并将其x属性的值改为6,show方法弹栈。由于是两个引用指向同一个对象,不管是哪一个引用改变了引用所指向的对象中的值,其他引用再次使用都是改变后的值;图解如下:
      引用变量作为参数传递.png

    封装

    面向对象三大特征:封装、继承、多态;

    封装表现:1.方法就是一个最基本的封装体;2.类其实也是一个封装体;

    封装的好处:1.提高了代码的复用性;2.隐藏了实现细节,还要对外提供可以访问的方式,便于调用者的使用,这是核心之一,也可以理解为就是封装的概念;3.提供了安全性。

    private关键字

    private可以修饰成员内容包括成员方法和成员变量;类中不需要对外提供的内容都私有化,包括属性和方法;

    被private修饰的内容不能在其他类访问;私有仅是封装的体现形式而已;

    对私有变量访问的方式可以提供对应的setXxx(赋值)和getXxx(取值)的方法;

    this关键字

    当在方法中出现局部变量和成员变量同名的时候,可以在成员变量名面前加上this.来区分成员变量和局部变量;访问成员变量用this

    对象的内存解释(图解)

    class Person {
        private int age;
        public int getAge() {
            return this.age;
        }
        public void setAge(int age) {
            this.age = age;
        }
    }
    public class PersonDemo {
        public static void main(String[] age) {
            Person p = new Person();
            p.setAge(30);
            System.out.println("大家好,今年我" + p.aetAge() + "岁");
        }
    }
    
    下图为程序中内存对象的创建使用过程: 内存对象创建使用过程.png
    1. 先执行main方法(压栈),执行其中的Pers p = new Person();
    2. 在堆内存中开辟空间,并为其分配内存地址0x1234,紧接着成员变量默认初始化(age = 0);将内存地址0x1234赋值给栈内存中的Person p变量;
    3. 继续执行p.setAge(30)语句,这时会调用setAge(int age)方法,将30赋值为setAge方法中的age变量;执行this.age = age语句,将age变量值30赋值给成员变量,this.age为30;
    4. setAge()方法执行完毕后,弹栈,回到main()方法,执行输出语句System.out.println(),控制台打印p对象中的age年龄值;

    this代表的是对象,哪个对象调用了this所在的方法,this就代表哪个对象

    继承概述

    • 在java中,类的继承是指在一个现有类的基础上去构建一个新的类,构建出来的新类被称作子类,现有类被称作父类,子类会自动拥有父类所有;
    • 子类会自动拥有父类所有非private修饰的属性和方法;
    • 所有类直接或间接的继承了Object类,可以多层继承;

    继承的定义格式和使用

    格式:class 子类 extends 父类 {}

    //定义员工类
    class Employee {
            String name;
            public void work() {
                System.out.println("尽心尽力工作");       
            }
    }
    //定义研发部员工Developer继承,员工类Employee
    class Developer extends Employee {
        public void printName() {
            System.out.println("name=" + name)
        }
    }
    //测试类
    public class Example01 {
        public static void main(String[] args) {
            Developer d = new Developer(); 
            d.name = "小明";
            d.printName();
            d.work();
        }
    }
    

    通过子类对象既可以调用自身的非private修饰的成员,也可以调用父类的非private修饰的成员

    继承的好处及注意事项

    继承的好处:

    1. 继承的出现提高了代码的复用性和可维护性,提高软件开发的效率;
    2. 继承的出现让类与类之间产生了关系,提供了多态的前提;

    继承的弊端:类与类之间的耦合度过高;

    注意事项:

    1. Java中类只支持单继承,不允许多继承,也就是说一个类只能有一个直接父类;例如以下不允许:

      class A()
      class B()
      class C extends A, B {}  //C类不可以同时继承A类和B类
      
    2. 多个类可以继承一个父类,例如以下时允许的:

    class A()
    class B() extends A {}
    class C() extends A {}   //类B和类C都可以继承类A
    
    1. 在Java中,多层继承时可以的,即一个类的父类可以再去继承另外的类,例如C类继承自B类,而B类又可以去继承A类,这时C类也可以称为A类的子类,例如以下是允许的:

      class A()
      class B extends A {}
      class C extends B {} //类C继承类B,类C是类B的子类,同时也是类A的子类
      
    2. 在Java中子类和父类是一种相对概念,也就是说一个类是某个类父类的同时,也可以是另一个类的子类;

    继承--子父类中成员变量的特点

    父类中的成员变量如果是非私有的,子类可以直接访问,若父类中的成员变量私有了,子类是不能直接访问的。

    • 子类的对象调用成员变量的时候,子类自己有使用子类,子类自己没有使用父类;
    • 当子父类中出现了同名成员变量时,在子类中若要访问父类中的成员变量,必须使用关键字super来完成,super用来表示当前对象中包含的父类对象空间的引用;
    class Fu {
        //Fu中的成员变量
        int num = 5;
    }
    class Zi extends Fu {
        //Zi中的成员变量
        int num = 6;
        void show() {
            //子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用super关键字\
            //访问父类中的num
            System.out.println("Fu num = " + super.num);
            //访问子类中的num
            System.out.println("Zi num = " + this.num);         
        }
    }
    

    继承--子父类中成员方法特点

    • 当在程序中通过对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在机会执行父类中相应的方法;

    • 子类中出现与父类一模一样的方法时,会出现覆盖操作,也称为override重写,复写或者覆盖;

      class Fu {
            public void show() {
                System.out.println("Fu show");
          }
      }
      class Zi extends Fu {
            //子类复写了父类中的show方法
            public void show() {
                System.out.println("Zi show");
          }
      }
      /**
       *为什么要有重写:在复杂工程中,Fu类的方法是最先存在的,如果项目需求变了,该方法的功能不能够满足我们          *的需求,此时我们也不会去改变这个方法,因为项目中有可能有大量的功能已经使用到该方法,如果随意修改可
       *能使调用该方法的功能出现问题,所以使用重写方式基于原有功能提供更强的功能
       */
      

      重写,写法上必须一模一样,方法的返回值类型、方法名、参数列表都必须一样;

    抽象类--产生

    分析事物时,发现了共性内容,就出现向上抽取,会有这样一种特殊情况,就是方法功能声明相同,但方法功能主体不同,那么这时也可以抽取,但只抽取方法声明,不抽取方法主体,那么此方法就是一个抽象方法

    定义了抽象函数的类也必须被abstract关键字修饰,被abstract关键字修饰的类就是抽象类

    抽象方法定义格式:

    public abstract 返回值类型 方法名(参数)

    抽象类定义格式:

    abstract class 类名{}

    //抽象类不能实例化对象,不能new的
    //不能创建对象原因:如果能new,那么对象.调用抽象方法,抽象方法没有主体,根本就不能运行
    abstract class Developer {
        public abstract void work();//抽象函数,需要abstract修饰
    }
    class JavaEE extends Developer {
        public void work() {
            System.out.println("developing website");
        }
    }
    

    抽象类使用:定义继承抽象类的类,将抽象方法重写

    抽象类特点

    1. 抽象类和抽象方法都需要被abstract修饰,抽象方法一定要在抽象类中
    2. 抽象类不可以直接创建对象,原因:调用抽象方法没有意义;
    3. 只有覆盖了抽象中所有的抽象方法后,其子类才可以创建对象,否则该子类还是一个抽象类;

    抽象类的细节问题

    1. 抽象类一定是个父类;
    2. 抽象类中可以不定义抽象方法,而且也不会没有意义,因为这样不会让其他人直接创建该类对象;
    3. 抽象关键字abstract不可以和private、final、static关键字并存;因为私有的方法字类是无法继承到的;

    接口概念

    接口是功能的集合;同样可以看作是一种数据类型,比抽象类更为抽象的抽象类;

    接口只描述所应该具备的方法,并没有具体实现;具体的实现由接口的实现类来完成,这样将功能的定义与实现分离,优化了程序设计。

    接口不可以创建对象;

    接口定义

    与定义类class不同,定义接口使用interface关键字;定义格式如下:

    public interface 接口名 {
        抽象方法1;
        抽象方法2;
    }
    
    • 接口中的方法均为公共访问的抽象方法
    • 接口中无法定义普通的成员变量;

    接口的实现类

    实现类的实现动作类似继承,关键字使用implements

    实现类实现接口后,久相当于声明:“我应该具备这个接口中的功能”,实现类需要重写方法以实现具体的功能;

    class 类 implements 接口 {
        重写接口中的方法
    }
    

    在类实现接口后,该类就会将接口中的抽象方法继承过来,此时该类需要重写该抽象方法,完成具体的逻辑

    1. 接口中定义功能,当需要该功能时,可以让实现类实现该接口,接口只声明应该具备该方法,是功能的声明;
    2. 在实现类中重写方法,实现功能,是方法的具体实现;

    接口中的成员变量特点

    • 接口中可以定义成员变量,但是变量必须有固定的修饰符public static final,所以接口中的变量也称为常量,其值不能改变
    interface Demo {
        public static final int NUM = 3;//接口中的变量值不能改变       
        public abstract void show1();
        public abstract void show2();
     }
    

    接口中的成员方法特点

    • 接口中可以定义方法,方法也有固定的修饰符public abstract;(不写也是默认public abstract修饰;建议写,增加可读性)
    • 子类必须覆盖掉接口中的所有抽象方法,子类才可以实例化,否则子类就是一个抽象类;
    class DemoImpl implements Demo {
        //重写接口中的方法
        public void show1(){}
        public void show2(){}
    }
    

    接口的多实现

    接口最重要的体现:解决多继承的弊端,将多继承这种机制在java中通过多实现完成

    interface Fu1 {
        public abstract void show1();
    }
    interface Fu2 {
        public abstract void show2();
    }
    //多实现;同时实现多个接口
    class Zi implements Fu1, Fu2 {
        public void show1(){}
        public void show2(){}
    }
    
    如何解决多继承弊端:

    多继承弊端:多继承时,当多个父类中有相同功能时,子类调用会产生不确定性;其实核心原因就是在于多继承父类中功能有主体,而导致调用运行时,不确定运行哪个主体内容;

    多实现能解决多继承弊端的原因在于:接口中的功能都没有方法体,由子类来明确

    类在继承类的同时实现多接口

    子类通过继承父类扩展功能,通过继承扩展的功能都是子类应该具备的基础功能,如果子类想要继续扩展其他类中的功能,可以通过实现接口来完成;

    class Fu {
        public void show() {}
    }
    interface Inter {
        public abstract void show1();
    }
    class Zi extends Fu implements Inter {
        public void show1() {}
    }
    

    接口的出现避免了单继承的局限性,父类中定义事物的基本功能,接口中定义事物的扩展功能;

    接口的多继承

    多个接口之间可以extends进行继承

    interface Fu1 { void show()}
    interface Fu2 { void show1()}
    interface Fu3 { void show2()}
    interface Zi extends Fu1, Fu2, Fu3 {
        void show3();
    }
    

    如果多个接口存在相同方法,若有个类实现了这些接口,那么就要实现接口中的方法,由于接口中的方法是抽象方法,子类实现后也不会发生调用的不确定性;

    接口思想

    举例:电脑上留有很多插口,这些插口可以插入相应设备,这些设备之所以能插在上面,原因在于这些设备在生产的时候符合了了插口的使用规则,否则将无法插入接口中,更无法使用,插口的出现,让我们可以使用更多的设备;

    好处:

    1. 接口的出现扩展了功能
    2. 接口其实就是暴露出来的规则
    3. 接口的出现降低了耦合性,即设备与设备之间实现了解耦

    接口的出现方便后期使用和维护,一方在使用接口(如电脑),一方在实现接口(插在插扣上的设备),例如:笔记本使用这个规则(接口),电脑外围设备实现这个规则(接口);

    接口和抽象的区别

    interface 缉毒 {
        public abstract void 缉毒();
    }
    abstract class 犬科 {
        public abstract void 吃饭();
        public abstract void 吼叫();
    }
    class 缉毒犬 extends 犬科 implements 缉毒 {
        public void 缉毒 () {};
        void 吃饭 () {};
        void 吼叫 () {};
    }
    class 缉毒猪 implements 缉毒 {
        public void 缉毒 () {}
    }
    

    从以上例子可以总结接口和抽象类的区别:

    相同:

    • 均位于继承的顶端,用于被其他类实现或继承

    • 都不能直接实例化对象

    • 都包含抽象方法,其子类都必须覆写这些抽象方法

    区别:

    • 抽象类为部分方法提供实现,避免子类重复实现这些方法,提高代码复用性,接口只能包含抽象方法
    • 一个类只能继承一个直接父类(可能是抽象类),却可以实现多个接口(接口弥补了java的单继承);
    • 接口是这个事物中的额外内容,继承体系是一种like...a关系

    二者的选用:

    • ​ 优先选用接口,尽量少用抽象类;
    • 需要定义字累的行为,又要为子类提供共性功能时才选用抽象类;

    多态概述

    多态是继封装、继承之后面向对象的第三大特性

    Java作为面向对象的语言,可以描述一个事物的多种形态,如Student类继承了Person类,一个Studenet的对象便既是Student,又是Person。

    Java中多态的代码体现在一个子类对象(实现类对象)既可以给这个子类(实现类对象)引用变量赋值,又可以给这个子类(实现类)的父类(接口)变量赋值。如:Student类可以是Person类的子类,那么一个Student对象既可以赋值给一个Student类型的引用,也可以赋值给一个Person类型的引用。

    最终多态体现为:父类引用变量可以指向子类对象

    多态的前提是必须有子父类关系,或者类实现接口关系,否则无法完成多态

    在使用多态后的父类引用变量调用方法时,会调用子类重写后的方法

    多态的定义与使用格式

    多态的定义格式,就是父类的引用变量指向子类对象;

    父类类型 变量名 = new 子类类型();
    变量名.方法名();
    
    1. 普通类多台定义的格式:
    父类 变量名 = new 子类();
    class Fu {}
    class Zi extends Fu {}
    //类的多态使用
    Fu f = new Zi();
    
    1. 抽象类多态定义的格式:
    抽象类 变量名 = new 抽象类子类();
    abstract class Fu {
            public abstract void method();
    }
    class Zi extends Fu {
        public void method () {
            System.out.println("重写父类抽象方法");
        }
    }
    //抽象类的多态使用
    Fu fu = new Zi();
    
    1. 接口多态定义的格式
    接口 变量名 = new 接口实现类();
    interface Fu {
            public abstract void method();
    }
    class Zi implements Fu {
        public void method() {
            System.out.println("重写接口抽象方法");
        }
    }
    //接口的多态使用
    Fu fu = new Zi();
    

    同一个父类的方法会被不同的子类重写,在调用方法时,调用的为各子类重写后的方法

    Person p1 = new Student();
    Person p2 = new Teacher();
    p1.work();  //调用Student类中重写的work方法
    p2.work();  //调用Teacher类中重写的work方法
    //当变量名指向不同的子类对象时,由于每个子类重写父类方法的内容不同,所以会调用不同的方法
    

    多态--成员的特点

    多态成员变量------当子父类中出现同名的成员变量时,多态调用该变量时:

    编译时期:参考的是引用变量所属的类中是否有被调用的成员变量;如果没有,编译失败;

    运行时期:也是调用引用变量所属的类中的成员变量;

    简而言之:编译和运行都参考等号的左边;编译运行看左边

    多态成员方法------

    编译时期:参考引用变量所属的类,如果所属类中没有调用的方法,编译失败;

    运行时期:参考引用变量所指的对象所属的类,并运行对象所属类中的成员方法;

    简而言之:编译看左边,运行看右边;但是,如果静态方法,编译和运行都看父类

    instance关键字

    可以通过instanceof关键字来判断某个对象是否属于某种数据类型

    Person p1 = new Student();
    boolean flag1 = p1 instanceof Student;  //flag结果为true
    boolean flag2 = p1 instanceof Teacher;  //flag结果为false
    

    多态-----转型

    多态的转型分为向上转型和向下转型;

    向上转型:当有子类对象赋值给一个父类引用时,便是向上转型;多态本身就是向上转型的过程;

    父类类型 变量名 = new 子类类型();
    Person p = new Student();
    

    向下转型:一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用转为子类引用,这个过程是向下转型;如果是直接创建父类对象,是无法向下转型的。

    子类类型 变量名 = (子类类型) 父类类型变量;
    Student stu = (Student) p; //变量p实际上指向Student对象
    

    多态的好处与弊端

    当父类的引用指向子类对象时,就发生了向上转型,即把子类类型对象转成了父类类型。向上转型的好处是隐藏了子类类型,提高了代码的扩展性。

    上转型的弊端,只能使用父类共性的内容,而无法使用子类特有功能,功能有限制

    abstract class Animal {
        public abstract void eat();
    }
    class Dog extends Animal {
        void eat() {
            System.out.prinln("啃骨头");
        }
        void lookHome() {
            System.out.println("看家");
        }
    }
    class Cat extends Animal {
        void eat() {
            System.out.println("吃鱼");
        }
        void catchMouse() {
            System.out.println("抓老鼠");
        }
    }
    public class Test {
        public static void main(String[] args) {
            Animal a = new Dog();   //多态形式,创建一个Dog对象
            a.eat();    //调用对象中的方法,会执行Dog类中的eat方法
            /**
                 *为了使用Dog类的lookHome方法,需要向下转型
                 *向下转型过程中,可能会发生类型转换的错误,即ClassCastException异常
                 *那么,在转之前需要做健壮性判断
                 */
            if (!a instanceof Dog) {
                System.out.println("类型不匹配,不能转换");
                return;
            }
            Dog d = (Dog) a;    //向下转型
            d.lookHome();   //调用Dog类的lookHome方法     
        }
    }
    
    多态举例
    class 毕姥爷 {
        void 讲课() {
            System.out.println("语文");
        }
        void 钓鱼() {
            System.out.println("钓鱼");
        }
    }
    //毕老师继承了毕姥爷,就有拥有了毕姥爷的讲课和钓鱼的功能
    //但毕老师和毕姥爷讲课内容不一样,因此毕老师要覆盖毕姥爷的讲课功能
    class 毕老师 extends 毕姥爷 {
        void 讲课() {
            System.out.pringtln("Java");
        }
        void 看电影() {
            System.out.println("看电影")
        }
    }
    public class Test {
        //多态形式
        毕姥爷 a = new 毕老师();  //向上转型
        a.讲课(); //这里表象时毕姥爷,其实真正讲课的仍然是毕老师,因此调用的也是毕老师的讲课功能
        a.钓鱼;   //这里表象是毕姥爷,但对象其实是毕老师,而毕老师继承了毕姥爷,即毕老师也具有钓鱼功能;
        //当要调用毕老师特有的看电影功能时,就必须要进行类型转换
        毕老师 b = (毕老师) a;
        b.看电影();
    }
    

    总结封装、继承、多态这三大特征的作用:

    • 封装:把对象的属性与方法的实现细节隐藏,仅对外提供一些公共的访问方式;
    • 继承:子类会自动拥有父类所有可继承的属性和方法;
    • 多态:配合继承与方法重写提高了代码的复用性和扩展性;如果没有方法重写,则多态同样没有意义;

    以一个笔记本案例来解释接口

    需求分析:

    1. 使用笔记本,笔记本有运行功能,需要笔记本对象类运行这个功能;
    2. 想使用一个鼠标,悠悠一个功能使用鼠标,并多了一个鼠标对象;
    3. 还想使用一个键盘,又要多一个功能和一个对象

    问题:这样每多一个功能就需要在笔记本类中定义一个方法,那么这个程序的扩展性非常差;需要降低鼠标、键盘等外围设备和笔记本之间的耦合性;

    /**
     *USB接口,包含开启、关闭功能
     *笔记本类,包含运行、关机功能,使用USB设备
     *鼠标类,要符合USB接口
     *键盘类,要符合USB接口
     */
    //鼠标、键盘、笔记本三者之间应该遵守的规则
    interface USB {
        public abstract void open(); //开启功能
        public abstract void close();   //关闭功能
    }
    //鼠标实现USB规则
    class Mouse implements USB {
        public void open() {
            System.out.println("鼠标开启");
        }
        public void close() {
            System.out.println("鼠标关闭");
        }
    }
    //键盘实现USB规则
    class KeyBoard implements USB {
        public void open() {
            System.out.println("键盘开启");
        }
        public void close() {
            System.out.println("键盘关闭");
        }
    }
    //定义笔记本
    class NoteBook {
        //笔记本开启功能
        public void run() {
            System.out.println("笔记本运行");
        }
      
        //笔记本使用USB设备,这时当笔记本对象调用这个功能时,必须给其传递一个符合USB规则的USB设备
        public void useUSB(USB usb) {
            if (usb != null) {
                usb.open();
                usb.close();
            }
        }
        public void shutDown() {
            System.out.println("笔记本关闭");
        }
    }
    //测试类
    public class Test {
        public static void main(String[] args) {
            //创建笔记本对象
            NoteBook noteBook = new NoteBook();
            noteBook.run();
          
            //创建鼠标实体对象
            Moust mouse = new Mouse();
            noteBook.useUSB(mouse);
          
            //创建键盘实体对象
            KeyBoard keyBoard = new KeyBoard();
            keyBoard.useUSB(keyBoard);
          
            //笔记本关闭
            noteBook.shutDown();
        }
    }
    
    

    构造方法介绍

    字面上理解为构建创造时使用的方法,即就是对象创建时要执行的方法,既然是对象创建时要执行的方法,那么只要在new对象时,知道其执行的构造方法时什么,就可以在执行这个方法的时候给对象进行属性赋值;

    因此构造方法的作用,就是:在new的同时给成员变量赋值,给对象属性进行初始化

    构造方法格式:

    修饰符 构造方法名 (参数列表) {
        
    }
    

    构造方法的体现

    • 构造方法没有返回值类型,也不需要写返回值;因为它是为构建对象的,对象创建完,构造方法就执行结束;
    • 构造方法名称必须和类型保持一致;
    • 构造方法没有具体的返回值;

    构造方法的调用赋值和内存图解

    构造方法是专门用来创建对象的,也就是在new对象时自动调用构造方法

    //案例
    class Person {
            //Person的成员属性age和name
        private int age;
        private String name;
        //Person的构造方法,拥有参数列表
        Person (int age, String name) {
            //接受到创建对象时传递进来的值,赋值给成员变量
            this.age = age;
            this.name = name;
        }
        public void speak () {
            System.out.println("name = " + name + ", age = " + age);
        }
    }
    
    class PersonDemo {
        public static void main (String[] args) {
                //创建Person对象,并明确对象的年龄和姓名
            Person p2 = new Person(23, "张三");
            p2.speak();
        }
    }
    //在创建对象时,会调用与参数列表对应的构造方法
    
    上述代码的图解: 构造方法的内存图.JPG
    1. 首先会将main方法压入栈中,执行main方法中的new Person(20, "张三");
    2. 在堆内存中分配一片区域,用来存放创建的Person对象,这片内存区域会有属于自己的内存地址,然后给成员变量默认初始化(age = null, age = 0);
    3. 执行构造方法中代码,将20赋值给this.age,将“张三”赋值给this.name,这段代码执行结束后,成员变量age和name的值已经改变,执行结束后构造方法弹栈,Person对象创建完成,将Person对象的内存地址赋值给p2。

    构造方法的重载

    如果我们没有显式指定构造方法,当在编译Java文件时,编译器会自动给class文件中添加默认的构造方法;

    如果我么显式地指定了构造方法,那么当在编译Java源文件时,编译器就不会再给class文件中添加默认构造方法;

    class Person {
            //如果没有显式指定构造方法,编译会在编译时自动添加默认的构造方法
        //Person() {}   //空参数的默认构造方法
    }
    

    要不要在类中写构造方法:需根据描述事物的特点来确定;当描述的事物需要在创建其对象时就要明确属性的值,就需在定义类的时候书写带参数的构造方法;若创建对象时不需要明确其属性,则不需书写构造方法(不输血会有默认的空参构造方法);

    • 构造方法的细节
      1. 一个类中可以有多个构造方法,多个构造方法是以重载的形式存在的
      2. 构造方法是可以被private修饰的;作用:其他程序无法创建该类的对象;
    class Person {
            private int age;
        private String name;
        //私有无参数构造方法,即不能通过new Person()语句创建本类对象
        private Person () {}
        //多个构造方法以重载的形式存在
        Person (int age) {
            this.age = age;
        }
        Person(String name, int age) {
            this.age = name;
            this.age = age;
        }
    }
    

    构造方法和一般方法区别

    1. 构造方法在对象创建时就执行了,而且只执行一次
    2. 一般方法是在对象创建后,需要使用时才被对象调用,并可以被多次调用;

    那么有了构造方法之后,可以对对象的属性进行初始化,那么还需要对应的set和get方法吗?:

    需要。因为对象在创建之后需要修改和访问相应的属性值时,只能通过set或者get方法来操作;

    this调用构造方法

    构造方法之间的调用可以通过this关键字来完成

    class Person {
        private int age;
        private String name;
            Person () {}
        Person (String name) {
            this.name = name;
            }
        Person(String name, int age) {
            //调用其他构造方法,通过this关键字来调用
            this(name);
            this.age = age;
        }
    }
    class PersonDemo {
        public static void main(String[] args) {
            Person person = new Person("张三", 23);
        }
    }
    
    构造方法调用的原理图.png

    图解说明:

    1. 先执行main方法,main方法压栈,执行其中的new Person("张三", 23);
    2. 堆内存中开辟空间,并为其分配内存地址,紧接着成员变量默认初始化(name=null, age=0);
    3. 拥有两个参数的构造方法Person(String name, int age)压栈,在这个构造方法中有一个隐式的this,因为构造方法是给对象初始化的,哪个对象调用到这个构造方法,this就指向堆中的哪个对象;
    4. 由于Person(String name, int age)构造方法中使用了this(name)构造方法,Person(String name)就会压栈,并将"张三"传递给name,在Person(String name)构造方法中同样也有隐式的this,this的值相同,这时就会执行其中this.name=name,即把"张三"赋值给成员的name,当赋值结束后构造方法Person(Stirng name)就会弹栈;
    5. 程序继续执行构造方法Person(String name, int age)中的this.age = age,这时就会把23赋值给成员属性age,赋值结束后构造方法Person(String name, int age)弹栈;Person对象在内存中创建完成,并将内存地址赋值给main方法中的person引用变量;
    • this代表对象,具体哪个对象调用了this所在方法this就代表哪个对象;
    • 调用其他构造方法的语句必须定义在构造方法的第一行,原因是初始化动作要最先执行
    class Person {
        private int age;
        private String name;
            Person () {}
        Person (String name) {
            this.name = name;
            }
        Person(String name, int age) {
            //调用其他构造方法,通过this关键字来调用
            this(name);
            this.age = age;
        }
    }
    class PersonDemo {
        public static void main(String[] args) {
            Person person = new Person("张三", 23);
        }
    }
    

    图解说明:

    1. 先执行main方法,main方法压栈,执行其中的new Person("张三", 23);
    2. 堆内存中开辟空间,并为其分配内存地址,紧接着成员变量默认初始化(name=null, age=0);
    3. 拥有两个参数的构造方法Person(String name, int age)压栈,在这个构造方法中有一个隐式的this,因为构造方法是给对象初始化的,哪个对象调用到这个构造方法,this就指向堆中的哪个对象;
    4. 由于Person(String name, int age)构造方法中使用了this(name)构造方法,Person(String name)就会压栈,并将"张三"传递给name,在Person(String name)构造方法中同样也有隐式的this,this的值相同,这时就会执行其中this.name=name,即把"张三"赋值给成员的name,当赋值结束后构造方法Person(Stirng name)就会弹栈;
    5. 程序继续执行构造方法Person(String name, int age)中的this.age = age,这时就会把23赋值给成员属性age,赋值结束后构造方法Person(String name, int age)弹栈;Person对象在内存中创建完成,并将内存地址赋值给main方法中的person引用变量;
    • this代表对象,具体哪个对象调用了this所在方法this就代表哪个对象;
    • 调用其他构造方法的语句必须定义在构造方法的第一行,原因是初始化动作要最先执行

    子父类中构造方法的调用

    创建子类对象的时,父类的构造方法会先执行,因为子类中所有构造方法的第一行有默认的隐式super()语句

    public class Test {
            public static void main(String[] args) {
            new Zi();
        }
    }
    class Fu {
        int num;
        Fu () {
            System.out.println("Fu构造方法" + num);
            num = 4;
        }   
    }
    class Zi extends Fu {
        Zi() {
            //super();  隐式调用父类空参构造方法
            System.out.println("Zi构造方法"+this.num);
        }
    }
    /**
     *执行结果:
     *Fu构造方法0
     *Zi构造方法4
     */
    

    通过上述例子结果可发现,子类构造方法在执行时,调用了父类构造方法,这说明子类构造方法中有一句super();

    原因:子类会继承父类中的内容,所以子类在初始化时,必须先到父类中去执行父类的初始化工作,这样才可以使用父类的内容

    当父类中没有空参构造方法时,子类的构造方法必须有显式的super语句,指定要访问的父类有参构造方法

    • this()和super()只能定义在构造方法的第一行,因为初始化动作要先执行;

    • this()是调用本类的构造方法,super()是调用父类的构造方法,且两条语句不能同时存在

    • 只要保证子类的所有构造方法中,有且至少一个调用到父类的构造方法,直接或间接都可以

    • 如果子类的构造方法里this()和super()都不写,则默认的构造方法第一行是隐式的super()

    final关键字的概念

    继承的出现提高了代码的复用性方便开发,但是随之也有问题,有些类在描述完之后,不想被继承,或者有些类中的部分方法功能是固定的,不想让子类重写,可是当子类继承了这些特殊类之后,就可以对其中的方法进行重写,需要解决这个问题。

    要解决上述问题,需要使用到一个关键字final;final是个修饰符,可以用来修饰类、类成员、以及局部变量

    final的特点

    • final修饰类不可以被继承,但是可以继承其他类

    • final修饰的方法不可以被覆写,但父类中没有被final修饰的方法,子类可以覆写后可以加final

      class Fu {
            public final void method1(){};//final修饰的方法,不可以被覆写,但可以被继承使用
            public void methd2(){}
      }
      class Zi extends Fu {
            public final void method2(){}
      }
      
    • final修饰的变量称为常量,这些变量只能被赋值一次

    • 引用类型的变量值为对象地址值,地址值不能更改,但是地址内的对象属性值可以修改

      final Person p = new Person();
      p.name = "xiaoming";//可以更改p对象中name属性值
      
    • 修饰成员变量,需要在创建对象前赋值,否则报错

      class Demo {
            //直接赋值
            final int m = 100;
            //final修饰的成员变量,需要在创建对象前赋值,否则报错
            final int n;
            public Demo() {
                n = 2016;//可以在创建对象时所调用的构造方法中赋值
          }
      }
      

    static关键字概念

    当在定义类的时候,类中都会有相应的属性和方法,而属性和方法都是通过创建本类对象调用的,当在调用对象的某个方法时,这个方法没有访问到对象的特有数据时,方法创建这个对象有些多余,可是不创建对象又调用不了,这时就会想,我们是否能不创建对象就可以调用方法;

    是可以的,可以通过static关键字来实现,static它是静态修饰符,一般来修饰类中的成员;

    static特点

    • 被static修饰的成员变量属于类,不属于这个类某个对象;

    也即是说,多个对象在访问或修改static修饰的成员变量时,其中一个对象将static成员变量值进行了修改,其他对象中的static成员变量跟着改变,即多个对象共享同一个static成员变量

    class Demo {
        public static int num = 100;
    }
    class Test {
        pubkic static void main() {
            Demo d1 = new Demo();
            d1.num = 200;
            System.out.println(d1.num);//结果为200
            System.out.println(d2.num);//结果为200
        }
    }
    
    • 被static修饰的成员可以并且建议通过类名直接访问

      类名.静态成员变量
      类名.静态成员方法名(参数)
      对象名.静态成员变量名//不建议这样方式,会警告
      对象名.静态成员方法名(参数)//不建议这样的方式,会警告
       
      

    static注意事项

    • 静态内容是优先于对象存在,只能访问静态,不能使用this/super,静态修饰的内容存在于静态区

      class Demo {
            public int num = 100;
            //静态方法
            public static void method() {
                //不能使用this.num;不能使用this/super。因为静态内容先于对象存在
                //System.out.println(this.num);
          }
      }
      
    • 同一个类中,静态成员只能访问静态成员

      class Demo {
            public int num = 100;//成员变量
            public static int count = 200;//静态成员变量
            public static void method() {
                //静态方法中只能访问静态成员或静态成员方法
                System.out.println(count);          
            }
      }
      

    static静态的使用场景

    • 何时使用静态成员变量:加static修饰成员的时候,这个成员会被类的所有对象所共享。一般把共性数据定义为静态的变量
    • 何时使用static修饰成员方法:静态的方法只能访问静态的成员,如果静态方法中引用到了静态的其他成员,那么这个方法需要声明为静态的方法

    对象中的静态调用

    在多态中,非静态方法,编译看父类,运行看子类,如果父类内没有编译失败;

    但是多态的静态方法,编译看父类,运行仍然看父类。因为静态和对象关系,属于静态绑定;

    定义静态常量

    开发中,在类中定义一个静态常量,通常使用public static final修饰的变量来完成定义,此时变量名大写,多个单词之间用下划线连接

    public static final 数据类型 变量名 = 值;
    class Company {
        public static final String COMPANY_NAME = "ICBC";
        public static void method() {
            System.out.println("一个静态方法");
        }
    }
    //当想使用类的静态成员时,不需要创建对象,直接使用类名.来访问即可
    class Test {
        System.out.println("Company.COMPANY_NAME");
        Company.method();
    }
    
    • 接口中的每个成员变量默认使用public static final修饰

    • 所有接口中的成员变量已是静态常量,由于接口没有构造方法,所以必须显式赋值,可以直接用接口名访问

      interfac Inter {
            public static final int COUNT = 100;
      }
      class Test {
            System.out.println(Inter.COUNT);
      }
      

    匿名对象概念

    匿名对象指创建对象时,只有创建对象的语句,却没有把对象地址赋值给某个变量;

    public class Person {
        public void eat() {
            System.out.println()
        }
    }
    //创建一个普通对象
    Person person = new Person();
    //创建一个匿名对象
    new Person();
    

    匿名对象的特点

    • 创建匿名对象直接使用,没有变量名

      new Person.eat();//eat方法被一个匿名的Person对象调用了
      
    • 匿名对象可以作为方法接收的参数、方法返回值使用

      class Demo {
            public static Person getPerson() {
                //匿名对象作为方法返回值
                return new Person();
          }
          public static void method(Person p) {}
      }
      class Test {
            public static void main(String[] args) {
                Person person = Demo.getPerson();//调用一个Person对象
                Demo.method(person);//调用method方法
                Demo.method(new Person);//匿名对象作为方法接收的参数
          }
      }
      

    内部类概述

    • 什么是内部类:将类写在其他类的内部,可以写在其他类的成员位置和局部位置,这时写在其他类内部的类就称为内部类,其他类也称为外部类;
    • 什么时候使用内部类:在描述事物时,若一个事物内部还包含其他可能的事物,比如在描述汽车时,汽车中还包含发动机,这时发动机就可以使用内部类来描述;
    • 内部类的分类:内部类分为成员内部类和局部内部类;定义内部类时,就是一个正常定义类的过程,同样包含各种修饰符、继承与实现关系,在内部类中可以直接访问所在外部类的所有成员

    成员内部类

    成员内部类,定义在外部类的成员位置,与类中的成员变量相似,可通过外部类对象进行访问;

    //定义格式
    class 外部类 {
        修饰符 class 内部类 {
            
        }
    }
    //访问方式
    外部类名.内部类名 变量名 = new 外部类名().new 内部类名();
    
    //定义类
    class Body {//外部类,身体
            private boolean life = true;
        public class Heart {//内部类,心脏
            public void jump() {
                System.out.println("心脏扑通扑通的跳");
                System.out.println("声明状态" + life);
            }
        }
    }
    //访问内部类
    public static void main(String[] args) {
        //创建内部类对象
        Body.Heart bh = new Body().new Heart();
        //调用内部类中的方法
        bh.jump();
    }
    
    成员内部类的同名变量调用
    public class Outer {
        int i = 1;
        class Inner {
            int i = 2;
            public void inner() {
                int i = 3;
                System.out.println(Outer.this.i)
            }
        }
    }
    

    局部内部类

    局部内部类,定义在外部类方法中的局部位置,与访问方法中的局部变量相似,可通过调用方法进行访问;

    //定义格式
    class 外部类 {
        修饰符 返回值类型 方法名(参数) {
            class 内部类 {
                
            }
        }
    }
    //访问方式
    在外部类方法中,创建内部类对象 进行访问
    
    class Party {//外部类,聚会
        public void puffBall() {//吹气球方法 
            class Ball {//内部类,气球
                public void puff() {
                    System.out.println("气球膨胀了");
                }
            }
            //创建内部类对象,调用puff方法
            new Ball.puff()
        }
    }
    
    //访问内部类
    public static void main(String[] args) {
        //创建内部类对象
        Party party = new Party();
        //调用外部类中的puffBall方法
        p.puffBall();
    }
    

    匿名内部类概念

    匿名内部类是最常用到的内部类,也是局部内部类的一种

    定义的匿名内部类有两个含义:

    • 临时定义某一指定类型的子类;
    • 定义后即刻创建刚刚定义的这个子类的对象;

    作用:匿名内部类是创建某个类型子类对象的快捷方式

    定义匿名内部类的作用与格式

    格式:

    new 父类或接口() {
        //进行方法重写
    }
    
    //已经存在的父类
    public abstract class Person {
        public abstract void eat();
    }
    //定义并创建该父类的子类对象,并用多态的方式赋值给父类引用变量
    Person person = new Person() {
      public void eat() {
            System.out.println("wo chi le");
      }
    };
    p.eat();
    

    使用匿名对象的方式,将定义子类与创建子类对象两个步骤由一个格式一次完成,虽然是两个步骤,但是两个步骤是连在一起完成的;

    匿名内部类如果不定义变量引用,则也是匿名对象;

    new Person() {
            public void eat() {
            System.out.println("wo chi le");
        }       
    }.eat();
    

    包的概念

    java的包,就是文件夹,包里存放的是类文件;

    当类文件很多的时候,通常会采用多个包进行存放管理它们,这种方式称为分包管理

    在项目中,将相同功能的类放到一个包中,方便管理,并且日常项目的分工也是以包作为边界;

    类中声明的包必须与实际class文件所在文件夹情况一致,即类声明在a包下,则生成的.class文件必须在a文件夹下,否则程序运行时会找不到类;

    包的声明格式

    通常使用公司网址反写,可以有多层包,包名采用全部小写,多层包之间.连接

    声明包的语句必须写在程序有效代码的第一行(注释不算);

    包的访问

    在访问类时,为了能够找到该类,必须使用含有包名的类全名;

    类的简化访问:当要使用一个类时,这个类与当前程序在同一个包中(即同一个文件夹中),或者这个类是java.lang包中的类,通常可以省略包名,直接使用该类;

    import导包

    每次使用类时,都需要写很长的包名,很麻烦,通过import导包的方式来简化,通过导包的方式使用该类,可以避免使用全类名编写;

    import 包名.类名
    

    import导包代码书写的位置,在声明package后,定义所有类class前;

    访问修饰符

    java中提供了四种访问权限,使用不同的访问权限,被修饰的内容会有不同的访问权限:

    public protected default private
    同一类中
    同一包中(子类与无关类)
    不同包的子类
    不同包的无关类中

    归纳,在日常开发过程中,编写的类、方法、成员变量的访问:

    • 若要仅可在本类中访问,使用private修饰

    • 若要本包中的类都可以访问,不加修饰符即可

    • 若要本包中的类与其他包中的子类可以访问,用protected修饰;

    • 若要所有包中的所有类都可以访问,使用public修饰

    • 注意:如果类使用public修饰,则类名必须与文件名相同,一个文件中只能有一个public的类

    局部代码块

    局部代码块是定义在方法或语句中;特点:

    1. 以{}划定的代码区域,此时只需关注作用域的不同即可;
    2. 方法和类都是以代码块的方式划定边界的;
    class Demo {
            public static void main(String[] args) {
            {
                int x = 1;
                System.out.println("普通代码块" + x);//1
            }
            int x = 99;
            System.out.println("代码块往外" + x);//99
        }
    }
    

    构造代码块

    构造代码块是定义在类中成员位置的代码块;特点:

    1. 优先于构造方法执行,构造代码块用于执行所有对象均需要的初始化动作

    2. 每创建一个对象均会执行一次构造代码块

      public class Person {
           private String name;
           private int age;
               //构造代码块
           {
               System.out.println("构造代码块执行了");
           }
           Person() {
               System.out.println("Person无参数的构造函数执行")
          }
           Person(int age) {
               this.age = age;
               System.out.println("Person(age)参数的构造函数执行");
          }
      }
      class PersonDemo {
           public static void main(String[] args) {
               Person person = new Person();
               Person person1 = new Person(23);
          }
      }
      

    静态代码块

    静态代码块是定义在成员位置,使用static修饰的代码块;特点:

    1. 优先于主方法执行,优先于构造代码块执行,当以任意形式第一次使用到该类时执行
    2. 该类不管创建多少对象,静态代码块只执行一次
    3. 可用于给静态变量赋值,用来给类进行初始化
    public class Person {
        private String name;
        private int age;
        //静态代码块
        static {
            System.out.println("静态代码块执行了")
        }
    }
    

    不同修饰符使用细节

    修饰符 形式 可修饰内容
    public权限修饰符 公共访问 类、方法、成员变量
    protected权限修饰符 受保护访问 方法、成员变量
    默认什么也不写 默认访问 类、方法、成员变量
    private权限修饰符 私有访问 方法、成员变量
    static静态修饰符 方法、成员变量
    final最终修饰符 类、方法、成员变量、局部变量
    abstract抽象修饰符 类、方法

    编写程序时,权限修饰符一般放于所有修饰符之前,不同的权限修饰符不能同时使用

    • abstract与private不能同时使用

    • abstract 与static不能同时使用

    • abstact与final不能同时使用

    • 修饰类能够使用的修饰符:

      public、默认的、final、abstract
      使用最多的是public
      public class Demo {}//最常用方式
      class Demo2 {}
      public final class Demo3 {}
      public abstract class Demo4 {}
      
    • 修饰成员变量能够使用的修饰符:

    成员变量 public、protected、默认的、private、final、static
    使用最多的是private
    public int count = 100;
    protected int count2 = 100;
    int count3 = 100;
    private int count4 = 100;//最常用的方式
    public final int count5 = 100;
    public static int count6 = 100;
    
    • 修饰构造方法能够使用的修饰符:

      构造方法 public、protected、默认的、private、
      使用最多的是public
      public Demo() {}//使用最多的方式
      protected Demo() {}
      Demo() {}
      private Demo() {}
      
    • 修饰成员方法能够使用的修饰符:

      成员方法 public、ptotected、默认的、private、final、static、abstract
      使用最多的是public、static、abstract
      public void method() {}//最常用的方式
      protected void method() {}
      void method() {}
      private void method() {}
      public final void method() {}
      public static void method() {}//最常用的方式
      public abstract void method() {}//最常用的方式
      

    定义成员变量还是局部变量

    如果需要定义的变量是事物的一部分时,定义成成员变量;如:

    //长方形类
    public class CFX {
            //因为长与宽,在现实事物中是属于事物的一部分,所以定义成成员变量
        private int chang;
        private int kuan;
      
        public CFX(int chang, int kuan) {
            this.chang = chang;
            this.kuan = kuan;
        }
        //求周长
        public double zhouchang() {
                return  (this.chang + this.kuan) * 2;
        }
        //求面积
        public double mianji() {
            return this.chang * this.kuan;
        }
        
        public int getChang() {
            return chang;
        }
    
        public void setChang(int chang) {
            this.chang = chang;
        }
    
        public int getKuan() {
            return kuan;
        }
    
        public void setKuan(int kuan) {
            this.kuan = kuan;
        }
    }
    

    如果需要定义的变量不应该是类的一部分,仅仅是功能当中需要参与计算的数,则定义为形参变量;如:

    public class MathTool {
        //求两个数的和的两倍
        public double area(int number1, int number2) {
                return (number1 + number2) * 2;      
        }       
    }
    

    类作为方法参数和返回值

    • 类作为方法参数

      class Person {
            public void show() {
                System.out.println("show方法执行了");
          }
      }
      class public Test {
            public static void main(String[] args) {
                Person person = new Person();
                method(person);
          }
            public static void method(Person person) {
                p.show;
          }
      }
       
      
    • 类作为方法返回值

      class Person {
            public void show() {
                System.out.println("show方法执行了");
          }
      }
      public class Test {
            public static void main(String[] args) {
                Person person = method();
                person.show();
          }
            public static Person method() {
                Person person = new Person();
                return person;
          }
      }
      

    抽象类作为方法参数和返回值

    • 抽象类作为方法参数

      abstract class Person {
            public abstract void show();
      }
      class Student extends Person {
            public void show() {
                System.out.println("重写了show方法");
          }
      }
      public class Test {
            public static void main(String[] args) {
                Person person = new Student();
                method(person);
          }
            public static void method(Person person) {
                p.show();
          }
      }
      
    • 抽象类作为方法返回值

      abstract class Person {
            public abstract void show();
      }
      class Student extends Person {
            public void show() {
                System.out.println("重写了show方法");
          }
      }
      public class  Test {
            public static void main(String[] args) {
                Person person = method();
                p.show();
          }
            public static Person method() {
                    Person person = new Student();
                return person;
          }
      }
      

    接口作为方法参数和返回值

    • 接口作为方法参数

      interface Smoke {
            public abstract void smoking();
      }
      class Student implements Smoke {
            public void smoking() {
                System.out.println("课下吸口烟,赛过活神仙");
          }
      }
      public class Test {
            public static void main(String[] args) {
                Smoke smoke = new Student();
                method(smoke);  
          } 
            public static void method(Smoke smoke) {
                smoke.smoking();
          }
      }
      
    • 接口作为方法返回值

      interface Smoke {
            public abstract void smoking();
      }
      class Student implements Smoke {
            public void smoking() {
                System.out.println("课下吸口烟,赛过活神仙");
          }
      }
      public class Test {
            public static void main(String[] args) {
                    Smoke smoke = method();
                smoke.smoking();
          }
            public static Smoke method() {
                Smoke smoke = new Student();
              return smoke;
          }
      }
      

    相关文章

      网友评论

          本文标题:java面向对象

          本文链接:https://www.haomeiwen.com/subject/ecsuuhtx.html