Java基础第七天-面向对象编程(中级)

从这个阶段开始安装IDEA使用

在idea中,当我们run一个文件时,会先编译成.class之后再运行,src文件底下存放的是源码文件,运行过后的out文件夹下存放的是编译后的.class文件。

  • IDEA常用快捷键,通过设置-按键映射-自行搜索一些常用的完成功能的快捷键

  •  alt + insert 自动在类中生成构造器
  • CTRL+alt+L自动优化代码,即在没有打空格的地方不美观拥挤的地方给你打上空格调整
  • 查看一个类的层级关系 ctrl + h
  • 将光标放在一个方法上,输入ctrl + b 可以定位到方法
  • 通过在new 类名()后加.var后按回车可以自动分配变量名
  • 文件-设置-编辑器-实时模板可以查看系统分配的模板,也可以自己设置模板

包的三大作用:

  1. 区分相同名字的类
  2. 当类很多时,可以很好的管理类
  3. 控制访问范围

包的本质:

创建不同的文件夹/目录来保存类文件

包的命名:

  • 规则:只能包含数字、字母、下划线、小圆点,但不能用数字开头,不能是关键字或保留字
  • 规范:一般是小写字母+小圆点。例:com.公司名.项目名.业务模块名 :com.sina.crm.user

常用的包:

  • java.lang. *   lang包是基本包,默认引用,不需要再引入比如使用Math
  • java.util. *   util包,系统提供的工具包,工具类,比如使用Scanner
  • java.net. *  网络包,做网络开发
  • java.awt. *   做java的界面开发

比如import.java.util.Scanner这句话表示引入util包下的Scanner类;而import.java.util.*这句话表示把util包下的所有类都导入(建议还是需要使用到哪个类就导入哪个类,不建议使用*的方式导)

  1. package的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package。
  2. import指令位置放在package的下面,在类定义前面可以有多句且没有顺序要求。
    package com.use;
    import java.util.Arrays;
    public class test {
        public static void main(String[] args) {
            int[] arr = {11,4,6,8};
            Arrays.sort(arr);
            for (int i = 0; i < arr.length; i++) {
                System.out.println(arr[i]);
            }
            }
        }

访问修饰符

Java提供四种访问控制修饰符,用于控制方法和属性的访问权限:

  • 公开级别:public,对外公开
  • 受保护级别:protected,对子类和同一个包中的类公开
  • 默认级别:没有修饰符号,向同一个包中的类公开
  • 私有级别:private,只有类本身可以访问,不对外公开

封装

把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作[方法],才能对数据进行操作。

  1. 隐藏实现细节 
  2. 可以对数据进行验证,保证安全合理

写setXxx和getXxx太慢,可以用快捷键alt+insert选择get与set,在set中完成对一些属性的控制校验环节设置,增加业务逻辑。

将构造器结合setXxx使用,可以在构造器中直接写set方法。下面是一段有关封装使用的代码示例:

public class test {
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("jackrose");
        person.setAge(18);
        person.setSalary(5000);
        System.out.println(person.info());
        System.out.println(person.getSalary());
        Person person1 = new Person("jack", 18, 3000);
    }
}
class Person{
     public String name;
     private int age;
     private double salary;

    public Person() {
    }

    public Person(String name, int age, double salary) {
//        this.name = name;
//        this.age = age;
//        this.salary = salary;
        this.setName(name);//这里的this都可以省去
        this.setAge(age);
        this.setSalary(salary);
    }

    public void setName(String name) {
        if(name.length() >= 2 && name.length() <= 6) {
            this.name = name;
        }else{
            System.out.println("名字的长度要在2-6个字符之间");
            this.name = "无名人";
        }
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        //在set中加入验证以及一些控制设置
        if(age >= 1 && age <= 120){
        this.age = age;
        }else{
            System.out.println("你设置的年龄不对,需要在1-120,给默认年龄18");
            this.age = 18;
        }
    }
    public double getSalary() {
        //可以增加对当前对象的权限判断
        //比如增加密码验证之类的
        return salary;
    }
    public void setSalary(double salary) {
        this.salary = salary;
    }
    public String info() {
        return "信息为 name=" + name + ", age=" + age + ", salary=" + salary;
    }
}

继承

主要作用就是解决代码的复用性。当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends来声明继承父类即可。

//基本语法
class 子类 extends 父类{
}
  1. 子类就会自动拥有父类定义的属性和方法。
  2. 父类又叫超类,基类。
  3. 子类又叫派生类。

 继承细节问题:

  1. 子类继承了所有的属性和方法,但是私有属性和方法不能再子类直接访问,要通过公共的方法(在父类中定义)去访问。
  2. 子类必须调用父类的构造器,完成父类的初始化。
  3. 当构建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过。当希望指定去调用父类的某个构造器时,也要显式的调用一下super()。
  4. super在使用时,需要放在构造器第一行。
  5. this在使用时,也需要放在构造器第一行,所以this和super不能共存在一个构造器。
  6. java所有类都是Object的子类(输入ctrl+H可以看到类的继承关系)。
  7. 父类构造器的调用不限于直接父类(上一级父类),将一直往上追溯直到Object类(顶级父类)。
  8. 子类最多只能继承一个父类(直接继承),即java中是单继承制。那么如何让A类继承B类和C类:让A类继承B类之后,再让B类去继承C类,这样A类就可以在继承B的同时又继承C了。
  9. 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系。

 继承的本质分析:

执行上述代码,创建一个son子类时内存布局运行过程如图,细节多看:

看以下代码,思考会输出什么

package com.use.extend_.exercise;

import jdk.nashorn.internal.ir.CallNode;

public class Exercise01 {
    public static void main(String[] args) {
        B b = new B();
    }
}
class A{
    A(){
        System.out.println("a");
    }
    A(String name){
        System.out.println("a name");
    }
}
class B extends A{
    B(){
        this("abc");
        System.out.println("b");
    }
    B(String name){
        System.out.println("b name");
    }
}

输出的结果是:首先进去B类中的无参构造器,有this赋值即调用本类的B(String name)构造器,但是因为有隐藏的super,所以会去先调用父类的无参构造器,即A类中的无参构造器,所以先输出a,然后回到B类中(String name)构造器,执行到输出 b name ,然后退回到B类的无参构造器执行输出 b 语句。这里注意B类中的无参构造器没有super因为super和this不能共存,所以这里的super存在于B类的第二个构造器中。

super关键字

super代表父类的引用,用于访问父类的属性、方法、构造器。

  • 访问父类的属性,但不能访问父类的private属性。语法是super.属性
  • 访问父类的方法,但不能访问父类的private方法。语法是super.方法
  • 访问父类的构造器,只能放在构造器的第一句,只能出现一句。语法是super(),必须在构造器中使用
  • 使用super调用遵循就近原则,在多个基类中有同名的成员,如果在”近“的父类中找到了就直接调用,找不到再往上一级,并且要遵循访问权限问题,如果遇到private的方法或属性,不是选择跳过继续去上一级寻找而是会抛出异常。

例:如果在B类中调用cal方法,即在B类中写cal(),那么

super和this的比较

方法重写/覆盖(override)

简单地说就是子类有一个方法,和父类的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的方法(这里的子类和父类不一定只是一层的关系,也可以隔着好几层级)。

  1. 子类的方法的参数,方法名称,要和父类方法的参数,方法名称完全一样。
  2. 子类方法的返回类型和父类方法的返回类型一样,或者是父类返回类型的子类,比如父类的返回类型是Object,子类方法返回类型是String。如父类中的方法是public Object getInfo(){},子类中public String getInfo(){}同样也可以构成方法重写。
  3. 子类方法不能缩小父类方法的访问权限(访问权限:public > protected > 默认 > private)如父类中方法 void say(){},那么子类中使用public void say(){}也是可以构成方法重写的,但是如果把public换成private就不行了

方法重写和方法重载的比较

多态

方法或对象具有多种形态,多态是面向对象的第三大特征,多态是建立在封装和继承基础之上的。

方法重载体现多态,对同名的方法传入不同的参数就会调用不同的方法,体现多态

方法重写也体现多态。对同名的方法,不同的对象去调用会自动定位到不同的类中,体现多态

多态的具体体现

对象的多态

  • 一个对象的编译类型和运行类型可以不一致,例:Animal animal = new Dog(),animal的编译类型是Animal,运行类型是Dog
  • 编译类型在定义对象时就确定了,不能改变
  • 运行类型是可以变化的,例:animal = new Cat();此时animal的运行类型变成了Cat,但是它的编译类型仍是Animal
  • 编译类型看定义是,看等号的左边,运行类型看等号的右边

多态的注意事项和细节问题​​​​​​

  • 多态的前提是:两个对象(类)存在继承关系
  • 多态的向上转型:
  1. 本质是父类的引用指向了子类的对象。
  2. 语法是 父类类型  引用名 = new 子类类型();
  3. 特点是 编译类型看左边,运行类型看右边。
  4. 可以调用父类中的所有成员但是需要遵守访问权限,不能调用子类中特有成员,最终的运行效果看子类的具体实现。
  • 多态的向下转型:
  1. 语法:子类类型 引用名 = (子类类型) 父类引用;例: Cat cat = (Cat) animal;就把编译类型从Animal强转成了Cat。
  2. 只能强转父类的引用,不能强转父类的对象。
  3. 要求父类的引用必须指向的是当前目标类型的对象。例:我们上面举的例子,把编译类型从Animal强转成了Cat的前提是,我们原先的animal指向的就是堆里面的Cat对象(父类引用),即最开始那句Animal animal = new Cat();我们执行强转操作之后,得到了一个cat对象也是指向堆里的Cat对象,我们只能把当初指向猫的animal对象强转成猫,而不能强转成其他的类型,比如我们不能写Dog dog = (Dog) animal;会抛出异常
  4. 当向下转型后,就可以调用子类类型中所有的成员。

注意:属性没有重写一说,如果需要输出的是属性,那么依旧要看等号左边的编译类型,如果调用的是方法才需要看等号右边的运行类型,这里强调一下:

  1. 属性(成员变量)是类的状态信息,描述了对象的特征和属性
  2. 属性的类型、大小和布局在编译时就确定了,因此在编译时就可以确定属性的相关信息
  3. 方法(成员函数)是类的行为操作,定义了对象可以执行的操作
  4. 方法的实现代码是在运行时执行的,因此在编译时只能确定方法的声明,具体的方法实现由运行时决定
  • instanceof 比较操作符,用于判断对象的运行类型是否为XX类型或XX类型的子类型

Java的动态绑定机制

  1. 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
  2. 当调用对象属性时,没有动态绑定机制,哪里声明就在哪里使用

看上图代码,当我们在主方法中运行以下代码运行结果会是怎样?

A a = new B();
System.out.println(a.sum());
System.out.printlb(a.sum1());

我们先来分析这三行代码,第一句表示的是一个向上转型,对象a的编译类型是A,运行类型是B,那么输出a.sum()即方法调用时,会去运行类型中即B类中寻找有无方法sum,B类中有所以直接调用输出20+20=40,那么同理下一句的输出也是去B类中寻找的方法,调用结果是20+10=30。但是,如果我们把图片中B类中的sum与sum1方法都注释掉,即B类中不存在这两个方法了,那么输出结果又会如何呢?首先因为运行类型是B,所以出现方法调用依旧是先去B类中寻找,发现没有sum方法,去B类的父类中即A类中寻找,找到了sum方法,进入使用发现调用了方法getI,但是B类中也存在方法getI,那么调用的是哪个方法呢,这个就牵扯到了Java的动态绑定机制了,按照上面我们给出的第一条绑定机制:当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定,a的运行类型是B,调用a的方法时会和a的运行类型B绑定,所以即使是在A类中遇到了getI方法的调用,此时依旧要先去调用B类中的getI方法,B中getI调用结果是20,返回到A中的sum方法结果是20+10=30,同理下一句调用sum1方法,B中没有此方法去B的父类A类中寻找,找到了sum1,调用需要输出i+10,这里的i的取值怎么取?根据绑定机制第二条:当调用对象属性时,没有动态绑定机制,哪里声明就在哪里使用,我们需要知道i的值,直接就在A类中寻找即可,所以输出是10+10=20。

多态数组

 数组的定义类型为父类类型,里面保存的实际元素类型为子类类型。看课堂案例

多态参数

 方法定义的形参类型为父类类型,实参类型允许为子类类型。主要看课堂案例

Object类详解

  •  equals方法:

==和equals的对比(面试题):==是一个比较运算符

  1. ==既可以判断基本类型,又可以判断引用类型
  2. 如果判断的是基本类型,判断的是值是否相等
  3. 如果判断的是引用类型,那么判断的是地址是否相等,即判定的是不是堆里面的同一个对象

equals是Object类中的一种方法,只能用来判断引用类型,默认判断的是地址是否相同,但是在子类中会得到重写,比如在Integer以及String中就是判断“内容”是否相等,系统自动重写了equals方法。例:

  •  hashcode方法

两个引用,如果指向的是同一个对象,则哈希值肯定是一样的。哈希值主要根据地址号来的,不能完全将哈希值等价于地址

  •  toString方法

默认返回:全类名(包名+类名)+@+哈希值的十六进制,子类往往会重写toString方法,用于返回对象的属性信息,默认的alt+insert快捷键重写一般是把对象的属性值输出。当直接输出一个对象时,toString方法会被默认的调用

  • finalize方法

当对象被回收时(比如把一些对象置成null时),系统自动调用该对象的finalize方法。子类可以重写该方法,做一些释放资源的操作。我们在实际开发中,几乎不会运用finalize,更多的是为了应付面试

  •  断点调试

可以用断点调试一步一步的看源代码执行的过程,从而发现错误的所在。在断点调试过程中,是运行状态,是以对象的运行类型来执行的。

断点调试的快捷键:F7:跳入方法内;F8:逐行执行代码;shift+F8:跳出方法回到原来的位置;F9:resume,执行到下一断点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值