在Java语言中类可以嵌套定义,广义的内部类指的是定义在另一个类当中的一个类
eg:
public class Demo{
}
class Computer{
private int a;
private class Cpu{
}
}
说明:
位置:定义在成员位置
语法:权限修饰符 class 类名{ 类体}
4种都可以
不能有static
的声名
可以有全局常量
可以继承和实现外部的类或者接口
还可以继承成员内部类
外部类和成员内部类的关系:把成员内部类当作外部类的一个成员看待
----->
:表示访问
this.
访问的是内部类成员,外部类.this.
访问的是外部类成员对象.
访问对象.
访问对象.
访问Outer.Inner inner = new Outer().Inner();
位置:成员位置
语法:在成员内部类前面加static
4种都可以
普通类有的都有
可以继承和实现外部的类或者接口
还可以继承静态内部类,成员内部类不可以
外部类和静态内部类的关系:两个独立的类,静态内部类不依赖于外部类
----->
:表示访问
对象.
访问对象.
访问对象.
访问对象.
访问对象.
访问Outer.Inner inner = new Outer().Inner();
静态内部类和外部类之间的类加载,它们会相互影响吗?
答:是不会的,静态内部类和外部类之间本身就没有依赖关系,它们的类加载、new对象,都是没有关系的
定义:局部内部类是定义在一个方法或者一个作用域里面的类,简单来说,将局部内部类看成是局部变量即可,该类的有效范围仅在作用域内部。(这意味着要创建它的对象,必须在作用域内部创建)
位置:局部位置
语法:class 类名 { 类体}
不需要了,没有意义,只在作用域内有效
不能有静态static
声明
可以有全局常量、构造方法、构造代码块
可以继承和实现外部的类或者接口(重要作用)
外部类和局部内部类的关系:把局部内部类当作外部类的一个局部变量理解
----->
:表示访问
成员名
去访问;this.
;类名.this.
访问的外部类成员对象.
访问对象.
访问对象.
访问对象.
访问public static IA m1(){
class A implements IA{
}
return new A();
}
interface IA{
}
方法需要传入一个对象,形参数据类型是引用数据类型时。
public static void main(String[] args){
class Son extends Father{
}
m3(new Son());
}
public static void m3(Father father){}
这个类是一次性的。
在局部内部类的成员方法中,如果想要访问作用域内部的局部变量,那么该变量:
要么直接就用final
修饰,是一个局部常量。
要么就是一个事实上的常量,即只能赋值一次,相当于用final
修饰它。
在Java8之前版本的JDK中,如果不给能在局部内部类内部,访问的局部变量直接加上final修饰,会编译报错。
原因? -----------------> 局部变量,和对象的生命周期冲突
private
、protected
修饰内部类。private
修饰内部类、外界感受不到该类存在。内部类的缺点
内部类的缺点也是显而易见,语法很复杂,在类中定义内部类也会导致类的结构变复杂,影响代码可读性。
除此之外,不合理使用内部类还可能导致内存泄漏(了解)
持有外部类对象引用的内部类对象,如果始终被使用而没有释放:
那么连带着外部类对象,也不会被销毁和释放内存。
这在极端的场景下,会导致堆内存溢出,存在一定的风险。
匿名内部类,指的就是这个内部类没有名字。当然成员内部类和静态内部类没法没有名字,所以 匿名内部类指的是"没有名字的局部内部类"。
是一种特殊的局部内部类
// 局部位置
new 类名或者接口名(){
// 某类名或者接口名的子类的类体
};
// 局部位置
解释说明:
new
表示创建对象,表示创建语法中"类名/接口名"的子类对象。new IA(){
@Override
public void m2(){
System.out.println("IA");
}
}.m2();
IA ia = new IA(){
@Override
public void m2(){
System.out.println("IA");
}
};
ia.m2();
ia.m2();
综上,两种使用方式场景不同。如果有多次使用需求,就需要父引用接收;反之如果仅用一次,或者需要访问子类独有成员,就必须直接使用,不能用引用接收。
匿名内部类经常使用在以下两个场景中:
方法需要返回一个对象,返回值类型是引用数据类型时。
方法需要返回一个对象时,可以在方法的局部位置写一个 基于X类或X接口的匿名内部类对象 ,然后直接作为返回值返回给外界。
eg:
public static Father func1(){
return new Father(){
@Override
void m2(){
System.out.println("IAAAA");
};
}
方法需要传入一个对象,形参数据类型是引用数据类型时。
方法需要传入一个对象实参时,可以在调用方法的实参位置填入一个 基于X类或X接口的匿名内部类对象,就将它作为一个实参传给方法使用。
public static void main(String[] args){
func4(new IA(){
@Override
void m2(){
System.out.println("IAAAA");
};
}
使用匿名内部类的优点:
使用匿名内部类的缺点:
总之,酌情使用匿名内部类对象,可以简化代码书写,方便省事。但不要为了使用而使用,假如存在多个场景都需要一个子类对象,那还是直接创建一个子类出来好了。
问题:不修改main
方法和接口Inter
的前提下,补齐Outer
类代码,要求在控制台输出HelloWorld
几种实现方式:
public class Demo{
public static void main(String[] args) {
Outer.method().show();
}
}
interface Inter {
void show();
}
class Outer {
// 1. 手写类实现接口
public static Inter method(){
return new InterImpl();
// 2. 局部内部类
class Inner implements Inter{
@Override
public void show(){
System.out.println("HelloWorld");
}
}
return new Inner();
// 3. 匿名内部类
return new Inner(){
@Override
public void show(){
System.out.println("HelloWorld");
}
}
// 4. lambda表达式
return () -> System.out.println("HelloWorld");
}
}
// 1. 手写类实现接口
class InerImpl implements Inter{
@Override
public void show(){
System.out.println("HelloWorld");
}
}
Lambda表达式虽然说是取代接口的匿名内部类,但也不是什么接口都能用Lambda表达式创建子类对象。
Lambda表达式要求的接口中,必须有且仅有一个必须要实现的抽象方法。这种接口在Java中,被称之为功能接口。
功能接口在语法上,可以使用注解@FunctionalInterface
标记在接口头上,用于检测一个接口是否是功能接口。
功能接口指的是,有且仅有一个必须要子类实现的抽象方法的接口。
@FunctionalInterface
interface IA{
void m1();
}
答:
不是,Java8中的默认方法和静态方法不需要子类实现,功能接口中可以允许有它们存在。
不是,有极个别比较特殊的抽象方法,可以不需要子类实现。
注:Object类是Java每一个类的父类,所以Object类当中的方法实现就可以作为接口抽象方法的实现。比如:
功能接口不仅有一个抽象方法
@FunctionalInterface
interface IA{
void test();
boolean equals(Object obj);
}
接口IA
仍然是一个功能方法,因为抽象方法boolean equals(Object obj);
可以直接使用Object类中的实现,无需子类实现。
(形参列表) -> {
// 方法体
}
说明:
(形参列表)
表示功能接口中,必须要重写的抽象方法的形参列表。->
由一个英文横杠 + 英文大于号字符组成,它是Lambda表达式的运算符,读作goes to。{ //方法体 }
表示功能接口中,必须要重写的抽象方法的,方法体实现。我们需要帮助编译器,明确Lambda表达式所表示的对象的类型
直接用父接口引用接收
由于Lambda表达式表示的子类对象并没有自己独有的成员,所以直接用父类引用接收完全不会有任何问题。
不用引用接收,但是要直接告诉编译器Lambda表达式是哪个接口的子类对象,语法上有点像强转(但不是)。
语法:
((父接口的名字)Lambda表达式).方法名(实参)
这种方式有点类似于匿名对象,所以必须直接调用方法,不然会编译语法报错。
借助方法完成类型推断。
举例:
public class Demo {
public static void main(String[] args) {
// 1. 使用父接口进行接收
IA ia = () -> {
System.out.println("方法1");
};
ia.m1();
// 2. 类似强制类型转换,没有使用引用接受
((IA) () -> {
System.out.println("方法2");
}).m1();
func2(() -> {
System.out.println("方法3");
});
}
// 3. 借助方法完成类型推断
// a. 可以借助方法的返回值数据类型完成类型推断,因为方法的返回值编译器已经知道该返回什么对象。
public static IA func1() {
// 使用lambda
return () -> {
System.out.println("方法3");
};
}
// b. 可以借助方法的形参的数据类型完成类型推断,因为方法的实参编译器已经知道该传入什么对象。
public static void func2(IA ia){
ia.m1();
}
// 用于检测一个接口是不是功能接口
@FunctionalInterface
interface IA{
void m1();
}
}
逐个部分简化:
(形参列表)
能不能简化呢?是可以的,因为功能接口中有且仅有一个必须要实现的抽象方法,那么:
形参的数据类型是可以省略的,因为方法已经固定死了,形参一定是那些,不需要写出来。但形参的名字是不可省略的(因为要在方法体中使用)
特殊情况下:
()
小括号,也是可以省略的。{ //方法体 }
方法体能不能简化呢?当然是可以的:
实际上在多数情况下,都不太可能一句话把方法体写完。多数情况下,Lambda表达式的抽象方法实现都会很复杂,那这样Lambda表达式就会写的很复杂,这就很难受了。而Lambda表达式,本质上就是重写了一个抽象方法的子类对象,所以Java允许Lambda表达式的抽象方法的实现可以直接指向一个已经存在的方法,而不是自己书写实现。这种语法在Java中称之为"方法引用"!
public class Demo1 {
public static void main(String[] args) {
INoReturnTwoParam iNoReturnTwoParam = (int a, int b) -> {
System.out.println(a + b);
};
iNoReturnTwoParam.test(1, 2);
// 1. 简化写法:形参列表的数据类型可以省略
INoReturnTwoParam iNoReturnTwoParam1 = (a, b) -> {
System.out.println(a);
System.out.println(b);
};
System.out.println("----------------------------");
INoReturnOneParam iNoReturnOneParam = (int a) -> {
System.out.println(a++);
};
// 简化写法
INoReturnOneParam iNoReturnOneParam1 = a -> {
System.out.println(a++);
};
iNoReturnOneParam.test(10);
System.out.println("----------------------------");
// 2. 如果形参为空,括号是不能省略掉的
INoReturnNoParam iNoReturnNoParam = () -> {
System.out.println("test");
};
iNoReturnNoParam.test();
System.out.println("----------------------------");
IHasReturnTwoParam iHasReturnTwoParam2 =
(a, b) -> {
int result = a + b;
return result;
};
int result = iHasReturnTwoParam2.test(1, 2);
System.out.println(result);
// 3. 简化写法
// 如果方法重写的方法体只有一条语句的话,那么可以省略大括号。(类似于if/for省略大括号)
// 如果只有一条语句且这条语句是返回值语句,那么大括号和return可以一起省略
IHasReturnTwoParam iHasReturnTwoParam3 =
(a, b) -> a + b;
INoReturnNoParam ip = () -> System.out.println("1111");
}
//无返回值无参数的功能接口
@FunctionalInterface
interface INoReturnNoParam {
void test();
}
//无返回值有一个参数的功能接口
@FunctionalInterface
interface INoReturnOneParam {
void test(int a);
}
//无返回值两个参数的功能接口
@FunctionalInterface
interface INoReturnTwoParam {
void test(int a, int b);
}
//有返回值无参数的功能接口
@FunctionalInterface
interface IHasReturnNoParam {
int test();
}
//有返回值一个参数的功能接口
@FunctionalInterface
interface IHasReturnOneParam {
int method(int a);
}
//有返回值两个参数的功能接口
@FunctionalInterface
interface IHasReturnTwoParam {
int test(int a, int b);
}
}
Lambda表达式的主体只有1条语句时, 程序不仅可以省略主体的大括号, 还可以通过英文双冒号::
的语法来引用方法, 进一步简化Lambda表达式的书写.
什么样的方法,能够作为方法引用指向的功能接口中抽象方法的实现?只看三点:
它是String类中的成员方法
语法:subString(String s, int start, int end) [start, end)
eg:
String s = "abcde";
String substring = s.subString(1,3); // bc
public class Demo3 {
public static void main(String[] args) {
// 1. 静态方法引用
// lambda写法
IA ia = () -> A.func1();
ia.testA();
IA ia1 = A::func1;
ia1.testA();
IB ib = s -> System.out.println(s);
ib.testB("aaaa");
// 2. 对象名引用成员方法
// Lambda写法
IC ic = a -> new C().func2(a);
ic.testC(100);
IC ic1 = new C()::func2;
ic1.testC(200);
// 3. 类名引用成员方法
ID id = (s, start, end) -> s.substring(start, end);
id.testD("abcdef", 1, 3);
ID id1 = String::substring;
String str = id1.testD("abcdef", 1, 3);
System.out.println(str);
// 4. 构造方法引用
IE ie = (a, b) -> new E(a, b);
ie.testE(1, 2);
IE ie1 = E::new;
ie1.testE(1, 3);
}
}
@FunctionalInterface
interface IA {
void testA();
}
@FunctionalInterface
interface IB {
void testB(String s);
}
@FunctionalInterface
interface IC {
void testC(int a);
}
@FunctionalInterface
interface ID {
String testD(String s, int start, int end);
}
@FunctionalInterface
interface IE {
void testE(int a, int b);
}
class A {
// 定义一个静态方法 作为IA接口中的testA方法的实现
static void func1() {
System.out.println("IA");
}
}
class C {
void func2(int m) {
System.out.println(m);
}
}
class E {
int a;
int b;
public E(int a, int b) {
this.a = a;
this.b = b;
}
}
1. 优点:
极大得简化了代码,使代码变得更加优雅。
函数式编程的代表,可能是未来高端的编程趋势
Stream API代码
list.
stream().
filter(stu -> stu.getAge() >= 18).
map(Student::getScore).
forEach(System.out::println);
注:该Stream API完成,将学生对象集合中的,所有大于等于18岁的学生的成绩输出的工作。
2. 缺点: