JAVA三大特性


面向对象程序的三大特性:封装,继承,多态

1. 封装

封装:将数据和操作数据的方法有机结合,隐蔽对象的属性和实现细节,仅对外公开接口来和对象进行交互。

1.1 访问限定符

JAVA主要通过类和访问权限来实现封装:类将数据和封装数据的方法结合在一起,而访问限定符控制方法或者属性能否直接在类外使用,除了可以限定类中成员的可见性,也可以控制类的可见性

范围从小到大依次是private,default,protected,public

可以这样理解:

public:一个人的外貌特征,谁都可以看到

default(什么都不写时的默认权限):对于自己家族不是秘密(同一个包中),对于其他人来说是隐私

private:只有自己知道,其他人都不知道

1.2 先看private:

例:

public class Student {
    private String name;
    public int age;
}
public class Test {
    public static void main(String[] args) {
        Student s=new Student();
        s.name="zuozuo";//这一句编译不通过,会报错
    }
}
//结果:java: name 在 TESTDEMO.Student 中是 private 访问控制

我们知道,private修饰的属性只可以在当前类中使用

1.3 默认权限

默认权限只能在同一个包中使用默认权限也叫做包访问权限,被其修饰的属性方法只能在当前包中使用。

1.3.1 扩展一下包的概念:

软件包:为了更好的管理类,把多个类收集在一起成为一组,称为软件包。

包的另一个重要作用:在同一个工程中允许存在名称相同的类,只要处在不同的包中即可。

1.3.2 导入包中的类:

JAVA中提供了很多现成的类供我们使用,例如Date类,可以使用java.util.Date导入java.util这个包中的Date

一种简便的方法,使用import语句导入包

例:如果需要使用java.util中的其他类,可以使用import java.util.*

import java.util.*;
public class Test {
    public static void main(String[] args) {
        Date date=new Date();
        System.out.println(date.getTime());//得到一个毫秒级别的时间戳
    }
}

但是,更建议显式的指定要导入的类名,否则容易出现冲突的情况

例:

import java.util.*;
import java.sql.*;
public class Test {
    public static void main(String[] args) {
        Date date=new Date();
        System.out.println(date.getTime());
    }
}
//结果:java: 对Date的引用不明确
//java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配

因为util和sql都存在一个Date这样的类,此时会出现歧义,编译出错

在这种情况下需要使用完整的类名:

import java.util.*;
import java.sql.*;
public class Test {
    public static void main(String[] args) {
        java.util.Date date=new java.util.Date();
        System.out.println(date.getTime());
    }
}

可以使用import static导入包中的静态方法和属性

例:

import static java.lang.Math.*;
public class Test {
    public static void main(String[] args) {
        double x=30;
        double y=40;
        //静态导入的方式写起来更方便
        //double result=Math.sqrt(Math.pow(x,2)+Math.pow(y,2));
        double result=sqrt(pow(x,2)+pow(y,2));
        System.out.println(result);
    }
}

注意,import static java.lang.Math.*;这个语句导入了Math类的所有静态方法,但是是随用随取

并且,import和C++的#include差别很大。

1.4 pretected关键字

被protected修饰的可以在同一包中的同一类使用,可以在同一包中的不同类,可以在不同包中的子类中被使用(与继承有关)。

注意:

父类中private成员变量虽然子类不能直接访问,但是也继承到子类了

1.5 自定义包

基本规则:

1.在文件最上方加一个package语句指定该段代码在哪个包中

例:

package com.bite.www2;
//声明当前类在哪个包底下

2.如果一个类没有package语句,则被放到默认包中

1.6 static成员

以学生类为例子,我们对学生类实例化了三个队象s1,s2,s3,每个对象都有自己特有的名字,年龄,性别,分数:

例:

public class Student {
    public String name;
    public int age;
    public String sex;
    public double score;

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

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                ", score=" + score +
                '}';
    }

    public static void main(String[] args) {
        Student s1=new Student("hh",6,"nv",98);
        Student s2=new Student("zuozuo",9,"nv",90);
        Student s3=new Student("ww",10,"nan",70);
        System.out.println(s1.toString());
    }
}

调试发现,s1对象中的内容如图:
在这里插入图片描述
s2对象内容如图:

在这里插入图片描述
s3对象内容如图:

在这里插入图片描述
我们假设三个同学是一个班级的,那上课肯定在同一个教室,那能否给类中加一个成员变量,来保存同学上课时的教室,答案是不行的。

因为之前在Student类中定义的成员变量,每个对象中都包含一份(称为实例变量),教室这个属性并不需要每个学生对象中都存储一份,而是需要让所有的学生来共享。在JAVA中,被static修饰的成员,称为静态成员,或者类成员,其不属于某个具体的对象,是所有对象共享的

1.6.1 static修饰成员变量

static修饰的成员变量称为静态成员变量,静态成员变量最大的特性是:不属于某个具体的对象,是所有对象所共享的。

特性:

1.不属于某个具体的对象,是类的属性,所有对象共享,不存在某个对象的空间中

2.既可以通过**对象访问**,也可以通过**类名访问**,更推荐类名访问

3.类变量**存储在方法区**4.生命周期伴随类的一生(随类的加载而创建,类的卸载而销毁)

例:

public class Student {
    public String name;
    public int age;
    public String sex;
    public double score;
    public static String classRoom="102";//classRoom被static修饰,不属于任何一个对象

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

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                ", score=" + score +
                '}';
    }

    public static void main(String[] args) {
        Student s1=new Student("hh",6,"nv",98);
        Student s2=new Student("zuozuo",9,"nv",90);
        Student s3=new Student("ww",10,"nan",70);
        System.out.println(s1.classRoom);
        System.out.println(s2.classRoom);
        System.out.println(s3.classRoom);
        System.out.println(Student.classRoom);//也可以通过类名访问
    }
}
//结果:
//102
//102
//102
//102

上述代码通过调试,可以在监视窗口发现,静态成员变量并没有存储到某个具体的对象中。

1.6.2 static修饰成员方法

一般类中的成员属性设置为private,成员方法设置为public,那设置之后,Student类中的classRoom属性如何在类外访问?

例:

public class Student {
    private String name;
    private int age;
    private String sex;
    private double score;
    private static String classRoom="102";

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

在另一个类中:

public class Test {
    public static void main(String[] args) {
        System.out.println(Student.classRoom);
    }
}
//结果:java: classRoom 在 TESTDEMO.Student 中是 private 访问控制

静态成员变量一般通过静态成员方法来访问:

例:

public class Student {
    private String name;
    private int age;
    private String sex;
    private double score;
    private static String classRoom="102";

    public Student(String name, int age, String sex, double score) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.score = score;
    }
    public static String getClassRoom(){
        return classRoom;
    }

在另一个类中:

public class Test {
    public static void main(String[] args) {
        System.out.println(Student.getClassRoom());
    }
}
//结果:102

静态方法可以互相调用:

例:

public static void func(){
        System.out.println("hhh");
    }
    public static void func2(){
        func();//或者Student.func();
        System.out.println("www");
    }

不能在静态方法中访问非静态成员变量:

例:

public static String getClassRoom(){
        System.out.println(this);
        return classRoom;
    }
    //结果:java: 无法从静态上下文中引用非静态 变量 this

例:

public static String getClassRoom(){
        age+=1;
        return classRoom;
    }
    //结果:java: 无法从静态上下文中引用非静态 变量 this

静态方法中不可以调用任何非静态方法,因为非静态方法有this参数,在静态方法中调用时无法传递this引用

例:

public static String getClassRoom(){
        doClass();
        return classRoom;
    }
    public void doClass(){
        System.out.println("上课");
    }
    //结果:java: 无法从静态上下文中引用非静态 变量 this

1.6.3 static成员变量初始化

静态成员变量初始化分为两种:就地初始化和静态代码块初始化

首先看就地初始化:也就是在定义时直接给出初始值

1.7 代码块

概念:使用{ }定义的一段代码称为代码块,根据代码块定义的位置以及关键字,可分为四种:

1.普通代码块
2.构造快
3.静态块
4.同步代码块(与多线程有关)

1.7.1 普通代码块

也就是定义在方法中的代码块

例:

public class Test {
    public static void main(String[] args) {
        {//直接使用{}定义
            int x=10;
            System.out.println("x1="+x);
        }
    }
}

1.7.2 构造代码块

构造块:定义在类中的代码块(不加修饰符),也叫实例代码块,构造代码块一般用于初始化实例成员变量

例:

public class Student {
    private String name;
    private int age;
    private String sex;
    private double score;
    private static String classRoom="102";
    //实例代码块
    {
        this.name="zuozuo";
        this.age=18;
        this.sex="nv";
        this.score=90;
    }
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                ", score=" + score +
                '}';
    }
    public static void main(String[] args) {
        Student student=new Student();
        System.out.println(student.toString());
    }
  
}
//结果:Student{name='zuozuo', age=18, sex='nv', score=90.0}

1.7.3 静态代码块

概念:使用static修饰的代码块,用于初始化静态成员变量

例:

public class Student {
    private String name;
    private int age;
    private String sex;
    private double score;
    private static String classRoom="102";
    //实例代码块
    {
        this.name="zuozuo";
        this.age=18;
        this.sex="nv";
        this.score=90;
        System.out.println("I am instance init()");
    }
    //静态代码块
    static{
        classRoom="108";
        System.out.println("I am static init()");
    }
    public Student(){
        System.out.println("I am Student init()");
    }
    public static void main(String[] args) {
        Student student1=new Student();
        System.out.println("=========");
        Student student2=new Student();
    }
}
//结果:I am static init()
//I am instance init()
//I am Student init()
//=========
//I am instance init()
//I am Student init()
我们发现:

1.静态代码块**不管生成多少个对象,其只会执行一次**

2.静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的

3.如果**一个类中包含多个静态代码块**,在编译代码时,编译器会**按照定义的先后次序依次执行(合并)**

4.**实例代码块只有在创建对象时才会执行**

1.8 内部类

当一个事物的内部还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么这个内部的完整结构最好使用内部类。在JAVA中,可以将一个类定义在另一个类或者方法的内部,前者为内部类,后者为外部类。

例:

public class OutClass {//外部类
    class InnerClass{//内部类
        
    }
}
注意:定义在class 类名{}花括号外部的,即使在一个文件(包)里,都不能称为内部类

例:

public class A {
    
}
class B{
    
}
//A和B是两个独立的类,彼此没有关系

补充:内部类和外部类共用同一个JAVA源文件,但是经过编译之后,内部类会形成单独的字节码文件。

1.8.1 内部类的分类

1.实例内部类:未被static修饰的类

例:

public class OuterClass {
    private int a;
    static int b;
    int c;
    public void methodA(){
        a=10;
        System.out.println(a);
    }
    public static void methodB(){
        System.out.println(b);
    }
    //实例内部类
    class InnerClass{
        int c;
        public void methodInner(){
            //在实例内部类中可以直接访问外部类中:任意访问限定符修饰的成员
            a=100;
            b=200;
            methodA();
            methodB();
            //如果外部类和实例内部类中具有相同名称成员时,优先访问内部类自己的
            c=300;
            System.out.println(c);
            //如果要访问外部类同名成员时,必须:外部类名称.this.同名成员名字
            OuterClass.this.c=400;
            System.out.println(OuterClass.this.c);
        }
    }

    public static void main(String[] args) {
        OuterClass outerclass=new OuterClass();
        System.out.println(outerclass.a);
        System.out.println(OuterClass.b);
        System.out.println(outerclass.c);
        outerclass.methodA();
        outerclass.methodB();
        System.out.println("========实例内部类的访问=========");
        //要访问实例内部类中的成员,必须创建实例内部类的对象
        //而普通内部类定义与外部类成员定义位置相同,因此创建实例内部类对象时必须借助外部类
        //创建实例内部类对象
        OuterClass.InnerClass innerClass1=new OuterClass().new InnerClass();
        //也可以先将外部类对象创建出来,然后再创建实例内部类对象
        OuterClass.InnerClass innerClass2=outerclass.new InnerClass();
        innerClass2.methodInner();
    }
}
结果:
//0
//0
//0
//10
//0
//========实例内部类的访问=========
//10
//200
//300
//400

注意:

1.外部类中的任何成员都可以在实例内部类方法中直接访问
2.实例内部类所处的位置与外部类成员位置相同,因此也受访问限定符的约束
3.在实例内部类方法中访问同名成员时,优先访问自己的,如果要访问外部类同名的成员,必须:`外部类名称.this.同名成员`来访问
4.实例内部类对象必须在先有外部类对象前提下才能创建
5.实例内部类的非静态方法中包含了一个指向外部类对象的引用
6.外部类中,不能直接访问实例内部类中的成员,如果要访问必须先创建内部类的对象

2.静态内部类:被static修饰的成员类

public class OuterClass {
    private int a;
    static int b;
    public void methodA(){
        a=10;
        System.out.println(a);
    }
    public static void methodB(){
        System.out.println(b);
    }
    //静态内部类
    static class InnerClass{
        public void methodInner(){
            //在静态内部类只能访问外部类的静态成员
            a=100;//编译失败,因为a不是类成员变量
            b=200;
            methodA();//编译失败,因为methodA()不是类成员方法
            methodB();
        }
    }
    public static void main(String[] args) {
        //静态内部类对象创建和成员访问
        OuterClass.InnerClass innerClass=new OuterClass.InnerClass();
        innerClass.methodInner();
    }
}
注意:

1.在静态内部类只能访问外部类中的静态成员

2.创建静态内部类对象时,不需要先创建外部类对象

3.局部内部类

定义在外部类的方法体或者{}中,该种内部类只能在其定义的位置使用

public class OuterClass {
    int a=10;
    public void method() {
        int b = 10;
        //局部内部类,不能被public,static等修饰符修饰
        class InnerClass {
            public void methodInnerClass() {
                System.out.println(a);
                System.out.println(b);
            }
        }
        //只能在该方法体内部使用,其他位置都不能用
        InnerClass innerClass = new InnerClass();
        innerClass.methodInnerClass();
    }
    public static void main(String[] args) {
        OuterClass.InnerClass innerClass=null;//编译失败
    }
}
注意:

1.局部内部类只能在所定义的方法体内部使用

2.不能被public,static等修饰符修饰

4.匿名内部类

2.继承

对共性的抽取,实现代码复用。是代码可以复用的重要手段,允许在保持原有特性基础上进行拓展,增加新功能,这样产生的类,叫派生类。

如:猫和狗都是动物Animal,都有name,age,weight等属性,狗有brak()方法,猫有miao()方法。我们可以认为Animal这个类有name,age,weight这些属性,有eat(),sleep()这些方法,猫和狗这两个类继承Animal这个类,在这个原有基础上,猫增加了miao()这个方法,狗增加了bark()这个方法。

Animal类称为父类或基类,Dog和Cat称为子类或者派生类。继承后,子类可以复用父类中的成员,也可以增加新的成员。子类继承父类之后,必须要添加自己特有的成员,体现出与基类的不同,否则就没有继承的必要了。

2.1 继承的语法

修饰符 class 子类 extends 父类{

}

2.2 父类成员的访问

2.2.1 子类访问父类的成员变量

子类和父类不存在同名的成员变量
public class Base {
    int a;
    int b;
}
public class Derived extends Base{
    int c;
    public void method(){
        a=10;
        b=20;
        c=30;
    }
}
子类和父类成员变量同名
public class Base {
    int a;
    int b;
    int c;
}
public class Derived extends Base{
    int a;
    char b;
    public void method(){
        a=10;//访问父类继承的还是自己的?
        b=20;//访问父类继承的还是自己的?
        c=101;//子类没有,肯定是父类的
    }
}
注意:

1.如果访问的成员变量子类有,则优先访问子类自己的

2.如果子类访问的成员变量子类无,则访问父类继承下来的,如果父类也没有定义,则编译报错

3.如果子类访问的成员变量与父类中成员变量同名,则优先访问自己的

也就是说,遵循就近原则

2.3 super关键字

有时会出现子类和父类存在同名的成员,如果要在子类方法中访问父类同名成员时,就要用到super关键字,主要作用是:在子类方法中访问父类成员。

public class Base {
    int a;
    int b;
    public void methodA(){
        System.out.println("Base methodA");
    }
    public void methodB(){
        System.out.println("Base methodB");
    }
}
public class Derived extends Base{
    int a;//与父类中成员变量同名且同类型
    char b;//与父类中成员变量同名但不同类型
    public void methodA(int a){//与父类methodA方法构成重载
        System.out.println("Derived methodA");
    }
    public void methodB(){//与父类methodB方法构成重写
        System.out.println("Dreived methodB");
    }
    public void methodC(){
        //对于同名的成员,直接访问时,访问的都是子类的
        a=100;//等价于:this.a=a;
        b=10;//this指当前对象的引用
        super.a=200;//super获取子类从父类对象中继承下来的
        super.b=20;
        //子类和父类构成重载的方法,直接可以通过参数列表区分访问的是子类还是父类的
        methodA();//没有传参,访问的是父类的
        methodA(20);//有int参数,访问的是父类的
        //在子类中访问重写的父类方法,要借助super
        methodB();//直接访问,访问的是子类的
        super.methodB();//访问的是父类的
    }
}
注意:
1.super只能在非静态方法中使用

2.super代表父类的引用,这样的理解是不对的,因为并没有实例化父类对象。正确的理解是:它只是一个关键字,最大的作用就是体现出更好的可读性。

2.4 子类构造方法

注意:子类对象构造时,需要先调用父类的构造方法,然后再调用子类的构造方法

例:

public class Base {
    public Base(){
        System.out.println("Base()");
    }
}
public class Derived extends Base{
    public Derived(){
        //super();   子类构造方法中默认会调用父类的无参构造方法,用户没有写时,编译器自动添加,而且必须是子类构造方法的第一句,并且只出现一次
        System.out.println("Derived()");
    }
}
public class Test {
    public static void main(String[] args) {
        Derived d=new Derived();
    }
}
//结果:Base()
     //Derived()

在子类构造方法中,没有写关于父类构造的代码,但在构造子类对象时,先执行父类的构造方法然后执行子类的构造方法。因为:子类的成员一部分是父类继承下来的,一部分是自己新增的,先有父再有子,所以构造子类对象时,先调用父类的构造方法,将其从父类继承下来的成员构造完整,然后再调用子类的构造方法,将子类新增的成员初始化完整。

注意:

1.如果父类显式定义无参或默认的构造方法,则子类构造方法的第一行默认有super()

2.如果父类的构造方法有参数,则需要程序员为子类显式定义构造方法,并选择合适的父类构造方法调用,否则编译失败

3.super()必须是子类构造方法中第一行语句

4.super()在子类构造方法中只能出现一次,且不能和this同时出现

第一条补充:

当父类和子类都没有提供任何构造方法时,编译器会默认提供这两个方法:

父类:

public Animal(){

       }

子类:

public Dog(){
      super();
    }

2.5 super和this

相同点:

1.都是Java中的关键字

2.只能在类的非静态方法中使用,访问非静态成员变量和方法

3.在构造方法中调用时,必须时构造方法中第一条语句,并且不能同时存在

不同点:

1.this是当前对象的引用

2.在非静态成员方法中,this访问本类的方法属性,super访问父类继承下来的方法属性

3.在构造方法中,this()用于调用本类构造方法,super()用于调用父类的构造方法,两种调用不能同时在构造方法中出现

4.构造方法中一定会存在super()的调用,用户没有写编译器也会自动添加,但是this()用户不写则没有

2.6 类中实例代码块 静态代码块 构造代码块执行的顺序

例:

public class Person {
    public String name;
    public int age;
    public Person(String name,int age){
        this.name=name;
        this.age=age;
        System.out.println("构造方法执行");
    }
    {
        System.out.println("实例代码块执行");
    }
    static{
        System.out.println("静态代码块执行");
    }
}
public class Test {
    public static void main(String[] args) {
       Person person1=new Person("zuozuo",18);
        System.out.println("=====================");
        Person person2=new Person("huahua",18);
    }
}
//结果:静态代码块执行
//实例代码块执行
//构造方法执行
//=====================
//实例代码块执行
//构造方法执行

结论:静态最优先,父类优先于子类

1.静态代码块先执行,并且只执行一次

2.当有对象创建时,才会执行实例代码块,实例代码块执行后,构造方法才执行

3.父类优先子类

例:

public class Animal {
    public String name;
    public int age;
    public Animal(String name, int age){
        this.name=name;
        this.age=age;
        System.out.println("Animal构造方法执行");
    }
    {
        System.out.println("Animal实例代码块执行");
    }
    static{
        System.out.println("Animal静态代码块执行");
    }
}
public class Dog extends Animal{
    public Dog(String name,int age){
        super("zuozuo",18);
        this.name=name;
        this.age=age;
        System.out.println("Dog构造代码块执行");
    }
    {
        System.out.println("Dog实例代码块执行");
    }
    static{
        System.out.println("Dog的静态代码块执行");
    }
}
public class Test {
    public static void main(String[] args) {
       Dog dog1=new Dog("huahua",19);
        System.out.println("===============");
        Dog dog2=new Dog("zuozuo",18);
    }
}

结果:

Animal静态代码块执行
Dog的静态代码块执行
Animal实例代码块执行
Animal构造方法执行
Dog实例代码块执行
Dog构造代码块执行
===============
Animal实例代码块执行
Animal构造方法执行
Dog实例代码块执行
Dog构造代码块执行

结论:

1.父类静态代码块优先子类的,并且最早执行

2.父类实例代码块和构造代码块紧接依次执行,然后是子类的实例代码块和构造代码块

3.第二次实例化子类对象时,父类和子类的静态代码块都将不再执行,也就是静态的只执行一次

2.7 继承方式

继承方式:单继承,多层继承,不同类继承同一个类,注意,不支持多继承

2.8 final关键字

final可以修饰变量,方法 类

2.8.1 final修饰变量

被final修饰后的变量变为常量,不能再次修改一般会大写。如果成员变量被final修饰必须同时给定一个初始值。

例:

public class Test {
    public static void main(String[] args) {
        final int a=10;
        a=20;
    }
}

结果:

无法为最终变量a分配值

例:

public class Test {
    public static void main(String[] args) {
        final int[] array={1,2,3};//final修饰的是array这个引用所指向的对象
        //array=new int [10];//这句会报错,
        array[0]=10;//这句不会报错
    }
}

结果就是证明:被final修饰的array这个引用指向的对象不能改变,但是可以改变里面的值

在这里插入图片描述
array里面存储的是对象的地址,而不是对象中的内容,所以array被final修饰后,array里面的地址就不可以再变了,但是其中的内容可以变

2.8.2 修饰类

被final修饰后的类,表示此类不能被继承

2.8.3 修饰方法

被final修饰的方法,表示该方法不能被重写

2.9 继承与组合

继承表示对象之间的关系是is-a:如狗是动物
组合表示对象之间的关系是has-a:如汽车有发动机(发动机有自己的零件和功能),方向盘。汽车是由这些组成的。

例:

public class Student {

}
public class Teacher {

}
public class School {
    public Student[] students=new Student[3];
    public Teacher[] teachers=new Teacher[3];
}
//涉及到组合

以上代码所涉及的图:
在这里插入图片描述
每当new一个school对象,就会new一个student和teacher对象,它们在内存中是这样指向的。

3.多态

完成某个行为,不同的对象去完成会产生不同的效果。也就是,同一件事情,发生在不同对象身上,会产生不同的结果。

3.1 多态的实现条件

1.必须在继承体系下

2.子类必须对父类方法重写

3.通过父类的引用调用重写的方法

例:

public class Animal {
    String name;
    int age;
    public Animal(String name, int age){
        this.name=name;
        this.age=age;
    }
    public void eat(){
        System.out.println("吃饭");
    }
}
public class Cat extends Animal {
    public Cat(String name,int age){
        super(name,age);
    }
    public void eat(){//重写Animal中的eat方法
        System.out.println(name+"吃猫粮");
    }
}
public class Dog extends Animal {
    public Dog(String name,int age){
        super(name,age);
    }
    public void eat(){//重写Aniaml中的eat方法
        System.out.println(name+"吃狗粮");
    }
}
public class Teat {
    public static  void eat(Animal a){
        a.eat();
    }
    public static void main(String[] args) {
        Cat cat=new Cat("zuozuo",10);
        Dog dog=new Dog("huahua",18);
        eat(cat);//当a引用Cat对象时调用Cat中的eat方法
        eat(dog);//当a引用Dog对象时调用Dog中的eat方法
    }
}
//结果:
//zuozuo吃猫粮
//huahua吃狗粮

调用eat这个方法时,参数类型为Animal,此时并不知道a调用的是哪个子类的实例,此时a这个引用调用eat()方法可能会有多种表现,这就是多态

3.2 重写(Override)

重写,也称为覆盖。重写就是子类对父类非静态,非private修饰,非final修饰,非构造方法代码块进行重新编写返回值和参数列表不能改变。也就是核心重写,外形不变

3.2.1方法重写的规则

1.子类重写父类方法时,返回值参数列表还有方法名要一致

2.子类重写的方法访问权限不能比父类中被重写的方法访问权限更低(大于等于)。例如:如果父类被public修饰,则子类重写的方法不能被protected修饰

3.父类被static,private,final修饰的方法(密封方法),构造方法不能重写

4.重写的方法可以使用@Override注解来显式指定,这个注解可以帮我们进行合法性校验。如把重写的方法的名称写错,编译器就会发现没有这个方法,就会编译报错,提示无法构成重写。

3.2.2 静态和动态绑定

静态绑定(前期绑定或者早绑定):在编译时根据用户传递的实参类型确定了调用的是哪个方法。典型代表就是重载

动态绑定(动态绑定或者早绑定):在编译时不能确定方法的行为,要等运行时,才能确定具体调用的是哪个方法

例:

1.向上转型
2.重写
3.通过父类引用调用父类和子类重写的方法

3.3 向上转型和向下转型

3.3.1 向上转型

理论上,=左右两边的类型要一致,否则赋值会出错,但发生向上转型是可以让 = 左右两边类型不一致就可以进行赋值的。

就是创建一个子类对象,将其当作父类对象使用

语法格式:父类类型 对象名=new 子类类型()

例:Animal animal=new Dog("zuozuo",18);

animal是父类类型,但可以引用一个子类对象,因为是从Dog这个小范围类型向Animal这个大范围类型转换。猫和狗都是动物,因此将子类对象转化为父类引用是合理的,大范围可以包括小范围,是安全的。

注意:

当发生向上转型后,通过父类的引用只能访问父类自己的成员,不能访问到子类特有的。

3.3.2 向上转型的方式

1.直接赋值
2.方法传参
3.方法返回

例:

public class Teat {
    public static  void eat(Animal a){//方法传参:形参为父类引用,接收任意子类的对象
        a.eat();
    }
    public static Animal buyAnimal(String var){//返回值:返回任意子类对象
        if("狗".equals(var)){
            return new Dog("gougou",12);
        }else if("猫".equals(var)){
            return new Cat("maomao",18);
        }else{
            return null;
        }
    }
    public static void main(String[] args) {
        Animal cat=new Cat("zuozuo",19);//直接赋值
        Dog dog=new Dog("huahua",10);
        eat(dog);
        Animal animal=buyAnimal("狗");
        animal.eat();
        animal=buyAnimal("猫");
        animal.eat();
    }
}

结果:

huahua吃狗粮
gougou吃狗粮
maomao吃猫粮

优点:代码更简单灵活

缺点:不能调用子类特有的方法

3.3.3 向下转型

向下转型不太安全

例:

public class Teat {

    public static void main(String[] args) {
        Cat cat=new Cat("huahua",18);
        Dog dog=new Dog("zuozuo",19);
        //向上转型,将animal当作Animal对象来处理
        Animal animal=cat;
        animal.eat();
        animal=dog;
        animal.eat();
        //animal.bark();//编译失败,因为animal此时为Animal类型,父类中没有bark方法,父类不能访问子类特有的
        //向上转型,下面这两行代码可以通过编译,但运行时会抛异常,因为animal此时指向的是狗,无法还原为猫
        cat=(Cat)animal;
        cat.miaomiao();
        //animal本来指向的是狗,因此将animal还原为狗是安全的
        dog=(Dog)animal;
        dog.bark();
    }
}

结果:

huahua吃猫粮
zuozuo吃狗粮
zuozuo汪汪叫

为了提高向下转型的安全性,引入了instanceof.如果该表达式为true,则可以安全转换

例:

public class Teat {

    public static void main(String[] args) {
        Cat cat=new Cat("huahua",18);
        Dog dog=new Dog("zuozuo",19);
        //向上转型
        Animal animal=cat;
        animal.eat();
        animal=dog;
        animal.eat();
        if(animal instanceof Cat){
            cat=(Cat)animal;
            cat.miaomiao();
        }
        if(animal instanceof Dog){
            dog=(Dog)animal;
            dog.bark();
        }
    }
}

3.4 多态的优缺点

例:

public class Shape {
    public void draw(){
        System.out.println("画图形");
    }
}
public class Rect extends Shape{
    public void draw(){
        System.out.println("矩形");
    }
}
public class Circle extends Shape{
    public void draw(){
        System.out.println("⚪");
    }
}
public class Flower extends Shape{
    public void draw(){
        System.out.println("❀");
    }
}
public class Teat {
    public static void drawShape(Shape shape){
        shape.draw();
    }

    public static void main(String[] args) {
        Rect rect=new Rect();
        drawShape(rect);
        Circle circle=new Circle();
        drawShape(circle);
        Flower flower=new Flower();
        drawShape(flower);//或者这样写:drawShape(new Flower());
    }
}

结果:

矩形
⚪
❀

另一种写法:

public class Teat {
    public static void drawShape(){
        //创建一个Shape对象的数组
        Shape[] shapes={new Flower(),new Rect(),new Circle()};
        for(Shape s:shapes){
            s.draw();
        }
    }

    public static void main(String[] args) {
        drawShape();
    }
}

3.4.1 多态的好处

1.降低代码的圈复杂度避免使用大量if-else

圈复杂度:一段代码中条件语句和循环语句出现的个数

2.可扩展能力强

3.4.2 多态的缺点

1.属性没有多态性
2.构造方法没有多态性

3.5 避免在构造方法中调用重写的方法

例:B为父类,D是子类,D中重写func方法,并在B的构造方法中调用func

public class B {
    public B(){
        func();
    }
    public void func(){
        System.out.println("B.func");
    }
}
public class D extends B{
    private int num=1;
    public void func(){
        System.out.println("D.func"+" "+num);
    }
}
public class Test {
    public static void main(String[] args) {
        D d=new D();
    }
}

结果:

D.func 0

分析:

1.构造D对象之前,会先调用B的构造方法去构造B。
2.当在父类的构造方法中调用父类和子类重写的方法的时候,会触发动态绑定,会调用子类的。
3.D类型的对象自身还没有构造,num处于未初始化的状态,为0
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
清楚说明了java三大特性:   抽象:处理各种问题的第一步,把具体的问题与解决问题的方法结合起来,这就是抽象的过程.    Java常见的访问控制修饰符:    Public :到处都可以访问,不受包和父子类关系的限制    默认: 只在同包内可以访问,不受父子类关系限制    Protected: 不同包的子类和同包的类中访问,这是一种继承访问。    Private:只有自己类内部能访问,其他部分无论包内外,父子类都不能访问    Final:修饰属性:常量,值不能被修改     要么在声明时候赋值,要么在构造方法中赋值,两次机会只能选取一次。       修饰方法 :final方法子类不能将其重写         修饰类:该类不能有子类    Static:修饰属性:静态属性是类所有实例共有,可以用”类名.属性”的方式访问,对于数值型变量,系统会给定初始值,不强制要求赋初始值.     方法:可以用”类名.属性”的方式访问,方法也为类的所有实例所共有     代码块 : 加载的优先级最高,甚至比主方法的加载还要早.    对于由final static共同修饰的变量,赋值的机会有两个,一是在声明的时候,二是在静态代码块中,两次机会有且只能有一次被选择.     继承:    允许存在父子类的关系,一个子类有且只有一个直接的父类(Java中的继承是类之间的单继承.) 当在内存形成类的信息绑定的时候不会出现多个交叉点,结构的清晰程度较高.Java本身的运行效率较低,只能达到C语言的五分之一.Java语言舍弃效率的完美要求,换来学习和使用的简单. 属性的遮盖: Father f = new Son(); 属性访问到的是父类的,方法访问到的是子类的. 方法的重载 :在类的内部一种方法出现多种表达方式,如参数表的不同,构造方法的重载 方法的重写:发生在父子类之间,子类方法对父类的方法有新的实现方法,但两个方法定义部分完全一致. 继承的目的: 实现代码的复用.理顺类型对象之间的结构关系. 构建一个父类的时候,要求他的子类与父类必须有理论上的确实的继承关系 代码复用的方法:    继承复用,完全的将父类非私有部分作为子类的内容,若需变化,子类只要重写父类的同名方法即可。 组合复用:选取某个类型中的一个对象的方法实现作为该类的方法实现过程,这两个类也称为理论父类和理论子类. 程序中是不出现extends关键字对两个类关系的描述的.    通过具体实例抽象出父类的过程称为泛化    通过父类来确认子类的过程称为特化.    在代码的类之间关系的设计阶段,理论上要求父子类之间没有相同的同名方法.但语法允许子类重写父类的方法.要求子类当中只有自己特有的方法和属性,其他部分与父类和其他同级子类相同的部分都从父类继承得来.    有关抽象类:    抽象类是用于描述自然界中层级较高的对象类,人们无法对一个抽象类实例化,如动物。只能用一个动物类的子类对抽象类的对象进行实例化。    抽象修饰符abstract 可以修饰类,修饰方法。    抽象类中可以定义非抽象的方法,但如果一个类中有抽象方法,则这个类必须被定义成抽象类。抽象类中有构造方法,但是没有抽象的构造方法。构造方法的存在完全是为了继承关系而存在。    与之形成对比的就是接口,接口中的所有方法要求定义成公开抽象方法,因为这些方法所表示的就是标准,标准的信息必须做到公开化,透明化,例如体育竞赛中的规则。    抽象类与接口之间的对比:接口与抽象类本质上是两个概念,但可以将接口看作是一个抽象层级比抽象类更高的特殊类。由于接口的信息中只有已知不可变值以及抽象方法,所以信息的绑定方式单一,从实现上可以满足继承但也不会出现网状的信息绑定效果,不会增加访问时的节点数。因此接口允许多继承。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值