Java 的各种内部类、Lambda表达式
内部类
内部类是指在一个外部类的内部再定义一个类。内部类的出现,再次打破了Java单继承的局限性。
内部类可以是静态 static 的,也可用 public,default,protected 和 private 修饰。(而外部顶级类即类名和文件名相同的只能使用 public 和 default)。
注意:内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两个类。
对于一个名为outer的外部类和其内部定义的名为inner的内部类。编译完成后出现 outer.class 和outer$inner.class 两类。所以内部类的成员变量/方法名是可以和外部类的相同的。
1 成员内部类
也就是普通的内部类,它在外部类里面一层。
成员内部类可以访问外部类的所有成员和方法;
外部类要访问成员内部类的成员和方法需要通过实例对象来访问;
成员内部类不能含有 static 的变量和方法。
想一想,非static的内部类,在外部类加载的时候,并不会加载它;可是你这个内部类却拥有 static 的变量和方法,这是必须先加载的,就会和这个内部类的加载时机冲突
事实上,这种写法编译器就能提示错误:
具体的使用方法,参考如下代码及注释:
public class Outer{
private int outi = 0;//外部类的成员
//外部类的方法
public void outPrint(){
System.out.println("out");
}
//成员内部类
class Inner{
int ini = outi + 1;//内部类可以访问外部类的成员变量
public void inPrint(){
outPrint();//内部类可以访问外部类的成员方法
System.out.println("in");
}
}
public static void main(String[] args) {
Outer outer = new Outer();
//这里因为main是静态方法,访问非静态的 Inner对象需要一个Outer对象
Inner inner = outer.new Inner();
inner.inPrint();//外部类访问内部类的成员方法
System.out.println(inner.ini);//外部类访问内部类的成员变量
}
}
2 静态内部类
解决普通的成员内部类不能含有 static 成员的问题,就是将成员内部类声明为 static 。
可以这么说:静态内部类只能访问其外围类的静态成员,除此之外与非静态内部类没有任何区别。
静态内部类相当于一个逻辑上可以独立出去的类,不过放在了内部而已。
- 静态内部类不依赖于外部类的加载。(根据是否使用去加载,相当于内外平行的关系。)
- 静态内部类不能直接访问外部类的非静态成员。(因为外部类加载的时候非静态成员是没有加载的,除非实例化之后)
对比普通成员内部类的代码如下:
public class Outer{
private int outi = 0;//外部类的成员
//外部类的方法
public void outPrint(){
System.out.println("out");
}
//静态成员内部类
static class Inner{
Outer outer = new Outer();
int ini = outer.outi+1;//只能通过实例
//int ini = outi + 1;//不能访问外部类的普通成员变量
public void inPrint(){
outer.outPrint();//只能通过实例
//outPrint();//不能访问外部类的普通成员方法
System.out.println("in");
}
}
public static void main(String[] args) {
Inner inner = new Inner();//可以直接new出来,因为相当于是两个独立的类
inner.inPrint();
System.out.println(inner.ini);
}
}
对于静态内部类来说,我们的感受会更加明显,既然两个类都没什么关系了,为什么还要放在内部作为一个内部类呢?
其实就是为了某一些层级关系,当 Inner 的使用范围很小,适用于包含在 Outer 类当中,但又不依赖于外在的类,就这么写,同时还能够让代码的类间层级关系更加清晰,而不用重新放在另一个文件。
3 局部内部类
局部内部类,是指内部类定义在方法内部,或者某一个作用域里的类。
- 局部内部类只能在方法里面实例化,外面就不行了;
- 局部内部类访问外部方法的变量,这个变量必须有 final 修饰。
例如下面的代码:
class Outter{
public void outMethod(){
final int i=0;
class Inner{
//使用i
}
Inner in=new Inner();
}
}
很明显,和普通的成员内部类是一样的定义,但是这里强调的特殊点:i 为什么必须是 final 的呢?
原因是:
- 当 JVM 运行到需要创建 Inner 对象之后,Outter 类已经全部运行完毕,那么垃圾回收机制是有可能释放掉局部变量 i 的,而如果按照普通的成员内部类的定义,i 应该可以访问才对,那这个问题需要一个约束,所以编译器解决了这个问题,他会在 Inner 类中创建了一个 i 的备份;
- 那么就会有新的问题,外部类的 i 变化的时候,内部类的 i 要一致才行;
- 因此不得不规定死这些局部域必须是常量,必须用 final 修饰,保证数据的一致。
4 匿名内部类
4.1 匿名内部类
当一个局部内部类没有名字,这就是匿名内部类。
- 实现的方式是建立一个带内容的外部类、或者接口的子类匿名对象。
- 和普通的局部内部类又不同,访问外部方法的变量,这个变量可以不是final修饰。(jdk1.8之后)
比如我们常见的两种
public static void main(String[] args) {
//第一种方式
new Thread(){
@Override
public void run() {
System.out.println("内部类输出……");
}
}.start();
//第二种方式
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("内部类输出……");
}
}).start();
}
- 第一种方式就是,建立了一个带内容的外部类,重写了它的方法,但是这个类我们没有起名字,而是直接创建了一个实例;
- 第二种方式就是通过实现一个接口,实现对应的方法,同样,这个实现类我们也没有起名字,而是直接创建了一个实例。
正是因为上面所说的,匿名内部类可以访问外部的变量,而且不用是 final ,同理也可以持有外部类的引用,这种情况都可能会导致内存泄漏问题。
外部类的生命周期到了,但是却因为内部类持有者外部类的引用,所以导致外部类无法被回收,造成内存泄漏。
解决方案就是:
- 使用静态内部类,并且不要持有外部类的引用,如果要调用外部类方法或使用外部类属性,可以使用弱引用(前面说过,静态内部类是和外部类平行的,弱引用会安全)。
4.2 匿名内部类和 lambda 表达式
从 java 8 开始,引入了 Lambda 表达式,将代码块作为参数使用更简洁的代码来创建只有一个抽象方法的接口(这种接口被称为函数式接口)的实例。
仍然用上面的匿名内部类的代码作为示例:
public static void main(String[] args) {
new Thread(()-> System.out.println("内部类输出")).start();
}
可以看出,lambda 表达式代替匿名内部类的时候,lambda 代码块写的是代替实现抽象类的方法体,总结一下lambda表达式的语法主要由三部分构成:
形参列表,如果只有一个参数可以省略括号,当无参数类型时可以使用()或者obj来代替。
箭头 ->
代码块部分,如果代码只有一行则可以省略掉花括号,不然使用花括号将lambda表达式的代码部分标记出来。
写法方面,我们再自己定义一个接口,然后写一下 lambda 式有参数的情况:
//自定义接口
interface Origin{
int sum(int a, int b);//待实现方法,有参数
}
public class LambdaTest {
public static void main(String[] args) {
//写法1:使用lambda表达式实现接口
Origin o = (int a, int b)-> {
return a+b;
};
//写法2:省略参数类型
Origin o1 = (a, b)->{
return a+b;
};
//写法3,省略花括号(只适用于方法实现只有一行的情况)
Origin o2 = (a, b)-> a+b;
System.out.println(o.sum(100,100));
}
}
4.3 java 的四种引用类型
强引用:
最常见的普通对象引用,只要还有强引用指向一个对象,说明那个对象还活着,垃圾回收不会回收这种对象;
弱引用:
垃圾回收器一旦发现只具有弱引用的对象,不管当前内存空间是否足够,都会回收他的内存。(即使弱引用被其他强引用引用,还是会被回收)
软引用:
如果一个对象只具备软引用,如果内存空间足够,那么 JVM 就不会 GC 它,如果内存空间不足了,就会GC该对象。
虚引用:
如果一个对象只具有虚引用,那么它就和没有任何引用一样,随时会被JVM当作垃圾进行GC。
不会被回收的情况?
强引用,只要还存在强引用就不会被回收。
关于强引用和弱引用带来的问题, 最明显的就在 ThreadLocal 的使用上。
Java 的各种内部类、Lambda表达式的更多相关文章
- Java核心技术-接口、lambda表达式与内部类
本章将主要介绍: 接口技术:主要用来描述类具有什么功能,而并不给出每个功能的具体实现.一个类可以实现一个或多个接口. lambda表达式:这是一种表示可以在将来的某个时间点执行的代码块的简洁方法. 内 ...
- Java函数式编程和lambda表达式
为什么要使用函数式编程 函数式编程更多时候是一种编程的思维方式,是种方法论.函数式与命令式编程的区别主要在于:函数式编程是告诉代码你要做什么,而命令式编程则是告诉代码要怎么做.说白了,函数式编程是基于 ...
- Java基础教程:Lambda表达式
Java基础教程:Lambda表达式 本文部分内容引用自OneAPM:http://blog.oneapm.com/apm-tech/226.html 引入Lambda Java 是一流的面向对象语言 ...
- java函数式编程之lambda表达式
作为比较老牌的面向对象的编程语言java,在对函数式编程的支持上一直不温不火. 认为面向对象式编程就应该纯粹的面向对象,于是经常看到这样的写法:如果你想写一个方法,那么就必须把它放到一个类里面,然后n ...
- 最全最强 Java 8 - 函数编程(lambda表达式)
Java 8 - 函数编程(lambda表达式) 我们关心的是如何写出好代码,而不是符合函数编程风格的代码. @pdai Java 8 - 函数编程(lambda表达式) 简介 lambda表达式 分 ...
- Java 8:掌握 Lambda 表达式
本文将介绍 Java 8 新增的 Lambda 表达式,包括 Lambda 表达式的常见用法以及方法引用的用法,并对 Lambda 表达式的原理进行分析,最后对 Lambda 表达式的优缺点进行一个总 ...
- Java基础进阶:内部类lambda重点摘要,详细讲解成员内部类,局部内部类,匿名内部类,Lambda表达式,Lambda表达式和匿名内部类的区别,附重难点,代码实现源码,课堂笔记,课后扩展及答案
内部类lambda重点摘要 内部类特点: 内部类可以直接访问外部类,包括私有 外部类访问内部类必须创建对象 创建内部对象格式: 外部类.内部类 对象名=new外部类().new内部类(); 静态内部类 ...
- 0028 Java学习笔记-面向对象-Lambda表达式
匿名内部类与Lambda表达式示例 下面代码来源于:0027 Java学习笔记-面向对象-(非静态.静态.局部.匿名)内部类 package testpack; public class Test1{ ...
- Upgrading to Java 8——第一章 Lambda表达式
第一章 Lambda表达式 Lamada 表达式是Java SE 8中最重要的新特性,长期以来被认为是在Java中缺失的特性,它的出现使整个java 语言变得完整.至少到目前,在这节中你将学习到什么是 ...
- Java 终于有 Lambda 表达式啦~Java 8 语言变化——Lambda 表达式和接口类更改【转载】
原文地址 en cn 下载 Demo Java™ 8 包含一些重要的新的语言功能,为您提供了构建程序的更简单方式.Lambda 表达式 为内联代码块定义一种新语法,其灵活性与匿名内部类一样,但样板文件 ...
随机推荐
- Revit二开---Schemachema扩展数据
一.什么是Schema Schema是Revit扩展数据的技术关键词,revit到这里,需要对Revit二开基础有一定了解. 二.Schema架构 建立revit扩展数据第 ...
- # c++运算符重载之 前置++, 后置++, 负号运算符, 类型转换函数, 以及输入输出运算符
c++运算符重载之 前置++, 后置++, 负号运算符, 类型转换函数, 以及输入输出运算符 标签(空格分隔): c++ 前言 我在c++学习的过程中, 对这几个不太常见的运算符重载不太会写.出现了很 ...
- MongoDB学习1:认识文档数据库MongoDB
1. 关于MongoDB 什么是MongoDB 一个以JSON为数据模型的文档数据库 为什么叫文档数据库 文档来自于"JSON Document",并非我们一般理解的pdf,wor ...
- 实型(浮点型):float、double
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<string.h> #include<stdlib. ...
- Elasticsearch第五篇:PlainElastic.Net 操作 Elasticsearch
再次强调,我安装的Elasticsearch 版本是 7.8.0 ,C# 操作 Elasticsearch 的驱动有 NEST.Elasticsearch.net .PlainElastic.Net ...
- HotSpot的垃圾回收器
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现.这里讨论的收集器基于JDK 1.7 Update 14之后的 HotSpot 虚拟机,这个虚拟机包含的所有收集器如下图所示 上图 ...
- DataGrid添加进度条列
DataGridColumn类型的继承树 DataGridColumn的派生类: 一般情况下DataGridBoundColumn和DataGridComboBoxColumn足以满足多数列的样式,如 ...
- Vue管理系统前端系列二相关工具引入及封装
目录 sass-loader/vuex 等的引入说明 引入 element 引入 axios 1.基本使用 2.封装使用 2.1 开发环境配置请求地址 2.2 配置代理 2.3 添加接口相关文件 sa ...
- 第4章 DDL数据定义
第4章 DDL数据定义 4.1 创建数据库 1)创建一个数据库,数据库在HDFS上的默认存储路径是/user/hive/warehouse/*.db. hive (default)> creat ...
- 精讲响应式WebClient第3篇-POST、DELETE、PUT方法使用
本文是精讲响应式WebClient第3篇,前篇的blog访问地址如下: 精讲响应式webclient第1篇-响应式非阻塞IO与基础用法 精讲响应式WebClient第2篇-GET请求阻塞与非阻塞调用方 ...