目录
面向对象是java的核心,贯穿于整个java体系,这个概念会一直伴随我们后面的学习。
1.面向过程和面向对象
1.1面向过程和面向对象概念
面向过程,比较注重步骤,侧重每一步做了什么
1)把冰箱门打开
2)把大象装进去
3)把冰箱门关上
步骤和步骤之间是紧密相连 按顺序进行。具体的每个步骤用函数(方法)来实现
面向对象,注重对象,侧重是谁来做
主体:大象 熊猫 冰箱
大象{
走(冰箱){}
}
熊猫{
走(冰箱){}
}
冰箱{
size;
打开()
关闭()
}
1.2面向对象和面向过程的区别
比如我现在不把大象装冰箱了,我想装其他的动物,或者改变冰箱的大小,怎么办呢?
1)把大冰箱门打开
2)把大熊猫装进去
3)把大冰箱门关上
主体:大象 熊猫 冰箱
大象{
走(冰箱){}
}
熊猫{
走(冰箱){}
}
冰箱{
size;
打开()
关闭()
}
1)步骤 ,对象
面向过程:耦合度高,扩展性低 (智能手机)
面向对象: 耦合度低,扩展性高 (灯泡、组装电脑)
2)不是对立的,是相辅相成的
对象中的具体方法还是通过方法一步一步实现
所以面向对象只是宏观把控,在微观上还需要面向过程来实现
3)简单问题用的面向过程,复杂问题用面向对象解决
当然这块内容不是一下子就能理解的透透的,这个需要你要通过后期知识积累和项目沉淀来慢慢消化的,这块你听大概就行
1.3面向对象的专业术语
OOD:Object-Oriented Design
OOA:Object-Oriented Analasis
OOP:Object-Oriented Programming
1.4面向对象的三大特征
封装
继承
多态
2.类和对象的概念
软件存在的意义就是解决现实世界的问题,所以软件就需要对现实世界进行模拟。现实世界有什么,软件里面就得有什么 。
面向对象编程之所以能成为主流是因为符合人的思维模式。比如我说动物,你脑子是不是会有一个动物的画面,有的人想的老虎,有的人想的是小猫 动物就是类,每个人心里动物的肯定不一样。那在这动物就是一个类,张三脑子想的动物就是对象。
面向对象编程的思想关注的是对象对吧。java中万事万物皆对象,也就是说我们周围的事务都可以看做是对象,比如桌子、电脑、键盘、鼠标等等。那么怎么创建对象?创建对象就必须得现有类,那到底什么是类,什么是对象,类和对象有什么关系呢?
2.1类
类:具有共同特征事务的一种描述,一种抽象的概念,对象:是一个实际存在的个体。
例如:“汽车”就是一个类(所有的汽车都有方向盘、发动机、都能行驶,这是它们的共同特征),“你家的那个汽车”就是一个真实存在的对象。
“人类”就是一个类,一个叫张三 李四的人 这就是对象
类描述事物的共同特征,那么“人”类都有哪些共同特征呢?人类都有姓名、性别、年龄等状态信息(属性),他们还有一个共同的行为就是吃饭(方法)。
类包含:属性+方法,属性描述的是状态是名次,方法描述的是行为动作,动词。
2.2类和对象关系
类和对象关系:类---》创建---》对象(实例) 实例化
对象共同特征----》抽象---》类
但是你有没有发现,当我们把类对应某个具体对象上之后,姓名可以叫张三,是不是也可以叫李四....是。所以我们在访问姓名、性别、年龄的时候,必须先有人这个对象,通过真实存在的人对象去访问他的属性,包括“吃饭”的时候,只有“人”类是不行的,必须先有人类对象,让人类对象去执行“吃饭的”这个动作。
2.3如何定义类
具体我们在java中如何定义一个类呢?
我们得知:类 = 属性 + 方法,而属性描述的是状态,方法描述的是行为动作。行为动作以方法的形式存在,那属性以什么形式存在呢?例如:姓名、性别、年龄,大家想起之前学习的变量了吗?变量用来存储数据。不错,对象的属性以变量形式存在。
package com.example.course.demo01;
/*“人”类:
属性+ 方法
状态信息(名词):姓名、性别、年龄-----》属性
行为动作(动词):吃饭、睡觉 -------------》方法
变量分类:方法内/外
成员变量:方法外
局部变量 :方法内
*/
public class Person {
//int a;//定义int类型变量,变量名叫a
//身份证号
//姓名
String name;//实例变量,对象级别的,只有有了对象才能去访问
//性别(男true 女false)
boolean sex;
int age;
}
这里所说的变量是我们之前提过的“成员变量当中的实例变量”。为什么叫实例变量呢,实例变量就是对象级别的变量,这样的变量要求必须先存在对象,通过对象才能访问。以上程序当中每个人的no、name、age、sex这些属性都是不一样的,要想访问,必须先创建对象才能访问,不能直接通过类去访问。没有学生对象,谈何学号!
例如:“中国人”这个类,有一个属性是“身份证号”,每一个中国人的“身份证号”都是不一样的,所以身份证号必须使用一个真实存在的“中国人对象”来访问。不能使用“中国人”这个类去访问身份证号。一个类可以实例化N多个对象,假设通过“中国人”这个类创建了100个“中国人对象”,那么“身份证号”必然会有100个实例变量空间去存储。
2.4创建对象
class Test {
public static void main(String[] args) {
/*
数据类型:基本数据类型(四类八种)
引用数据类型:String,class修饰的类型
new Person();会再堆中开辟一块内存,然后将内存的地址赋给了p
p成为引用
* */
int a=10;
//创建Person对象
Person p=new Person();
//如何访问对象的内容 引用.属性
/*实例变量来说,如果不手动初始化,系统会给你一个默认值
* byte short int long 0
* double float 0.0
* boolean false
* char \u0000
*
*/
System.out.println(p.name);//null
System.out.println(p.sex);//false
System.out.println(p.age);//0
//给属性赋值
p.name="张三";
p.age=18;
p.sex=true;
//输出属性的值
System.out.println(p.name);//null
System.out.println(p.sex);//false
System.out.println(p.age);//0
}
}
练习:日期类:包括年,月,日三个成员变量,显示日期的方法
提供构造方法:定义无参构造方法,和有参构造方法
public class Date {
//年
int year;//
//月
int month;
//日
int day;
}
class TestDate{
public static void main(String[] args) {
//创建Date对象
Date d=new Date();
}
3.构造方法
3.1语法
[修饰符列表] 方法名([形参列表]){
方法体
}
(1)[]内容不是必须的,目前修饰符列表先默认写成public
(2)方法名必须和类名保持一致
(3)构造方法是一种特殊的方法,没有返回值类型的
(4)形参列表如果不写,就是无参构造
无参构造和有参构造方法属于方法的重载
3.2构造方法调用
new 构造方法名([实参]);
注:[1]构造方法不写的时候,系统会默认自动给生成无参构造
如果写了有参构造,系统不会默认生成无参构造
建议如果你写了有参构造,手动把无参构造也要加上
[2]构造方法虽然没有返回值类型,但它执行结束之后实际上会返回该对象在堆中的内存地址,然后把这个内存地址赋值给了这个变量,这个变量就是“引用”,通过这个引用我们就可以访问这块内存了;这个结果目前可以把它等同看做是对象的内存地址(严格来说不是真实的对象内存地址)
public class Date {
//年
int year;//
//月
int month;
//日
int day;
//无参构造
public Date(){
System.out.println("无参构造");
}
//有参构造
public Date(int _year,int _month,int _day){
System.out.println("有参构造");
year=_year;
month=_month;
day=_day;
}
public Date(int _year){
System.out.println("有参构造");
}
public Date(int _year,int _month){
System.out.println("有参构造");
}
}
class TestDate{
public static void main(String[] args) {
//创建Date对象
Date d=new Date();
System.out.println(d.year+"-"+d.month+"-"+d.day);
Date d1=new Date(2022,11,1);
System.out.println(d1.year+"-"+d1.month+"-"+d1.day);
}
}
3.3构造方法的作用
日期为什么都是0呢,执行构造方法的时候没有手动给属性赋值,系统会自动赋默认值。实际上我们可以在构造方法中手动给这些属性赋值--》构造方法的作用是专门用来创建对象同时给属性赋值的。
注意:无论手动赋值还是系统默认赋值,都是在执行构造方法的时候赋的值,因为执行了构造方法才会有对象,有了对象才能去访问实例变量
class TestDate{
public static void main(String[] args) {
//创建Date对象
Date d=new Date();
d.year=2022;
d.month=10;
d.day=14;
System.out.println(d.year+"-"+d.month+"-"+d.day);
Date d1=new Date(2022,11,1);
System.out.println(d1.year+"-"+d1.month+"-"+d1.day);
//没有对象,哪来的实例变量
//System.out.println(Date.year);
d1=null;
/*Exception in thread "main" java.lang.NullPointerException:
Cannot read field "year" because "d2" is null*/
System.out.println(d1.year+"-"+d1.month+"-"+d1.day);
}
}
注意:实例变量如果没有手动赋值,系统默认赋值---》都是构造方法在执行的时候才会去赋值,它是对象级别的。没有对象,哪来的实例变量。所以实例变量不能通过类名来访问,如System.out.println(Date.year);
3.4引用为空的时候
d1=null;
/*Exception in thread "main" java.lang.NullPointerException:
Cannot read field "year" because "d2" is null*/
System.out.println(d1.year+"-"+d1.month+"-"+d1.day);
以上程序语法正确,编译通过但是程序在运行阶段当d1 = null执行之后表示“引用d1”不再保存java对象的内存地址,相当于这条线断了就无法找到堆内存,也无法访问这个java对象的内容了,这种情况下就会发生空指针异常。就好比一个小孩儿放风筝,通过拽线来操控风筝,结果线断了,再拽风筝线的时候,已经无法再操控风筝了,这对于小孩儿来说是一种异常。而java程序中把这种异常叫做NullPointerException。
总之,当一个“空的引用”去访问实例变量时一定会发生空指针异常。
3.5实例变量是一个引用,String是引用类型
因为Student对象当中的name这个“属性/实例变量”是一个引用,保存的不是”zhangsan”字符串,而是字符串对象的内存地址。(按照实际来说,字符串”zhangsan”是在方法区的字符串常量池当中,这个后期再继续进行修正)----》接下来,我们再来看看当属性是其他类型引用的时候
Student 加入Date类型的birth属性
package com.example.course.demo03;
/*实例变量是引用数据类型*/
public class Test {
public static void main(String[] args) {
Person p=new Person();
p.name="zhangsn";
p.age=18;
p.sex=true;
//创建Date对象
Date d=new Date(2000,1,1);
p.birth=d;
System.out.println(p.name+p.age+p.sex);
System.out.println(p.birth.year+"-"+p.birth.month+"-"+p.birth.day);
}
}
class Person {
String name;//实例变量,对象级别的,只有有了对象才能去访问
//性别(男true 女false)
boolean sex;
int age;
//生日
Date birth;
//构造方法
public Person(){
}
public Person(String _name){
System.out.println("有参");
name=_name;
}
public Person(String _name,boolean _sex,int _age,Date _birth){
System.out.println("有参");
name=_name;
sex=_sex;
age=_age;
birth=_birth;
}
}
总结:对象中实例变量也可以是引用类型。那么当一个对象的属性是引用的时候应该如何访问这个引用所指向的对象呢?这里其实有一个规律,大家记住就行:类当中有什么就可以“.”什么,例如:Vip类中有birth属性,那么就可以v.birth,那么v.birth是Date类型,Date类当中有year属性,那么就可以v.birth.year,你懂了吗?
3.6构造方法调用的传参问题
/*变量传参问题
基本数据类型传的是数值,引用数据类型传的是内存地址
数值和内存地址都是“值”,所以基本数据类型和引用数据类型都属于“值”传递
*
* */
public class Test01 {
public static void main(String[] args) {
int a=10;
int b=a;//b=10
b=20;
System.out.println(a);//10
Person p1=new Person("张三");
Person p2=p1;//p1和p2存的是相同的内存地址,都指向了堆中相同的对象
Person p3=new Person("张三三");
System.out.println(p2);//内存地址
p2.name="李四";
System.out.println(p3.name);//张三三
}
}
class Person {
String name;//实例变量,对象级别的,只有有了对象才能去访问
//性别(男true 女false)
boolean sex;
int age;
//生日
Date birth;
//构造方法
public Person(){
}
public Person(String _name){
System.out.println("有参");
name=_name;
}
public Person(String _name,boolean _sex,int _age,Date _birth){
System.out.println("有参");
name=_name;
sex=_sex;
age=_age;
birth=_birth;
}
}
思考下:a赋值给b,a把什么给了b?p1赋值给p2,p1把什么给了p2?
其实a,b,p1,p2就是4个普通的变量,唯一的区别只是a和b都是基本数据类型的变量,p1和p2都是引用数据类型的变量(或者说都是引用),a变量中保存的那个“值”是10,p1变量中保存的那个“值”是0x8888(java对象内存地址),本质上来说10和0x8888都是“值”,只不过一个“值”是整数数字,另一个“值”是java对象的内存地址,大家不要把内存地址特殊化,它也是一个普通的值。
那么“赋值”是什么意思呢,顾名思义,赋值就是把“值”赋上去。a赋值给b,本质上是把a变量中保存的“值10”复制了一份给了b。p1赋值给p2本质上是把p1变量中保存的“值0x8888”复制了一份给了p2。
总结:通过以上内存图我们可以看出“赋值”运算的时候实际上和变量的数据类型无关,无论是基本数据类型还是引用数据类型,赋的都是值。只不过对于引用数据类型来说占两块内存,它赋的这个值是java对象在堆中的内存地址。所以最终会导致两个引用指向同一个堆内存中的java对象,通过任何一个引用去访问堆内存当中的对象,此对象内容都会受到影响。我们来验证一下,让a++,a应该变成了11,但是b不会变,让p1.name = “波利”,然后输出bird2.name的结果肯定也是”波利”
3.7实例代码块
package com.example.course.demo04;
/*
*
* 实例变量和实例方法都是属于对象级别的。
* 实例方法的方法体中可以调用其他的实例方法。
*
* 实例代码块
* 语法结构:{}
* */
public class Dog {
{
System.out.println("我是实例代码块的内容我,对象级别的");
System.out.println("注意:我是构造方法执行之前执行的");
}
//成员变量中实例变量
private String name;
private String pinzhong;
//无参构造方法
public Dog(){
System.out.println("无参构造");
}
//成员方法中的实例方法
public String getName(){
return name;
}
public void eat(){
System.out.println("吃骨头");
shake();
}
public void haul(){
System.out.println("汪汪~");
guard();
}
public void guard(){
System.out.println("看门~");
}
public void shake(){
System.out.println("摇尾巴");
haul();
}
}
4.封装
4.1什么是封装
封装从字面上来理解就是包装的意思,专业点就是信息隐藏。在现实世界当中我们可以看到很多事物都是封装好的,比如“鼠标”,外部有一个壳,将内部的原件封装起来,至于鼠标内部的细节是什么,我们不需要关心,只需要知道鼠标对外提供了左键、右键、滚动滑轮这三个简单的操作。对于用户来说只要知道左键、右键、滚动滑轮都能完成什么功能就行了。
4.2封装的好处?
--封装可以隐藏内部实现细节,对外只提供了简单的安全的操作入口,所以封装之后,实体更安全了。
--代码来说降低程序的耦合度,提高程序的扩展性,以及重用性或复用性;例如“鼠标”可以在A电脑上使用,也可以在B电脑上使用。
4.3怎么用?
通过案例感受下没有封装,程序会有什么问题。
package com.example.course.demo04;
import com.example.course.demo03.Date;
/*
封装:
1.生活中的封装
(1)鼠标:外壳把里面的组件封装起来了,只暴露左键、右键、滚轮。
对于用户来说,鼠标内部怎么工作的,不用关心,只需要知道左键、右键、滚轮
(2)好处:
安全:里面的组件你是接触不到
扩展性好:在A电脑能用,在B电脑也能用
2.没有封装的案例
结论:对类中的属性进行封装,外部程序不能随便访问呢
3.如何封装
(1)用private(私有的)修饰属性
(2)给外部程序提供访问的接口
对属性的访问无非就读和写,所以我们开发两个方法get(读取)/set(写)方法
含有static修饰的方法叫静态方法,通过 "类名.方法名()"调用----》类级别
没有static修饰的方法叫实例方法,----》对象级别
注意 (1)get/set方法是实例方法,是对象级别的
(2)get/set方法首字母小写,后面遵循驼峰标志。
(3)set可以为属性赋值(构造方法也可以为属性赋值)
* */
public class Test {
public static void main(String[] args) {
Person p=new Person();
/*p.age=18;
p.age=-18;//与现实不符
System.out.println(p.age);*/
p.setAge(-18);
System.out.println(p.getAge());
Dog d=new Dog();
d.eat();
}
}
class Person {
private String name;
//性别(男true 女false)
private boolean sex;
private int age;
//生日
private Date birth;
//get方法
//public static 含有
public int getAge(){
return age;
}
public void setAge(int _age){
if(_age<0||_age>100){
System.out.println("年龄必须0~100之间");
}else{
age=_age;
}
}
public String getName(){
return name;
}
//构造方法
public Person(){
}
public Person(String _name){
System.out.println("有参");
name=_name;
}
public Person(String _name, boolean _sex, int _age, Date _birth){
System.out.println("有参");
name=_name;
sex=_sex;
age=_age;
birth=_birth;
}
}
学生类,年龄设置负数--》编译运行都没有问题,但是结构与现实不符。所以外部的程序不能随便访问这个属性,需要对属性进行封装---》加private,报错--》设置完之后 太安全,安全的外部的程序都无法访问了,所以这个时候就需要进入封装的第二步了:对外提供公开的访问入口,让外部程序统一通过这个入口去访问数据就可以了。
对于“一个”属性来说,我们对外应该提供几个访问入口呢?通常情况下我们访问对象的某个属性,不外乎读取(get)和修改(set),所以对外提供的访问入口应该有两个,这两个方法通常被称为set方法和get方法(还记得之前我们接触的方法都是被static修饰的,这些方法直接采用“类名”的方式调用,而不需要创建对象,在这里显然是不行的,set和get方法属于对象级别的,“不同的对象”调用get方法最终得到的数据是不同的,例如zhangsan调用getName()方法得到的名字是zhangsan,lisi调用getName()方法得到的名字是lisi,显然get方法是一个对象级别的方法,不能直接采用“类名”调用,必须先创建对象,再通过“引用”去访问。所以这个方法定义的时候不能使用static关键字修饰,不用static修饰方法我们又叫实例方法。实例方法必须使用“引用”的方式调用。)。
4.4总结
--封装的步骤应该是这样的:需要被保护的属性使用private进行修饰,给这个私有的属性对外提供公开的set和get方法,其中set方法用来修改属性的值,get方法用来读取属性的值。
--set和get方法在命名上也是有规范的,规范中要求set方法名是set + 属性名(属性名首字母大写),get方法名是get + 属性名(属性名首字母大写)。
--其中set方法有一个参数,用来给属性赋值,set方法没有返回值,而get方法不需要参数,返回值类型是该属性所属类型
--先记住,以后讲:另外set方法和get方法都不带static关键字,不带static关键字的方法称为实例方法,这些方法调用的时候需要先创建对象,然后通过“引用”去调用这些方法,实例方法不能直接采用“类名”的方式调用。
有的读者可能会有这样的疑问:构造方法中已经给属性赋值了,为什么还要提供set方法呢?注意了同学们,这是两个完全不同的时刻,构造方法中给属性赋值是在创建对象的时候完成的,当对象创建完毕之后,属性可能还是会被修改的,后期要想修改属性的值,这个时候就必须调用set方法了。