学习数组与面向对象编程

学习数组与面向对象编程

数组学习

数组的声明与创建

必须先声明数组引用变量,才能在程序中使用数组。下面是声明数组引用变量的语法:

dataType[] arrayRefVar;//首选方法
//或
dataType arrayRefVar[];//第二种方法

使用new操作符来创建数组并将地址赋值给已经声明过的数组引用变量的,语法如下:

arrayRefVar=new dataType[arraySize];

也可直接同时完成数组的声明与创建:

dataType[] arrayRefVar=new dataType[arraySize];//第一种方法,或者
dataType arrayRefVar[]=new dataType[arraySize];//第二种方法

采用arrayRefVar.length来获取数组长度。

内存的简单分析

Java程序运行时内存可简单分为三大核心分区:虚拟机栈(JVM Stack)、堆(Heap)以及方法区(Method Area)。

堆——所有对象的“大仓库”

JVM管理的最大一块内存,所有new出来的对象、数组的本体都存在这里。线程共享,多个线程可以同时访问堆里的同一个对象。同时也是垃圾回收的主战场,当一个对象没有任何栈里的引用变量指向它时,就会被标记为垃圾,等待回收。容易出现OutOfMemoryError

虚拟机栈——方法的临时工作台

简称栈,线程私有,为每个Java方法执行服务,每个方法被调用时,都会在栈里创建一个栈帧,里面存:

  • 方法里的局部变量,包括基本变量类型(会包括这个基本类型的具体数值)以及引用对象的变量(会存放这个引用在堆里面的具体地址)

  • 操作数栈(执行加减乘除等运算的临时空间)

  • 方法出口地址(方法执行完之后要回到哪里)

    方法执行结束,对应的栈帧就会销毁,生命周期和线程绑定。递归太深时,栈帧太多会报StackOverflowError(栈溢出)。

方法区——类的“档案库”

线程共享。存放class的蓝图信息:写的类编译成.class文件之后,类的结构(方法、字段、继承关系)都会被加载到方法区。同时也存static静态变量,static变量属于类本身,不属于某个对象,所以直接存在方法区里,所有对象共享同一份。方法区也包含运行时常量池。

数组的基本特点

  • 数组中的元素可以是任何数据类型,包括基本类型和引用类型。
  • 数组本身就是对象,数组中的每个元素相当于该对象的成员变量。Java中对象是在堆中的,因此数组无论保存什么类型的元素,数组对象本身是在堆中的。

数组的初始化

  • 静态或动态初始化。例:

    int[] a={1,2,3};
    Man[] men={new Man(1,1),new Man(2,2)};
    a[0]=4;
    
  • 数组的默认初始化:数组的元素相当于类的实例变量,因此数组一经分配空间,每个元素都按照实例变量同样的方式被默认初始化。

数组的使用

  1. Each遍历:语法如下:

    for(dataType element:array){
        ...
    } 
    
  2. 数组可作方法的参数,也可作为方法的返回值。

多维数组

以二维数组为例,其声明创建的语法为:

dataType[][] array=new dataType[][]
//或
dataType array[][]=new dataType[][]

初始化举例:

int array[][]={{1,2},{3,4},{5,6}};

array[0]、array[1]等同样也可视为一个一维数组,具有一维数组的性质。

Arrays类的介绍

一个关于数组的工具类。由于数组对象本身并没有什么方法可以供我们调用,但API中提供了一个工具类Arrays,从而可以对数组对象进行一些基本的操作。Arrays类中的方法都是static修饰的静态方法,在使用时可以直接使用类名进行调用,不必使用对象来调用

面向对象编程

面向对象编程(Object-Oriented Programming, OOP)的本质就是以类的方式组织代码,以对象的组织(封装)数据

Java和C语言一样方法/函数都只支持值传递而不支持引用传递,但同样也可以借助指针间接修改外部变量,但这并不属于引用传递。

属性和方法构成一个类。在写类的非静态方法的时候,可以调用this.属性变量名来表示当前对象的属性变量。

构造器

使用new关键字创建对象时,本质上是在使用类中的构造器,也被称为构造方法。构造器的作用是为了初始化所生成的对象的属性变量。它的语法有两个特点:

  1. 构造器/构造方法的名称必须和所属类的名字相同
  2. 必须没有返回类型,也不能写void

编写代码时如果不写构造器,则默认隐式生成一个无参构造器:

public ClassName(){}

此时如果new一个对象ClassName object=new ClassName()的话,只会进行默认的初始化赋值。

如果想要在new对象的时候直接对对象的属性进行初始化赋值ClassName object=new ClassName(data1,data2,...)则可以在类中写入有参构造器(快捷键alt+insert可以快速生成构造器):

public ClassName(dataType feature1, dataTtpe feature2,...){
    this.feature1=feature1;
    this.feature2=feature2;
    ...
}

需要注意的是,一旦写了有参构造,那么无参构造器就不能够忽略不写。

代码块

类中的匿名代码块(也叫构造代码块)和构造方法类似,都是创建一个新的对象时会优先执行的代码,匿名代码块的执行顺序甚至在构造方法之前。语法如下:

{
    ...
}

静态代码块即用static修饰的匿名代码块,并且只执行一次,随着类的调用即被执行出来,此后再使用该类创建对象则不会再执行该代码块,因此静态代码块执行的顺序会在匿名代码块之前,语法如下:

static {
    ...
}

封装

通常,应该禁止直接访问一个对象中数据的实际表示,而应该通过对象的能被公共使用过的方法即操作接口来访问。

当修饰词中含有private时,外部程序不能直接访问该类对象的该属性或方法。一般而言,会在类中加入一个get/set方法方便外部访问(快捷键alt+insert可以快速生成getter/setter):

public dataType getData(){
    return this.feature;
}//get方法

public void setData(dataType data){
    this.feature=data;
}//set方法

继承

子类(派生类)具备父类(基类)的所有特性即为继承。Java中类只有单继承没有多继承,即每个子类都只能有且只有一个父类。两个类之间的继承关系可以靠extends来实现:

public class SonClass extends FatherClass{...}

当new一个子类对象的时候,如果不先写调用所属父类的构造器的代码,则会先默认调用所属父类的无参构造,再调用所属子类的构造器。

需要注意的是,父类对象中的private私有的东西子类对象是不允许调用的。

通过快捷键Ctrl+h可以展开继承树。需要注意的是,Java中所有的类都默认直接或间接继承于Object类,即所有的类中,Object类是最高级的父类。

super详解

在子类的方法中,使用super.即可调用所属父类的属性变量与方法,使用this.即可调用当前类的属性和方法。

在构造器中,使用super()super(...)即可调用所属父类的无参构造和有参构造,且这句代码必须放在当前构造器的第一行。同时,使用this()this(...)也可以调用当前类的无参构造和有参构造,作用是省略重复的代码,直接复用已经写过的构造器,再在此基础上加上本构造器的额外的语句(本构造器不能够调用本构造器),因此调用别的构造器这段代码必须放在本构造器的代码第一行。因此,在一个构造器中不能同时调用父类构造器和本类的其他构造器。

方法重写

由于父类的方法不能满足子类的需求,因此子类可以重写(override)父类的方法。重写父类的方法时,要求方法名称以及参数列表都必须和需要被重写的父类的方法完全相同,同时,修饰符所限定的范围可以扩大但不能缩小(限定范围的修饰符从大到小依次为:public>protected>default>private,需要注意的是private为父类私有的,子类无法调用或者重写)。通过快捷键alt+insert可以快速重写方法。

多态

多态指同一个方法调用,不同的子类对象,会执行出不一样的结果。多态存在的条件有:

  1. 有继承关系
  2. 子类重写父类方法
  3. 父类引用指向子类对象

执行过程:当执行new SonClass()时,实际上创建了子类对象,并且继承了父类的属性和方法,而且还完成了对父类方法的重写。编译过程:当引用该对象时,要根据引用的类型来决定最终该对象的属性和方法的访问权限。当引用类型为父类时,该对象允许被访问的为父类含有的属性以及父类中包含的同名的方法。当访问越界时就会出现编译报错。

static方法(静态方法内部不允许直接调用本类的非静态的属性变量)属于类,不属于实例,因此也不能重写。用对象来调用静态方法,是Java的语法糖,本质上还是类调用,这样子类对象也可以“继承”static方法。final方法可以被继承,但不能被子类重写。

修饰对象 final的含义
变量 变量值不能被修改(常量)
方法 方法不能被子类重写
类不能被继承

instanceof关键字和类型转换

instanceof作用是判断左边的对象是不是直接或间接属于右边的类,返回值是true或false。需要注意的是,在编译过程中,会判断左边的对象的引用的类型是否与右边的类有着直接或间接的父子关系,如果没有则是无法比较的,会出现编译报错。而在执行过程中,会判断左边引用实际所指的对象的类型是否直接或间接属于右边类的子类。

类型转换实际上是改变引用的类型,类型转换要求转换之后的类型不能比引用实际所指的对象的类型还要低级。从高级低级转的时候,需要进行强制转换。

导入包

  • 导入类:import 包名.类名
  • 导入类的静态变量/静态方法:import static 包名.类名.静态成员

抽象类和抽象方法

abstract修饰符可以用来方法也可以用来修饰类。如果修饰方法,则该方法为抽象方法,如果修饰类,那么该类就是抽象类。抽象类存在的作用是将多个子类共同的东西提取出来,节省代码提高开发效率。

抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类。

抽象类不能使用new关键字来创造对象,它是用来让子类继承的。抽象方法,只有方法的声明,没有方法的实现(即没有方法体),它是用来让子类实现即重写的。子类继承抽象类,那么就必须实现抽象类没有实现的抽象方法,否则该子类也要声明为抽象类。抽象类中抽象方法的声明举例:

public abstract void Method();

抽象类中同样也有构造器,也可以有匿名代码块和静态代码块。

接口

接口相当于是抽象类的更加抽象化,抽象类里面具体实现和规范(抽象方法)都有,但接口只有规范,自己无法写方法。接口中没有构造方法、匿名代码块或者静态代码块。

接口中所有声明的方法默认带有修饰符public abstract,所有定义的变量都带有修饰符public static final,即接口只能定义静态常量。

Java中可以利用接口实现多继承,语法如下:

public class ClassName implements Interface1, Interface2,...{...}

类中必须实现接口中声明的方法。

内部类

成员内部类

顾名思义,即一个类中间含有另外的类:

public class OuterClass{
    ...
    public class InnerClass{
        ...
    }    
}

通过外部类的对象来实例化内部类:

OuterClass outer=new OuterClass();
OuterClass.InnerClass inner=outer.new InnerClass();

好处是内部类可以获得外部类的私有属性或私有方法。

静态内部类

static不能被允许修饰单独的外部类(顶级类),只能用来修饰内部类,静态内部类属于外部类本身,而不属于外部类的对象,创建内部类对象可以使用如下语法:

OuterClass.InnerClass inner=new OuterClass.InnerClass();

局部内部类

即写在方法、构造器、代码块内部的类,它和方法里的局部变量是一个级别,只在当前方法内部生效

匿名内部类

首先介绍一下匿名对象,当要调用别的类的对象的属性或方法时可以直接创建匿名对象来调用new ClassName.featurenew ClassName.method(),匿名对象是一次性使用,不能重复使用。

匿名内部类的核心是临时创建一个没有名字的子类/实现类,必须用{}来写子类的代码(比如重写方法),以两段代码为例:

场景1:接口引用指向普通实现类对象

interface Fruit{void eat();}

class Apple implements Fruit{
    @Override
    public void eat(){
        System.out.println("咬一口苹果");
    }
}

public class Test{
    public static void main(String[] args){
        Fruit f=new Apple();//f为引用,引用类型为借口,引用所指向的对象是实现了Fruit中方法的类(即Apple类)的对象
        f,eat();//输出结果:咬一口苹果
    }
}

场景2:接口引用指向匿名内部类对象

interface Fuit{void eat();}

public class Test{
    public static void main(String[] args){
        Fruit f=new Fruit(){
            @Override
            public void eat(){
                System.out.println("吃匿名水果");
            }
        };//这里的new Fruit(){...}不是实例化接口,而是创建了一个没有名字的类,在这个类当中实现了接口内的方法,并且理解创建了一个这个类的对象。
        f,eat();//输出结果:吃匿名水果
    }
}

场景2中即实现了创建一个匿名内部类对象。

posted @ 2026-05-28 15:08  去银行整点金条  阅读(7)  评论(0)    收藏  举报