面向对象高级部分笔记汇总

面向对象编程高级部分知识汇总(类变量与类方法详解、main方法详解、代码块详解、单例设计模式介绍、final关键字详解、抽象类详解、接口详解、内部类详解)

类变量和类方法

类变量

在类属性中加一个变量conut,在构造时+1,来统计该类的对象个数

public class staticbianl {
    public static void main(String[] args) {
        Child c1 = new Child("mxy");
        Child c2 = new Child("yll");
        Child c3 = new Child("Yll");
        System.out.println(c3.count);
        //输出3
    }
}
class Child{
    private String name;
    public static int count = 0;//会被所有对象共享

    public Child(String name) {
        this.name = name;
        count++;
    }
}

在JDK8之前存在方法区的静态域中,JDK8以后存放在堆中的这个类对应的class对象的最后。

不管static在哪里,所有对象使用没区别,且都符合以下共识:

被同一个类的所有对象共享

在类加载时就形成

什么是类变量

静态变量/静态属性,一个类的所有对象共享的变量,任一对象访问均取到相同值,任一对象修改也修改同一变量。

定义语法

  • 访问修饰符 static 数据类型 变量名(推荐)
  • static 访问修饰符 数据类型 变量名

访问

  • 类名.类变量名(推荐)
  • 对象名.类变量名
  • 注意访问必须遵守其访问权限

细节

  • 需要让所有对象共享一个属性时,使用类对象
  • 加上static称为类变量或者静态变量,否则是普通变量
  • 推荐使用 类名.类变量名来访问(满足访问权限情况下)
  • 实例变量不能通过类名.变量名来访问
  • 静态变量在类加载的时候就已经创建了,所以我们在没有创建对象实例的时候就能通过类名.类变量来使用
  • 类变量的生命周期随着类的加载而开始,随着类的消亡而销毁
  • 普通成员方法和静态成员方法都可以修改静态变量

类方法

创建:访问修饰符 static 数据返回类型 方法名(){} (推荐)

调用:类名.类方法名 或 对象名.类方法名

使用场景:方法中不涉及到任何和对象相关的成员,可以将方法设计成静态方法,这样不创建实例就能调用某个方法(当作工具使用),提高开发效率

好处:不用实例化对象就可以调用,在内存中不用重新开辟空间

可以参考Math类,其中有很多类方法

注意事项

类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区
类方法中没有this参数,但是普通方法隐含this参数

在这里插入图片描述

类方法可以通过类名或对象名调用
普通方法和对象有关,需要通过对象名来调用,比如对象名.方法名(参数),不能通过类名调用
类方法中只能访问静态变量或静态方法 不能 访问非静态变量或非静态方法
一个小用法:
public static void payFee(double fee){
    //this.fee = fee; 静态方法无this参数,不能使用this
    //但是可以使用类名.静态属性来调用,避免命名冲突
    Child.fee = fee;
}

小结:

1. 静态方法只能访问静态成员

2. 非静态方法可以访问所有成员

3. 注意要遵守访问权限规则

理解main方法语法

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

细节

  • main方法由JVM调用

  • JVM需要调用main方法,因此访问权限必须是public

  • JVM在执行main方法时不必创建对象,因此该方法必须是static

  • 接收String类型的数组参数,该参数中保存执行Java命令时传递给所允许的类的参数

  • 在main方法中可以使用当前类中的所有静态方法或静态属性,但是非静态方法和非静态属性需要通过创建对象来使用

  • args 数组参数从何处传入?在执行过程中输入的(执行程序时通过命令行控制)

public class Main01 {
    public static void main(String[] args) {
        for(String arg: args){
            System.out.println(arg);
        }
    }
}

在这里插入图片描述

在这里插入图片描述

代码块

语法

[修饰符]{
    代码
};

public class CodeBlock1 {
    public static void main(String[] args) {
        Movie movie = new Movie("三体");
    }
}

class Movie{
    private String name;
    private double price;
    private String author;

    //若需要在三个构造器中都执行相同语句,可以把相同语句防在一个代码块中
    //这样不管要用哪个构造器创建对象都会先调用代码块的内容
    {
        System.out.println("电影开始放映了...");
    }

    public Movie(String name) {
        System.out.println("构造器被调用");
        this.name = name;
    }

    public Movie(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public Movie(String name, double price, String author) {
        this.name = name;
        this.price = price;
        this.author = author;
    }
}

//输出:
//电影开始放映了...
//构造器被调用
//可以看出代码块是先于构造器调用的
//有利于重用

注意

  • 修饰符可选,写的话只能写static

  • 代码块分为两类,一类是用static修饰的静态代码块,一类是没有static修饰的 普通代码块

  • 代码可以为任何逻辑语句

  • ;号可以写上,可以用省略

  • 代码块先于构造器调用

  • static代码块 随着类的加载而执行,只会执行一次,如果是普通代码块,每创建一个对象就执行一次

  • 注意普通代码块在对象创建时执行,在类加载时不执行,比如A.a1;时,普通代码块就不会执行

public class CodeBlock1 {
    public static void main(String[] args) {
        Movie movie = new Movie("三体");
        Movie movie1 = new Movie("LaLa Land");
        Movie movie2 = new Movie("让子弹飞");
    }
}

class Movie{
    private String name;
    private double price;
    private String author;

    //若需要在三个构造器中都执行相同语句,可以把相同语句防在一个代码块中
    //这样不管要用哪个构造器创建对象都会先调用代码块的内容
    {
        System.out.println("普通代码块");
    }
    static{
        System.out.println("static代码块");
    }

    public Movie(String name) {
        System.out.println("构造器被调用");
        this.name = name;
    }

    public Movie(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public Movie(String name, double price, String author) {
        this.name = name;
        this.price = price;
        this.author = author;
    }
}

//运行结果
static代码块
普通代码块
构造器被调用
普通代码块
构造器被调用
普通代码块
构造器被调用
  • 类什么时候被加载 (重要)

    • 创建对象实例的时候(new)
    • 创建子类对象实例的时候,父类也会被加载,且父类先于子类加载
    • 直接使用静态成员时也会被加载
    public class CreateTest {
        public static void main(String[] args) {
            new A();
            System.out.println(A.a1);
        }
    }
    class A extends B{
        
        public static int a1 = 699;
        
        static{
            System.out.println("类A被加载");
        }
    }
    
    class B{
        static {
            System.out.println("类B被加载");
        }
    }
    
    //输出:
    //类B被加载
    //类A被加载
    //699
    

执行顺序问题

  • 1. 调用静态代码块和静态属性的初始化(静态属性和静态代码块初始化调用的优先级一致,若有多个,按定义顺序调用)

    public class CodeBlockDetail {
        public static void main(String[] args) {
            AA a = new AA();
        }
    }
    
    class AA{
        private static int n1 =getVal();
        static {
            System.out.println("静态代码块被调用");
        }
        public static int getVal(){
            System.out.println("getVal被调用");
            return 699;
        }
    }
    //输出:
    //getVal被调用
    //静态代码块被调用
    
    public class CodeBlockDetail {
        public static void main(String[] args) {
            AA a = new AA();
        }
    }
    
    class AA{
        static {
            System.out.println("静态代码块被调用");
        }
        private static int n1 =getVal();
        public static int getVal(){
            System.out.println("getVal被调用");
            return 699;
        }
    }
    //输出:
    //静态代码块被调用
    //getVal被调用
    
  • 2. 普通代码块和普通属性初始化调用的优先级一致,若有多个,按定义顺序调用

  • 3. 调用构造方法

构造器 最前面隐藏了super()和调用普通代码块

创建子类对象时,静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序:

  • 父类静态成员

  • 子类静态成员

  • 父类普通代码块和普通属性初始化

  • 父类构造器

  • 子类普通代码块和普通属性初始化

  • 子类构造器

public class CodeBlockDetail {
    public static void main(String[] args) {
        AA a = new AA();
    }
}

class AA extends A{
    private static int n1 =getVal();
    private int n2;
    {
        System.out.println("AA的普通代码块被调用");
    }

    public AA(){
        //super();
        //调用本类普通代码块
        System.out.println("AA的无参构造器");
    }

    public AA(int n2) {
        //super();
        //调用普通代码块
        this.n2 = n2;
    }

    static {
        System.out.println("AA的静态代码块被调用");
    }
    public static int getVal(){
        System.out.println("AA的静态方法被调用");
        return 699;
    }
}

class A extends B{
    public static int a1 = 699;
    static{
        System.out.println("类A静态代码块");
    }

    public A() {
        System.out.println("A的构造器");
    }
}

class B{
    static {
        System.out.println("类B静态代码块");
    }
}
//输出:

//静态成员执行之前先执行父类静态成员的内容B静态代码块
类A静态代码块

//然后执行本类静态成员
AA的静态方法被调用
AA的静态代码块被调用

//然后执行父类构造方法super()
A的构造器
    
//然后执行本类普通代码块
AA的普通代码块被调用
    
//然后执行本类无参构造器
AA的无参构造器

单例设计模式

设计模式:大量实践中总结和理论化之后优选的代码结构

单例模式:采取一定方法,保证在整个软件系统中,对某个类只能存在一个对象实例(不能再有第二个),并且该实例只能提供一个获得其对象实例的方法。

两种模式

  • 饿汉式

  • 懒汉式

饿汉式单例模式的实现:

  • 1、构造器私有化 -> 防止直接new

  • 2、类的内部创建 静态 对象

  • 3、向外暴露一个 静态 的公共方法 getInstance

public class singleTest {
    public static void main(String[] args) {
        //通过静态方法可以直接使用非静态方法
        Single01.getInstance().sayHi();
        //通过方法可以获取对象
        Single01 single01 = Single01.getInstance();
        single01.sayHi();
        Single01 single011 = Single01.getInstance();
        //无论用instance获取几次对象,得到的都是同一个对象
        System.out.println(single01==single011);
    }
}
//输出:
//Hi~
//Hi~
//true


class single01 {
    //构造器私有化
    private single01(){};
    
    //提供一个静态属性类型 single01
    //先创建一个这个对象的实例
    private static single01 instance = new single01();
    
    //创建一个public的静态方法,用于访问该实例
    public static single01 getInstance(){
        return instance;
    }
    public void sayHi(){
        System.out.println("Hi~");
    }
}

注意一定要用static对instance与getInstance进行修饰,这样在使用时才能不new一个对象就能使用此方法与此成员。

为什么叫做 “饿汉式” 还没有使用实例的时候就创建好了(着急) 比较占用内存空间

懒汉式单例模式的实现:

饿汉式可能造成创建了对象但是没有使用,造成较大的内存浪费。为了弥补这一弊端,使用懒汉式(使用了才会出现)

实现步骤

  • 1、构造器私有化

  • 2、定义一个私有静态对象,但是不new

  • 3、暴漏一个静态公共方法getInstance(),判断私有静态对象是否被创建,被创建就原封不动的返回,未创建就创建一个(由于构造器私有化,只能在getInstance()中创建对象,不会在外部创建)。

实现示例:

package com.single_design.lazy;

public class single02{
    public static void main(String[] args) {
        //不new没法用
//        new Cat("mxy");
        Cat cat1 = Cat.getInstance();
        System.out.println(cat1.n);
        Cat cat2 = Cat.getInstance();
        System.out.println(cat1 == cat2);
    }
}
//输出:
//构造器被使用
//999
//true


class Cat {
    private String name;
    //1、构造器私有化
    private Cat(String name) {
        System.out.println("构造器被使用");
        this.name = name;
    }
    public static int n = 999;
    //2、定义一个静态属性对象,但不new
    private static Cat cat;
    //3、暴漏一个静态公共方法,可以返回一个Cat对象
    public static Cat getInstance(){
        //如果还没有创建猫,就new 一个
        if(cat == null){
            cat = new Cat("mxy");
        }
        return cat;
    }
}

饿汉式与懒汉式对比:

  • 主要区别是对象创建的时机不同,饿汉式是在类加载的时候就创建对象,而懒汉式是在使用时才创建对象

  • 饿汉式不存在线程安全问题,懒汉式存在线程安全问题(同时有多个线程进入getInstance方法时,在一瞬间会创建多个对象,没有真正实现单例模式)

  • 饿汉式存在资源浪费的可能性,若一个对象没使用就会浪费资源

  • java.lang.Runtime就是经典的单例模式

final关键字

可修饰类、属性、方法和局部变量

用途:

  • 不希望类被继承时

  • 不希望父类中的某个方法被子类重写/覆盖

  • 不希望类的某个属性的值被修改

  • 不希望某个 局部变量 被修改

使用细节:

  • final修饰的属性又称为常量, 名字要按照XX_XX_XX来命名

  • final修饰的属性在定义时必须赋初值,赋值可选以下位置:

    • 定义时 public final double TAX_RATE = 0.08;
    • 在构造器中
    • 在代码块中
  • 如果final修饰的属性是静态(public final static int MAX_NUM = 23435465;)的,则初始化的位置只能是

    • 定义时
    • 静态代码块中
    • 注意不能在构造器中赋值
  • final类不能继承,但是可以实例化对象(“能使用”)

  • 类不是final类但是有final方法,则该方法虽不能重写,但是可以被继承(“能使用”)

  • 如果一个类已经是final类了,就不用再把方法也定义成final方法了

  • final不能修饰构造器

  • final往往和static搭配使用,底层编译器对此做了优化处理(用类中的属性不会导致类加载),效率更高

public class StaticFinal {
    public static void main(String[] args) {
        System.out.println(AA.num);
    }
}

class AA{
    public final static int num = 1000;
    static {
        System.out.println("类AA被加载了");
    }
}

//输出
1000
//若不加final,会使得类被加载,浪费内存空间
  • 包装类(Integer,Double,Float,Boolean等类都是final类),String类也是final类

抽象类

父类的某些方法不能确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类

抽象方法:没有实现的方法,即没有方法体

//方法声明为抽象方法的时候也要将其类声明为抽象类
abstract class Animal {
    private String name;
    public abstract void eat();
}

注意:

  • 抽象方法不能有方法体

  • 一旦一个方法被声明为抽象方法,其所在的类必须被声明为抽象类

  • 子类一定要实现抽象方法,否则会报错

  • 抽象类不能被实例化(不能new)

  • 抽象类可以没有抽象方法

  • abstract只能修饰类或者方法,不能修饰属性

  • 抽象类可以拥有任意成员(因为其本质还是类,可以有类的各种成员)构造器、属性、非抽象方法、代码块

  • 若一个类继承了抽象类,则这个类必须要实现这个抽象类所有的抽象方法,除非这个子类也是抽象的

  • 抽象类、方法不能使用private、final和static来修饰,这些关键字都是和重写相悖的

抽象类最佳实践:模板设计模式

好处:提高代码复用性

示例:若不使用抽象类,则需要在每个类中都写calculateTime

public abstract class TestTemplate {
    public abstract void job();
    public void calculateTime(){
        long start = System.currentTimeMillis();
        job();//动态绑定机制,优先寻找子类的job()方法
        long end = System.currentTimeMillis();
        System.out.println("job()方法执行时间:"+(end - start));
    }
}






public class TestFile {
    public static void main(String[] args) {
        A a = new A();
        B b = new B();
        a.calculateTime();
        b.calculateTime();
    }
}

class A extends TestTemplate{
    public void job(){
        long sum = 0;
        for (int i = 0; i < 1000000; i++) {
            sum += i;
        }
    }
}

class B extends TestTemplate{
    public void job(){
        int sum = 1;
        for (int i = 0; i < 100000; i++) {
            sum *= i;
        }
    }
}

接口

快速入门:

基本语法:

interface 接口名{

​ //属性

​ //方法

}

class 类名 implements 接口{

​ //自己的属性

​ //自己的方法

​ //必须实现的接口的抽象方法

}

public interface UsbInterface {
    public void start();
    public void stop();
}

public class Phone implements UsbInterface {
    @Override
    public void start() {
        System.out.println("Phone start");
    }

    @Override
    public void stop() {
        System.out.println("Phone stop");
    }
}

public class Camera implements UsbInterface {
    @Override
    public void start() {
        System.out.println("Camera start");
    }

    @Override
    public void stop() {
        System.out.println("Camera stop");
    }
}

public class Computer {
    public void work(UsbInterface usbInterface){
        usbInterface.start();
        usbInterface.stop();
    }
}

public class Test01 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        Camera camera = new Camera();
        Computer computer = new Computer();
        computer.work(phone);
        computer.work(camera);
    }
}

//输出:
Phone start
Phone stop
Camera start
Camera stop

应用场景:

便于控制与管理,保证程序的规范性

注意事项:

  • 在Jdk7.0之前,接口里的所有方法都没有方法体

  • 在Jdk8.0后,接口类中可以有静态方法,默认方法,即可以有方法的具体实现(要用default关键字修饰)

    public interface Interface01 {
        public void hi();
        //默认方法
        default public void ok(){
            System.out.println("ok!");
        }
        //静态方法
        public static void happy(){
            System.out.println("Happy!");
        }    
    }
    
  • 接口中的所有未实现的方法都是抽象方法,可以省略abstract

  • 接口不能被实例化,继承接口的子类才能被实例化

  • 接口中所有方法都默认是public方法,接口中的抽象方法可以不被abstract修饰

  • 抽象类实现接口,可以不实现接口的方法(都是抽象方法)

    public abstract class Up implements UsbInterface {
    }
    
  • 一个类可以同时实现多个接口(一个人只能有一对血缘双亲,但是可以有很多个项目经理)

    interface IA{
        void hi();
    }
    
    interface IB{
        void say();
    }
    
    class Pp implements IA, IB{
    
        @Override
        public void hi() {
            System.out.println("hi");
        }
    
        @Override
        public void say() {
            System.out.println("say");
        }
    }
    
  • 接口中的属性只能是final的,而且因此的修饰符是public static final修饰符,例如:public static final int a = 10;(必须初始化)

    public class Interface02 {
        public static void main(String[] args) {
            //证明是static
            System.out.println(IA.a);
            //证明是final:
            //IA.a = 20;//报错
        }
    }
    
    interface IA{
        int a = 10;//等价于public static final int a = 10;
        void hi();
    }
    
  • 接口中属性的访问形式:接口名字.属性名

  • 接口不能继承其他类,但是可以继承其他接口

    interface IA{
        int a = 10;//等价于public static final int a = 10;
        void hi();
    }
    
    interface IB{
        void say();
    }
    
    interface IC extends IB, IA{
        void hello();
    }
    
  • 接口的修饰符只能是public 与默认

  • 接口的实现与继承类似,可以继承接口中的属性,例如:

    public class Test02 {
        public static void main(String[] args) {
            B b = new B();
            System.out.println(A.a);
            System.out.println(b.a);
            System.out.println(B.a);
        }
    }
    
    interface A{
        int a = 33;
    }
    
    class B implements A{
    
    }
    

接口与继承对比

继承一个类,类中的方法就自然的能被子类使用

接口相当于对单继承模式的一个补充,例子:

public class Test03 {
    public static void main(String[] args) {
        LittleMonkey sun = new LittleMonkey("孙行者");
        sun.climb();
        sun.fly();
        sun.swim();
    }
}

class Monkey{
    public String getName() {
        return name;
    }

    public void setName(String name) {
        setName(name);
    }

    private String name;

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

class LittleMonkey extends Monkey implements Bird, Fish{
    public LittleMonkey(String name) {
        super(name);
    }
    public void climb(){
        System.out.println(getName()+" 会爬树");
    }

    @Override
    public void fly() {
        System.out.println(getName()+" 会飞");
    }

    @Override
    public void swim() {
        System.out.println(getName()+" 会游泳");
    }
}

interface Bird{
    void fly();
}

interface Fish{
    void swim();
}

总结:

  • 子类继承父类就自动拥有父类的功能
  • 如果子类需要拓展功能,就可以通过实现接口的方式实现拓展
  • 解决问题的区别
    • 继承解决代码的复用性和可维护性
    • 接口的价值在于设计,设计好各种规范的方法,由其他类实现这些方法
  • 接口比继承更加灵活,继承需要is - a,而接口只需要 like - a
  • 接口在一定程度上实现代码解耦

接口的多态特性

多态参数

public class Test01 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        Camera camera = new Camera();
        Computer computer = new Computer();
        computer.work(phone);
        computer.work(camera);
    }
}


public class Computer {
    //解读:
    //usbInterface形参是接口类型UsbInterface
    //此方法接收实现了UsbInterface接口的类的对象实例
    //只要是实现了UsbInterface的对象都可以传入
    public void work(UsbInterface usbInterface){
        usbInterface.start();
        usbInterface.stop();
    }
}


public class Camera implements UsbInterface {
    @Override
    public void start() {
        System.out.println("Camera start");
    }

    @Override
    public void stop() {
        System.out.println("Camera stop");
    }
}

public class Phone implements UsbInterface {
    @Override
    public void start() {
        System.out.println("Phone start");
    }

    @Override
    public void stop() {
        System.out.println("Phone stop");
    }
}
接口引用可以指向实现了接口的类的对象
public class Test04 {
    public static void main(String[] args) {
        //接口引用可以指向实现了接口的类的对象
        UsbInterface usb1 = new Phone();
        usb1.start();
        usb1 = new Camera();
        usb1.stop();
    }
}

多态数组

接口同样可以向下转型
public class PolyArray {
    public static void main(String[] args) {
        UsbInterface[] usbArray = new UsbInterface[5];
        usbArray[0] = new Phone();
        usbArray[1] = new Camera();
        usbArray[2] = new Camera();
        usbArray[3] = new Phone();
        usbArray[4] = new Camera();
        for (int i = 0; i < usbArray.length; i++) {
            usbArray[i].start();
            //若是Phone类,调用独有call方法
            if(usbArray[i] instanceof Phone){
                //向下转型
                Phone phone = (Phone)usbArray[i];
                phone.call();
            }
            usbArray[i].stop();
        }
    }
}

接口的多态传递现象

public class MoreGive {
    public static void main(String[] args) {
        IG ig = new Teacher();
        IH ih = new Teacher();
        ig.hi();
    }
}

interface IH{
    void hi();
}
interface IG extends IH{}
class Teacher implements IG{

    @Override
    public void hi() {
        System.out.println("hi");
    }
}

当继承的父类与实现的接口有重复属性时:

public class Test06 {
    public static void main(String[] args) {
        C1 c1 = new C1();
        c1.PrintX();
    }
}

interface A1{
    int x = 10;
}

class B1{
    int x = 20;
}

class C1 extends B1 implements A1{
    public void PrintX(){
        //System.out.println(x);//错误,原因不明确的x
        //可以明确的指定x
        //访问接口的x就使用A1.x,父类的就super.x
        System.out.println(A1.x);
        System.out.println(super.x);
    }
}

内部类(重难点)

一个类的内部又完整的嵌套了另一个类的结构,被嵌套的类称为内部类,嵌套它的类称为外部类

类的五大成员:

  • 属性

  • 方法

  • 构造器

  • 代码块

  • 内部类

基本语法

class Outer{
    class Inner{
        
    }
}
class Other

内部类的分类:

  • 定义在外部类局部位置上(方法内、代码块内)

    • 局部内部类(有类名)
    • 匿名内部类(无类名)
  • 定义在外部类的成员位置上

    • 成员内部类(无static修饰)
    • 静态内部类(有static修饰)

局部内部类的使用

定义在方法中或者代码块里,作用域在方法体或者代码块中,本质仍是一个类
  • 可以直接访问外部类的所有成员,包括私有成员
  • 不能添加访问修饰符,因为它的地位与一个局部变量相同,局部变量是不可以用修饰符的。但是与局部变量相同,可以用final 修饰
  • 作用域:仅仅在定义它的方法或者代码块中
  • 局部内部类访问外部类的成员可以直接访问,而外部类访问局部内部类的成员需要创建对象再访问,且必须在作用域内

使用示例:

package com.innerclass_;

public class Test01 {
    public static void main(String[] args) {
        Outer outer = new Outer(101);
        outer.m1();
        outer.inner01.speak();
    }
}

class Outer{
    class Inner01{//内部类,在Outer类的内部
        public void speak(){
            System.out.println("Inner01中的n1:" + n1);
        }
    }
    Inner01 inner01 = new Inner01();
    //作用域仅在定义它的方法中
    private int n1 = 100;
    public void m1(){
        System.out.println("方法m1");
        //可以用final修饰
        final class Inner02{//局部内部类
            //可以直接访问外部类的所有成员
            public void f1(){
                System.out.println("Inner02中的n1:"+n1);
                System.out.print("Inner02中的:");
                m2();
            }
        }
        Inner02 inner02 = new Inner02();
        //在方法中使用内部类需要创建Inner02对象,然后调用内部类方法即可
        inner02.f1();
    }
    private void m2(){
        System.out.println("方法m2");
    }
    {
        System.out.println("Outer代码块");
    }

    public Outer(int n1) {
        System.out.println("Outer构造器");
        setN1(n1);
    }

    public int getN1() {
        return n1;
    }

    public void setN1(int n1) {
        this.n1 = n1;
    }
}

//输出:
Outer代码块
Outer构造器
方法m1
Inner02中的n1:101
Inner02中的:方法m2
Inner01中的n1:101
  • 外部其他类不能访问局部内部类(因为局部内部类是一个局部变量)
  • 如果外部类和局部内部类的成员重名,默认遵守就近原则,如果想访问外部类的成员,可以使用外部类名.this.成员去访问。
    示例:
    public class Test01 {
        public static void main(String[] args) {
            Outer outer = new Outer(101);
            outer.inner01.getName();
            outer.inner01.getInner01();
            outer.inner01.sayHi();
            outer.inner01.getOuterSayHi();
        }
    }
    
    class Outer{
        class Inner01{//内部类,在Outer类的内部
            private String name = "yll";
            public void speak(){
                System.out.println("Inner01中的n1:" + n1);
            }
    
            public void getName(){
                System.out.println("Inner01中的成员name:"+ name);
                //Outer本质是外部类的对象,即哪个对象调用了m1,Outer.this就是哪个对象
                System.out.println("Inner02中的成员name:"+Outer.this.name);
            }
    
            public void getInner01(){
                System.out.println(inner01);
            }
    
            public void sayHi(){
                System.out.println("Inner01 say hi.");
            }
    
            public void getOuterSayHi(){
                System.out.print("Inner01中访问sayHi:");
                Outer.this.sayHi();
            }
        }
        Inner01 inner01 = new Inner01();
        //作用域仅在定义它的方法中
        //Inner02 inner02 = new Inner02();
        private String name = "mxy";
        public void sayHi(){
            System.out.println("Outer say hi");
        }
    }
    
    //输出:
    Outer代码块
    Outer构造器
    Inner01中的成员name:yll
    Inner02中的成员name:mxy
    com.innerclass_.Outer$Inner01@1b6d3586
    Inner01 say hi.
    Inner01中访问sayHi:Outer say hi
    

匿名内部类

匿名内部类是定义在外部类的局部位置中的类,且没有类名

new 类或接口(参数列表){
    类体
};
  • 本质是类,且是内部类
  • 内部类没有名字(在JVM中有,但被隐藏起来了)
  • 内部类同时也是一个对象

匿名内部类解决的问题:

    //基于接口的匿名内部类
    //需求:想使用接口A,并创建对象,且此对象只使用一次,以后不再使用
    //传统方式:写一个类实现接口,并创建对象使用
    //不足之处:
    // 假如需要用许多调用不同的cry方法的对象话需要创建许多类,不方便
    //        A a = new Tiger();
//        a.cry();
//        a = new Cat();
//        a.cry();
        //由此引出匿名类

例子(基于接口的匿名内部类):

public class AnonymousInnerClass {
    public static void main(String[] args) {
        Outer2 outer2 = new Outer2();
        outer2.method();
    }
}

class Outer2{
    private int n1 = 10;
    public void method(){
        //基于接口的匿名内部类
        //需求:想使用接口A,并创建对象,且此对象只使用一次,以后不再使用
        //传统方式:写一个类实现接口,并创建对象使用
        //不足之处:
        // 假如需要用许多调用不同的cry方法的对象话需要创建许多类,不方便
//        A a = new Tiger();
//        a.cry();
//        a = new Cat();
//        a.cry();
        //由此引出匿名类,使用匿名类简化开发
        A tiger = new A(){
            @Override
            public void cry() {
                System.out.println("Tiger cry~");
            }
        };
        //tiger的运行类型是什么?就是匿名内部类
        //底层:
//        class XXXX implements A{
//
//            @Override
//            public void cry() {
//                System.out.println("Tiger cry~");
//            }
//        }
        //可以根据此方法来查看类名:(类名为外部类加$加序号)
        System.out.println("tiger的运行类型="+tiger.getClass());
        //输出为tiger的运行类型=class com.anonymous_.Outer2$1
        //new 表示在底层创建了匿名类Outer2$1后立马就创建了实例,并把地址返回给tiger
        //匿名类不能重复使用,但是对象可以
        tiger.cry();
    }
}
interface A{
    public void cry();
}

class Tiger implements A{

    @Override
    public void cry() {
        System.out.println("Tiger Cry~");
    }
}

class Cat implements A{

    @Override
    public void cry() {
        System.out.println("Cat meow meow~");
    }
}

基于类的匿名内部类:

class Outer3{
    public void sayHi(){
        //基于类的匿名内部类
        //father的编译类型:Father
        //运行类型:Outer2$2
        //不带大括号运行类型就是Father
        //注意("mxy")会传递给Father构造器,但是不能重写Father构造器
        Father father = new Father("mxy"){
            @Override
            public void speak() {
                System.out.println("内部匿名类重写了speak方法");
            }
        };
        //输出:class com.anonymous_.Outer2$1
        System.out.println("father对象的运行类型:"+ father.getClass());
    }
}

class Father{
    private String name;
    
    public void speak(){
        System.out.println("类Father  speak");
    }
    
    public String getName() {
        return name;
    }

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

    public Father(String name) {
        setName(name);
    }
}

基于抽象类的匿名内部类:

抽象类的匿名类必须实现,原因与抽象类的子类必须实现相同
public class AnonymousTest {
    public static void main(String[] args) {
        Outer.play();
    }
}

class Outer{

    public static void play(){
        Animal animal = new Animal() {
            //必须实现
            @Override
            public void eat() {
                System.out.println("小狗吃骨头...");
            }
        };
        animal.eat();
    }
}

abstract class Animal{
    public abstract void eat();
}

匿名内部类细节:

  • 匿名内部类即是类的定义,其本身也是一个对象
  • 可以直接调用
    class Outer{
    
        public static void play(){
            new Animal() {
                @Override
                public void eat() {
                    System.out.println("小猫吃鱼...");
                }
            }.eat();
            //只调用一次eat方法可以这么写
        }
    }
    
  • 可以访问外部类的所有成员,包括私有的
  • 不能添加访问修饰符,因为其地位就是局部变量
  • 外部其他类不能访问匿名内部类
  • 若外部类和匿名内部类的成员重名,匿名内部类访问时遵循就近原则,若非要访问外部类成员,可以用 外部类名.this.成员去访问

匿名内部类的实践:

  • 当作实参直接传递,简洁高效
    public class Use {
        public static void main(String[] args) {
            show(new AA(){
    
                @Override
                public void cry() {
                    System.out.println("I'm crying...");
                }
            });
        }
        public static void show(AA a){
            a.cry();
        }
    }
    //若不使用匿名内部类,需要对接口进行实现,然后再new对象传参,很麻烦(硬编码)
    interface AA{
        public void cry();
    }
    

成员内部类

成员内部类定义在外部类的成员位置,并且无static修饰

注意:

  • 可以访问外部类所有成员,包括私有的
    public class Test1 {
        public static void main(String[] args) {
            Outer outer = new Outer();
            outer.t1();
        }
    }
    class Outer{
        private int n1 = 10;
        class Inner{
            public void say(){
                System.out.println("Outer的n1:"+n1);
            }
        }
    
        public void t1(){
            Inner inner = new Inner();
            inner.say();
        }
    }
    
  • 可以添加任意的访问修饰符(public protected 默认 private),因为其地位就是一个成员
  • 作用域:
    • 与外部类其他成员一致,为整个类体
    • 成员内部类访问外部类可以直接访问
    • 外部类访问成员内部类需要先创建再访问
    • 外部其他类访问成员内部类(可以的),访问方式如下:
      • 创建外部对象后new来访问
        public class Test1 {
            public static void main(String[] args) {
                Outer outer = new Outer();
                //1、
                Outer.Inner inner = outer.new Inner();
                //new Inner();Inner是成员 因此需要先创建Outer后再创建
            }
        }
        
      • 在外部类中创建一个返回new内部类的方法,通过调用外部类的方法来访问
        public class Test1 {
            public static void main(String[] args) {
                Outer outer = new Outer();
                outer.t1();
                //2、
                Outer.Inner inner1 = outer.getInner();
            }
        }
        
  • 如果外部类和内部类的成员重名时,内部类访问会遵顼就近原则, 因此若想访问外部类的成员,需要(外部类名.this.成员)去访问

静态内部类

定义在外部类的成员位置,有static修饰

注意:

  • 可以直接访问外部类的所有静态成员,包括私有的,但是不能访问非静态成员
  • 可以添加任意访问修饰符,因为其地位就是一个成员
  • 作用域:同其他的成员一样,为整个类体
    public class Test1 {
        public static void main(String[] args) {
            Outer outer = new Outer();
            outer.useInner();
        }
    }
    
    class Outer{
        private static int n1 = 1000;
        private double nt = 99.99;
        public static class Inner{
            public void say(){
                System.out.println("Outer类中的n1:"+n1);
                //System.out.println("Outer类中的nt"+ nt);
                //不可访问非静态成员
            }
        }
        public void useInner(){
            Inner inner = new Inner();
            inner.say();
        }
    }
    
  • 访问:
    • 内部类可以访问外部类所有的静态成员,但是不能访问非静态成员
    • 外部类访问内部类可以通过类名.静态成员来访问,或创建内部类访问其非静态成员
    • 外部其他类访问内部类的方法:
      • 1、由于是静态内部类,可以直接通过类名访问(在访问权限内)
        public class Test1 {
            public static void main(String[] args) {
                //外部其他类使用Inner不需要新建Outer
                Outer.Inner inner = new Outer.Inner();
            }
        }
        
      • 2、编写一个方法,返回静态内部类(尽量写为静态方法)
        public class Test1 {
            public static void main(String[] args) {
                //静态方法返回Inner
                Outer.Inner inner1 = Outer.getInner();
            }
        }
        
  • 重名:依然遵守就近原则,若要访问外部类,直接用外部类名.成员去访问(只能是静态成员)

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值