- 当一个类中的所有方法都是抽象方法的时候,我们就可以将其定义为接口,接口也是一种引用类型,它比抽象类还要抽象
接口存在的两个重要意义:
- 规则的定义(实现接口必须实现其所有抽象方法)
- 程序的扩展性(需要实现重写其抽象方法)
- 接口用关键字interface来定义
- public interface 接口名{}
public interface InterDemo1 {
// 这是一个接口
public abstract void test();
}
- 接口不能实例化,即不能创建接口对象
- 接口和类之间是实现关系,通过implements关键字表示
- public class 类名 implements 接口名{}
- 接口的子类(实现类),要么重写接口中所有抽象方法,要么作为抽象类定义
public class InterDemo1Impl implements InterDemo1{
@Override
public void test() {
System.out.println("重写test...");
}
}
public abstract class InterDemo1Impl2 implements InterDemo1{
}
注:
- 接口和类的实现关系,可以单实现,也可以多实现。public class 类名 implements 接口名1, 接口名2{}
- 可以多实现的原因:因为接口内方法都没有具体实现,虽然存在同名冲突,但都要重写,因此可以多实现
- 接口中的成员变量:只能是常量,且默认是静态的
- 若不写修饰符,默认修饰符:public static final
- 没有构造方法
- 因为接口主要是扩展功能的,而没有具体存在,即无法实例化
- 在JDK7之前,成员方法只能是抽象方法
- 若不写修饰符,则默认修饰符:public abstract
public interface InterDemo1 {
// 这是一个接口
int i = 10; // 成员变量默认修饰符public static final
public static final int b = 20;
public abstract void test();
// 没有构造方法
// public InterDemo1(); 报错
void test2(); // 成员方法默认修饰符public abstract
}
1.4 JDK8版中接口成员的特点
1.4.1 JDK8版本后,允许在接口内定义默认方法
- 默认方法:使用关键字default修饰的非抽象方法,可以拥有方法体
- 作用:解决接口升级问题
接口中默认方法的定义格式:
- 格式:public default 返回值类型 方法名(参数列表){}
- 范例:public default void show(){}
接口中默认方法的注意事项:
- 默认方法不是抽象方法,所以不强制被重写,即可被实现类直接继承使用
- 但是可以被重写,重写的时候必须去掉default关键字
- public可以省略,default不能省略,否则系统默认其为抽象方法,不能拥有方法体
public interface InterDemo2 {
// JDK8版本后,允许在接口内定义默认方法
// 格式:public default 返回值类型 方法名(参数列表){}
public default void test() {
System.out.println("这是一个默认方法,必须要加default修饰");
}
// public void test2(){} 若略去default则编译报错,但public可以省略
// Interface abstract methods cannot have body,系统默认其为抽象方法
}
// 实现类可以不重写默认函数,当然也可以重写
public class InterDemo2Impl implements InterDemo2{
public void impl2(){
test();
}
}
public class Test {
public static void main(String[] args) {
InterDemo2Impl impl2 = new InterDemo2Impl();
impl2.impl2(); // 打印:这是一个默认方法,必须要加default修饰
}
}
1.4.2 JDK8版本后,接口中允许定义static静态方法
接口中静态方法的格式:
- 格式:public static 返回值类型 方法名(参数列表){}
- 范例:public static void show(){}
接口中静态方法的注意事项:
- 静态方法中只能通过接口名调用,不能通过实现类名或者对象名调用
- public可以省略,static不能省略,否则系统默认其为抽象方法,不能拥有方法体
public interface InterDemo3 {
// JDK8版本后,接口中允许定义static静态方法
// 格式:public static 返回值类型 方法名(参数列表){}
public static void test() {
System.out.println("这是一个静态方法,必须要去掉default,且只能通过接口名调用或被接口中其他静态函数使用");
}
// public void test2(){} 报错,public可以省略,static不能省略
// Interface abstract methods cannot have body,系统默认其为抽象方法
}
public class InterDemo3Impl implements InterDemo3{
public void impl3(){
// 实现类中只能通过接口名.的方式调用
InterDemo3.test();
}
}
1.5 JDK9版本中接口成员的特点
JDK9版本后,支持在接口中定义私有方法
定义方式与在普通类中定义私有函数相同:
- 普通私有函数: private 返回值类型 方法名(参数列表){}
- 范例1: private void show(){}
- 静态私有函数: private static 返回值类型 方法名(参数列表){}
- 范例2: private static void method(){}
public interface InterDemo4 {
// JDK9版本后,支持在接口中定义私有方法
// 普通私有方法格式:private 返回值类型 方法名(参数列表){}
private void test() {
System.out.println("这是一个私有方法,必须要去掉default,且只能在接口内部由默认非静态方法调用");
}
// 静态私有方法格式:private static 返回值类型 方法名(参数列表){}`
private static void test2() {
System.out.println("这是一个静态私有方法,必须要去掉default,且只能在接口内部由静态方法调用");
}
// 使用默认非静态方法调用
public default void func1() {
test();
}
// 使用静态方法调用
public static void func2() {
test2();
}
}
1.6 接口的使用思路☆
- 如果发现一个类中所有的方法都是抽象方法,那么就可以将该类,改进为一个接口
- 涉及到了接口大面积更新方法,而不想去修改每一个实现类,就可以将更新的方法,定义为带有方法体的默认方法(使用default关键字修饰,且去掉abstract关键字)
- 希望默认方法调用的更加简洁,可以考虑设计为static静态方法(需要去掉default关键字),切记没有默认静态方法的说法
- 默认方法中出现了重复的代码,可以考虑取出一个私有方法(需要去掉default关键字)
- 类和类之间的关系
- 继承关系,只能单继承,不能多继承,但是可以多层继承
- 类和接口之间的关系
- 实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时,实现多个接口class A A extends B implements
- 注:如果extends继承的父类和接口出现了相同的方法声明,但是代码逻辑不一样,则系统会优先调用extends继承的父类
- 接口和接口的关系
- 继承关系,可以单继承,也可以多继承
- 定义:同一个对象,在不同时候表现出来的不同形态
- 举例:猫
- 我们可以说猫是猫:猫 cat = new 猫();
- 我们也可以说猫是动物:动物 animal = new 猫();
- 这里猫在不同时刻表现出来了不同的形态,这就是多态
- 举例:猫
- 多态的三个前提条件
- 必须要有 继承或实现 关系
- 必须要有方法重写
- 必须要有父类引用(引用类型变量),指向子类对象(实例化后的具体对象)
class Animal {
public void eat(){
System.out.println("动物吃饭");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
public class Test01 {
public static void main(String[] args) {
// 当前事物, 是一只猫
Cat c = new Cat();
// 当前事物, 是一只动物
Animal a = new Cat();
a.eat();
}
}
2.2 多态中成员访问特点
-
构造方法:同继承一样,子类默认会通过super()访问父类的空参构造方法
-
成员变量:编译看左边(父类),执行看左边(父类)
- 即调用时先看父类中是否有此变量,运行时也使用的是父类中的变量
-
成员方法:编译看左边(父类),执行看右边(子类)
- 即调用时查看父类中是否有此方法,但运行时调用子类重写后的方法(多态)
public class Test01 {
public static void main(String[] args) {
Father f = new Child();
// 若父类中无变量a或test方法,则编译报错
System.out.println(f.a); // 10
f.test(); // 子类test...
}
}
class Father{
int a = 10;
public void test(){
System.out.println("父类test...");
}
}
class Child extends Father{
int a = 20;
public void test(){
System.out.println("子类test...");
}
}
2.3 多态的好处和弊端为什么成员变量和成员方法的访问不一样呢?
- 因为成员方法有重写,而成员变量没有
- 多态的好处:提高了程序的扩展性
- 具体体现:定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的任意子类对象
- 多态的弊端:不能使用子类的特有功能,因为编译时必须要求父类中存在其方法的声明
-
向上转型
- 父类引用指向子类对象就是向上转型
- 会自动完成转换
-
向下转型(强制类型转换)
- 格式:子类型 对象名 = (子类型)父类引用;
public class Test02 {
public static void main(String[] args) {
// 向上转型
Animal animal = new Dog();
// 向下转型 子类型 对象名 = (子类型)父类引用;
Dog dog = (Dog) animal;
}
}
abstract class Animal {
public abstract void eat();
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("Dog ...eat");
}
}
2.5 多态中转型存在的风险
-
风险
如果被转的引用类型变量,对应的实际类型和目标类型不是同一种类型,那么在转换的时候就会出现ClassCastException
-
解决方案
-
关键字
instanceof
-
使用格式
变量名 instanceof 类型
- 通俗的理解:判断关键字左边的变量,是否是右边的类型,返回boolean类型结果(可增加if语句进行条件判断)
-
public class Test02 {
public static void main(String[] args) {
// 向上转型
Animal animal = new Dog();
// 向下转型 子类型 对象名 = (子类型)父类引用;
if(animal instanceof Dog){
System.out.println("引用变量的实际类型是目标类型");
Dog dog = (Dog) animal;
}else {
System.out.println("引用变量的实际类型不是目标类型");
}
}
}
abstract class Animal {
public abstract void eat();
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("Dog ...eat");
}
}
三、内部类
3.1 内部类的基本使用
-
内部类概念
- 在一个类中定义一个类。举例:在一个类A的内部定义一个类B,类B就被称为内部类
-
内部类定义格式
class 外部类名{ 修饰符 class 内部类名{ } } -
内部类的访问特点 (类似于成员方法)
- 内部类可以直接访问外部类的成员,包括私有
- 外部类要访问内部类的成员,必须创建对象
-
成员内部类的定义位置
- 在类中方法,跟成员变量是一个位置
-
外界创建成员内部类格式
- 格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象;
- 举例:Outer.Inner oi = new Outer().new Inner();(要记住)
public class OuterTest1 {
public static void main(String[] args) {
// 外界创建成员内部类
// 格式Outer.Inner oi = new Outer().new Inner();
Outer.Inner oi = new Outer().new Inner();
oi.test(); // Inner....
}
}
class Outer{
class Inner{
public void test(){
System.out.println("Inner....");
}
}
}
- 私有成员内部类
- 将一个类,设计为内部类的目的,大多数都是不想让外界去访问,所以内部类的定义应该私有化,私有化之后,再提供一个可以让外界调用的方法
- 仅由方法内部创建内部类对象并调用。
public class OuterTest2 {
public static void main(String[] args) {
new Outer().useInner();
}
}
class Outer {
private class Inner {
public void test() {
System.out.println("Inner....");
}
}
public void useInner(){
// 私有内部类仅由方法内部创建内部类对象并调用
Inner inner = new Inner();
inner.test();
}
}
- 静态成员内部类(静态成员随外部类的加载而加载)
- 静态成员内部类访问格式:外部类名.内部类名 对象名 = new 外部类名.内部类名();
- 静态成员内部类中的静态方法:外部类名.内部类名.方法名();
public class OuterTest3 {
public static void main(String[] args) {
// 静态成员内部类访问格式
// 外部类名.内部类名 对象名 = new 外部类名.内部类名();
Outer.StaticInner inner = new Outer.StaticInner();
inner.testStaticMethod(); // Inner....
// 静态成员内部类中的静态方法:外部类名.内部类名.方法名();
Outer.StaticInner.testStaticMethod(); // Inner....
// 使用内部提供的方法
new Outer().useInner(); // Inner....
}
}
class Outer {
public static class StaticInner {
public static void testStaticMethod() {
System.out.println("Inner....");
}
}
public void useInner() {
StaticInner.testStaticMethod(); // Inner....
}
}
3.3 局部内部类
-
局部内部类定义位置
- 局部内部类是在方法中定义的类
-
局部内部类访问方式
- 局部内部类,外界是无法直接使用,需要在方法内部创建对象并使用
- 该类可以直接访问外部类的成员,也可以访问方法内的局部变量
public class OuterTest4 {
static String s0 = "访问外部内静态成员变量";
public static void main(String[] args) {
String s1 = "访问方法内局部变量";
class MethodInner {
String s2 = "访问类内成员变量";
public void test() {
System.out.println("局部内部类,在方法内定义");
System.out.println(s0); // 在静态方法内定义,只能访问静态成员变量
System.out.println(s1);
System.out.println(s2);
}
}
// 只有在方法内能访问得到
new MethodInner().test();
}
}
3.4 匿名内部类
-
匿名内部类的前提
- 存在一个类或者接口,这里的类可以是具体类也可以是抽象类
-
匿名内部类的格式
- 格式:new 类名 ( ) { 重写方法 } new 接口名 ( ) { 重写方法 }
new Inter(){
@Override
public void method(){}
}
-
匿名内部类的本质
- 本质:是一个继承了该类或者实现了该接口的子类匿名对象
-
匿名内部类的细节
- 匿名内部类可以通过多态的形式接受
Inter i = new Inter(){ // 用接口类或抽象类对象以多态的形式接受
@Override
public void method(){
}
}
- 匿名内部类直接调用方法
interface Inter{
void method();
}
class Test{
public static void main(String[] args){
new Inter(){
@Override
public void method(){
System.out.println("我是匿名内部类");
}
}.method(); // 直接调用方法
}
}
- 函数传参使用接口方法
public class TestInter {
public static void main(String[] args) {
useInter(new Inter() {
@Override
public void test() {
System.out.println("使用匿名内部类向方法传参");
}
});
}
public static void useInter(Inter inter) {
inter.test();
}
}
interface Inter {
public void test();
}
四、Lambda表达式
4.1 概述
-
格式:(形式参数) -> {代码块}
- 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
- ->:由英文中画线和大于符号组成,固定写法。代表指向动作
- 代码块:是我们具体要做的事情,也就是以前我们写的方法体内容
-
组成Lambda表达式的三要素:
- 形式参数,箭头,代码块
public class TestInterLambda {
public static void main(String[] args) {
// lambda表达式帮我们简化了创建对象、实现接口等繁琐的语法结构,而更关注于重写的方法逻辑本身
// 这种不关心谁来做(哪个对象去做),而只关心怎么做的思想叫函数式编程思想
useInter(()->{
System.out.println("使用lambda表达式简化匿名内部类");
});
}
public static void useInter(Inter inter) {
inter.test();
}
}
interface Inter {
public void test();
}
4.2 Lambda表达式练习
- 有参数无返回值
public class TestInter {
public static void main(String[] args) {
// 匿名内部类方式实现
useInter(new Inter() {
@Override
public void test(String message) {
System.out.println("匿名内部类" + message);
}
});
// lambda表达式方式实现
useInter((String message) -> {
System.out.println("带形参的lambda表达式" + message);
});
}
public static void useInter(Inter inter) {
inter.test("message");
}
}
interface Inter {
public void test(String message);
}
- 有返回值无参数
public class TestInter02 {
public static void main(String[] args) {
// 匿名内部类方式实现
useInterRandNum(new RandNum() {
@Override
public int returnRandom() {
Random random = new Random();
int num = random.nextInt(10) + 1;
return num;
}
});
// lambda表达式方式实现;必须要有return,否则报错
useInterRandNum(() -> {
Random random = new Random();
int num = random.nextInt(10) + 1;
return num;
});
}
public static void useInterRandNum(RandNum inter) {
int num = inter.returnRandom();
System.out.println("随机数为" + num);
}
}
interface RandNum {
public int returnRandom();
}
- 有参数有返回值
public class TestInter03 {
public static void main(String[] args) {
// 匿名内部类方式实现
useCalculator(new Calculator() {
@Override
public int calculate(int a, int b) {
return a + b;
}
});
// lambda表达式方式实现
useCalculator((int a, int b) -> {
return a + b;
});
}
public static void useCalculator(Calculator calculator) {
int sum = calculator.calculate(10, 20);
System.out.println("求和结果为" + sum);
}
}
interface Calculator {
public int calculate(int a, int b);
}
4.3 Lambda表达式的省略模式
- 省略的规则
- 参数类型可以省略。但是有多个参数的情况下,不能只省略一个,要省全省
- 如果参数有且仅有一个,那么小括号可以省略
- 如果代码块的语句只有一条,可以省略大括号和分号,和return关键字
- 例如改写上例
public class TestInter03 {
public static void main(String[] args) {
// lambda表达式方式实现
// 省略了形参的参数类型,不能只省略一个,要省全省
// 省略了大括号、return和分号(因为代码块语句只有一句)
useCalculator((a, b) -> a + b);
}
public static void useCalculator(Calculator calculator) {
int sum = calculator.calculate(10, 20);
System.out.println("求和结果为" + sum);
}
}
interface Calculator {
public int calculate(int a, int b);
}
4.4 Lambda表达式的使用前提
- 使用Lambda必须要有接口,不能操作普通类或抽象类
- 并且要求接口中有且仅有一个 抽象方法,多个抽象方法只能选择使用匿名内部类或实现类
- 总结:lambda只适用于只有一个抽象方法的接口
- 所需类型不同
- 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
- Lambda表达式:只能用于接口
- 使用限制不同
- 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
- 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
- 实现原理不同
- 匿名内部类:编译之后,产生一个单独的.class字节码文件,会保存在磁盘中!
- Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成代码块,不会保存在磁盘中



