面向对象(类与对象)

1.类与对象

1.1 什么是对象

对象:是一个一个实实在在的,具体的个体(实体)。比如:一个个人、一辆辆车、一本本书等等。

1.2 什么是类

类:一组具有相同特征的对象的抽象描述

如:

张三,510220199612013669,18256958568,158936587.... 代表一个对象

李四,510220199612013667,18256958575,158936787.... 代表一个对象

姓名、身份证号码、手机号码、qq号码..... 对应一个类

此时就可以将这些对象抽取相同的特征(属性)封装为一个类

public class Person {
    String name;//名称
    String idCard;//身份证
    String phone;//手机
    String qq;//qq号
}

通俗而言:类就相当于施工图,我们可以根据施工图来知道一栋楼每套房子具有那些功能区(共同的特质),进而造出楼房。

在Java中根据施工图创建对象的关键字是 new。

从代码编写的角度而言,必须先创建类,才能创建出对象。

1.3 什么是面向对象

面向对象是一种思想或编程方式。

面向对象的过程是以“对象”为中心。

Scanner input = new Scanner(System.in);//input就是一个Scanner类的对象

System.out.print("请输入姓名:");
String name = input.next();

System.out.print("请输入年龄:");
int age = input.nextInt();

input.close();

与面向对象对应的另一种编程思想:面向过程。它是以函数为中心,实现代码就是通过函数调用把过程串起来。(c语言是面向过程编程)

public class ArrayTools{
    public static int max(int[] arr){
        int max = arr[0];
        for(int i=1; i<arr.length; i++){
            if(arr[i] > max){
                max = arr[i];
            }
        }
        return max;
    }
    
    public static void print(int[] arr){
        for(int i=0; i<arr.length; i++){
            System.out.print(arr[i] + "  ");
        }
        System.out.println();
    }
    
    public static void main(String[] args){
        int[] arr = {2,4,5,3,1};
        print(arr);
        int result = max(arr);
        System.out.println("最大值:" + result);
    }
}

这一段代码,如果抛开ArrayTools类的定义,那么3个函数是独立的,它们互相调用即可,这种方式就是面向过程的编程思想。

另外一个场景描述,来理解面向对象与面向过程的区别:

把大象装进冰箱的问题?

面向过程面向对象
以步骤为中心,即以函数为中心以类/对象为中心
(1)把冰箱门打开 (2)把大象装进去 (3)把冰箱门关上(1)定义人这个类,包含pull,push功能,包含name属性 (2)定义冰箱这个类,包含open,close,save功能,包含size、brand等属性 (3)定义大象这个类,包含walk功能,包含weight属性 (4)定义测试类,包含main方法,在main方法中创建人、冰箱、大象的对象,调用对象的方法来完成

 1.4 如何定义类

语法格式:

【①修饰符】 ②class ③类名{
    ④类的成员
}

如:

public class Person{
    String name; //类的成员
}

 1.5 如何new对象

语法格式:

new 类名()

 如果没有用变量引用对象,这个对象是一个“匿名对象”。匿名对象都是一次性,无法反复使用

public class Test {
    public static void main(String[] args) {
        new Person();//创建了一个Person类的对象
        System.out.println(new Person());//创建一个Person类的对象,并且打印这个对象
        //com.yang.dto.Person@3b07d329
        System.out.println(new Person());//又创建一个Person类的对象,并且打印这个对象
        //com.yang.dto.Person@41629346
        //上述三个对象是匿名对象,都是独立的,无法重复使用的

    }
}

 如何用变量引用一个对象?

类名 对象名 = new 类名();

 对象名本质上也是一个变量,是一个引用数据类型的变量。

public class Test {
    public static void main(String[] args) {
        Person p = new Person();
        System.out.println(p);
        System.out.println(p);
        System.out.println(p);
        //p这个对象可以被反复使用
        /*
        这里的p是对象名,同时也变量名

        Java的数据类型:
        (1)基本数据类型:byte,short,int,long,float,double,char,boolean
           int a = 1;
        (2)引用数据类型:数组、类等
            int[] arr = new int[5];
            Person p = new Person();

            int[],Person是数据类型,从这个角度来说,arr, p也是变量。
            因为这种变量引用的是一个对象,所以这个变量称为引用数据类型的变量,也称为对象名。

         */
    }
}

2.类的成员之一:成员变量

2.1 如何定义成员变量

【修饰符】 class 类名{
    【①修饰符】 数据类型  成员变量名;
}

【】表示可选
public class Person {
    public static String star;//星球   成员变量,因为它有static修饰,它就是静态变量
    public String name;//名称          成员变量,因为它没有static修饰,它就是非静态成员变量,简称实例变量
    public String idCard;//身份证      成员变量,因为它没有static修饰,它就是非静态成员变量,简称实例变量
    public String phone;//手机        成员变量,因为它没有static修饰,它就是非静态成员变量,简称实例变量
    public String qq;//qq号          成员变量,因为它没有static修饰,它就是非静态成员变量,简称实例变量
}

成员变量的作用是描述事物的属性和数据特征。

2.2 如何使用成员变量

2.2.1 跨类使用成员变量

对象名.实例变量

类名.静态变量

虽然静态变量也可以通过 “对象.静态变量”的方式访问,但是我们更推荐“类名.静态变量”

2.2.1 本类使用成员变量

 2.3 成员变量的特点

  • 实例变量的值是每一个对象都是独立的

  • 静态变量的值是整个类共享的,无论是哪个对象,或类修改了它,那么都是统一修改的,因为它只有1份。

  • 成员变量有默认值

数据类型默认值
byte0
short0
int0
long0L
float0.0F
double0.0
char\u0000 编码值为0的空字符
booleanfalse
引用数据类型null

2.4 变量的内存分析图

又上图可知:

每一个对象实例都是独立的,都有各自的内存空间,放在堆中。

一个类的静态变量是共享的,只有一份内存空间,放在方法区中。

当Person类 被类加载器加载后,就会立刻在方法区中开辟一个空间存放静态变量。如果没有给静态变量赋值,那么就会赋上默认值。

当new对象的时候,就会在堆中开辟一个空间存放对象的实例变量(一个对象一个空间)

当方法被调用入栈时,就会在栈中开辟空间,存放局部变量

Person的过程:

① 类加载器

② 给Person的静态变量在方法区中分配空间

③  调用main方法,main方法入栈

(1)给Person.start变量赋值

(2)创建对象,p1在堆中开辟空间,存放p1的实例变量值(默认值) 

(3)创建对象,p2在堆中开辟空间,存放p2的实例变量值(默认值)    

(4)给p1.name   p2.name赋值

注意:因为main方法比较特殊,main方法执行完毕代表程序运行完毕,此时整个JVM虚拟机也会退出。

2.4 静态变量和实例变量对比

实例变量静态变量
跨类访问方式对象名.实例变量类名.静态变量
它的值是共享的还是独立每一个对象独立的整个类共享的
是否有默认值
内存位置方法区

 2.4 引用数据类型的成员变量

总结:

  • 所有引用数据类型的成员变量,默认值是null

  • 如果没有给引用数据类型的成员变量赋值对应的对象,那么通过它. 成员变量等都会发生 NullPointerException(空指针异常)

3 类的成员之二:成员方法

3.1 方法的分类

根据方法的修饰符是否有static修饰,分为:

3.1.1 静态方法

总结:

  • 本类调用:直接用

  • 跨类调用:类名.静态方法

3.1.2 非静态方法(实例方法)

总结:

  • 本类使用:直接使用

  • 跨类调用:对象名.实例方法

3.2 成员方法使用本类的成员变量

总结:本类的静态方法,不能直接使用 本类的实例变量 或 实例方法。

访问方式是否允许
本类的静态方法 访问 本类的静态变量、静态方法可以
本类的静态方法 访问 本类的实例变量、实例方法×
本类的实例方法 访问 本类的实例变量、实例方法可以
本类的实例方法 访问 本类的静态变量、静态方法可以

4.继承

4.1 什么是继承

java中继承的意义是实现代码的复用,扩展以及事物之间is-a的关系。

4.2 如何实现继承

语法格式:

【修饰符】 class  子类名 extends 父类名{
    //子类的成员
}
public class Person { //父类
    public String name;
    public int age;
    public String getInfo(){
        return "姓名:" + name +",年龄:" + age;
    }

    public void eat(){
        System.out.println(name +"在吃东西");
    }
}


//Student是子类
public class Student extends Person{
    public int score;
    public String getInfo(){
        return super.getInfo() +",成绩:" + score;
        //super.getInfo()是复用父类的方法
    }

    public void exam(){
        System.out.println(name+"在参加考试");
    }
}


public class TestStudent { //测试类
    public static void main(String[] args) {
        Student s = new Student();
        s.name = "张三"; //从父类继承的属性声明
        s.age = 23;//从父类继承的属性声明
        s.score = 89;//Student子类自己扩展的属性
        System.out.println(s.getInfo());
        s.eat();//从父类继承的方法声明
        s.exam();//子类自己扩展的方法
    }
}

 4.3 继承的特点和要求

4.3.1 单继承

Java中一个子类有且只能有1个直接父类。比喻:只有1个亲生父亲。

【修饰符】 class  子类名 extends 父类名{ //即extends后面只能写一个父类名,不能同时写多个父类名
    //子类的成员
}

4.3.2 多层继承

Java中一个父类,还可以有它的父类。类的成员遵循代代相传的原则。比喻:代代相传,香火延续。  

 4.3.3 继承属性和方法

父类的所有成员变量和成员方法代表的事物特征都会继承到子类中。但是,父类中私有的成员变量和成员方法在子类中是无法直接使用的,需要间接使用。

public class Person {
    private String name; //私有化
    public int age;
    
    public String getName(){
        return name;
    }
}


public class Student extends Person{
    public void exam(){
        //System.out.println(name +"现在" + age + "岁,正在参加考试"); 
        //上面的语句报错,父类name加private就不能在子类中直接使用了
        System.out.println(getName() +"现在" + age + "岁,正在参加考试");
        //子类可以通过getName()方法间接使用name。age没有加private,子类就可以直接使用
    }
}

 4.3.4 不会继承父类的构造器

子类不会直接继承父类的构造器,子类的构造器首行必须调用父类的构造器。调用父类构造器的目的是为了借用父类构造器的代码为从父类继承的成员变量进行初始化赋值。

  • 默认情况下,会调用父类的无参构造。

  • 可以手动调用父类的有参构造。

此时就需要两句代码:

  • super() :调用父类的无参构造。这句代码可以省略。

  • super(实参列表):明确调用父类的有参构造。这句代码不可以省略。

  • 它们必须在子类构造器的首行。

情况一:不能借调父类构造器的情况
public class Person {
    private String name;
    public int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getInfo(){
        return "姓名:" + name +",年龄:" + age;
    }
}


public class Student extends Person{
    public int score;
    
    public String getInfo(){
        return super.getInfo() +",成绩:" + score;
        //super.getInfo()是复用父类的方法
    }
}


public class TestStudent3 {
    public static void main(String[] args) {
        Student s1 = new Student();
        //因为Student类没有编写任何构造器,编译器自动添加了无参构造
        Student s2 = new Student("王五",25);//报错
        //Student类没有写有参构造器,编译器不会自动添加,也不会从父类继承有参构造
    }
}
情况二:调用父类构造器的情况
public class Person {
    private String name;
    public int age;

    public Person() {
    }

    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 String getInfo(){
        return "姓名:" + name +",年龄:" + age;
    }

    public void eat(){
        System.out.println(name +"在吃东西");
    }
}



public class Student extends Person{
    public int score;

    public Student() {
        super();//调用父类无参构造,可以省略
    }

    public Student(String name, int age, int score) {
        super(name, age);//调用父类的有参构造
        this.score = score;
    }

    public String getInfo(){
        return super.getInfo() +",成绩:" + score;
        //super.getInfo()是复用父类的方法
    }

    public void exam(){
        //System.out.println(name +"现在" + age + "岁,正在参加考试");
        //上面的语句报错,父类name加private就不能在子类中直接使用了
        System.out.println(getName() +"现在" + age + "岁,正在参加考试");
        //子类可以通过getName()方法间接使用name。age没有加private,子类就可以直接使用
    }
}


public class TestStudent4 {
    public static void main(String[] args) {
        Student s1 = new Student();
        Student s2 = new Student("王五",25,100);
    }
}

4.4 子类可以重写父类的方法

4.4.1 重写

当子类从父类继承了一个方法,但是该方法的实现(即方法体)不完全适用于子类,那么子类就可以对其进行重写。

public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getInfo(){
        return "姓名:" + name +",年龄:" + age;
    }
}



public class Student extends Person{
    public int score;
    
        public Student() {
        super();//调用父类无参构造,可以省略
    }

    public Student(String name, int age, int score) {
        super(name, age);//调用父类的有参构造
        this.score = score;
    }
    
    //此时去掉@Override,getInfo方法也是重写
    /*
    加@Override的意义:
    (1)可读性更好。程序员一看到它,就知道这是一个重写的方法
    (2)可以让编译器,对getInfo()方法做严格的格式检查,看它有没有违反重写的要求。
       如果你没有违反重写的要求,那么不加@Override也没有影响。
    */
    @Override //标记当前的getInfo方法是重写父类的getInfo方法,不是子类新增的方法
    public String getInfo(){
        return super.getInfo() +",成绩:" + score;
        //super.getInfo()是复用父类的方法
    }
}

 4.4.2 方法重载和重写的区别

重载(Overload)重写(Override)
位置同一个类中或重写的方法是在子类中,被重写的方法在父类中
①权限修饰符不看(1)被重写方法不能是private (2)重写方法的权限修饰符必须 >= 被重写方法的权限修饰符
①其他修饰符不看被重写方法不能是static
②返回值类型不看(1)当返回值类型是void或8种基本数据类型,那么重写方法的返回值类型必须与被重写方法的返回值类型完全一致。 (2)当返回值类型是引用数据类型,那么重写方法的返回值类型必须 <= 被重写方法的返回值类型
③方法名必须相同必须相同
④形参列表必须不同必须相同(只看类型、个数、顺序,不看形参名)

 

4.4.3 Object类toString的重写

Java的所有类都直接或间接的继承java.lang.Object类。Object类中有11个方法,这11个方法会被继承到所有子类中。其中非常好用,常用的一个方法就是toString方法。

 

  • 这个方法比较特殊,当我们打印对象时,或者对象与字符串拼接时,都会自动调用对象的toString方法。

  • 建议所有子类都重写toString方法。否则默认返回 类名 @ 对象哈希值的无符号十六进制值。

  • 可以通过快捷键自动生成toString方法,快捷键是Alt + Insert。

public class Animal {
    private String name;
    private double weight;

/*    public String toString(){ //手动重写
        return "名称:" + name  +",体重:" +weight;
    }*/

    @Override
    public String toString() {//快捷键生成
        return "Animal{" +
                "name='" + name + '\'' +
                ", weight=" + weight +
                '}';
    }
}



public class TestAnimal {
    public static void main(String[] args) {
        Animal a = new Animal();
        System.out.println(a);//自动调用a的toString方法
        System.out.println(a.toString());
        /*
        如果没有重写,打印的是如下信息:
        com.yang.inherited.Animal@4eec7777
        类名 @ 对象的hash码的无符号十六进制形式
         */
        System.out.println(a.hashCode());
        //1324119927(十进制) -> 4eec7777(十六进制)
        
    }
}

 4.4.4 super关键字

super:引用父类的xxx

常见的用法有3种:

(1)super.方法(【实参列表】)

想要在子类中调用父类被重写方法时,必须加 "super."。

(2)super() 或 super(实参列表)

  • super():明确调用父类的无参构造。这句可以省略。

  • super(实参列表):明确调用父类的有参构造

  • 它们都必须在子类构造器的首行

(3)super.实例变量

当子类声明与父类同名的实例变量时,如果两个实例变量都可见,那么在子类中可以通过“super.实例变量”来访问父类的实例变量。

但是,不推荐父子类声明同名的实例变量。

thissuper
意思当前对象父类声明的xx
调用方法this.实例方法 完全可以省略this.super.实例方法 不可以省略super.,除非你没重写该方法。
使用成员变量this.成员变量 成员变量与局部变量重名,用它。super.成员变量 父子类成员变量重名,用它。
调用构造器this() 或 this(实参列表) 调用本类的构造器super() 或 super(实参列表) 调用父类的构造器

 说明:this() 、 this(实参列表)、 super() 、super(实参列表) 这四句不能同时出现在一个构造器中。只能四选一。如果四句都没写,默认就是super()。

5.抽象类

5.1 抽象类是什么

父类是代表众多子类的共同特征,父类中的成员变量(即属性)、成员方法(即功能)是所有子类都有的。

当事物的分类层次变多之后,越上层的父类成员越少,越上层的父类会变的越来越抽象。甚至出现在父类中某个方法无法给出适用于所有子类的方法体实现。

例如:定义图形Graphic,所有图形都有求面积的方法area(),但是所有的子类:Rectangle矩形、Circle圆形、Triangle三角形等,它们的求面积公式都不相同,所以在父类图形Graphic中,area()没法给出具体的代码,因为没有一个通用的公式。

通常这样的方法,会被定义为抽象方法。抽象的关键字:abstract

注意:Java中规定,凡是包含抽象方法的类,必须是抽象类。

【①其他修饰符】 abstract ②返回值类型 ③方法名(【④形参列表】);   //没有方法体,  它是抽象方法

【其他修饰符】 abstract class 类名{ //抽象类
    
}

public abstract class Graphic {
    public abstract double area();
}

5.2 抽象类的特点 

① 抽象类不能直接new对象,即不能实例化

② 抽象类是用来继承的,一旦被子类继承就要重新父类的抽象方法

③ 抽象类也有构造器(抽象类的构造器不是给自己用的,而是给子孙类使用的)

public abstract class Animal {
    private String name;
    private double weight;

    public Animal() {
    }

    public Animal(String name, double weight) {
        this.name = name;
        this.weight = weight;
    }

    public abstract void eat();
    public abstract void sleep();
}




public class Dog extends Animal{

    @Override
    public void eat() {
        System.out.println("啃骨头");
    }

    @Override
    public void sleep() {
        System.out.println("趴着睡");
    }
}

6.接口

6.1 什么是接口

需求:

  • 定义一个Animal抽象父类,包含抽象方法eat

  • 定义一个非抽象的子类Bird,继承Animal,重写抽象方法eat,新增一个fly()

  • 定义一个非抽象的子类Fish,继承Animal,重写抽象方法eat,新增一个swim()

  • 定义一个非抽象的类Plane(飞机),没有继承Animal,也有一个方法fly()

问题1:Bird与Plane有相同的功能方法,fly方法,它应该被抽取它们的共同的父类中,但是Bird已经有一个直接父类Animal了,不能再继承另一个父类了?

原因:Java的类有单继承的限制。

解决:突破单继承的限制。需要引入接口的概念,来解决单继承的限制问题。

问题2:类与类之间需要是is-a的关系。例如Student is a Person。当某些类虽然有共同的方法,但是它们不适用用一个公共的父类来作为它们的总的分类。

原因:父类与子类应该是逻辑上属于一个事物类别,只是子类是更加具体的一个子类别。例如:学生是人这个大类别中的一个子类别,狗、鸟是动物这个大类别中的子类别。但是鸟、飞机、热气球、风筝等很难找到一个公共的事物分类。

解决:需要引入接口的概念,来突破is-a的关系。

接口更像是一种标准。一个接口中通常会定义符合这个标准的事物应该具备什么功能。例如:USB接口应该具备供电、传输数据等。

6.2 如何定义接口

【修饰符】 interface 接口名{
    //成员
}

6.3 接口的特点和要求

① 接口是抽象的不能直接new对象

② 接口是用来被实现的,实现接口的类必须重写接口中的抽象方法

【修饰符】 class 实现类名 implements 接口们{//接口们表示可以是多个接口
    
}

 当实现类实现(implements)一个接口后,就必须重写这个接口的所有的抽象方法,否则这个类就必须是抽象类。

比喻:直接父类比喻成亲生父亲。接口比喻成干爹。

③ 接口中的成员和类中的成员有什么不同

(1)接口没有构造器

(2)接口中只允许有 public static final 的常量,不能有普通的成员变量。其中public static final是可以省略。

(3)JDK8之前,接口中只允许有public abstract 的抽象方法。此时public abstract可以省略。

(4)JDK8版本,接口中开始允许有public default 的默认方法。此时public可以省略。default在接口中不能省略。但是在实现类中default必须去掉。

随着JDK的版本的更新,新版本中需要在旧的接口中增加一些方法,但是按照早期的规则,只能增加抽象方法,此时就会导致它之前所有的实现类都报错,因为都需要重新实现这个新的抽象方法,这样的问题就太大了。

为了解决这个问题,接口才引入的默认方法。

默认方法与抽象方法的区别:

  • 默认方法有{方法体},而抽象方法没有

  • 默认方法实现类可以选择重写,也可以选择不重写。但是抽象方法实现类必须重写。

(5)JDK8版本,接口中开始允许有public static的静态方法。此时public可以省略。static不可以省略,而且静态方法方法不能被重写。

因为静态方法的调用,根本不需要创建类或接口的对象,因此接口中引入静态方法完全符合语法。引入静态方法反而会让接口更强大。

(6)JDK9版本,接口中又引入了私有的方法。

这个私有方法的语法,是为了解决接口中多个默认方法,或多个静态方法存在相同代码的问题。

注意:私有方法不能是default,abstract,但是可以是static。

public interface Swimming {
//    public static final int MAX_SPEED = 100;
    int MAX_SPEED = 100;//省略public static final

//    public abstract void swim();
    void swim();//省略public abstract 省略抽象方法

    default   void run(){//省略public 默认方法
        common();
    }

    static void sleep(){//省略public  静态方法
        System.out.println("静态方法");
    }

    default   void run2(){//public
        common();
    }

    default   void run3(){//public
        common();
    }

    private void common(){ //私有方法
        for(int i=1; i<=5; i++){
            System.out.println("默认方法");
        }
    }
}

④ 接口支持多继承 (接口与接口之间才支持多继承。)

类与类之间是单继承。
接口与接口之间是多继承。
类与接口之间是多实现。


public interface A {
    void a();
}


public interface B {
    void b();
}


public interface C {
    void c();
}


public interface D extends A,B,C{
}


public class E implements D{
    @Override
    public void a() {

    }

    @Override
    public void b() {

    }

    @Override
    public void c() {

    }
}

 6.4 接口与抽象类的一些常规题

6.4.1 接口与抽象类的区别

  • 抽象类有单继承的限制,而接口没有。

  • 接口的成员有很多限制,抽象类的成员很全面。

6.4.2 冲突问题 

① 变量冲突
public class Father {
    public int a = 1;
}


public interface DryFather {
    //默认是public static final
    int a = 2;
}


public class Son extends Father implements DryFather{
    public void method(){
//        System.out.println("a = " + a );//错误,因为此时父类和实现的接口中都有a变量不能找到这儿具体指的是哪一个
        System.out.println("父类的a = " + super.a);
        System.out.println("父接口的a = " + DryFather.a);
    }
}

7.多态

7.1 什么是多态

在Java中多态的含义:一个对象的方法有多种形态(即多种声明方式)

  • 方法的重载:编译时多态

  • 方法的重写:运行时多态

运行时多态的好处:可以实现方法的动态绑定,即给父类变量赋值不同的子类对象,可以动态的执行子类各自重写的方法。

7.2 运行时多态

7.2.1 多态引用语法

父类类型  变量名 = 子类对象;

7.2.2 多态引用时调用方法有一个原则:

编译看父类,运行看子类。

  • 只要子类重写了所调用的方法,就执行子类重写的方法体。

  • 如果子类没有重写所调用的方法,仍然去父类找方法执行。

public class Animal {
    public void eat(){
        System.out.println("~~~~");
    }
    public void eat(String food){
        System.out.println("吃:" + food);
    }
}
public class Dog extends Animal{
    @Override
    public void eat() {
        System.out.println("啃骨头");
    }

    public void watchHouse(){
        System.out.println("看家");
    }
}
public class TestDog {
    public static void main(String[] args) {
        Animal obj = new Dog();
        //obj编译时看左边,obj编译时看的是Animal类型中的方法
        //obj运行时看右边,obj运行时看的是Dog类型中的方法
        obj.eat(); //执行Dog重写的eat()方法
        obj.eat("杂食");
        //执行先找Dog类是否重写了eat(String food),如果重写了,就会执行Dog类重写的eat(String food)
        //如果没有重写,仍然执行父类继承的eat(String food)
//        obj.watchHouse();
        //编译报错,因为编译看左边,Animal类中没有watchHouse方法
    }
}

7.3 运行时多态的应用

7.3.1 多态数组

可以用一个父类/父接口类型的数组来管理一组子类的对象,统一管理它们相同的行为特征。

数组的类型是父类[],实际存储的是子类对象。

例1:

public class Animal {
    public void eat(){
        System.out.println("~~~~");
    }
    public void eat(String food){
        System.out.println("吃:" + food);
    }
}
public class Bird extends Animal{
    @Override
    public void eat() {
        System.out.println("吃虫子");
    }

    public void fly(){
        System.out.println("飞的更高");
    }
}
public class Dog extends Animal{
    @Override
    public void eat() {
        System.out.println("啃骨头");
    }

    public void watchHouse(){
        System.out.println("看家");
    }
}
public class Cat extends Animal{
    @Override
    public void eat() {
        System.out.println("吃老鼠");
    }

    public void catchMouse(){
        System.out.println("抓老鼠");
    }
}
public class TestAnimals {
    public static void main(String[] args) {
        //现在有一组动物对象,它们分别是Dog、Cat、Bird类的对象
        Animal[] animals = new Animal[3];
        animals[0] = new Dog();
        animals[1] = new Cat();
        animals[2] = new Bird();
        //声明:animals[0]、animals[1]、animals[2]元素的类型是Animal类型
        //赋值:animals[0]、animals[1]、animals[2]分别赋值Animal类的子类对象
        //变相的多态引用   Animal animals[0] = new Dog();

        
        for (Animal animal : animals) {
            animal.eat();
            //编译时看左边,或看父类, animal此时只能.出Animal类中声明的eat()和eat(String food)
            //运行时看右边,或看子类, animal.eat()实际执行的都是子类重写的eat()
        }
    }
}

7.3.2 多态参数

方法的形参是父类类型,实参是子类对象。

public class TestAnimals2 {
    public static void main(String[] args) {
        look(new Cat());
        /*
        look方法的形参(Animal animal)
        look方法的实参(new Cat())
        变相的多态引用     Animal animal = new Cat();
         */


        Dog d = new Dog();
        look(d);//有名字的对象作为实参
        look(new Bird());

    }

    //定义一个方法,可以看各种动物吃东西的行为
    public static void look(Animal animal){
        animal.eat();
    }
/*    public static void look(Dog dog){
        //System.out.println("看狗的方法:");
        dog.eat();
    }
    public static void look(Cat cat){
       // System.out.println("看猫的方法:");
        cat.eat();
    }
    public static void look(Bird bird){
       // System.out.println("看鸟的方法:");
        bird.eat();
    }*/
}

7.3.3 多态返回值

方法的返回值类型是父类类型,实际返回的是子类对象。

public class TestAnimals3 {
    public static void main(String[] args) {
        Animal dog = buy("狗");
        //因为这个方法实际返回的是new Dog()
        //变相的多态引用    Animal dog =  new Dog();
        dog.eat();
    }

    public static Animal buy(String type){
        switch (type){
            case "猫": return new Cat();
            case "狗": return new Dog();
            case "鸟": return new Bird();
        }
        return null;
    }

    /*public static Dog buyDog(){
        return new Dog();//直接返回匿名对象
    }

    public static Cat buyCat(){
        Cat c = new Cat();
        return c;//直接返回有名对象
    }

    public static Bird buyBird(){
        return new Bird();//直接返回匿名对象
    }*/

}

7.4 向上转型与向下转型

之前基本数据类型:

自动类型转换:

char c = 'a';
double d = c;  //这里是把c变量中的'a'的编码值copy一份,然后再自动提升为double类型

强制类型转换:

double d = 97.5;
char c = (char)d;

向上和向下转型是指父类和子类之间类型的转换

向上转型:让子类对象在编译时以父类的类型出现。一旦向上转型后,就会失去子类自身的特有方法,只能调用父类中有的方法。

向下转型:让一个父类对象在编译的时候以子类的类型出现。

注意:①在向下类型转型的时候有风险,可能出现ClassCastException(类型转换异常)。

          ②除非父类中变量引用的就是当前子类的对象,否则就会发生ClassCastException。

          ③为了安全起见,向上转型之前最好用 instanceof判断。用于判断某个变量中是否是某个类型的对象。

无论是向上转型,还是向下转型,对象的本质类型(运行时类型)从头到尾都没有变过,就是你new的类型。

public class TestAnimals2 {
    public static void main(String[] args) {
        //用一个数组管理一组动物对象
        Animal[] animals = new Animal[3];
        animals[0] = new Dog();
        animals[1] = new Cat();
        animals[2] = new Bird();

        for (Animal animal : animals) {
            animal.eat();

            //这里不仅想要调用 它们公共的 eat(),
            // 还想要调用Dog的watchHouse(),
            // Cat的catchMouse(),
            // Bird的fly()
            if(animal instanceof Dog) {
                Dog d = (Dog) animal;
                d.watchHouse();
            }else if(animal instanceof Cat){
                Cat c = (Cat) animal;
                c.catchMouse();
            }else if(animal instanceof Bird){
                Bird b = (Bird) animal;
                b.fly();
            }
        }
    }
}

8.内部类

8.1 什么是内部类

内部类是指在一个类的内部中定义另一个类,外面的类称为外部类,里面的类称为内部类。

public class Outer{ //外部类
    public class Inner{ //内部类
        
    }
}

8.2 内部类有多少种

有四种内部类形式:

  • 静态内部类(源码中可以看到):定义在类的内部作为成员的类,它可以访问外部类的所有成员。

  • 非静态内部类(源码中可以看到):用 static 关键字修饰的内部类,它与外部类的实例无关,可以直接通过类名访问。

  • 局部内部类(几乎没用):定义在方法内部的类,只能在方法内部使用,并且可以访问方法内的局部变量(但必须是 final 或等效于 final 的)。

  • 匿名内部类(经常使用):没有显式名称的内部类,通常用于创建一次性的对象。

①   根据声明的位置不同,可以分为成员内部类和局部内部类。

public class Outer{
    
    public class InnerOne{ //成员内部类
        
    }
    
    public void method(){ //方法
        class InnerTwo{ //局部内部类
            
        }
    }
}

②  成员内部类根据有没有static,可以分为静态内部类和非静态内部类

public class Outer{
    
    public class InnerOne{ //成员内部类,无static修饰,称为非静态的成员内部类,简称非静态内部类
        
    }
    
    public static class InnerTwo{//成员内部类,有static修饰,称为静态的成员内部类,简称静态内部类
        
    }
}

③  局部内部类根据没有名字,可以分为局部内部类和匿名内部类

public class Outer{
       
    public void method(){ //方法
        class InnerTwo{ //局部内部类,因为有名字的局部内部类,简称局部内部类
            
        }
        
        new Object(){ //匿名内部类
            //.....
        };
    }
}

8.3 匿名内部类

1、因为匿名内部类没有名字,所以声明这个类的同时就必须把它唯一的对象给创建好,所以声明它的时候会带着new

2、每一个匿名内部类都有自己独立的字节码文件。

3、匿名内部类中可以使用外部类的所有成员,包括私有的。

  • 如果匿名内部类在静态方法中,不可以使用外部类的非静态成员,因为是静态方法不允许。

  • 如果匿名内部类在非静态方法中,可以使用外部类的所有成员,包括静态的,非静态的。

4、匿名内部类还可以使用外部类当前方法的局部变量,但是该局部变量是final的。如果是JD8之前,必须手动加final,JDK8之后是自动加final。

语法格式:

new 父类名(){
    //定义类的成员
}


new 父类名(实参列表){
    //定义类的成员
}


new 父接口(){
    //定义类的成员
}

//接口是没有构造器的,这里()代表的是调用Object类的无参构造。

 案例:

public class TestInner {
    private static int numA = 1;//静态变量
    private int numB = 2;//实例变量

    public static void main(String[] args) {
        //下面创建的不是Father抽象类本身的对象,而是Father的匿名子类对象
       final int numC = 3;//局部变量    final修饰变量的意义,numC变量就不能修改值
        //JDK8之前,numC局部变量被匿名内部类使用之后,就必须手动加final声明
        //JDK8之后,numC局部变量被匿名内部类使用之后,就会自动加final声明,final可以省略

        //多态引用
        Father f1 = new Father(){
            @Override
            public void say() {
                System.out.println("我是Father类的匿名子类1的say方法");
                System.out.println("numA = " + numA);
//                System.out.println("numB = " + numB);//错误,原因是main方法不能使用非静态的numB
                System.out.println("numC = " + numC);
            }
        };
        f1.say();

    }
}

8.4 局部内部类

当某个匿名内部类,需要定义自己的类成员,而不是重写父类或父接口的方法。或者某个匿名内部类需要有多个对象时,才需要用局部内部类。

案例:

public class TestInner3 {
    public static void main(String[] args) {
        /*new Father("哈哈哈"){
            @Override
            public void say() {
                System.out.println("哈哈哈");
            }
        };

        new Father("hhh"){
            @Override
            public void say() {
                System.out.println("哈哈哈");
            }
        };*///上面的写法,会导致有两个匿名内部类

        class Son extends Father{
            public Son(String info) {
                super(info);
            }

            @Override
            public void say() {
                System.out.println("哈哈哈");
            }
        }
        Son s1 = new Son("哈哈哈");
        Son s2 = new Son("hhh");
        s1.say();
        s2.say();
        method();
    }

    public static void method(){
        class Son{

        }
    }
}

8.5  静态内部类和非静态内部类

两个角度来看:

  • 它也是类

  • 它又是成员

静态内部类非静态内部类
该用静态内部类还是非静态内部类呢?不需要在内部类中使用外部类的非静态成员,就用它需要在内部类中使用外部类的非静态成员,就用它
声明位置外部类的方法外外部类的方法外
有没有static
权限修饰符public、protected、缺省、privatepublic、protected、缺省、private
是否可以继承父类可以可以
是否可以实现父接口可以可以
是否可以有自己类成员呢可以可以。(它里面声明静态成员是从JDK16开始)
是否有自己的字节码文件?
是否可以直接使用外部类的静态成员可以可以
是否可以直接使用外部类的非静态成员不可以可以
外部类的静态方法中是否可以使用内部类的静态成员可以。内部类名.静态成员可以。内部类名.静态成员
外部类的静态方法中是否可以使用内部类的非静态成员可以。创建静态内部类对象即可。不可以。无法创建非静态内部类对象。
外部类的非静态方法中是否可以使用内部类的静态成员可以。内部类名.静态成员可以。内部类名.静态成员
外部类的非静态方法中是否可以使用内部类的非静态成员可以。创建静态内部类对象即可。可以。创建非静态内部类对象即可。
在测试类中,是否可以创建内部类的对象可以。(见下)可以(见下)
(1)测试类中 创建静态内部类的对象
    外部类名.静态内部类名 对象名 = new 外部类名.静态内部类名();

(2)测试类中 创建非静态内部类的对象
    外部类名 对象1 = new 外部类名();
	外部类名.非静态内部类名 对象2 = 对象1.new 非静态内部类名();

案例: 

、public class Outer {
    private static int outA;
    private int outB;

    protected static class InnerOne {//静态内部类
        private int inA;
        private static int inB;

        public void inMethod(){
            System.out.println("outA = " + outA);
//            System.out.println("outB = " + outB);//错误
            //同一个类的成员之间,静态的成员不能直接使用非静态的成员
        }

        public static void one(){
            System.out.println("one");
        }
    }

    public static void outMethod(){
        //使用InnerOne静态内部类的两个成员 inA,inB
//        System.out.println("InnerOne.inA = " + InnerOne.inA);//错误。inA不是静态成员,就不能用“类名.”访问
        InnerOne one = new InnerOne();
        System.out.println("one.inA = " + one.inA);
        System.out.println("InnerOne.inB = " + InnerOne.inB);

        //使用InnerTwo静态内部类的两个成员 inA,inB
        System.out.println(InnerTwo.inB);
       // InnerTwo two = new InnerTwo();//错误,无法创建非静态内部类的对象
        //因为要创建InnerTwo的对象,依赖于Outer类的对象,
        //但是outMethod()方法中没有Outer的对象this
//        System.out.println(this);//错误
    }

    public void outShow(){
/*        //使用InnerOne静态内部类的两个成员 inA,inB
//        System.out.println("InnerOne.inA = " + InnerOne.inA);//错误。inA不是静态成员,就不能用“类名.”访问
        InnerOne one = new InnerOne();
        System.out.println("one.inA = " + one.inA);
        System.out.println("InnerOne.inB = " + InnerOne.inB);

        //使用InnerTwo静态内部类的两个成员 inA,inB
        System.out.println("InnerTwo.inB = " + InnerTwo.inB);
         InnerTwo two = new InnerTwo();
        System.out.println("two.inA = "+ two.inA);*/

        System.out.println(this);
        System.out.println(Outer.this);
    }


    protected class InnerTwo {//非静态内部类
        private int inA;
        private static int inB;

        public void inMethod(){
            System.out.println("outA = " + outA);
            System.out.println("outB = " + outB);
        }

        public static void two(){
            System.out.println("two");
        }
    }

    public InnerTwo getInnerTwo(){
        return new InnerTwo();
    }
}
public class TestInner {
    public static void main(String[] args) {
        //调用Outer类中InnerOne内部类的inMethod方法
        //调用非静态方法就用  对象名.非静态方法
        Outer.InnerOne in = new Outer.InnerOne();
        in.inMethod();

        //调用Outer类中InnerTwo内部类的inMethod方法
        //调用非静态方法就用  对象名.非静态方法
        Outer outer = new Outer();
//        Outer.InnerTwo two = outer.new InnerTwo();
        Outer.InnerTwo two = outer.getInnerTwo();
        two.inMethod();

        //调用非静态方法就用  对象名.非静态方法
        Outer out = new Outer();
        out.outShow();

        //调用Outer类中InnerTwo内部类的two方法。调用静态方法就用  类名.静态方法
        Outer.InnerTwo.two();

        //调用Outer类中InnerOne内部类的one方法。调用静态方法就用  类名.静态方法
        Outer.InnerOne.one();
    }
}

8.6 面向对象总结

面向对象总的原则:

  • 同一个类中静态的成员不可以直接使用非静态的成员

  • 访问一个类的静态成员:类名.静态成员

  • 访问一个类的非静态成员:对象名.非静态成员

9.分析题

9.1 代码分析题(多态)

9.1.1.如下代码是否可以编译通过,如果能,结果是什么,如果不能,为什么?

public class Father{
	private String name = "hhhh";
	int age = 0;
}

public class Child extends Father{
	public String grade;
	
	public static void main(String[] args){
		Father f = new Child();
		System.out.println(f.name);// 编译报错,name被private修饰,不能直接访问
        System.out.println(f.age);// 可以
        System.out.println(f.grade);// 编译报错,因为此时向上转型,对象的类型为Father类,不能访问子类变量
	}
}


9.1.2.阅读如下代码,分析运行结果

public class Test {
	public static void main(String[] args) {
		A a = new B();
		System.out.println(a.num);// 对象类型为A num=1
		System.out.println(((B)a).num);// 对象类型为B  num=2
		System.out.println(((A)((B)a)).num);// 对象类型为A  num=1
		System.out.println("-------------------");
		B b = new B();
		System.out.println(b.num);// 对象类型为B  num=2
		System.out.println(((A)b).num);// 对象类型为A  num=1
		System.out.println(((B)((A)b)).num);// 对象类型为 B  num=2
	}
}
class A{
	int num = 1;
}
class B extends A{
	int num = 2;
}

9.1.3.阅读如下代码,分析运行结果

package com.atguigu.homework3;

public class Test03 {
    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new B();
        B b = new B();
        C c = new C();
        D d = new D();
        System.out.println("(1)" + a1.show(b));
        System.out.println("(2)" + a2.show(d));
        System.out.println("(3)" + b.show(c));
        System.out.println("(4)" + b.show(d));
    }
}
class A{
    public String show(D obj){
        return ("A and D");
    }
    public String show(A obj){
        return "A and A";
    }
}
class B extends A{
    public String show(B obj){
        return "B and B";
    }
    public String show(A obj){
        return "B and A";
    }
}
class C extends B{

}
class D extends B{

}

.。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值