面向对象之内部类—【成员内部类】【静态内部类】【局部内部类】【Lambda表达式】【方法引用】


内部类

上一章我们详细介绍了类,我们现在已经知道该如何创建类、使用类了。当然,类的创建其实可以有多种多样的方式,并不仅仅局限于普通的创建。内部类顾名思义,就是创建在内部的类,那么具体是什么的内部呢,我们接着就来讨论一下。

**注意:**内部类很多地方都很绕,所以说一定要仔细思考。

成员内部类

我们可以直接在类的内部定义成员内部类:

public class Test {
    public class Inner {   //内部类也是类,所以说里面也可以有成员变量、方法等,甚至还可以继续套娃一个成员内部类
        public void test(){
            System.out.println("我是成员内部类!");
        }
    }
}

成员内部类和成员方法、成员变量一样,是对象所有的,而不是类所有的,如果我们要使用成员内部类,那么就需要:

public static void main(String[] args) {
    Test test = new Test();   //我们首先需要创建对象
    Test.Inner inner = test.new Inner();   //成员内部类的类型名称就是 外层.内部类名称
}

虽然看着很奇怪,但是确实是这样使用的。我们同样可以使用成员内部类中的方法:

public static void main(String[] args) {
    Test test = new Test();
    Test.Inner inner = test.new Inner();
    inner.test();
}

注意,成员内部类也可以使用访问权限控制,如果我们我们将其权限改为private,那么就像我们把成员变量访问权限变成私有一样,外部是无法访问到这个内部类的:

image-20220924122217070

可以看到这里直接不认识了。

这里我们需要特别注意一下,在成员内部类中,是可以访问到外层的变量的:

public class Test {
    private final String name;
    
    public Test(String name){
        this.name = name;
    }
    public class Inner {
        public void test(){
            System.out.println("我是成员内部类:"+name);
         		//成员内部类可以访问到外部的成员变量
          	//因为成员内部类本身就是某个对象所有的,每个对象都有这样的一个类定义,这里的name是其所依附对象的
        }
    }
}

image-20220924123600217

每个类可以创建一个对象,每个对象中都有一个单独的类定义,可以通过这个成员内部类又创建出更多对象,套娃了属于是。

所以说我们在使用时:

public static void main(String[] args) {
    Test a = new Test("小明");
    Test.Inner inner1 = a.new Inner();   //依附于a创建的对象,那么就是a的
    inner1.test();

    Test b = new Test("小红");
    Test.Inner inner2 = b.new Inner();  //依附于b创建的对象,那么就是b的
    inner2.test();
}

那现在问大家一个问题,外部能访问内部类里面的成员变量吗?

那么如果内部类中也定义了同名的变量,此时我们怎么去明确要使用的是哪一个呢?

public class Test {
    private final String name;

    public Test(String name){
        this.name = name;
    }
    public class Inner {

        String name;
        public void test(String name){
            System.out.println("方法参数的name = "+name);    //依然是就近原则,最近的是参数,那就是参数了
            System.out.println("成员内部类的name = "+this.name);   //在内部类中使用this关键字,只能表示内部类对象
            System.out.println("成员内部类的name = "+Test.this.name);
          	//如果需要指定为外部的对象,那么需要在前面添加外部类型名称
        }
    }
}

包括对方法的调用和super关键字的使用,也是一样的:

public class Inner {

    String name;
    public void test(String name){
        this.toString();		//内部类自己的toString方法
        super.toString();    //内部类父类的toString方法
        Test.this.toString();   //外部类的toSrting方法
        Test.super.toString();  //外部类父类的toString方法
    }
}

所以说成员内部类其实在某些情况下使用起来比较麻烦,对于这种成员内部类,我们一般只会在类的内部自己使用。

静态内部类

前面我们介绍了成员内部类,它就像成员变量和成员方法一样,是属于对象的,同样的,静态内部类就像静态方法和静态变量一样,是属于类的,我们可以直接创建使用。

public class Test {
    private final String name;

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

    public static class Inner {
        public void test(){
            System.out.println("我是静态内部类!");
        }
    }
}

不需要依附任何对象,我们可以直接创建静态内部类的对象:

public static void main(String[] args) {
    Test.Inner inner = new Test.Inner();   //静态内部类的类名同样是之前的格式,但是可以直接new了
  	inner.test();
}

静态内部类由于是静态的,所以相对外部来说,整个内部类中都处于静态上下文(注意只是相当于外部来说)是无法访问到外部类的非静态内容的:

在这里插入图片描述

只不过受影响的只是外部内容的使用,内部倒是不受影响,还是跟普通的类一样:

public static class Inner {

    String name;
    public void test(){
        System.out.println("我是静态内部类:"+name);
    }
}

其实也很容易想通,因为静态内部类是属于外部类的,不依附任何对象,那么我要是直接访问外部类的非静态属性,那到底访问哪个对象的呢?这样肯定是说不通的。

局部内部类

局部内部类就像局部变量一样,可以在方法中定义。

public class Test {
    private final String name;

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

    public void hello(){
        class Inner {    //直接在方法中创建局部内部类
            
        }
    }
}

既然是在方法中声明的类,那作用范围也就只能在方法中了:

public class Test {
    public void hello(){
        class Inner{   //局部内部类跟局部变量一样,先声明后使用
            public void test(){
                System.out.println("我是局部内部类");
            }
        }
        
        Inner inner = new Inner();   //局部内部类直接使用类名就行
        inner.test();
    }
}

只不过这种局部内部类的形式,使用频率很低,基本上不会用到,所以说了解就行了。

匿名内部类

匿名内部类是我们使用频率非常高的一种内部类,它是局部内部类的简化版。

还记得我们在之前学习的抽象类和接口吗?在抽象类和接口中都会含有某些抽象方法需要子类去实现,我们当时已经很明确地说了不能直接通过new的方式去创建一个抽象类或是接口对象,但是我们可以使用匿名内部类。

public abstract class Student {
    public abstract void test();
}

正常情况下,要创建一个抽象类的实例对象,只能对其进行继承,先实现未实现的方法,然后创建子类对象。

而我们可以在方法中使用匿名内部类,将其中的抽象方法实现,并直接创建实例对象:

public static void main(String[] args) {
    Student student = new Student() {   //在new的时候,后面加上花括号,把未实现的方法实现了
        @Override
        public void test() {
            System.out.println("我是匿名内部类的实现!");
        }
    };
    student.test();
}

此时这里创建出来的Student对象,就是一个已经实现了抽象方法的对象,这个抽象类直接就定义好了,甚至连名字都没有,就可以直接就创出对象。

匿名内部类中同样可以使用类中的属性(因为它本质上就相当于是对应类型的子类)所以说:

Student student = new Student() {
    int a;   //因为本质上就相当于是子类,所以说子类定义一些子类的属性完全没问题
    
    @Override
    public void test() {
        System.out.println(name + "我是匿名内部类的实现!");   //直接使用父类中的name变量
    }
};

同样的,接口也可以通过这种匿名内部类的形式,直接创建一个匿名的接口实现类:

public static void main(String[] args) {
    Study study = new Study() {
        @Override
        public void study() {
            System.out.println("我是学习方法!");
        }
    };
    study.study();
}

当然,并不是说只有抽象类和接口才可以像这样创建匿名内部类,普通的类也可以,只不过意义不大,一般情况下只是为了进行一些额外的初始化工作而已。

Lambda表达式

前面我们介绍了匿名内部类,我们可以通过这种方式创建一个临时的实现子类。

特别的,如果一个接口中有且只有一个待实现的抽象方法,那么我们可以将匿名内部类简写为Lambda表达式:

public static void main(String[] args) {
    Study study = () -> System.out.println("我是学习方法!");   //是不是感觉非常简洁!
  	study.study();
}

在初学阶段,为了简化学习,各位小伙伴就认为Lambda表达式就是匿名内部类的简写就行了(Lambda表达式的底层其实并不只是简简单单的语法糖替换,感兴趣的可以在新特性篇视频教程中了解)

那么它是一个怎么样的简写规则呢?我们来看一下Lambda表达式的具体规范:

  • 标准格式为:([参数类型 参数名称,]...) ‐> { 代码语句,包括返回值 }
  • 和匿名内部类不同,Lambda仅支持接口,不支持抽象类
  • 接口内部必须有且仅有一个抽象方法(可以有多个方法,但是必须保证其他方法有默认实现,必须留一个抽象方法出来)

比如我们之前写的Study接口,只要求实现一个无参无返回值的方法,所以说直接就是最简单的形式:

() -> System.out.println("我是学习方法!");   //跟之前流程控制一样,如果只有一行代码花括号可省略

当然,如果有一个参数和返回值的话:

public static void main(String[] args) {
    Study study = (a) -> {
        System.out.println("我是学习方法");
        return "今天学会了"+a;    //实际上这里面就是方法体,该咋写咋写
    };
    System.out.println(study.study(10));
}

注意,如果方法体中只有一个返回语句,可以直接省去花括号和return关键字:

Study study = (a) -> {
    return "今天学会了"+a;   //这种情况是可以简化的
};
Study study = (a) -> "今天学会了"+a;

如果参数只有一个,那么可以省去小括号:

Study study = a -> "今天学会了"+a;

是不是感觉特别简洁,实际上我们程序员追求的就是写出简洁高效的代码,而Java也在朝这个方向一直努力,近年来从Java 9开始出现的一些新语法基本都是各种各样的简写版本。

如果一个方法的参数需要的是一个接口的实现:

public static void main(String[] args) {
    test(a -> "今天学会了"+a);   //参数直接写成lambda表达式
}

private static void test(Study study){
    study.study(10);
}

当然,这还只是一部分,对于已经实现的方法,如果我们想直接作为接口抽象方法的实现,我们还可以使用方法引用。

方法引用

方法引用就是将一个已实现的方法,直接作为接口中抽象方法的实现(当然前提是方法定义得一样才行)

public interface Study {
    int sum(int a, int b);   //待实现的求和方法
}

那么使用时候,可以直接使用Lambda表达式:

public static void main(String[] args) {
    Study study = (a, b) -> a + b;
}

只不过还能更简单,因为Integer类中默认提供了求两个int值之和的方法:

//Integer类中就已经有对应的实现了
public static int sum(int a, int b) {
    return a + b;
}

此时,我们可以直接将已有方法的实现作为接口的实现:

public static void main(String[] args) {
    Study study = (a, b) -> Integer.sum(a, b);   //直接使用Integer为我们通过好的求和方法
    System.out.println(study.sum(10, 20));
}

我们发现,Integer.sum的参数和返回值,跟我们在Study中定义的完全一样,所以说我们可以直接使用方法引用:

public static void main(String[] args) {
    Study study = Integer::sum;    //使用双冒号来进行方法引用,静态方法使用 类名::方法名 的形式
    System.out.println(study.sum(10, 20));
}

方法引用其实本质上就相当于将其他方法的实现,直接作为接口中抽象方法的实现。任何方法都可以通过方法引用作为实现:

public interface Study {
    String study();
}

如果是普通从成员方法,我们同样需要使用对象来进行方法引用:

public static void main(String[] args) {
    Main main = new Main();
    Study study = main::lbwnb;   //成员方法因为需要具体对象使用,所以说只能使用 对象::方法名 的形式
}

public String lbwnb(){
    return "卡布奇诺今犹在,不见当年倒茶人。";
}

因为现在只需要一个String类型的返回值,由于String的构造方法在创建对象时也会得到一个String类型的结果,所以说:

public static void main(String[] args) {
    Study study = String::new;    //没错,构造方法也可以被引用,使用new表示
}

反正只要是符合接口中方法的定义的,都可以直接进行方法引用,对于Lambda表达式和方法引用,在Java新特性介绍篇视频教程中还有详细的讲解,这里就不多说了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值