JavaSE学习总结(七)面向对象(下)抽象类/接口/接口的默认方法/抽象类和接口的区别/链式编程/成员内部类/局部内部类/匿名内部类/类中定义接口/函数式接口/Lambda表达式
面向对象(下)
一、抽象类
(一)抽象类概述
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
在Java中,一个没有方法体的方法应该定义为抽象方法,而类中如果有抽象方法,该类必须定义为抽象类。
(二)抽象类特点
1.抽象类和抽象方法必须用abstract关键字修饰
抽象类格式:abstract class 类名 {}
抽象方法格式:public abstract void eat();
2.抽象方法没有方法实现体,只给出一个方法声明
抽象类,就是被abstract所修饰的类,父类将所有子类的共性功能向上抽取后,他并不知道,每个子类对这个共性功能的具体实现,所以没有必要在父类中给出共性功能的具体实现,而是给出声明即可,所谓给出功能的声明,就是将此功能抽象出来,然后强制子类必须重写该抽象的功能。
3.抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类
4.抽象类中可以有构造方法,但抽象类不能进行实例化,那么要构造方法有什么作用呢?
用于子类访问父类数据时的初始化
5.抽象类不能直接实例化,那么抽象类如何实例化呢?
按照多态的方式,由具体的子类实例化。其实这也是多态的一种,抽象类多态。
6.抽象类的子类
要么是抽象类
要么重写抽象类中的所有抽象方法
案例演示1
public class MyTest {
public static void main(String[] args) {
//new Animal();报错:抽象类不能直接创建对象
//我们可以采用多态间接的去实例化抽象类
Animal an=new Cat();
an.eat();
an.sleep();
an.show();
}
}
//一旦一个类中,有了抽象方法,此类必须为抽象类
//一个抽象类中也可以没有抽象方法
//抽象类中既可以有抽象方法,也可以有非抽象方法,抽象方法,强制子类重写,非抽象方法,可以让子类继承下去用
abstract class Animal {
public Animal() {
System.out.println("父类的构造方法执行了");
}
//abstract关键字:抽象的,可以修饰类,修饰方法
public abstract void eat(); //抽象方法,此方法没有方法实现体
public abstract void sleep();//抽象方法,此方法没有方法实现体
//抽象类中既可以有抽象方法,也可以有非抽象方法
public void show(){
System.out.println("这是父类的一个非抽象方法");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫爱吃鱼");
}
@Override
public void sleep() {
System.out.println("猫白天睡觉");
}
}
案例演示2
抽象类的子类,要么重写父类中所有的抽象方法,要么自己也是一个抽象类
public class MyTest {
public static void main(String[] args) {
}
}
abstract class A {
public abstract void a();
public abstract void aa();
}
abstract class B extends A{//抽象类的子类,如果选择不重写抽象类的抽象方法,那么它必须也是一个抽象类
public abstract void b();
}
class C extends B{//C类必须重写B类和A类的所有抽象方法
@Override
public void a() {
}
@Override
public void aa() {
}
@Override
public void b() {
}
}
问1:一个类如果没有抽象方法,可不可以定义为抽象类 ? 如果可以,有什么意义?
答:可以,定义为抽象类,外界就不能直接创建该类的对象。
问2:abstract不能和哪些关键字共存?
答:
- private:如果用private修饰抽象方法,该方法不能被重写,矛盾。
- final:如果用final修饰抽象类,该类不能被继承,矛盾;如果用final修饰抽象方法,该方法不能被重写,矛盾。
- static:如果用static修饰抽象方法,方法变为静态方法,静态方法不存在重写。
(三)抽象类的成员特点
1.成员变量:既可以是变量,也可以是常量。
2.构造方法:有。
用于子类访问父类数据的初始化。
3.成员方法:既可以是抽象的,也可以是非抽象的。
- 抽象方法: 强制要求子类重写。
- 非抽象方法: 子类继承,提高代码复用性。
案例演示
public class MyTest {
public static void main(String[] args) {
}
}
abstract class A{
//抽象类中的成员变量 即可定义变量也可以定义常量
int num=100;
public static final int num2=1000;
//抽象类中有构造方法,用来让子类创建对象时初始化父类数据
public A(int num) {
this.num = num;
}
}
二、接口
(一)接口概述
接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
接口被实现体现的是:”like a”的关系。 接口中定义的是该继承体系的扩展功能。
(二)接口特点
1.接口用关键字interface表示
格式:interface 接口名 {}
2.类实现接口用implements表示
格式:class 类名 implements 接口名 {}
3.接口不能实例化
那么,接口如何实例化呢?
按照多态的方式来实例化。
4.接口的子类
a:可以是抽象类。但是意义不大。
b:可以是具体类。要重写接口中的所有抽象方法。(推荐方案)
总而言之,除非实现接口的类是抽象类,否则该类要实现接口中的所有方法。
案例演示
public class MyTest {
public static void main(String[] args) {
Cat cat = new Cat();
Animal an = cat;
an.eat();
an=new Dog();
an.eat();
// CalcInterface 是猫类的一个父接口,猫类也是父接口的一个子类
CalcInterface c = cat;
c.calc();
//多态
c= new Dog();
c.calc();
//接口不能直接实例化
}
}
abstract class Animal {
public abstract void eat();
}
interface CalcInterface {
public abstract void calc();
}
class Cat extends Animal implements CalcInterface{//继承Animal类的同时实现CalcInterface接口
@Override
public void eat() {
System.out.println("猫吃鱼");
}
@Override
public void calc() {
System.out.println("猫经过不断地努力学习,会做算术了");
}
}
class Dog extends Animal implements CalcInterface{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
@Override
public void calc() {
System.out.println("狗经过不断地努力学习,会做算术了");
}
}
(三)接口的成员特点
1.成员变量:只能是常量,并且是静态的。
默认(缺省)修饰符:public static final
建议:自己手动给出。
2.构造方法:接口没有构造方法。
3.成员方法:没有非抽象方法,只能是抽象方法。
默认(缺省)修饰符:public abstract
建议:自己手动给出。
案例演示
public class MyTest {
public static void main(String[] args) {
//接口中的成员变量全是静态的公共常量
}
}
interface A{
//接口中成员变量前面有默认的修饰符:public static final
int a=100;
public static final int b=1000;
//接口中成员方法前面存在默认修饰符:public abstract
void test();
public abstract void show();
}
(四)接口的默认方法
在JDK1.8之后,接口中提供了用default修饰的默认方法,可以给出方法的具体实现,子类可以继承下去用
默认方法不是抽象方法,可以被继承,不强制重写,也可以重写
案例演示
public class MyTest{
public static void main(String[] args) {
C c= new C();
c.test1();
c.test22();
c.show1();
c.show11();
A a=c;
a.show1();
a.test1();
a.test2();
B b=c;
b.show11();
b.test11();
b.test22();
}
}
interface A{
void show1();
//JDK1.8之后接口中可以定义默认方法,可以有方法的具体实现
default void test1(){//前面缺省一个public,默认方法不是抽象方法
System.out.println("test1");
}
default void test2() {
System.out.println("test2");
}
}
interface B {
void show11();
default void test11() {
System.out.println("test11");
}
default void test22() {
System.out.println("test22");
}
}
class C implements A,B{
@Override
public void show1() {
System.out.println("show1");
}
@Override
public void show11() {
System.out.println("show11");
}
//接口的默认方法不强制重写,可以直接继承用,当然也可以重写
@Override
public void test22() {
System.out.println("Test22");
}
}
三、区别与联系
(一)类与类、类与接口、接口与接口的关系
1.类与类:
继承关系。只能单继承,不能多继承,可以多层继承。
2.类与接口:
实现关系。可以单实现,也可以多实现,并且还可以在继承一个类的同时实现多个接口。
3.接口与接口:
继承关系。可以单继承,也可以多继承。
案例演示
public class MyTest {
public static void main(String[] args) {
}
}
class Fu{
public void test(){
};
}
interface A{
void a();
}
interface B{
void b();
}
class Zi extends Fu implements A,B{//可以继承一个类的同时实现多个接口
@Override
public void a() {
}
@Override
public void b() {
}
}
interface C{
void c();
}
interface D{
void d();
void dd();
}
interface E extends C,D{//接口和接口是继承关系,可以多继承
void e();
void ee();
}
class F implements E{//要实现E中和E继承的接口中的所有抽象方法
@Override
public void c() {
}
@Override
public void d() {
}
@Override
public void dd() {
}
@Override
public void e() {
}
@Override
public void ee() {
}
}
(二)抽象类和接口的区别
1.成员区别
- 抽象类:
成员变量:可以是变量,也可以是常量
构造方法:有
成员方法:可以是抽象方法,也可以是非抽象方法 - 接口:
成员变量:只可以是常量
构造方法:无
成员方法:只可以是抽象方法
2.关系区别
具体见(一)
3.设计理念区别
抽象类被继承体现的是:”is a”的关系。 抽象类中定义的是该继承体系的共性功能。
接口被实现体现的是:”like a”的关系。 接口中定义的是该继承体系的扩展功能。
四、形式参数与返回值
(一)形式参数
1.类名作为形式参数
如果一个方法的形参要一个类 类型,那就传一个该类的对象
public class MyTest {
public static void main(String[] args) {
Student student = new Student();
int num=2;
//如果一个方法的形参要一个类 类型,就传一个该类的对象
set(student,num);
student.show(new Student(),100);
System.out.println(student.num);
}
public static void set(Student student,int num){
student.num=num;
}
}
class Student{
int num=10;
public void show(Student student,int num){
student.num=num;
}
}
2.抽象类名作为形式参数
如果一个方法的形参要一个抽象类 类型,那就传一个该抽象类的子类对象
public class MyTest {
public static void main(String[] args) {
int num=1;
Zi zi = new Zi();
//如果一个方法的形参要一个抽象类 类型,那就传一个该抽象类的子类对象
set(zi,num);
System.out.println(zi.num);
}
public static void set(Fu fu,int num){
fu.num=num;
}
}
abstract class Fu{
int num=100;
public abstract void show(int num);
}
class Zi extends Fu{
int num=10;
@Override
public void show(int num) {
this.num=num;
}
}
3.接口名作为形式参数
如果一个方法的形参要一个接口类型,那就传一个该接口的子类对象
public class MyTest {
public static void main(String[] args) {
int num=1;
//如果一个方法的形参要一个接口类型,那就传一个该接口的子类对象
B b = new B();
set(b,num);
System.out.println(b.num);
System.out.println(b.NUM);
System.out.println(A.NUM);
System.out.println(B.NUM);
}
public static void set(A a,int num){
new B().num =num;
a.show(num);
}
}
interface A{
int NUM=100;
void show(int num);
}
class B implements A{
int num =10;
@Override
public void show(int num) {
this.num =num;
}
}
(二)返回值
1.类名作为返回值类型
public class MyTest {
public static void main(String[] args) {
int num = 100;
A a = new A();
A a1 = a.getA(num);
System.out.println(a.num);
System.out.println(a1.num);
System.out.println(a);
System.out.println(a1);
System.out.println(a == a1);
A a2 =getAA(a,num);
System.out.println(a.num);
System.out.println(a2.num);
System.out.println(a);
System.out.println(a2);
System.out.println(a == a2);
}
public static A getAA(A a,int num) {
A a1 = new A();
a1.num = num;
return a1; //如果一个方法的返回值类型是一个类 类型,那就返回该类的一个对象
}
}
class A {
int num = 1;
public A getA(int num) {
A a = new A();
a.num = num;
return this; //如果一个方法的返回值类型是一个类 类型,那就返回该类的一个对象
}
}
2.抽象类名作为返回值类型
如果一个方法的返回值类型要一个抽象类 类型,那就返回一个该抽象类的子类对象
public class MyTest {
public static void main(String[] args) {
Zi zi = new Zi();
Fu fu = zi.getFu(zi, 109);
System.out.println(zi.num);
System.out.println(fu.num);
System.out.println(zi==fu);
}
}
abstract class Fu {
int num = 10;
}
class Zi extends Fu {
int num=100;
public Fu getFu(Fu fu, int num) {
fu.num = num;
return this;//如果一个方法的返回值类型要一个抽象类 类型,那就返回一个该抽象类的子类对象
}
}
3.接口名作为返回值类型
如果一个方法的返回值类型要一个接口类型,那就返回该接口的一个子类对象
public class MyTest {
public static void main(String[] args) {
MyClass myClass = new MyClass();
int num=2;
MyInterface myInterface = get(myClass, num);
myClass.show(109);
System.out.println(myClass.num);
System.out.println(myInterface.NUM);
}
public static MyInterface get(MyClass myClass,int num){
myClass.num=num;
return myClass;//如果一个方法的返回值类型要一个接口类型,那就返回该接口的一个子类对象
}
}
interface MyInterface{
int NUM=100;
void show(int num);
}
class MyClass implements MyInterface{
int num=1;
@Override
public void show(int num) {
this.num=num;
}
}
五、链式编程
所谓的链式编程就是可以通过"点"语法,将需要执行的代码块连续的书写下去,使得代码简单易读,书写方便。
public class MyTest {
public static void main(String[] args) {
Student student = new Student();
student.getStudent(student, 100).show(109);//链式编程
//等价于:
//Student student = new Student();
//Student student1 = student.getStudent(student, 100);
//student1.show(109);
System.out.println(student.num);
}
}
class Student {
int num = 10;
public Student getStudent(Student stu, int num) {
stu.num = num;
return this;
}
public void show(int num) {
this.num = num;
}
}
六、关键字package和import
(一)package
1.包(package)的概述
包,就是文件夹
为了更好地组织类,Java 提供了包机制,用于区别类名的命名空间。
2.包的作用
- 把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
- 如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
- 包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。
Java 使用包(package)这种机制是为了防止命名冲突。
3.包的划分:
- 按照功能
- 按照模块
4.定义包的格式
package 包名;
多级包用.分开即可
5.定义包的注意事项
A:package语句必须是程序的第一条可执行的代码
B:package语句在一个java文件中只能有一个
C:如果没有package,默认表示无包名
案例演示
package com.study.util;
public class Student{
}
那么它的保存路径应该是com/study/util/Student.java
(二)import
1.导包的概述
我们发现,每次使用不同包下的类的时候,都需要在类名的前面加包的全路径,比较麻烦。这个时候,java就提供了导包的功能
2.导包格式
import 包名.类名;
导入该包的某个类
import 包名.*;
导入该包的所有类(不建议)
一个.java文件中,书写顺序:package-import-class
七、修饰符
(一)权限修饰符
1.种类
private(私有的) 、默认、protected(受保护的)、public(公共的)
2.被修饰后的访问权限
本类 | 同一个包下(子类和无关类) | 不同包下(子类) | 不同包下(无关类) | |
---|---|---|---|---|
private | √ | |||
默认 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
(二)关于修饰符
1.修饰符:
权限修饰符:private,默认的,protected,public
状态修饰符:static,final
抽象修饰符:abstract
2.修饰类的关键字:
权限修饰符:默认修饰符,public
状态修饰符:final
抽象修饰符:abstract
用的最多的就是:public
3.修饰成员变量的关键字:
权限修饰符:private,默认的,protected,public
状态修饰符:static,final
用的最多的就是:private
4.修饰构造方法的关键字:
权限修饰符:private,默认的,protected,public
用的最多的就是:public
5.修饰成员方法的关键字:
权限修饰符:private,默认的,protected,public
状态修饰符:static,final
抽象修饰符:abstract
用的最多的就是:public
6.除此以外的组合规则:
成员变量:public static final
成员方法:public static
public abstract
public final
八、内部类
(一)内部类概述
1.内部类定义: 把类定义在其他类的内部,这个类就被称为内部类。
举例:在类A中定义了一个类B,类B就是内部类。
2.内部类访问特点
- 内部类可以直接访问外部类的成员,包括私有。
- 外部类要访问内部类的成员,必须创建对象。
3.内部类分类
按照内部类位置分类
- 成员位置:在成员位置定义的类,被称为成员内部类。
- 局部位置:在局部位置定义的类,被称为局部内部类。
案例演示
public class B {
//成员内部类
class A {
}
public void show() {
//局部内部类
class C {
}
}
}
(二)成员内部类
1.成员内部类的访问
如何在无关类中直接访问内部类的非私有成员。
先创建对象,格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象;
案例演示
public class MyTest {
public static void main(String[] args) {
Outer outer = new Outer();
System.out.println(outer.num);
outer.outerShow();
System.out.println("---------------");
//在无关类创建成员内部类的语法
//外部类名.内部类名 对象名 = 外部类对象.内部类对象;
Outer.Inner inner=new Outer().new Inner();
System.out.println(inner.b);
//System.out.println(inner.c);报错
inner.innerTest();
inner.innerShow();
System.out.println("---------------");
outer.method();
}
}
class Outer {//外部类
int num = 10;
private int a = 100;
//定义成员内部类
class Inner {
int b = 109;
private int c=1;
public void innerShow() {
System.out.println("内部类的show方法");
}
//内部类可以直接访问外部类的成员变量和成员方法,包括私有
public void innerTest(){
System.out.println(num);
System.out.println(a);
outerShow();
outerTest();
}
}
public void outerShow() {
System.out.println("外部类的show方法");
}
private void outerTest() {
System.out.println("外部类的test方法");
}
//外部类,想要访问内部类的成员,得创建内部类的对象
public void method(){
//创建内部类的对象
Inner inner = new Inner();
System.out.println(inner.b);
System.out.println(inner.c);//外部类访问内部类私有成员变量
inner.innerShow();
}
}
2.成员内部类的常见修饰符
- private 为了保证数据的安全性
- static 为了方便访问数据
注意事项:
- 静态内部类访问的外部类数据必须也是静态的。
- 静态内部类的成员方法可以是静态的也可以是非静态的
案例演示1
被private修饰的成员内部类
public class MyTest {
public static void main(String[] args) {
//创建内部类的对象
//内部类被private修饰了,外界就不能直接创建内部类对象了
//Wai.Nei nei = new Wai().new Nei();不适用
//那外界该怎么访问私有的成员内部类呢?
//可以通过外部类的方法访问
Wai wai = new Wai();
wai.waiShow();
}
}
class Wai{
//private 可以修饰内部类
private class Nei{
int num=10;
public void neiShow(){
System.out.println("内部类的show方法");
}
}
public void waiShow(){
Nei nei = new Nei();
System.out.println(nei.num);
nei.neiShow();
}
}
案例演示2
被static修饰的成员内部类
成员内部类被静态修饰后的访问方式是:
格式:外部类名.内部类名 对象名 = new 外部类名.内部类名();
public class MyTest {
public static void main(String[] args) {
//内部类被静态修饰后,创建内部类的语法要改变
Wai.Nei nei = new Wai.Nei();
}
}
class Wai {
static int num = 10;
private int a = 100;
static class Nei {
public void neiShow() {
//静态内部类要访问外部类的成员只能访问静态的
System.out.println(num);
waiShow();
//System.out.println(a);报错
}
}
public static void waiShow() {
System.out.println("外部类的show方法");
}
}
思考题
要求:使用已知的变量,在控制台输出30,20,10。
class Outer {
public int num = 10;
class Inner {
public int num = 20;
public void show() {
int num = 30;
System.out.println(填空); //30
System.out.println(填空); //20
System.out.println(填空); //10
}
}
}
class InnerClassTest {
public static void main(String[] args) {
Outer.Inner oi = new Outer().new Inner();
oi.show();
}
}
答案:
num
this.num 或 Inner.this.num
new Outer().num 或 Outer.this.num
(三)局部内部类
与成员内部类不同的是,局部内部类,外界不能直接创建其对象,只能通过外部类访问
public class MyTest {
public static void main(String[] args) {
//与成员内部类不同的是,局部内部类,外界不能直接创建其对象,只能通过外部类访问
Outer outer = new Outer();
outer.waiShow();
}
}
class Outer {
int num=10;
private int a=100;
public void waiShow(){
//定义局部内部类
//内部类可以直接访问外部类的成员变量和成员方法,包括私有的
class Inner{
int c=1;
public void neiShow(){
System.out.println(c);
System.out.println(num);
System.out.println(a);
}
}
//外部类访问内部类要创建对象
Inner inner = new Inner();
System.out.println(inner.c);
inner.neiShow();
//除了局部内部类所在方法的外界无法直接访问该内部类
}
}
注意:
局部内部类访问的局部变量必须用final修饰
为什么呢?
因为局部变量的生命周期与局部内部类的对象的生命周期的不一致,局部变量会随着方法的调用完毕而消失,这个时候,局部内部类的对象并没有立马从堆内存中消失(只有当没有人再引用该对象时,它才会消失,它不会随着所在方法运行结束而消失)。为了让数据还能继续被局部内部类对象使用,就用final修饰,这样,通过final将局部变量"复制"一份,复制品直接作为局部内部中的数据成员。因此,当运行栈中的真正的局部变量消失时,局部内部类对象仍可以访问局部变量。给人的感觉:好像是局部变量的"生命期"延长了。
案例演示
public class MyTest{
public static void main(String[] args) {
Outer out = new Outer() ;
out.show() ;
}
}
class Outer {
// 成员变量
private int a = 40 ;
// 成员方法
public void show() {
// 定义一个局部变量
int b = 45 ;//JDK1.7之前要加final不然报错,但是在JDK1.8默认加上了,不会报错
// 局部内部类
class Inner {
public void method(){
System.out.println(a) ;
System.out.println(b) ;
//b=50;//报错,final修饰的变量不可改变
}
}
// 创建对象
Inner i = new Inner() ;
// 调用方法
i.method() ;
}
}
(四)匿名内部类
1.匿名内部类的格式和理解
(1)匿名内部类:就是局部内部类的简化写法。
(2)前提:存在一个类或者接口;这里的类可以是具体类也可以是抽象类。
(3)格式:
new 类名或者接口名(){
重写方法;
} ;
123
(4)本质是什么呢?
是一个继承了该类或者实现了该接口的子类匿名对象。
案例演示1
public class MyTest{
public static void main(String[] args) {
//匿名内部类:是局部内部类的简写
//不需要再多定义一个类继承A类
new A(){
};
//还可以通过匿名内部类调用方法
new A(){
}.test();
}
}
class A{
public void test(){
System.out.println("test");
}
}
案例演示2
public class MyTest {
public static void main(String[] args) {
//匿名内部类,本质上是一个对象,是谁的对象,是实现了该接口或继承了该抽象类的子类对象
new AA(){
@Override
public void show() {
System.out.println("重写了show方法");
}
}.show();
}
}
abstract class AA{
public abstract void show();
}
案例演示3
public class MyTest{
public static void main(String[] args) {
//一个匿名内部类只能调用一个方法
new MyInterface(){
@Override
public void show() {
System.out.println("重写了接口中的show方法");
}
@Override
public void test() {
System.out.println("重写了接口中test方法");
}
}.show();
//一个匿名内部类只能调用一个方法
new MyInterface() {
@Override
public void show() {
System.out.println("重写了接口中的show方法");
}
@Override
public void test() {
System.out.println("重写了接口中test方法");
}
}.test();
//那我想同时调用两个方法怎么办?
//给匿名内部类起个名字
MyInterface my= new MyInterface() {
@Override
public void show() {
System.out.println("重写了接口中的show方法");
}
@Override
public void test() {
System.out.println("重写了接口中test方法");
}
};
//同一个对象,去调用两个方法
my.show();
my.test();
}
}
interface MyInterface{
void show();
void test();
}
2.匿名内部类在开发中的应用
方法的形式参数或返回值是引用类型的情况,通常需要一个子类对象,而匿名内部类就是一个子类匿名对象,所以,可以使用匿名内部类改进以前的做法。
案例演示1
匿名内部类作为形式参数
public class MyTest {
public static void main(String[] args) {
//匿名内部类可以作为参数传递
//调用set方法,参数为匿名内部类
set(new MyInterface() {
@Override
public void show() {
System.out.println("重写了show方法");
}
@Override
public void test() {
System.out.println("重写了test方法");
}
});
//方法二
MyInterface my= new MyInterface() {
@Override
public void show() {
System.out.println("重写了show方法2222");
}
@Override
public void test() {
System.out.println("重写了test方法2222");
}
};
set(my);
}
public static void set(MyInterface myInterface){
myInterface.show();
myInterface.test();
}
}
interface MyInterface{
void show();
void test();
}
案例演示2
匿名内部类作为返回值
public class MyTest {
public static void main(String[] args) {
A a = getA();
a.show();
}
public static A getA(){
return new A() {
@Override
public void show() {
System.out.println("重写了show方法");
}
};
}
}
abstract class A {
public abstract void show();
}
思考题
按照要求,补齐代码,要求在控制台输出”HelloWorld”
class MyTest {
public static void main(String[] args) {
Outer.method().show();
}
}
interface Inter {
void show();
}
class Outer { //补齐代码 }
Outer.method()是用类去调的方法,证明method方法必须是静态的,而后边还紧接着链式编程跟了一个show方法,证明Outer.method方法返回的是一个对象,而show方法在Inter接口中,因此Outer.method返回的应该是这个接口的子类对象,才能调用show方法。
public class MyTest {
public static void main(String[] args) {
//链式编程
Outer.method().show();
}
}
//接口
interface Inter {
void show();
}
class Outer {
public static Inter method(){
return new Inter() {
@Override
public void show() {
System.out.println("helloworld");
}
};
}
}
3.匿名内部类中的this关键字
interface Inter {
public static final int a = 23 ;
}
public class MyTest {
public static void main(String[] args) {
new Inter() {
public void show() {
// 这个this表示的是匿名内部类的这个对象
System.out.println(this.a);
}
}.show();
}
}
this表示的就是这个匿名内部类的对象,而它又是父接口的实现类,因此它可以使用接口中的成员变量。
4.类中定义接口
public class MyTest {
public static void main(String[] args) {
//要想重写show方法,有几种方式?
//间接方式(通过外部类创建内部接口的匿名内部类对象来重写)
Outer outer = new Outer();
outer.waiShow();
//直接方式(通过直接创建接口的匿名内部类对象重写)
new Outer.Inner() {
@Override
public void show() {
System.out.println("重写了接口中的show方法2");
}
}.show();
}
}
class Outer{
//成员内部接口(而局部不能定义接口)
interface Inner{
void show();
}
public void waiShow(){
//匿名内部类
new Inner(){
@Override
public void show() {
System.out.println("重写了接口中的show方法");
}
}.show();
}
}
(五)四大函数式接口
理解Functional Interface(函数式接口)是学习Java8 Lambda表达式的关键所在。
函数式接口的定义:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。
1.函数型接口 Function
案例演示
import java.util.function.Function;
public class MyTest{
public static void main(String[] args) {
//Function函数型接口,有一个输入参数,有一个输出
Function<String,String> function=new Function<String,String>() {
@Override
public String apply(String str) {
return str;
}
};
System.out.println(function.apply("abcd"));
}
}
2.断定型接口 Predicate
案例演示
import java.util.function.Predicate;
public class MyTest{
public static void main(String[] args) {
Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String str) {
return str.isEmpty();
}
};
System.out.println(predicate.test(""));
}
}
3.消费型接口 Consumer
案例演示
import java.util.function.Consumer;
public class MyTest{
public static void main(String[] args) {
//有参数,无返回值
Consumer<String> consumer = new Consumer<String>(){
@Override
public void accept(String str) {
System.out.println(str);
}
};
consumer.accept("abcd");
}
}
4.供给型接口 Supplier
案例演示
import java.util.function.Supplier;
public class MyTest{
public static void main(String[] args) {
//没有参数,有返回值
Supplier<Integer> supplier = new Supplier<Integer>(){
@Override
public Integer get() {
return 1024;
}
};
System.out.println(supplier.get());
}
}
(六)Lambda表达式
对于函数式接口,我们可以通过Lambda表达式来创建该接口的对象。
λ是希腊字母表中排序第十一位的字母,英语名称为Lambda,Lambda表达式其实质属于函数式编程的概念。
(params)-> expression [ 表达式 ]`
`(params)-> statement [ 语句 ]`
`(params)-> { statements }
为什么要用Lambda表达式?
- 避免匿名内部类定义过多
- 去掉了一堆没有意义的代码,只留下核心的逻辑,看起来更简洁
案例演示1
外部类、成员内部类、局部内部类、匿名内部类、Lambda表达式
interface Like{//函数式接口:只有一个抽象方法
void like();
}
//1.外部类
class Like1 implements Like{
@Override
public void like() {
System.out.println("I like lambda1");
}
}
public class MyTest {
//2.成员内部类
private static class Like2 implements Like{
@Override
public void like() {
System.out.println("I like lambda2");
}
}
public static void main(String[] args) {
Like l = new Like1();
l.like();
l = new Like2();
l.like();
//3.局部内部类
class Like3 implements Like{
@Override
public void like() {
System.out.println("I like lambda3");
}
}
l=new Like3();
l.like();
//4.匿名内部类
l=new Like() {
@Override
public void like() {
System.out.println("I like lambda4");
}
};
l.like();
//5.lambda表达式
l=()->{
System.out.println("I like lambda5");
};
l.like();
//也可以写成:
//l=()-> System.out.println("I like lambda5");
//l.like();
}
}
案例演示2
抽象方法带参数的Lambda表达式;Lambda表达式的一步步简化
interface Like{//函数式接口:只有一个抽象方法
void like(int a);
}
//1.外部类
class Like1 implements Like{
@Override
public void like(int a) {
System.out.println("I like lambda"+a);
}
}
public class MyTest {
//2.成员内部类
private static class Like2 implements Like{
@Override
public void like(int a) {
System.out.println("I like lambda"+a);
}
}
public static void main(String[] args) {
Like l = new Like1();
l.like(1);
l = new Like2();
l.like(2);
//3.局部内部类
class Like3 implements Like{
@Override
public void like(int a) {
System.out.println("I like lambda"+a);
}
}
l=new Like3();
l.like(3);
//4.匿名内部类
l=new Like() {
@Override
public void like(int a) {
System.out.println("I like lambda"+a);
}
};
l.like(4);
//5.lambda表达式
l=(int a)->{
System.out.println("I like lambda"+a);
};
l.like(5);
//6.简化lambda表达式:去掉参数类型
l=(a)-> {
System.out.println("I like lambda"+a);
};
l.like(6);
//7.简化lambda表达式:去掉括号
l=a-> {
System.out.println("I like lambda"+a);
};
l.like(7);
//8.简化lambda表达式:去掉花括号(前提:抽象方法的逻辑只有一行)
l=a->System.out.println("I like lambda"+a);
l.like(8);
}
}
案例演示3
抽象方法有多个参数的Lambda表达式
interface Like{//函数式接口:只有一个抽象方法
void like(int a,int b,int c);
}
//1.外部类
class Like1 implements Like{
@Override
public void like(int a,int b,int c) {
System.out.println("I like lambda"+a+b+c);
}
}
public class MyTest {
//2.成员内部类
private static class Like2 implements Like{
@Override
public void like(int a,int b,int c) {
System.out.println("I like lambda"+a+b+c);
}
}
public static void main(String[] args) {
Like l = new Like1();
l.like(1,1,1);
l = new Like2();
l.like(2,2,2);
//3.局部内部类
class Like3 implements Like{
@Override
public void like(int a,int b,int c) {
System.out.println("I like lambda"+a+b+c);
}
}
l=new Like3();
l.like(3,3,3);
//4.匿名内部类
l=new Like() {
@Override
public void like(int a,int b,int c) {
System.out.println("I like lambda"+a+b+c);
}
};
l.like(4,4,4);
//5.lambda表达式
l=(int a,int b,int c)->{
System.out.println("I like lambda"+a+b+c);
};
l.like(5,5,5);
//6.简化lambda表达式:去掉参数类型
l=(a,b,c)-> {
System.out.println("I like lambda"+a+b+c);
};
l.like(6,6,6);
//7.简化lambda表达式:去掉花括号(前提:抽象方法的逻辑只有一行)
l=(a,b,c)->System.out.println("I like lambda"+a+b+c);
l.like(7,7,7);
}
}
总结:
- 要使用Lambda表达式,前提是接口必须是函数式接口
- 抽象方法的逻辑只有一行的情况下,Lambda表达式才能去掉花括号
- 多个参数的情况下,也能去掉参数类型,要去掉就都去掉,但是括号不能去掉