最近咱们在更新Java相关教程,由基础到进阶,希望能够帮到大家。
今天,咱们来介绍一下Java的面向对象,文章较长,建议大家先收藏后慢慢欣赏啊~~~
1、概述
方法,也可以称之为函数,在其他语言中可能方法和函数的概念和语法是不同的,比如Python中的函数是可以在文件中独立定义并存在的,而方法则是在类之中定义的函数,但是在Java中,方法和函数都指的是同一个语法,都是一样的,既可以称它为方法,也可以称它为函数。需要注意以下几点:
2、定义方法
修饰符列表:这是可选项,不是必须的。如果不写,则使用默认选项,对于访问控制符,如public、private等,缺省的访问控制权限为包范围内。
返回值类型:使用“return 值”返回一个值,且这个值的类型必须和指定的返回值类型一致,指定的返回值类型可以是Java中的任何数据类型,包括基本数据类型和所有引用类型。如果此方法不返回任何值,就必须将其指定为void,表示此方法不返回任何值,即方法体中不能有“return 值”这样的语句,但是可以写“return;”表示返回void。
方法名:遵循标识符的定义规则,但通常应该注意以下几点:
形式参数列表:
方法调用:使用点号“.”进行调用,但是注意,方法体中的程序只有在调用时才会去执行,定义或编译时都不会执行。
3、static方法调用
如果修饰符列表中有static的话,则称之为静态方法,调用此方法的语法格式为“类名.方法名(实际参数列表)”,但如果是调用本类中的static方法,则可以省略类名,直接使用方法名进行调用。
4、参数值的传递
在调用方法时,给方法传递的参数为变量的值(即值传递),而不是变量本身,因为如果传递的是变量本身,那岂不是就可以在调用的方法中使用这个变量了,但实际情况却是,调用的方法只能使用自己这个作用域中的局部变量,而不能使用它的调用者所在的作用域中的变量。所以传递的就是变量的值,从而使得方法的形参有了自己的值。
5、方法重载(overload)
重载原则:重载的方法功能相似的时候可以考虑使用方法重载,但是功能不同的时候尽量使用方法名称不同的方法来定义。
重载机制:
6、JVM内存分配
如果只是定义方法,不去调用,则不会被执行,在JVM中也不会给该方法分配运行所需的内存,只有在调用这个方法的时候才会动态的分配该方法所需的内存空间。
JVM的内存划分中主要有三大块的内存空间(当然也还有其他的内存空间):
7、方法递归
方法递归是指方法在方法体中调用自身,使用方法递归时应注意以下几点:
1、封装和类定义
类主要描述的对象的状态(属性)和动作(方法)。
类也是面向对象编程中“封装”特性在语法上的体现,封装特性的优点通常有以下几点:
一个普通的类的定义语法如下:
[修饰符列表] class 类名{
属性;
方法;
}
成员变量:在类体之中、方法之外的变量称之为成员变量。成员变量如果没有手动赋值的话,系统会自动赋予默认值(一切向“0”看齐)。成员变量又分为:
注意:类也是一种数据类型,属于引用数据类型,它的类型名称就是对应的类名。
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()这种用法,当一个子类的构造方法的第一行既没有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的使用,注意以下几点:
9、继承
继承特性优点:继承最基本的作用是代码复用,但是最重要的作用却是有了继承才有了方法的覆盖和多态机制。
单继承:Java中的继承机制只支持单继承,一个类不能同时继承多个类,只能继承一个类。语法如下:
// 继承使用extends关键字
[修饰符列表] class 类名 extends 父类名{
类体;
}
可以继承的数据:
多继承:Java中虽然只支持单继承,但是可以间接实现多继承:
C extends B{
}
B extends A{
}
A extends T{
}
// 这样C直接继承B,但间接继承了T和A类
默认基类:Java中一个类如果没有显式继承任何类,那么该类默认继承javaSE库中提供的java.lang.Object类。
需要注意一个概念,当一个子类在继承某个父类时,在运行时,不是说在子类中查找对应方法或属性,子类中没有再到父类中查找,而是在定义时,如果继承了某个父类,那么这个类的定义中就包含了父类继承过来的某些方法和属性,即子类对象执行的方法和属性总是自己的属性和方法。
10、方法的覆盖/重写(override)
方法的覆盖也称为方法的重写,子类将父类继承过来的方法进行重新编写被称为方法的重写,方法重写时需要注意:
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("小狗在奔跑!!!");
}
}
使用抽象类时,应注意以下几点:
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...");
}
}
使用接口的使用,需要注意以下几点:
15、抽象类和接口的区别
其实,在实际使用中还是接口使用的多,抽象类使用的少。抽象类和接口看着有许多相似的地方,但它们之间的区别还是有很多的,如下:
以上就是小编整理的Java面向对象,只是个人的一些见解,有不全的地方欢迎大家留言交流,共同进步。