java-面向对象OOP

一、标识符

  1. Java对各种变量、 方法和类等要素命名时使用的字符序列称为标识符。凡是自己可以起名字的地方都叫标识符。
  2. 定义合法标识符规则:由 26 个英文字母大小写,0-9 ,_或 $ 组成。数字不可以开头。不可以使用关键字和保留字,但能包含关键字和保留字。
  3. Java中严格区分大小写,长度无限制。

二、 Java 中的名称命名规范:

  1. 包名:多单词组成时所有字母都小写:xxxyyyzzz。
  2. 类名、接口名:多单词组成时,所有单词的首字母大写(驼峰命名):XxxYyyZzz。
  3. 变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写:xxxYyyZzz。
  4. 常量名:所有字母都大写。多单词时每个单词用下划线连接:XXX_YYY_ZZZ。
  5. 注意:在起名字时,为了提高阅读性,要尽量有意义,“见名知意”。不要使用中文作为标识符

三、面向对象

面向对象和面向过程的区别

  1. 面向过程思想:(具体的)

      1. 步骤清晰简单,第一步做什么,第二步做什么…
      1. 面对过程适合处理一些较为简单的问题
  2. 面向对象思想:(抽象的)

      1. 物以类聚,分类的思维模式,思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向对象的过程的思索。
      1. 面向对象适合处理复杂的问题,适合处理需要多人协作的问题
  3. 总结: 对于描述复杂的事物,为了宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个思路。但是,具体到微观操作,仍然需要面向过程的思路去处理。

三、1、面向对象(OOP)介绍:

  1. 面向对象的本质就是:以类的方式组织代码,以对象的方式封装数据。
  2. 类和对象的区别与联系:
      1. 类是抽象的,概念的,代表一类事物,比如人类,猫类…,即它是数据类型
      1. 对象是具体的,实际的,代表一个具体事物,即,是实例
      1. 类是对象的模板,对象是类的一个个体,对应一个实例

java内存结构

  • Java内存结构是Java虚拟机(JVM)在运行时负责管理的内存区域,主要包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区(元空间)以及直接内存等部分。下面是对这些内存结构的详细分析:
  1. 程序计数器(Program Counter Register)
    • 功能:程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时,通过改变这个计数器的值来选取下一条需要执行的字节码指令。
    • 特性:
      • 线程私有:每个线程都有自己独立的程序计数器,互不干扰。
      • 唯一不会抛出OutOfMemoryError的内存区域:因为程序计数器占用的内存空间很小,且是线程私有的。
      • 生命周期与线程一致:随着线程的创建而创建,随着线程的销毁而销毁。
  2. Java虚拟机栈(Java Virtual Machine Stacks)
    • 功能:Java虚拟机栈是线程私有的,用于描述Java方法执行的内存模型。每个方法在执行的同时都会创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
    • 特性:
      • 线程私有:与程序计数器一样,Java虚拟机栈也是线程私有的。
      • 栈帧概念:每个方法从调用直至执行完成的过程,对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
      • 异常情况:
        StackOverflowError:如果线程请求的栈的深度大于虚拟机所允许的深度,将抛出此异常。
        OutOfMemoryError:如果虚拟机栈可以动态扩展,但在扩展时无法申请到足够的内存,将抛出此异常。
  3. 本地方法栈(Native Method Stack)
    • 功能:本地方法栈与虚拟机栈所发挥的作用非常相似,但主要区别在于虚拟机栈为虚拟机执行Java方法(即字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。
    • 特性:
      • 线程私有:与虚拟机栈一样,本地方法栈也是线程私有的。
      • 异常情况:与虚拟机栈相同,也可能抛出StackOverflowError和OutOfMemoryError异常。
  4. Java堆(Java Heap)
    • 功能:Java堆是Java虚拟机所管理的内存中最大的一块,被所有线程共享。它的主要目的是存放对象实例和数组。
    • 特性:
      • 线程共享:Java堆是唯一一个被所有线程共享的内存区域。
      • 垃圾收集的主要区域:Java堆是垃圾收集器的主要工作区域,因此也被称为“GC堆”。
      • 分代收集算法:现代Java虚拟机大多采用分代收集算法,将Java堆进一步细分为新生代和老年代等区域。
      • 异常情况:如果堆中没有足够的内存来完成实例分配,并且堆也无法再扩展时,将抛出OutOfMemoryError异常。
  5. 方法区(元空间)(Method Area / Metaspace)
    • 功能:方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在JDK 1.8及以后的版本中,方法区被元空间(Metaspace)所取代,元空间使用本地内存。
    • 特性:
      • 线程共享:方法区(或元空间)是各个线程共享的内存区域。
      • 存储内容:存储类的元数据、常量池、静态变量等。
      • 异常情况:当方法区(或元空间)无法满足内存分配需求时,将抛出OutOfMemoryError异常。
  6. 直接内存(Direct Memory)
    • 功能:直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但是,NIO(New Input/Output)等库会使用到直接内存,以提高数据交换的效率。
    • 特性:
      • 非JVM规范定义:直接内存不是由Java虚拟机直接管理的内存区域。
      • 可能导致OutOfMemoryError异常:当直接内存使用量超过系统设置的最大值时,可能会抛出OutOfMemoryError异常。

三、2、类和对象的内存分配机制:

  1. 1)Java内存的结构分析

      1. 栈:一般存放基本数据类型(局部变量)
      1. 堆:存放对象(Cat cat,数组等)(要特别注意字符串的存放位置
      1. 方法区:常量池(常量,比如字符串)和 类加载信息
      1. 示意:[Cat(name,age,price)]
  2. 流程分析:

案例:

class Person{
 int age=90;
 Sting name;
 Person(String n,int a){
   name=n;
   age=a;
   }
}
Person p=new Person("小明",20);
**流程分析**
     1. 加载Person类信息(Person.class),只会加载一次
     2. 在堆中分配空间
     3. 完成对象的初始化
     【3.1默认初始化 age=0;name=null;
        3.2显式初始化,age=90;name=null;
        3.3构造器的初始化 age=20;name=小明;
       】
     4. 将对象在堆中的地址,返回给p(p是对象名,也可以理解为对象的引用)

语法:
1.先声明,再创建;

Cat cat;
cat=new Cat();

2.直接创建

Cat cat=new Cat();

代码:

public static void main(String[] args){
//这里是创建一个猫对象赋值给cat,cat就是猫类的实例化
//类名 变量名=new 类名()
    Cat cat=new Cat();
    cat.name='小白';
    cat.age=10;
    cat.color='白色';
    //引用类的方法:对象名.方法名()
    cat.show();
}
//这里是定义一个猫类
// class 类名{}
class Cat{
//这里是定义三个属性(成员变量)
//属性是类的一个组成部分,既可以是基本数据类型,也可以是引用类型(数组,对象...)
    String name;
    int age;
    String color;
//这里是定义一个方法(成员方法),主要用于描述一些行为,动作
//1.提高代码的复用性
//2.可以将实现的细节封装起来,然后供其他对象调用
   public void show(){
      ...
   }

}

代码分析:

1.先加载Cat类信息(属性和方法信息)
2.在堆中分配空间,进行默认初始化(看对应的规则)
3.把对象地址赋给cat,cat就指向对象
4.进行指定初始化(属性的赋值)
5.调用方法

在这里插入图片描述

在这里插入图片描述

三、3、数组内存图

  1. 内存是计算机中的重要原件,临时存储区域,作用是运行程序。我们编写的程序是存放在硬盘中的,在硬盘中的程序是不会运行的,必须放进内存中才能运行,运行完毕后会清空内存。
  2. Java 虚拟机要运行程序,必须要对内存进行空间的分配和管理。
  3. ➢ Java 虚拟机的内存划分
      1. 栈(stack):存放的都是方法中的局部变量。方法的运行一定要在栈当中运行。
      • 局部变量:方法的参数。或者是方法{}内部的变量。
      • 作用域:一旦超出作用域,立刻从栈内存当中消失。
      1. 堆(Heap):凡是 new 出来的东西,都在堆当中。
      • 堆内存里面的东西都有一个地址值:16 进制。
      • 堆内存里面的数据,都有默认值。规则:
        • 整数:默认为 0。 浮点数:默认为 0.0
        • 字符:默认为’\u0000’。 布尔:默认为 false
        • 引用类型:默认为 null。
      1. 方法区(Method Area):存储类相关信息,包含方法信息。
      1. 本地方法栈(Native Method Stack):与操作系统相关
      1. 寄存器(PC Register):与 CPU 相关。

三、4、变量的作用域

代码演示:

class A{
//a为全局变量,它的作用域就是整个A类
  int a=10;
  public void show(){
  //这里的b就是局部变量,作用域就是在整个show()方法里面
     int b=20;
     int a=100;
     //如果这里的b没有赋值,因为它没有默认值,所以下面那个输出语句就会报错
     System.out.println(b);
     //因为局部变量和成员变量(全局变量)有相同的变量a
     //就近原则,这里的a输出的是100
      System.out.println(a);
  }
}
  • (一)、基本使用

      1. java中主要的变量:属性(成员变量)和局部变量
      1. 局部变量是指成员方法里面定义的变量
      1. 全局变量(属性):作用域为整个类体
      • 局部变量:也就是除了属性以外的其他变量,作用域为定义它的代码块中
      1. 全局变量可以不赋值直接使用,因为有默认值
      • 局部变量必须赋值后才能使用,因为没有默认值
  • (二)、注意事项和细节

      1. 属性和局部变量可以重名,访问时遵循就近原则
      1. 在同一个作用域中,比如在同一个成员方法中,两个局部变量,不能重名
      1. 属性的声明周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁;
      • 局部变量生命周期短,伴随着它的代码块的执行而创建,伴随着代码块的结束而死亡
    • 8.作用域不同
      • 全局变量:可以被本类使用,也可以被其它类使用(利用对象调用)
      • 局部变量:只能在本类中对应的方法中使用
      1. 修饰符不同
      • 全局变量(属性)可以加修饰符
      • 局部变量不可以加修饰符

(三)、修饰符

修饰符同类同包子类不同包
public1111
protected1110
默认1100
private1000

(四)、构造器:

    public static void main(String[] args) {
//        在创建对象时,系统自动调用该类的构造方法
//     new A(),括号里面并没有参数,所以这里是调用无参构造器的
        A a = new A();
//        这是有参构造器
        A a1=new A("大刀");
    }
}

class A{
    int a=20;
    public void show(){
        int b=10;
        System.out.println(b);
    }
    public A(){
//        这是无参构造器
    }
    public A(String name){
//        有参构造器,可以根据自己的需求去设置构造器
    }
基本介绍:
构造方法又叫构造器,是类的一种特殊的方法,它的主要作用是完成对新对象的初始化。
基本语法:
[修饰符]方法名(参数列表){
   方法体;
}
说明:
      1. 构造器的修饰符可以默认
      2. 构造器没有返回值
      3. 方法名 和类名字必须一样
      4. 参数列表 和 成员方法一样的规则
      5. 构造器的调用是由系统完成的

注意事项和使用细节:
 1. 一个类可以定义多个不同的构造器,即构造器重载
 2. this()这种语法只能出现在构造方法中的第一行(所以不能和super()在同一个构造器)
 3. 构造器名字要和类名相同
 4. 构造器没有返回值
 5. 构造器是完成对象的初始化,并不是创建对象
 6. 在创建对象时,系统自动调用该类的构造方法
 7. 如果程序员没有定义构造方法,系统会自动给类生成一个默认无参构造方法(也叫默认构造方法)
 8. 一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用无参构造器,除非显式地定义一下

(五)、this关键字:

class B{

    public String name;

    public B(){}
    public B(String name) {
//        这里就是调用本类的无参构造器,this();
//        只能在构造器中使用,并且要放在构造器的第一行
        this();
//        这里的this.name的意思就是:本类的name属性
        this.name = name;

    }
}

java虚拟机会给每个对象分配this,代表当前对象。
总结:简单来说就是,哪个对象调用,this就代表哪个对象
this就相当于指向它本身。

注意事项和使用细节
   1. this关键字可以用来访问属性,方法,构造器
   2. this用于区分当前类的属性和局部变量
   3. 访问成员方法的语法:this.方法名(参数列表);
   4. 访问构造器语法:this(参数列表);!!!注意,这个只能在构造器中使用
   5. this不能在类定义的外部使用,只能在类定义的方法中使用。

(六)、super关键字:

class C{
    private int a=10;
    public int c=20;

    public C(){
        System.out.println("父类的无参构造器");
    }
    public void show(){

    }
    private void eat(){

    }
}
class D extends C{
    public D(){
//        调用父类的无参构造器
//        如果不把super()放在第一行就会报错
        super();
        System.out.println("子类构造器");
        
    }
    public void s(){
//        调用父类非私有属性
        super.c=100;
//        调用父类非私有方法
        super.show();
    }
}
基本介绍:super代表父类的引用,用于父类的属性,方法,构造器。
基本语法:
      1. 访问父类的属性,但!!!不能访问父类的private属性!!!
              super.属性名;
      2. 访问父类的方法,但!!!不能访问父类的private方法!!!
          super.方法名(参数列表);
      3. 访问父类的构造器:
          super.(参数列表);
          只能放在子类构造器的第一行
          只能出现一句

super给编程带来的便利或者细节:
     1. 调用父类的构造器的好处:分工明确,父类属性由父类初始化,子类属性由子类初始化。
     2. 当子类中有父类的成员(属性和方法)重名时,为了访问父类的成员,必须通过super指定。
         如果没有重名,使用super、this、直接访问是一样的效果。
     3. super的访问不限于直接父类,如果多个基类中都有同名的成员,使用super访问遵循就近原则
     4. super(参数列表),必须放在构造器的第一行
    

四、面向对象的三大特性:

四、1、 封装

1)、基本介绍

  1. 封装就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作[方法],才能对数据进行操作。
  2. 封装实现的三步:
      1. 将属性进行私有化private(就是说我们不能直接修改属性)
      1. 提供一个公共的(public)set方法,对于属性判断并且赋值
      1. 提供一个公共的(public)get方法,用于获取属性的值,
  public void setXxx(类型 参数名){
   //加入数据验证的业务逻辑
   属性=参数名;  
  }
public 数据类型getXxx(){
   //权限判断,Xxx是某个属性
   return xx;
   }

四、2、继承

1)、基本介绍:
继承可以解决代码复用,让我们的编程更加靠近人类思维
当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends关键字来声明继续父类即可

2)、继承的基本语法

class 子类 extends 父类{
}

说明:

  1. 子类就会自动拥有父类定义的(非私有的)属性和方法
  2. 父类又叫超累,基类
  3. 子类又叫派生类

3)、继承的作用

  1. 代码的复用性提高了
  2. 代码的扩展性和维护性提高了

四、2.1、 继承的细节

父类Base:

public class Base {  //父类
    public int n1=100;
    protected  int n2=200;
    int n3=300;
    private int n4=400;

//    public Base() {//无参构造器
//        System.out.println("父类Base()......");
//    }
//
    public Base(String name,int age){//有参构造器
        System.out.println("父类的有参构造器被调用Base(String name,int age)");
    }

//    在父类提供一个公共的方法返回了n4
    public int getN4(){
        return n4;
    }

    public void test100(){
        System.out.println("test100");
    }
    protected void test200(){
        System.out.println("test200");
    }
    void test300(){
        System.out.println("test300");
    }
    private void test400(){
        System.out.println("test400");
    }
//    公共方法调用父类的私有方法
    public void callTest400(){
        test400();
    }
}

子类Sub:

public class Sub extends Base{
    public Sub() {//无参构造器
         //(如果没有调用父类其他构造器的时候)这里可以理解为
        // 隐藏了一个: super();->默认调用父类的无参构造器

        //这里因为父类的无参构造器被删除了,所以必须调用父类的其他构造器,否则会报错
        super("smith",10);
        System.out.println("子类Sub()....");
    }

//    当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器
    public  Sub(String name){
        super("jack",20);//指定调用父类的哪个构造器
        System.out.println("子类的Sub(String name)构造器被调用");
    };

    public void sayOk(){
//      非私有的属性和方法可以在子类直接访问
//        私有属性和方法不能在子类中直接访问
        System.out.println("n1="+n1+" n2"+n2+" n3"+n3+" n4是私有属性,无法访问");
        test100();
        test200();
        test300();
//        通过父类提供公共的方法去访问
        System.out.println("通过父类的公共方法getN4()得到n4= "+getN4());
        callTest400();//调用父类的公共方法得到父类的私有方法
    }
}

  1. 子类继承了所有父类的属性和方法,非私有的属性和方法可以在子类直接访问,但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问。
    代码测试:
public class ExtendsDetail {
    public static void main(String[] args) {
        Sub sub = new Sub();
        sub.sayOk();
    }
}

运行结果:

父类的有参构造器被调用Base(String name,int age)
子类Sub()....
n1=100 n2200 n3300 n4是私有属性,无法访问
test100
test200
test300
通过父类的公共方法getN4()得到n4= 400
test400
  1. 子类必须调用父类的构造器,完成父类的初始化
public Sub() {//无参构造器
         //(如果没有调用父类其他构造器的时候)这里可以理解为
        // 隐藏了一个: super();->默认调用父类的无参构造器

        //这里因为父类的无参构造器被删除了,所以必须调用父类的其他构造器,否则会报错
        super("smith",10);
        System.out.println("子类Sub()....");
    }
  1. 当创建子类对象时,不管使用子类的哪个构造器,默认情况下,总会去调用父类的无参构造器如果父类没有提供无参构造器,则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过。(注意,super是调用父类数据的关键字)
  2. 如果希望指定去调用父类的某个构造器,则显式地调用一下:super(参数列表)
  3. super在使用时,必须放在构造器的第一行
  4. super () 和this()都只能放在构造器的第一行,因此这两个方法不能共存在一个构造器
  5. java所有类都是Object的子类
  6. 父类构造器的调用不限于直接父类!它是要一直往上追溯,直到Object类(顶级父类)
  7. 子类最多只能继承一个父类(即直接继承),即Java中是单继承机制
  8. 不能滥用继承,子类和父类必须满足正常的逻辑关系(举例:蔬菜类可以被白菜,生菜等等子类继承)

子类的本质分析
代码:

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.getAge());
        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="大头儿子";
}

运行结果:

大头儿子
39
旅游

代码分析图:
在这里插入图片描述

四、3、 多态

  1. 基本介绍:方法或者对象具有多种形态,是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
  2. 方法的多态:重写和重载就体现多态。
public static void main(String[] args) {
//        方法重载体现多态
        BB bb = new BB();
        System.out.println(bb.sum(12,10));
        System.out.println(bb.sum(12,10,20));
//        上面就能体现方法的多态,同一个方法,引用不同的参数个数,实现不一样的功能
//        这对于sum方法来说,就是多种状态的体现
        
//        方法重写体现多态
        AA aa = new AA();
        aa.say();
        bb.say();
    }
}

class AA{
    public void say(){
        System.out.println("AA say() 方法被调用...");
    }
}

class BB extends AA{
    public int sum(int n1,int n2){
        return n1+n2;
    }
    public int sum(int n1,int n2,int n3){
        return n1+n2+n3;
    }
    
    public void say(){
        System.out.println("BB say() 方法被调用...");
    }
}

运行结果:

22
42
AA say() 方法被调用...
BB say() 方法被调用...

  1. 对象的多态(核心):
  - 一个对象的编译类型和运行类型可以不一致
  - 编译类型在定义对象时,就确定了,不能改变
  - 运行类型是可以变化的
  - 编译类型看定义时=号的左边,运行类型看=号的右边
下面代码的说明:
Master类的public void feed(Animal animal,Food food) 引入两个类Animal,和Food
,Animal是Dog,Cat等等的主类,Food是fish,rice的主类
这里就能体现对象的多态了:
       以父类作为参数,然后可以传子类进去使用。

Master类:

public class Master{
    private String name;

    public Master(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
//    使用多态机制,可以统一管理主人喂食的问题,即可以代替下面的一堆代码
//    animal  编译类型是Animal,可以指向(接收)Animal的子对象
//   food  编译类型是Food,可以指向(接收)Food子类的对象
    public void feed(Animal animal,Food food){
        System.out.println("主人 "+ name + " 给" + animal.getName()+" 吃" + food.getName());
    }
//    //主人给小狗喂食骨头
//    public void feed(Dog dog,Bone bone){
//        System.out.println("主人 "+ name +" 给" + dog.getName() + " 吃" + bone.getName());
//    }
    主人给小猫喂黄花鱼
//    public void feed(Cat cat,Fish fish){
//    System.out.println("主人 "+ name +" 给" + cat.getName() + " 吃" + fish.getName());
//  }
}

主方法:

public static void main(String[] args) {
        Master tom = new Master("汤姆");
        Dog dog = new Dog("大黄");
        Bone bone=new Bone("大棒骨");
        tom.feed(dog,bone);

        Cat cat = new Cat("小花猫");
        Fish fish = new Fish("黄花鱼");
        System.out.println("===============");
        tom.feed(cat,fish);

        //给小猪喂米饭
        Pig pig=new Pig("小花猪");
        Rice rice = new Rice("米饭");
        tom.feed(pig,rice);

    }

多态的注意事项和细节

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

1. 多态的向上转型:
    本质:父类的引用指向子类的对象
    语法:父类类型 引用名=new 子类类型();
    特点:编译类型看左边,运行类型看右边。
               可以调用父类中的所有成员(遵循访问规则)
               不能调用子类的特有成员
               最终运行效果看子类的具体实现:即调用方法时,按照从子类(运行类型)开始查找方法

Animal类:

public class Animal {
    public void cry(){
        System.out.println("Animal下的cry()方法:动物在叫.....");
    }
}

Dog类:

public class Dog extends Animal{
    public void cry(){
        System.out.println("Dog类里面的cry方法:小狗汪汪叫");
    }
    public void show(){
        System.out.println("子类的特有方法");
    }

}

Cat类:

public class Cat extends Animal{
    public void cry(){
        System.out.println("Cat类里面的cry方法:小猫喵喵叫");
    }
}

主类:

public class PolyObject {
    public static void main(String[] args) {
//        体验一下对象多态的特点

//    animal的编译类型 就是Animal, 运行类型就是Dog
        Animal animal=new Dog();
//       因为运行时,这时(即执行到该行时,animal的运行类型是Dog,而且Dog中有与父类方法重写,所以,这个cry方法就是Dog的cry
//        注意,上面这个是向上转型,它能调用父类的所有方法(需要遵循访问原则),但不能调用子类特有的方法
        animal.cry();

//      show()方法是子类中特有的,所有无法访问  animal.show();
//     animal的编译类型 就是Animal, 运行类型就是Cat
        animal=new Cat();
        animal.cry();
    }
}

运行结果:

Dog类里面的cry方法:小狗汪汪叫
Cat类里面的cry方法:小猫喵喵叫
 2. 多态的向下转型
   语法:子类类型 引用名=(子类类型) 父类引用;
   特点:
       - 只能强转父类的引用,不能强转父类的对象
       - 要求父类的引用必须指向的是当前目标类型的对象
       - 当向下转型侯,可以调用子类类型中所有的成员

Animal类:

public class Animal {
    String name="动物";
    int age=10;
    public void sleep(){
        System.out.println("睡");
    }
    public void run(){
        System.out.println("跑");
    }
    public void eat(){
        System.out.println("吃");
    }
    public void show(){
        System.out.println("hello,你好");
    }
}

Cat类:

public class Cat extends Animal {
    public void eat(){//方法重写
        System.out.println("猫吃鱼");
    }
    public void catchMouse(){//cat特有的方法
        System.out.println("猫抓老鼠");
    }
}

主类:

public class PolyDetail {
    public static void main(String[] args) {
        //向上转型: 父类的引用指向子类的对象
//        语法: 父类类型  引用名 =new 子类类型();
        //可以调用父类的所有成员(需要遵循访问权限)
        //        不能调用子类中特有的成员
        //因为在编译阶段,能够调用哪些成员,是由编译类型决定的
        Animal animal=new Cat();
//        调用方法(运行)时,按照从子类(!!!!运行类型)开始查找、、、、、、、、、、、、

//        Object object=new Cat();  可以
//        最终的运行效果要看子类的具体实现
        animal.eat();
        animal.run();
        animal.sleep();
        animal.show();
//        多态的向下转型:
//        1.语法: 子类类型 引用名 =(子类类型)父类类型;
//        想要调用Cat的特有方法
        Cat cat=(Cat) animal;
//              animal原先是指向Cat的
//              cat的编译类型是Cat,运行类型也是Cat
        cat.catchMouse();
//        2.只能强转父类的引用,不能强转父类的对象
//        3.要求父类的引用必须指向的是当前目标类型的对象
//        Dog dog=(Dog) animal;  是不可以的,因为animal原先是指向Cat的
//        4.当向下转型后,可以调用子类类型中所有的成员

    }
}

运行结果:

猫吃鱼
跑
睡
hello,你好
猫抓老鼠

  1. 属性重写问题
    1)属性没有重写之说!属性的值要看编译类型
    注意:它与方法的调用是不一样的,方法的调用是从运行类型开始查询的
 public static void main(String[] args) {
        //属性没有重写之说,属性的值要看对象的编译类型(即看=号的左边)
//         注意:它与方法的调用是不一样的,方法的调用是从运行类型开始查询的
        Base sub = new Sub();
        System.out.println(sub.count);//这里的值是Base类的count=10
        Sub sub1 = new Sub();  //
        System.out.println(sub1.count);//这里的值是Sub类的count=20
    }
class Base{//父类
    int count=10;//属性
}

class Sub extends Base{//子类
    int count=20;//属性
}
  1. instanceOf 比较操作符
    用于判断对象的运行类型是否为XX类型或者XX类型的子类型
 public static void main(String[] args) {
//        看对象实例的运行类型(即=号的右边)
        Son son = new Son();
        System.out.println(son instanceof Son); //true
        System.out.println(son instanceof Father);//true

//        编译类型:Father, 运行类型:Son
        Father father=new Son();
        System.out.println(father instanceof Father);//true
        System.out.println(father instanceof Son);//true

        Object obj=new Object();
        System.out.println(obj instanceof Father);//false
    }

class Father{

}
class Son extends Father{

}

练习1:

public static void main(String[] args) {
        double d=13.3;
        long i=(long)d;
        System.out.println(i); //13
        int in=5;
//        boolean b=(boolean)in;//语法错误,int类型不能强转为Boolean
        Object obj="hello";  //可以,这是向上转型
        String objStr=(String)obj;  //向下转型
        System.out.println(objStr); //hello
        Object integer = new Integer(5); //向上转型
        //下面一句是错误的,指向Integer的父类引用,不能向下转型转换为String类型
//        String str =(String)integer;
        Integer str1=(Integer)integer;//向下转型
    }

练习2:

 public static void main(String[] args) {
        Sub02 sub02 = new Sub02();
        System.out.println(sub02.count);//20
        sub02.display();//20,调用方法,看base的运行类型

        Base02 base=sub02;
        //true,两个对象的比较,看的是地址是否一样,这里因为指向的是同一个空间,所以是true
        System.out.println(base==sub02);
        System.out.println(base.count);//10
        base.display();//20,调用方法,看base的运行类型

//        总结访问属性是要看编译类型的(=号的左边)
//        调用方法是从运行类型开始查找的
    }

class Base02{
    int count=10;
    public void display(){
        System.out.println(this.count);
    }
}
class Sub02 extends Base02{
    int count=20;
    public void display(){
        System.out.println(this.count);
    }
}

四、4、动态绑定机制

  1. 当调用对象的方法的时候,该方法会和对象的内存地址(或者说是运行类型)绑定
  2. 当调用对象属性时,没有动态绑定机制,即哪里声明,哪里使用。
public class DongTaiBDJZ {
    public static void main(String[] args) {
        //a的编译类型是A1,运行类型是B1
        A1 a = new B1();
        System.out.println(a.sum());//40
// -》当子类B1的sum方法去掉以后,这里就会父类A1的sum方法,
// sum方法里面有个getI方法(这里应该是运行类型B1的getI方法)
        System.out.println(a.sum());//30

        System.out.println(a.sum1());//30
//当子了类B1的sum1方法去掉以后,这里就会调用父类A1的sum1方法
//  sum1方法里面有个i(属性),哪里声明,哪里使用,这个i就是A1的i;
        System.out.println(a.sum1());//20

    }
}
class A1{
    public int i=10;
//    动态绑定机制
    public int sum(){
        return getI()+10;
    }
    public int sum1(){
        return i+10;
    }
    public int getI(){
        return i;
    }
}
class B1 extends A1{
    public int i=20;
    public int sum(){
        return i+20;
    }
    public int getI(){
        return i;
    }
    public int sum1(){
        return i+10;
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值