6 Java的基本程序设计结构(基本语法5)- 面向对象进阶

文章目录


面向对象进阶

这块是java编程基础!!!!


一、 static 静态

关于静态这个东西前面已经简单讲过,这里再详细讲一讲。

1 静态变量

(1)基本定义和用法

  • 定义:被static修饰的成员变量,叫做静态变量。
  • 特点:**被该类所有对象共享(简单来说你修改任意一个对象里面的static成员变量,所有对象的该成员变量的值都会变)
  • 调用方式**
    (1)直接类名调用(推荐)
    (2)对象名调用

关于被该类所有对象共享这个举个例子说明一下。
一个学生类,里面有一个静态成员变量是老师,修改任意一个学生对象的老师这个成员变量,其余学生对象的老师也会统一修改。简单来说,静态成员变量是属于这个类,是属于所有对象,修改一个对象就会影响所有对象。(现实中学生类一个班老师肯定都是一样的,就可以使用静态成员变量修饰)

package cn.hjblogs.demo;

public class Student {
   
    private String name;
    private int age;
    public static String teacher_name;     // 静态变量
    // static String teacher_name;     // 不用public修饰就这样写就可以了

    public Student() {
   
    }

    public Student(String name, int age) {
   
        this.name = name;
        this.age = age;
    }

    public String getName() {
   
        return name;
    }

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

    public int getAge() {
   
        return age;
    }

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

    public static String getTeacher_name() {
   
        return teacher_name;
    }

    public static void setTeacher_name(String teacher_name) {
   
        Student.teacher_name = teacher_name;
    }

    public void show(){
   
        System.out.println("name: " + name + ", age: " + age + ", teacher_name: " + teacher_name);
    }
}

package cn.hjblogs.demo;

public class Test {
   
    public static void main(String[] args) {
   
        Student stu1 = new Student("张三", 18);
        Student stu2 = new Student("李四", 20);

        stu1.show();                   // name: 张三, age: 18, teacher_name: null
        stu2.show();                   // name: 李四, age: 20, teacher_name: null

        stu1.setTeacher_name("张老师");
        stu1.show();                   // name: 张三, age: 18, teacher_name: 张老师
        stu2.show();                   // name: 李四, age: 20, teacher_name: 张老师

//        Student.setTeacher_name("张老师");
//        stu1.show();                   // name: 张三, age: 18, teacher_name: 张老师
//        stu2.show();                   // name: 李四, age: 20, teacher_name: 张老师
    }
}

可以看到修改一个学生对象的老师其余学生对象的老师也变了。或者直接修改这个类的老师

(2)静态变量内存图

在这里插入图片描述

  • 静态变量是随着内的加载而加载的,优于对象出现在内存里面的。
  • 其实所有内存图都一样都是地址套地址,这里所有对象都拿着一个静态区里面静态变量的地址去找静态变量,这样静态变量还是个字符串,那又要拿着地址去串词里面找字符串(因为之前讲过字符串是存在串词里面的)所以这里面是一个地址套一个地址(一种比喻,反正肯定存在一个映射关系)

2 静态方法

(1)基本定义和用法

  • 定义:被static修饰的成员方法,叫做静态方法。
  • 特点:
    (1)多用在测试类和工具类中
    (2)javabean类中很少会用
  • 调用方式:
    (1)直接类名调用(推荐)
    (2)对象名调用

(2)工具类

工具类
目前我们已经学习了java中的三种类:

  • javabean类:用来描述一类事物的类,比如:Student、Dog、老婆
  • 测试类:用来检查其他类是否书写正确,带有main方法的类,是程序的入口
  • 工具类:不是用来描述一类事物的,而是帮我们做一些事情的类(里面,java.lang里面的Math类就是这样一个工具类,帮我们封装了一些数学函数)

书写工具类有一些注意事项

  • 类名见名知义
  • 构造方法私有化要,因为工具类创建对象没有任何意义,那么我们就将构造方法私有化直接不让创建对象就可以了
  • 方法定义为静态

下面的这个练习详细写了一个工具类的构造

练习:按下面需求写一个工具类

在这里插入图片描述

package cn.hjblogs.demo;

public class ArrayUtil {
   

    private ArrayUtil() {
   
    }

    public static String printArray(int[] arr) {
   
        StringBuilder sb = new StringBuilder("[");
        for (int i = 0; i < arr.length; i++) {
   
            sb.append(arr[i]);
            if (i != arr.length - 1) {
   
                sb.append(", ");
            }
        }
        sb.append("]");
        return sb.toString();
    }

    public static double getAverage(double[] arr) {
   
        double sum = 0;
        for (double v : arr) {
   
            sum += v;
        }
        return sum / arr.length;
    }
}


package cn.hjblogs.demo;

public class TestDemo {
   
    public static void main(String[] args) {
   
        int[] arr = {
   1, 2, 3, 4, 5};
        System.out.println(ArrayUtil.printArray(arr));             // [1, 2, 3, 4, 5]

        double[] arr2 = {
   1.1, 2.2, 3.3, 4.4, 5.5};
        System.out.println(ArrayUtil.getAverage(arr2));     // 3.3
    }
}

3 static注意事项

关键:静态只能访问静态,不能访问动态;动态可以访问所有

  • 静态方法只能访问静态变量和静态方法(静态方法中,只能访问静态)
    因为静态变量和方法是随着类的加载而加载的,优于对象出现在内存里面的。我们一般都会限定不能实例化,那么除了静态变量和方法其他的都没实例化,都没有进入内存,访问个屁。
  • 静态方法中是没有this关键字的
    前面讲this内存时将过this指向实例化的那个对象,但现在静态方法压根不需要实例化对象,this自然也就没有
  • 非静态方法可以访问所有

一句话:静态的东西随着类的加载而加载。当类被编译成字节码文件里面方法放进方法区,所有静态的就全部加载到内存(静态区里面去了),这里方法都没进栈呢,对象压根还没到创建的时候,静态的就早早进内存了,但非静态的东西是和对象有关的,对象都没创建进内存,访问个屁。
参考视频
在这里插入图片描述


4 重新认识main方法

在这里插入图片描述

  • public :被JVM调用,访问权限足够大
  • static:被JVM调用,静态,不用创建对象,直接类名访问
    因为main方法是静态的,所以测试类中其他方法也需要是静态的(因为静态只能访问静态,不能访问动态)
  • void:被JVM调用,不需要JVM返回值
  • main:一个通用的名称,虽然不是关键字,但是被JVM识别
  • String[] args:以前用于接收键盘录入数据的,现在没用; String[] 数组 args 变量名



二、继承

1 继承的概念

继续面向对象三大特性之一继承的学习
面向对象三大特性:封装、继承、多态

那么继承怎么理解呢?
在这里插入图片描述
继承:

  • java中提供一个关键字extends,用这个关键字,我们可以让一个类和另一个类建立起继承关系
  • 语法:public class Student extends Persons { }
  • Student称为子类(派生类),Persons称为父类(基类或超类)

使用继承的好处:

  • 可以把多个子类中重复的代码抽取到父类中了,提高了代码的复用性。
  • 子类可以在父类的基础上,增加其他功能,使子类更强大。

学好继承主要有两个点:

  • 自己设计
  • 能会用别人的

什么时候考虑用继承呢?
在这里插入图片描述

2 继承的特点

  • java只支持单继承,不支持多继承,但支持多层继承(A继承B,B继承C这种)。
  • 单继承:一个子类只能继承一个父类
  • 不支持多继承:子类不能同时继承多个父类
  • 支持多层继承(A继承B,B继承C这种):但这种关系有一个头的,java的设计中java的每一个类都间接或者直接的继承与Object这个总类。
  • 子类继承父类的所有属性和方法,你实例化子类但父类不一定实例化父类,所以你set改变了子类中的东西,父类是没有任何变化的,因为父类都没有实例化。所以如果你对继承有疑惑,修改子类对象东西会影响到父类吗?答案是你修改子类,父类都不一定实例化了,根本没有任何影响。但是反过来父类是影响子类的,
    (1)修改子类不会影响父类
    (3)父类发生变化会影响子类
    (3)子类继承父类的几乎所有属性和方法(具体能继承哪些,后面有一节会详细说明,另外还一般是非私有的才能继承),可以直接 子类.属性或者子类.父类方法调用

    比如 public class Student { } 如果我们没有写继承,但java内部这个类默认是会直接继承Object这个类的。

【注】:java不支持多继承,但是Python和C++支持多继承。

下面来一个练习,看是不是真的理解了
在这里插入图片描述
先看一下结构
在这里插入图片描述

package cn.hjblogs.Animal;

public class Animal {
   
    public Animal() {
   
    }
    public void eat(){
   
        System.out.println("吃饭");
    }
    public void drink(){
   
        System.out.println("喝水");
    }
}

package cn.hjblogs.Animal;
public class Cat extends Animal {
   

    public Cat() {
   
    }
    public void catchMouse() {
   
        System.out.println("抓老鼠");
    }
}

package cn.hjblogs.Animal;
public class Dog extends Animal {
   
    public Dog() {
   
    }
    public void seeDoor() {
   
        System.out.println("看家");
    }
}

package cn.hjblogs.Animal;
public class DragenLiCat extends Cat {
   
    public DragenLiCat() {
   
    }
}

package cn.hjblogs.Animal;
public class RagdollCat extends Cat{
   
    public RagdollCat() {
   
    }
}

package cn.hjblogs.Animal;
public class HuskyDog {
   
    public HuskyDog() {
   
    }
    public void downHome() {
   
        System.out.println("拆家");
    }
}

package cn.hjblogs.Animal;
public class TeddyDog extends Dog {
   
    public TeddyDog() {
   
    }
    public void wipe(){
   
        System.out.println("蹭一蹭");
    }
}

package cn.hjblogs.Animal;

public class Test {
   
    public static void main(String[] args) {
   
        // 创建对象
        RagdollCat a = new RagdollCat();
        // 调用方法
        a.eat();         // 吃饭
        a.drink();       // 喝水 
        a.catchMouse();   // 抓老鼠
    }
}

3 继承到底能继承父类中的哪些内容?

先说结论吧,能继承的东西无非就是 构造方法、成员变量、成员方法中考虑就是了

内容 非私有 私有
构造方法 不能 不能
成员变量
成员方法(只有虚方法才能被继承) 不能
内容 虚方法(非private、static、final修饰的方法) 非虚方法
成员方法(只有虚方法才能被继承) 不能

其实在不同语言中基本上私有的都继承不了
1、java这里面有个特例,但没关系,私有就算能继承但由于是私有继承了子类也访问不了,这个继承了和没继承效果是一样的。(对于继承下来的私有变量非要用就只能通过get和set方法进行拿和修改了,前提是父类中的get和set不是私有,要是也是私有那完了,彻底访问不了了)
2、所以我们记住,私有的不能被继承(不是很严谨,但是够用)

这三个里面比较复杂的就是构造方法的问题了,这里面涉及到了方法的重写和super调用。具体的语法详情我们在(5)继承中的语法中会详细演示



4 继承中的内存图

参考视频

成员变量
在这里插入图片描述
和之前主要有两个不同之处

  • public class Zi extends Fu{}: 加载字节码文件时同时会将父类的字节码文件一起存到方法区中
  • new 创建对象在堆中一个地址分成了两块,一块存父的所有属性和方法,另一块存子独有的

在这里插入图片描述

成员方法
开始这个之前先要了解一下java中的虚方法表,java在继承中调用方法并不是一级一级不断往上找,因为这样如果继承链过长,会导致效率过慢。java的设计解决方法是从最上层每个类将自己的虚方法(能被继承方法)放进自己的虚方法表,子类继承这个表再在里面添加自己的虚方法,这样每个子类调用方法去自己的虚方法表里面找效率一下就提高了,不是虚方法表再回自己独有的去找,找不到就报错。
在这里插入图片描述

在这里插入图片描述

5 继承中的语法特点(主要讲述继承中的语法)

(1)继承中:成员变量的访问调用

成员变量的访问特点很简单就一句话:就近原则,先在本类中就近原则(先在方法内部找,方法内部找不到去类里面成员变量区找),本类找不到(成员变量区都找不到)再去父类中找。
下面这个例子就是典型的就近原则,但是现实中写代码不可能这么写啊(妥妥的超级危险代码),这里演示就近原则才举一个这么极端的例子:
在这里插入图片描述
小练习:

package cn.hjblogs.Business;

public class Test {
   
    public static void main(String[] args) {
   
        // 创建对象
        Zi z = new Zi();
    }
}

class Fu{
   
    String name = "Fu";
    String hobby = "喝茶";
}

class Zi extends Fu{
   
    String name = "Zi";              // 子类继承属性,属性是可以重写的属性
    String game = "吃鸡";

    public Zi(){
   
        //如何打印Zi
        System.out.println(name);          // Zi(就近原则)
        System.out.println(this.name);     // Zi
        //如何打印Fu
        System.out.println(super.name);    // Fu
        //如何打印喝茶
        System.out.println(hobby);         // 喝茶(就近原则)
        System.out.println(this.hobby);    // 喝茶(继承父类属性,并且没有重写)
        System.out.println(super.hobby);   // 喝茶
        //如何打印吃鸡
        System.out.println(game);          // 吃鸡(就近原则
        System.out.println(this.game);     // 吃鸡
    }
}

可以看到就近原则的存在,使得用法很灵活,但就近原则这个东西虽然java给你做了一层就近原则的防护,但这是危险代码的事实不会改变,因此我们平时应该养成习惯,就近原则尽量不要使用,用this、和super不香吗,没必要折腾给代码增加风险。

总结一下就近原则:

  • (1)直接变量名访问,先在调用的方法的局部找,找不到再到类的成员变量区找,还找不到就只能到父类里面去找了
  • (2)使用this访问,直接到类的成员变量区去找(并且如果类继承了父类,直接this也能找到对应属性;理解成this的成员变量区有部分属性是隐藏的,带上this还是能够访问到)。
  • (3)使用super访问:直接到父类去找了。

(2)继承中:成员方法的访问调用和子类对父类方法的重写

成员方法的调用还是一样的规则,使用就近原则。(所以子类对父类方法的重写就是典型的调用案例)
结合下面的直接调用,完全重写和部分重新的代码,一下子就清晰了

package cn.hjblogs.Business;

public class Test {
   
    public static void main(String[] args) {
   
        // 创建对象
        ChinesePerson a = new ChinesePerson();
        // 调用方法
        a.lunch();         // 吃饭 喝水 吃饭 喝水
        System.out.println("=====================================");

        ForeignPerson b = new ForeignPerson();
        b.lunch();         // 外国人吃牛排 喝水 外国人吃牛排 喝水 吃饭 喝水


    }
}

class Person {
   

    public void eat() {
   
        System.out.println("吃饭");
    }

    public void drink() {
   
        System.out.println("喝水");
    }
}


// 演示继承中方法调用的优先级 this和super
class ChinesePerson extends Person {
   
    public void lunch(){
   
        // this这里方法调用同样是就近原则,先调用子类的方法,如果子类没有再调用父类的方法
        this.eat();
        this.drink();

        // 直接调用父类的方法
        super.eat();
        super.drink();
    }
}

// 演示继承中方法的重写的两种方式 1.完全重写 2.部分重写(在父类的部分上进行修改  一般结合super)

class ForeignPerson extends Person {
   

    // 完全重写
    public void eat(String a) {
   
        System.out.println(a);   // 外国人吃牛排
    }
    // 部分重写(在父类的部分上进行修改  一般结合super)
    @Override
    public void drink() {
   
        super.drink();  // 调用父类的方法
        System.out.println("外国人喝红酒");
    }


    public void lunch(){
   
        // this这里方法调用同样是就近原则,先调用子类的方法,如果子类没有再调用父类的方法
        String food = "外国人吃牛排";
        this.eat(food);         // 外国人吃牛排
        this.drink();       // 喝水 外国人喝红酒

        // 直接调用父类的方法
        super.eat();         // 吃饭
        super.drink();       // 喝水
    }
}

【注】:关于方法继承重写,添加新的方法参数没有任何关系,如果要用super调用父类中的方法,该有的参数就都要传全才行

另外关于方法重写在java中还有一些语法规范:

  • 当父类的方法不能满足子类现在的需求时,需要进行方法重写
  • 书写格式:函数名一样即可(严格来说就是子类出现了和父类中一模一样的方法声明,我们就称子类这个方法是重写的方法)。java中重写方法的名称、形参列表必须与父类中的一致。(不一致就不是方法的重写了,叫做创建了一个新方法)。这点来看Python要比java灵活多了。
  • @Override重写注释(不加也没任何关系)
    (1)@Override是放在重写后的方法上,校检子类重写时语法是否正确。
    (2)加上注解后如果有红色波浪线,表示语法错误。
    (3)建议重写方法都加@Override注解,代码安全,优雅!但是我个人觉得不加这个注解会更加灵活好用,下面是解释,反正我以后没有特殊规定是不会写的。

另外,在java中@Override认为如果方法重写过程中参数变多,或者参数顺序变量,那么@Override认为这就不是方法的重写,这是一个新的属于子类的方法。(结合前面的方法重载来解释,好像java中的确是将参数不同,顺序不同的同名函数不是看成一个函数,而是多个函数),这是一个小细节。
Python中就没这些规定了,语言不同,一些细微差别还是有的,所以建议@Override注解如果没有强制要求,可以不写,加了@Override注解反而丧失了灵活性,代码给自己看的,何必管别人。
我喜欢将java中的这种方法重写叫做狭义的方法重写,Python中的那种更加灵活叫做广义的方法重写。java中想实现广义的方法重写也简单,不要加@Override注解就行了,参数顺序随便变,随便加参数,新方法就新方法呗,够灵活就可以了。

  • 子类重写父类方法时,访问权限子类必须大于等于父类(空着不写<protected<public),通常都是public,public,所有权限都给你
  • 子类重写父类方法时,返回值类型必须小于等于父类(不重要,针对狗类里面有方法返回动物类这种离谱现象的操作,子类里面有个方法的返回值是父类,很扯)
  • 只有被添加到虚方法表(非private、static、final修饰的方法)中的方法才可以被重写。

最后,不要将眼光限定在java一种规则上,如果其他语言的规则更好用,我们为什么不直接使用那种规则,更加灵活当然更好啊。


(3)构造方法在继承调用语法(采用继承中的方法重写手段)

基本用法
  • java中构造方法不能继承很显然,毕竟子类和父类构造方法不可能重名,这种涉及天然导致java中的构造方法不能被继承。因此在子类中只能进行构造方法的重写。
  • Python中的构造方法_init_如果在子类中没有被显式写出,那么Python会默认继承父类的_init_方法;但是我们写Python类怎么可能不写_init_方法,因此,还是要进行_init_方法的重写

在子类中进行构造方法的重写,有分先后顺序必须要干的三件事情(严格按照顺序)

  • 第一件事情就是父类中构造方法传入的参数,子类的构造方法也必须要传入
  • 第二件事情:用super调用父类的构造方法把继承来的父类属性初始化了
  • 第三件事情:初始化子类其他的属性或者其他操作

看下面代码示例就很好理解了,java和Python版都有:

【注】:java可以一个文件里面写多个类(但是只有一个类能被public修饰),这里为了好讲述才这样写一下,实际java开发中还是要一个类一个文件夹. 算了还是分多个文件class避免误解吧!

java版

public class Parent {
   
    int value;

    Parent(int value) {
   
        this.value = value;
    }
}
public class Child extends Parent {
   
    int childValue;

    Child(int value, int childValue) {
   
        
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值