Java笔记:Java面向对象

2020-05-17 16:00:42来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

Java笔记:Java面向对象

一、方法

1、概述

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

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

2、定义方法

语法如下:

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

 

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

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

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

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

形式参数列表:

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

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

3、static方法调用

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

4、参数值的传递

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

5、方法重载(overload)

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

重载机制:

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

6、JVM内存分配

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

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

7、方法递归

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

  • 方法递归非常的耗费栈内存,所以能不用递归就尽量不用递归。
  • 如果递归没有设置结束标识或者递归太深就会发生栈内存溢出的错误,导致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+属性名首字母大写(形参列表){
    属性名=形参值;
    ...
}

 

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

5、引用参数的传递

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

6、构造方法(constructor)

语法如下:

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

 

构造方法的调用:构造方法的作用是通过调用构造方法来创建对象并初始化实例变量的值,而构造方法的调用使用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、继承

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

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

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

可以继承的数据:

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

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

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

 

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

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

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

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

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

10、多态

向上转型(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;
       }
   }

 

11、instanceof运算符

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

 

三、修饰符

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。

 


原文链接:https://www.cnblogs.com/guyuyun/p/12903244.html
如有疑问请与原作者联系

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:Spring Boot自定义Banner

下一篇:Mockito如何mock一条链式调用