java面向对象OOP
基本概念
-
面向过程与面向对象
面向过程:关注代码实现的细节、复用性
面向对象:先把每个过程的代码实现细节整合到对象中,只要找到对象就能拥有对象身上所有的功能。
面向对象基于面向过程
-
类与对象
对一类对象进行抽取,对共有的特征抽取成属性,共有的行为抽取成方法,这一类对象抽取成类。 — 类就是对象的概括,对象是类的具体实现
在java中所有非静态的属性和方法都要通过对象调用
万物皆对象
-
构造方法
-
与类同名,没有返回值类型
-
如果类中没有定义任何形式的构造方法,JVM会在底层默认添加一个无参构造
-
构造方法支持重载,有参构造可以进行属性初始化
-
如果类中定义任何形式的构造方法,JVM不会再添加无参构造
-
类中最少会有一个构造方法
-
-
this指针
-
关键字:代表当前类的对象
-
this可以代表类还没有产生对象,可以代表类刚创建的对象,也可以代表正在使用的对象
-
this不是真实的对象,是一个虚拟的指代,指代地址值
-
this的主要功能是在本类的构造方法中调用其他形式的构造方法,this语句一定要在构造方法的首行
示例:
class Person { // 特征 --- 属性 String name; char gender; int age; // 如果一个类中没有定义构造方法,JVM在底层会默认定义一个无参构造 // 构造方法特点: 1.与类同名 2. 没有返回值类型 public Person(){ System.out.println("this:" + this); // this:cn.zss.Person@15aeb7ab } // 支持重载,有参构造 // 如果类中定义了任意一个构造方法,JVM不会再添加 public Person(String name){ // this (关键字) // 代表当前类的对象 this.name = name; System.out.println("A"); System.out.println("this:" + this); } public Person(String name, int age, char gender){ // this 语句在本类中的构造方法中调用其他形式的构造方法 // this 语句一定要在首行 this(name); this.gender = gender; this.age = age; System.out.println("B"); } // 行为 --- 方法 public void eat(){ // this可以代表类还没有产生对象 System.out.println(this.name + " eat too much"); System.out.println("this:" + this); // this:cn.zss.Person@15aeb7ab } } public class Demo{ public static void add (int num){ num += 1; } // 只要参数是引用类型只能接受对象 public static void add (Person p){ // new Person() 形参接受Person类对象 p.age ++; } public static void main(String[] args) { // 构造方法,创建对象 Person p = new Person(); p.age = 19; p.name = "lili"; p.eat(); // lili eat too much // 实参传入基本数据类型 System.out.println(p.age); // 19 add(p.age); System.out.println(p.age); // 19 // 实参传入引用数据类型 add(p); System.out.println(p.age); // 20 System.out.println("--------------------------------------"); Person p2 = new Person("Poter",18,'男'); } }
-
-
构造代码块
- 在类内方法外的{}内
- 用于属性初始化
- 优先于任意形式的构造方法执行
示例:
class Baby { // 属性 String name; char gender; int age; // 构造代码块 // 会优先于所有的构造方法先执行,与位置无关 { this.name = "Poter"; this.gender = '男'; } // 无参构造 public Baby(){ System.out.println("无参构造方法"); } public Baby(String name, int age){ this.name = name; this.age = age; System.out.println("有参构造方法"); } } public class Demo{ public static void main(String[] args) { Baby a = new Baby(); System.out.println(a.name+","+a.gender); Baby b = new Baby("xiaoming",1); System.out.println(b.name+","+b.gender+","+b.age); } }
-
局部代码块
- 在方法内的{}
- 控制变量的生命周期,提高内存的利用率
示例:
public class Demo{ public static void main(String[]args){ int x = 1; //局部(方法)代码块 //改变变量的生命周期 { int y = 2; System.out.println(x+y); } } }
-
局部变量与成员变量
a. 位置
-
成员变量:类内方法外
-
局部变量:方法内
b. 使用范围
-
成员变量:整个类
-
局部变量:方法内
c. 内存
- 成员变量:堆
- 局部变量:栈
d. 生命周期
- 成员变量:随着类创建的对象而产生,随着对象被系统回收时才消失
- 局部变量:方法被调用执行时才产生,随着方法的调用结束才消失
-
面向对象的特性
- 封装、继承、多态(抽象)
封装
- 类,方法 — 体现了封装的复用性
- 属性私有化 — 类中的属性进行私有化,提供公共的访问方式(方法)用于间接的操作私有化属性,提高代码的数据安全性
示例:
public class ObjectDemo {
public static void main(String[] args) {
Person p = new Person("lili",18,'女');
p.setAge(20);
System.out.println(p.getAge());
}
}
class Person{
// 私有化属性只能在本类中直接使用
private String name;
private char gender;
private int age;
public Person(String name, int age, char gender){
// 对参数进行判断
this.name = name;
this.gender = gender;
if (age >=0 && age <= 120){
this.age = age;
}
}
// 自己定义的getter 和 setter 方法
// 提供方法间接给属性赋值
public void setAge(int age){
if (age >=0 && age <= 120){
this.age = age;
}
else{
System.out.println();
}
}
// 提供方法间接的获取属性值
public int getAge(){
return this.age;
}
// java 可以提供自动提供的get方法和set方法
// 右键 点击 Generator --- 选择Getter and Setter
}
继承
-
如果多个类的内容出现重复,就要把重复的内容放到新类中,原来的类和新类之间通过extends关键字产生关联关系—继承。原来的类叫子类(派生类),新的类叫父类(超类)。子类可以继承父类的部分信息
-
继承可以提高代码的复用性
-
继承的方式(单继承 树状结构):类只能有一个父类,但可以有多个子类
-
方法的重写:在父子类中出现方法签名一致的方法
-
重写原则:两等两小一大
-
两等:方法签名一致;如果父类的方法返回值类型是基本数据类型或者void, final,那么子类的返回值类型就和父类完全一致
-
两小:如果父类的方法返回值类型是引用类型,那么子类的方法返回值类型要么和父类的方法返回值类型一致,要么是父类方法返回值类型的子类;子类抛出异常小于等于父类异常类型
class A{} class B extends A{} class C{ public A m(){ // 返回值类型为A return null; } } class D extends C{ public B m(){ // 返回值类型为A的子类 return null; } }
-
访问权限修饰符
关系:本类,子类,同包类,其他类
本类 子类 同包类 其他类 public 可以 可以 可以 可以 protected 可以 可以 可以 不可以 默认 可以 同包子类 可以 不可以 private 可以 不可以 不可以 不可以 **一大:**子类的方法访问权限修饰符要么和父类的方法访问权限修饰符一致,要么比父类的范围大
class EDemo{ // 类的私有化,对子类不可见 private void m(){ } void n(){} // 默认 } class EDemo1 extends EDemo{ public void m(){ } // 不是重写,相当于定义一个新方法 public void n(){} // 重写,public范围大于默认范围 }
**父类私有化信息、构造方法、构造代码块都不能被子类继承 **
public class ExtendDemo2 { } class EDemo{ // 类的私有化,对子类不可见 private void m(){ } void n(){} // 默认 } class EDemo1 extends EDemo{ public void m(){ } // 不是重写,相当于定义一个新方法 public void n(){} // 重写 }
注意: 如果protected修饰的信息和要获取的位置关系是其他类位置,保证子类对象只能在本类中使用才能调用protected修饰的信息
/*定义packet cn.zss.a中的类A*/ package cn.zss.a; public class A { protected void m(){} } /*定义packet cn.zss.b中的类B和C*/ package cn.ysu.b; import cn.ysu.a.A; // 导入类A的包 public class B extends A{ public static void main(String[] args) { // 创建对象 A a = new A(); B b = new B(); // a对象和m()所在的位置是其他类 // a.m(); // error 无法访问protected修饰的方法 b.m(); // correct // b对象和m()所在位置是其他类,B类继承A类 } } class C extends A{ public void n(){ C c = new C(); c.m(); // c对象和m()所在位置是其他类,C类继承A类 B b = new B(); // b.m(); // error // 如果protected修饰的信息和要获取的位置关系是其他类位置 // 保证子类对象只能在本类中使用才能调用protected修饰的信息 } }
-
-
-
super
-
关键字,代表父类的对象,可以调用父类的属性和方法
-
super语句—在子类构造方法中调用父类的构造方法
- 子类**所有形式*的构造方法默认都会通过super()调用父类的无参构造
- 如果父类没有提供无参构造,子类所有的构造方法需要通过super语句强制调用父类对应形式有参构造
- super 语句需要在首行 —和this冲突不能同时存在,但是this可以和super()的默认形式(不手动调用)同时存在
-
父类对象优先于子类对象先存在
- 父子类执行顺序(父类构造代码块-父类构造方法-子类构造代码块-子类构造方法)
public class ExtendsDemo2 { public static void main(String[] args) { Pig p = new Pig("lili"); p.eat(); } } class Animal{ // 父类对象优先于子类对象先存在 // public Animal(){ // System.out.println("父类的无参构造"); // 1 // } // public Animal(String name){ System.out.println("父类的有参构造"); } // 方法 public void eat(){ System.out.println("Animals are eating"); } public void sleep(){ System.out.println("zzz..."); } } class Pig extends Animal{ // 父类对象优先于子类对象先存在 // 若父类没有无参构造需要手动调用父类的有参构造 // super 语句需要在首行 public Pig() { super("lili"); // 手动调用父类有参构造,否则报错 // supper语句 --- 在子类的构造方法中调用父类的构造方法 // super(); 默认 System.out.println("子类的无参构造"); // 2 } // 子类所有形式的构造方法都会默认先调用父类的无参构造 public Pig(String name){ super("lili"); // 手动调用父类有参构造,否则报错 System.out.println("子类有参构造函数"); } // 重写方法 public void eat() { System.out.println("The pig is eating"); // 在java中所有非静态属性和方法都需要对象调用 // this -代表当前类的对象 // supper - 代表父类的对象 sleep(); // 相当于 super.sleep(); } }
-
多态
- 在代码执行过程中可以呈现的多种形式
-
编译时多态 — 在编译时期绑定代码
- 体现:重载
-
运行时多态 — 在运行时期绑定代码
-
体现:重写,向上造型(在执行时才能知道对象调用的方法)
-
前提:继承
-
向上造型
- 声明类是父类,实际创建的类是子类
- 编译看左边引用的类型,运行看具体的对象类型
- 向上造型的对象调用方法,可以调用哪些方法看父类,方法的具体执行看子类是否重写父类的方法,如果有重写调用子类方法,否则调用父类方法(父类 — 目录 子类 — 正文)
-
优点:
- 统一参数类型
- 解耦 (降低耦合度 高内聚、低耦合)
-
示例:
public class DTDemo { public static void main(String[] args) { // Pet p; // p = new Dog(); Pet p = new Dog(); // 声明类是父类,实际创建类是子类 // 向上造型 // 向上造型的对象调用方法 // 可以调用哪些方法看父类 // 方法的具体执行看子类是否重写父类的方法 // 如果有重写调用子类方法,否则调用父类方法 p.eat(); // Dogs like to eat bones p.sleep(); // zzz... // p.bark(); // error,父类中没有,无法调用 // 调用方法 // 匿名对象 --- 当做参数使用 System.out.println("-------------------------"); petsEat (new Pet()); // Pets need to eat petsEat (new Cat()); // 向上造型 Cats like to eat fish petsEat (new Dog()); // 向上造型 Dogs like to eat bones } public static void petsEat(Pet p){ // 统一参数类型 p.eat(); } } class Pet{ // 方法 public void eat(){ System.out.println("Pets need to eat"); } public void sleep(){ System.out.println("zzz..."); } } class Dog extends Pet{ @Override public void eat() { // super.eat(); System.out.println("Dogs like to eat bones"); } public void bark(){ System.out.println("Wang Wang barking"); } } class Cat extends Pet{ @Override public void eat() { // super.eat(); System.out.println("Cats like to eat fish"); } public void CatchMice(){ System.out.println("Cats can catch mice"); } }
-
解析重写原则
-
子类的方法访问权限修饰符要么和父类方法访问权限修饰符一致,要么比父类的范围大
class A{ public void fun(){} } class B extends A{ void fun(){} // 反证,证明这是错的 } A a = new B(); // 向上造型的对象a的声明类为A类,可以调用A类中的fun(),这个方法在任意位置都能被访问 a.fun(); // 向上造型调用方法,方法的具体执行看子类(B类),执行B类的fun(),这个方法只能在同包范围内访问 // 此时前后矛盾,证明“一大”原则正确
-
如果父类的方法返回值类型是引用类型,那么子类的方法返回值类型要么和父类的方法返回值类型一致,要么是父类方法返回值类型的子类
class A{} class B extends A{} // B为A的子类 class C{ public B fun(){ return null; } } class D extends C{ public A fun(){ // 反证,证明这是错的 return null; } } C c = new D(); // 向上造型的对象c,声明类C类,可以调用C类中的fun(),返回的是B类型的对象,就能调用B类的方法 A a = c.fun(); // 向上造型对象调用方法,方法的具体执行看子类(D类),调用D类中的fun(),返回的是A类的对象a,a就可以调用到A类的方法 // 此时A类中不会包含B类中所有方法,矛盾,证明“一小”的原则是正确的
-
-
static
-
关键字,代表静态
-
可以修饰变量、方法、代码块、内部类
-
static 修饰变量
-
静态变量 static 变量的定义
-
静态变量属于类,所以静态变量也称为类变量
-
生命周期:静态变量随着类的加载(静态常量池)而被加载到方法区的静态区中,在静态区时会对静态变量赋予系统默认初始值,类加载之后不再移除,直到整个程序运行结束,静态变量直到类被移除才会释放
-
调用静态变量时可以通过类名.静态变量名的形式来调用也可以通过对象.静态变量名调用,为了提高程序可读性,建议通过类名.静态变量名的形式调用。
-
应用场景:类创建的所有的对象都会对静态变量进行共享 (如果有共享的场景就可以使用静态变量)
-
System.in, System.out,in和out都是静态变量
-
注意:
- 在构造代码块当中不能定义静态变量
- 类加载之后才能创建对象,构造代码块在创建对象时执行,静态变量在类加载时就要初始化
- 在构造方法中不能定义静态变量,原因同上
- 在构造方法和构造代码块中可以给静态变量赋值
public class StaticDemo { public static void main(String[] args) { Person p = new Person(); p.name = "Potter"; p.age =18; p.gender = '男'; p.eat(); System.out.println("name:"+p.name+",gender"+p.gender); Person p1 = new Person(); p1.name = "lili"; p1.gender = '女'; p.eat(); System.out.println("name:"+p1.name+",gender:"+p1.gender); System.out.println("name:"+p.name+",gender:"+p1.gender); } } class Person{ String name; int age; static char gender; public void eat(){ System.out.println("People need to eat"); } }
内存图:
- 在构造代码块当中不能定义静态变量
-
static修饰方法
-
static修饰方法
-
生命周期
- 静态方法随着类的加载(静态常量池)而加载到方法区的静态区,不会赋予初始值。当调用静态方法时,静态方法会被加载到栈中执行。
-
调用格式: a.类名.静态方法名()(推荐) b.对象.静态方法名()
-
注意:
静态信息只能直接调用静态信息不能直接调用非静态信息,非静态信息可以直接调用非静态信息和静态信息
- 静态方法中不能定义静态变量
- 静态方法随着类加载到方法区的静态区中(只是存储方法,并未执行);被调用时才开始运行程序
- 静态变量在类加载时加载到静态区并初始化
- 静态方法中不能直接调用非静态方法
- 非静态变量和非静态方法必须通过对象来调用
- 当使用类名来调用静态方法时,可能没有创建对象
- 可以在调用非静态方法之前创建对象,通过对象来调用非静态方法
- 静态方法(如 main方法)中不能使用this/super关键字
this 当前对象 super 父类对象 - 子类可以继承父类的静态方法
- 静态方法可以重载,但是不能重写
- 静态方法是和类绑定的
- 重写针对的是对象 — 与对象一个级别
- 子类可以存在与父类方法签名一致的方法,父子类中方法签名一致的方法要么全是静态,要么全是非静态,否则会报错
- 静态方法中不能定义静态变量
-
静态代码块
- static修饰构造代码块
- 在类被使用时执行,包括创建类的对象,调用类的静态方法以及调用类的静态属性
- 所有的静态信息(静态代码块,静态方法,静态变量)都只加载一次,预先加载一些资源,给静态变量初始化
- 父子类之间执行顺序(父类静态信息(顺序)-子类静态信息(顺序)-父类对象级别(属性、构造代码块、构造方法)-子类对象级别(属性、构造代码块、构造方法))
- 注意:
- 静态代码块中不可以定义静态变量
- 静态代码块可以给静态变量赋值
class A{ static int i = 0; static{ // 静态代码块 --- 不能出现非静态属性 --- 与类同级 --- 预先加载一些资源,静态变量初始化 i = 20; System.out.println("静态代码块"); } public static void method(){ System.out.println("静态方法"); } } public class StaticDemo2 { public static void main(String[] args) { A a = new A(); // 静态代码块 System.out.println(A.i); // 0 System.out.println("------------------------"); A.method(); // 静态方法 } }
示例:
public class StaticTest3 { public static void main(String[] args) { System.out.println(STDemo1.x + "," + STDemo1.y); // 2, 1 } } class STDemo1{ /* 加载 执行第一步 执行第二步 执行第三步 sd null 0x01 0x01 0x01 x 0 1 2 2 y 0 1 1 1 */ static STDemo1 sd = new STDemo1(); // 1 static int x = 2; // 2 static int y; // 3 public STDemo1(){ x ++; // 1 y ++; // 1 } }
final
- 关键字 — 修饰符
- 可以修饰数据、方法和类
-
final 修饰数据
-
final 变量的定义
-
final 修饰的数据就是最终值(无法改变的值)
-
final修饰基本类型数据,基本类型数据的值无法改变
-
final修饰的引用数据类型,引用数据类型的地址值无法改变,元素值依然可以改变
-
如果final修饰的是成员变量且没有进行初始化,需要保证在创建对象之前要进行初始化
-
如果final修饰的是静态常量且没有进行初始化,保证在类加载完成之前进行初始化
class FDemo{ final int i1 = 2; // 如果成员变量没有进行初始化,要保证对象创建之前可以给定最终值 final int i2; final int i3; // 如果i是静态常量没有进行初始化,要保证在类加载完成之前进行初始化 final static int i; // 构造代码块 -- 保证创建对象之前可以给定最终值 { i2 = 2; } // 构造方法 -- 保证创建对象之前可以给定最终值 public FDemo(){ i3 = 7; } static { i = 4; } }
-
-
final修饰方法
final修饰的方法可以进行重载但不能进行重写
-
final修饰类
final修饰的最终类,没有子类(不能被继承),但是可以有父类
abstract
-
关键字—修饰符 方法、类
-
如果所有的子类都对父类中某个方法进行了不同程度的重写,那么父类的这个方法的方法体就没有实际意义,把方法体去掉加上abstract修饰就变成了抽象方法
-
如果一个类中含有抽象方法那么这个类就必须是抽象类。
-
如果一个普通类继承抽象类那么需要重写所有的抽象方法,如果不想重写所有的抽象方法就要变成抽象类
-
注意:
- 抽象类可以定义属性和方法
- 抽象类不一定含有抽象方法
- 抽象类可以定义构造方法,不能创建对象
- final/static/private不可以修饰抽象方法 — 抽象方法需要被重写所以不能
- final不能修饰抽象类— 抽象类需要被继承抽象方法才能被重写
- 抽象方法可以支持重载
示例:
public class AbstractDemo { public static void getPerimeter(Graph g){ System.out.println(g.getPerimeter()); } public static void getArea(Graph g){ System.out.println(g.getArea()); } public static void main(String[] args) { getPerimeter(new Rectangle(2.0, 4.0)); getPerimeter(new Circular(2.0)); getPerimeter(new Square(2.0)); getArea(new Rectangle(2.0, 4.0)); getArea(new Circular(2.0)); getArea(new Square(2.0)); } } abstract class Graph{ // 属性 private double width; private double length; // 有参构造 public Graph(double width, double length) { this.width = width; this.length = length; } public double getWidth() { return width; } public double getLength() { return length; } // 方法,周长 abstract public double getPerimeter(); // 方法,面积 abstract public double getArea(); } class Rectangle extends Graph{ // 手动调用父类的有参构造 Rectangle(double width, double length){ super(width, length); } // 重写 public double getPerimeter(){ return 2 * (getWidth() + getLength()); } public double getArea(){ return getWidth() * getLength(); } } class Square extends Rectangle{ // 调用父类的有参构造 Square(double width){ super(width, width); } } class Circular extends Graph{ public Circular(double r) { super(r,r); } public double getPerimeter(){ return 2 * getWidth() * 3.14; } public double getArea(){ return 3.14 * getWidth() * getWidth(); } }