Java 基础 内部类和 lambda

发布于 2024-10-26 22:30:17 字数 10206 浏览 10 评论 0

内部类,匿名类,匿名内部类

内部类

什么是内部类?简单来说就是定义在别的类内部的类,下面就是一个简单的内部类

class outer{
    class inner{

    }
}

定义内部类的哲学意义是什么?

某些外部类和内部类具有包含关系,是强关联的而且内部类离开外部类不再具有存在的意义,举个例子,比如汽车底盘之于汽车的关系。

从代码使用的角度来讲

它可以直接访问修改外部类的值

内部类

public class OuterClass {
    private Integer a;
    private static String staticString= "outer static";
    private String modifyString = "outer";

    private String same = "outer";
    public OuterClass(Integer a) {
        this.a = a;
    }

    private void outerClassMethod(){
        System.out.println("outClassMethod is called");
    }
    private static void outerClassStaticMethod(){
        System.out.println("outClassStaticMethod is called");
    }

    public String getModifyString() {
        return modifyString;
    }

    class InnerClass{
        private String same = "inner";

        //可以直接通过方法名调用到外部类私有方法和私有对象
        public Integer getA(){
            outerClassMethod();
            return a;
        }

        //这是重名情况,最小作用域原则
        public void getSame(){
            //调用自己的变量
            System.out.println(same);
            //调用外部类的变量
            System.out.println(OuterClass.this.same);
        }

        public void modify(){
            modifyString = "inner";
        }
    }}

当我们调用时,要记住这个内部类要和外部类紧密结合,即这个内部类无法脱离外部类使用

所以要这么写

       //等价写法
//        OuterClass outerClass = new OuterClass(10);
//        OuterClass.InnerClass innerClass1 = outerClass.new InnerClass();
        OuterClass.InnerClass innerClass = new OuterClass(10).new InnerClass();
        System.out.println(innerClass.getA());
        innerClass.getSame();

控制台输出

outClassMethod is called
10
inner
outer

对于修改变量的操作

        OuterClass outerClass = new OuterClass(10);
        OuterClass.InnerClass innerClass1 = outerClass.new InnerClass();
        System.out.println(outerClass.getModifyString());
        innerClass1.modify();
        System.out.println(outerClass.getModifyString());

控制台输出

outer
inner

静态内部类

public class OuterClass {
    private Integer a;
    private static String staticString= "outer static";
    private String modifyString = "outer";

    private String same = "outer";
    public OuterClass(Integer a) {
        this.a = a;
    }

    private void outerClassMethod(){
        System.out.println("outClassMethod is called");
    }
    private static void outerClassStaticMethod(){
        System.out.println("outClassStaticMethod is called");
    }

    public String getModifyString() {
        return modifyString;
    }
    static class InnerStaticClass{
        //只能调用外部类的静态方法和字段
        public void getOuterStatic(){
            outerClassStaticMethod();
            System.out.println(staticString);
        }
    }

}

静态内部类与内部类不同的一点在于它不需要依赖外部类实例化

 OuterClass.InnerStaticClass staticClass = new OuterClass.InnerStaticClass();
        staticClass.getOuterStatic();

总结

内部类,静态内部类比较   
 字段访问权限函数访问权限实例化是否依赖外部类
内部类全部全部
静态内部类静态静态

探秘实现原理*

点开 out 文件夹

1604128774884

再通过反编译可得

1604128924758

1604128933761

即无论是内部类还是静态内部类都是一个独立的类

所以说内部类存在一个对外部类的引用,而静态内部类没有,所以这就是是否需要外部类的区别的根源

考虑一个情况,外部类是个短生命周期的情况下,其内部类被另外的类所引用,即外部类本该被回收但是由于被内部类强引用,导致无法回收,进而内存泄漏。

这种情况就是短生命被长生命周期引用导致的内存泄漏

举个例子 这种情况在回调中比较常见,此时 runnable 被 gc root 持有,runnable 这个匿名内部类持有 Main 的引用 ,故 Main 还在 gc root 链上,无法被回收

明明 Main 生命周期已经结束但由于这个内部类生命周期还没结束导致了”内存泄露“

1650542598762

匿名类

使用

匿名类就是你无法显式看到类名的类

使用方法,使用这种方法就可以达成“new 一个抽象类或者接口的效果”

public static void main(String[] args){
    //通过匿名类获取 
      AbstractClass abstractClass = new AbstractClass() {
            @Override
            protected void method3(String s) {

            }
        };
///通过普通类获取
        NormalClass normalClass = new NormalClass() {
            @Override
            public void method2(String s) {
                super.method2(s);
            }
        };
//通过接口获取
        InterfaceA interfaceA = new InterfaceA() {
            @Override
            public void interfaceMethod() {

            }
        };

}

原理探秘

这也是一种内部类

反编译一下

1604130693983

1604130756584

1604130762593

1604130771544

所以说通俗的说法就是原地实现一个接口或者匿名类

获取实际类名*

通过反射获取

System.out.println(abstractClass.getClass());
        System.out.println(normalClass.getClass());
        System.out.println(interfaceA.getClass());

输出

class com.Main$1
class com.Main$2
class com.Main$3

调用其中被我们额外添加的函数*

因为他们的引用类型被限制为他们的父类或者父接口,所以无法通过引用.方法() 调用我们额外添加的函数

InterfaceA interfaceA = new InterfaceA() {
            @Override
            public void interfaceMethod() {

            }
            public void customer(){
                System.out.println("customer");
            }
        };
//interfaceA.customer(); 无法调用
反射方法
Method method = interfaceA.getClass().getDeclaredMethod("customer");
        method.invoke(interfaceA);
方法句柄
MethodHandle methodHandle = MethodHandles.lookup().findVirtual(interfaceA.getClass(), "customer", MethodType.methodType(void.class));
        methodHandle.invoke(interfaceA);
自动类型推导

var 关键字可以编译期智能推导类型,注意这里的 var 并不是动态类型的 var,其仍是强类型和静态类型的

var interfaceA = new InterfaceA() {
            @Override
            public void interfaceMethod() {

            }
            public void customer(){
                System.out.println("customer");
            }
        };
interfaceA.customer();

这就是你不知道类型,但是编译器知道

反编译下

1604131728030

匿名内部类

就是匿名类和内部类结合,例如上面写的那个例子

用 lambda 表达式简化匿名内部类书写*

如何从签名的角度将一个方法与其他方法分离

方法有三个特性表现自己和其余方法的不同

  1. 调用者,从 OOP 的角度来看就是这个方法归属于哪个类
  2. 方法的返回值,即预期返回类型
  3. 方法形参,即预期的输入类型

另外在 java 中声明的异常也算是方法签名的一部分,这个不在讨论范围内

Method method = HttpVerticle.class.getMethod("start", Promise.class);
System.out.println(method);
//public void verxtx.HttpVerticle.start(io.vertx.core.Promise) throws java.lang.Exception

从数学的角度来说函数就是值对值的映射关系,再从 OOP 的角度来说,如果我声明一个 int 到 Srting 的函数,即

int -> String 的映射,这只是一种关系,即声明签名。其余符合这个映射关系的函数就可以视为这个声明的实现

即如下伪代码也是成立的

void invoke((int-> String)  function){
   String s = function(1);
//.......
}

String transfer(int a){
//....
return a.toString;
}

invoke(transfer);

这个就是函数可以如同对象一样传递

java 如何书写 lambda 表达式

首先 java lambda 表达式比较受限,只支持 sam(Single Abstract Method)接口,而且是以对象的形式支持的

其核心就在于形参,方法体和返回值

()->{} ​形式

观察这个接口

public interface Handler<String> {
  int handle(String s);
}

首先确定形参 String s,其次确定返回值 int

Handler lambda  = (String s) -> {
//do something
return 1;
}

若方法体只有单行且返回值为 int,简化版本 (s) -> s.length

lambda 实质

通过字节码可以看出,调用 lambda 方法时使用了 invokedynamic ,该字节码命令是为了支持动态语言特性而在 Java7 中新增的。Java 的 lambda 表达式实现上也就借助于 invokedynamic 命令。

字节码中每一处含有 invokeDynamic 指令的位置都称为 动态调用点 ,这条指令的第一个参数不再是代表方法调用符号引用的 CONSTANT_Methodref_info 常量,而是变成为 JDK7 新加入的 CONSTANT_InvokeDynamic_info 常量,从这个新常量中可得到 3 项信息:引导方法(Bootstrap Method,此方法存放在新增的 BootstrapMethods 属性中)、方法类型和名称。引导方法是有固定的参数,并且返回值是 java.lang.invoke.CallSite 对象,这个代表真正要执行的目标方法调用。根据 CONSTANT_InvokeDynamic_info 常量中提供的信息,虚拟机可以找到并执行引导方法,从而获得一个 CallSite 对象,最终调用要执行的目标方法。

实际上翻译策略就是翻译到一个新的类,需要外部捕获的变量则是通过构造器传入保存在字段中

此时由于不是内部类的那种需要捕获外部类指针故不太可能存在内存泄露问题

以上看不懂?没关系

我们看反射结果

以我的项目中一个例子,所以实际上就是生成了一个函数而已

for (Method method : HttpVerticle.class.getDeclaredMethods()) {
      System.out.println(method);
    }
//private static void verxtx.HttpVerticle.lambda$start$3(io.vertx.ext.web.RoutingContext)

扩展内容

以上只包含了简单的 ()->{} ​形式,其实还有 类名::方法名 ​这种方法引用的形式,自行学习。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

上一篇:

下一篇:

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

梦冥

暂无简介

0 文章
0 评论
23 人气
更多

推荐作者

謌踐踏愛綪

文章 0 评论 0

开始看清了

文章 0 评论 0

高速公鹿

文章 0 评论 0

alipaysp_PLnULTzf66

文章 0 评论 0

热情消退

文章 0 评论 0

白色月光

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文