java5-面向对象-枚举-接口-抽象类-静态内部类

1,类的定义,局部变量与成员变量的区别

//属性,定义在方法中为局部变量(必须初始化),定义在类中方法外为成员变量(非必须初始化)
//成员变量对应对象中的属性,各属性由对应的对象所有
[修饰符] 属性类型 属性名 = 默认值;

//方法
[修饰符] 方法返回类型 方法名(形参列表){
    执行语句;
}
  • 局部变量与成员变量的区别:
    • 代码中的位置不同:
      • 局部变量:定义在方法/代码块中
      • 成员变量:定义在类中,方法外
    • 作用范围不同:
      • 局部变量:所定义的方法/代码块
      • 成员变量:所定义的类中的方法
    • 是否有默认值:
      • 局部变量:没有(不赋初值会报错)
      • 成员变量:有(不必赋初值)
    • 是否要初始化:
      • 局部变量:一定需要
      • 成员变量:不需要
    • 内存中的位置不同:
      • 局部变量:栈
      • 成员变量:堆
    • 作用时间不同:
      • 局部变量:当前方法/代码块从开始执行到执行完毕
      • 成员变量:当前对象从创建到销毁

2,创建对象

//定义的类为引用数据类型
类 对象名 = new 类()
  • 第一次创建对象的时候,会进行类的加载,而之后再创建时就不加载了
  • 初始化创建对象的时候,对象的属性没有被赋值,有默认的初始化的值
  • 调用:对象名.属性/方法

3,构造器

  • new关键字就是在调用一个方法,这个方法叫做构造方法(构造器);构造器无法被点操作符或在其他非构造器中被调用,需要通过new语句在创建对象时间接调用;构造器可以其他重载的构造器,语法为this(实参列表);并且必须是第一行,后面可以继续有代码;构造器不能自己调用自己(死循环了)。
  • 调用构造器的时候,如果类中没有写构造器,系统会默认分配一个构造器;但是如果写了构造器,系统就不会再自动添加无参数的构造器了。
  • 构造器的格式
    修饰符 构造器名字() {}	//空构造器,空参构造器,没有任何参数的构造器
    
  • 构造器与方法的区别:
    • 构造器定义时没有返回值类型
    • 方法体内部不能有return语句
    • 构造器名字必须和类名一致
  • 构造器的作用(python中的初始化函数):为成员变量赋值(再构造器发挥作用之前,成员变量已经赋初值)
  • 注意:一般不会在空构造器中进行初始化操作,因为这样的话每个对象的属性的值就一样了;实际上,只需要保证空构造器的存在就可以了,里面的东西不用写。(虽然系统会默认分,但是不写可能会报错,特别在一些框架中)
  • 构造器重载
    修饰符 构造器名字() {}	//空构造器
    修饰符 构造器名字(成员变量类型1 成员变量1, 成员变量类型2 成员变量1, ...) {}	//重载
    //这样在new的时候可以传参,也可以不传参
    //一般都会保证空构造器存在,一般不进行属性的赋值操作
    //属性赋值操作写到构造器的重载里
    //构造器重载后,如果空构造器忘写了,系统不会再分配默认的空构造器,再使用空构造器的实例化方法就会报错
    //构造器形参名字和属性名重名的时候,会出现就近原则造成赋值失败,怎么办?在要表示对象的属性前加上this.进行修饰,这里的this代表的就是正在创建的对象(相当于python中的self)
        public Person(String name, int age, double height){
            this.name = name;
            this.age = age;
            this.height = height;
        }
    

4,内存分析

https://www.bilibili.com/video/BV1Ff4y147WP?p=127
https://www.bilibili.com/video/BV1Ff4y147WP?p=128&spm_id_from=pageDriver
https://www.bilibili.com/video/BV1Ff4y147WP?p=129&spm_id_from=pageDriver

5,创建对象的过程,this关键字

  • 创建对象的过程
    • 在第一次遇到一个类时,对这个类进行加载,只加载一次
    • 创建对象,在堆中开辟空间
    • 对对象进行初始化操作,属性赋值都是默认的初始值
    • new关键字调用构造函数,执行构造方法,在构造器中对属性重新进行赋值
  • this指代的就是当前的对象
  • this关键字的用法
    • this可以修饰属性:当属性名与形参/局部变量发生重名时,都会发生就近原则;此时直接使用变量名,指的是离得最近的那个形参/局部变量,此时可以用this修饰指代属性
    • this可以修饰方法
    • this可以修饰构造器:同一类中的构造器(构造器重载)也可以相互调用,但是this修饰构造器必须放在第一行
          public Person(int age){
              this.age = age;
          }
      
          public Person(int age, String name){
              this(age);
              this.name = name;
          }
      
          public Person(int age, String name, double height){
              this(age, name);
              this.height = height;
          }
      
    • 同一类中不发生冲突,this可以省略不写

6,static关键字

  • static可以修饰:属性,方法(不包括构造器),代码块,内部类
  • static修饰属性(静态属性/非静态属性)(类变量/实例变量)
    • 在类加载的时候,会将静态内容加载到方法区的静态域中
    • 被所有该类的对象共享
    • 静态内容先于对象存在
    • 被static修饰的属性,可以通过 类名.属性名 的方式访问
    • 应用场景:对象中所有属性是相同且共享的,可以用static修饰属性
  • static修饰方法:
    • static和public都是修饰符,先写哪个都行
    • 静态方法只能使用参数和静态变量;静态方法中不可访问非静态属性和方法,不可使用this关键字(因为静态内容先于对象存在)
    • 静态方法可以用 对象.方法名()类名.方法名() 的方法调用
    • 静态方法在同一个类中可以直接调用
  • 例子
    public class Test {
        //属性
        int id;
        static int sid;
    
        //main方法
        public static void main(String[] args) {
            Test t1 = new Test();
            t1.id = 10;
            t1.sid = 10;
    
            Test t2 = new Test();
            t2.id = 20;
            t2.sid = 20;
    
            Test t3 = new Test();
            t3.id = 30;
            t3.sid = 30;
    
            System.out.println(t1.id);	//10
            System.out.println(t2.id);	//20
            System.out.println(t3.id);	//30
    
            System.out.println(t1.sid);	//30
            System.out.println(t2.sid);	//30
            System.out.println(t3.sid);	//30
        }
    }
    
    public class Demo {
        int id;
        static int sid;
    
        public void a(){
            System.out.println(id);     //静态可访问
            System.out.println(sid);    //非静态也可访问
            System.out.println("-----------a");
        }
    
        public static void b(){
    //        System.out.println(id);     //静态方法中不可访问非静态属性
    //        a();                        //静态方法中不可访问非静态方法
            System.out.println(sid);    //非静态可访问
            System.out.println("-----------b");
        }
    
        public static void main(String[] args) {
            Demo test1 = new Demo();
            test1.a();
    
            test1.b();
            Demo.b();
            b();        //同一类中直接调用
        }
    }
    

7,代码块

  • 类的组成:属性、方法、构造器、代码块、内部类
  • 代码块分类:
    • 普通块:写在方法中,可以限制局部变量的作用范围(只在代码块中可用)
    • 构造块:写在方法外,在对象生成的过程中会自动执行,先于构造器执行
    • 静态块:用static修饰的构造块为静态块。程序由上至下运行,在第一次遇到一个类的加载过程中就会执行静态块(不生成对象也会执行,只执行这一次),访问属性只能访问静态属性和静态方法。在实际应用中:创建工厂,数据库的初始化信息会放入静态块。
    • 同步块(多线程)
  • 执行顺序:静态块-构造块-构造器-普通块
    import java.sql.SQLOutput;
    public class Test {
        //属性
        int a;
        static int b;
    
        //方法
        public void a(){
            System.out.println("--------------a");
            //1,普通块
            {
                System.out.println("--------这是普通块");
                int num = 10;
                System.out.println(num);
            }
        }
    
        public static void b(){
            System.out.println("--------------b");
        }
    
        //构造器
        public Test(){
            System.out.println("这是空构造器");
        }
    
        public Test(int a){
            this.a = a;
        }
    
        //2,构造块
        {
            System.out.println("-----------这是构造块");
        }
    
        //3,静态块
        static {
            System.out.println("-----------这是静态块");
        }
    
        public static void main(String[] args) {
            Test t1 = new Test();
            t1.a();
        }
    
    }
    

8,析构与finalize方法

  • 有些面向对象的程序设计语言,特别是C++,有显式的析构器方法,其中放置一些当对象不再使用时需要执行的清理代码。在析构器中,最常见的操作是回收分配给对象的存储空间。由于Java有自动的垃圾回收器,不需要人工回收内存,所以Java不支持析构器。
  • 当然,某些对象使用了内存之外的其他资源,例如,文件或使用了系统资源的另一个对象的句柄。在这种情况下,当资源不再需要时,将其回收和再利用将显得十分重要。可以为任何一个类添加finalize方法。finalize方法将在垃圾回收器清除对象之前调用。在实际应用中,不要依赖于使用finalize方法回收任何短缺的资源,这是因为很难知道这个方法什么时候才能够调用。
  • 例子
public class test9 {
    public static void main(String[] args) throws InterruptedException {
        TestForFinalize testForFinalize = new TestForFinalize();
        testForFinalize = new TestForFinalize();    //虽然指向了新的对象,但是没有清楚也还是没有触发finalize方法
    }
}

class TestForFinalize{
    int a;

    public TestForFinalize(){a=1;}

    @Override
    public void finalize(){
        System.out.println(a);
    }
}

9,包

  • 包对应目录
  • 包的作用:解决重名问题,解决权限问题
  • 包名的定义:
    • 全部小写
    • 中间用.隔开
    • 一般都是公司域名倒着写:com.jd
    • 加上模块的名字:com.jd.login com.jd.register
    • 不能使用系统中的关键字 nul, con, com1, ..., com9, ...
    • 包声明的位置都在非注释性代码的第一行
  • 导包
    • idea可以设置自动导包

    • 使用不同包下的类需要导包,import \*\*.\*\*.\*\*

    • 导包以后,还想用其他包下同名的类,就必须自己手动写所在的包

      import java.util.Date;
      
      public class Test {
          public static void main(String[] args) {
              new Date();
              new java.sql.Date(1000L);
          }
      }
      
    • 同一个包下的类使用时不需要导包,可以直接使用

    • 在java.lang包下的类,可以直接使用不需要导包

    • idea导包的快捷键:alt+enter

    • 直接导入.*是导入包下的所有类,但是java中导包没有包含和被包含的关系,就算用.*导入所有,如果包里有子包,导入子包里面的类还要用import声明,可以设置idea目录格式为平级方便导入

    • 静态导入,在静态导入后,同一个类中有自定义的同名方法,优先使用自定义的方法

      import static java.lang.Math.*;
      //导入java.lang下的Math类中的所有静态的内容
      
      public class Test {
          public static void main(String[] args) {
              System.out.println(random());
              System.out.println(PI);
              System.out.println(round(5.6));
          }
      }
      

10,封装

  • 将变量和对变量的操作都定义在一个类中,就是类的封装,这是一种思路上的转化。
  • 封装的精髓是:可以实现对变量加权限、加判断(权限和判断外界还不可见)、代码多处复用等诸多好处。
  • Java中通过关键字private,protected,public实现封装
    • private修饰的属性外界无法直接访问,只能通过定义public方法进行访问,而且在public方法中可以加入限制条件
  • idea快捷键生成getter,setter方法:alt+insert
  • 例子
    //永远18岁
    public class Girl {
        //属性
        private int age;
    
        //读取年龄方法
        public int readAge(){
            return age;
        }
    
        //设置年龄方法
        public void setAge(int age){
            if(age>=30){
                this.age = 18;
            }else {
                this.age = age;
            }
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            Girl g1 = new Girl();
            g1.setAge(1);
            System.out.println(g1.readAge());
        }
    }
    

权限修饰符

同一个类 同一个包 子类 所有类
private *
default/缺省/不写 * *
protected * * *
public * * * *
  • 属性,方法常用权限修饰符:private,缺省,protected,public
  • 类常用修饰符:缺省,public
  • 一般属性private,方法public

11,继承

  • 概念
    • 类是对对象的抽象,继承是对类的抽象
    • 父类/基类/超类,子类/派生类
    • 继承是 is-a 的关系
    • 父类private修饰的内容,子类也会继承
    • 一个父类可以有多个子类;一个子类只能有一个直接父类,但可以间接的继承自其他类;继承具有传递性:A继承自B,B继承自C,A也继承自C
    • 所有的类都直接或间接得继承自Object
    • 子类继承了父类的所有属性和方法
    • 使用子类的引用可以调用父类的public, protected, default方法
    • 使用子类的引用可以访问父类的public, protected, default方法
    • 子类对象可以认为有一个特殊/隐藏的父类的对象(可以认为继承链上的所有父类都会创建一个隐藏的父类对象),这个父类对象和子类对象之间通过super关键字进行沟通,子类使用super可以调用父类的方法和属性(当然要满足访问控制符的控制)
    • super是子类和父类沟通的桥梁,但是并不是父类的引用(与this自引用不一样,比如this可以作为方法的返回值,而super不可以,super必须加.以使用方法或变量)
  • 语法
public class Student extends Person{}			//子类Student继承自父类Person

super(参数列表);  //使用父类的构造方法
* 必须是子类构造方法的第一个语句,缺省状态下会自动去父类找一个无参的构造方法进行调用,如果父类中没有无参的构造方法就会报错(所以写类的时候一定要写一个无参的构造方法,因为你也不知道你的类会不会作为父类被继承)
* 在使用super调用父类的构造方法时不能使用super访问父类的方法和属性(使用super访问父类的方法和属性不能作为参数传递给->使用super调用父类的构造方法),不可以使用子类成员变量和方法;
* 可以使用静态变量和方法;
* 都是<init>方法。
* 在构造器调用重载的构造器时,this修饰的构造器也必须放在第一个语句,那么如果这两种情况碰到一起呢?会发现谁放到前面都是会报错的。如下面的代码:
public class Student extends Person{
  public Student() {}
  public Student(String name, int age){
    Super(name, age);  //这两句怎么调整先后顺序都是错的
    this();
  }
}
* 那么怎么办呢?可以定义一个init方法,构造函数都调用init方法,类似下面:
public class Student extends Person{
  public init(String name) {setName(name);}
  public init(String name, int age) {setName(name); setAge(age);}
  public Student(String name) {init(name);}  //通过init避免构造方法调用重载的构造方法时,使用this修饰
  public Student(String name, int age) {init(name, age);}
  public Student(String name, int age){
    Super();  //这两句怎么调整先后顺序都是错的
    init(name, age);
  }
}

super.方法();    //使用父类的方法

super.属性      //使用父类的属性
  • 继承和组合的区别:
    • 继承-is-a-父类与子类之间的关系,组合-has-a-类与成员之间的关系。
    • 组合是 has-a 的关系,在确实是用继承还是组合时,需要回答一个问题:XX到底是YY的一种?还是只是组合了YY?
    • 如果只是简单的拿来父类的属性和方法进行使用的话,仅仅是原封不动的拿来,应该优先考虑组合。
    • 继承的精髓在于子类可以对父类的方法进行重写,以此来替换父类中不适用于子类的方法,这也是继承和组合最大的不同。
  • 方法重写/覆盖
    • 当父类提供的方法在子类中不适用时,要对父类的方法进行重写(继承的精髓所在)
    • 格式要求:子类中的方法名和父类方法一致,参数列表(个数、类型、顺序)与父类方法一致(修饰符可以不同),返回值类型也必须一致。
    • 子类覆盖父类的方法,不可以用可见性更低的修饰符,但是可以用可见性更高的修饰符。
    • 重载和重写的区别:
      • 重载发生在同一个类中,当方法相同,形参列表(个数、类型、顺序)不同时,多个方法构成重载
      • 重写发生在不同类中,当父类提供的方法在子类中不适用时,要对父类的方法进行重写
      • 英文 位置 修饰符 返回值 方法名 参数列表 抛出异常 方法体
        重载 overload 同一个类中 无关 无关 必须相同 必须不同 无关 不同
        重写 override 子类父类中 父类修饰符范围小于子类(如:父类private,子类public) 必须相同 必须相同 必须相同 小于等于 不同
  • 继承链长的时候,IDEA中ctrl+h或者Navigate->Type Hierarchy可以查看继承关系。
  • 父类和子类的引用赋值关系
    • 可以用子类的对象给父类的引用赋值,也就是父类的引用可以指向子类的对象;但是反之不行,不能用子类的引用指向父类的对象(父类中并没有子类中的属性和方法);
    • 当父类的引用指向子类的对象,只能通过父类的引用,像父类一样操作子类的对象,可以使用的变量和方法都是父类中有的,子类中独有的不能使用(使用受限,引用类型决定了能使用哪些操作);而变量和方法的效果是子类中的效果;
    • 对上句话的总结:引用类决定使用范围(决定能用什么),对象类决定效果
    • 如果确定一个父类的引用指向的对象就是一个子类的对象(或者子类的子类的对象),可以强制类型转换将对象赋值给一个子类的引用;
      Student s = (Student) personObject;
    • 不存在子类向父类的强制类型转换,因为子类的引用不可能指向一个父类对象;强转会报错,java会自动检查
    • 可以强转的对象,要么是强转后引用的类,要么是强转后引用的类的子类
  • 一个例子
//父类
package 第二章.thing;

public class commodity {
    private String name;
    private int price;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price){
        if(price<=0){
            System.out.println("价格应该为正数");
            throw new RuntimeException();
        }
        this.price = price;
    }

    public commodity() {}
    public commodity(String _name, int _price){
        setPrice(_price);
        setName(_name);
    }

    public double describe(){
        System.out.printf(this.name + "售价为:" + this.price);
        return this.price;
    }
}
//子类
package 第二章.thing;

public class phone extends commodity{

    String cpu;

    public phone() {}
    public phone(String name, int price, String cpu){
        super(name, price);
        this.cpu = cpu;
    }

    public double describe(){
        System.out.printf(getName() + " 售价为:" + getPrice() + " CPU:" + this.cpu + "\n");
        return getPrice();
    }
}
//调用
package 第二章;

import 第二章.thing.phone;

public class phoneAPPMain {
    public static void main(String[] args) {
        phone myPhone = new phone("华为", -2000, "麒麟9000");
        double price = myPhone.describe();
        System.out.println(price);
    }
}

12,多态-重载-重写

  • 多态基于继承;
  • 分清引用类和对象类:引用类是声明变量(变量就是地址)时用的类,对象类是给变量真正赋值时值的类(或者说变量所指向的地址所指向的在堆中的数据的类),对象类是引用类的同类或子类。
  • 在 10,继承中所提到,父类引用可以指向子类对象,由于一个父类可以有多个子类,每个子类中的同名方法可以有不同的实现功能,当用不同子类的对象给父类的引用赋值时,父类引用们调用同名方法却会有不同的功能效果(因为这里实际执行的是不同子类里的同名方法;引用类型决定能用什么,对象类型决定实际效果);这种同类引用调用同名方法却有不同效果的现象,就是多态的一种(重写)
  • 多态的两种形式:重载(overload) 重写(override)
    • 重载-静态多态-受传入参数的引用类控制
      • 重载实际调用哪个方法,根据传入参数的引用类确定,和传入参数的对象类无关;这里和多态中重写方法的实际效果根据对象类确定不同。
      • 重载所传入的参数和所有形参的引用类都不匹配时,会从传入参数的引用类出发,一层层向父类找,直到有父类和形参引用类匹配的重载方法为止再进行调用。
    • 重写/覆盖-动态多态-受调用方法的变量的对象类控制
  • 理清多态的核心问题:要调用哪个类的哪个方法,这个方法用到的数据(this引用)是谁。
    • 继承中隐藏的父类对象中的this自引用是子类的this!!!,比如调用某个方法1时;子类中没有匹配的方法1;会去找父类中匹配的方法1;而在父类中的该方法1又调用了某个方法2;方法1调用方法2是通过this.方法2进行调用的,虽然this可以省略;此时这个this是最初的那个子类,会先去子类中找有没有方法2,如果有,执行的是子类中的方法2!!!;如果子类中还没有方法2,再一层层向父类找。
    • 总之,this自引用就是实际对象的对象类,就算是去到了隐藏的父类中,所有的this还是原来的那个对象类!!!

13,对封装、继承、多态的总结:

  • 封装针对变量,精髓在于配合权限修饰符强制性为变量加权限、加判断(权限和判断外界还不可见)、代码多处复用等诸多好处。
  • 继承针对方法,精髓在于子类可以对父类的方法进行重写/覆盖,以此来替换父类中不适用于子类的方法。
  • 多态针对方法,有重载和重写两种。
    • 重写-精髓在于 基于继承(多态的基础是继承) 实现 同引用类(对象类不同) 同名方法效果不同(通过改变对象类和重写),引用类(只能是对象类的父类或同类)决定使用范围(决定能用什么),对象类(只能是引用类的子类或同类)决定效果(对象类中如果没有要调用的方法,再一层层向上去找父类中的同名方法)。
    • 静态方法的使用范围和效果都是由引用类决定的,这是由于静态方法以及普通方法的加载机制不同。

14,枚举

  • 枚举是一种特殊的类,特点是:必须在开始的地方以一种特殊的形式调用构造方法以创建枚举所有的实例。(枚举有多少对象在开始就已经确定了,对象之间用 , 分隔,最后用 ; )
  • 构造方法必须是private的(不写也是private的),这意味着是不允许私自创建枚举类的实例。
  • 所有的枚举类继承自 Enum
======================================定义enum======================================
public enum Category {
    //必须首先创建所有枚举的对象,并且对象不可重名
    FOOD(1),    //, 分割
    COOK(2),
    SNACK(3),
    CLOTHES(4),
    ELECTRIC(5);    //最后 ;

    //可以有属性
    private int id;

    //构造方法必须是private的,不写也是private的,这就意味着对象只能在类中创建
    private Category(int id){
        this.id = id;
    }

    public int getId(){
        return this.id;
    }

    public void setId(int id) {
        this.id = id;
    }

    //重写toString方法
    public String toString(){
        return "{Category "+ id + "}";
    }
}

======================================使用enum======================================
import java.util.Scanner;

public class useCategory {
    public static void main(String[] args) {
        //使用方法1:***.values() 会返回枚举类中所定义的所有对象
        for(Category category : Category.values()){
            System.out.println("===="+category.getId());
            System.out.println(category.ordinal()); //继承自Enum,对象的序号
            System.out.println(category.name());    //继承自Enum,对象的名字
            System.out.println(category.toString());    //默认返回名字,可重写
        }

        //使用方法2:获取枚举实例
        //2.1:直接获取实例
        Category food1 = Category.FOOD;
        System.out.println(food1.getId());
        System.out.println(food1);  //调用toString方法

        //2.2:valueOf(类似反射)
        Category food2 = Category.valueOf("FOOD");
        System.out.println(food2.getId());
        System.out.println(food2);

        //2.2
        Scanner in = new Scanner(System.in);
        System.out.println("请输入枚举的名字");
        String categoryName = in.next();
        Category categoryInput = Category.valueOf(categoryName.trim().toUpperCase());
        System.out.println("输入的枚举是 "+categoryInput+" "+categoryInput.name());

        //使用方法3:比较
        System.out.println(food1==food2);   //返回的是同一个对象
    }
}

15,接口

  • 定义
    • 接口的定义使用interface
    • 接口中可以定义抽象方法,抽象方法使用public abstract修饰(可省略,默认就是public abstract),抽象方法有:名字、参数和返回值,没有方法体,以;结束。
    • 接口中不能定义局部变量(成员变量),定义的变量都是public static final修饰的,这三个修饰符同样可以省略。
  • 使用
    • 接口不可通过new实例化,只能由通过implements实现接口,并且必须将接口中的抽象方法全部重写以赋予具体的方法体(IDEA中可以alt+insert直接插入所有的方法)。
    • 一个类只能继承一个父类,并同时实现多个接口,比如:public class A extends B implements C, D{}
    • 实现的多个接口中允许定义一样的方法,但是要求方法名、参数和返回值类型(函数签名)必须一模一样。
    • 一个类实现接口,就是从接口继承了抽象方法。
    • 接口可以作为引用类(和父类做引用类一样),也可以使用instanceof进行判断。
  • 接口继承
    • 接口也可以继承接口,并且一个接口可以继承多个接口,接口之间继承用extends
    • 继承的接口,可以有重复的方法,但是签名相同时返回值必须完全一样
  • 有方法体的接口
    • 在java8中,接口允许有静态方法。
    • 在java8中,接中允许有缺省实现的抽象方法,使用default修饰,一个类不可以从两个接口中获得相同的缺省方法。
      • 实现有缺省方法的接口后,对于缺省方法,一个类有三种选择:
        • 继承,直接使用(相当于将缺省方法的代码直接拷贝到当前类中)
        • 重新生命缺省方法为abstract,相当于将接口中缺省方法的方法体拒之门外,但是此时类必须是抽象类(子类必须重写)
        • 覆盖/重写,重新实现
    • 在java8中,接中可以有私有方法,使用private修饰,用于辅助缺省实现的抽象方法。
    • 在java8中,接口依旧不允许有成员变量(接口的红线,可以有方法,但是不允许有状态)。

16,抽象类

  • 抽象类用abstract修饰,抽象类可以继承别的类或者抽象类,也可以实现接口
  • 与普通类的区别1-抽象类可以有抽象方法,抽象方法可以来自实现的接口,也可以自定义
  • 与普通类的区别2-抽象类不可以被实例化(就算没有抽象方法也不可以被实例化)
  • 抽象类的构造方法语法与普通类一样
  • 抽象类中定义的抽象方法,可以是protected,也可以是缺省的(和接口中的默认为public不同)

17,静态内部类

  • 静态内部类,是在类中通过static修饰的类,可以有访问控制符(public)。静态内部类和静态方法,静态变量一样,都是类的静态组成部分。
  • 静态内部类也是类,在继承、实现接口方面与一般的类是一样的。
  • 静态内部类还可以定义静态内部类。
  • 可以在所在类内部,和所在类相互访问private修饰的成员(静态内部类的访问权限和外部类代码的权限是一样的)。
  • 静态内部类没有外部类的this自引用

18,成员内部类

  • 与静态内部类相比,区别就在于没有static修饰。
  • 是在类中定义的类,不可以包含任何静态方法、静态变量、静态内部类(非static代码块不可以使用static修饰的代码),但是可以有 final static 修饰的基本数据类型变量
  • 可以有访问控制符
  • 和外部类代码的权限是一样的,可以访问外部private修饰的成员
  • 成员内部类有外部类的this自引用外部类.this.方法/属性
  • 外部使用方法,语法很怪外部类.内部类 对象名 = 外部类.new 内部类(String[] args)

19,局部内部类

  • 局部内部类,是在类中直接定义的类(更具体来说是在方法里定义的类)
  • 不可以包含任何静态方法、静态变量、静态内部类,但可以有 final static 修饰的基本数据类型变量
  • 不可以有访问控制符
  • 和外部类代码的权限是一样的,可以访问外部private修饰的成员
  • 局部内部类有外部类的this自引用外部类.this.方法/属性
  • 和成员内部类相比,局部内部类还可以访问参数和局部变量,但是这两者必须是实际final的(访问的参数和局部变量在方法中只能被赋予一次值)
  • 局部内部类,就好像局部变量一样,方法内部的东西出了方法体就不可被访问

20,匿名类/匿名方法

  • 用来创建接口或者抽象类实例的(使用匿名类实现接口/抽象类中定义的抽象方法,将接口/抽象类补全为普通类就可以创建对象了),匿名类可以出现在任何有代码的地方

21,各种类总结

  • 枚举:有固定个数实例的类,父类是Enum
  • 非公有类:最不特殊的类,可以认为就是被缺省访问控制符修饰的类。和public class区别仅仅是可以被访问的范围不一样。如果一个文件只有非公有类,那么类名和文件名可以不一样,当然文件后缀必须是java。
  • 内部类:内部类的特殊之处在于可见性以及可以访问的数据以及方法。内部类会被认为是类本身的代码,所以外部类的private成员对其可见。
    • 类里面有静态变量、成员变量和局部变量,对比来看,内部类也分为三种,这些内部类的访问修饰符,可以访问的数据以及可见性都可以对比着记忆。
      • 静态内部类:可以有访问修饰符,可以在类外部访问(对比静态变量)
      • 成员内部类:可以有访问修饰符,有外部类对象的this自引用(对比成员方法),可以在外部使用,但是创建对象语法需要指明外部对象
      • 局部内部类:没有访问修饰符(对比局部变量),有外部类的引用,访问参数和局部变量,必须是final的
    • 内部类可以有父类,可以实现接口。
  • 匿名类:一种创建接口和抽象类对象的语法,任何可以new一个对象的地方,都可以使用匿名类。
    • 匿名类只能实现/继承一个接口/抽象类,本身没有名字。
    • 如果是在成员方法或者给成员方法赋值时创建匿名类,那么会有对外部对象的this自引用。
    • 匿名类也可以访问外部类的private属性。
  • 无论是内部类还是匿名类,类都只有一个,对象可以有多个。不会在每次执行到内部类声明的地方,就创建一个新的类。

22,lambda表达式和匿名类

  • lambda是函数式编程。很多语言中,函数(方法)是一等公民,无需依附其他任何元素即可存在,并可作为参数和返回值。在java中本来只有类是一等公民,方法必须依附于某个类,但是在java8之后,java也支持lambda了。
  • lambda相当于jvm帮我们生成了一个类来实现接口,并调用我们提供的方法。
  • lambda表达式必须能够符合接口中定义的抽象方法,从参数、到返回值、到异常,都必须匹配。
  • 完整形式的lambda表达式:(参数)->{代码块}
  • lambda表达式可以有返回值,使用return语句即可(也可以没有)
  • lambda可以使用外部数据(和匿名内部类是一样的)
//例子1-匿名内部类
public class test {

    public static void main(String[] args) {
        List<String> test1 = new ArrayList<>();
        for(int i=0; i<10; i++){
            test1.add(i+"");
        }
        System.out.println(test1.toString());

        //匿名内部类
        test1.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s+ " Hellow!");
            }
        });
    }
}
//相同功能的lambda表达式
public class test {

    public static void main(String[] args) {
        List<String> test1 = new ArrayList<>();
        for(int i=0; i<10; i++){
            test1.add(i+"");
        }
        System.out.println(test1.toString());

        //lambda表达式
        test1.forEach(s-> System.out.println(s+ " Hellow!"));
    }
}
posted @ 2021-08-31 14:30  tensor_zhang  阅读(64)  评论(0编辑  收藏  举报