面向对象编程(OOP) Object Oriented Programming
面向对象的特点:封装 继承 多态 (抽象)
介绍
什么是类
具有相同特点(属性)和行为(动作)的集合的总称.
人类
植物类
手机类
什么是对象
是某一类的具体体现 实例
将类的抽象概念具体化.
人类的对象: 张三,特朗普,…
植物类的对象 : 路边的梧桐,杨树
手机类的对象 : 正在使用的手机
什么是属性
是一类事物的特点/特征.
人类的属性/特征 : 肤色,年龄,身高,体重…
植物类的属性 : 树干,树叶,树根,花,果实
手机的属性 : 价格,重量,颜色,形状
什么是方法
一类事物都可以进行的动作/行为.
人类的动作 : 吃饭睡觉打豆豆
植物的动作 : 开花,结果,光合作用
手机的动作 : 打电话,发短信
以学生类为例
编写类
Student.java文件代表学生类
类中有属性和方法
学生类:
属性 : 姓名 年龄 学号 学校…
方法 : 学习 吃饭 睡觉
属性的写法:
[访问修饰符] 数据类型 变量名;
[]内的代码,可写可不写
[访问修饰符] 返回值类型 方法名([参数1],[参数2],…){
方法内的执行语句;
…
}
访问修饰符 : 控制访问权限
返回值类型 : 该方法执行后返回的结果的数据类型(基本数据类型/引用数据类型 )
void : 空,方法执行后不返回任何数据
…
如果方法需要返回数据,
1) 那就需要在返回值类型处写上返回数据的数据类型
2) 在方法体的最后使用 return 值;的格式将值明确显示返回.
方法名 : 见名知意,驼峰原则
() 参数列表 :
- 形式参数 : 该方法执行可能用到的数据
- 形式参数的写法: 数据类型 变量名;
- 列表内可以写多个参数,或者不写
- 当方法参数列表有参数时,在调用时(让方法执行时)就必须给方法的参数赋值
{} 方法体
执行语句 : 该方法真正的功能体现
package com.zhiyou100.oop;
public class Student {
public String name;
public int age;
public String stuNo;
String schoolName;
public String study(String book,String pen) {
System.out.println("在学习"+book);
String name = "张三";
// return "李四";
return name;
}
void eat() {
System.out.println("吃");
}
void sleep () {
System.out.println("睡觉");
}
}
对象的创建使用
通过类 创建对象 <==> 实例化
类名 对象名 = new 类名();
数据类型 变量名 = 值;
对象属性取值:
数据类型 变量名 = 对象名.属性名;
对象属性赋值:
对象名.属性名 = 值;
调用对象的方法
- 方法有参数,调用时必须传入实参;实参与形参的个数,顺序,数据类型要保持
- 方法没有参数,不能给参数
- 方法无返回值,一定不能接收返回值
- 方法有返回值,可以接收,也可以不接收
A : 无返回值
A1 无返回值无参
对象名.方法名();
A2 无返回值有参数
对象名.方法名(值1,值2,…);
B : 有返回值
B1 有返回值无参
[数据类型 变量名 = ]对象名.方法名();
B2 有返回值有参数
[数据类型 变量名 = ]对象名.方法名(值1,值2,…);
扩展:
一个类可以创建多个对象,并且每个对象多有自己的属性和方法
package com.zhiyou100.oop;
public class TestOOP {
public static void main(String[] args) {
// 创建对象
Student stu1 = new Student();
System.out.println(stu1);
// 获得属性值
String name = stu1.name;
int age = stu1.age;
String stuNo = stu1.stuNo;
String schoolName = stu1.schoolName;
System.out.println("name = "+name);
System.out.println("age = "+age);
System.out.println("stuNo = "+stuNo);
System.out.println("schoolName = "+schoolName);
// 属性赋值
stu1.name = "张三";
stu1.age = 18;
stu1.schoolName = "北京大学";
stu1.stuNo = "100001";
System.out.println("name = "+stu1.name);
System.out.println("age = "+stu1.age);
System.out.println("stuNo = "+stu1.stuNo);
System.out.println("schoolName = "+stu1.schoolName);
System.out.println("----------分割线---------");
// A1 无返回值无参
stu1.eat();
// A2 无返回值有参数
stu1.sleep("地");
// B1 有返回值无参
String result1 = stu1.playGame();
System.out.println("结果 : "+result1);
// B2 有返回值有参数
String result2 = stu1.study("<java从入门到精通>", "黑笔");
System.out.println("谁在学习 ? : " + result2);
}
}
对象在内存的图示
两个引用指向一个对象
public class TestOOP3 {
public static void main(String[] args) {
/*
* 两个引用指向一个对象
* s1 是对象名,也是变量,也可以叫做引用
* s1引用的是内存中堆中的对象的地址值
* Student s2 = s1;
* 将引用赋值给s2.
* s1 和 s2 操作的是内存中的同一个对象
*/
Student s1 = new Student();
Student s2 = s1;
s1.name = "刘备";
System.out.println(s2.name);
};
}
方法的参数列表是类名时,该如何调用 ?
调用时,传入该类型的对象
完整的内存图
局部变量和成员变量
不同点
成员变量: 成员属性/全局变量/全局属性
位置 : 类中,方法外
在内存的位置 : 在堆内存
生命周期 : 随着对象创建在堆中出现,随着对象被垃圾回收(GC)而消失
初始值 : 随着创建对象,赋默认值
局部变量:
位置: 方法参数列表中,或者方法体内
在内存的位置 : 在栈内存
生命周期 : 随着方法进栈而出现,方法执行完弹栈而消失
初始值 : 在使用前必须给定值.
使用时的注意事项
当方法局部变量与成员变量重名时,
方法内使用时 “就近原则”,默认使用方法自己的局部变量
如果重名时,想要继续使用成员变量.要使用this指代
this.属性 指代成员变量
this就是当前调用该方法的对象
访问控制符修饰类
- 访问修饰符都可以修饰属性/成员变量
- 访问修饰符都可以修饰方法
- 访问修饰符又叫访问控制符/权限控制符
- 作用就是: 控制被修饰的属性或者方法在被别处(包括当前类或者其他类中)的访问权限.
封装
封装的概念
把客观事物封装成抽象的类,并且类可以把自己的属性和方法只让可信的类或者对象进行操作,对不可信的类或者对象隐藏,这样的过程叫做封装。
封装的分类
对属性的封装
将属性设置为private(私有),限制其只能在类的内部使用;提供set、get方法,set方法给属性赋值,get方法获得属性值。
setter getter 的方法名写法:setXxx、getXxx,Xxx 是类的属性首字母大写,访问修饰符不可以是private。
对方法的封装
对于方法的封装,将外部可以访问方法设置为public,将外部不能访问的方法设置为private
构造方法
构造方法/构造函数/构造器/constructor
作用 :
创建对象,属性赋值
new 构造方法();
语法格式 :
访问修饰符 类名() {
}
特点:
- 任何一个类中都默认隐式存在无参构造方法
- 构造方法没有返回值类型,连void都没有
- 构造方法的方法名是当前类的类名,首字母大写
- 在程序中显示写出任意一个构造方法.那么隐式的不再提供.
继承
一个新类从现有的类派生,这样的过程叫做继承
那么在继承的过程中,新类被称为子类,现有的类被称为父类,子类将会继承父类的属性和行为。
继承的语法
[修饰符] class 子类类名 extends 父类类名{子类类体部分}
子类的扩展
子类除了可以拥有父类非私有的属性和方法外,也可以扩展自己的属性和方法
使用
- Java中的继承是单继承,也就是一个类只能有一个父类。
- 如果一个类没有显式的继承某一个类,那么它有一个默认的父类是java.lang.Object类
- Java继承了父类非私有的成员变量和方法,但是请注意:子类是无法继承父类的构造方法的
- 子类可以使用父类的属性和方法,也可以使用租父类属性和方法 --> 多层次继承
在继承中成员变量的访问
1 当子类中没有该成员变量时,那么调用的就是父类的成员变量
2 当子类与父类有重名的成员变量时,默认访问的是子类自己的属性
但是一般情况时,子类不会有与父类重名的属性,因为继承就是为了使用父类的属性和方法
在继承中成员方法的访问
1 当子类中没有该方法时,那么调用的就是父类的方法
2 当子类有与父类重名的方法时 ——》方法的重写(@Override)
当子类重写了父类方法后,在调用时,调用的就是子类自己的方法
在继承中构造方法
- 构造方法不能继承,也不能被重写.更不能被对象调用.
- 在继承中,子类的构造方法会在构造方法第一行隐式调用父类无参构造. super();
- 如果现式写出调用父类构造方法的代码,必须写在第一行.
- 默认是调用父类无参的,也可以使用super(参数)调用父类有参数的构造方法
多态
概念
什么是多态
多态顾名思义即为多种形态的意思
Java中多态的含义
发送消息给某个对象,让这个对象自行决定采用哪种行为响应这个消息
子类对象的引用赋值给父类引用变量来实现动态的方法调用
属性没有多态性,方法有多态性
Java中形成多态的前提
继承
重写
父类引用指向子类对象
父类无法调用子类特有的方法
想要调用子类特有的方法,需要由子类自己的对象调用
向下转型: 父类转成子类
父类由哪个子类向上转型而成,那就只能向下转型成哪个子类. 而不是随便转变成任意子类.
a instanceof Dog: a变量如果是Dog类的对象,就返回true
抽象
1 抽象方法没有方法体
2 抽象方法还需要由abstract修饰
3 抽象方法必须定义在抽象类中
4 抽象类需要abstract修饰
5 抽象类不一定有抽象方法
6 抽象类也可以拥有正常的方法
7 属性不能被abstract修饰
8 抽象类可以有构造方法,但是不能创建对象
9 抽象类还是类
10 抽象类不能创建对象
11但是可以使用多态特性
继承抽象类
子类继承抽象类,必须重写父类的全部抽象方法,否则,就必须将子类也定义成抽象类
接口
接口相当于一种规范,约束
含义
各个小组的开发工作是并行的。在很多时候,对于不同小组的程序员来说,清楚地说明各自的软件如何相互作用,并就此达成统一“约定”是很重要的。这种情况下,每一小组都可以在对其他小组怎么编写代码一无所知的情况下,就能编写自己的代码,并且各自小组编写的代码能很好的协同工作。接口就是这样的“约定”。
在Java中接口不仅仅是程序开发过程中“约定”,更是更加抽象化的抽象类.
接口就是让你知道它在做什么,而无需知道它们怎么做;接口更深层的理解是:使定义(规范和约束)和实现(具体的代码逻辑)分离,它是沟通(交互)的中介物(具体实现)的抽象化。
接口即是设计:在设计层面,接口可以避免我们陷入对细节的过多思考,可以让我们站在一个更高的视角对系统做更好的评估,比如系统的交互设计是否合理,功能是否缺失,是否具备可行性,是否过于复杂等等。接口即是约定:在编码层面,接口可以明确的告诉开发人员如何使用(接口的语义,需要什么作为输入,会有什么输出),而开发人员只需要根据这些约定去实现具体的功能代码即可。统一类的共同行为:接口用来统一类的共通行为,当不同的类需要进行信息共享时,是不需要特别去创建类间的关系。举例来说,一个人(Human)及一只鹦鹉(Parrot)都会吹口哨(whistle),然而 Human 及 Parrot 不应该为 Whistler 的子类,最好的做法是令他们为 Animal 的子类,而他们可以使用 Whistler 的接口进行沟通。
接口是一种比抽象类更加抽象的“类”。这里给“类”加引号是我找不到更好的词来表示,但是我们要明确一点就是,接口本身就不是类.
特点
1 接口文件是interface修饰,接口不是类
2 接口中的成员变量默认都被public final static
3 接口中不存在正常带方法体的方法,默认都是抽象方法,都默认被public abstract修饰在jdk8后可以使用default
4 接口中没有构造方法,不能创建对象
实现
- 类 实现 接口,可以多实现,多个接口用,隔开
- 类 继承 类,是单继承
- 接口 继承 接口,且可以多继承,多个,接口逗号隔开
-
- 类实现接口,要重写全部的抽象方法
- 如果不重写或者没有全部重写,那么子类要定义成抽象类
类可以在实现接口的同时,继承其他类–>写法是先 extends 后 implements
static关键字的作用与用法
static 在可用来修饰属性、方法。
常用方式:
static:main方法固定搭配(规定)
public static void main(String[] args){}
static特点
- 可以修饰属性,方法,在内存中只有一份
- 修饰的属性叫做类属性 该属性属于当前字节码文件(xx.class),被当前类创建出的所有对象共享,同理还有类方法
- 凡是被static修饰的属性和方法,会随着类加载而初始化
类属性比对象属性先在内存出现,类属性可以不通过对象调用
类名.静态属性;
类名.静态方法();
注意:
- 静态方法只能调用静态的属性和方法 --> 静态只能调用静态
- 非静态方法既可以非静态属性和方法,也可以调用静态的属性和方法–> 非静态什么都可以调用
- 静态方法中不能使用this
说明:
- static变量:按照是否静态的对类成员变量进行分类:一种是被static修饰的变量,叫静态变量(类变量);另一种是没有被static修饰的变量,叫实例变量。
区别:
a、对于静态变量在内存中只会一次拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中优先加载完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。
b、对于实例变量,没创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。 - static代码块:static代码块是类加载时,初始化自动执行的,其优先级仅次于静态变量。如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。
- static方法:static方法可以直接通过类名调用,任何的实例也都可以调用,因此static方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(也就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。因为static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。tatic方法只能访问static的变量和方法,因为非static的变量和方法是需要创建一个对象实例才能访问的,而static的变量/方法不需要创建任何对象。
提示:
static的数据或方法,属于整个类的而不是属于某个对象的,是不会和类的任何对象实例联系到一起。所以子类和父类之间可以存在同名的static方法名,这里不涉及重载。所以不能把任何方法体内的变量声明为static。
this与super
重写(Override)与重载(Override)
重写(Override)
重写是子类对父类的允许访问的方法的实现过程进行重新编写!返回值和形参都不能改变。即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。也就是说子类能够根据需要实现父类的方法。
方法重写的规则
- 参数列表必须完全与被重写方法的相同; 返回类型必须完全与被重写方法的返回类型相同;
- 访问权限不能比父类中被重写的方法的访问权限更高。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。
- 父类的成员方法只能被它的子类重写。 声明为final的方法不能被重写。 声明为static的方法不能被重写,但是能够被再次声明。
- 如果一个方法不能被继承,那么该方法不能被重写。
- 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法。
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
- 构造方法不能被重写。 如果不能继承一个方法,则不能重写这个方法。
重载(Overload)
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
只能重载构造函数
方法重载的规则
- 被重载的方法必须改变参数列表;
- 被重载的方法可以改变返回类型;
- 被重载的方法可以改变访问修饰符;
- 被重载的方法可以声明新的或更广的检查异常;
- 方法能够在同一个类中或者在一个子类中被重载
final关键字:
* 1 修饰变量
* 变量不再是变量而是常量.必须赋初始值且值不可再改变.
* 2 修饰方法
* final修饰的方法不能被重写
* 3 修饰类
* finale修饰类,类不能被继承