Java面向对象
一、类
- java程序的最基本单位
- 对一类有共同属性和行为事物的抽象叫做类
二、对象
1、定义
实实在在看得见摸得着的实体叫对象
- 注:new出来的对象存在于内存的
堆
中,有默认值。比如String
为null
,int
为0
代码执行过程中内存原理分析
Demo
- 类的定义
package com.student;
public class Student {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void show(){
System.out.println("name=" + name + " , " +"age=" + age);
}
}
2、单对象内存原理
- 测试
package com.student;
public class StudentDemo {
public static void main(String[] args) {
Student s1 = new Student();
s1.setName("小明");
s1.setAge(18);
s1.show();
}
}
1、main
方法在内存中进栈
执行构造方法,创建一个Student类得实例对象s1,于是在内存的堆空间开辟空间存储s1成员变量,并初始化成员变量。构造方法出栈。
2、setName
方法在内存中进栈
s1指向堆中的对象实例地址(假设是001),this指向的方法调用者实例,也就是s1.
方法执行结束出栈。
之后setAge
方法同理。
4、执行show
方法,show
方法在内存
3、多对象内存原理
- 测试
package com.student;
public class StudentDemo {
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student();
s1.setName("小明");
s2.setName("小红");
s1.setAge(18);
s2.setAge(20);
s1.show();
s2.show();
}
}
后续方法调用同理,都是一个类方法执行,先进入内存栈区域,如果是赋值操作,那么调用者对象便指向内存堆区域的真实地址,对成员变量进行赋值操作,在方法执行完毕之后,会将方法进行出栈,随后后续方法接在内存中入栈,执行响应操作。
以demo的测试代码为例,接下来是Student的setAge方法入栈,调用者是s1,便指向s1在内存堆中的真实地址,对s1的age属性进行赋值,之后setAge方法出栈,接着又是Student的setAge方法入栈,可这回调用者是s2,于是指向s2在内存堆中的真实地址,对s2的age属性进行赋值,之后setAge方法出栈。接下来要执行的是Student类的show方法,两个对象都有调用,依次是s1先调用show方法入栈,执行过后出栈,然后是s2调用show方法入栈,执行后出栈。最后main方法执行完毕,释放s2,s1在内存中为存储成员变量开辟的空间,之后main方法出栈。到此为止测试执行完毕。
4、对象指向相同地址的内存原理
- 测试
package com.student;
public class StudentDemo {
public static void main(String[] args) {
Student s1 = new Student();
s1.setName("小明");
s1.setAge(18);
Student s2 = s1;
s2.setName("小红");
s2.setAge(20);
}
}
三、类中的变量
区别 | 成员变量 | 局部变量 |
---|---|---|
类中位置 | 类方法之外 | 类方法内或类方法声明中 |
内存中位置 | 堆 | 栈 |
生命周期 | 因为在堆中,所以随着类得创建而创建,随着类的销毁而销毁 | 因为在栈中,所以随着类方法的调用而产生,随着方法的执行完毕而销毁 |
初始化 | 有初始化 | 没有初始化,必须要先创建,然后赋值,才能被使用 |
this指针
this修饰的变量指代成员变量
- 作用:解决局部变量隐藏成员变量的问题。
- 方法中this的指向:方法被哪个对象调用this就指向哪个对象。
private修饰符
属于权限操作符,修饰类成员
- 作用:保护类成员不被其他类使用,被private修饰的变量只能本类使用!
四、构造方法
- 如果没有定义构造方法,默认使用无参构造方法。
- 如果定义了构造方法,系统将不再提供构造方法(建议都手动加上无参构造方法)
五、面向对象三大特征:封装、继承、多态
(一)封装
封装是面向对象编程语言对客观世界的模拟,客观世界里的成员变量都是隐藏在对象内部,外界是无法直接操作的
- 原则:将类得某些信息通过private属性隐藏在类内部,外界不能直接访问,需要通过该类提供的public方法进行操作或访问。
- 好处:
- 通过方法来控制成员变量的操作,提高了代码的安全性。
- 把代码用方法进行封装,提高了代码的复用性。
(二)继承
继承就是有一堆的类,把这些类的共性方法抽取出来,作为一个父类(也可叫做超类、基类),之后这些类就作为它的子类,这些子类就能继承父类中的所有非私有成员。
- 注意事项:
- 子类继承父类以后可以拥有父类的非私有成员
- 构造方法不能被继承,私有成员不能被继承,static修饰的成员不能被继承
- 格式:class 子类 extends 父类
- 访问成员变量:
- 不重名:如果访问父类的对象,那只能直接访问父类的非私有成员变量。如果访问子类的对象,那访问的是子类和父类的非私有成员变量。
- 直接使用中,父类对象、本类对象重名:优先访问
=
左边的对象类型的变量,如果要对其进行区别可以使用this
来特指本类对象、super
特指父类对象 - 通过方法使用时,父类对象、本类对象、本类中的局部变量都重名:主要先看使用的是哪个类的方法,在方法中优先访问本类的局部变量,访问本类成员使用
this
指代,访问父类的成员使用super
指代。
- 访问成员方法
- 不重名:如果访问的是父类的成员方法,那就只能访问父类的成方法。如果访问子类,那么访问的是子类的成员方法和父类的非私有成员方法。
- 重名:优先调用的同名方法主要看new的对象是什么类型,如果要显示进行区分最好使用
this
、super
来专门指代。
- 方法的重写
-
要求:重写的方法必须和原来的方法一模一样,虽然可以在方法的返回值可以是原来方法的子类型,但一般还是写作一模一样的类型。
-
使用场景:
a. 子类想对父类方法进行增强,一般加一句 super.method();
b. 父类方法是抽象的需要子类进行具体化。
面向对象中的抽象
在继承中不得不谈到抽象,因为在现实世界中有很多东西的共性的十分抽象的,不能一句话说得清,比如人吃饭、狗吃饭、鱼吃饭,他们具体的吃饭行为大不相同,如果要把“吃饭”这个动作进行提取为一个公共函数,那么就必须使用抽象的方法,也就是说只声明方法名,并声明为抽象方法,具体实现我们先不管,等定义具体的实现类时在进行行为的定义。
-
抽象方法:修饰符 abstract 返回类型 方法名(参数列表)
-
抽象类: abstract class 类名
-
注意事项:
- 将一类事物的共性方法抽取出来,但这个方法说不清道不明,就可以吧这个方法定义为抽象方法。
- 抽象方法一定要在抽象类中。
- 抽象类中不一定有抽象方法。
- 抽象类可以有成员变量、构造方法、普通方法。
- 抽象类不能直接实例化。
- 抽象类的构造方法是为了子实现类初始化时构造方法可以调用,用来初始化父抽象类中的成员变量
- 抽象类的子实现类必须重写父类的全部抽象方法
- 使用场景:一般作为父类
(三)多态
现实世界中,一种事物有多种形态,比如我们自己,在学校是学生,在家是儿子。
一句话说多态就是:父类对象引用指向子类对象,调用父子共用的方法(子类重写后的方法)。
- 前提
- 有父子继承关系(接口实现关系)
- 同名方法的重写
- 父类引用指向子类对象
父类 对象名 = new 子类()
- 多态的使用步骤
-
定义类
-
定义子类继承父类,重写父类方法
-
父类引用指向子类对象–>父类名 对象名 = new 子类() ,调用重写方法
注意事项:
1.必须有子父类继承关系(接口,实现类的关系)
2.必须又方法的重写
3.父类引用指向子类对象–>父类名父类名 对象名 = new 子类() ,调用重写方法
- 多态中的访问特点
- 多态中如果出现子类和父类变量同名的访问:看
=
左边的类型,是什么类型就访问哪个类型的变量 - 多态中如果出现子类和父类方法同名的访问:看
new
的对象是什么类型,访问的就是哪个类型的方法
如果子类中没有变量或者方法就访问父类
- 多态和普通面向对象的优点和缺点
- 普通面向对象:
优点:可以访问父类和本类的所有费私有方法
缺点:扩展性差 - 多态:
优点:扩展性强,尤其在函数参数调用,重写方法调用上
缺点:不能通过指向子类的引用来使用子类的特有方法
对于多态的缺点,也可以通过向下转型的方式解决。
- 转型
- 向上转型:默认都是向上转型,也就是父类对象引用指向子类对象
- 向下转型:
//((Dog) animal).watchDoor();
Dog dog = (Dog) animal;
dog.watchDoor();
通过向下转型就可以调用子类的特有方法
- 向下转型可能会抛出异常:Exception in thread “main” java.lang.ClassCastException
对于这个异常可以通过关键字instanceof
来解决,在转型的时候进行判断,判断对象是否属于某个类型。
使用方法:对象名 instanceof 类型