大纲
java内部类的四大作用_奋斗的bigHead的博客-CSDN博客
(四大内部类:匿名内部类+成员内部类+局部内部类+静态内部类)
(lambda+函数式接口+函数式编程)
1.为什么学习内部类以及它的好处:
(1)在学习java的时候,一个儿子只能有一个父亲,哈哈,继承只能是单继承,那么内部类就是java提供的多继承的解决方案(干爹)。每一个内部类都可以独立的继承一个接口实现。
(2)语法糖,比如书写线程池代码可以大大简化书写,或者当一个类或者接口只服务于一个类时,无需去继承或者实现接口,写一个内部类,在里面重写方法就可以了。
(3)事件驱动程序。回调函数
(4) 当某个类只为一个类提供服务时,可以将这个类定义成内部类,可以解决接口或者抽象类不能实例化的问题。
(5)Java 从 Java 8 开始支持 lambda 表达式,这才算是支持函数式编程。函数式编程有什么好处呢?如果将其与依赖注入技术结合,可以很好地遵守开闭原则,实现控制反转,便于异步调用等等。在事件驱动模型中常常应用这一技术。
Java 函数式编程与事件驱动模式_java事件驱动_暗诺星刻的博客-CSDN博客
Java事件驱动模型框架实现_java 事件框架_大林子先森的博客-CSDN博客
java 事件驱动模式_事件驱动模型实例详解(Java篇)_苟全性命的博客-CSDN博客
观察者模式与事件监听器模式--java设计模式扫盲_java 监听事件和 观察者模式的区别_打不死的小欣的博客-CSDN博客
2.内部类编译过程
内部类虽然和外部类写在同一个java文件中, 但是编译完成后, 还是生成各自的class文件,内部类通过this访问外部类的成员。
1 编译器自动为内部类添加一个成员变量, 这个成员变量的类型和外部类的类型相同, 这个成员变量就是指向外部类对象(this)的引用;
2 编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型, 在构造方法内部使用这个参数为内部类中添加的成员变量赋值;
3 在调用内部类的构造函数初始化内部类对象时,会默认传入外部类的引用。
编译指令 javac classpath(.java文件的路径)
反编译指令 javap -v(详细信息) classpath(.class文件的路径)
3.四大内部类
写一个类的成员方法内的叫匿名内部类
写在成员方法外和成员方法同等级的叫做成员内部类
-
成员内部类:和成员变量一个级别
-
局部内部类:在方法里的内部类(方法域内生效 或者 方法内某一段代码块域内生效)
-
匿名内部类:基本上属于接口的实现类,一次性使用的场景。
-
静态内部类:static修饰的成员内部类。
1.匿名内部类(一次性实现一个接口)
=> 不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。
=> 一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
// 实现关系下的匿名内部类:
interface A{
void show();
}
public class B{
//编写回调方法 :callInner
public void callInner(){
// 接口关系下的匿名内部类
new A(){
//实现子类 但是没有名字 所以叫匿名内部类
@Override
public void show() {
System.out.println("A接口方法...");
}
}.show();
}
}
// 测试:
public class Demo {
public static void main(String[] args) {
B b= new B();
b.callInner();
}
}
2. 成员内部类(和类的成员变量,成员方法同等级别,链式声明调用)
=> 在一个类中除了可以定义成员变量,成员方法,还可以定义类,这样的类称为成员内部类。
=> 在成员内部类中可以访问外部类的所有成员资源(包括私有成员),例如成员变量和成员方法。
=> 在外部类中,同样可以访问成员内部类的变量和方法。
//定义测试类
public class Test {
public static void main(String[] args) {
Outer outer =new Outer();//创建外部类对象
Outer.Inner inner =outer.new Inner();//创建内部类对象
inner.show1();//测试在成员内部类中访问外部类成员变量和方法
outer.test2();//测试在外部类中访问成员内部类变量和方法
}
}
//定义外部类Outer
class Outer {
int m=0;//定义外部类成员变量
void test1(){//定义外部类成员方法
System.out.println("外部类成员方法");
}
//定义内部类Inner
class Inner{
int n=1;
void show1(){//定义内部类方法,访问外部类成员变量和方法
System.out.println("外部类成员变量m="+m);
test1();
}
void show2(){//定义内部类成员方法
System.out.println("内部类成员方法");
}
}
void test2(){//定义外部类方法,访问内部类成员变量和方法
Outer.Inner inner =new Outer().new Inner();
System.out.println("内部类成员变量n="+inner.n);
inner.show2();
}
}
1.内部类可以直接调用外部类的属性和方法(包括私有的)
2.外部类要调用内部类的属性和方法,需要通过内部类的对象去调用
3.创建内部类对象格式: Outer.Inner in = new Outer().new Inner();等价于【Outer outer=new Outer(),Inner inner=outer.new Inner().】
4.当外部类和内部类的属性重名时,可以通过 :外部类.this.属性 来访问外部类的属性
当外部类和内部类的方法重名时,可以通过 :外部类.this.方法() 来访问外部类的方法
5.成员内部类,直接写在外部类里面,和成员属性和方法位置一样
6.成员内部类,是可以被继承的
7. 内部类可以继承 ,需要通过外部类去找到内部类
-------------------------------------------------------------------------------------------------------------------------
3.局部内部类(定义在方法内,和局部变量同等级别,也叫方法内部类)
局部内部类:又称“方法内部类”,就是定义在某个局部范围中的类,它和局部变量一样,都是在方法中定义的,其有效范围只限于方法内部。
在局部内部类中,局部内部类可以访问外部类的所有成员变量和方法,而局部内部类中的变量和方法却只能在创建该局部内部类的方法中进行访问。
局部内部类的使用场景:若方法返回值是一个接口,在没有子类或者不知道子类的情况下, //在方法中创建一个局部内部类,其他地方不需要用,仅在方法中使用
package day13;
public class Demo2 {
public static void main(String[] args) {
}
public Too test(){
// //若方法返回值是一个接口,在没有子类或者不知道子类的情况下,
// //在方法中创建一个局部内部类,其他地方不需要用,仅在方法中使用
class T implements Too{
@Override
public void sayHi(){
}
}
//返回实现Too的类T对象
return new T();
}
}
interface Too{
public void sayHi();
}
4.静态内部类
就是使用static关键字修饰的成员内部类。
静态内部类只能访问外部类的静态成员,同时通过外部类访问静态内部类成员时,可以跳过外部内直接通过内部类访问静态内部类成员。
public class Demo07 {
public static void main(String[] args) {
Outer o=new Outer();//创建外部类对象
Outer.Inner oi=new Outer().new Inner();//创建内部类对象
Outer.StaticInner ois=new Outer.StaticInner();
}
}
class Outer{
int num=10;
class Inner{//非静态成员内部类
public void show(){
System.out.println("Inner"+num);
}
}
static class StaticInner{//静态成员内部类
public static int a;
}
public void method(){
//非静态方法可以创建静态或非静态的成员内部类对象 this
Inner i=new Inner();
StaticInner si=new StaticInner();
}
public static void say(){
//静态方法不可以直接创建非静态成员内部类
//Inner i=new Inner();
//静态方法可以直接创建静态成员内部类
StaticInner si=new StaticInner();
}
}
4.函数式接口
1.⼀个接口中,要求实现类必须实现的抽象方法,有且只有⼀个!这样的接口,就是函数式接口。
=>只有一个抽象方法的接口
=>函数式编程体现就是Lambda表达式
//有且只有一个实现类必须要实现的抽象方法,所以是函数式接口
interface Test{
public void test();
}
2.@FunctionalInterface注解
用在接口之前,判断这个接口是否是⼀个函数式接口。 如果是函数式接口,没有任何问题。如果不是函数式接口,则会报错。
5.lambda表达式
lambda表达式,其实本质来讲,就是⼀个匿名函数。因此在写lambda表达式的时候,
不需要关心方法名是什么。
实际上,我们在写lambda表达式的时候,也不需要关心返回值类型。
我们在写lambda表达式的时候,只需要关注两部分内容即可:参数列表和方法体
lambda表达式的基础语法:
(参数1,参数2,…) -> {
方法体
};
参数部分:方法的参数列表,要求和实现的接口中的方法参数部分⼀致,包括参数的数量和类型。
方法体部分 : 方法的实现部分,如果接口中定义的方法有返回值,则在实现的时候,注意返回值的返回。
-> : 分隔参数部分和方法体部分。
因为函数式接口,只有一个抽象方法,所以可以省略方法名
package test;
public class Test04 {
public static void main(String[] args) {
//使用lambda表达式实现接口
Test test = () -> {
System.out.println("test");
};
test.test();
}
}
interface Test{
public void test();
}
参数的小括号
如果方法的参数列表中的参数数量 有且只有⼀个,此时,参数列表的小括号是可以省略不写的。
注意事项:
- 只有当参数的数量是⼀个的时候, 多了、少了都不能省略。
- 省略掉小括号的同时, 必须要省略参数的类型方法体部分的精简
-
方法体⼤括号的精简
当⼀个方法体中的逻辑,有且只有⼀句的情况下,⼤括号可以省略
方法体部分的精简
方法体⼤括号的精简
当⼀个方法体中的逻辑,有且只有⼀句的情况下,⼤括号可以省略
Test test = name -> System.out.println(name+"test");
test.test("小新");
return的精简
如果⼀个方法中唯⼀的⼀条语句是⼀个返回语句, 此时在省略掉大括号的同时, 也必须省略掉return。
Test test = (a,b) -> a+b;
6.函数引用
lambda表达式是为了简化接口的实现的。在lambda表达式中,不应该出现比较复杂的逻辑。如果在lambda表达式中出现了过于复杂的逻辑,会对程序的可读性造成非常大的影响。如果在lambda表达式中需要处理的逻辑比较复杂,⼀般情况会单独的写⼀个方法。在lambda表达式中直接引用这个方法即可。
函数引用:引用⼀个已经存在的方法,使其替代lambda表达式完成接口的实现
1.静态方法的引用
语法:类::静态方法
注意事项:
- 在引用的方法后面,不要添加小括号。
- 引用的这个方法,参数(数量、类型)和返回值,必须要跟接口中定义的⼀致
package test; /** * @author: Mercury * Date: 2022/3/20 * Time: 18:17 * Description:lambda表达式静态方法引用 * Version:1.0 */ public class Test05 { public static void main(String[] args) { //实现多个参数,一个返回值的接口 //对一个静态方法的引用,语法:类::静态方法 Test1 test1 = Calculator::calculate; System.out.println(test1.test(4,5)); } } class Calculator{ public static int calculate(int a,int b ){ // 稍微复杂的逻辑:计算a和b的差值的绝对值 if (a > b) { return a - b; } return b - a; } } interface Test1{ int test(int a,int b); }
2.非静态方法的引用
语法:对象::非静态方法
注意事项:
- 在引用的方法后⾯,不要添加小括号。
- 引用的这个方法, 参数(数量、类型) 和 返回值, 必须要跟接口中定义的⼀致。
package test;
/**
* @author: Mercury
* Date: 2022/3/21
* Time: 8:14
* Description:lambda表达式对非静态方法的引用
* Version:1.0
*/
public class Test06 {
public static void main(String[] args) {
//对非静态方法的引用,需要使用对象来完成
Test2 test2 = new Calculator()::calculate;
System.out.println(test2.calculate(2, 3));
}
private static class Calculator{
public int calculate(int a, int b) {
return a > b ? a - b : b - a;
}
}
}
interface Test2{
int calculate(int a,int b);
}
3.构造方法的引用
使用场景
如果某⼀个函数式接口中定义的方法,仅仅是为了得到⼀个类的对象。此时我们就可以使用构造方法的引用,简化这个方法的实现。
语法:类名::new
注意事项:可以通过接口中的方法的参数, 区分引用不同的构造方法。
package com.cq.test;
/**
* @author: Mercury
* Date: 2022/4/27
* Time: 10:31
* Description:lambda构造方法的引用
* Version:1.0
*/
public class Test {
private static class Dog{
String name;
int age;
//无参构造
public Dog(){
System.out.println("一个Dog对象通过无参构造被实例化了");
}
//有参构造
public Dog(String name,int age){
System.out.println("一个Dog对象通过有参构造被实例化了");
this.name = name;
this.age = age;
}
}
//定义一个函数式接口,用以获取无参的对象
@FunctionalInterface
private interface GetDog{
//若此方法仅仅是为了获得一个Dog对象,而且通过无参构造去获取一个Dog对象作为返回值
Dog test();
}
//定义一个函数式接口,用以获取有参的对象
@FunctionalInterface
private interface GetDogWithParameter{
//若此方法仅仅是为了获得一个Dog对象,而且通过有参构造去获取一个Dog对象作为返回值
Dog test(String name,int age);
}
// 测试
public static void main(String[] args) {
//lambda表达式实现接口
GetDog lm = Dog::new; //引用到Dog类中的无参构造方法,获取到一个Dog对象
Dog dog = lm.test();
System.out.println("修狗的名字:"+dog.name+" 修狗的年龄:"+dog.age); //修狗的名字:null 修狗的年龄:0
GetDogWithParameter lm2 = Dog::new;//引用到Dog类中的有参构造,来获取一个Dog对象
Dog dog1 = lm2.test("萨摩耶",2);
System.out.println("修狗的名字:"+dog1.name+" 修狗的年龄:"+dog1.age);//修狗的名字:萨摩耶 修狗的年龄:2
}
}