1、接口
接口:只提供形式,不提供任何的具体实现。
接口中只有抽象方法和常量,不可能有非抽象方法,常量必须被赋值。
- 常量默认被public static final修饰
- 方法默认被public static abstractl修饰
- 接口不能被实例化
- 子类必须重写接口中的所有方法,否则只能是抽象类,但是子类继承了该抽象类必须重写接口中的方法。
- 接口可以多继承接口
- 当实现接口中的方法时,返回值类型和形参列表不能变。
2、内部类
内部类是在一个类的内部定义的类
-
当某个类只为一个类提供服务时,可以将这个类定义成内部类
-
可以解决接口或者抽象类不能实例化的问题
内部类分为成员内部类、静态内部类、局部内部类、匿名内部类四种
成员内部类
一个类可以拥有一个类(注意不是对象)作为成员
public class memberTest {
int a;//成员变量
class b {
//成员内部类
}
void fuunc c() {
//方法
}
}
- 内部类可以直接调用外部类的属性和方法(包括私有的)
- 外部类要想调用内部类的属性和方法,需要通过实例化内部类的对象去调用
- 要想在别的类中创建一个内部类,需要先实例化一个外部类的实例再实例化内部类(成员内部类是实例相关的),创建内部类对象格式:
外部类名 outer = new 外部类名(); 内部类名 inner = outer.new 内部类名();
外部类名.内部类名 in = new 外部类名().new 内部类名(); //两者等价
- 当外部类和内部类的属性或方法重名时,在内部类可以通过
外部类名.this.属性名(方法名)
访问外部类的属性和方法 - 非静态内部类不能有静态变量,但可以访问外部类静态变量
静态内部类
静态内部类就是使用static关键字修饰的成员内部类。静态内部类只能访问外部类的静态成员。
- 要想在别的类中创建一个静态内部类,不需要先实例化一个外部类的实例再实例化内部类(静态成员内部类是类相关的),创建静态内部类对象格式:
- 静态内部类不可以直接调用外部类的非静态属性和方法
- 静态内部类可以有非静态成员变量或成员方法
外部类名.静态内部类名 staticInner = new 外部类名.静态内部类名();
局部内部类
Java中的方法可以返回一个类或接口或者其实例化对象,我们可以在方法的内部创建一个类或接口然后将其返回,这种在方法内声明的类就称作局部内部类,又称“方法内部类”。
下面就是一个局部内部类的测试,我们使用局部内部类的方法来实现一个简易的计算器程序,innerClassTest()
方法用以根据输入的运算符来返回一个内部类。其中使用了反射的知识,见这里。
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
class InnerTest {
public static Class<?> innerClassTest(char a) {
switch (a) {
case '+' -> {
class inner extends ClassA {
@Override
public int calc(int i, int j) {
return i + j;
}
public inner() {
}
}
return inner.class;
}
case '-' -> {
class inner extends ClassA {
@Override
public int calc(int i, int j) {
return i - j;
}
public inner() {
}
}
return inner.class;
}
case '*' -> {
class inner extends ClassA {
@Override
public int calc(int i, int j) {
return i * j;
}
public inner() {
}
}
return inner.class;
}
case '/' -> {
class inner extends ClassA {
@Override
public int calc(int i, int j) {
return i / j;
}
public inner() {
}
}
return inner.class;
}
default -> System.out.println("ERROR");
}
return ClassA.class;
}
public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
Class<?> clazz = innerClassTest('*');
Constructor<?> constructor = clazz.getConstructor();
ClassA a = new ClassA();
ClassA b = (ClassA) constructor.newInstance();
System.out.println(a.calc(3, 4)); //输出-666
System.out.println(b.calc(3, 4)); //输出12
}
}
class ClassA{
public ClassA() {
}
public int calc(int i, int j) {
return -666;
}
}
匿名内部类
在java中调用某个方法时,如果该方法的参数是一个接口类型,除了可以传入一个接口实现类作为参数,还可以使用匿名内部类实现接口来作为该方法的参数。匿名内部类其实就是没有名称的内部类,在调用含有接口类型参数的方法时,通常为了简化代码,我们不会创建一个接口的实现类作为方法参数传入,而是直接通过匿名内部类的形式传入一个接口类型参数,在匿名内部类中完成方法的实现。
对于上述计算器的例子,由于我们不需要知道局部内部类的具体名称,所以我们可以使用匿名内部类来实现:
class InnerTest {
public static ClassA innerClassTest(char a) {
ClassA inner = null;
switch (a) {
case '+' -> inner = (int i, int j) -> i + j;
case '-' -> inner = (int i, int j) -> i - j;
case '*' -> inner = (int i, int j) -> i * j;
case '/' -> inner = (int i, int j) -> i / j;
}
return inner;
}
public static void main(String[] args) {
ClassA a = innerClassTest('*');
System.out.println(a.calc(3, 4)); //输出12
}
}
@FunctionalInterface
interface ClassA {
int calc(int i, int j);
}
除此之外,当我们某一个方法需要传入一个类的时候,我们可以使用匿名内部类的方法来实现:
public class Test {
public static void innerFuncClassTest(ClassA a){
a.out();
}
public static void main(String[] args) {
innerFuncClassTest(new ClassA(){
@Override
public void out(){System.out.println("2");}
});
}
}
class ClassA{
public void out(){}
}
这样的效果也可以使用Lambda表达式来实现,通过lambda表达式可以建议的实现接口。
注意,可以lambda表达式的接口只能是函数式接口,也就是只有一个方法的接口(可以有成员变量,只能由JVM自动将其解析为静态变量),可以用@FunctionalInterface
来注释函数式接口,这样编译器会检测是否满足上述要求。
@FunctionalInterface//被该字段注释的接口只能有一个方法
public interface Face {
int A = 0;//这里有一个成员变量
//注意interface里的成员变量自动且必须由public static final修饰
int test(int i);//这里有一个方法
//注意interface里的非静态方法自动且必须由public abstract修饰
public static void main(String[] args) {
Face invoke = (int i)->-i;
//这里使用lambda表达式来实现接口
//lambda表达式相当于重写了接口中的test方法
System.out.println(invoke.test(1) + " " + invoke.A);//输出为“-1 0”
}
}