知识点总结于毕向东Java基础视频教程25天版本,侵权请联系删除。
第三章:面向对象
面向对象概念
理解面向对象
- 面向对象是相对面向过程而言
- 面向对象和面向过程都是一种思想
- 面向过程强调的是功能行为
- 面向对象将功能封装进对象,强调具备了功能的对象。
- 面向对象是基于面向过程的。
面向对象特点
- 是一种符合人们思考习惯的思想
- 可以将复杂的事情简单化
- 将程序员从执行者转换成了指挥者
- 完成需求时:
-先要去找具有所需的功能的对象来用。
-如果该对象不存在,那么创建一个具有所需功能的对象。
-这样简化开发并提高复用。
面向对象开发,设计,特征
- 开发的过程:其实就是不断的创建对象,使用对象, 指挥对象做事情。
- 设计的过程:其实就是在管理和维护对象之间的关系。
- 面向对象的特征: 封装(encapsulation) 、继承(inheritance)、多态(polymorphism)
类与对象
类与对象的关系 (图例)
- 使用计算机语言就是不断的在描述现实生活中 的事物。
- java中描述事物通过类的形式体现,类是具体事物的抽象,概念上的定义。
- 对象即是该类事物实实在在存在的个体。
类与对象的关系如图:
可以理解为:
- 类就是图纸
- 汽车就是堆内存中的对象
类的定义
- 生活中描述事物无非就是描述事物的属性和行为。
如:人有身高,体重等属性,有说话,打球等行为。 - Java中用类class来描述事物也是如此。
属性:对应类中的成员变量。
行为:对应类中的成员函数。 - 定义类其实在定义类中的成员(成员变量和成员函数 )。
成员变量和局部变量的区别
- 成员变量:
–成员变量定义在类中,在整个类中都可以被访问。
–成员变量随着对象的建立而建立,存在于对象所在的堆内存中。
–成员变量有默认初始化值。 - 局部变量:
–局部变量只定义在局部范围内,如:函数内,语句内等。
–局部变量存在于栈内存中。
–作用的范围结束,变量空间会自动释放。
–局部变量没有默认初始化值。
创建和使用对象
class Car//对Car这类事物进行描述
{
String color = "red"; int num = 4;
void show()
{
System.out.println("color="+color+"..num="+num);
}
}
class CarDemo
{
public static void main(String[] args)
{
Car c = new Car();//建立对象 c.color = "black";//对对象的属性进行修改 c.show();//使用对象的功能。
}
}
对象内存结构
匿名对象
- 匿名对象是对象的简化形式
- 匿名对象两种使用情况
–当对对象方法仅进行一次调用的时
–匿名对象可以作为实际参数进行传递
class Demo
{
public static void main(String[] args){
car c=new car();//在栈中加载主函数,主函数中有c并存储在堆中创建好的car对象的地址值,则c指向了对象
showCar(c);//在栈中开辟showCar方法空间,包含方法中的变量。并接收c中对象的地址值,从而也指向同一个在堆中的对象,并修改了对象
showCar(new car());//匿名对象可以作为实际参数进行传递。在堆内存中产生了一个对象并传递给了栈内存中函数showCar()空间中的变量c。即c指向了该对象,所以该对象不是垃圾。等到方法结束,内存释放。
new car().run();//匿名对象,只在堆内存中开辟空间,没有在栈内存中的引用。调用对象中的方法。
}
public static void showCar(car c){
c.wheel=3;
c.color="blue";
c.run();
}
}
class car
{
int wheel=4;
String color="red";
void run(){
System.out.println(wheel+" "+color);
}
}
运行结果:
3 blue
3 blue
4 red
封装(Encapsulation)
-
封装:是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
-
好处:
1.将变化隔离。
2.便于使用。
3.提高重用性。
4.提高安全性。 -
封装原则:
–将不需要对外提供的内容都隐藏起来。
– 把属性都隐藏,提供公共方法对其访问。
封装private
-
private关键字
–是一个权限修饰符。
–用于修饰成员(成员变量和成员函数)
–被私有化的成员只在本类中有效。 -
常用之一:
–将成员变量私有化,对外提供对应的set ,get方法对其进行访问。提高对数据访问的安全性。
构造函数
特点:
- 函数名与类名相同。
- 不用定义返回值类型。
- 不可以写return语句。
- 不能被调用,只有在创建对象时调用。
作用:给对象进行初始化。
注意:
- 默认构造函数的特点:不带参数。
- 多个构造函数是以重载的形式存在的。
构造代码块
- 对象一建立就运行,而且优先于构造函数执行。
作用:给对象进行初始化。
和构造函数的区别:构造代码块是给所有对象进行统一初始化;而构造函数是给对应的对象初始化。
class Demo
{
public static void main(String[] args){
baby babe =new baby();
baby babe1=new baby("张三",1);
}
}
class baby
{
private String name;
private int age;
{//构造代码块,所有对象初始化都会运行
System.out.println("i'm crying...");
}
baby()
{
System.out.println(name+" "+age);
}
baby(String na,int nu)//构造函数重载
{
name=na;
age=nu;
System.out.println(name+" "+age);
}
}
运行结果:
i'm crying...
null 0
i'm crying...
张三 1
this关键字
- 特点:this代表其所在函数所属对象的引用。 换言之:this代本类对象的引用。
什么时候使用this关键字呢?
当在函数内需要调用该函数的对象时,就用this。
class Demo
{
public static void main(String[] args){
baby babe =new baby();
baby babe1=new baby("张三",1);
}
}
class baby
{
private String name;
private int age;
{//构造代码块,所有对象初始化都会运行
System.out.println("i'm crying...");
}
baby()
{
System.out.println(name+" "+age);
}
baby(String name,int age)//构造函数重载
{
/*使用this关键字代表当前对象的成员变量name=局部变量name,
在上面的主函数中创建的两个对象中相当于babe.name=name/babe1.name=name;*/
this.name=name;
this.age=age;
System.out.println(name+" "+age);
}
}
运行结果:
i'm crying...
null 0
i'm crying...
张三 1
this语句:
- 用于构造函数之间的互相调用。
- 因为一般函数不能直接调用构造函数,所以this语句不能用在一般函数中。
- 只能定义在构造函数的第一行,因为初始化要先执行。因此每个构造方法只能使用一次。
在带参数的构造函数baby中添加this语句:
baby(String name,int age)//构造函数重载
{
/*
this语句,指代执行当前对象的无参构造函数。
this()相当于baby()
*/
this();
/*使用this关键字代表当前对象的成员变量name=局部变量name,
在上面的主函数中创建的两个对象中相当于babe.name=name/babe1.name=name;*/
this.name=name;
this.age=age;
System.out.println(name+" "+age);
}
运行结果:
i'm crying...
null 0
i'm crying...
null 0
张三 1
注意:要避免两个构造函数之间使用this互相调用对方,这样会导致死循环。
static关键字
static
- 用于修饰成员(成员变量和成员函数)
- 被修饰后的成员具备以下特点:
1.随着类的加载而加载,加载于方法区当中
2.优先于对象存在
3.被所有对象所共享
4.可以直接被类名调用 - 静态的使用
–什么时候定义静态变量?
当对象中出现共享数据时,该数据被静态所修饰。(对象的特有数据是定义成非静态且存在堆中)
–什么时候定义静态函数?
当功能内部没有访问到非静态数据(对象的特有数据)。 - 使用注意
1.静态方法只能访问静态成员(非静态方法既可以访问静态变量也可以访问非静态变量)。
2.静态方法不可以写this,super关键字。
因为静态对象优先于对象存在,由于this代表了当前对象,所以静态方法中不可以出现this。
3.主函数是静态的。
4.静态函数间的调用省略了"类名."。(非静态之间省略了"this.")
在baby类中添加static修饰的成员变量和成员函数
class Demo
{
public static void main(String[] args){
baby.show();//直接通过类名调用静态方法
}
}
class baby
{
private String name;
private int age;
static String country="china";
{
System.out.println("i'm crying...");
}
baby()
{
System.out.println(name+" "+age);
}
baby(String name,int age)
{
this();
this.name=name;
this.age=age;
System.out.println(name+" "+age);
}
static void show()
{
System.out.println(country); //访问了静态变量
}
}
静态的优点:
- 对对象的共享数据进行单独空间的存储,节省了空间。
- 可以直接被类名调用。
静态的缺点:
- 生命周期过长,静态过多耗费内存。
- 访问出现局限性。(静态只能访问静态)
main函数
主函数是一种特殊的函数。作为程序的入口,可以被jvm调用。
- 主函数的定义
public:代表着该函数访问权限是最大的。
static:代表主函数随着类的加载而已经存在。
void:主函数没有具体的返回值。
main:不是关键字,属于被jvm识别的特殊单词。
(String[] args):函数的参数,参数类型是一个数组,该数组中的元素是字符串。args为变量名,可更改。
主函数的格式是固定的,由jvm识别。
class Demo
{
public static void main(String[] args){
String[] arr={"1","2","3"};
MainTest.main(arr);//调用MainTest类中的主函数
/*在cmd编译好java文件后,在控制台输入java Demo haha并回车,会输出haha。
这是因为虚拟机把类后的数据haha作为做函数的参数赋给了数组args[0]*/
System.out.println(args[0]);
}
}
class MainTest
{
public static void main(String[] args){
for(int i=0;i<args.length;i++)
System.out.println(args[i]);
}
}
执行语句:
输出结果:
静态的应用
每一个程序中都有共性的功能,我们可以将这些功能进行抽取,独立封装以便复用。所以可以将一些方法封装在一个类中。
class Tool
{
Tool(){//构造函数,用于初始化对象
}
public int getMax(int[] arr){
...
}
public int getMin(int[] arr){
...
}
public int sort(int[] arr){
...
}
}
class Demo
{
public static void main(String[] args){
String[] arr={1,45,63,234,33};
Tool tool=new Tool();
tool.getMax(arr);
}
}
但这样就有些问题:
- 对象是用于封装数据的,可是Tool对象并未封装特有数据。
- 操作数组的每一个方法都没有用到Tool对象的特有数据。
这时就可以考虑让程序更加严谨,该类是不需要对象的。
- 可以将Tool中的方法都定义成static的。直接通过类名调用即可。
将方法都静态后,可以方便使用,但是该类还是可以被其它程序建立对象的。为了更加严谨,强制让该类不能建立对象。
- 可以通过构造函数私有化完成。即private。
class Tool
{
private Tool(){//构造函数私有化
}
public static int getMax(int[] arr){
...
}
public static int getMin(int[] arr){
...
}
public static int sort(int[] arr){
...
}
}
class Demo
{
public static void main(String[] args){
String[] arr={1,45,63,234,33};
Tool.getMax(arr);
}
}
静态代码块
随着类的加载就会执行且执行一次,执行优先级高于构造代码块和类中主函数。
格式:static{}
class Demo
{
public static void main(String[] args){
new StaticCode(4);//创建匿名对象并调用带参构造函数
}
}
class StaticCode
{
int num=9;
StaticCode(){
System.out.println("b");
}
static{ //静态代码块,当类加载时执行,所以优先执行
System.out.println("a");
}
{//构造代码块,当对象被创建时执行,第二个执行
System.out.println("c"+this.num);
}
StaticCode(int x){//构造函数,当对应的对象创建后后执行,最后执行
System.out.println("d");
}
}
执行顺序:
静态代码块/静态变量>构造代码块/成员变量>构造函数
(同级按照代码书写顺序执行)
对象的初始化过程和对象调用成员过程
对象的初始化过程
StaticCode s =new StaticCode();
该句话都做了什么事情?
- 因为new到了StaticCode.class,所以会先找到StaticCode.class文件并加载到内存中。
- 执行该类中的static代码块,如果有的话,就给StaticCode.class类进行初始化。
- 在堆内存中开辟空间,分配内存地址。
- 在堆内存中建立对象的特有属性,并进行默认初始化。
- 对属性进行初始化。
- 对对象进行构造代码块初始化。
- 对对象进行对应的构造函数初始化。
- 将内存地址赋给栈内存中的s变量。
对象调用成员过程
class Demo
{
public static void main(String[] args){
Person p=new Person("zhangsan",20);
p.setname("lisi");
}
}
class Person
{
private String name;
private int age;
private static String country="china";
Person(String name,int age){
this.name=name;
this.age=age;
}
public void setName(String name){
this.name=name;
}
public void speak(){
System.out.println(this.name+" "+this.age);
}
public static coid showCountry(){
System.out.println("country="+country);
}
}
- 现在栈中开辟主函数main的空间,里面包含p变量。
- 在方法区中开辟Person类空间,空间中加载静态成员和方法体。
- 在堆中创建对象,里面包含成员变量name,age并初始化,将对象地址值赋给p。
- 在栈内存中开辟方法setName空间,空间中有局部变量name和this(代表当前对象)。
- 将当前对象地址值赋给this,即现在this代表p所指向在堆内存中的对象。
- 把局部变量name的值赋给对象中成员变量name,所以zhangsan改为lisi。
单例设计模式
设计模式是解决每一类问题最行之有效的方法。而单例设计模式是解决一个类在内存中只存在一个对象。保证对象唯一性。
- 为了避免其它程序过多建立该类对象,先禁止其他程序建立该类对象。
- 为了让其他程序可以访问到该类对象,需要在本来中先自定义一个对象。
- 为了方便其它程序对自定义对象的访问,可以对外提供一些访问方式。
饿汉式
对象一进内存直接创建对象。
Single类加载时,对象已经创建。
class Demo
{
public static void main(String[] args){
Single ss= Single.getInstance(); //通过类名调用静态方法获取已经创建的对象的地址
ss.run();
}
}
class Single
{
private Single(){} //构造函数私有化
private static Single s = new Single();//当类加载时创建好唯一对象,设为静态才能被静态方法调用
public static Single getInstance(){//创建可调用的方法返回对象的地址
return s;
}
public static void run(){
System.out.println("hh");
}
}
懒汉式
对象是方法被调用时,才初始化,也叫做对象的延时加载。
single类进内存,对象还没有创建,只有调用了getInstance方法时,才创建对象。
class Single
{
private Single(){}
private static Single s = null;
public static Single getInstance(){
if(s==null){
synchronized(Single.class){
/*
为了避免由于存在多个程序同时调用函数时导致CPU生成多个对象,用synchronized内置锁避免多
个程序内逐一满足条件后再挨个执行创建对象的情况。
*/
if(s==null)
s=new Single();
}
}
return s;
}
public static void run(){
System.out.println("hh");
}
}
总的来说,使用单例模式最终仍会用到对象的调用,所以对象一定会创建,所以饿汉式对于资源占用的较小,优先被使用。