• Java笔记:Java面向对象


    一、方法

    1、概述

    方法,也可以称之为函数,在其他语言中可能方法和函数的概念和语法是不同的,比如Python中的函数是可以在文件中独立定义并存在的,而方法则是在类之中定义的函数,但是在Java中,方法和函数都指的是同一个语法,都是一样的,既可以称它为方法,也可以称它为函数。需要注意以下几点:

    • 方法是定义在类体之中的。
    • 类体之中的多个方法之间是没有顺序关系的。
    • 方法体之中不能再定义方法。

    2、定义方法

    语法如下:

    [修饰符列表] 返回值类型 方法名(形式参数列表){
        方法体;
    }

    修饰符列表:这是可选项,不是必须的。如果不写,则使用默认选项,对于访问控制符,如public、private等,缺省的访问控制权限为包范围内。

    返回值类型:使用“return 值”返回一个值,且这个值的类型必须和指定的返回值类型一致,指定的返回值类型可以是Java中的任何数据类型,包括基本数据类型和所有引用类型。如果此方法不返回任何值,就必须将其指定为void,表示此方法不返回任何值,即方法体中不能有“return 值”这样的语句,但是可以写“return;”表示返回void。

    方法名:遵循标识符的定义规则,但通常应该注意以下几点:

    • 最好见名知意,不要为了方便省时,就写func1、func2等这种看不出方法大概功能的方法名。
    • 最好是动词。
    • 首字母小写,其后的每个单词的首字母大写,即遵循驼峰命名法。

    形式参数列表:

    • 形参是局部变量,即它们的作用范围只在方法体之内。
    • 多个形参之间使用逗号隔开。
    • 形参中起决定性作用的是其数据类型,形参的名字就是局部变量的名字。
    • 在调用方法时给方法传递的真实数据被称为“实际参数”,即实参。
    • 对于实参列表和形参列表的使用,需要注意,它们的数量必须相同,而且对应的数据类型也必须一致。

    方法调用:使用点号“.”进行调用,但是注意,方法体中的程序只有在调用时才会去执行,定义或编译时都不会执行。

    3、static方法调用

    如果修饰符列表中有static的话,则称之为静态方法,调用此方法的语法格式为“类名.方法名(实际参数列表)”,但如果是调用本类中的static方法,则可以省略类名,直接使用方法名进行调用。

    4、参数值的传递

    在调用方法时,给方法传递的参数为变量的值(即值传递),而不是变量本身,因为如果传递的是变量本身,那岂不是就可以在调用的方法中使用这个变量了,但实际情况却是,调用的方法只能使用自己这个作用域中的局部变量,而不能使用它的调用者所在的作用域中的变量。所以传递的就是变量的值,从而使得方法的形参有了自己的值。

    5、可变长参数

    方法的可变长参数指的是定义方法时只需要定义参数的类型,而不用写死这种类型的参数个数,使用时也可以根据需要传入不同的参数个数。

    public class Test{
        public static void main(String[] args){
            // 使用时,可传入0-n个参数
            func();
            func(10);
            func(10, 20, 30);
        }
        
        // 语法格式:定义方法的参数时,使用形如“类型... 参数名称”的格式,注意,类型名称之后一定是三个点
        // 在方法中使用这个参数时,可以将这个可变长参数当做数组来处理
        public static void func(int... args){
            // code...
        }
        
        // 可变长参数的定义必须也只能是参数列表的最后一个位置,且一个方法只能有一个可变长参数
        public static void func(String s, int... args){
            // code...
        }
    }

    6、方法重载(overload)

    重载原则:重载的方法功能相似的时候可以考虑使用方法重载,但是功能不同的时候尽量使用方法名称不同的方法来定义。

    重载机制:

    • 重载的方法必须在同一个类中。
    • 重载的方法它们的方法名必须相同。
    • 参数列表不同:包括参数数量不同,以及数量相同但顺序上类型不同两种情况。
    • 注意:方法重载只和方法名与参数列表有关,与返回值与修饰符列表无关,即只要方法名和参数列表满足重载机制,就算作方法的重载。

    7、JVM内存分配

    如果只是定义方法,不去调用,则不会被执行,在JVM中也不会给该方法分配运行所需的内存,只有在调用这个方法的时候才会动态的分配该方法所需的内存空间。
    JVM的内存划分中主要有三大块的内存空间(当然也还有其他的内存空间):

    • 方法区内存:专门用于代码的存放,当然方法的代码片段也是存放在这里的。方法的代码片段属于“.class”文件的一部分,在JVM加载“.class”字节码文件的时候就会将对应的方法加载到方法区内存中,所以这三块内存中最先有数据的就是方法区内存。并且方法代码片段在内存中只有一份,但是可以重复调用。
    • 堆内存(heap):主要用于存放创建的实例对象,包括对象自身的数据,如属性值等,因为对象的数据是存放在对象内部的。Java的垃圾回收机制主要针对的也是这块内存区域,只要堆内存中的对象没有其他地方对它进行引用的话就会被回收掉。
    • 栈内存:在方法调用执行的时候,需要给该方法分配独立的内存空间,这个内存空间就是在栈内存空间中进行分配的(方法在调用的瞬间,会在栈中给该方法分配独立的内存空间,此时,会在栈中发生压栈动作,方法执行结束之后,给该方法分配的内存空间就会被全部释放,此时,会在栈中发生弹栈动作)。方法中的局部变量因为是在方法体中声明定义,所以局部变量的内存空间也是在栈中分配的(即栈内存中主要存储的是局部变量)。
    • 关于栈(stack):栈是一种数据结构(数据结构反应的是数据的存储形态,数据结构这个词和概念是独立的,并不只是在Java中有),栈中包括栈底元素、栈中元素、栈顶元素和栈帧,栈帧永远指向栈顶元素,并且只有栈顶元素处于活跃状态,其他元素则处于静止状态。
      • 压栈/入栈/push:将一个元素加入栈的栈顶。
      • 弹栈/出栈/pop:将一个栈顶元素弹出栈。
      • 栈数据结构的特点:先进后出,后进先出。
    • 注意:堆内存和方法区内存都只是各有一个,但是栈内存是一个线程有一个栈内存。

    8、方法递归

    方法递归是指方法在方法体中调用自身,使用方法递归时应注意以下几点:

    • 方法递归非常的耗费栈内存,所以能不用递归就尽量不用递归。
    • 如果递归没有设置结束标识或者递归太深就会发生栈内存溢出的错误,导致JVM停止工作。因为每一次调用一个方法,哪怕是调用自身,都会在栈中开辟一块新的内存来创建这个方法的运行环境(压栈),所以递归调用会导致不断的内存开辟,即不断的压栈,最终内存不够用时还在进行内存的开辟和压栈操作,肯定就会出错了。

    二、类和对象

    1、封装和类定义

    类主要描述的对象的状态(属性)和动作(方法)。

    类也是面向对象编程中“封装”特性在语法上的体现,封装特性的优点通常有以下几点:

    • 将程序的实现原理的复杂性进行封装,只对外提供简单的操作入口。
    • 封装之后才能形成真正的“对象”和“独立体”的概念。
    • 封装意味着程序可以重复使用。
    • 封装之后,对于对象本身,提高了安全性。

    一个普通的类的定义语法如下:

    [修饰符列表] class 类名{
        属性;
        方法;
    }

    成员变量:在类体之中、方法之外的变量称之为成员变量。成员变量如果没有手动赋值的话,系统会自动赋予默认值(一切向“0”看齐)。成员变量又分为:

    • 实例变量(没有static修饰符)
    • 静态变量(有static修饰符)

    注意:类也是一种数据类型,属于引用数据类型,它的类型名称就是对应的类名。

    2、对象创建和内存分配

    对象就是类实例化之后的具体个体,类到对象的过程称之为实例化,反过来,对象到类的过程则称之为抽象。

    new关键字:Java中使用new关键字来创建一个对象,new关键字也是Java中的一个运算符。

    内存分配:当在方法区内存中的代码执行时,会在栈内存中开辟一块该方法对应的内存空间,而在方法执行过程中使用new关键字创建一个对象时,则会在堆内存中开辟一块该对象对应的内存空间。所以方法中定义的局部变量是在栈中的,而创建的对象则是在堆内存中的。实例对象每一个对象都会有自己的一块内存空间,即100个对象就会分配100个内存空间。

    指针屏蔽:Java中想要访问堆内存中的数据,必须通过引用,而不能直接操作堆内存,因为Java中屏蔽了指针的概念,不能通过指针的方式直接访问或操作内存中的数据。

    访问属性:对于实例变量属性的读取和修改,使用语法格式“引用.变量名”进行读取,使用语法格式“引用.变量名=值”对属性进行修改。注意,实例变量存储在堆内存中对应的实例对象内部,且不能通过类名的方式来访问。

    3、空指针异常NullPointerException

    当一个引用类型的变量的值不再是指向某个对象的内存地址,而是null,此时再去访问对象的相关属性或方法就会发生空指针异常,因为此时的变量不再指向该对象,而是值为null了,无法去访问该对象了,更不要说访问对象中的属性和方法了,空引用访问实例相关数据就一定会出现空指针异常。

    4、get方法和set方法

    属性私有化:在封装特性中,类中的所有属性都应该使用private修饰符进行修饰,private表示私有的,表示此属性只有在本类中才能访问,在类的外部不能访问。但是在类中应该为外部访问这些属性提供一些简单的公开的(public)操作入口,如对应的get方法和set方法。

    get方法和set方法的写法如下:

    // get方法
    public 返回值类型 get+属性名首字母大写(){
        return 属性名;
    }
    
    // set方法
    // 注意,形参的名字如果和属性名相同了,那么属性名前面应该加一个this关键字
    // 因为不加this关键字的话,由于名称是相同的,Java的就近原则会认为它俩都是同一个局部变量,即形参
    public void set+属性名首字母大写(形参列表){
        属性名=形参值;
        ...
    }

     示例:

    public class A{
        private int age;
        
        public int getAge(){
            // 这里可以使用this.age,也可以不用this
            // return this.age
            return age;
        }
        
        public void setAge(int age){
            // 这里因为形参和属性名相同了,所以必须用this加以区分
            this.age = age;
        }
    }

    注意:get和set方法是没有static修饰符的,使用的是public修饰符,没有static修饰符的方法的访问方式为“引用.方法名(实参)”。

    5、引用参数的传递

    对象变量通常也称之为引用,因为在栈中这个变量只是个局部变量,而对象变量的值是该对象在堆内存中的内存地址,当然,这个内存地址则指向堆内存中的该对象实例。所以对于基本数据类型,值的传递不会影响到原本变量的值,但是对于类的实例,因为传递的值是内存地址,所以它虽然不会影响原本局部变量的值(即内存地址),但是如果对内存地址中的对象实例进行修改则会影响到内存地址指向的实例对象,即原本的局部变量指向的实例对象会被修改。

    6、构造方法(constructor)

    语法如下:

     // 构造方法,也称为构造器(constructor)。
     // 构造方法是不用也不能指定返回值类型的。
     // 注意,构造方法名必须和类名相同,所以这里的语法就直接写类名了。
     [修饰符列表] 类名(形式参数列表){
         构造方法体;
     }

     示例:

    public class A{
        private int i;
        // 下面的两个构造方法使用了方法的重载机制
        public A(){
            System.out.println("类A的无参构造方法!");
        }
        public A(int i){
            // 使用this关键字区分实例变量和方法的局部变量
            this.i = i;
            System.out.println("类A的有参构造方法!");
        }
    }

    构造方法的调用:构造方法的作用是通过调用构造方法来创建对象并初始化实例变量的值,而构造方法的调用使用new关键字“new 构造方法名(实参列表)”,注意new之后调用的其实是构造方法名而不是类名,但因为两者是相同的,所以可能会让人误以为调用的是类名。

    构造方法返回值:虽然没有指定返回值类型,但是构造方法的返回值类型就是其所在类的类型,返回值就是新创建的对象的引用,但是注意的是这个返回值是不需要开发人员手动编写的,即构造方法的定义中,返回值类型和返回值都不需要人为的去定义。

    默认构造方法:当类中没有定义构造方法时,系统会给该类提供一个无参数的默认构造器。需要特别注意的是,如果类中提供了构造方法,那么系统就不再为这个类提供默认的无参数构造方法了,所以,如果在类中提供了自己的构造方法,那么推荐手动将无参的构造方法加上,因为这个构造方法太常用了。

    关于构造方法,还应该注意以下几点:

    • 构造方法支持重载机制。
    • 因为实例变量是属于实例的,所以构造方法是先创建对象再初始化实例变量。

    7、this关键字

    其实每一个实例对象中都有一个this变量,this中保存的是自身所在实例对象的内存地址,即this是指向实例对象本身的一个引用类型的变量。可以换一种方式理解,this可以出现在实例方法中,而方法中的this代表当前正在执行这个方法动作的实例对象。

    在实例方法中对实例变量的访问,由于它是实例变量,所以不使用this关键字也是可以访问的,所以this在多数情况下是可以不写的。this主要用于区分实例变量和局部变量,比如setter方法和构造方法中就比较常用。

    当然,this不能在含有static修饰符的方法中使用。

    this关键字除了使用“this.xxx”的方式表示实例对象的使用之外,还可以在构造方法中以“this(实参列表)”形式表示调用本类的另一个构造方法,但是注意,使用这种用法时这个语句只能出现在构造方法的第一行(当然这个语句之后可以添加其他的语句,但前面就不能有其他任何语句了),如:

     public class User{
         private int age;
         public User(int age){
             this.age = age;
         }
         public User(){
             // 此处表示调用另一个构造方法
             // 但是注意,这个语句只能是此构造方法的第一个语句
             this(18);
             // 之后可以加别的语句
             System.out.println("my age is " + this.age);
         }
     }

     8、super关键字

    super关键字和this关键字在用法上有许多相似的地方,但是this代表的是当前实例对象,而super代表的是当前子类的父类的特征(包括属性和方法),通常用于访问父类的某些属性和方法。和this对比着看,它们的相似之处如下:

    • super用法也有两种:”super.“和”super()“。
    • super也是只能出现在实例方法和构造方法中,不能在静态方法中使用。
    • super在大多情况下也是可以省略的,除非特定指明需要使用父类中的属性或方法的时候才使用。
    • super()这种用法也是只能出现在构造方法的第一行,通过当前的构造方法去调用父类的构造方法,super的一个作用是代码复用,另一个作用是在创建子类对象的时候先初始化父类的特征(属性等)。父类的特征如属性因为大多是private,并不能通过直接赋值来初始化,此时就需要使用super来调用父类的构造方法来进行初始化了。

    对于super()这种用法,当一个子类的构造方法的第一行既没有this(),也没有super(),那么默认会有一个super()执行,表示在子类的构造方法中调用父类的无参构造方法,此时必须保证父类必须有一个无参构造方法,推荐在类的定义中都手动写好一个无参的构造方法。当然,要是你自己手动调用了this(实参列表)或者super(实参列表),程序就会按照你写进行调用了。示例如下:

    public class TestSuper{
        public static void main(String[] args){
            // 执行结果:
            // 类A的无参构造方法!
            // 类A的无参构造方法!
            new B();
    }
    }
    
    
    class A{
        public A(){
            System.out.println("类A的无参构造方法!");
        }
    }
    
    
    class B extends A{
        public B(){
            // 由main方法的输出可以看出类A的无参构造方法也是被执行的,
            // 其实如果没有手动调用super(),此处会默认执行一个super()
            // super();
            System.out.println("类B的无参构造方法!");
        }
    }

    关于super的使用,注意以下几点:

    • Java中允许在子类中出现和父类一样的同名变量或同名属性,此时,如果想要在子类中访问父类的这个同名的属性,就需要使用“super.xxx”的形式去访问了。
    • super不是引用,保存的也不是内存地址,也不指向任何对象,只是代表当前对象内部的父类特征。

    9、继承

    继承特性优点:继承最基本的作用是代码复用,但是最重要的作用却是有了继承才有了方法的覆盖和多态机制。

    单继承:Java中的继承机制只支持单继承,一个类不能同时继承多个类,只能继承一个类。语法如下:

    // 继承使用extends关键字
      [修饰符列表] class 类名 extends 父类名{
          类体;
      }

    可以继承的数据:

    • private私有的不支持继承。
    • 构造方法不支持继承。
    • 其他数据可以被继承。

    多继承:Java中虽然只支持单继承,但是可以间接实现多继承:

      C extends B{
      }
      B extends A{
      }
      A extends T{
      }
      // 这样C直接继承B,但间接继承了T和A类

    默认基类:Java中一个类如果没有显式继承任何类,那么该类默认继承javaSE库中提供的java.lang.Object类。

    需要注意一个概念,当一个子类在继承某个父类时,在运行时,不是说在子类中查找对应方法或属性,子类中没有再到父类中查找,而是在定义时,如果继承了某个父类,那么这个类的定义中就包含了父类继承过来的某些方法和属性,即子类对象执行的方法和属性总是自己的属性和方法。

    10、方法的覆盖/重写(override)

    方法的覆盖也称为方法的重写,子类将父类继承过来的方法进行重新编写被称为方法的重写,方法重写时需要注意:

    • 方法重写发生在具有继承关系的父子类之间,且是可以继承的方法上(私有的以及构造方法不能继承,也就不能进行重写了)。
    • 重写时必须遵守:返回值类型相同,方法名相同,形参列表相同。
    • 访问权限不能更低,但是可以更高,private最低,public最高。
    • 抛出异常不能更多,但是可以更少。
    • 静态方法不存在重写。
    • 覆盖只谈方法,不谈属性。

    11、多态

    向上转型(upcasting):子类型 --> 父类型,可以理解为自动类型转换。
    向下转型(downcasting):父类型 --> 子类型,可以理解为强制类型转换。
    在类和类之间,无论是向上转型还是向下转型,都必须具有继承关系,不然编译不通过。

    多态语法机制:父类型的引用指向子类型对象这种机制导致程序在编译阶段和运行阶段出现了两种不同的形态或状态,这种机制可以称为一种多态语法机制。

    多态的作用:降低程序的耦合度,提高程序的扩展力。能使用多态就多使用多态,即父类型引用指向子类型对象。
    多态的核心思想:面向抽象编程,尽量不要面向具体编程。

    示例:重点在注释哦

      public class Animal{
           public void run(){
               System.out.println("动物在移动!");
           }
       }
       
       public class Cat extends Animal{
           public void run(){
               System.out.println("猫在散步!");
           }
           
           public void catchMouse(){
               System.out.println("猫在抓老鼠!");
           }
       }
       
      public class Bird extends Animal{
          public void run(){
               System.out.println("鸟儿在飞翔!");
           }
      }
      
       public class Test{
           public static void main(String[] args){
               // 此处为向上转型,从Cat类型自动转换为Animal类型
               Animal cat1 = new Cat();
               // 向上转型之后,可以访问父类型中的方法,但是如果这个方法被子类型中重写了
               // 那么执行的就是子类型中的方法了,并且类型转化之后不能再执行子类型中特有的方法了
               // 比如catchMouse方法,但是需要注意的是,虽然类型转换了,但是引用指向的堆内存中的
               // 对象依然是最开始创建的Cat类型的源对象cat1,所以执行方法时原则就是子类型中没有就执行继承自父类型的方法,如果子类型中有这个方法时就执行子类型中的方法,但是不能执行子类型中特有的方法。
               // 在编译阶段会将符合语法的该对象的方法绑定,这个过程称之为静态绑定,只有静态绑定成功之后才能运行程序。这个例子中,静态绑定是将Animal的move方法绑定到cat1对象,因为cat1是声明为Animal类型的,而Animal类是有move方法的,所以能绑定成功。
               // 在运行阶段则会将实际运行的方法绑定到该对象上,这个过程称之为动态绑定,这个例子中,动态绑定是,在运行时,由于是先在内存中生成的对象是new出来的Cat类型的对象,虽然在等号赋值运算时类型被转换为Animal类型了,但是内存中其实还是那个被创建好的Cat类型的cat1对象,所以会执行Cat类中的move方法。
               cat1.run();  // 输出为:猫在散步!
               
               // 此处会编译不通过,虽然cat对象有catchMouse方法,但是类型转换后,因为Animal类型中没有catchMouse方法,所以编译不通过,即静态绑定失败。当然,也就不可能继续运行了。
               cat1.catchMouse();
               
               // 向下转型,这里不仅能编译通过,还能正确执行catchMouse方法,因为cat1其本质就是最初在内存中创建的Cat类型对象,而Cat类是由这个方法的
               Cat cat2 = (Cat)cat1;
               cat2.catchMouse();
               
               // 此处的向下转型编译能能通过,但是运行会报错java.lang.ClassCastException(除了空指针异常之外另一个著名的异常),即类型转换异常,而且只有在向下转型的时候会发生。
               // 因为第一个语句向上转型后,其实际还是个Bird类型对象,在第二个语句的向下转型,因为
               // Animal类型和Cat类型之间具有继承关系,所以可以编译通过,但是运行时由于它本质是Bird类型
               // 对象,不能转换成Cat类型对象,因为Bird和Cat之间没有继承关系,所以会报错。
               Animal bird1 = new Bird();
               Cat cat3 = (Cat)bird1;
           }
       }

    12、instanceof运算符

    语法:“引用 instanceof 数据类型名”,返回值为true/false,true表示这个引用指向的内存真实对象就是该数据类型的对象,false则表示这个引用指向的内存真实对象不是该数据类型的对象。如上例中“Animal bird1 = new Bird();”的bird1虽然转换成了Animal类型,但其真实内存对象其实是Bird类型的,所以如果执行“bird1 isinstanceof Bird”就会返回true。
    Java编程规范中,在进行强制类型转换时,建议先使用instanceof运算符判断引用的类型再进行转换。

    13、抽象类

    抽象类使用abstract关键字修饰,是类和类之间共同特征的提取而形成的类,通常抽象类中含有抽象方法,但也不是说抽象类中就一定需要定义抽象方法。对于抽象方法的定义,需要注意,它同样需要abstract关键字修饰,同时不能有大括号,还需要以分号结尾。

    抽象类也属于一种引用类型,使用抽象类来定义一个子类的对象,这种语法正是多态的应用,即向上转型,父类型的引用指向子类型的对象。

    // 语法
    [修饰符列表] abstract class 类名{
        // 通常含有抽象方法,但也不是必须的
        类体;
    }

    示例:

    // 抽象类使用abstract关键字修饰
    abstract class Animal{
        // 抽象方法也使用abstract关键字修饰
        // 并且抽象方法定义时不能有大括号
        public abstract void run();
    }
    
    
    class Dog extends Animal{
        // 如果子类继承自抽象类,但自身又不是抽象类
        // 那么子类就必须重写/覆盖/实现抽象类中的所有抽象方法
        public void run(){
            System.out.println("小狗在奔跑!!!");
        }
    }

    使用抽象类时,应注意以下几点:

    • 抽象类无法实例化,无法创建对象,只能是用于子类来继承。所以也由此引出另一个点,final关键字和abstract关键字是不能联合使用的,因为final修饰的类是无法被继承的。
    • 抽象类的子类也可以是抽象类。
    • 抽象类是可以有构造方法的,但是这个构造方法是给子类用的,因为抽象类是无法实例化的。
    • 抽象类中可以没有抽象方法,但是有抽象方法的类必须是抽象类。
    • 如果子类继承自抽象类,且子类不是抽象类,那么子类就必须实现(其实就是方法重写/覆盖)抽象类中的所有抽象方法(如果有)。当然,如果子类也是抽象的,那么抽象方法的实现就不是必须的。抽象方法的实现注意两点:去掉abstract关键字,以及加上具体实现的方法体。

    14、接口

    接口在学习时候虽然可以将它当做是类来理解,但是注意,接口并不是类,定义使用的关键字是interface而不是class,但是编译之后也是一个class字节码文件。

    同时,和抽象类一样,接口也是一种引用类型,使用接口的时候,可以使用多态,或者说接口的使用离不开多态,因为接口本身无法直接创建对象,一旦创建对象就必然是接口的“子类”,即向上转型,父类型的引用指向子类型的对象。

    // 语法
    [修饰符列表] interface 接口名{
        常量或抽象方法;
    }

    示例:

    public class HelloWorld{
        public static void main(String[] args){
            // 这里是向上转型
            A a = new C();
            a.func1();
            // 这里是向下转型,接口的向下转型编译不会报错,但是运行的时候可能报ClassCastException异常
            // 所以无论是类之间的向下转型还是接口之间的向下转型,转型之前,都应该使用instanceof运算符判断下
            // 当然,这里是不会报错的,因为a对象本质上是个C对象,而C中有实现了B,所以转成B是没有问题的。
            B b = (B)a;
            b.func2();
            // 输出结果:
            // func1...
            // func2...
        }
    }
    
    // 接口中只能定义常量和抽象方法,并且都只能是public
    interface A{
        // 接口中的常量都只能是public static final修饰的,这三个关键字也是可以省略不写的,编译的时候会自动加上的。
        int i = 10;
        // 接口中的抽象方法都只能是public abstract修饰的,这两个关键字是可以省略不写的,编译的时候会自动加上的。
        void func1();
    }
    
    interface B{
        void func2();
    }
    
    // 类实现接口使用implements,而不是extends
    // 一个类可同时实现多个接口,类对接口的多实现相当于多继承,这其实弥补了Java的类和类之间只能单继承的缺点。
    // 如果实现接口的类不是抽象类,那么就必须实现接口中的所有方法
    class C implements A, B{
        public void func1(){
            System.out.println("func1...");
        }
        public void func2(){
            System.out.println("func2...");
        }
    }

    使用接口的使用,需要注意以下几点:

    • 接口中只允许定义常量和抽象方法。接口中的常量都只能是public static final修饰的,这三个关键字是可以省略不写的,编译的时候会自动加上的。接口中的抽象方法都只能是public abstract修饰的,同样,这两个关键字也是可以省略不写的,编译的时候会自动加上的。
    • 需要注意的是,重写接口中的方法时,修饰符public不能省略(接口中定义时是可以省略的)。
    • 接口和接口之间也可以继承,并且接口支持多继承,继承的接口之间使用逗号隔开即可。
    • 一个类可同时实现多个接口,类对接口的多实现相当于多继承,这其实弥补了Java的类和类之间只能单继承的缺点。
    • 类和类之间的继承使用关键字extends,而类和接口之间的实现使用关键字implements关键字,但是注意,实现接口的类也可以是抽象类,只要加上关键字abstract就行,因为接口中的方法全是抽象的,而抽象类中是可以包含抽象方法的。所以一个非抽象的类实现一个接口的话必须重写接口中的所有抽象方法。
    • 在进行强制类型转换时,两个接口类型之间没有继承关系也可以进行强转,编译器不会报错,但是运行时可能发生ClassCastException异常,这和类之间的强转不同,类的强转在编码的时候就不允许,见上面的示例。
    • 一个类中如果同时有extends和implements,编码时应该extends关键字在前,implements关键字在后,其实,由此可以看出,接口的作用是提取某些动作,类实现接口就是在给自身添加某些动作,当不想要这个动作时,删掉这个接口和对应的方法即可。

    15、抽象类和接口的区别

    其实,在实际使用中还是接口使用的多,抽象类使用的少。抽象类和接口看着有许多相似的地方,但它们之间的区别还是有很多的,如下:

    • 抽象类是半抽象的,接口是完全抽象的。
    • 抽象类中有构造方法,接口中没有构造方法。
    • 抽象类属于类,类和类之间只能单继承,而接口和接口之间支持多继承。
    • 一个类可以同时实现多个接口,而一个抽象类只能继承一个类(单继承)。

    三、修饰符

    1、static

    静态变量:带有static关键字的变量,称之为静态变量,并且,在类加载的时候就静态变量开始初始化了,不需要创建对象它的内存就已经开辟了,并且是存储在方法区中的

    静态方法:带有tatic修饰符的方法称为静态方法,在静态方法中不能访问实例变量和实例方法,当然,也包括this关键字,而是只能访问同样带有static修饰符的变量(静态变量)。

    使用原则:当一个方法或变量它的执行与具体的对象无关,或者说所有对象都会用到这个方法或变量,并且还不会因为对象的不同而发生变化,此时就应该将它定义为static类型。而当一个行为或动作执行的过程中需要对象参与,或者说不同对象执行这个动作的结果可能会不同,那么这个方法就应该定义为实例方法,不应该加static关键字。同理,当一个属性在不同的对象中可能会不同时,那这个属性就应该定义为实例变量,也不应该加static关键字。

    访问static变量和方法:带有static的方法和变量,可以使用类名的方式去访问,也可以使用引用的方式去访问,但使用引用的方式去访问,其实本质上也是使用类名的方式去访问的,因为你会发现当这个引用为null时也能去访问static的方法和变量,而不会报空指针异常,所以不推荐使用引用的方式去使用static方法。

    static另一种语法的使用:静态代码块,在类加载的时候就会去执行这个方法,定义和使用示例如下:

     // 语法
     static{
         java语句;
     }
     // 静态代码块在一个类中可以编写多个
     public class StaticTest{
         static{
             System.out.println("--->1");
         }
         
         static{
             System.out.println("--->2");
         }
         
         public static void main(String[] args){
             System.out.println("main method!!!");
         }
     }
     
     // 运行结果为:可以看到静态代码块在main方法之前运行了。
     // --->1
     // --->2
     // main method!!!

    2、final

    final关键字的使用,需要注意以下几点:

    • final是一个关键字,表示最终的、不可变的。
    • final修饰的类无法被继承。
    • final修饰的方法无法被覆盖。
    • final修饰的变量一旦赋值之后,不可重新赋值。

    实例变量如果声明为final变量,那么在声明的同时就需要给它赋值,不然就会报错。因为类的实例变量在调用构造方法之后还没有被赋值的话就会被系统赋予默认值,而final变量是不能重新赋值的,所以如果允许final实例变量声明的时候不赋值,那么这个变量将永远是系统默认的值,这样肯定是不行的,所以语法上就要求final的实例变量必须在声明的同时必须手动赋值或者在构造方法中给它赋值。示例如下:

     public class A{
         // 第一种方式:声明的同时赋值
         final int a = 10;
         // 第二种方式:先声明,然后在构造方法中赋值
         final int b;
         public A(){
             this.b = 20;
         }
         // 注:其实这两种方式都是一种方式,都是在构造方法执行过程中给实例变量赋值的。
     }

    注意,final修饰的引用虽然不能再指向其他对象,但是所指向的对象内部的内存是可以被修改的。

    final修饰的实例变量通常会和static联合使用,被称为常量。

    3、常量

    语法格式:“public static final 类型 常量名 = 值”。final表示不可被修改,static表示无论实例化多少对象都只会保存一份数据在方法区内存中,此时,就算被声明为public也不用担心被别人修改,因为它本身就是final不可被修改的。
    Java规范中,所有常量必须全部使用大写,单词之间使用下划线连接。

    4、访问控制权限修饰符

    对于属性和方法,以下4种都可以使用,但是对于类,只可以使用public和缺省的方式定义,但是无论是类还是属性和方法,这4中的作用范围都是相同的:

    • public:表示公开的,在任何位置都可以访问。
    • protected:在同一个包下,或者在其子类中,那就可以访问。
    • 缺省:只允许在同一个包下的类访问。
    • private:表示私有的,只能在本类中可以访问。
    • 修饰符的作用范围:private < 缺省 < protected < public。
  • 相关阅读:
    两种方法生成随机字符串
    cmd命令总结
    NOI前乱写
    多校模拟9
    字符串 口胡
    HEOI2020游记
    省选模拟104
    省选模拟103
    省选模拟102
    省选模拟101
  • 原文地址:https://www.cnblogs.com/guyuyun/p/12903244.html
Copyright © 2020-2023  润新知