基本思想
面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)的主要思想是把构成问题的各个事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙一个事物在整个解决问题的步骤中的行为。面向对象程序设计中的概念主要包括:对象、类、数据抽象、继承、动态绑定、数据封装、多态性、消息传递。通过这些概念面向对象的思想得到了具体的体现。
易混概念
“面向对象”和“基于对象”
面向对象的三大特点(封装,继承,多态)缺一不可。通常“基于对象”是使用对象,但是无法利用现有的对象模板产生新的对象类型,继而产生新的对象,也就是说“基于对象”没有继承的特点。而“多态”表示为父类类型的子类对象实例,没有了继承的概念也就无从谈论“多态”。很多流行技术都是基于对象的,它们使用一些封装好的对象,调用对象的方法,设置对象的属性。但是它们无法让程序员派生新对象类型。他们只能使用现有对象的方法和属性。所以当你判断一个新的技术是否是面向对象的时候,通常可以使用后两个特性来加以判断。“面向对象”和“基于对象”都实现了“封装”的概念,但是面向对象实现了“继承和多态”,而“基于对象”没有实现这些,的确很饶口。
“面向过程”和“面向对象”
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
可以拿生活中的实例来理解面向过程与面向对象,例如五子棋,面向过程的设计思路就是首先分析问题的步骤:1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤2,9、输出最后结果。把上面每个步骤用不同的方法来实现。
如果是面向对象的设计思想来解决问题。面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为1、黑白双方,这两方的行为是一模一样的,2、棋盘系统,负责绘制画面,3、规则系统,负责判定诸如犯规、输赢等。第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。
可以明显地看出,面向对象是以功能来划分问题,而不是步骤。同样是绘制棋局,这样的行为在面向过程的设计中分散在了多个步骤中,很可能出现不同的绘制版本,因为通常设计人员会考虑到实际情况进行各种各样的简化。而面向对象的设计中,绘图只可能在棋盘对象中出现,从而保证了绘图的统一。
举例:小王老师用电脑上课
老师{
姓名;
上课(电脑){
电脑.开();
电脑.运行();
电脑.关();
}
}
电脑{
开(){}
运行(){}
关机(){}
}
老师 t = new 老师();
t.上课(上课);
这两句发生了什么
不要去的对象里赋值
调用的时候再去赋值
此时堆栈情况:
类与对象的关系
类是对象的模板,对象是类的实例
类:对事物的描述,属性+行为
对象:该类事物创建的一个实例,可以通过对象名去调用类里面的属性和行为
成员变量与局部变量的区别
生命周期:
成员变量是随着对象的加载而加载,随着对象的消失而消失
局部变量是随着方法的运行而出现,随着方法的弹栈而消失
定义位置:
成员变量只能定义在类中(对象里)
局部变量可以定义在方法或者语句中
初始化:
成员变量默认有初始值
局部变量没有默认值,必须赋值后才可以使用
内存分配位置:
成员变量在 堆内存中
局部变量在 栈方法中
匿名对象
public class Test_2 {
public static void main(String[] args) {
Phone p = new Phone();
p.brand = "vivo";
p.color = "black";
p.price = 20;
p.run();
//用匿名对象的方法去调用
new Phone().brand="oppo";//0x01
new Phone().color="red";//0x02
new Phone().price=10;//0x03
new Phone().run();//0x04
// 以上内容创建了多个对象,在堆内存中存在着4个对象(new了4次)
}
}
输出结果是默认值?
原因:
1.每一个 new 都在堆内存中开辟了一个空间,给Phone对象,而Phone对象中有成员变量。且成员变量是有默认值的。接着 .brand .price .color 是把各自Phone对象里的成员变量修改掉。
2.第四个new 也是在堆内存中新建了一个Phone对象,然后要去调用Phone对象的 run() 方法。调用前并没有修改其中成员变量的属性,因此这个run()输出的是三个属性的默认值。
3.执行之后前三个new没有用,就变成了垃圾
匿名对象使用场景
public class Test_3 {
public static void main(String[] args) {
// 普通调用模式
Phone phone= new Phone();
phone.run();
phone.run();
phone.run();//创建了一个对象,对某个方法调用了多次
//以上调用run()方法的形式,不能简化成匿名对象,它会产生多个对象
//当调用一个可以简化成匿名对象,多次就不行
//简化匿名对象
new Phone().run();//创建了多个对象,并且调用了多次
new Phone().run();
new Phone().run();
new Phone().run();
}
}
匿名对象好处:
方便书写,但一不小心变海王
减少代码重复性
package javaSE_day6;
public class Test_4 {
public static void main(String[] args) {
Phone p=new Phone();
p.brand="oppo";
p.color="white";
p.price=5000;
p.run();
Phone p1=new Phone();
p1.brand="oppo";
p1.color="white";
p1.price=5000;
p1.run();
}
}
/* 从这一段代码中看出这是两个手机,如果10000个怎么办,
new 10000个吗?*/
//解决方法:产生的都是新手机,可以将手机作为一个对象传递到这个方法中
//show()
package javaSE_day6;
public class Test_4 {
public static void main(String[] args) {
Phone p=new Phone();
show(p);
Phone p1=new Phone();
show(p1);
}
/* 从这一段代码中看出这是两个手机,如果10000个怎么办,
new 10000个吗?*/
//解决方法:产生的都是新手机,可以将手机作为一个对象传递到这个方法中
public static void show(Phone p){
p.brand="oppo";
p.color="white";
p.price=5000;
p.run();
}
}
## 封装 对属性封装,增加安全性
setXxx() , getXxx()
下面的例子中将Person的age属性 设置为私有,用set get进行封装 ```java public class Person { //属性 private int age; //设置值 public void setAge(int a){ if (a>0 && a<200) { age=a; //赋值 }else { System.out.println("你的年龄非法"); } } //取值 public int getAge(){ return age; } //行为 public void speak(){ System.out.println("age="+age); } } ``` ```java public class Test_01 {
public static void main(String[] args) {
// 创建对象
Person p = new Person();
//对属性赋值
p.setAge(40);
System.out.println(p.getAge());
//调用方法
p.speak();
}
}
## 构造方法
对类进行初始化(对类里面的属性进行赋值),每当new一个对象时就会调用<br />编译器:你有就用你自己的,没有我给你加一个
```java
public class Student {
//属性
private String sex;
private int age;
//构造方法
Student(){
System.out.println("A");
}
//行为
public void show(){
System.out.println("sex:"+sex+"\tage="+age);
}
}
public class StudentTest {
public static void main(String[] args) {
// 创建对象
Student stu = new Student();//new的时候过去调用构造方法
stu.show();
}
}
构造方法—“先天” 赋值
public class Student {
//属性
private String sex;
private int age;
//构造方法
Student(String s,int a){
System.out.println(s+a);
sex=s;
age=a;
}
//行为
public void show(){
System.out.println("sex:"+sex+"\tage="+age);
}
}
public class StudentTest {
public static void main(String[] args) {
// 创建对象
Student stu = new Student("男",78);
stu.show();
}
}
构造方法内存图
构造方法_总结
1.没有返回值,因为对象创建之后就结束了,不需要结果,void不需要写,要区别一般方法。
2.一个类可以有多个构造方法,它们都是以重载的格式出现的
3.构造方法也有return语句的,用于初始化结束(加不加都行,后面都没有语句了)
4.构造方法 可以用private修饰,但是没有意义
构造方法和一般方法的区别
1.写法不同
2.运行上有差别
构造方法:对象一创建就会调用相应的构造方法
一般方法:对象创建之后调用的
3.调用的次数不同
构造方法在创建对象的时候只调用一次
一般方法可以用对象名调用多次
构造之后可以封装吗?
可以
souce里面找set get 直接封装到类里
public class Student {
//属性
private String sex;
private int age;
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//构造方法
Student(String s,int a){
System.out.println(s+a);
sex=s;
age=a;
}
//行为
public void show(){
System.out.println("sex:"+sex+"\tage="+age);
}
}
在实例中用stu去调用set
public class StudentTest {
public static void main(String[] args) {
// 创建对象
Student stu = new Student("男",78);
stu.setSex("女");
stu.setAge(18);
stu.show();
}
}
this的指向作用——渣出天际的this
this:我是this,每天都会有形形色色的对象找到我,并和我在堆和栈中进行一些深入交流。这个例子中我在print()方法的房间里,你们哪个对象谁一调用print(),进来就会找到我,我就会马上记住你的地址,顺着你的地址,我就可以找到你在堆中成员变量的值。其实当你进来调用的那一刻,我就是你的人了。但我不能只是你一个人的,我愿意拥抱任何一个调用我的对象。
构造方法“真正的”书写格式
有 this
private String name;
private String sex;
public Teacher(String name, String sex) {
this.name = name;
this.sex = sex;
}
总结:
构造方法是对象初始化调用的?给哪个对象初始化了?
通过this关键字记录住对象的地址,并通过this来明确初始化对象
在构造方法中调用其他构造方法
this(参数);
this到底代表了什么?
this代表的就是一个对象,代表哪一个对象了?代表的是当前对象,哪个调用所在方法的this就指向哪个对象
构造方法可以调用一般,一般方法不可以调用构造方法
一个构造方法只可以构造一个其他的构造方法(this写在第一行)
不能递归调用
static
静态调用,静态是随着类的加载而加载
1 . 用来修饰成员 2 . 静态不能访问非静态
//静态调用,类名.方法名();
Person.sleep();
//普通方法调用
Person p=new Person();
p.sleep();
态不能访问非静态_例01:
public class Person {
private String sex;
private int age;
private String name;
public static void sleep(){
System.out.println("sex"+sex);
System.out.println("age"+age);
System.out.println("name"+name);
}
}
报错原因:静态不能访问非静态
分析过程:
1.内存里加载一个对象Person
2.对象里,静态的属性/方法先被加载,这里会先加载sleep()
3.而sleep()里的age,name,sex属性,还没有加载进来,在内存里找不到,报错。
如何解决:给属性也加上静态。或者把方法里的static去掉。
正解:
public class Person {
private static String sex;
private static int age;
private static String name;
public static void sleep(){
System.out.println("sex"+sex);
System.out.println("age"+age);
System.out.println("name"+name);
}
}
静态不能访问非静态_例02
在静态方法sleep()里定义了一个“非静态”的局部变量sex,
sleep()是静态方法,它里面代码块,syso方法里的sex,age,name当然也是静态的。本来syso里的sex是静态的,现在被新定义的局部变量一搞,变成非静态的了。
那么把新定义的局部变量加一个static试试
发现还是报错了。
原因是,静态只能修饰成员变量,不可以修饰局部变量。
那么什么时候定义静态变量呢?
静态变量和成员变量的区别
- 所属范围不同:静态是属于类的,成员变量是属于对象的
- 调用不同:静态变量可以被对象和类调用(一般都用类名调用)
成员变量只能由对象调用
- 加载时期不同:静态变量随着类的加载而加载,成员变量随着对象的加载而加载
- 内存存储的区域不同:静态变量是存储在方法区的,成员变量是存储在堆内存中的
单例
保证对象唯一性
(test里去new对象会报错,因为new的时候要去调用构造,而单例用的是私有化构造方法)
分类:饿汉;懒汉。
懒汉可以延迟加载,饿汉不行,饿汉一加载就会创建一个对象,懒汉是这个对象一开始是null,当进入方法时创建对象
饿汉式单例如下:
public class Single {
//私有化构造方法
private Single(){
}
//实现对象的可控性,在本类中创建一个对象
static Single s=new Single();
//对外提供一个访问的方法
public static Single getInstance(){
return s;
}
}
public class SingleTest {
public static void main(String[] args) {
//静态调用要用 类名.方法名
Single.getInstance();
//new对象的方式,此调用不符合单例格式,不要用这个格式
//new Single.getInstance();
//s1,s2都是同一个对象
Single s1 = Single.getInstance();
System.out.println(s1);
Single s2 = Single.getInstance();
System.out.println(s2);
}
}
饿汉式单例的弊端:
不管后期有没有用到这个对象,都进行了对象的创建
这样做的好处是编写简单,但无法做到延迟加载
延迟加载例:
用户名密码登录,首先要拿一个验证,要去查询有没有这个东西,如果这个时候不去查询,不去做一个延迟加载,用户名和密码一写就登陆成功就不对了
懒汉式单例如下
//懒汉式
public class Single02 {
//1.私有化构造方法
private Single02(){
}
//2.在本类中创建对象
static Single02 s = null;
//3.对外提供一个可访问方法
static Single02 getInstance(){
if (s==null) {
s=new Single02();
}
return s;
}
}
public class Single02Test {
public static void main(String[] args) {
//静态调用
Single02.getInstance();
}
}
再看一个例子(如下):
public class SuperMan {
//属性
private String name;
//1.定义构造
private SuperMan(String name) {
this.name = name;
}
//2.在本类中创建一个对象
static SuperMan s = new SuperMan("小强");
//在本类中定义一个方法
public static SuperMan getInstance(){
return s;
}
public void fly(){
System.out.println(name+"可以飞");
}
}
public class SuperManTest {
public static void main(String[] args) {
// 用单例的方法解决了对象唯一性
SuperMan s33=SuperMan.getInstance();
System.out.println(s33);
s33.fly();
SuperMan s2=SuperMan.getInstance();
System.out.println(s2);
s2.fly();
}
}
类文件的创建过程:
声明 类/对象名为 SuperMan
声明SuperMan的一个私有属性name(成员变量)
定义SuperMan类的私有构造(用于创建对象时先天赋值),指明以后要创建SuperMan类的对象,就要传进来一个name,把这个name赋给当前类的名字
创建一个SuperMan类的一个对象 ,并带上名字“小强”,给这个新建的对象起名为s;
在SuperMan类中提供一个可供外界访问的方法,到时候外界通过 "变量名点方法名()"的形式调用这里;这个方法的写法是 “类名空格方法名”,由于要在外界访问,所以要public,又因为外界main()是用的static静态调用,所以这里也要static;方法返回的是第二步新建的对象s,这个对象s目前已被创建,因此在内存中占一定的空间,且它只有一个属性,也就是它的名字“小强”;因此这里完整的写法是“public static 大写类名 空格 方法名(){ return 新建对象变量名,这里是s;}”
就是说此后外界只要调用getInstance(),就会得到关于对象s的所有信息;当然必须要有一个和对象s完全匹配的载体,来全盘接收对象s的信息。所以只需要定义一个和对象s相同的类,再取一个名字,就可以把getInstance()的值赋过来了;
最后定义了一个fly()方法,可以看出要使用fly()方法,必须要有name的值,这里的name指向的其实是this.name,也就是当前类的成员变量name的值,当前类的类名已经有了,是新建对象s传进去的“小强”。
所以外界成功调用fly()方法的条件只有一个,那就是用同样类的变量去调用。刚好getInstance()那里定义了一个同类的变量,拿来用就好。
回调过程中的思路:
SuperMan.getInstance()返回的东西赋给s33,事实上返回的东西是对象s,而对象s只有一个属性,就是它的名字“小强”。
这里输出的是s33的地址,也就是s的地址
对象s33调用fly()方法,fly()方法中要用到当前类的成员变量name的值(this.name),通过构造赋值,得知this.name是“小强”
继承
继承的好处:
提高了代码的复用性,给另一个特征提供了前提(多态)
什么时候继承?
必须保证类与类之间存在关系,肉食动物是动物的一种,苹果是水果的一种,存在的是is—a的关系
java中继承的特点:
java允许单继承,不能直接继承多个类,将多继承以另一种形式体现
**单继承:**子类只可以有一个父类
**多继承:**一个子类可以有多个父类
单继承体系_;例:
//子类Student
public class Student extends Person {
Student(String name, int age) {
super(name, age);
}
//构造方法
//行为
void study(){
System.out.println("name"+name+"\tage"+age);
}
}
//子类Teacher
public class Teacher extends Person {
//构造方法
Teacher(String name,int age) {
super(name,age);
}
//行为
void work(){
System.out.println("name"+name+"\tage"+age);
}
}
//父类Person
public class Person {
//属性
String name;
int age;
//构造方法
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
//测试Test
public class Test {
public static void main(String[] args) {
//创建对象
Student stu = new Student("小明", 30);
stu.study();
Teacher tea = new Teacher("coco", 30);
tea.work();
//当new对象时,找到构造方法后发现,构造方法里存在super,代表找父类
//相当于把参数传到了父类,这里父类做了一个赋值操纵
//把当前对象的属性进行了赋值,所以打印时是有结果的
}
}
this和super的区别:
this代表的是当前对象
super代表的是父类的引用,super()不可以单独使用,必须要super.变量;或者super();
public class Fu {
int num=4;
}
public class Zis extends Fu {
int num=5;
void show(){
System.out.println("num="+super.num);
}
}
public class ZisTest {
public static void main(String[] args) {
Zis f = new Zis();
f.show();
}
}
覆盖
子类在运行时,它里面定义了一个和父类一样的方法名,子类里面的方法重写(覆盖)父类里的方法;
什么情况下去用覆盖呢?
子类要扩展父类功能的时候
继承的弊端
继承相当于打破了封装
如果不让父类不让子类重写怎么办?
在方法上加一个final就可以了
继承在程序中的体现
1.成员变量
2.成员方法
3.构造方法
隐式super()
public class Test {
public static void main(String[] args) {
// 创建对象
Zi zi=new Zi();//Fu Zi
}
}
public class Zi extends Fu{
Zi() {
super();//这个子类的构造里存在一个隐式的super()
System.out.println("Zi");
}
}
public class Fu {
public Fu() {
System.out.println("Fu");
}
}
子类会继承父类的内容,所以子类在初始化时,必须要先找到父类去执行父类的初始化操作,才可以更方便地调用父类中的内容。
出现super报错的情况;为什么?
因为参数列表不对应,在父类中没有找到相应的构造方法
如何解决?
在子类的super里传参,或者修改/增加父类的构造
经典父A子C
public class Fu1 {
Fu1() {
System.out.println("fu...A");
}
Fu1(int x){
System.out.println("fu...B");
}
}
public class Zis extends Fu1{
Zis() {
System.out.println("Zis....C");
}
Zis(int a){
System.out.println("Zis...D");
}
}
public class ZisTest {
public static void main(String[] args) {
//创建子类对象
Zis z =new Zis();
}
}
经典父A子D
提示:继承体系中,默认添加的隐式super()是不带参数的
public class ZisTest {
public static void main(String[] args) {
//创建子类对象
Zis z =new Zis(5);
}
}
public class Zis extends Fu1{
Zis() {
System.out.println("Zis....C");
}
Zis(int a){
System.out.println("Zis...D");
}
}
public class Fu1 {
Fu1() {
System.out.println("fu...A");
}
Fu1(int x){
System.out.println("fu...B");
}
}
如何输出父B子D呢?
就需要手动在子类里添加带参super
这里就不演示了
this()和super()不能同时出现在一个构造里。
final
final是一个关键字:可以修饰类,方法,变量(成员,局部,静态)
特点:
1.final修饰的类是一个最终类,不能再派生子类
2.如果类中有些方法不需要重写,让指定的方法加一个final即可
因为final它是一个最终方法,不可以重写
3.final修饰量是一个常量,只能被赋值一次
什么时候去定义一个常量?当程序中不需要更改的时候就可以定义为一个常量
4.final定义的规范:被final修饰的常量字母都是大写的,如果有多个字母组成,就用_来连接 final doule PI=3.14;
5.final会把变量最终话,只能显示赋值
public class FinalTest {
int a;
public static void main(String[] args) {
FinalTest f =new FinalTest();
System.out.println(f.a);
}
}
这里的 a是有默认值的
而如果在int a 前加一个final,会报错
报错原因:说是没定义,其实是因为final修饰的变量必须要先赋值,在使用