Java进阶03 面向对象高级(续)

Java进阶03面向对象高级(续)

一、接口新特性

1、JDK8的新特性

1.1 允许在接口中定义非抽象方法

允许在接口中定义非抽象方法,但需要使用关键字default修饰,这些方法就是默认方法

  • 作用:解决接口升级的问题

  • 格式

    public default 返回值类型 方法名(参数列表){}
    ​
    public default void show(){}
  • 注意事项

    ①默认方法不是抽象方法,所以不强制被重写 (但是可以被重写,重写的时候去掉default关键字

    ②public可以省略,default不能省略

    ③如果实现了多个接口,多个接口中存在相同的方法声明,子类就必须对该方法进行重写

1.2 允许在接口中定义静态方法

接口中允许定义static静态方法

  • 格式

    public static 返回值类型 方法名(参数列表){}
    ​
    public static void show(){}
  • 注意事项

    ①静态方法只能通过接口名调用,不能通过实现类名或者对象名调用

    ②public可以省略,static不能省略

2、JDK9的新特性

接口中允许定义private私有方法

  • 格式

    //格式1:private 返回值类型 方法名(参数列表){}
    public void show(){}
    ​
    //格式2:private static 返回值类型 方法名(参数列表){}
    private static void method(){}
  • 应用场景

    interface Inter { 
        public default void start(){
            System.out.println("start方法执行...");
            System.out.println("日志记录");    }    
        public default void end(){
            System.out.println("end方法执行...");
            System.out.println("日志记录");    
        }
    }

    start方法和end方法中都有日志记录,为了提高代码复用性,减少冗余,可以将日志记录抽取为一个私有方法,让需要记录的方法调用即可

    public default void log(){
        System.out.println("日志记录");
    }

二、代码块

在Java类下,使用 { } 括起来的代码被称为代码块

1、局部代码块(了解即可)

 public static void main(String[] args) {
    
     {
            System.out.println("我是局部代码块");
     }
     
 }
  • 位置:方法中

  • 作用:限定变量的生命周期,提早释放内存,提高内存利用率

2、构造代码块(了解即可)

class Student {
​
    {
        System.out.printIn("我是构造代码块")
    }
​
}
  • 位置:类当中方法外

  • 特点:每次创建对象的时候执行,而且优先于构造方法执行

  • 作用:如果发现多个构造方法中,存在相同的代码,就可以【考虑】使用构造代码块优化提高代码复用性

3、静态代码块

class Student {
​
    static {
        System.out.println("我是静态代码块");
    }
    
}
  • 位置:类中方法外,需要被static修饰

  • 特点:随着类的加载而执行,只执行一次

  • 作用:常用于项目的一些初始化操作

4、相关面试题(重点)

public class CodeTest1 {
    
    static {
        System.out.println("CodeTest1的静态代码块执行了");
    }
    
    {
        System.out.println("测试类构造代码块");
    }
    
    public static void main(String[] args) {
        new Zi();
    }
}
​
class Fu {
    
    static {
        System.out.println("Fu...static代码块执行了");
    }
    
    {
        System.out.println("Fu类构造代码块");
    }
    
    public Fu() {
        System.out.println("Fu类空参数构造方法");
    }
    
    public Fu(int num) {
        System.out.println("Fu类带参数构造方法");
    }
}
​
class Zi extends Fu {
    
    static {
        System.out.println("Zi...static代码块执行了");
    }
​
    {
        System.out.println("Zi类构造代码块");
    }
​
    public Zi() {
        super();      //这句代码不写系统也会自己加,分析运行过程时要注意
        System.out.println("Zi类空参数构造方法");
    }
​
    public Zi(int num) {
        System.out.println("Zi类带参数构造方法");
    }
}
  • 执行结果

    CodeTest1的静态代码块执行了
    Fu...static代码块执行了
    Zi...static代码块执行了
    Fu类构造代码块
    Fu类空参数构造方法
    Zi类构造代码块
    Zi类空参数构造方法
  • 执行过程分析

程序运行,加载主方法所在类的字节码文件进方法区,即执行CodeTest1类的静态代码块,打印CodeTest1的静态代码块执行了

进main方法,new Zi(),用到了Zi类即加载Zi类的字节码文件,但Zi类继承了Fu类,所以先加载Fu类字节码文件进方法区,Fu类的静态代码块随即执行,打印Fu...static代码块执行了;

然后再加载Zi类字节码文件进方法区,Zi类的静态代码块也随即执行,打印Zi...static代码块执行了;

紧接着,有new进栈,调用Zi类空参构造new对象,由于Zi类空参构造的第一句系统默认会加一句super();,因此在创建对象的时候会先调用Fu类的空参构造,而Fu类中含有构造代码块,会优先于构造方法执行,所以先打印Fu类构造代码块,再打印Fu类空参数构造方法;

之后才应该执行Zi类空参构造,而Zi类也含有构造代码块,会优先其构造方法执行,所以先打印Zi类构造代码块,再打印Zi类空参数构造方法。

三、内部类

内部类就是定义在一个类里面的类

1、创建格式

外部类名.内部类名 对象名 = new 外部类对象().new 内部类对象();
​
Outer.Inner in = new Outer().new Inner();

2、好处(了解)

  • 内部类可以直接使用外部类成员,包括私有

  • 可以更加合理的运用封装思想来设计对象

3、访问特点

3.1 内访外

内部类访问外部类成员可以直接访问,包括私有

3.2 外访内

需要创建对象访问

3.3 小Demo
class Outer{
    int num =10;
    class Inner{
        int num = 20;
        public void show(){
            int num = 30;
            System.out.printIn(num);                 //30
            System.out.printIn(this.num);            //20
            System.out.printIn(Outer.this.num);      //10
        }
    }
}

注意:在成员内部类中访问所在外部类对象的格式为:外部类.this

4、内部类的分类

4.1 成员内部类

成员内部类是最常见的一种内部类,定义一个成员内部类就和定义成员变量类似,直接在一个类的内部再定义一个类,和成员变量可以说是同级关系。

4.2 静态内部类

有static修饰的成员内部类

class Outer{
    static class Inner{
    }
}
  • 创建对象格式

    外部类名.内部类名 对象名 = new 外部类名.内部类对象();
    ​
    Outer.Inner in = new Outer.Inner();
4.3 局部内部类(鸡肋语法,了解即可)

局部内部类放在方法、代码块、构造器等执行体中

public class InnerClassDemo3 {
    public static void main(String[] args) {
        A a = new A();      //为了调用show方法创建A类对象
        a.show();       //为了调用show中的print方法调show方法
    }
}
​
​
class A {
    public void show() {
        // 局部内部类
        class B {          
            public void print() {
                System.out.println("print...");
            }
        }
​
        int a = 10;
​
        B b = new B();
        b.print();     //要想调用局部内部类的print方法,必须要调用它所在的show方法,而show方法又在A类中,想要调用必须创建A类对象,通过A类对象去调用show方法,然后在show方法中再创建B类对象去调用print,比较繁琐
    }
}

注意:此处的class B前不能加权限修饰符,因为其所处的方法都已经是public了,再给他加权限修饰符是不被允许的。切记:局部不能加权限修饰符,成员可以加

4.4 匿名内部类

匿名内部类本质上是一个特殊的局部内部类(定义在方法内部)

  • 前提:需要存在一个接口或类

  • 格式

    new 类名/接口(){
        
    }
    ​
    //这段代码不仅实现了接口,还重写了方法,同时创建了对象
    new MyInter(){
        @Override
        public void show(){
            System.out.println("匿名内部类(实现类)重写后的show方法...")
        }
    };
    ​
    interface MyInter{
        void show();
    }

    注意:new 类名(){} 表示继承这个类;new 接口名(){}表示实现这个接口

  • 匿名内部类可以作为方法的实际参数进行传输

    public class Test1 {
        /*
             思路: 为了调用useInter方法
                   1. 编写实现类
                   2. 重写方法
                   3. 创建实现类对象, 作为参数传入
        */
        public static void main(String[] args) {
            useInner(new InnerImpl());
           
        }
            
        //模拟:方法的形参是接口类型
        //问题:这种情况,该传入的实参是什么?
        //回答:实现类对象
        public static void useInner(Inner i) {
            i.show();
        }
    }
    ​
    interface Inner{
        void show();
    }
    ​
    class InnerImpl implements Inner{
        @Override
        public void show() {
            System.out.println("实现类重写show方法");
        }
    }
    ​
    
  • public class Test1 {
        /*
           匿名内部类: 可以将以上三个步骤, 折成一步完成.即 这段代码既实现了接口,又重写了方法,还创建了对象做为实参传入调用方法
        */
        public static void main(String[] args) {
            useInner(new Inner() {      
                @Override
                public void show() {
                    System.out.println("匿名内部类重写后的show方法");
                }
            });
        }
    ​
        public static void useInner(Inner i){
            i.show();
        }
    }
    ​
    interface Inner{
        void show();
    }

    注意事项:掌握了匿名内部类,也不能完全舍弃传统的实现类写法

  • 使用场景:当接口中的抽象方法个数只有1-2个的时候,使用匿名内部类最合适

  • 特点:可以让代码更加简洁,定义一个类的同时对其进行实例化

四、Lambda表达式

1、概述

Lambda表达式是JDK8开始后的一种新语法格式

2、作用

简化匿名内部类的代码

3、简化格式

(匿名内部类被重写方法的形参列表)->{被重写方法的方法体}
注:->只是语法格式,无实际含义

注意:Lambda表达式只能简化函数式接口,函数式接口指有且仅有一个抽象方法的接口,通常会在接口上加一个校验注解@FunctionalInterface,也是标记该接口必须满足函数式接口。

  • 这里有一个需要主义的细节:如果接口中声明的抽象方法是Object类中的方法结构,不算数

    //此处该注解不报错,属于函数式编程,因为toString和equals方法都是Object类中的,即使声明也不算数
    @FunctionalInterface   
    interface MyInter{
        void show();
        public abstract String toString();
        public abstract boolean equals();
    }

4、Lambda简化规则

  • 参数类型可以省略不写

  • 如果只有一个参数,参数类型可以省略,同时()也可以省略

  • 如果Lambda表达式的方法体代码只有一行代码,可以省略大括号不写,同时要省略分号;此时,如果运行代码是return语句,必须省略return不写,同时也必须省略”;“不写

5、用Lambda表达式简化上述匿名内部类的代码

public class Test1 {
    public static void main(String[] args) {
        useInner(()->{System.out.println("Lambda表达式重写后的show方法");});    //简化后语句
    }
​
    public static void useInner(Inner i){
        i.show();
    }
}
​
@FunctionalInterface
interface Inner{
    void show();
}

6、Lambda表达式和匿名内部类的区别

区别使用限制不同实现原理不同
匿名内部类可以操作类、接口编译之后,产生一个单独的.class字节码文件
Lambda表达式只能操作函数式接口编译之后,没有一个单独的.class字节码文件

7、Q:接口有没有继承Object类?

A:表面上没有,实际上有。如果接口没有继承Object类,多态创建对象调用Object类的方法会出现编译错误。为了避免这个问题,底层是默认让该接口继承了Object类的所有方法

public class LambdaDemo2{
    public static void main(String[] args){
        //多态创建对象
        MyInter m = new MyInterImple();   //编译看左,执行看右。如果没有继承Object,编译看左发现MyInter中没有toString会直接报错,无法编译
        m.toString();        
    }
}
​
interface MyInter{
    
}
​
class MyInterImpl extends Object implements MyInter{
    
}

五、常用API

API(Application Programming interface)应用程序编程接口,简单来说,就是Java已经写好的一些类和方法,我们直接拿过来用就可以了

1、Object类

所有的类都直接或者间接的继承了 Object 类 (祖宗类)。Object类的方法是一切子类都可以直接使用的

1.1 Object类的toString方法
  • Object类的toString方法

    默认返回当前对象在堆内存中的地址信息类的全类名@十六进制哈希值。

     public String toString(){
         return getClass().getName()+"@"+Integer.toHexString(hashCode());     
     }
    • getClass().getName():获取类的全类名

    • "@":分隔符

    • Integer.toHexString(hashCode()):十六进制哈希值,常被人称作地址值

    然而在开发中,输出对象地址是毫无意义的,我们更多得是希望看到对象的内容,因此需要重写toString方法

  • 自己重写toString

    @Override
    public String toString(){
        return name+","+age;
    }
  • IDEA生成toString

    @Override
    public String toString() {
        return "Student{" +
               "name='" + name + '\'' +
                ", age=" + age +
                   '}';
    }

结论:如果今后打印对象名,看到的不是地址信值,说明这个类重写过toString方法。如果在该类源码中没看到toString方法,继续向上找,其父类肯定重写了toString方法

1.2 Object类的equals方法
  • Object类的equals方法

    public boolean equals(Object obj){
        return (this == obj)
    }

    默认是比较当前对象与另一个对象的地址是否相同,相同返回true,不同返回false。该方法存在的意义是为了子类能够根据自己的需求重写该方法,一般是让对象之间比较内容

  • 自己重写方法

    @Override
    //this:stu1对象
    //obj:stu2对象
    public boolean equals(Object obj){
        if(obj instanceof Student){
            Student stu = (Student)obj;
            return this.age == stu.age &&this.name.equals(stu.name);
        }else{
            return false;
        }
    }
  • IDEA自动生成的重写方法

    @Override
    //this:stu1
    //o:stu2
    public boolean equals(Object o) {
        //1、比较两个对象的地址,地址相同,直接返回true
        if (this == o) return true;
        /*
         2.1、看传入的对象是否为null,是的话,直接返回false。
             因为代码能执行到这里,说明stu1一定不是null(有数据),如果stu2是null(无数据),则直接false,不用再判断
         2.2、比较两个对象产生的字节码,如果字节码不相同,代表类型不同,直接返回false
        */
        if (o == null || getClass() != o.getClass()) return false;
        //代码执行到这里,说明类型相同且非空,可以放心向下转型
        Student student = (Student) o;
        //比较内容
        return age == student.age && Objects.equals(name, student.name);
    }
1.3 Objects

Objects类与Object还是继承关系,objects类是从JDK 1.7开始之后才有的

  • Objects常见方法

    • equals方法

      比较两个对象,底层会先进行非空判断,从而可以避免空指针异常,再进行equals比较。注意:该方法底层依赖于我们重写的equals方法

      //底层源码
      public static boolean equals(Object a,Object b){
          return (a == b)||(a != null && a.equals(b));
      }
      ​
      //上述代码的另一种形式
      public static boolean equals(Object a,Object b){
          if(a == b){
              return true;
          }
          if(a == null){
              return false;
          }else{
              return a.equals(b);
          }
      }

      源码避免空指针异常的分析:若a是null值,则a!=null判断结果为false,由于双与(&&)具有短路效果,左边判断为false后,右边不执行,a就没有机会调用equals方法,避免了空指针调用;若a不是null值,则a!=null判断结果为true,双与(&&)左边为true,右边继续执行,a会调用我们重写的equals方法去比较内容

    • isNull方法

      判断变量是否为null

      //底层源码
      public static boolean isNull(Object obj){
          return obj == null;
      }

  • 26
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值