Skip to content

一、复习

1、面向对象的基本特征

(1)封装:

  • 目的或好处:隐藏内部的实现细节、对成员的访问是可控,只能按照其指定的暴露的方式来访问。提高了代码的维护性。
    • 例如:属性私有化,如果没有提供 get/set,那么外部是不知晓有这个属性的。如果提供了 get/set 方法,可以在 get/set 方法写我们想要的逻辑的代码,如加条件判断(对 set 的属性值进行控制)、在 get 方法中也可以加计算。

(2)继承

  • 目的或好处:代码的复用、代码的扩展、可以表示事物与事物之间的 is-a 关系。提高代码的扩展性。
    • 代码的复用:父类声明过的成员,子类不需要重复声明。
    • 代码的扩展:子类可以新增成员,或对父类的某些方法进行重写。

(3)多态(今天讲)

image-20250709084759683

2、抽象类

抽象类的语法格式:

java
【其他修饰符】 abstract class 抽象类名{
    【其他修饰符】 abstract 返回值类型 方法名(【形参列表】); //抽象方法
}
  • 如果一个类中有抽象方法,那么这个类一定是抽象类。
  • 如果一个类没有抽象方法,那么这个类可以是抽象类,也可以不是抽象类。换句话说,抽象类中可以有抽象方法,也可以没有抽象方法。如果一个类没有抽象方法却声明为抽象类的话,就一个目的,为了让你创建它更具体的子类的对象。

3、接口

接口的语法格式:

java
【修饰符】 interface 接口名{

}

类实现接口的语法格式:

java
【修饰符】 class 类名extends 父类】 implements 父接口们{

}

接口继承接口的语法格式:

java
【修饰符】 interface 子接口名 extends 父接口们{

}

接口的成员:

  • JDK8 之前:

    • 公共的静态的常量:public static final,它们可以省略
    • 公共的抽象方法:public abstract,它们可以省略。非抽象的实现类一定要重写。
  • JDK8 之后:

    • 公共的默认方法:public default,其中 public 可以省略。default 不可以省略。默认方法子类可以重写可以不重写。当实现类重写接口的默认方法时,default 要去掉。
    • 公共的静态方法:public static,其中 public 可以省略。static 不可以省略。静态方法是不可以被重写的。
  • JDK9 之后:

    • 私有的方法:private,private 不可以省略。这个方法只能在接口内部使用。

二、关键字

1、其中 private、protected、public、final、abstract、static、native 等这些都可以修饰方法,那么它们哪些可以搭配使用,哪些不可以?

  • final 的方法:方法不能被重写

    • private:可以,但没必要同时 final 又 private。private 的方法不会被重写。
    • 缺省:可以
    • protected:可以
    • public:可以
    • abstract:不可以,矛盾的。
    • static:可以,但没必要同时 final 又 static。static 的方法不能被重写。
    • native:可以
  • abstract 的方法:抽象方法

    • private:不可以。因为 abstract 必须被重写,而 private 的方法不能被重写。
    • 缺省:可以
    • protected:可以
    • public:可以
    • static:不可以。因为(1)静态方法不能被重写(2)如果通过抽象类名.静态抽象方法名时,没有方法体可以执行。
    • native:不可以。因为 abstract 的方法表示方法体在子类中,native 的方法表示方法体在底层 C/C++中。
  • static 的方法:静态方法

    • private:可以
    • 缺省:可以
    • protected:可以
    • public:可以
    • native:可以

2、private、protected、public、final、abstract、static、native 等哪些能修饰类(先不讨论内部类)

  • private:不可以
  • 缺省:可以
  • protected:不可以
  • public:可以
  • final:可以。不能被继承,太监类。
  • abstract:可以
  • static:不可以
  • native:不可以
  • 不可以同时 final 和 abstract

3、private、protected、public、final、abstract、static、native 等哪些能修饰成员变量

  • private:可以
  • 缺省:可以
  • protected:可以
  • public:可以
  • final:可以,但是要手动赋值
  • abstract:不可以,没有抽象变量的说法。
  • static:可以
  • native:不可以
  • static,final:可以。这种情况一般要大写。

4、private、protected、public、final、abstract、static、native 等哪些能修饰构造器

  • 构造器前面只能加权限修饰符:private、缺省、protected、public

5、private、protected、public、final、abstract、static、native 等哪些能修饰局部变量

  • 只能是 final

三、面向对象的基本特征之三:多态

3.1 多态的好处(重要)

  • 同一个父类或父接口可以有不同的子类实现
  • 主要体现为方法的重写(运行时多态)和方法的重载(编译时多态)
  • 允许使用统一的父类或父接口处理不同子类对象,提高了程序的灵活性和扩展性。

3.2 什么是多态引用(重要)

当父类或父接口的变量,指向一个子类的对象时,就是多态引用。

语法格式:

java
父类或父接口的变量 = 子类的对象;

3.3 多态引用有什么不同(重要)

调用方法时,遵循编译时看左边,运行时看右边的原则,从而实现方法的动态绑定。

看右边:如果子类重写了该方法,一定执行子类的。如果子类没有重写该方法,那么仍然执行父类声明的方法体。

java
package com.atguigu.polymophism;

public class Animal {//动物
    public void eat(){
        System.out.println("吃东西");
    }
}
java
package com.atguigu.polymophism;

public class Dog extends Animal{//狗
    @Override
    public void eat() {
        System.out.println("狗吃屎");
    }

    public void watchHouse(){
        System.out.println("看家");
    }
}
java
package com.atguigu.polymophism;

public class TestAnimal {
    public static void main(String[] args) {
 /*       Dog d = new Dog();//本态引用
        d.eat();
        d.watchHouse();*/

        Animal a = new Dog();//多态引用,父类的变量指向了一个子类的对象
        a.eat();
       // a.watchHouse();//编译报错,因为编译时看左边,a的左边是Animal类型,现在Animal类中没有watchHouse()方法,所以编译报错。
    }
}

3.4 多态的应用(重要)

其他子类代码:

java
package com.atguigu.polymophism;

public class Cat extends Animal{//猫
    @Override
    public void eat() {
        System.out.println("猫吃老鼠");
    }
    public void catchMouse(){
        System.out.println("抓老鼠");
    }
}
java
package com.atguigu.polymophism;

public class Pig extends Animal{//猪

    @Override
    public void eat() {
        System.out.println("啥都吃");
    }
}
java
package com.atguigu.polymophism;

public class Bird  extends Animal{
}
java
package com.atguigu.polymophism;

public class Husky extends Dog{//哈士奇
}

3.4.1 多态数组

用一个父类类型的数组,管理一组子类的对象。

java
package com.atguigu.polymophism;

public class TestAnimal2 {
    public static void main(String[] args) {
        //在一个数组中,统一管理Animal的不同子类的对象
       /* Dog[] dogs = new Dog[3];
        dogs[0] = new Dog();
        dogs[1] = new Cat();//错误
        dogs[2] = new Pig();//错误
        */

        Animal[] animals = new Animal[3];
        animals[0] = new Dog();//多态引用,因为animals[0]是Animal父类类型的,右边是Dog对象
        animals[1] = new Cat();
        animals[2] = new Pig();

        //多态引用, a是Animal父类类型的,但是实际上 数组的元素分别是Dog、Cat、Pig类型的
        for (Animal a : animals) {//增强for循环,即foreach
            a.eat();
        }
    }
}

3.4.2 多态参数

用一个父类类型的形参,接收各种子类对象的实参。

java
package com.atguigu.polymophism;

public class TestAnimal3 {
    public static void main(String[] args) {
        Dog d1 = new Dog();
        look(d1);//方法调用时,形参(Animal a)是 Animal类型,实参d1是Dog类型。实参是给形参赋值的
                //Animal a = d1;

        Cat c1 = new Cat();
        look(c1);

        Pig p1 = new Pig();
        look(p1);

        Bird b1 = new Bird();
        look(b1);
    }

    //定义一个方法,可以看各种动物吃东西
    //3个重载方法,这种实现方法,如果新增子类,需要新增方法。如果要删除某个子类,也要删除对应的方法。
    /*public static void look(Dog d){
        d.eat();
    }
    public static void look(Cat c){
        c.eat();
    }
    public static void look(Pig p){
        p.eat();
    }*/
    public static void look(Animal a){
        a.eat();
    }
}

3.5 向上转型与向下转型(重要)

向上转型:本来是子类的对象,但是却赋值给父类的变量,编译器就会把它按照父类的类型进行处理,此时调用方法时,只能看父类的。

向下转型:当某个对象被赋值给父类或父接口的变量后,发生了向上转型,此时失去了调用子类新增的方法的能力,如果需要调用子类新增方法的能力,就必须将其向下转型。向下转型操作可能有风险,需要通过 instanceof 判断进行避免,避免 ClassCastException 异常。

java
package com.atguigu.polymophism;

public class TestClassCast {
    public static void main(String[] args) {
        //double d = 1;
        Animal a = new Dog(); //向上转型
        a.eat();//编译器把a定为Animal类型,只能调用Animal中有的方法,Animal没有子类有的方法,调不了
//        a.watchHouse();//报错

        //如果希望上面的对象调用Dog类中扩展的新方法,只能将其向下转型
        //int a = (int)d;
       /* Dog d = (Dog) a;//向下转型,需要强制进行
        d.eat();
        d.watchHouse();*/

        /*Cat c = (Cat) a;//向下转型。编译时a以Animal类型处理,那么Animal是Cat的父类,所以可以向下转型。
        //上面这句代码发生了运行时异常,它是ClassCastException类型转换异常。
        // 因为实际上a指向的是Dog对象,Dog与Cat之间是兄弟关系,不能强转。
        //即向下转型要小心,可能有风险。
        c.eat();
        c.catchMouse();*/

       /* if(a instanceof Dog){//instanceof,判断a中的对象是不是Dog类型的实例对象
            Dog d = (Dog) a;//向下转型,需要强制进行
            d.eat();
            d.watchHouse();
        }else if(a instanceof Cat){
            Cat c = (Cat) a;
            c.eat();
            c.catchMouse();
        }*/

        //这里有一个新特性:模式匹配
        if(a instanceof Dog d){//instanceof,判断a中的对象是不是Dog类型的实例对象
            d.eat();
            d.watchHouse();
        }else if(a instanceof Cat c){
            c.eat();
            c.catchMouse();
        }
        System.out.println("=============================");
        //总结:当a变量中实际new的对象的类型 <= instanceof后面判断的类型,结果就是true
        System.out.println(a instanceof Dog);//true
        System.out.println(a instanceof Animal);//true
        System.out.println(a instanceof Object);//true
        System.out.println(a instanceof Cat);//false
        System.out.println(a instanceof Husky);//false
    }
}

3.6 分析和总结

image-20250709112210079

image-20250709112216354

当出现以下情况时,就会有多态的表现: 编译时看父类,运行时看子类

(1)继承和重写

(2)多态引用:父类的变量或数组装的是子类的对象

(3)调用可能被子类重写的方法

3.7 多态引用时调用成员变量问题(了解)

java
package com.atguigu.field;

public class Father {
    public int a = 1;
}
java
package com.atguigu.field;

public class Son extends Father{
    public int a = 2;
}
java
package com.atguigu.field;

public class TestFatherAndSon {
    public static void main(String[] args) {
        Father f = new Son();//多态引用
        System.out.println("f.a = " + f.a);//f.a = 1
        /*
        多态引用后,如果不是调用方法,而是直接访问属性,那么不会遵循编译时看左边,运行时看右边的原则。
        而是遵循:只看编译时类型。
         */
        System.out.println("((Son)f).a = " + ((Son)f).a);//2
        Son s = (Son) f;
        System.out.println("s.a = " + s.a);//2

        System.out.println("====================");
        Son son = new Son();//本态引用
        System.out.println("son.a = " + son.a);//2
        System.out.println("((Father)son).a = " + ((Father)son).a);//1
        Father father = son;//向上转型
        System.out.println("father.a = " + father.a);
    }
}

3.8 虚方法(了解)

虚方法:在 Java 中代表的就是可能被子类重写的方法。只要不是 final、static、private 都是虚方法。这么说的话,抽象方法是天生的虚方法。接口的默认方法也是虚方法。

在多态引用时,虚方法的调用比较复杂:

  • 编译时看左边,看父类
    • 如果存在重载的情况,根据实参找形参,并且实参看的也是编译时类型。
      • 如果有实参类型与形参类型一样的,最匹配
      • 如果实参类型没有与形参类型一样的,找实参类型 小于 形参类型的,可以兼容。
  • 运行时看右边
    • 在父类中找到匹配方法之后,再看子类中是否有对匹配的方法进行重写的。
    • 如果有重写刚刚匹配的方法的,就执行重写的。
    • 如果没有重写刚刚匹配的方法的,就仍然执行刚刚匹配的方法。

image-20250709113744171

java
package com.atguigu.virtual;

class MyClass{
    public void method(Father f) {
        System.out.println("father");
    }
    public void method(Son s) {
        System.out.println("son");
    }
}
class MySub extends MyClass{
    public void method(Father d) {
        System.out.println("sub--father");
    }
    public void method(Daughter d) {
        System.out.println("daughter");
    }
}
class Father{

}
class Son extends Father{

}
class Daughter extends Father{

}
public class TestVirtualMethod {
    public static void main(String[] args) {
        Father f = new Father();
        Son s = new Son();
        Daughter d = new Daughter();

        MyClass my = new MySub(); //多态引用
        my.method(f);//调用虚方法method。运行结果是sub--father
        /*
        (1)编译时看左边(父类)
        my是MyClass类型,就在MyClass类中找可以匹配的方法。
        f是实参,它是Father类型,它与哪个method方法的形参更匹配。
        public void method(Father f) {
            System.out.println("father");
        }

        (2)运行时看右边(子类)
        my的=右边new的是MySub的对象,就要看MySub类中是否有对刚刚匹配的方法进行了重写。
        如果有,就执行重写后的。如果没有就仍然执行刚刚匹配的方法。
        这里有重写
         public void method(Father d) {
            System.out.println("sub--father");
        }
        执行重写后的。
         */

        my.method(s);//调用虚方法method。运行结果是son
        /*
        (1)编译时看左边(父类)
        my是MyClass类型,就在MyClass类中找可以匹配的方法。
        s是实参,它是Son类型,它与哪个method方法的形参更匹配。
        public void method(Son s) {
            System.out.println("son");
        }

        (2)运行时看右边(子类)
        my的=右边new的是MySub的对象,就要看MySub类中是否有对刚刚匹配的方法进行了重写。
        如果有,就执行重写后的。如果没有就仍然执行刚刚匹配的方法。
        这里没有重写

        执行在父类中匹配的方法。
         */

        my.method(d);//调用虚方法method。运行结果是
        /*
        (1)编译时看左边(父类)
        my是MyClass类型,就在MyClass类中找可以匹配的方法。
        d是实参,它是Daughter类型,它与哪个method方法的形参更匹配。没有最匹配的,但是可以找到兼容的。
        public void method(Father f) {
            System.out.println("father");
        }

        (2)运行时看右边(子类)
        my的=右边new的是MySub的对象,就要看MySub类中是否有对刚刚匹配的方法进行了重写。
        如果有,就执行重写后的。如果没有就仍然执行刚刚匹配的方法。
        这里有重写
         public void method(Father d) {
            System.out.println("sub--father");
        }
        执行重写后的。
         */

        Father b = new Son();
        my.method(b);//调用虚方法method。运行结果
        /*
        (1)编译时看左边(父类)
        my是MyClass类型,就在MyClass类中找可以匹配的方法。
        b是实参,它是Father类型(编译是Father),它与哪个method方法的形参更匹配。
        public void method(Father f) {
            System.out.println("father");
        }

        (2)运行时看右边(子类)
        my的=右边new的是MySub的对象,就要看MySub类中是否有对刚刚匹配的方法进行了重写。
        如果有,就执行重写后的。如果没有就仍然执行刚刚匹配的方法。
        这里有重写
         public void method(Father d) {
            System.out.println("sub--father");
        }
        执行重写后的。
         */
    }
}

四、内部类

4.1 什么内部类

定义在类的内部的类。它可以分为 4 种小的类别:

  • 成员内部类:定义在方法外
    • 静态内部类:有 static
    • 非静态内部类:没有 static
  • 局部内部类:定义在方法内
    • 有名字的局部内部类
    • 匿名的局部内部类(用的最多)

4.2 匿名内部类

4.2.1 匿名内部类的语法(掌握)

语法格式:

java
new 父类名(){  //匿名子类的构造器调用的是父类的无参构造
    //定义类的成员
}
new 父类名(实参列表){// 匿名子类的构造器调用的是父类的有参构造
    //定义类的成员
}
new 父接口名(){ //匿名子类的构造器调用的是Object父类的无参构造
    //定义类的成员
}
  • 因为匿名内部类没有名字,因此在声明类的同时,就需要把这个类的唯一的对象直接创建好。即声明一个新的类与创建对象是同时进行的。
  • 类的成员可以有成员变量、构造器、成员方法、内部类、代码块(一会儿再讲)。但是因为匿名内部类没有名字,所以构造器写不了。理论上可以内部类中再定义内部类,但是不会这么干。
  • 匿名内部类不建议写到过于复杂,所以一般在匿名内部类中只会定义方法,而且通常是重写父类或父接口的抽象方法。

image-20250709141737072

image-20250709141852513

示例 1

java
package com.atguigu.inner.anymous;

public interface Flyable {//接口
    void fly();//抽象方法
}
java
package com.atguigu.inner.anymous;

public class TestFlyable {
    public static void main(String[] args) {
        //接口不能直接new对象
       // Flyable f = new Flyable();//错误,这里不能直接创建Flyable接口本身的对象。

        //多态引用
        //左边f1是Flyable父接口类型,右边是一个匿名的实现类的对象
        Flyable f1 = new Flyable() {
            @Override
            public void fly() {
                System.out.println("我要飞的更高!");
            }

            public void run(){
                System.out.println("我要跑的很快!");
            }
        };
        f1.fly();
        //f1.run();//错误,因为编译时f1是Flyable类型,Flyable接口中没有run方法

        /*
        new Flyable() {
            @Override
            public void fly() {
                System.out.println("我要飞的更高!");
            }

            public void run() {
                System.out.println("我要跑的很快!");
            }
        }.run();*/

    }
}

示例 2

java
package com.atguigu.inner.anymous;

public abstract class Father {//抽象类
    private String name;

    public Father() {
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public abstract void show();//抽象方法
}
java
package com.atguigu.inner.anymous;

public class TestFather {
    public static void main(String[] args) {
        //抽象类不能直接创建对象
//        Father f = new Father();//错误,不能创建Father类本身的对象

        //创建Father子类的对象,可以是匿名子类
        Father f = new Father() {
            @Override
            public void show() {
                System.out.println("hello" + getName());
            }
        };
        f.show();

        Father f2 = new Father("张三") {
            @Override
            public void show() {
                System.out.println("hi" + getName());
            }
        };
        f2.show();
    }
}

4.2.2 匿名内部类的应用

为什么要用匿名内部类呢?

哪里需要接口的实现类对象,哪里声明匿名内部类实现这个接口,遵循高内聚的开发原则,也省略了给类取名字,减少了.java 文件的数量。

java
package com.atguigu.inner.anymous;

public interface MyComparator {//定义一个接口
    int compare(Object o1, Object o2);
}
/*
这个方法可以比较2个对象的大小。但有统一的要求:
    当o1对象 大于 o2对象,返回正整数;
    当o1对象 小于 o2对象,返回负整数;
    当o1对象 等于 o2对象,返回0;
 */
java
package com.atguigu.inner.anymous;

public class MyArrays {//模仿Arrays写数组工具类
    //定义一个方法:可以为任意对象数组找最大值
/*    public static Object findMax(Object[] arr){
        Object max = arr[0];
        for(int i=1; i<arr.length; i++){
            if(arr[i] > max){//报错,因为 arr[i]和max里面是地址值,地址值不能比较大小
                max = arr[i];
            }
        }
        return max;
    }*/

    public static Object findMax(Object[] arr,MyComparator comparator){
        Object max = arr[0];
        for(int i=1; i<arr.length; i++){
            //按照约定,arr[i]大于max就会返回一个正整数
            if(comparator.compare(arr[i], max) > 0){
                max = arr[i];
            }
        }
        return max;
    }
}
java
package com.atguigu.inner.anymous;

public class Circle {
    private double radius;

    public Circle() {
    }

    public Circle(double radius) {
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    @Override
    public String toString() {
        return "Circle{" +
                "radius=" + radius +
                '}';
    }
}
java
package com.atguigu.inner.anymous;

public class TestCircle {
    public static void main(String[] args) {
        Circle[] circles = new Circle[3];
        circles[0] = new Circle(2.0);
        circles[1] = new Circle(1.5);
        circles[2] = new Circle(1.0);

        MyComparator my = new MyComparator() {
            @Override
            public int compare(Object o1, Object o2) {
                Circle c1 = (Circle) o1;
                Circle c2 = (Circle) o2;
//                return (int) (c1.getRadius() - c2.getRadius());//有瑕疵
                if(c1.getRadius() > c2.getRadius()){
                    return 1;
                }else if(c1.getRadius() < c2.getRadius()){
                    return -1;
                }else{
                    return 0;
                }
            }
        };
        Object max = MyArrays.findMax(circles, my);
        System.out.println("半径最大的圆:" + max);
    }
}

image-20250709145343399

java
package com.atguigu.inner.anymous;

public class Student {
    private String name;
    private int score;

    public Student() {
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}
java
package com.atguigu.inner.anymous;

public class TestStudent {
    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("张三",85);
        students[1] = new Student("李四",90);
        students[2] = new Student("王五",75);

        System.out.println("数组的元素:");
        for (Student student : students) {
            System.out.println(student);
        }

        //找最大值
//        MyArrays.findMax(students, new MyComparator());//因为接口不能直接创建对象
        MyComparator my = new MyComparator() {
            @Override
            public int compare(Object o1, Object o2) {
                Student s1 = (Student) o1;
                Student s2 = (Student) o2;
                /*if(s1.getScore() > s2.getScore()){
                    return 1;
                }else if(s1.getScore() < s2.getScore()){
                    return -1;
                }else{
                    return 0;
                }*/

                return s1.getScore() - s2.getScore();
            }
        };
        Object max = MyArrays.findMax(students, my);
        System.out.println("最高分的同学:" + max);
    }
}

4.2.3 分析

image-20250709160813472

image-20250709160916167

image-20250709160932209

4.3 有名字的局部内部类(了解)

局部内部类可以使用外部类的私有成员,也可以使用外部类定义局部内部类的方法的局部变量,但是此局部变量必须是 final 的。

示例 1:

java
package com.atguigu.inner.local;

public interface Flyable {
    void fly();
}
java
package com.atguigu.inner.local;

public class Plane implements Flyable{ //有名字的,独立的实现类
    @Override
    public void fly() {
//        System.out.println("我要飞入云霄" + name);
        //错误,因为Plane类不能用TestFlyable的私有成员
    }
}
java
package com.atguigu.inner.local;

public class TestFlyable {
    private static String name;//私有的内部成员,成员变量

    public static void main(String[] args) {
        int speed = 100;//局部变量,它必须是final的。JDK8之后,一旦它被局部内部类使用了,就会默认加final
        //局部内部类,且是有名字,且它实现了Flyable接口
        class Bird implements Flyable {
            @Override
            public void fly() {
                System.out.println("我要飞的更高" + name );
                System.out.println("速度:" + speed);
                //内部类,可以直接使用外部类的私有成员。
            }
        };

        Bird b1 = new Bird();
        Bird b2 = new Bird();

        b1.fly();
        b2.fly();

        Plane p1 = new Plane();
        Plane p2 = new Plane();
        p1.fly();
        p2.fly();
    }
}

示例 2:

java
package com.atguigu.inner.local;

public class TestOuter {
    public static void main(String[] args) {
        Object obj = show();//多态引用,左边是父类Object类型,右边是Inner
        //上面的语句结束了之后,show()出栈了,show()的a不在栈里面
        System.out.println(obj.toString());//编译看左边Object,运行看子类Inner的toString
    }

    public static Object show(){
        int a = 1;//a是show()的局部变量。它的生命周期:从调用show开始,到show结束
        class Inner{
            public String toString(){
                return "a = " + a;
                //此a实际上不是外面的a,编译器会在Inner类的内部,自动添加了一个 成员变量是a,初始化为此时外部方法局部变量a的值
                //只有这样,当调用in对象的toString方法时,a的生命周期还在。
            }
        }
//        a = 2;//不可以修改。如果能修改,就会导致 Inner的toString方法中a的值的歧义,到底是1还是2。
        Inner in = new Inner();
        return in;
    }
}

4.3 成员内部类(只要能区分什么能访问什么不能访问即可)

静态内部类非静态内部类
成员成员变量、构造器、成员方法、内部类、代码块,与普通类一样成员变量、构造器、成员方法、内部类、代码块,与普通类一样
使用外部类的成员(包括私有的)只能用外部类的静态成员可以外部类的所有成员
外部类的方法中,哪些能用它们外部类的所有方法都能用只能在外部类的非静态方法中使用
在外部类的外面使用内部类的静态成员外部类名.静态内部类名.静态成员外部类名.非静态内部类名.静态成员
在外部类的外面使用内部类的非静态成员(一般需要避免)外部类名.静态内部类 对象名 = new 外部类名.静态内部类();
对象名.非静态成员
外部类名 对象名 1 = new 外部类名();
外部类名.非静态内部类 对象名 2 = 对象名 1.new .非静态内部类();
对象名 2.非静态成员

当内部类与外部类的成员重名了,如何区别?

(1)与外部类的静态成员重名: 外部类名.静态成员

(2)与外部类的非静态成员重名:外部类名.this.非静态成员

总的原则:

(1)同一个类中,静态的不能直接访问非静态的

(2)跨类使用,使用静态成员,通过“类名.”,使用非静态成员,一定要先 new 对象,通过“对象.”

如何选择该定义为静态内部类还是非静态内部类呢?

原则:在这个成员内部类中,如果要用到外部类的非静态成员,那么这个成员内部类只能是非静态。

​ 如果不会用到外部类的非静态成员,那么这个成员内部类可以是静态的,也可以是非静态。静态会更简单一些。

同等问题:如果选择一个方法定义为静态方法还是非静态方法?

​ 如果在方法中,需要使用本类的实例变量等非静态成员,那么这个方法只能是非静态的。

​ 如果在方法中,不需要使用本类的实例变量等非静态成员,那么这个方法可以是静态的,也可以是非静态。静态会更简单一些。

如何选择一个成员变量是静态还是非静态的?

如果这个成员变量的值是大家共享的,那么就应该是静态的。

如果这个成员变量的值是每一个对象独立的,那么就应该是非静态。

示例 1

java
package com.atguigu.inner.member;

public class Outer {//外部类
    private int outA;
    private static int outB;

     class One{//非静态的成员内部类
        public void show() {
            System.out.println("outA = " + outA);
            System.out.println("outB = " + outB);
        }

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

    static class Two{//静态的成员内部类
        public void show() {
//            System.out.println("outA = " + outA);//Two类是静态的,不能直接使用外部类非静态的成员
            System.out.println("outB = " + outB);
        }

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

    public static void outShow(){
        /*One one = new One();//One是非静态的,outShow是静态的。同样静态方法不能直接使用本类其他非静态成员
        one.show();*/

        Two two = new Two();
        two.show();
    }

    public void outPrint(){
        One one = new One();
        one.show();
        Two two = new Two();
        two.show();
    }

}
java
package com.atguigu.inner.member;

public class TestOuter {
    public static void main(String[] args) {
        //这里调用One的show()
        Outer outer = new Outer();
        Outer.One one = outer.new One();
        one.show();


        //这里调用Two的show()
        Outer.Two two = new Outer.Two();
        two.show();

        //跨类使用另一个类的成员时,使用对方的静态成员,用类名.,
        // 使用对方的非静态成员,用对象.


//        这里调用One或Two的print方法
        Outer.One.print();
        Outer.Two.print();
    }
}

示例 2

java
package com.atguigu.inner.member;

public class Wai {
    private static int a = 1;
    private int b = 2;

    class Inner{
        private static int a = 3;
        private int b = 4;

        public void show(){
            System.out.println("外面a = " + Wai.a);
            System.out.println("里面a = " + a);
            System.out.println("外面的b = " + Wai.this.b);
            System.out.println("里面的b = " + this.b);
            //这里有2个this,有2个当前对象,一个是外部类的当前对象,一个是内部类的当前对象
            //因为为了show方法,需要创建2个对象
        }
    }
}
java
package com.atguigu.inner.member;

public class TestWaiInner {
    public static void main(String[] args) {
        Wai w = new Wai();
        Wai.Inner i = w.new Inner();
        i.show();
    }
}

五、代码块(了解)

类的成员有 5 个:

  • 成员变量(重点)
  • 构造器(重点)
  • 成员方法(重点)
  • 内部类(除了匿名内部类需要大家会写,其余能看懂即可)
  • 代码块(简单了解)

5.1 静态代码块

语法格式:

java
【修饰符】 class 类名{
    static{
        //它就是静态代码块
    }
}

有什么用?

  • 它是用来在类加载时为静态变量初始化的。

执行的特点:

  • 每一个类的静态代码块只会执行 1 次。

5.2 非静态代码块

语法格式:

java
【修饰符】 class 类名{
    {
        //它就是非静态代码块
    }
}

有什么用?

  • 它是辅助构造器在 new 对象时为实例变量初始化的。

执行的特点:

  • 每次 new 对象时都要执行。new 一次执行一次。

5.3 示例代码

示例 1

java
package com.atguigu.block;

public class Demo {
    static {
        System.out.println("1、静态代码块");
    }
    {
        System.out.println("2、非静态代码块");
    }

    public static void show(){
        System.out.println("3、haha");
    }
}
java
package com.atguigu.block;

public class TestDemo {
    public static void main(String[] args) {
        Demo.show();

        Demo d1 = new Demo();
        Demo d2 = new Demo();
    }
}

示例 2

java
package com.atguigu.block;

public class Father {

    public Father(){
        System.out.println("5、父类的无参构造");
    }
    {
        System.out.println("2,父类的非静态代码块");
    }
    static{
        System.out.println("1、父类的静态代码块");
    }
}
java
package com.atguigu.block;

public class Son extends Father{

     public Son(){
        System.out.println("6、子类的无参构造");
    }

    {
        System.out.println("4,子类的非静态代码块");
    }
    static{
        System.out.println("3、子类的静态代码块");
    }
}
java
package com.atguigu.block;

public class TestSon {
    public static void main(String[] args) {
        Son s1 = new Son();
        Son s2 = new Son();
    }
    //(1)静态先于非静态
    //(2)父类先于子类
    //(3)非静态代码块先于构造器
}