面向对象
定义
把数据及对数据的操作方法放在一起,作为一个相互依存的整体——对象。对同类对象抽象出其共性,形成类。类中的大多数数据,只能用本类的方法进行处理。类通过一个简单的外部接口与外界发生关系,对象与对象之间通过消息进行通信。程序流程由用户在使用中决定。对象即为人对各种具体物体抽象后的一个概念,人们每天都要接触各种各样的对象,如手机就是一个对象。
类与对象
对象:具体的事物,具体的实体(有状态(属性),有行为(方法))。例如:学生是一个对象,状态:age,name,height…行为:吃饭,睡觉,打游戏…
类:对对象向上抽取出像 的部分,形成类,类是抽象的,是一个模板。例如:Student是一个对象,Teacher也是一个对象,他们共同属性是:age,name,height,共同行为有:吃饭,睡觉,那么就可以向上抽取共同的部分创建一个Person类,被创建的Person又可以创建无数个对象,如:员工对象,他拥有的属性:name,age。行为:吃饭睡觉(当然一个具体的对象还可以拥有自己独特的属性和行为,类只是一个创建对象的模板,它是抽象的)
面向对象三个阶段
OOA:面向对象分析(Object Oriented Analysis)
OOD:面向对象设计(Object Oriented Design)
OOP:面向对象编程(Object Oriented Programming )
类与对象的创建
创建类
代码
-
创建一个Person类: public class Person {
int age;
String name;
public void eat(){
System.out.println("吃饭");
}
public void sleep(String address){
System.out.println("我在"+address+"睡觉");
}
public String introduce(){
return "名字:"+name+",年龄:"+age;
}
}
-
专门创建一个Test类(用于完成业务逻辑代码) public class Test {
public static void main(String[] args) {
Person sh= new Person();
sh.age=22;
sh.name="苏欢";
Person yu = new Person();
yu.name = "李雨";
yu.age = 20;
System.out.println(sh.name+sh.age+yu.name+yu.age);
sh.eat();
yu.eat();
sh.sleep("我是");
yu.introduce();
String str = yu.introduce();
System.out.println(str);
System.out.println(yu.introduce());
}
}
-
总结:
- 创建对象时会进行类的加载,且只在第一次创建对象时加载一次
- 属性:通过赋值有自己独立的值
- 方法:对象调用类中通用的方法,即共享的
成员变量和局部变量
成员变量(可以称类的属性)
- 位置:在类中方法外
- 作用范围:当前类中所有方法里面或则代码块里面
- 有默认值
引用数据类型默认:null
-
内存位置:堆中 -
作用时间:当前对象创建到销毁
局部变量
- 位置:在方法中或则代码块中
- 作用范围:当前的方法中或代码块中
- 没有默认值,所以必须初始化
- 内存位置:栈中
- 作用时间:当前方法执行到结束
※构造器
-
定义 构造器通常也叫构造方法、构造函数,构造器在每个项目中几乎无处不在。当你new一个对象时,就会调用构造器。构造器格式如下: [修饰符,比如public] 类名 (参数列表,可以没有参数){
}
-
默认构造器 如果没有定义构造器,则会默认一个无参构造器,这就是为什么你定义了一个对象,比如 People,没有定义任何构造器却可以new这个对象,比如 new People() 。如果自定义了构造器,则会覆盖默认构造器。例如: public Person(){
}
-
作用 不是为了创建对象,在调用构造器之前构造器就已经创建好了,并且属性有默认初始化的值,给对象属性进行一系列赋值操作 Person p = new Person();//创建对象在堆里开辟空间,系统给空间赋一个地址,最后地址给了左侧的p -
※构造器重载 与普通方法一样,构造器也支持重载。一个对象中是可以支持同时定义多个构造器,通过不同的参数列表来实现重载。经常看到代码中new一个对象时,有时传入参数,有时又可以不用传。比如:new Person()跟new Person(“张三 ”),这里就是重载了。 利用构造器重载给属性赋值:
public Person(String a,int b){
name = a;
age = b;
}
public Person(String a){
name = a;
}
一般的来讲定义形参的时候会要求见名知意,这就出现一个问题,形参名字与属性名重名,例如上面的a,b换成name和age。怎样规避这个问题呢? 形参不变,属性前加this. 例如改进上面代码: public Person(String name,int age){
this.name = name;
this.age = age;
}
public Person(String a){
name = a;
}
this代表你创建的那个对象(this.name: 当前对象的名字) -
注意
- 没有返回值
- 内部不能有return语句
- 构造器必须和类名一样
- 一般不会在空构造器里面赋初始值,因为每个对象属性都一样了
- 与位置顺序无关
- 一般保证空构造器的存在
- 当重载构造器以后,假如忘记写,系统不会分配默认的空构造器,那么调用就会出错
内存分析
-
代码1 public class Person {
int id;
int age;
public static void main(String args[]){
Person p1= new Person();
}
}
分析图 -
代码2 public class Person {
int id;
int age;
String school;
public Person (int a,int b,String c){
id=a;
age=b;
school=c;
}
public static void main(String args[]){
Person p= new Person(1,20, "海淀");
}
}
分析步骤: 1.首先为main方法开辟栈帧 2.第一次遇见Person时,加载字节码信息,加载到方法区 3.根据字节码信息在堆中创建对象 4.系统会进行默认初始化 5.调用new关键字,进入构造器进行赋值 6.为Person构造器开辟栈帧 7.将1赋值给a,20赋值给b,而字符串比较特殊,方法区中有字符串常量池专门放字符串 遇到“海淀”时,将“海淀”放入字符串常量池中对应产生一个地址 8.进入构造器进行一系列赋值操作 9.构造器结束,产生的形参,局部变量都要消失 10.对象创建完成,赋值完成 11.将左侧赋值给右侧的p,p在main方法对应的栈帧中 分析图 -
代码3 class Person{
int id;
int age;
String school;
Person (int a,int b,String c){
id=a;
age=b;
school=c;
}
public void setAge(int a){
age=a;
}
}
public class Test {
public static void main(String[] args) {
Test t=new Test();
int age=40;
Person tom=new Person(1,20,"海淀");
Person jack=new Person(2,30,"朝阳");
t.change1(age);
t.change2(tom);
t.change3(jack);
System.out.println(age);
System.out.println("id:"+jack.id+",age:"+jack.age+",school:"+jack.school);
}
public void change1(int i){
i=3366;
}
public void change2(Person p){
p=new Person(3,22,"西城");
}
public void change3(Person p){
p.setAge(66);
}
}
分析图
this
创建对象的过程:
- 在第一次遇到一个类的时候,对这个类要进行加载,只加载一次。
- 创建对象,在堆中开辟空间
- 对对象进行初始化操作,属性赋值都是默认的初始值。
- new关键字调用构造器,执行构造方法,在构造器中对属性重新进行赋值
用法:
1.this修饰属性
当属性名字和形参发生重名的时候,或者 属性名字 和局部变量重名的时候,都会发生就近原则,所以如果我要是直接使用变量名字的话就指的是离的近的那个形参或者局部变量,这时候如果我想要表示属性的话,在前面要加上:this.修饰
2.this修饰方法
public void play(){
this.game();
System.out.println("上网");
System.out.println("洗澡");
}
public void game(){
System.out.println("打游戏");
}
3.this修饰构造器
public Person(){
}
public Person(int age,String name,double height){
this(age,name);
this.height=height;
}
public Person(int age,String name){
this(age);
this.name=name;
}
public Person(int age){
this.age=age;
}
总结:同一个类中的构造器可以相互用this调用,注意:this修饰构造器必须放在第一行
static
1.static修饰属性
public class Test {
int id;
static int sid;
public static void main(String[] args) {
Test t1 = new Test();
t1.id = 10;
t1.sid = 10;
Test t2 = new Test();
t2.id = 20;
t2.sid = 20;
Test t3 = new Test();
t3.id = 30;
t3.sid = 30;
System.out.println(t1.id);
System.out.println(t2.id);
System.out.println(t3.id);
System.out.println(t1.sid);
System.out.println(t2.sid);
System.out.println(t3.sid);
}
}
分析图 一般官方的推荐访问方式:可以通过类名.属性名的方式去访问:
Test01.sid=100;
System.out.println(Test01.sid);
总结:
- 在类加载的时候一起加载入方法区中的静态域中
- 先于对象存在
- 访问方式: 对象名.属性名 类名.属性名(推荐)
static修饰属性的应用场景:某些特定的数据想要在内存中共享,只有一块 --》这个情况下,就可以用static修饰的属性
public class MsbStudent {
String name;
int age;
static String school;
public static void main(String[] args) {
MsbStudent.school = "马士兵教育";
MsbStudent s1 = new MsbStudent();
s1.name = "张三";
s1.age = 19;
MsbStudent s2 = new MsbStudent();
s2.name = "李四";
s2.age = 21;
System.out.println(s2.school);
}
}
属性: 静态属性 (类变量) 非静态属性(实例变量)
2.static修饰方法
package com.suhuan.Test02;
public class Demo {
int id ;
static int sid;
public void a(){
System.out.println("a方法");
}
public static void b(){
System.out.println("b方法");
}
public static void main(String[] args) {
Demo d = new Demo();
d.a();
Demo.b();
d.b();
b();
}
}
总结归纳:
-
public和static都是修饰符,没有先后顺序的说法 -
在静态方法中不能访问非静态属性(原因:static修饰的东西先于对象存在,在类加载的时候就被加载,static加载完了还没有对象,b方法就可以直接调用,不用依托对象去调用id是属于对象特有的属性,所以没对象就去访问id是不合理的) -
静态方法中不能访问非静态方法 -
在静态方法中不能访问this关键字 -
非静态方法可以用类名去调用 -
静态方法对象和类名都可以调用
代码块
类的组成:属性,方法,构造器,代码块,内部类
代码块分类:
1.普通块
位置:在方法中
作用:限制了局部变量的作用范围
2.构造块
位置:方法外
作用:解决在方法外写代码的能力
3.静态块
位置:方法外,加static修饰
注意:在静态块中只能访问静态属性和静态方法
4.同步块(多线程)
public class Test {
int a;
static int sa;
public void a(){
System.out.println("业务逻辑1");
{
int a=10;
System.out.println(a);
}
}
{
System.out.println("123");
}
static{
System.out.println("静态块");
System.out.println(sa);
b();
}
public static void b(){
System.out.println("业务逻辑2");
}
public Test(){
}
public Test(int a){
this.a=a;
}
public static void main(String[] args) {
Test t = new Test();
t.a();
Test t1 = new Test();
t1.a();
}
}
总结:
代码块的执行顺序:(由上往下)
- 静态块(只在类加载的时候执行一次,所以以后实战写项目,数据库的初始化信息都放入静态块,一般要用于执行一些全局性的初始化操作)
- 构造块
- 构造器
- 普通块
包,import
1.包的作用:
为了解决重名问题(实际上包对应的就是盘符上的目录) 解决权限问题
2.包名定义:
- 名字全部小写
- 中间用.隔开
- 一般都是公司域名倒着写 : com.jd com.msb
- 加上模块名字:com.jd.login com.jd.register
- 不能使用系统中的关键字:nul,con,com1—com9…
- 包声明的位置一般都在非注释性代码的第一行
3.导包问题
在创建对象的时候,如果在同一个类下不用导包,如果没有在同一个类下就要自动导包或则手动导包
4.总结
总结: (1)使用不同包下的类要需要导包: import **..; 例如:import java.util.Date; (2)在导包以后,还想用其他包下同名的类,就必须要手动自己写所在的包。 (3)同一个包下的类想使用不需要导包,可以直接使用。 (4)在java.lang包下的类,可以直接使用无需导包:
(5)IDEA中导包快捷键:alt+enter ,可以自己设置自动导包
(6)可以直接导入*
(7)在Java中的导包没有包含和被包含的关系
5.静态导入
package com.suhuan.Test03;
import static java.lang.Math.*;
public class Demo {
public static void main(String[] args) {
System.out.println(random());
System.out.println(PI);
System.out.println(round(2.3));
}
public static int round(double a){
return 100;
}
}
三大特性
封装 (Encapsulation)
定义:面向对象的封装就是把描述一个对象的属性和行为的代码封装在一个类中,有些属性是不希望公开的,或者说被其他对象访问的,所以我们使用private修饰该属性,使其隐藏起来;类中提供了方法(用public修饰),常用的是getter、setter方法,可以操作这些被隐藏的属性,其他类可以通过调用这些方法,改变隐藏属性的值。
目的:增强安全性和简化编程,使用者不必在意具体实现细节,而只是通过外部接口即可访问类的成员。实现软件部件的**“高内聚、低耦合”**,防止程序相互依赖性而带来的变动影响。在面向对象的编程语言中,对象是封装的最基本单位,面向对象的封装比传统语言的封装更为清晰、更为有力。
构造器可以赋值,setter也可以赋值,那么他们俩的区别是什么呢?
构造器只是仅仅对对象的属性进行一些列的赋值操作而没有对属性进行限制作用,而setter方法可以对属性不仅可以对属性进行赋值而且还可以进行限制
例如:通过构造器给age赋值150那么打印的结果也是150,肯定是不合理的,想要合理怎么做呢?
在构造器里面利用当前对象(this)调用setter方法进行限制
public class Student {
private String name;
private int age;
private double height;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if(age<120&&age>0){
this.age=age;
}else{
this.age=3;
}
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public Student() {
}
public Student(String name, int age, double height) {
this.name = name;
this.setAge(age);
this.height = height;
}
}
public class Test {
public static void main(String[] args) {
Student s = new Student();
s.setAge(100);
System.out.println(s.getAge());
Student s1 = new Student("李四",1560,178.3);
System.out.println(s1.getName()+s1.getAge()+s1.getHeight());
}
}
继承 ( inheritance)
定义:多个类具有共同的属性(成员变量)与行为(成员方法)的时候,将这些共同的部分向上抽取出来定义到一个公共的类中,其他及各类可以与这个公共的类形成继承关系,从而在多个类中不需要重 复定义公共部分!这个公共的类就是父类,也称为超类或者基类,其他的类就是子类。子类可以直接访问父类的非私有化成员变量,访问父类的私有化成员变量可以使用super.get()方法。父类封装的东西子类也继承了,只是间接调用而已。
**简单的说:**继承是类与类的一种关系,比较像集合中的从属于关系。例如:Student(属性:年龄,姓名,身高。方法:吃饭,睡觉)属于Person,Teacher(属性:年龄,姓名,身高。方法:吃饭,睡觉)也属于人类。把他们共同的部分抽取出来定义到Person类中,那么Student类和Teacher类就是Person类的子类(派生类),Person类就是Student类和Teacher类的父类(基类)。在Java中是单继承的,也就是说一个子类只有一个父类。让他们的关系是子类 is a父类的关系。
public class Person {
private int age;
private String name;
private double height;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public void eat(){
System.out.println("吃饭");
}
public void sleep(){
System.out.println("睡觉");
}
}
public class Student extends Person {
private int sno;
public int getSno() {
return sno;
}
public void setSno(int sno) {
this.sno = sno;
}
public void study(){
System.out.println("学习");
}
}
public class Test {
public static void main(String[] args) {
Student s = new Student();
s.setSno(1001);
s.setAge(22);
s.setHeight(187.2);
s.setName("李四");
System.out.println(s.getName()+"...");
s.eat();
s.sleep();
s.study();
}
}
内存分析图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2hJQjDgC-1626968413996)(D:\截图\6.png)]
优点:减少代码量,能很好的提高复用率。使类与类之间存在继承关系,是实现多态操作的前提。
缺点:继承使得多个类之间具有了子父类关系,当一个类存在多个子类的时候,如果父类发生变化,那么这些子类会跟着一同变化,造成类与类之间的“强耦合”关系。
总结:
- 执行构造方法创建对象完成对象的初始化时,先执行父类的构造,完成父类的初始化,再执行本类的初始化工作。
- 第一次创建Person(父)类,是直接继承Object,而Object是jdk提供的工具类,自然不会有属性,所以Object类只提供了无参的构造,在执行Person方法时,无论是无参还是有参都会先执行父类Object的无参构造。
- 但是对于Person的子类Student,必须也要有自己的构造方法,由于构造执行的特性,对于无参构造,先执行了Student的无参构造,对于有参构造,则先执行父类Student的有参,然后才执行本类属性的初始化。
- 构造方法constructor中,无论是否显式还是隐式调用super(),子类在创建对象调用时都会执行super();
- 显式调用构造是在需要通过父类的带参构造来完成子类的带参构造。
- 对于父类私有的属性,即使子类继承,但是在test.java中,子类是无法直接访问的,在制定子类的带参构造方法时,只能通过调用父类的带参构造来完成。对于父类非私有的属性,子类对象可以直接调用。
- 继承具有传递性,Student继承Person,Person继承Object(所有类的根基)
多态 (polymorphism)
权限修饰符
private
public class A {
private int age;
public void eat(){
System.out.println(age);
}
}
public class Test {
public static void main(String[] args) {
A a = new A();
a.age();
}
}
同一类中可以访问,不同类中不能直接访问
default(缺省)
一般不写
同一个类中,同一个包中可以访问布尔default修饰的属性
protected
package com.suhuan.Test06;
public class A {
protected int age;
public void eat(){
System.out.println(age);
}
}
package com.suhuan.Test06;
public class Test {
public static void main(String[] args) {
A a = new A();
System.out.println(a.age);
}
}
package com.suhuan.Test05;
import com.suhuan.Test06.A;
public class Test01 extends A {
public void a(){
System.out.println(age);
}
}
public
整个项目中都可以访问
一般写代码属性用private修饰,方法用public修饰
方法重写
定义:Java程序中,类的继承关系可以产生一个子类,子类继承父类,它具备了父类所有的特征,继承了父类所有的方法和变量。
子类可以定义新的特征,当子类需要修改父类的一些方法进行扩展,增大功能,程序设计者常常把这样的一种操作方法称为重写,也叫称为覆写或覆盖。
要求:子类的方法名字和父类的方法名必须相同,参数列表(个数,类型,顺序)也要和父类方法保持一致
public class Person {
public void eat(){
System.out.println("吃");
}
public void sleep(){
System.out.println("睡觉");
}
}
public class Student extends Person {
public void study(){
System.out.println("学习");
}
public void eat(){
System.out.println("吃小龙虾");
}
}
public class Test {
public static void main(String[] args) {
Student s = new Student();
s.eat();
}
}
内存分析图:
重载和重写的区别:
重载:同一类中,当方法名相同,形参列表不同的时候,多个方法构了重载
重写:在不同的类中,子类对父类提供的方法不满意的时候要对父类的方法进行重写
| 英文 | 位置 | 修饰符 | 返回值 | 方法名 | 参数 | 抛出异常 | 方法体 |
---|
重载 | overload | 同一类中 | 无关 | 无关 | 相同 | 不同 | 无关 | 不同 | 重写 | override | 子类父类中 | 父类的权限修饰符低于子类 | 父类的返回值类型大于子类 | 相同 | 相同 | 小于等于 | 不同 |
【说明】方法重写时:
子类中方法的访问修饰符必须 >= 父类中对应方法的访问修饰符 (访问修饰符大小关系已在本文中列出)
子类中方法的返回值类型可以和被重写方法的返回值类型相同,或者是被重写方法类型的子类型。
即 子类中方法的返回值类型必须 <= 父类中对应方法的返回值类型
以上结论对于抽象方法的实现同样适用。
如果子类将父类中的方法重写了,调用的时候肯定是调用被重写过的方法,那么如果现在一定要调用父类中的方法该怎么办呢?
接下来就要用到super关键字的引用
super
1.super修饰属性,方法
在子类的方法中,可以通过super.属性或则super.方法的方式,显示的去调用父类提供的属性和方法。通常super.默认不写
public class Student extends Person{
int sid;
public void stu(){
System.out.println("Student特有方法");
}
public void a(){
System.out.println(age);
eat();
}
}
如果子类中和父类中有同名的属性,在子类方法中调用该属性的时候调用的是子类中的属性,只不过调用的子类属性前this.(当前对象)省略了,如果想要调用父类中的同名属性就必须显示的加上super.进行修饰(显示调用)
public void a(){
System.out.println(age);
System.out.println(this.age);
System.out.println(super.age);
eat();
}
方法也是同样的道理
public void a(){
System.out.println(age);
System.out.println(this.age);
System.out.println(super.age);
eat();
this.eat();
super.eat();
}
public void eat(){
System.out.println("子类吃饭");
}
2.super修饰构造器
在调用子类空构造器的时候,要向上调用父类的空构造器,因为要对子类进行初始化赋值的时候先要把父类进行初始化赋值只不过子类的空构造器中的super();省略而已
public class Student extends Person {
int sid;
public Student() {
}
}
而父类中的空构造器中也有父类(Object),省略了super();
public Person() {
super();
}
也就是说平时我们写的空构造器中第一行都有super();原因就是必须先把父类初始化完成才进行子类的初始化
利用super可以调用父类的有参构造器
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public Student(int sid) {
this.sid = sid;
}
public Student(int sid,String name,int age){
super(age,name);
this.sid=sid;
}
【注意】:所有构造器的第一行都有super();如果super已近显示的调用了super父类有参构造器,那么他的第一行就没有默分配的super();了
那么能不能将this.sid=sid;替换成this(sid);呢
显然是不能的,因为super修饰构造器要放在第一行,this修饰构造器也要放在第一行。
图解:
继承条件下构造方法的执行过程(面试题)
public class Person {
int age;
String name;
public Person() {
}
public Person(int age, String name) {
super();
this.age = age;
this.name = name;
}
}
public class Student extends Person {
int sid;
public Student() {
}
public Student(int age, String name, int sid) {
super(age, name);
this.sid = sid;
}
}
public class Test {
public static void main(String[] args) {
Student s = new Student(22,"李四",1001);
}
}
分析图:
Object类中的一些方法
1.toSring方法
方法的原理:
现在,使用toString方法的时候,打印出来的东西 “不好看”,对于其他人来说不友好,可读性不好 我们现在是想知道对象的信息,名字,年龄,身高。。。。。。 现在的格式不好:
public class Test {
public static void main(String[] args) {
Student s = new Student(19, "lisa", 150.2);
System.out.println(s);
System.out.println(s.toString());
}
}
出现的问题:子类Student对父类Object提供的toString方法不满意,不满意--》对toString方法进行重写
总结:toString的作用就是对对象进行“自我介绍”,一般子类对父类提供的toString都不满意,都要进行重写。
重写快捷键:Alt+Insert
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
", height=" + height +
'}';
}
2.equals方法
作用:比较对象具体内容是否相等,源码中发现:底层依旧是比较的地址值
public boolean equals(Object obj) {
return (this == obj);
}
那么就要对equals方法在子类中进行重写(快捷键即可)
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Phone phone = (Phone) o;
return Double.compare(phone.price, price) == 0 &&
year == phone.year &&
Objects.equals(brand, phone.brand);
}
类与类之间的关系
1.将一个类作为另一个类中方法的形参
public void love(Boy boy){
System.out.println("男友名字:"+boy.name+"年龄:"+boy.age);
boy.buy();
}
2.一个类1作为另一个类2的属性
必须要创建这个类1创建具体的对象才能调用
public class Girl {
String name;
double height;
Mom m ;
public void weChat(){
m.say();
}
public void love(Boy boy){
System.out.println("男友名字:"+boy.name+"年龄:"+boy.age);
boy.buy();
}
public Girl(String name, double height) {
this.name = name;
this.height = height;
}
public Girl() {
}
}
public class Mom {
public void say(){
System.out.println("唠叨");
}
}
public class Test {
public static void main(String[] args) {
Boy b = new Boy(30,"tom");
Girl g = new Girl("lisa",100.3);
g.love(b);
g.m = new Mom();
g.weChat();
}
}
其他的关系
|