JavaSE笔记(2.4)Java基础 - 内部类、Lambda表达式

前言

在编程过程中,总会不经意的使用内部类
内部类是什么?

目录

  1. 内部类
  2. 静态内部类
  3. 成员内部类
  4. 局部内部类
  5. 匿名内部类
  6. Lambda表达式
  7. 函数式接口
  8. 函数式接口的作用
  9. 类型推导
  10. Lambda表达式的简化及运用

内部类

在 Java 中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类

public class InnerClass {
    private int id;
    public class inner{

    }
}

就可以称inner类为内部类

内部类大致可以分为四种:静态内部类,成员内部类,局部内部类,匿名内部类

静态内部类

静态内部类:定义在类内部的静态类

静态
静态关键字static主要作用在于创建独立于具体对象的域变量或者方法
被static修饰的类就是静态类
那为什么需要静态呢?
对于一个类,一般都是new该类的类对象后,JVM会创建唯一的一个.class对象,分配一个存储空间,才可以使用对象的属性,那怎么即使没有创建对象,也能使用属性和调用方法
就是使用静态static

静态static的特点

  • static可以修饰变量,方法
  • 被static修饰的变量或者方法是独立于该类的任何对象,也就是说,这些变量和方法不属于任何一个实例对象,而是被类的实例对象所共享
  • 在类被加载的时候,就会去加载被static修饰的部分。
  • 被static修饰的变量或者方法是优先于对象存在的,也就是说当一个类加载完毕之后,即便没有创建对象,也可以去访问。

这需要理解对象的创建过程:
在实例化对象的过程中

  1. JVM首先会检查相关类是否已经加载并初始化
  2. 如果没有,则JVM立即进行加载并调用类构造器完成类的初始化
  3. 在类初始化过程中或初始化完毕后,根据具体情况才会去对类进行实例化

在这里插入图片描述
JVM类生命周期概述:加载时机与加载过程
要注意的是:静态方法只可以访问静态方法,不能访问非静态方法,为什么?
静态方法在类加载时就加载了,而非静态方法要在实例时才会加载,那么静态方法就无法识别非静态方法,相反非静态方法可以访问静态方法

static修饰的方法、变量在类被加载即生成.class文件时也被加载了,所以可以直接调用不用创建类对象
例:

package com.company.javaBasis;

public class InnerClass {
    private int id;
    public static void  sayhello(){
        System.out.println("hello world");
    }
    public void sayhello1(){
        System.out.println("hello Java");
        InnerClass.sayhello();
    }
    public static void main(String[] args){
        InnerClass.sayhello();
        InnerClass innerClass=new InnerClass();
        innerClass.sayhello1();
    }
}

在这里插入图片描述

可以看出main方法也是静态类
在main这个静态类中,可以直接调用了静态sayhello()方法
而要调用sayhello1()这个非静态方法就需要实例化InnerClass对象

有点跑题。。。回到静态内部类

public class InnerClass {
  public static class staticinner{
        
    }
}

staticinner就是静态内部类

  1. 静态内部类可以访问外部类所有的静态变量和方法,即使是 private 的也一样。
  2. 静态内部类和一般类一致,可以定义静态变量、方法,构造方法等。
  3. 其它类使用静态内部类需要使用“外部类.静态内部类”方式,如下所示:Out.Inner inner =
    new Out.Inner();inner.print();

成员内部类

成员内部类:定义在类内部的非静态类

public class InnerClass {
    public static class staticinner{

    }
    public class inner{
        
    }
}    

这个inner类就是成员内部类

成员内部类不能定义静态方法和变量(final 修饰的除外)。这是因为成员内部类是非静态的,类初始化的时候先初始化静态成员,如果允许成员内部类定义静态变量,那么成员内部类的静态变量初始化顺序是有歧义的

局部内部类

局部内部类:定义在方法中的类
如果一个类只在某个方法中使用,则可以考虑使用局部类
需要注意的是局部内部类不能有访问修饰符,它可以访问外部类的所有成员以及当前方法的常量(final修饰的)
局部内部类典型实现应用为获取内部类所实现接口的实现
例:

package com.company.javaBasis;


interface Hello{
    String sayhello();
        }
public class InnerClass {
    private final int id=3;
    //method方法,返回数据类型为Hello对象
    Hello method(){
        //局部内部类localinner继承hello接口
         class LocalInner implements Hello{

             @Override
             public String sayhello() {
                 //局部内部类可以使用final类型数据
                 return "hello"+id;
             }
         }
         //返回一个Hello的子类LocalInner对象
         return new LocalInner();
    }

    public static void main(String[] args){
        //创建类InnerClass对象
        InnerClass innerClass=new InnerClass();
        //得到method方法返回的hello对象
        Hello hello=innerClass.method();
        System.out.println(hello.sayhello());
    }
}

在这里插入图片描述

匿名内部类

匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一
个接口。同时它也是没有 class 关键字,这是因为匿名内部类是直接使用 new 来生成一个对象的引

例:
创建一个Person类,里面创建一个方法eat();
创建类Anonymous 测试

package com.company.javaBasis;

import com.company.entity.Person;

public class Anonymous {
    public static void main(String[] args){
        Person person=new Person(){
        //匿名内部类,无class
            public void eat(){
                System.out.println("Eat Meat");
            }
        };
        person.eat();
        //匿名内部类相对于创建了一个子类
        System.out.print(person.getClass());
    }
}

在这里插入图片描述
person.getClass()返回的类名叫class com.company.javaBasis.Anonymous$1
发现匿名内部类其实是创建了一个子类
可以看出,使用匿名内部类使得代码更简洁了
那还能更简洁吗?

Lambda表达式

Jdk1.8中引入了一个新的操作符 “->” 该操作符称为箭头操作符或 Lambda 操作符,使用() -> {} 替代匿名类
箭头操作符将 Lambda 表达式拆分成两部分:
左侧:Lambda 表达式的参数列表
右侧:Lambda 表达式中所需执行的功能, 即 Lambda 体
理解Lambda
无参无返回值

package com.company.javaBasis;

public class Anonymous {
    public static void main(String[] args){
        Runnable runnable= () -> System.out.println("Eat Fish");
        runnable.run();
        System.out.println(runnable.getClass());
    }
}

在这里插入图片描述

Lambda表达式也是创建了一个子类

无参无返回值

有一个参数无返回值

输入String类型的参数

        Consumer<String> consumer=(x) ->System.out.println("带参Lambda表达式:"+x);
        consumer.accept("lambda");
        System.out.print(consumer.getClass());

在这里插入图片描述

有多个参数有返回值

        Comparator<Integer> com = (x, y) -> {
            System.out.println("有两个参数有返回值Lambda");
            return Integer.compare(x, y);
        };
        int compare = com.compare(100, 200);
        System.out.println(compare);

在这里插入图片描述

函数式接口

那么为什么上面的Runnable 、Consumer、Comparator就可以使用Lambda,而我们普通的类就不可以,关键在与函数式接口

看看Runnable的源码解释:
在这里插入图片描述在这里插入图片描述

特点:

(1)含有@FunctionalInterface注解

(2)只有一个抽象方法

再看看Consumer接口
在这里插入图片描述
在这里插入图片描述只有函数式接口的变量或者是函数式接口,才能够赋值为Lambda表达式

函数式接口的作用

  • 函数式接口(Functional Interface)是Java 8对一类特殊类型的接口的称呼。这类接口只定义了唯一的抽象方法的接口(除了隐含的Object对象的公共方法,因此最开始也就做SAM类型的接口(Single
    Abstract Method)
  • 定义函数式接口的原因是在Java Lambda的实现中,开发组不想再为Lambda表达式单独定义一种特殊的Structural函数类型,称之为箭头类型(arrow type,依然想采用Java既有的类型(class, interface, method等)。
  • 增加一个结构化的函数类型会增加函数类型的复杂性,破坏既有的Java类型,并对成千上万的Java类库造成严重的影响。权衡利弊,因此最终还是利用SAM接口作为 Lambda表达式的目标类型。
  • 对于函数式接口来说@FunctionalInterface并不是必须的,只要接口中只定义了唯一的抽象方法的接口那它就是一个实质上的函数式接口,就可以用来实现Lambda表达式。

函数式接口能够接受匿名内部类的实例化对象,换句话说,我们可以使用匿名内部类来实例化函数式接口的对象,而Lambda表达式能够代替内部类实现代码的进一步简化。

Java为我们提供了四个比较重要的函数式接口:

  • 消费型接口:Consumer< T> void accept(T t)有参数,无返回值的抽象方法;
  • 供给型接口:Supplier < T> T get() 无参有返回值的抽象方法;
  • 断定型接口:Predicate< T> boolean test(T t):有参,但是返回值类型是固定的boolean
  • 函数型接口:Function< T,R> R apply(T t)有参有返回值的抽象方法

util包下还有很多函数式接口

自定义函数式接口

package com.company.javaBasis;

@FunctionalInterface
interface MyLamdTest{
    void test();
        }

public class LambdaTest {
    public static void main(String[] args){
        MyLamdTest myLamdTest=() -> System.out.println("hello");
        myLamdTest.test();
    }
}

在这里插入图片描述

注意:自定义函数式接口只能有一个方法,因为Lambda是一个接口方法,如果有两个方法就不知道指定哪个

类型推导

什么是类型推导?

Consumer<String> consumer=(x) ->System.out.println("带参Lambda表达式:"+x);
consumer.accept("lambda");

这里的x并没有设置类型
其实用ArrayList时也有类型推导

ArrayList<String> arrayList=new ArrayList<>();

ArrayList<>()里直接省略了

lambda本身具有类型推导,那么这个类型推导可以做到什么程度呢?编译器负责推导lambda的类型,它利用上下文被期待的类型当做推导的目标类型,当满足下面条件时,就会被赋予目标类型:

(1)被期待的目标类型是一个函数式接口

(2)lambda的入参类型和数量与该接口一致

(3)返回类型一致

(4)抛出异常类型一致

lambda最后会由编译器生成static方法在当前类中,利用了invokedynamic命令脱离了内部类实现的优化

Lambda表达式的简化及运用

Runnable runnable= () -> {
	System.out.println("Eat Fish")
}
//Lambda体只有一条语句可以去大括号{}
Runnable runnable= () -> System.out.println("Eat Fish");

Consumer<String> consumer=(x) ->System.out.println("带参Lambda表达式:"+x);
//只有一个参数可以去小括号()
Consumer<String> consumer=x ->System.out.println("带参Lambda表达式:"+x);
//如果Lambda体只有一条语句,并且是 return 语句,则可以省略return
Comparator<Integer> com = (x, y) -> compare(x, y);

上述是自己完成Lambda体的语句,如果引用方法呢?
方法归属者::方法名, 静态方法的归属者为类名,普通方法归属者为对象

静态方法引用:ClassName::methodName

实例上的实例方法引用:instanceReference::methodName

超类上的实例方法引用:super::methodName

类型上的实例方法引用:ClassName::methodName

构造方法引用:Class::new

数组构造方法引用:TypeName[]::new

	//已实现的方法	
    public String sayHello(){
        return "hello";
    }
    
    public class Anonymous {
    public static void main(String[] args){
    	 //实例化Anonymous对象
   		 Anonymous anonymous=new Anonymous();
   		 //使用无参有返回值函数式接口Supplier 
    	 Supplier supplier= anonymous::sayHello;
    	 //Supplier 的get()方法获得返回值
       	 System.out.println(supplier.get());
     }

lambda集合遍历:

		//list遍历
        ArrayList<String> arrayList=new ArrayList<>();
        arrayList.add("hello");
        arrayList.add("welcome");
        arrayList.add("to");
        arrayList.add("here");
        arrayList.forEach(c-> System.out.println(c));
        //map遍历
        HashMap<Integer,String> hashMap=new HashMap<>();
        hashMap.put(1,"this");
        hashMap.put(2,"is");
        hashMap.put(3,"hashmap");
        hashMap.forEach((key,value)->System.out.println("key:"+key+",value:"+value));

在这里插入图片描述

发布了49 篇原创文章 · 获赞 0 · 访问量 1232

猜你喜欢

转载自blog.csdn.net/key_768/article/details/104235442