Java核心技术卷Ⅰ-第六章接口、lambda表达式与内部类

文章详细介绍了Java中的接口,包括其特性、默认方法以及与类的交互。接着讨论了lambda表达式的语法和使用场景,强调了其作为可传递代码块的特性。此外,还阐述了内部类的概念,包括非静态和静态内部类,以及局部和匿名内部类的用途和规则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

重点

1.接口

2.lambda表达式

3.内部类


1.接口


  • 接口用于描述类应该做什么,而不是指定它们具体怎么做

  • 接口的属性:

    • 接口不是类(即不能使用new实例化一个接口)
    • 可使用instanceof检查一个对象是否实现某接口
    • 可使用extends扩展接口
    • 接口中不能包含实例字段,但可包含常量
    • 接口中的方法自动为public,接口中的字段为public static final
    • 每个类只能有一个超类,但可实现多个接口
  • 接口可以提供多重继承的大多数好处,还能避免多重继承的复杂性和低效性

  • Java8中允许在接口中添加静态方法;Java9中允许方法为private方法,并且该方法可以是静态方法也可以是实例方法:

interface A{
    public static void test(){
        System.out.println("ooo");
    }    
}
interface B{
    private int test1(){
        return 1;
    }
    private static int test2(){
        return 2;
    }
}
  • 可以为任何接口方法提供默认实现,但必须用default修饰,但是会带来冲突:

    • 如果一个类实现了两个接口,并且这两个接口都提供了相同方法的默认实现,则编译器会报错:
    class C implements A, B {}  // 编译器报错
    interface A{
        default void test(){
            System.out.println("interfaceA");
        }
    }
    interface B{
        default void test(){
            System.out.println("interfaceB");
        }
    }
    
    • 如果一个类继承了超类并且实现了接口,该超类和接口都有相同的方法,接口也对该方法提供了默认实现,则最终会采取类优先(即只考虑超类方法,接口中的默认方法被忽略,这也是为Java7提供兼容性):
    class C extends A implements B {}  // 创建C类的实例c,如果执行c.test()打印“super class”
    class A{
        public void test(){
            System.out.println("super class");
        }
    }
    interface B{
        default void test(){
            System.out.println("interface");
        }
    }
    
  • Objectclone方法默认是浅拷贝(即对象内属性引用的对象只会拷贝引用地址,而不会将引用的对象重新分配内存)。浅拷贝存在的问题就是可能改变原对象中的属性(因为指向了同一个对象),但是也是需要区分情况:

    • 原对象和浅克隆对象共享的子对象为不可变对象(如String类型的对象),这种共享是安全的,比如Employee类中存在String类型的name属性,此时原对象为emp,克隆对象为emp2emp2修改了name属性的值后会重新创建一个String类型对象,意味着原对象中该属性的值不会被改变

    • 原对象和浅克隆对象共享的子对象为可变对象(如Date类型对象),这种共享就不安全

  • 所有数组类型会存在公共的clone方法,而不是定义protected,调用后会创建原数组所有元素的副本:

int[] a = {1, 2, 3};
int[] b = a.clone();
b[1] = 22;
System.out.println(a[1]);  // 还是2

2.lambda表达式

该表达式是一个可传递的代码块,可以在以后执行一次或多次


  • lambda表达式的语法规则:

    • 即使表达式没有参数,也需要提供空括号:
    ()->{
        for(int i = 0; i < 10; i++){
        	System.out.println(i);
        }
    }}
    
    • 如果可推导出表达式的参数类型,则可忽略其类型:
    // 不需要写为String first和String second
    Comparator<String> cmp = (first, second) -> first.length() - second.length()
    
    • 如果方法中只有一个参数,而且参数类型可以推导出来,可以省略小括号:
    ActionListener l = event -> xxx;
    
    • 表达式的返回类型会由上下文推导出来,无需指定:
    // 不使用表达式
    class LenComparator implements Comparator<String>{
        public int compare(String first, String second){ 
            return first.length() - second.length();
    	}
    }
    Arrays.sort(xxx, new LenComparator());
    // 使用后
    Arrays.sort(xxx, (first, second) -> first.length() - second.length());  
    
    • 可以使用var指示推导的类型:
    (@NonNull var first, @NonNull var second)-> first.length() - second.length(); 
    
    • 表达式只在某些分支返回一个值,其他分支没有返回值是不合法的:
    (int x)->{
        if(x >= 0){
            return 1;
    	}
        // 还应该返回值
    }
    
  • 函数式接口是只有一个抽象方法的接口,如果需要这种接口的对象时,可以提供一个lambda表达式(即lambda表达式可以转换为函数式接口):

// 底层会接收实现了Comparator<String>的某个类的对象,在该对象上调用compare方法后会执行表达式中的内容
Array.sort(arr, (first, second)->first.length() - second.length());
  • lambda表达式中,对于变量的限制有以下几点:

    • 只能引用值不会改变的变量:
    public static void countDown(int start, int delay){
        ActionListener listener = event -> {
            start--;  // 报错,因为在表达式中修改变量会引起并发安全的问题
        };
        new Timer(delay, listener).start();
    }
    
    • 不能引用在外部会改变的变量:
    public static void repeat(){
        for(int i = 1; i <= 10; i++){
            System.out.println(i);  // 报错,i在不断改变,因此不能捕获i
        }
        new Timer(1000, listener).start();
    }
    
    • 表达式中不能声明和方法局部变量同名的参数(毕竟还是在同一个方法中的,需要符合基本要求):
    public void test(){
        String first = "psj";
        // 报错,已经有first变量了
        Comparator<String> comp = (first, second)->first.length() - second.length();  
    }
    
    • 表达式中使用的this是创建表达式的方法的this
    public class Application{
        public void init(){
            // 调用的是Application的toString方法
            ActionListener l = event -> System.out.println(this.toString());  
        }
    }
    
  • lambda表达式的重点在于延迟执行(毕竟如果要立即执行代码就无需包装在表达式中的,直接写就是):

3.内部类


  • 使用内部类的原因:

    • 内部类可以对同一个包中的其他类隐藏
    • 内部类方法可以访问定义这些方法的作用域中的数据,包括私有数据
      • 非静态内部类的对象存在隐式引用,指向实例化该对象的外部类对象(意味着可以访问外部对象的全部状态);静态内部类没有该引用
  • 内部类的特殊语法规则:

    • 内部类中声明的所有静态字段都必须是final
    • 内部类不能有static方法
    • 不同于外部类,内部类可以声明为private(可以将内部类理解为外部类的一个属性,属性当然可以私有)
    public class Test {
        private class inner{  // 可以为private
            static int a = 1;  // 报错,需要加上final并赋值
            static void test(){}  // 报错,不能使用static
        }
    }
    
  • 局部内部类:

    • 声明局部内部类不能使用publicprivate
    • 局部内部类的作用域限定在声明该类的块中
    • 局部内部类对外界隐藏(下面代码只有ATest方法知道innerA类的存在)
    • 局部内部类可以访问局部变量,也可以访问外部类的字段
    // 局部内部类(innerA)适用于该类只出现在外部类(A)某个方法(ATest)中使用
    public class A{
        String name = "psj";
        public void ATest(int num){
            class innerA{  // 这里的innerA不需要继承某个类或接口,不同于匿名内部类
                public void innerATest(){
                    System.out.println(num);  // 访问局部变量
                    System.out.println(name);  // 访问外部类的字段
                }
            }
            innerA a = new innerA();
            a.innerATest();
        }
    
        public static void main(String[] args) {
            A a = new A();
            a.ATest(10);  // 输出10和psj
        }
    }
    
  • 匿名内部类:下面代码可以理解为创建一个类的对象,这个类继承了B类,并且定义了需要实现的方法innerATest

    • 匿名内部类不能有构造器(构造器要求和类名一致,它连类名都没有)
    • 匿名内部类可以提供一个对象初始化块
    // 想要创建某个类(B)的一个对象,可以不需要为类指定名字
    public class A{
        String name = "psj";
        public void ATest(int num){
            var b = new B(){  // new xxx()的xxx可以是接口也可以是类
                public void innerATest(){
                    { 
                        // 对象初始化块 
                    }
                    System.out.println(num);
                    System.out.println(name);
                }
            };
            b.innerATest();
        }
    }
    class B{}
    

其他知识点


  • BigDecimal类中的equals方法和compareTo方法不兼容:
BigDecimal x = new BigDecimal("1.0");
BigDecimal y = new BigDecimal("1.00");
System.out.println(x.equals(y));  // false,因为两个数的精度不同
System.out.println(x.compareTo(y));  // 0(表示相等)
  • 回调是一种常见的设计模式,可以指定某个特定事件发生时采取的动作

  • 对于protected方法在子类中可用的意思是指子类代码中可以用该方法,不是在其他包下创建子类的对象后也可以使用(对于private方法也同理,指的是在本类代码中可以使用该方法)

// 包1下
public class A {
    protected void test(){}
}
// 包2下
public class B extends A {
    // 在静态方法中之所以不能直接调用非静态方法,是因为没有this指针,但是如果new一个对象就相当于有一个指针
    public static void main(String[] args) {
        B b = new B();
        b.test();  // 可以使用,因为B是A的子类,B无论在哪个包下,在其类中都可以使用protected方法
    }
}
// 包3下
public class C {
    public static void main(String[] args) {
        B b = new B();
        b.test();  // 报错,因为C不是A的子类,无法在代码中调用test方法
    }
}
public class A {
    private void test(){}

    public static void main(String[] args) {
        A a = new A();
        a.test();  // 可以使用,此时该方法是在本类中的
    }
}
class B{
    public static void main(String[] args) {
        A a = new A();
        a.test();  // 报错
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值