Java的面向对象(详解)

Java的面向对象(详解)

Java是一门面向对象的编程语言,一次编写到处运行。OOA(面向对象分析从)、OOD(面向对象设计)、OOP(面向对象编程)。

一、类和对象的关系

:是一类事物的抽象的模板,如人类、学生类等。
对象:是由一类事物中的某一具体的一个个体,如小明是一个学生,他具有学号、姓名、年龄等特征,相当于由学生类这个类生成出来的带有学号、姓名、年龄属性并附有小明个人的具体信息。所以对象就是new出来的一段内存,用来存储对象的实列属性和方法。
类和对象的关系:类是抽象的,而对象是具体的,对象是由类创建的实例。

二、访问修饰符

访问修饰符解释+访问权限范围
private私有的,仅在本类中可以使用,被private修饰的属性,需要通过setter和getter进行赋值和获取,或者通过构造函数进行赋值。
protected受保护的,可以在同包下所有类进行访问,或者不同包,通过继承关系的子类中可以进行访问。
default默认的,只能在同包下所有类访问。(属性前不写修饰符就是默认default)
public公共的,同一项目下所有类都可以访问。

三、构造器详解

四、this和super关键字

this:表示当前对象,每当new出一个对象的时候,对象内存中含有一个隐式的this变量指向自己。
super:超级、用于调用父类成员和方法区分于this。

  • 继承关系中:

在子类中,子类通过this关键字访问自己的属性和方法,通过super关键字访问父类的属性和方法。(this和super可以省略,省略后,使用就近原则,先在自己的类中查找是否有这个属性或方法,若有就调用自己的属性或方法,如果没有就区父类中查找,调用父类的属性和方法,注意父类私有的属性和方法不能被子类继承。见下面继承详解)

  • 构造方法和成员方法中:

(1)成员方法中或者构造方法中的形参如果与成员属性不同名的时候this是可以省略的,如果同名了就要加上this进行区分,因为如果没有this的话,方法中的局部变量是采用就近原则机制,两个都是形参,自己给自己赋值。所以一般带上this,避免出现错误。(this用于区分局部变量和实例变量时候不能省略。)
(2)this和super也可以使用在构造函数中,但是必须写在构造函数的第一行。表示用当前的构造方法调用其他的构造方法或者父类的构造方法(语法格式:this(参数1,参数2,参数,…,参数n))后面继承中具体详解。

五、static关键字

static修饰的组件不需要通过对象访问,而是直接通过类名访问,但也能通过对象引用访问(后面自己专研详解以下,在不同类中,对象为空也能拿到数据,这是不应该通过类名才能拿到吗//(提醒自己对应下面的多态案例三))。在类一加载的时候会给static修饰的属性和方法分配内存区,这个内存分配在静态内存中。后续所有的对象操作的是同一个内存。
类的组件执行顺序
类编译成.class文件,被JVM的类加载器加载——》从上往下初始化static修饰的组件(静态属性,静态代码块,静态方法,其中静态方法调用才会执行,静态属性和静态代码块直接分配内存)——》构造代码块——》执行构造器——》初始化成员属性,成员方法

六、封装

为了提高类的隐蔽性,对类的实现的细节进行隐藏,提供外部访问的接口即可,提高代码的可扩展性。
(1)对类的成员属性进行封装:将属性私有化(使用private进行修饰),提供对属性访问的接口,给属性添加setter和getter方法。
(2)对代码的封装:为了提高代码的复用性,尽量使用方法加参数传递对代码进行封装,并使该方法公有(用public修饰)。列如:公有的求和方法,调用这个方法给两个参数就能得到它们的和,却不用在意内部到底是怎么求和的。
注意:对Boolean类型的属性,获取属性使isXxx()的形式,与其他数据类型使getXxx()。

封装的优点:
(1)良好的封装可以减少类的耦合性(类与类的关联)。
(2)对类中封装的代码可以自由修改,而不影响其他类。
(3)最大程度提高类中属性的隐藏性和对属性的控制。

七、继承

当多个类中有些属性和方法相同时,可以新建一个类,封装这些属性和方法,让其他类来继承复用这个类的属性和方法。缩短开发时间。提高代码的复用度。
被继承的类称为父类,继承父类的类称为子类。父类又称为超类、基类。子类又称为新类、派生类。

语法:

public class 父类{......}
public class 子类 extends 父类{......}

子类拥有父类的哪些属性和方法?
(1)子类拥有父类公有(public修饰)的属性和方法、默认修饰下(default修饰)的同包下的属性和方法、不同包下的受保护的(protected修饰)属性和方法。
(2)子类不能继承父类私有的属性和方法,或(子类和父类在不同包)不同包下default修饰的属性和方法,不能继承父类的构造器。

  • 继承中子类的构造器
    案例一:
    父类People
public class People {
    static{
        System.out.println("这是父类People的静态代码块!");
    }
    public People(){
        System.out.println("这是父类People的无参构造器!");
    }
    {
        System.out.println("这是父类People的构造代码块!");
    }
    public void say(){
        System.out.println("你好呀!!!");
    }
}

子类Student

public class Student extends People{
    static {
        System.out.println("这是子类Student的静态代码块!");
    }
    public Student(){
        System.out.println("这是子类Student的无参构造器!");
    }
    {
        System.out.println("这是子类Student的构造代码块!");
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        new Student();//创建子类对象
        Student student=new Student();
        student.say();
    }
}

在这里插入图片描述

从图上的结果可以看出,先执行的顺序是父类静态代码块+子类静态代码块+父类构造代码块+父类构造器+子类构造代码块+子类构造器。这是为何呢???
因为new Student();创建子类对象的时候,会隐式的去先创建父类对象,再创建子类对象。所会先加载父类,再加载子类。由于静态资源是在类加载的时候完成初始化的,所以静态代码块在类加载的时候就执行了,并且只执行一次。因为静态资源加载的时候进行,类加载一次,将资源存放在方法区内存中,直到类不再使用的时候才会回收。所以静态代码块从头到尾只执行一次。而构造代码块就不一样,它存放在堆中,与对象相关,new一个对象就会执行一次,所以new多少个对象就会执行多少次。并且构造代码块先于构造器执行。
这里的Student类中虽然没有say()方法,但是其继承的父类中有此方法,所以能通过Student对象对其访问。其实可以看做隐式的Student中有一个say()方法从父类那里继承过来的,但是如果在子类中对这个方法进行重写后,输出的结果是子类中重写的方法。所以不管怎样其实是调用的子类中的方法,只不过可能子类中没有重写直接用的隐式传过来的父类方法,或者是重写后的方法。

案例二:

public class A {
    int age;
    public A(int age){
       this.age=age;//父类有参构造器
    }
}

public class B extends A{
    public B(int age){//super(参数);写在构造器第一行
        super(age);//你会发现如果子类中不写构造器会报错,或者这里不写super(a);也会报错
    }
}

当父类A中有一个有参构造器的时候,你会发现你的子类不写构造或者写一个无参构造器会报错,这是为什么呢?因为子类的构造器会默认去使用父类的无参构造器,对继承过来的属性进行初始化,而父类中已经写了一个有参构造器,系统不会给它隐式创建一个无参构造器。所以这是找不到父类无参构造器就会报错。因此,这是需要你去手动调用父类的有参构造器进行初始化。

八、多态

同一操作作用于不同的对象,可以产生不同的效果。

  • 实现多态的前提:
    (1)基于继承关系或基于实现关系。
    (2)子类或实现类必须对方法进行重写(没有重写的方法不具有多态的行为)。
    (3)父类的引用指向子类对象(接口的引用指向实现类的对象)。

  • 多态对象转换:
    (1)子类转父类,向上转型,自动转型。(upcasting)
    (2)父类转子类,向下转型,自动转型。(由于向下转型存在风险,所以一般会用instanceof对引用数据类型进行类型判断)

案例一:

//Animal父类
public class Animal {
    public void eat(){}
}

//Bird子类
public class Bird extends Animal{
    @Override
    public void eat() {//多态表现形式,子类必须对父类方法进行重写
        System.out.println("早起的鸟儿有虫吃!!!");
    }
}

//Dog子类
public class Dog extends Animal{
    @Override
    public void eat() {//多态表现形式,子类必须对父类方法进行重写
        System.out.println("狗在啃骨头!!!");
    }

    public void seeDoor(){//子类特有的方法
        System.out.println("狗在看门!!!");
    }
}

//Cat子类
public class Cat extends Animal{
    @Override
    public void eat() {//多态表现形式,子类必须对父类的方法进行重写
        System.out.println("猫咪在吃鱼!!!");
    }

    public void catchMouse(){//子类特有的方法
        System.out.println("猫在抓老鼠!!!");
    }
}

//People类
public class People {
    public void feed(Animal animal){//多态的好处作用,无论引用数据类型的参数怎么变,这个方法
    //无需重新修改方法,或者或者对其进行重载,每增加一个子类动物就添加一个重载方法
        animal.eat();
    }
}

//Test类
public class Test1 {
    public static void main(String[] args) {
        Animal bird = new Bird();//父类引用指向子类对象
        Bird bird1 = new Bird();

        Animal cat = new Cat();
        Cat cat1 = new Cat();

        Animal dog = new Dog();
        Dog dog1 = new Dog();

        People people = new People();
        people.feed(bird);
        people.feed(bird1);//向上自动转型
        System.out.println("==================================");
        people.feed(cat);
        people.feed(cat1);//向上自动转型
        System.out.println("==================================");
        people.feed(dog);
        people.feed(dog1);//向上自动转型
    }
}

在这里插入图片描述
Animal bird = new Bird();//父类引用指向子类对象,Bird bird1 = new Bird();//创建子类对象,people.feed(bird); //调用feed()方法,people.feed(bird1);//运行的结果一样,因为People方法中传入bird1,但是方法中参数数据类型是Animal,bird1是Bird类型,传入参数是虽然参数是子类型,但子类自动转换为Animal型(向上自动转型),程序不会报错。又因在多态机制中,如果子类重写了父类方法的化,就是调用子类的方法。
你会发现多态的好处,不管以后添加多少个其他动物子类,People类中的feed()方法永远无需修改。

案例二:另写一个测试类,其他代码还是一样。ClassCastException

public class Test2 {
    public static void main(String[] args) {
        Animal animal = new Dog();
        Dog dog = (Dog) animal;
        Cat cat = (Cat) animal;
    }
}

在这里插入图片描述
你会发现写Cat cat = (Cat) animal;编译居然没有报错,但运行的时候报错.ClassCastException(类型转换异常)。这是为啥呢?
因为Java程序分为两个阶段(编译阶段和运行阶段)。
Java程序会先分析编译阶段,再分析运行阶段,编译阶段不通过,根本无法运行。编译阶段的时候会发现animal是Animal数据类型,dog和cat都是它的子类数据类型,可以转换成功。但是运行的时候,JVM内存当中真实创建的对象是Dog,Dog无法转换成Cat类型,所以运行报错。
因此,有一关键字能对引用数据类型的数据类型进行判断instanceof(是这个的实例吗)

public class Test2 {
    public static void main(String[] args) {
        Animal animal = new Dog();
        if(animal instanceof Dog){
             Dog dog1=(Dog) animal;
        }
        if(animal instanceof Cat){
             Cat cat1=(Cat) animal;
        }
    }
}
public class Test2 {
    public static void main(String[] args) {
        Animal animal = new Dog();
        animal.eat();
        ((Dog) animal).seeDoor();
    }
}

在这里插入图片描述

在这里插入图片描述
我们创建了一个父类引用指向子类Dog,animal.eat()调用方法输出的结果是调用子类的方法,是因为子类中重写了父类的方法,虽然animal的数据类型是父类类型,但是其指向的对象是子类,又因子类中重写了这个方法,所以调用的是子类中的重写方法。用animal去调用子类中特有的方法seeDoor()方法你会发现,编译报错,因为编译器识别到animal是父类型,就去Animal类中查找并发现没有这个方法,= new Dog()赋值运算是在运行期间才会赋值的,所以会报错。

案例三:
多态机制下方法和属性的调用:
属性和成员方法:属性看左边引用数据类型(父类),没有重写的方法看左边引用数据类型(父类),重写了的方法看右边实质对象(子类)。父类的数据类型引用虽然指向子类对象但是不能调用子类的方法。如上面的案例二,需要强转到子类才行。
静态方法:无论有没有重写都是看左边的引用数据类型(父类)。

public class People {
    private int a=10;
    private int age=10;
      static String name="LH";

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public static String getName() {
        return name;
    }

    public static void setName(String name) {
        People.name = name;
    }

    public void say(){
        System.out.println("我是父类People的方法!!!"+this.age);
    }

    public static void study(){
        System.out.println("人类需要不断学习!!!");
    }
}
public class Student extends People{
    //private int a;
    private int age=18;
     static String name="张三";

    @Override
    public int getAge() {
        return age;
    }

    @Override
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public void say() {
        System.out.println("我是子类Student的方法!!!"+"\t学生年龄:"+this.age);
    }

    public static void study(){
        System.out.println("学生要考大学!!!");
    }
}
public class Test2 {
    public static void main(String[] args) {
        People people=new People();
        System.out.println(people.getAge());
        people.say();
        people.study();
        System.out.println("===========================================================");
        Student student=new Student();
        System.out.println(student.getAge());
        student.say();
        student.study();
        System.out.println("===========================================================");
        People student1=new Student();
        System.out.println(student1.getAge());//子类getAge方法重写 子类age=18
        student1.say();//重写方法看子类
        student1.study();//静态方法看父类(无论重写是否)
        System.out.println(student1.getName());//没有重写 父类 LH
        System.out.println(((Student) student1).name);//强转为子类  张三
        System.out.println(student1.name);//静态属性 父类 LH
        System.out.println("===================================================");
        student1=null;//引用为空
        System.out.println(student1.name);//static 类的属性 name父类
        student1.study();//People的say()方法
        //student1.say();//NullPointerException  成员方法必须有对象才能调用 空指针异常

        Student student2=new Student();
        //System.out.println(student2.a); 子类不能继承父类私有属性
    }
}

在这里插入图片描述

九、重载和重写

1、重载

2、重写(只对方法而言)
必要条件:
(1)两个方法名必须相同,且存在不同类中(父子类关系中,接口与实现类中)。
(2)子类重写的父类的方法,其方法的参数和返回值类型必须完全一样,方法体不一样。
(3)访问修饰符必须大于或等于父类的修饰符。如果父类的方法或属性是私有的(private修饰)根本无法继承,所以根本不存在重写。

3、重载和重写的区别
(1)重载发生在同一个类中,重写发生在两个类中,父子两个类或者接口与实现类中.
(2)重写必须满足方法名相同,返回值类型相同,参数列表相同,访问修饰符相同。而重载是方法名相同、参数列表不同,与返回值类型和访问修饰符无关。
(3)重写的访问修饰符必须子类大于或等于父类,重载没有要求,因为与访问修饰符无关。

十、抽象类

待续

十一、接口

待续

十二、final关键字

待续

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值