循循渐进详解内部类和Lambda表达式

内部类 与 Lambda表达式

一、内部类

程序在开发中为了更加准确地描述结构体的作用,提供有各种嵌套结构,而程序类也是允许嵌套的。

内部类(内部定义普通类、抽象类、接口的统称)是指一种嵌套的结构关系,说白了就是,在一个类(假设为A)内部还定义了一个类(假设为B)。

当然,A类和B类中都可以有属性和方法,下面,给个例子。

范例:定义内部类

package edu.blog.test18.inner01;

//外部类
public class Outer {
    //私有成员属性
    private String msg = "Hello World";

    //get方法
    public String getMsg() {
        return this.msg;
    }

    //外部类成员方法
    public void fun() {
        System.out.println("OuterClass--fun");
        //实例化内部类对象
        InnerClass innerClass = new InnerClass();
        //调用内部类的方法
        innerClass.print();
    }

    //内部类
    class InnerClass {
        //内部类成员方法
        public void print() {
            System.out.println("InnerClass--print");
            //内部类的方法 输出 外部类的成员属性
            System.out.println(Outer.this.msg);
        }
    }
}

测试类:

package edu.blog.test18.inner01;

public class InnerTestDemo01 {
    public static void main(String[] args) {
        //实例化 外部类对象
        Outer outerClass = new Outer();
        //调用外部类的方法
        outerClass.fun();
    }
}

/*
执行结果:
OuterClass--fun
InnerClass--print
Hello World
 */

本程序从代码理解难度并不大,核心的结构就是在Outer.fun()方法里实例化了内部类的对象,并且利用内部类中的print()方法直接输出了外部类中msg私有成员属性。

从类的组成来讲主要就是成员属性与方法,但是此时在一个类的内部又定义了若干个内部类结构,使得程序代码的结构非常的混乱,那么为何还要这么定义呢?

其实,实质上内部类在整体设计中最大的缺点就是破坏了良好的程序结构,但是最大的优点在于可以方便地访问外部类中的私有成员。

同时,外部类也同样可以访问内部类的私有成员。注意,内部类本身就是一个独立的结构,这样在进行普通成员属性访问时,为了明确地标记出属性是外部类所提供的,可以采用外部类.this.属性的形式进行标注。

范例:外部类访问内部类私有成员

外部类:

package edu.blog.test18.inner02;

//外部类
public class Outer {
    //外部类私有成员属性
    private String msg = "Hello World";

    //外部类公共方法
    public void fun() {
        //实例化内部类对象
        Inner in = new Inner();
        //通过内部类对象调用内部类方法
        in.print();
        //外部类访问内部类的私有属性
        System.out.println(in.info);
    }

    //内部类
    class Inner {
        //内部类私有成员
        private String info = "Java";

        //内部类公共方法
        public void print() {
            //访问Outer类中的私有成员属性
            System.out.println(Outer.this.msg);
        }
    }
}

测试类:

package edu.blog.test18.inner02;

public class InnerTestDemo {
    public static void main(String[] args) {
        //实例化外部类对象
        Outer outer = new Outer();

        //调用外部类的方法
        outer.fun();
    }
}

/*
执行结果:
Hello World
Java
 */

本程序在内部类中利用Outer.this.msg的形式调用外部类中的私有成员属性,而在外部类中也可以直接利用内部类的对象访问内部类的私有成员。


2、实例化内部类

值得注意的是,内部类虽然被外部类包裹,但是其本身也属于一个完成类,所以也可以直接进行内部类对象的实例化,可以使用以下的语法格式:

外部类.内部类 内部类对象 = new 外部类().new内部类();

//也上面的程序为例
Outer.Inner inner = new Outer().Inner();

由语法的格式可以看出,必须先获取相应的外部类实例化对象后,才可以利用外部类的实例化对象进行内部类对象实例化操作。

Tips:关于内部类的字节码文件名称。

当进行内部类源代码编译后,就会发现一个Outer$Inner.class字节码文件,其中所使用的标识符"$"在程序中会转化为".",所以内部类的全称就是“外部类.内部类”,由于内部类与外部类之间可以直接进行私有成员的访问,这就就必须保证在实例化内部类对象前先实例化外部类对象。


3、内部类私有化

如果说现在一个内部类不希望被其他类所调用,那么可以使用private关键字将这个内部类定义为私有内部类。

范例:内部类私有化

package edu.blog.test18.inner03;

//外部类
public class Outer {
    //私有成员变量
    private String msg = "Hello World";

    //内部类
    private class Inner {
        public void print() {
            //Outer类中的私有成员变量
            System.out.println(Outer.this.msg);
        }
    }
}

此时,Inner类使用了private定义,表示此类只允许被Outer一个类中使用。


4、内部接口

内部类不仅可以在类中定义,也可以应用在接口和抽象类之中,即可以定义内部的接口或内部抽象类。

范例:定义内部接口

package edu.blog.test18.inner04;

//外部接口
public interface IChannel {
    //抽象方法,发送消息
    public abstract void send(IMessage msg);

    //内部接口
    interface IMessage{
        //抽象方法,获取内容
        public abstract String getContent();
    }
}
package edu.blog.test18.inner04;

//外部接口实现子类
public class ChannelImpl implements IChannel {
    //重写方法
    @Override
    public void send(IMessage msg) {
        System.out.println("发送的消息:" + msg.getContent());
    }

    //内部接口实现子类(不是必须实现)
    class MessageImpl implements IMessage {
        //重写方法
        @Override
        public String getContent() {
            return "Hello World";
        }
    }
}

测试类:

package edu.blog.test18.inner04;

public class TestDemo {
    public static void main(String[] args) {
        //实例化外部接口实现子类
        IChannel channel = new ChannelImpl();

        //实例化 内部类接口实现子类 前需要先获取外部类实例化对象
        IChannel.IMessage message = new ChannelImpl().new MessageImpl();
        channel.send(message);
    }
}

/*
执行结果:
发送的消息:Hello World
*/

本程序使用内部类的形式定义了接口,并且分别为外部接口和内部接口定义了各自的子类。由于IMessage是内部接口,所以定义MessageImpl子类的时候也采用了内部类的定义形式。


5、静态内部类

static定义的结构可以不受类的使用制约,内部类在嵌套定义时,也可以使用static定义独立的类结构体。

静态内部类,即使用static关键字定义的内部类不再受到外部类实例化对象的影响,所以等同于一个“外部类”,内部类的名称为“外部类.内部类”

使用static关键字定义的内部类只能够调用外部类中static定义的结构,并且在进行内部类实例化的时候也不再需要先获取外部类实例化对象,static内部类对象实例化格式:

外部类.内部类 内部类对象 = new 外部类.内部类();

范例:使用static定义内部类

package edu.blog.test18.inner05;

public class Outer {
    //static属性
    private static final String MSG = "Hello World";

    //static内部类
    static class Inner {
        //访问static属性
        public void print() {
            System.out.println("print()---" + Outer.MSG);
        }
    }
}

测试类:

package edu.blog.test18.inner05;

public class TestDemo {
    public static void main(String[] args) {
        //实例化内部类对象
        Outer.Inner inner = new Outer.Inner();
        inner.print();
    }
}

/*
执行结果:
print()---Hello World
 */

本程序在Outer类的内部使用static关键字定义了Inner内部类,这样内部类就成为一个独立的外部类,在外部类实例化对象时内部类的完整名称将为Outer.Inner


6、局部内部类

局部内部类是定义在一个方法或者一个作用域代码块)里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。但是,局部内部类就像是方法里面的一个局部变量一样,不能用publicprotectedprivate以及static修饰符的。

因此,如果需要用局部内部类定义变量、创建实例或派生子类,那么都只能在局部内部类所在的方法或者代码块中进行。

范例:定义局部内部类(方法中)

package edu.blog.test18.inner06;

//外部类
public class Outer {
    //外部类属性
    private String msg = "Hello World";

    //外部类方法
    public void fun(int count01) {
        //方法中定义内部类
        class Inner {
            //内部类方法
            public void print(int count02) {
                //外部类私有属性
                System.out.println("外部类私有属性--" + Outer.this.msg);
                //外部类方法参数
                System.out.println("外部类方法参数--" + count01);
                //内部类方法参数
                System.out.println("内部类方法参数--" + count02);
            }
        }

        //在方法中直接 实例化内部类对象
        new Inner().print(888);
    }
}

测试类:

package edu.blog.test18.inner06;

public class TestDemo {
    public static void main(String[] args) {
        //实例化外部类对象 并调用外部类方法
        new Outer().fun(666);
    }
}

/*
执行结果:
外部类私有属性--Hello World
外部类方法参数--666
内部类方法参数--888
 */

本程序在Outer.fun()方法中定义了内部类Inner,并且在Inner内部类中实现了外部类中成员属性和方法参数的访问,可知局部内部类可随意访问外部类的成员变量和方法,即使是私有的。


7、匿名内部类

最后,我们来说一说最后一种内部类内,叫做匿名内部类。

顾名思义,所谓的匿名内部类就是一个没有显式的名字的内部类,其本质为匿名内部类会隐式地继承一个类或者实现一个接口,或者说,匿名内部类是一个继承了该类或者实现了该接口的子类匿名对象

在实际开发中,此种内部类用的是非常多的,以使用一个接口为例子,似乎得做如下几步操作:

  1. 定义子类实现接口
  2. 重写接口中的方法
  3. 创建子类对象
  4. 调用重写后的方法

而我们的目的,最终只是为了调用方法,那么能不能把以上四步简化合成一步呢?匿名内部类就是做这样的快捷方式。

前提:匿名内部类必须继承一个父类或者实现一个父接口

格式:

new 父类名或者接口名(){
	// 方法重写
	@Override
	public void method() {
		// 执行语句
	}
};

接下来我们,以接口为例子,才展示以下匿名内部类的使用。

定义接口:

package edu.blog.test18.inner07;

//定义一个父接口
public interface Flyable {
    //抽象方法 fly()
    public abstract void fly();
}

测试类:

package edu.blog.test18.inner07;

public class TestDemo {
    public static void main(String[] args) {
        /*
        	创建匿名内部类,并调用方法。
        	等号右边:是匿名内部类,定义并创建该接口的子类对象
        	等号左边:是多态赋值,接口类型引用指向子类对象
        */
        Flyable f = new Flyable() {
            //重写接口中的fly()方法
            @Override
            public void fly() {
                System.out.println("我是匿名内部类");
                System.out.println("我会飞啦啦啦~~");
            }
        };

        //调用 fly()方法
        f.fly();

        /*
        new Flyable() {
            @Override
            public void fly() {
                System.out.println("我是匿名内部类");
                System.out.println("我会飞啦啦啦~~");
            }
        }.fly();
         */
    }
}

/*
执行结果:
我是匿名内部类
我会飞啦啦啦~~
 */

有时后,我们某个方法的形式参数是接口或者抽象类时,也可以将匿名内部类作为参数传递,代码如下:

package edu.blog.test18.inner07;

public class TestDemo02 {
    public static void main(String[] args) {

        /*
        创建匿名内部类,直接传递给showFly(Flyable f)
         */
        showFly(new Flyable() {
            @Override
            public void fly() {
                System.out.println("真不巧,我还是会飞~~");
            }
        });
    }

    public static void showFly(Flyable f) {
        f.fly();
    }
}

/*
执行结果:
我依旧会飞~~
真不巧,我还是会飞~~
 */

二、Lambda表达式

1、函数式编程思想概述

在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情”。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做

  • 面向对象的思想:做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情
  • 函数式编程思想:只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程。

2、Lambda表达式初体验

案例需求:启动一个线程,在控制台输出一句话:多线程程序启动了。

  1. 实现方式一

    实现步骤:

    1. 定义一个类MyRunnable实现Runnable接口,重写run()方法;
    2. 创建MyRunnable类的对象;
    3. 创建Thread类的对象,把MyRunnable的对象作为构造参数传递;
    4. 启动线程。
  2. 实现方式二

    匿名内部类的方式改进。

  3. 实现方式三

    Lambda表达式的方式改进。

代码如下:

package edu.blog.test18.lambda01;

//方式一的线程类
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("我是方式一,多线程启动了~");
    }
}

package edu.blog.test18.lambda01;

public class LambdaTestDemo {
    public static void main(String[] args) {
        /*
        方式一:传统方法
         */
        Runnable myRun = new MyRunnable();
        Thread thread = new Thread(myRun);
        thread.start();

        /*
        方式二:匿名内部类
         */
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("我是方式二,多线程启动了~");
            }
        }).start();

        /*
        方式三:Lambda表达式
         */
        new Thread(() -> {
            System.out.println("我是方式三,多线程启动了~");
        }).start();
    }
}

/*
执行结果:
我是方式一,多线程启动了~
我是方式二,多线程启动了~
我是方式三,多线程启动了~
 */

3、Lambda表达式的标准格式

  • 组成Lambda表达式的三要素:

    形式参数、箭头、代码块。

  • 格式:

    (形式参数) -> {代码块}

    1. 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可;
    2. ->:由英文中画线和大于符号组成,固定写法,代表指向动作;
    3. 代码块:是我们具体要做的事情,也就是以前我们写的方法体内容

4、Lambda表达式的使用前提

Lambda的语法非常简洁,完全没有面向对象复杂的束缚。

但是使用时有几个问题需要特别注意:

  1. 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法

    无论是JDK内置的RunnableComparator接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda表达式。

  2. 使用Lambda必须具有上下文推断

    也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。

备注:有且仅有一个抽象方法的接口,称为“函数式接口”。


5、Lambda表达式的练习

5.1、无参无返回值的抽象方法

示例代码:

/*
需求:
1. 定义一个接口(Eatable),里面定义一个抽象方法:void eat();
2. 定义一个测试类(EatableDemo),在测试类中提供两个方法:
	一个方法是:useEatable(Eatable e);
    一个方法是主方法,在主方法中调用useEatable方法。
*/
package edu.blog.test18.lambda02;

//接口
public interface Eatable {
    public abstract void eat();
}
package edu.blog.test18.lambda02;

//实现类
public class EatableImpl implements Eatable{
    @Override
    public void eat() {
        System.out.println("我爱吃苹果~");
    }
}
package edu.blog.test18.lambda02;

public class EatableDemo {
    public static void main(String[] args) {
        //在主方法中调用useEatable方法
        Eatable e = new EatableImpl();
        useEatable(e);

        //匿名内部类
        useEatable(new Eatable() {
            @Override
            public void eat() {
                System.out.println("我爱吃荔枝~");
            }
        });

        //Lambda表达式
        useEatable(() -> {
            System.out.println("我爱吃芒果~");
        });
    }

    private static void useEatable(Eatable e) {
        e.eat();
    }
}

/*
执行结果:
我爱吃苹果~
我爱吃荔枝~
我爱吃芒果~
 */
5.2、有参无返回值的抽象方法

示例代码:

/*
需求:
1. 定义一个接口(Flyable),里面定义一个抽象方法:void fly(String s);
2. 定义一个测试类(FlyableDemo),在测试类中提供两个方法:
	一个方法是:useFlyable(Flyable f)
	一个方法是主方法,在主方法中调用useFlyable方法
*/
package edu.blog.test18.lambda03;

//接口类
public interface Flyable {
    public abstract void fly(String s);
}
package edu.blog.test18.lambda03;

//测试类
public class FlyableDemo {
    public static void main(String[] args) {
        //在主方法中调用useFlyable方法

        //匿名内部类
        useFlyable(new Flyable() {
            @Override
            public void fly(String s) {
                System.out.println(s);
                System.out.println("飞机自驾游");
            }
        });
        System.out.println("--------");

        //lambda表达式
        useFlyable((String s) -> {
            System.out.println(s);
            System.out.println("飞机自驾游");
        });
    }

    private static void useFlyable(Flyable f) {
        f.fly("风和日丽,晴空万里");
    }
}

/*
执行结果:
风和日丽,晴空万里
飞机自驾游
--------
风和日丽,晴空万里
飞机自驾游
 */
5.3、有参有返回值的抽象方法

示例代码:

/*
需求:
1. 定义一个接口(Addable),里面定义一个抽象方法:int add(int x,int y);
2. 定义一个测试类(AddableDemo),在测试类中提供两个方法:
	一个方法是:useAddable(Addable a)
	一个方法是主方法,在主方法中调用useAddable方法
*/
package edu.blog.test18.lambda04;

//接口类
public interface Addable {
    public abstract int add(int x, int y);

package edu.blog.test18.lambda04;

public class AddableDemo {
    public static void main(String[] args) {
        useAddable((int x, int y) -> {
            return x + y;
        });
    }

    private static void useAddable(Addable a) {
        System.out.println(a.add(333, 333));
    }
}

/*
执行结果:
666
 */

6、Lambda表达式的省略模式

Lambda表达式的省略规则:

  • 参数类型可以省略,但是有多个参数的情况下,不能只省略一个;
  • 如果参数有且仅有一个,那么小括号可以省略;
  • 如果代码块的语句只有一条,可以省略大括号和分号,和return关键字。

示例代码:

package edu.blog.test18.lambda05;

public interface Addable {
    public abstract int add(int x, int y);
}
package edu.blog.test18.lambda05;

public interface Flyable {
    public abstract void fly(String s);
}
package edu.blog.test18.lambda05;

public class LambdaTestDemo {
    public static void main(String[] args) {

        useAddable((int x, int y) -> {
            return x + y;
        });

        //参数的类型可以省略
        useAddable((x, y) -> {
            return x + y;
        });

        useFlyable((String s) -> {
            System.out.println(s);
        });

        //如果参数有且仅有一个,那么小括号可以省略

        useFlyable(s -> {
            System.out.println(s);
        });

        //如果代码块的语句只有一条,可以省略大括号和分号

        useFlyable(s -> System.out.println(s));

        //如果代码块的语句只有一条,可以省略大括号和分号,如果有return,return也要省略掉
        useAddable((x, y) -> x + y);
    }

    private static void useFlyable(Flyable f) {
        f.fly("风和日丽,晴空万里");
    }

    private static void useAddable(Addable a) {
        int sum = a.add(10, 20);
        System.out.println(sum);
    }
}

/*
执行结果:
30
30
风和日丽,晴空万里
风和日丽,晴空万里
风和日丽,晴空万里
30
 */

7、Lambda表达式和匿名内部类的区别

  1. 所需类型不同

    • 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类;
    • Lambda表达式:只能是接口。
  2. 使用限制不同

    • 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类;
    • 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式。
  3. 实现原理不同

    • 匿名内部类:编译之后,产生一个单独的.class字节码文件;

    • Lambda表达式:编译之后,没有一个单独的.class字节码文件,对应的字节码会在运行的时候动态生成。


注:此文章为个人学习笔记,如有错误,敬请指正。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

窝在角落里学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值