美文网首页
万字长文Java-类和对象解析(超详细)

万字长文Java-类和对象解析(超详细)

作者: Java码农 | 来源:发表于2022-04-14 16:43 被阅读0次

    一、类和对象

    1. 概述

    (1)类(Class)和对象(Object)是面向对象的核心概念。
    (2)类是对一类事物的描述,是抽象的、概念上的定义。
    (3)对象是实际存在的该类事物的每个个体,因而也称为实例(instance)。
    (4)“万事万物皆对象”。

    例子:

    ⭕ 可以理解为:类 = 抽象概念的人;对象 = 实实在在的某个人
    ⭕ 面向对象程序设计的重点是类的设计 。
    ⭕ 类的设计,其实就是类的成员的设计。

    2. 类的成员

    属性、方法、构造器、代码块、内部类

    3. 类的语法格式

    3.1 常见的语法格式:

    修饰符 class 类名 {
          属性声明;
          构造器;
          方法声明; 
    }
    

    注意:
    ⭕ 类的正文要用{ }括起来
    ⭕ 类的访问机制:
    在一个类中的访问机制:类中的方法可以直接访问类中的成员变量。 (例外:static方法访问非static,编译不通过。)
    在不同类中的访问机制:先创建要访问类的对象,再用对象访问类中 定义的成员。

    3.2 例子:

    public class Person{
       private int age ; //声明私有变量 age
       public void showAge(int i) { //声明方法showAge( )
       age = i; 
       } 
    }
    

    4. 对象的创建和使用

    4.1 对象的创建:

    ⭕ 创建对象语法:类名 对象名 = new 类名();

    4.2 对象的使用:

    ⭕ 访问对象成员语法:使用 “对象名.对象成员” 的方式访问对象成员(包括属性和方法)。

    代码演示:

    演示1:
    public class Zoo{
            public static void main(String args[]){        
             Animal xb=new Animal(); //创建对象
             xb.legs=4;//访问属性
             System.out.println(xb.legs);
             xb.eat();//访问方法
             xb.move();//访问方法
           }
    }
       
    class Animal {
          public int legs;
          public void eat(){
           System.out.println(“Eating.”);
          }
          public viod move(){
           System.out.println(“Move.”);
          }
    }
    
    演示2:
    class Person{
       int age;
       void shout(){
          System.out.println(“oh,my god! I am ” + age);
       }
    }
    

    ⭕ 类的定义如上(演示2),当执行Person p1 = new Person();语句,执行完后的内存状态。其中类定义如下:

    ⭕ 如果创建了一个类的多个对象,对于类中定义的属性,每个对象都拥有各自的一套副本,且互不干扰。

    代码演示:

    public class Zoo {
         public static void main(String args[]) {
               Animal xb = new Animal();
               Animal xh = new Animal();
               xb.legs = 4;
               xh.legs = 0;
               System.out.println(xb.legs); // 4
               System.out.println(xh.legs); // 0
               xb.legs = 2;
               System.out.println(xb.legs); // 2
               System.out.println(xh.legs); // 0
          } 
    }
    

    5. 对象的生命周期

    当将new出来的对象赋予null值后,该对象将会被搁置,成为垃圾被回收。

    6. 对象的内存解析

    ⭕ 堆(Heap),此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配,我们将new出来的结构(比如:数组、对象)加载在堆空间中。
    补充:对象的属性(非static的)加载在堆空间中。

    ⭕ 栈(Stack),是指虚拟机栈。虚拟机栈用于存储局部变量 等。局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、char 、 short 、 int 、 float 、 long 、double)、对象引用(reference类型,它不等同于对象本身,是对象在堆内存的首地址)。 方法执行完,自动释放。

    ⭕ 方法区(Method Area),用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

    ⭕ 编译完源程序以后,会生成一个或多个字节码文件。我们使用JVM中的类加载器和解释器对生成的字节码文件进行解释运行,意味着,需要将字节码文件对应的类加载到内存中,涉及到内存解析。

    普通对象的内存解析例子:

    对象数组的内存解析:
    注意:引用类型的变量,只可能存储两类值:null 或 地址值(含变量的类型)

    7. 匿名对象的创建

    (1)概念

    我们可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象。如:new Person().shout();

    (2)理解

    我们创建的对象,没有显式的赋给一个变量名。即为匿名对象。

    (3)特征

    匿名对象只能调用一次。

    (4)使用情况

    ⭕ 如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象。
    ⭕ 我们经常将匿名对象作为实参传递给一个方法调用。

    (5)代码演示

    public class InstanceTest {
        public static void main(String[] args) {    
            //匿名对象
    //      new Phone().sendEmail();
    //      new Phone().playGame();
            
            new Phone().price = 1999;
            new Phone().showPrice();//0.0
            
            //**********************************
            PhoneMall mall = new PhoneMall();
    //      mall.show(p);
            //匿名对象的使用
            mall.show(new Phone());     
        }
    }
    
    
    class PhoneMall{    
        public void show(Phone phone){
            phone.sendEmail();
            phone.playGame();
        }   
    }
    
    
    class Phone{
        double price;//价格   
        public void sendEmail(){
            System.out.println("发送邮件");
        }   
        public void playGame(){
            System.out.println("玩游戏");
        }   
        public void showPrice(){
            System.out.println("手机价格为:" + price);
        }   
    }
    

    二、类的成员之一:属性(field)

    1. 语法格式

    修饰符 数据类型 属性名 = 初始化值 ;

    (1)修饰符

    常用的权限修饰符有:private、缺省(xing[三声])、protected、public
    其它修饰符:static、final

    (2)数据类型

    任何基本数据类型(如int、Boolean) 或 任何引用数据类型。

    (3)属性名

    属于标识符,符合命名规则和规范即可。

    //举例:
        public class Person{
           private int age; //声明private变量 age
           public String name = “Lila”; //声明public变量 name
    }
    

    2. 成员变量(属性)vs 局部变量

    2.1 定义

    成员变量:又叫属性,在方法体、代码块、构造器 外,类 体内声明的变量。
    局部变量:在方法体、代码块、构造器 内部声明的变量。

    2.2 相同点

    • 1.1 定义变量的格式:数据类型 变量名 = 变量值;`
    • 1.2 先声明,后使用
    • 1.3 变量都有其对应的作用域,都有生命周期

    2.3 不同点

    (1)在类中声明的位置的不同

    ⭕ 成员变量:直接定义在类的一对{}内
    ⭕ 局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量。

    代码演示:

    public class UserTest { 
        public static void main(String[] args) {
            User u1 = new User();
            System.out.println(u1.name);
            System.out.println(u1.age);
            System.out.println(u1.isMale);      
            u1.talk("韩语");      
            u1.eat();       
        }
    }
    
    
    class User{
        //属性(或成员变量)
        String name;
        public int age;
        boolean isMale; 
        public void talk(String language){//language:形参,也是局部变量
            System.out.println("我们使用" + language + "进行交流");     
        }   
        public void eat(){
            String food = "烙饼";//局部变量
            System.out.println("北方人喜欢吃:" + food);
        }   
    }
    

    (2) 关于权限修饰符的不同

    ⭕ 成员变量:可以在声明属性时,指明其权限,使用权限修饰符。
    常用的权限修饰符:private、public、缺省、protected
    ⭕ 局部变量:不可以使用权限修饰符。

    (3) 默认初始化值的情况

    ● 成员变量:类的属性,根据其类型,都有默认初始化值。

    ● 局部变量:没有默认初始化值。
    意味着,局部变量除形参外,均需显式初始化。特别地:形参在调用时,我们再赋值即可。

    (4) 在内存中加载的位置不同

    ⭕ 成员变量:加载到堆空间中(非static)
    ⭕ 局部变量:加载到栈空间

    成员变量vs局部变量的内存位置:

    总结:

    3. 属性赋值的先后顺序

    ⭕赋值的位置:
    ① 默认初始化;
    ② 显式初始化/多个初始化块依次被执行;
    ③ 构造器中初始化;
    ④ 通过"对象.方法" 或 "对象.属性"的方式赋值;
    ⭕赋值的先后顺序:
    ① - ②(同级别下按代码的先后顺序执行) - ③ - ④

    4. 注意

    ⭕ 在类的内部,变量定义的先后顺序决定了初始化的顺序。即使成员变量定义散布于方法定义之 间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。

    代码演示:

    class Window {
         Window(int marker) { print("Window(" + marker + ")"); }
    class House {
         Window wl = new Window(1); // Before constructor
         House(){
         // Show that we're in the constructor: printCHouseO");
         w3 = new Window(33): // Reinitialize w3
         }
         Window w2 = new Window(2); // After constructor
         void f() { prlnt("f()"); )
         Window w3 = new Window(3): // At end
    }
    public class OrderOfInitialization {
         public static void main(String[] args) {
         House h = new HouseO ;
         h.f(); // Shows that construction 1s done
         }
    } 
    /* Output:
    Window(l)
    Window(2)
    W1ndow(3)
    House()
    Window(33)
    f()
    */
    /*解析:
    在House类中,故意把几个Window对象的定义散布到各处,以证明它们全都会在调用构造器或其他方法之前得到初始化。
    此外,w3在构造器内再次被初始化。由输出可见,w3这个引用会被初始化两次:一次在调用构造器前,一次在调用期间
    (第一次引用的对象将被丢弃,并作为垃圾回收)。
    */
    

    三、类的成员之二:方法(method)

    1.初步了解“方法”

    1.1 定义

    方法:描述类应该具有的功能。

    1.2 语法格式

    权限修饰符 其它修饰符( static / final / abstract )返回值类型 方法名(形参列表){undefined
    方法体
    }

    (1) 权限修饰符

    Java规定的4种权限修饰符:private、public、缺省、protected

    (2) 返回值类型

    ⭕ 分为“有返回值”与“无返回值”。
    ⭕如果方法有返回值,则必须在方法声明时,指定返回值的类型。同时,方法中,需要使用return关键字来返回指定类型的变量或常量:“return数据”。
    ⭕如果方法没有返回值,则方法声明时,使用void来表示。通常,没有返回值的方法中,就可以不需要使用return,但是,如果使用的话,只能“return;”来表示结束此方法的意思。

    代码演示:

     public void test1(){}//无返回值
     public void test2(){ return;}//无返回值
     public int test3(){ return 0;}//有返回值(以int为例)
    

    ⭕“return”关键字的使用
    ① 使用范围:使用在方法体中。
    ② 作用:
    ● 结束方法。
    ● 针对于有返回值类型的方法,使用"return 数据"方法返回所要的数据。
    ③ 注意:return关键字后面不可以声明执行语句。

    (3)方法名

    属于标识符,遵循标识符的规则和规范,“见名知意”

    (4) 形参列表

    (1)方法中的形参列表可以声明0个,1个,或多个形参。
    (2)格式:数据类型1 形参1,数据类型2 形参2,… 。

    代码演示:

    public void test1(){}
    public void test2(int i1){}
    public void test3(int i1,int i2){}
    ...
    

    (5) 方法体

    方法功能的体现。

    方法的分类:按照是否有形参及返回值

    1.3 举例

      public void eat(){}
      public void sleep(int hour){}
      public String getName(){}
      public String getNation(String nation){}
      Math类:sqrt()\random() \...
      Scanner类:nextXxx() ...
      Arrays类:sort() \ binarySearch() \ toString() \ equals() \ ...
    

    1.4 注意

    ⭕ 方法的使用中,可以调用当前类的属性或方法。
    ⭕ 特殊的:方法A中又调用了方法A:递归方法。
    ⭕ 方法中,不可以定义方法。
    ⭕ 方法被调用一次,就会执行一次
    ⭕ 没有具体返回值的情况,返回值类型用关键字void表示,那么方法体中可以不必使用return语句。如果使用,仅用来结束方法。
    ⭕ 定义方法时,方法的结果应该返回给调用者,交由调用者处理。
    ⭕ 方法中只能调用方法或属性,不可以在方法内部定义方法。

    1.5 方法参数的值传递机制

    Java里方法的参数传递方式只有一种:值传递。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。

    ⭕形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参

    代码演示:

    public class ValueTransferTest1 {
        public static void main(String[] args) {        
            int m = 10;
            int n = 20;     
            System.out.println("m = " + m + ", n = " + n);//m = 10, n = 20  
            ValueTransferTest1 test = new ValueTransferTest1();
            //交换的错误操作:
            test.swap(m, n);    
            
            //交换的正确操作:
    //      int temp = m ;
    //      m = n;
    //      n = temp;   
    
            System.out.println("m = " + m + ", n = " + n);// m = 10, n = 20
        }
            
        public void swap(int m,int n){
            int temp = m ;
            m = n;
            n = temp;
        }
    }
    

    ⭕形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参

    代码演示:

    public class ValueTransferTest2 {   
        public static void main(String[] args) {        
            Data data = new Data();     
            data.m = 10;
            data.n = 20;
            
            System.out.println("m = " + data.m + ", n = " + data.n);
            
            //交换m和n的值
    //      int temp = data.m;
    //      data.m = data.n;
    //      data.n = temp;
            
            ValueTransferTest2 test = new ValueTransferTest2();
            test.swap(data);                
            System.out.println("m = " + data.m + ", n = " + data.n);        
        }   
        public void swap(Data data){
            int temp = data.m;
            data.m = data.n;
            data.n = temp;
        }
    }
    
    class Data{ 
        int m;
        int n;  
    }
    

    参数类型为引用数据类型的内存解析:

    2.方法的重载(overload)

    2.1 定义

    ⭕ 在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。

    ⭕ 口诀:“两同一不同”
    “两同”: 同一个类、相同方法名;
    “一不同”:形参列表不同(参数个数不同,参数类型不同)

    代码演示:

    public void test1(int i1){}
    public void test1(int i1,int i2){}
    public void test1(String i1){}
    //以上三个test1()相互构成重载
    public void test2(int i,String s){}
    public void test2(String s,int i){}
    以上两个test2()相互构成重载
    

    2.2 举例

    Arrays类中重载的sort() / binarySearch()

    2.3 如何判断是否是重载?

    与方法的“权限修饰符”、“返回值类型”、“形参变量名”、“方法体”无关,只看“方法名”与“形参列表”。

    2.4 如何确定某一个指定的方法?

    通过“方法名 ---> 参数列表”
    注:形参列表中参数顺序的不同也足以区分两个方法,不过,一般情况下别这么做,因为这会使代码难以维护。

    代码演示:

    public class Test1 {
        static void f(String s,int i){
            System.out.print("String: "+s+", int: "+i);
        }
        static void f(int i,String s){
            System.out.print("int: "+i+", String: "+s);
        }
        public static void main(String args[]){
            f("String first",11);
            System.out.println();
            f(99,"Int first");
        }
    }
    //output:
    //String: String first, int: 11
    //int: 99, String: Int first
    //上例中两个print()方法虽然声明了相同的参数,但顺序不同,因此得以区分
    

    2.5 关于“可变个数形参”的方法

    (1)为jdk 5.0新增的内容。
    ⭕JDK 5.0以前:采用数组形参来定义方法,传入多个同一类型变量
    ⭕JDK5.0之后:采用可变个数形参来定义方法,传入多个同一类型变量

    代码演示:

           public static void test(int a ,String[] books);//JDK 5.0以前
           public static void test(int a ,String…books);//JDK5.0之后
    

    (2)可变个数形参的格式:(数据类型 … 变量名)

    代码演示:

        public void show(String ... strs){ }
    

    (3)当调用可变个数形参的方法时,传入的参数个数可以是:0个,1个,2个…。
    (4)可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载。
    (5)可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载,换句话说,二者不能共存。

    代码演示:

        public void show(String ... strs){  }
    //  public void show(String[] strs){ } 不能与上一个方法同时存在
    

    (6)可变个数形参在方法的形参中,必须声明在末尾。

    代码演示:

    //  public void show(String ...strs,int i){ }
    // 报错:The variable argument type String of the method show must be the last parameter
    

    (7)可变个数形参在方法的形参中,最多只能声明一个可变形参。

    代码演示:

    // public void show(String ...str1,String ...str2){ }
    

    2.6 注意

    (1)方法重载且形参类型为基本数据类型时时,
    ⭕ 如果传入的数据类型(实际参数类型)小于方法中的声明的形式参数类型,实际数据类型会被自动提升。char型略有不同,如果无法找到恰好接受char参数的的方法,就会把char直接提升至int型。
    ⭕ 如果传入的实际参数类型较大,就得通过类型转换来执行窄化转换,否则编译器将会报错。

    3.方法的重写(override / overwrite)

    3.1 定义

    在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。

    代码演示:

    public class Person {
       String name;
       int age;
       int sex;
       public void study(){
        System.out.println("studying");
       }
    }
    public class Student extends Person {
       //对父类中的study()进行重写
       public void study(){
        System.out.println("骑自行车");
       }
    }
    

    3.2 应用

    重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。

    3.3 使用

    语法格式:

    权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{
            //方法体
    }
    

    约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法

    (1)方法名与形参列表

    子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表。

    (2)权限修饰符

    子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符。
    特殊情况:子类不能重写父类中声明为private权限的方法。

    (3)返回值类型

    ⭕ 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void。

    ⭕ 子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型,即父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类。

    ⭕ 父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)。

    (4)异常类型

    ⭕ 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型 。

    ⭕ 重写方法不能抛出比被重写方法范围更大的异常类型。在多态的情况下, 对methodA()方法的调用-异常的捕获按父类声明的异常处理。

    代码演示:

    public class A {
          public void methodA() throws IOException {
            ……
          } 
    }
    public class B1 extends A {
          public void methodA() throws FileNotFoundException {
          ……
          } 
    }
    public class B2 extends A {
          public void methodA() throws Exception { //报错
          ……
          } 
    }
    

    (5)static/非static

    子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。

    四、类的成员之三:构造器

    1. 作用

    ⭕ 创建对象
    ⭕ 初始化对象的信息

    代码演示:

        Order o = new Order(); 
        Person p = new Person(“Peter”,15);
    

    2. 特征

    ⭕ 具有与类相同的名称,不声明返回值类型。(与声明为void不同)

    ⭕ 不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值。

    ⭕ 如果没有显式地定义类的构造器,则系统会默认提供一个空参的构造器,即“无参构造器”。

    ⭕ 定义构造器的格式:权限修饰符 类名(形参列表){ 初始化语句; }。

    代码演示:

    class Person{
        //构造器
        public Person(){
            System.out.println("Person().....");
        }
        
        public Person(String n){
            name = n;
            
        }
        
        public Person(String n,int a){
            name = n;
            age = a;
        }
    }   
    

    ⭕ 一旦显式地定义了类的构造器之后,系统就不再提供默认的空参构造器。

    ⭕ 一个类中,至少会有一个构造器。

    ⭕ 构造器是一种特殊类型的方法,因为它没有返回值。这与返回值为空(void)明显不同,对于空返回值,尽管方法本身不会自动返回什么,但仍可选择让它返回别的东西。
    构造器则不会返回任何东西(new表达式确实返回了对新建对象的引用,但构造器本身并没有任何返回值)。

    3.构造器的重载

    ⭕ 一个类中定义的多个构造器,彼此构成重载,对象的创建更加灵活,方便创建各种不同的对象。
    ⭕ 构造器重载,参数列表必须不同。

    代码演示:

         //构造器重载举例:
         public class Person{
              public Person(String name, int age, Date d) {this(name,age);…}
              public Person(String name, int age) {…}
              public Person(String name, Date d) {…}
              public Person(){…}
    } 
    

    五、类的成员之四:代码块

    1. 作用

    对Java类或对象进行初始化。

    2. 分类

    一个类中代码块若有修饰符,则只能被static修饰,称为静态代码块 (static block),没有使用static修饰的,为非静态代码块。

    2.1 静态代码块:用static 修饰的代码块

    ⭕ 可以有输出语句。
    ⭕ 可以对类的属性、类的声明进行初始化操作,static代码块通常用于初始化static的属性。

    代码演示:

    class Person {
        public static int total;
          static {
            total = 100;//为total赋初值
          }
          …… //其它属性或方法声明
    }
    

    ⭕ 不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
    ⭕ 如果一个类中定义了多个静态代码块,那么按照从上到下的顺序依次执行。
    ⭕ 静态代码块的执行要先于非静态代码块。
    ⭕ 静态代码块随着类的加载而执行,且只执行一次。

    2.2 非静态代码块:没有static修饰的代码块

    ⭕ 可以有输出语句。
    ⭕ 随着对象的创建而执行。
    ⭕ 可以在创建对象时,对类的属性、类的声明进行初始化操作。
    ⭕ 除了调用非静态的结构外,还可以调用静态的变量或方法。
    ⭕ 如果一个类中定义了多个非静态代码块,那么按照从上到下的顺序依次执行。
    ⭕ 每次创建对象的时候,都会执行一次。且先于构造器执行。

    代码演示:

    class Mug {
        Mug(int marker) {
            System.out.print("Mug(" + marker + ")");
        }
        void f(int marker) {
            System.out.print ("f(" + marker + ")");
        } 
    }
    public class Mugs {
        Mug mugl;
        Mug mug2;
        {
            mugl = new Mug(l);
            mug2 = new Mug(2);
            System.out.print("mugl & mug2 Initialized");
        }
        Mugs() {
            System.out.print("Mugs()");
        }
        
        Mugs(int i) {
            System.out.print("Mugs(int)");
        }
        public static void main(String[] args) {
            System.out.print("Inside main()");
            new Mugs();
            System.out.print("new Mugs() completed");
            new Mugs(l);
            System.out.print("new Mugs(l) completed");
        }
    } 
    /* Output:
    Inside main()
    Mug(l)
    Mug(2)
    mugl & mug2 initialized
    Mugs()
    new MugsO completed
    Mug(l)
    Mug(2)
    mugl & mug2 initialized
    Mugs(int)
    new Mugs(l) completed
    */
    

    3. 举例

    代码演示:

    public class BlockTest {
        public static void main(String[] args) {        
            String desc = Person.desc;
            //hello,static block-2
            //hello,static block-1
            //我是一个快乐的人!
            System.out.println(desc);//我是一个爱学习的人                
            Person p1 = new Person();
            //hello, block - 2
            //hello, block - 1
            //吃饭
            //我是一个快乐的人!
            Person p2 = new Person();
            //hello, block - 2
            //hello, block - 1
            //吃饭
            //我是一个快乐的人!
            System.out.println(p1.age);//1      
            Person.info();//我是一个快乐的人!
        }
    }
    
    
    class Person{
        //属性
        String name;    
        int age;
        static String desc = "我是一个人";   
        //构造器
        public Person(){        
        }
        public Person(String name,int age){
            this.name = name;
            this.age = age;
        }   
        //非static的代码块
        {
            System.out.println("hello, block - 2");
        }
        {
            System.out.println("hello, block - 1");
            //调用非静态结构
            age = 1;
            eat();
            //调用静态结构
            desc = "我是一个爱学习的人1";
            info();
        }
        //static的代码块
        static{
            System.out.println("hello,static block-2");
        }
        static{
            System.out.println("hello,static block-1");
            //调用静态结构
            desc = "我是一个爱学习的人";
            info();
            //不可以调用非静态结构
    //      eat();
    //      name = "Tom";
        }   
        //方法
        public void eat(){
            System.out.println("吃饭");
        }
        @Override
        public String toString() {
            return "Person [name=" + name + ", age=" + age + "]";
        }
        public static void info(){
            System.out.println("我是一个快乐的人!");
        }   
    }
    

    六、类的成员之五:内部类

    1. 引入

    当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。

    2. 理解

    ⭕ 在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
    ⭕ Inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称。

    3. 分类

    3.1 成员内部类(static成员内部类和非static成员内部类)
    ⭕成员内部类作为类的成员的角色:

    ● 可以被4种不同的权限修饰。
    ● 可以调用外部类的结构 。
    ● Inner class 可以声明为static的,但此时就不能再使用外层类的非static的成员变量。

    ⭕ 成员内部类作为类的角色:

    ● 可以在内部定义属性、方法、构造器等结构 。
    ● 可以声明为abstract类 ,因此可以被其它的内部类继承 。
    ● 可以声明为final的。
    ● 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)。

    ⭕注意:

    ● 非static的成员内部类中的成员不能声明为static的,只有在外部类或static的成员内部类中才可声明static成员。
    ● 外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式。
    ● 成员内部类可以直接使用外部类的所有成员,包括私有的数据。
    ● 当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的。

    ⭕ 举例:

    代码演示:

    //举例一:
    class Outer {
        private int s;
        public class Inner {
          public void mb() {
            s = 100;
            System.out.println("在内部类Inner中s=" + s);
          } 
        }
        public void ma() {
           Inner i = new Inner();
           i.mb();
         } 
        }
    public class InnerTest {
        public static void main(String args[]) {
           Outer o = new Outer();
           o.ma();
        } 
    }
    
    //举例二:
    
    ```java
    public class Outer {
        private int s = 111;
        public class Inner {
            private int s = 222;
            public void mb(int s) {
                System.out.println(s); // 局部变量s
                System.out.println(this.s); // 内部类对象的属性s
                System.out.println(Outer.this.s); // 外部类对象属性s 
            } 
        }
        public static void main (String args[]){
            Outer a = new Outer();
            Outer.Inner b = a.new Inner();
            b.mb(333);
        }
    }
    

    3.2 局部内部类(不谈修饰符)

    (1)语法格式

    class 外部类{
            方法(){
                class 局部内部类{ } 
            }
            {
                class 局部内部类{ } 
            }
    } 
    

    (2)使用

    ⭕ 只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类。
    ⭕ 但是它的对象可以通过外部方法的返回值返回使用,返回值类型只能是局部内部类的父类或父接口类型。

    代码演示:

    //返回一个实现了Comparable接口的类的对象
        public Comparable getComparable(){
            
            //创建一个实现了Comparable接口的类:局部内部类
            //方式一:
    //      class MyComparable implements Comparable{
    //
    //          @Override
    //          public int compareTo(Object o) {
    //              return 0;
    //          }
    //          
    //      }
    //      
    //      return new MyComparable();      
            //方式二:
            return new Comparable(){
                @Override
                public int compareTo(Object o) {
                    return 0;
                }           
            };      
        }
    

    ⭕ 在局部内部类的方法中(比如:show)如果调用局部内部类所声明的方法(比如:method)中的局部变量(比如:num)的话, 要求此局部变量声明为final的。
    jdk 7及之前版本:要求此局部变量显式的声明为final的。
    jdk 8及之后的版本:可以省略final的声明。

    代码演示:

    public void method(){
            //局部变量
            int num = 10;       
            class AA{                       
                public void show(){
    //              num =20;
                    System.out.println(num);                
                }                       
            }               
        }   
    

    (3)特点

    ⭕ 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号,以及数字编号。

    ⭕ 局部内部类可以使用外部类的成员,包括私有的。

    ⭕ 局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局部变量的声明周期不同所致。

    ⭕局部内部类和局部变量地位类似,不能使用public,protected,缺省,private。

    ⭕局部内部类不能使用static修饰,因此也不能包含静态成员。

    3.3 匿名内部类

    (1) 语法格式

     new 父类构造器(实参列表)|实现接口(){
    //匿名内部类的类体部分
    } 
    

    (2)特点

    ⭕ 匿名内部类必须继承父类或实现接口。
    ⭕ 匿名内部类只能有一个对象。
    ⭕ 匿名内部类对象只能使用多态形式引用。
    ⭕ 匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。
    ⭕ 一个匿名内部类一定是在new的后面,用其隐含实现一个接口或
    实现一个类。

    代码演示:

    interface A{
       public abstract void fun1();
    }
    public class Outer{
        public static void main(String[] args) {
          new Outer().callInner(new A(){
    //接口是不能new但此处比较特殊是子类对象实现接口,只不过没有为对象取名
            public void fun1() {
               System.out.println(“implement for fun1");
            }
          );// 两步写成一步了
          }
          public void callInner(A a) {
            a.fun1();
          }
    }
    

    4. 使用

    代码演示:

    public class InnerClassTest {
        public static void main(String[] args) {        
            //创建Dog实例(静态的成员内部类):
            Person.Dog dog = new Person.Dog();
            dog.show();
            //创建Bird实例(非静态的成员内部类):
    //      Person.Bird bird = new Person.Bird();//错误的
            Person p = new Person();
            Person.Bird bird = p.new Bird();
            bird.sing();        
            System.out.println();       
            bird.display("黄鹂");     
        }
    }
    
    class Person{
        String name = "小明";
        int age;    
        public void eat(){
            System.out.println("人:吃饭");
        }       
        //静态成员内部类
        static class Dog{
            String name;
            int age;        
            public void show(){
                System.out.println("卡拉是条狗");
    //          eat();
            }       
        }
        //非静态成员内部类
        class Bird{
            String name = "杜鹃";     
            public Bird(){          
            }       
            public void sing(){
                System.out.println("我是一只小小鸟");
                Person.this.eat();//调用外部类的非静态属性
                eat();
                System.out.println(age);
            }       
            public void display(String name){
                System.out.println(name);//方法的形参
                System.out.println(this.name);//内部类的属性
                System.out.println(Person.this.name);//外部类的属性
            }
        }       
        public void method(){
            //局部内部类
            class AA{
                
            }
        }   
        {
            //局部内部类
            class BB{           
            }
        }
        
        public Person(){
            //局部内部类
            class CC{           
            }
        }           
    }
    

    原文链接:https://blog.csdn.net/weixin_52533007/article/details/122583224

    相关文章

      网友评论

          本文标题:万字长文Java-类和对象解析(超详细)

          本文链接:https://www.haomeiwen.com/subject/naitertx.html