面向对象

面向对象

类和对象理解

物以类聚 人以群分

类:就是物以类聚的类 人以群分的群

对象:就是物以类聚的物 人以群分的人

类和对象的内存分配机制

内存结构分析

栈:存放基本数据类型(局部变量)

堆:存放对象,数组

方法区:常量池,类加载信息

作用域

  1. 全局变量和局部变量可以重名,访问时遵循就近原则
  2. 在同一个作用城中,比如在同一个成员方法中,两个局部变量,不能重名
  3. 属性生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。局部变量,生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而销毁。
  4. 全局变量/属性:可以被本类使用,或其他类使用(通过对象调用)
    局部变量:只能在本类中对应的方法中使用
  5. 全局变量/属性可以加修饰符
    局部变量不可以加修饰符

构造方法

构造方法又叫构造器(constructor),是类的一种特殊的方法,它的主要作用是完成对新对象的初始化。

  1. 方法名和类名相同
  2. 没有返回值
  3. 在创建对象时,系统会自动的调用该类的构造器完成对象的初始化。
  4. 构造器是完成对象的初始化,并不是创建对象
  5. 在创建对象时系统自动的调用该类的构造方法

THIS关键字

  1. 代表当前对象,谁调用就指向谁
  2. this 关键字可以用来访问本类的属性、方法、构造器
  3. this 用于区分当前类的属性和局部变量
  4. 访问成员方法的语法:this.方法名(参数列表);
  5. 访问构造器语法:this(参数列表); 注意只能在构造器中使用(即只能在构造器中访问另外一个构造器, 必须放在第一
    条语句)
  6. this 不能在类定义的外部使用,只能在类定义的方法中使用

SUPER关键字

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

  1. 访问父类的属性,但不能访问父类的 private属性 案例 super.属性名
  2. 访问父类的方法,不能访问父类的 private方法super方法名(参数列表)
  3. 访问父类的构造器,super(参数列表)只能放在构造器的第一句,只能出现一句!
  4. 调用父类的构造器的好处(分工明确,父类属性由父类初始化,子类的属性由子类初始化
  5. 当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过 super。如果没有重名,使用 super、this、直接访问是一样的效果!
  6. super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类的成员;如果多个基类(上级类)中都有同名的成员,使用 super访问遵循就近原则。A->B->C,当然也需要遵守访问权限的相关规则

访问修饰符

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

方法重载

  1. 方法名:必须相同
  2. 形参列表:必须不同(形参类型或个数或顺序),至少有一样不同,参数名无要求
  3. 返回类型:无要求

方法重写

两同两小一大

  1. 子类的方法的形参列表方法名称要和父类方法的形参列表方法名称完全一样
  2. 子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类(需要有继承关系)
  3. 子类方法的访问权限应比父类方法的访问权限更大或相等

封装:

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

JavaBean规范就是封装的体现。

1、类必须使用public修饰。
2、必须保证有公共无参数构造器。
3、包含了属性的操作手段(给属性赋值,获取属性值)。

继承

子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访
问,要通过父类提供公共的方法去访问

子类必须调用父类的构造器, 完成父类的初始化

当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过。

如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)

super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)

super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器

父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)

子类最多只能继承一个父类(指直接继承),即 JAVA中是单继承机制。

不能滥用继承,子类和父类之间必须满足物以类聚,人以群分的逻辑关系

结合继承细节,THIS,SUPER,访问修饰符,构造方法来个案例

定义子类

public class Son {}

定义父类

public class Parent {}

让子类继承父类

public class Son extends Parent {}

子类属性,构造方法及成员方法

public class Son extends Parent {
	//四种不同的修饰符
	private String name;
    public String id;
    protected String parent;
     String idCard;
    
    public Son(String name, String id, String parent, String idCard) {
		System.out.println("您好,你现在进入的是子类的有参构造方法!!!");
    }
    
    public String sonMethod(){
      
    }

    public Son() {
        System.out.println("您好,你现在进入的是子类的无参构造方法!!!");
    }
}

父类属性,构造方法

public class Parent {

    private String id;
    public String name;
    protected String age;
    String hobby;
    
    public Parent() {
        System.out.println("您好,你现在进入的是父类的无参构造方法!!!");
        this.id = "100";
        this.name = "别人家的孩子";
        this.age = "和你一样的岁数";
        this.hobby = "人家就爱学习";
    }
   

主方法

public static void main(String[] args) {
    //初始化 
      //------以下为输出结果------ 
    //您好,你现在进入的是父类的无参构造方法!!!
    //您好,你现在进入的是子类的无参构造方法!!!
    Son son =new Son(); 
    //根据输出结果得知当有子类继承父类的时候 子类要先调用父类的构造器完成父类的初始化 这里注意如果父类没有无参构造方法,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作
    
}
public static void main(String[] args) {
    Son son =new Son();
    //通过子类的sonMethod成员方法访问父类的属性
    //------以下为输出结果------
    //您好,你现在进入的是父类的无参构造方法!!!
    //您好,你现在进入的是子类的无参构造方法!!!
    //父类name属性=别人家的孩子
    //子类name属性=自己家的孩子
    //子类idCard属性=null
    //子类id属性=null
    //局部变量的name属性=隔壁邻居家的二狗子
    //子类继承父类访问父类属性id:100, name:别人家的孩子, age:和你一样的岁数, hobby:人家就爱学习.
    String s = son.sonMethod();
    System.out.println(s);
}

子类

public class Son extends Parent {
    private String name;
    public String id;
    protected String parent;
     String idCard;

    public Son(String name, String id, String parent, String idCard) {
            System.out.println("您好,你现在进入的是子类的有参构造方法!!!");
    }
    public String sonMethod(){
        //父类子类都有name属性 同名  父类属性是public String name 同包同类不同包不同类都可以访问  但是要访问父类需要加上super.属性
        System.out.println("父类name属性"+super.name);

        //子类属性是private String name 同类也可以访问
        System.out.println("子类name属性="+name);
        System.out.println("子类idCard属性="+idCard);
        System.out.println("子类id属性="+id);

        //此处又有一个局部变量 根据就近原则(局部变量和成员变量名字相同时,优先选择最近的变量) 所以此时name是隔壁家的二狗子
        String name ="隔壁邻居家的二狗子";
        System.out.println("局部变量的name属性"+name);
        //-------访问父类的属性------  也可以访问父类的方法,以属性举例

        //因为子类继承了父类,所有子类可以所有的属性和方法,非私有的属性和方法可以在子类直接访问,
        // 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
        // 又但是因为父类和子类有相同的属性是name和id 所以需要通过super.属性访问父类变量 子类的私有属性要通过父类提供的公共方法去访问
        return "子类继承父类访问父类属性id:"+getId()+", name:"+super.name+", age:"+age+", hobby:"+hobby+"." ;
    }

    public Son() {
        System.out.println("您好,你现在进入的是子类的无参构造方法!!!");
        this.name="自己家的孩子";
    }
}

父类

public class Parent {

    private String id;
    public String name;
    protected String age;
    String hobby;

    /**
     * 成员变量默认是不用赋值的(因为会破坏JavaBean的封装性)
     * 推荐使用建议在构造函数或提供setters方法对变量赋值
     * Spring中也是这样推荐
     */
    public Parent() {
        System.out.println("您好,你现在进入的是父类的无参构造方法!!!");
        this.id = "100";
        this.name = "别人家的孩子";
        this.age = "和你一样的岁数";
        this.hobby = "人家就爱学习";
    }

    /**
     * 给私有属性提供对外的公共方法
     * @return id
     */
    public String getId() {
        return id;
    }

}

为了避免多次创建对象,this(),super()必须放在构造放在第一行

public Son() {
    System.out.println("听说super必须放在构造器第一行???");
    super();
    //编译器给出的警告
    // 'super()' 调用必须是构造函数主体中的第一条语句
}

public Son() {
    System.out.println("听说this必须放在构造器第一行???");
    this();
    //编译器给出的警告
    // 'this()' 调用必须是构造函数主体中的第一条语句
}

当子类创建对象好后,建立查找的关系

/**
* 讲解继承的本质
*/
public class ExtendsTheory {
public static void main(String[] args) {
Son son = new Son();//内存的布局
//?-> 这时请大家注意,要按照查找关系来返回信息
//(1) 首先看子类是否有该属性
//(2) 如果子类有这个属性,并且可以访问,则返回信息
//(3) 如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息..)
//(4) 如果父类没有就按照(3)的规则,继续找上级父类,直到 Object...
System.out.println(son.name);//返回就是大头儿子
//System.out.println(son.age);//返回的就是 39
//System.out.println(son.getAge());//返回的就是 39
System.out.println(son.hobby);//返回的就是旅游
	}
}
 //爷类
class GrandPa {
String name = "大头爷爷";
String hobby = "旅游";}
//父类
class Father extends GrandPa {
String name = "大头爸爸";
private int age = 39;
public int getAge() {
return age;
   	}
}
//子类
class Son extends Father { 
String name = "大头儿子";
}

多态

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

一个对象的编译类型和运行类型可以不一致
编译类型在定义对象时,就确定了,不能改变
运行类型是可以变化的
编译类型看定义时=号的左边,运行类型看=号的右边

多态的前提是:两个对象(类)存在继承关系

多态的向上转型:

  1. 本质:父类的引用指向了子类的对象
  2. 语法:父类类型 引用名=new 子类类型();
  3. 特点:编译类型看左边,运行类型看右边
  4. 可以调用父类中的所有成员(需遵守访问权限)
  5. 不能调用子类中特有成员
  6. 最终运行效果看子类的具体实现

多态向下转型:

  1. 语法:子类类型引用名=(子类类型)父类引用
  2. 只能强转父类的引用,不能强转父类的对象
  3. 要求父类的引用必须指向的是当前目标类型的对象
  4. 当向下转型后,可以调用子类类型中所有的成员

动态绑定

当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定

当调用对象属性时,没有动态绑定机制,哪里声明,那里使用

多态应用

现有一个继承结构如下:要求创建 1 个 Person 对象、2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中,并调用每个对象say 方法.
应用实例升级调用子类特有的方法,比如Teacher 有一个 teach , Student 有一个 study怎么调用?

父类

public class Person {//父类
    private String name;
    private int age;

    public Person(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 String say() {//返回名字和年龄
        return name + "\t" + age;
    }
}

子类

public class Student extends Person {
    private double score;

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

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }
    //重写父类say

    @Override
    public String say() {
        return "学生 " + super.say() + " score=" + score;
    }
    //特有的方法
    public void study() {
        System.out.println("学生 " + getName() + " 正在学java...");
    }
}

子类

public class Teacher extends Person {
    private double salary;

    public Teacher(String name, int age, double salary) {
        super(name, age);
        this.salary = salary;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
    //写重写父类的say方法

    @Override
    public String say() {
        return "老师 " + super.say() + " salary=" + salary;
    }
    //特有方法
    public void teach() {
        System.out.println("老师 " + getName() + " 正在讲java课程...");
    }
}

main方法

public static void main(String[] args) {

        Person[] persons = new Person[5];
        persons[0] = new Person("一爽", 208);
        persons[1] = new Student("无E烦", 32, 888);
        persons[2] = new Student("阿祖", 40, 8888);
        persons[3] = new Teacher("段坤", 30, 108);
        persons[4] = new Teacher("校长牌望远镜", 34, 5);

        for (int i = 0; i < persons.length; i++) {
            //动态绑定机制
            System.out.println(persons[i].say());
            if (persons[i] instanceof Student) {
                //向下
                Student student = (Student) persons[i];
                student.study();
            } else if (persons[i] instanceof Teacher) {
                //向下
                Teacher teacher = (Teacher) persons[i];
                teacher.teach();
            } else {
                System.out.println("啥也不是");
            }
        }
    }

类变量

  1. 需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量)
  2. 类变量与实例变量(普通属性)区别类变量是该类的所有对象共享的,而实例变量是每个对象独享的
  3. 加上 static称为类变量或静态变量,否则称为实例变量/普通变量/静态变量
  4. 类变量可以通过类名类变量名或者对象名类变量名来访问,但推荐我们使用类名类变量名方式访问。【前提是满足访问修饰符的访问权限和范围】
  5. 实例变量不能通过类名.类变量名方式访问类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了
  6. 类变量的生命周期是随类的加载开始,随着类消亡而销毁。

代码块

代码化块又称为初始化块属于类中的成员[即是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过包围起来
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。

  1. static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建对象,就执行一次
  2. 类什么时候被加载
    ①刨建对象实例时(new)
    ②刨建子类对象实例,父类也会被加载
    ③使用类的静态成员时静态属性,静态方法
  3. 普通的代码块,在创建对象实例时,会被隐式的调用。被创建一次,就会调用一次,如果只是使用类的静态成员时,普通代码块并不会执行
  4. static代码块是类加载时,执行,只会执行一次普通代码块是在刨建对象时调用的,创建一次,调用一次

创建一个对象时,在一个类调用顺序是:

①调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
②调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
③调用构造方法。

final 关键字

  1. final修饰的属性又叫常量-般用XX_XX来命名

  2. final修饰的属性在定义时必须赋初值并且以后不能再修改在以下位置赋初始值

    ①定义时:如 public final double TAX RATE=0.08
    ②在构造器中
    ③在代码块中

  3. 如果final修饰的属性是静态的,则初始化的位置只能是定义时就初始化或者在静态代码块,不能在构造器中赋值

  4. final类不能继承,但是可以实例化对象

  5. 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承

抽象类

  1. 抽象类不能被实例化
  2. 抽象类不一定要包含 abstract方法。也就是说抽象类可以没有 abstract方法
  3. 一旦类包含了 abstract方法则这个类必须声明为 abstract[说明
  4. abstract只能修饰类和方法,不能修饰属性和其它的
  5. 抽象类可以有任意成员【抽象类本质还是类】,比如:非抽象方法构造器、静态属性等等
  6. 抽象方法不能有主体,即不能实现
  7. 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法

接口

  1. 接口不能被实例化
  2. 接口中所有的方法是 public 方法, 接口中抽象方法,可以不用 abstract 修饰
  3. 一个普通类实现接口,就必须将该接口的所有方法都实现,可以使用 alt+enter 来解决
  4. 抽象类去实现接口时,可以不实现接口的抽象方法

内部类

如果定义类在局部位置(方法中/代码块) :(1) 局部内部类 (2) 匿名内部类

定义在成员位置 (1) 成员内部类 (2) 静态内部类

一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类( inner class),嵌套其他类的类称为外部类( outer class)是我们类的第五大成员 [属性、方法、构造器、代码块、内部类],内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系

局部内部类

  1. 局部内部类是定义在外部类的局部位置,通常在方法
  2. 可以直接访问外部类的所有成员,包含私有的
  3. 不能添加访问修饰符,但是可以使用final 修饰
  4. 作用域 : 仅仅在定义它的方法或代码块中
  5. 局部内部类可以直接访问外部类的成员,比如下面 外部类属性和方法

匿名内部类

匿名内部类的基本语法 new类或接口(参数列表){ 类体 }

  1. 可以直接访问外部类的所有成员,包含私有的
  2. 不能添加访问修饰符因为它的地位就是一个局部变量
  3. 作用域:仅仅在定义它的方法或代码块中
  4. 外部其他类-不能访问-->匿名内部类(因为匿名内部类地位是一个局部变量)
  5. 匿名内部类--访问->外部类成员[访问方式:直接访问]
  6. 如果外部类和匿名内部类的成员重名时,匿名内部类访问默认遵循就近原则

成员内部类

  1. 可以直接访问外部类的所有成员,包含私有的
  2. 如果成员内部类的成员和外部类的成员重名,会遵守就近原则
  3. 可以通过 外部类名.this.属性 来访问外部类的成员
  4. 外部其他类,使用成员内部类的二种方式 外部类对象.new 成员内部类 在外部类写一个方法返回成员内部类实例

静态内部类

不能直接访问非静态成员

内部类练习

class Outer {

    private  int num = 888;

    private void outerMethodOne() {
        System.out.println("外部类的私有方法");
    }

    public void outerMethodTwo() {
        System.out.println("outerMethodTwo是相对于局部内部类来说是外部类的方法");
        /**
         * 局部内部类
         * 可以用final修饰
         */
        final class Inner {
            private int num = 666;

            public void innerMethodOne() {
                //直接访问外部类的方法
                outerMethodOne();
                //就近原则,访问外部类的属性需要类型.this.属性名
                System.out.println("num=" + num + " 外部类的num=" + Outer.this.num);
                System.out.println("Outer.this hashcode=" + Outer.this);
            }
        }

        /**
         * 匿名内部类简化了实现接口然后创建对象的步骤
         * 后续可以用 Lambda表达式继续简化
         */
      //jdk底层在创建匿名内部类 Outer$1,立即马上就创建了 Outer$1实例,并且把地址返回给Girl
      //  class Outer$1 implements Girl {
      //      @Override
      //      public void cry() {
      //          System.out.println("老虎叫唤...");
      //      }
      //  }
        Girl girl= new Girl() {
            @Override
            public void girlAge(int age) {
                System.out.println("这个女孩今年"+age+"岁");
            }
        };
        girl.girlAge(17);

        // 使用Lambda表示式简化
        //  Runnable runnable = () -> System.out.println("匿名内部类访问外部类属性="+Outer.this.num);
            Runnable runnable =new Runnable() {
                private int num=521;
                @Override
                public void run() {
                    System.out.println("当匿名内部类属性和外部类属性名字相同的时候,根据就近原则"+num);
                }
            };
            runnable.run();

        Inner inner = new Inner();
        inner.innerMethodOne();
    }

}

interface  Girl {
    /**
     * 女孩的年龄
     * @param age
     */
    public abstract void girlAge(int age);
}
posted @ 2021-09-16 16:27  布卷-  阅读(41)  评论(0)    收藏  举报