Java - 面向对象
面向对象专业术语:
OO (Oriented Object) : 面向对象
OOP (Oriented Object Programming) : 面向对象的编程
OOD (Oriented Object Design) : 面向对象的设计
OOA (Oriented Object Analysis) : 面向对象的分析
OOT(Oriented Object Test) : 面向对象的测试
什么是面向过程:
面向过程也是解决问题的一种思想, 当我们在解决问题时, 会按照预先设定的想法和步骤, 一步一步去实现, 而具体的每一步都需要我们去实现和操作. 这些步骤相互调用和协作, 完成我们的需求
上述描述的每一个具体步骤我们都是参与者, 并且需要面对具体的每一个步骤和过程, 这就是面向 过程最直接的体现
通过上面简单的描述发现, 面向过程, 其实就是面向着具体的每一个步骤和过程, 就是面对具体的 每一个功能函数. 这些功能函数相互调用, 完成需求
提出问题, 分解问题, 一一解决
实质上是函数的调用
什么是面向对象:
OOP (Oriented Object Programming):
当不再面对具体的每一个方法时, 发现操作也变的简单了很多. 而封装具体功能的这类, 是我们需 要面对的. 而基于这个封装了具体功能的类, 那怎么使用呢?
当面向封装了具体功能类, 若要使用这个类, 一般情况下, 在 Java 中需要通过创建这个类的实体来 使用. 这个实体称之为对象. 在开发中, 我们是在不断的找封装不同功能的类. 基于这些类, 创建其对 象, 使用这些对象完成相应的操作
通过上面的讲解和分析得出:面向对象是基于面向过程, 对象是将功能进行了封装. 只要找到了具 体的类, 创建出对象, 就可以调用其中的具体功能. 面向对象也是用来解决问题的一种思维模式
在以后开发中, 先找对象, 调用对象中的具体功能. 如果真的没有能够完成需求的对象, 这时就自 己创建对象, 并将所需的功能定义到对象中, 方便以后使用
面向对象的思想也就是人认识世界所采用的方式: 分门别类的思想:
世界上的万物人都进行了分类, 按照相似性把数不清的事物分归类到有限的类别中, 比如不管是百灵鸟还是麻雀都是鸟类, 每个人有姓名都是不一样的个体但都叫人类
类 (class): 类别
在 java 中, 使用 class
关键字定义类
属性:
类的固有特征
行为和动作:
函数 function (方法 method)
对象 (object): 一个类别中的具体案例
如何定义类:
类的成员:
- 属性
- 方法
- 构造方法
- 静态属性
- 静态方法 // 静态方法就是类方法
- 构造代码块
- 静态代码块
访问修饰符 类型 属性名称 = 属性值;
public class Test {
public static void main(String[] args) {
}
}
// 定义了一个狗类
class Dog {
// 固有属性, 类的固有特征
// 访问修饰符 类型 属性名称 = 属性值;
public String name;
public int age = 3;
// 狗会跑
public void run() {
}
// 狗会吃
public void eat() {
}
// 构造方法
public Dog() {
}
}
局部变量与成员变量的区别:
定义位置不同:
- 成员变量定义在类中
- 局部变量定义在方法中或者语句里面
在内存中的位置不同:
- 成员变量存储在堆内存的对象中
- 局部变量存储在栈内存的方法中
声明周期不同:
- 成员变量随着对象的出现而出现在堆中, 随着对象的消失而从堆中消失
- 局部变量随着方法的运行而出现在栈中, 随着方法的弹栈而消失
初始化不同:
- 成员变量因为在堆内存中, 所有都是默认的初始化值
- 局部变量没有默认的初始值, 必须手动给其赋值才可以使用
如何创建对象:
定义类, 在根据类来创建对象
可以通过类的构造方法创建对象, 使用 new
关键字:
类型 对象名称 = new 构造函数();
Scanner scan = new Scanner(System.in)
;
Random rand = new Random()
;
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
Dog dog_2 = new Dog();
}
}
如何访问对象:
对象.属性
class Test {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog.name);
Dog dog_2 = new Dog();
dog2.name = "screl";
System.out.println(dog2.name);
}
}
成员的初始化默认值:
int, long, short, byte: 0
double: 0
char: '\u0000' 0
对象 (Object) 的默认值是 null
this 指针:
在java中, 存在 this
关键字, 这个关键字默认会指向当前对象
每一个对象都带有一个 this
指针
非静态的属性和方法不能在静态方法中使用, this
只能用在非静态方法中, 不能用在静态方法中(类方法)
非静态的方法可以使用静态属性和方法
构造方法 (Constructor):
也叫做构造函数
作用: 创建对象的时候初始化值, 对对象进行针对性初始化
- 初始化过程: 默认初始化 -> 显示初始化 -> 针对性初始化
与类名相同, 没有返回值, 访问修饰符为 public
, 非 static
如果声明了返回值jvm会将其解释为普通方法
在构造对象的时候自动调用构造方法
调用其他构造函数的语句必须定义在构造函数的第一行, 原因是初始化动作要最先执行
构造函数之间禁止出现相互调用的情况
注意:
- 在 java 类中如果没有自己定义构造函数, jvm 会自动生成一个无参的构造函数, 构造出来的成员的值为默认值, 当定义了一个构造函数之后, jvm 就不会生成
- 定义构造函数时, 一定要定义一个无参构造
- 构造方法如果是
private
不能从类外部实例化
构造方法的细节:
- 一个类中可以有多个构造函数, 多个构造函数是以重载的形式存在的
- 构造方法中也是有
return
语句的, 用于结束初始化动作(可省略) - 构造方法是可以被
private
修饰的, 作用:其他程序无法创建该类的对象
class Dog {
public String name;
public String color;
// 自定义构造方法
public Dog(String name, String color) {
this.name = name;
this.color = color;
}
// 一定要定义一个无参的构造方法
public Dog() {
}
}
this 调用构造方法
在之前学习方法之间调用时, 可以通过方法名进行调用. 可是针对构造方法, 无法通过构造方法名来相互调用.
构造方法之间的调用可以通过 this
关键字来完成
构造方法调用格式:
this(args)
;
调用其他构造方法的语句必须定义在构造函数的第一行, 原因是初始化动作要最先执行.
构造方法之间禁止出现相互调用的情况.
class Dog {
String name;
int age;
String master;
public Dog() {
}
public Dog(String name, int age, String master) {
this(master); // 调用另一个构造方法
this.name = name;
this.age = age;
}
public Dog(String master) {
this.master = master;
}
}
构造代码块:
在类中可以用 { }
表示构造代码块
class Dog {
{
System.out.ptintln("这是一个构造代码块");
}
}
对象的创建流程及内存结构一:
- 在堆内存中开辟一块对象的内存空间, 并为其分配物理内存地址
- 对成员变量进行 默认初始化 (零值)
- 相应构造函数进栈, 先对成员变量进行 显式初始化
- 其次执行构造函数的内容, 对成员变量进行 针对性初始化
- 构造函数执行完成弹栈, 将对象的内存空间地址赋予相应的引用变量
static 关键字:
static
关键字可以修饰(属性, 方法, 类, 语法块, 导包)
static
还可以修饰内部类
被 static
修饰的只能执行一次
static
修饰的代码(方法或者属性), 会提前加载到内存中, 并且只有一份属于该类, 因此:
- 静态方法中, 无法直接调用非静态方法(或者属性)
- 被
static
修饰的属性和方法, 不属于对象, 属于该类, 直接可以使用类名称.属性(或者方法)来调用 - 非静态的方法可以使用静态属性和方法
- 在静态方法中调用非静态方法和非静态属性, 需要先实例化
静态内部类:
class Test {
// 静态内部类:
static class Test_2 {
}
}
静态方法:
class Test {
public static void function() {
}
}
静态属性:
class Test {
static int num;
}
静态代码块:
static { }
表示静态代码块, 只会在类加载时执行一次
class Dog {
static {
System.out.println("这是一个静态代码块");
}
}
对象的创建流程及内存结构(二)
javac
命令编译Java源代码生成字节码文件java
命令将字节码文件加载进虚拟机- 字节码文件具体加载进方法区(非静态区和静态区)
- JVM从静态区中寻找主函数
main()
, 并将其加载进栈开始执行程序 - 后续和(一)一样
静态变量与成员变量的区别
-
变量所属不同:
- 静态变量所属与类, 也称为类变量
- 成员变量所属于对象, 也称为实例变量(实例变量)
-
内存中的位置
- 静态变量存储于方法区中的静态区中
- 成员变量存储于堆内存中
-
在内存中出现的时间
- 静态变量随着类的加载而加载, 随着类的消失而消失
- 成员变量随着对象的创建而在堆内存中出现, 随着对象的消失而消失
-
调用方式
- 静态变量可以用类直接调用, 也可对象调用
- 成员变量只能被对象调用
成员的加载顺序:
静态属性最先运行, 然后是非静态属性, 然后是静态代码块, 然后是构造代码块, 再然后是构造方法, 再是静态方法, 最后是普通方法
重写函数:
在打印对象的时候自动调用 toString()
方法
打印一个继承 Object
的类, 不重写则默认调用的是 Object
的 toString()
方法, 打印出来的是地址的哈希值
用 @override
标记重写
class Dog {
public String name;
public String color;
// 构造函数
public Dog(String name, String color) {
this.name = name;
this.color = color;
}
// 重写
@override
public String toString() {
return this.name + this.color;
}
}
Java (权限) 访问修饰符:
注意: 只有 缺省 和 public
修饰符能修饰类
public
: 任何地方都可以正常访问
protected
: 只能在同包, 同类, 同子类中访问 // 主要用于修饰方法, 为继承设计
缺省(默认): 只能在同包和同类中访问 // 不能被继承, 子类不能访问
private
: 只允许当前类中访问 // 不能被继承
public
修饰类时:
加 public
表示全局类, 该类可以 import
到任何类内. 不加 public
默认为保留类, 只能被同一个包内的其他类引用
私有化构造函数:
- 工具类(全都是静态方法或者静态属性)
- 单例模式
单例模式:
某类创建对象只能且最多只有一个对象
将构造函数用 private
修饰, 让外界无法创建对象
在特殊情况下, 不允许外界创建对象, 减少堆内存占用
步骤:
- 私有化构造函数, 杜绝外界创建对象
- 内部创建该类对象(静态)
- 提供公共接口向外提供该类的对象
饿汉式:
提前提供一个对象
缺点: 即便没有使用对象, 也从始至终常驻内存, 浪费内存
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return singleton;
}
}
饱汉式:
访问的才提供对象
缺点: 线程不安全
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
内部类:
普通内部类
内部类可以直接访问外部类中的成员, 但外部类不能直接访问内部类, 若要访问, 必须创建内部类对象才能访问
内部类本身就是静态的, 用 static
修饰后类似于一个外部类了
创建一个内部类和对象:
class Test {
class Inner {
}
}
class Main {
Inner inner = new Test().new Inner();
}
静态内部类
如果用 static
修饰内部类, 则该内部类属于该外部类, 但不属于外部类的对象
静态内部类可包括静态成员也可包括非静态成员. 根据静态成员不能访问非静态成员的规定, 所以静态内部类不能访问外部类实例成员, 只能访问外部类的静态成员.
class Test2 {
static class Inner2 {
}
}
class Main2 {
Test2.Inner2 inner = new Test2.Inner2();
}
局部内部类:
在方法中创建局部内部类, 创建内部接口或者内部枚举也是一样:
class Outer {
public void test() {
class Inner {
int a = 1;
}
}
}
方法中的内部类不能有访问修饰符
匿名局部内部类:
class Outer {
int s = 12;
public void test() {
class Inner {
int a = 1;
}
new Inner() {
{
System.out.println(a);
System.out.println(s);
execute();
}
public void execute() {
System.out.println(s + 10);
}
};
}
public static void main(String[] args) {
Outer outer = new Outer();
outer.test();
}
}
非静态内部类细节
- 注意非静态内部类中不能定义静态成员变量和静态成员函数. 但可以定义静态成员常量. 原因常量 在生成字节码文件时直接就替换成对应的数字.
- 当内部类在外部类成员位置上被私有修饰, 在外部类以外的其他地方是无法访问的. 只能在外部类 中访问
- 有一个指针指向外部类
- 在外部创建内部类的实例时, 需要
Outer.Inner inner = new Outer().new Inner
以下怎么拿到num:
public class Test {
public static void main(String[] args) {
Outer.Inner in = new Outer().new Inner();
in.show();
}
}
class Outer {
int num = 5;// 外部类的成员变量
class Inner {
int num = 6;// 内部类的成员变量
void show() {
int num = 7; // 内部类局部变量
System.out.println("内部类局部num=" + num);
System.out.println("内部类成员num=" + this.num);
System.out.println("外部类成员num=" + Outer.this.num);
}
}
}
注意如果外部类的成员为 static
, 则内部类在访问外部类成员和方法时用外部类名.来访问
内部类本质上是外部类的成员, 所以外部类的 private
修饰符对内部类无效
为什么内部类可以直接访问外部类的成员, 那时因为内部类持有外部类的引用(外部类.this). 对 于静态内部类不持有 外部类.this 而是直接使用 外部类名.
面向对象三大特征:
- 封装
- 继承
- 多态
有人认为抽象也是一种特征, 但并不被公认
一. 封装 (Encapsulation):
为了安全性, 将成员私有化, 不让外界访问, 同时提供特定的访问方式, 相当于是一个外部类
class Cat {
// 首先私有化属性
private int id;
private String name;
private int age;
// 提供对应的公开访问方法
// get方法:
public int getId() {
return this.id;
}
// set 方法
public void setId(int id) {
this.id = id;
}
}
POJO对象(Plain Ordinary Java Object)
- 根据封装来写
- 私有化属性
- 提供公开的
setter
和getter
方法 - 至少两个或者以上的构造方法
Java Bean对象:
-
所有的成员变量都要使用
private
关键字进行修饰 -
为每一个成员变量编写
setter
、getter
方法 -
创建一个无参数的构造方法
-
创建一个有参数的构造方法
二. 继承 (Inheritance):
在面向对象中, 类与类之间可以存在继承关系
Java是一门典型的单继承编程语言
Java 中如何实现继承:
关键字 extends
父类(超类, 基类), 子类
private
修饰的属性和方法都是无法被子类继承的
protected
修饰的方法就是用于子类继承的
class RichMan() {
public int money = 100000000;
private String wife = "wife";
private void play() {
System.out.println("炒股");
}
protected void controlCompany() {
System.out.println("掌控公司");
}
}
class Son extends RichMan {
public static void main(String[] args) {
Son son = new SOn(); // son中的成员只有money, 方法只有controlCompany
System.out.println(son.money);
son.contralCompany();
}
}
继承的功能:
减少代码的重复, 提高代码的复用度
方法重写(覆盖):
重写 (OverWrite)
覆盖 (OverRide)
发生在继承中, 指的是, 子类继承了父类方法后不满足使用, 就重写该方法以满足子类使用
重写是访问修饰符权限可以放大但不能缩小, 除了访问修饰符, 返回值和参数还有函数名都保持一致
注解:
注解 | 作业 |
---|---|
@Override | 标记覆盖(重写) |
@Deprecated | 标记过时 |
@SuppressWarnings | 压制警告 |
@SuppressWarnings("all") | 忽略全部类型的警告 |
@SuppressWarnings("unchecked") | 忽略未检查的转化, 例如集合没有指定类型的警告 |
@SuppressWarnings("unused") | 忽略未使用的变量的警告 |
@SuppressWarnings("resource") | 忽略与使用 Closeable 类型资源相关的警告 |
@SuppressWarnings("path") | 忽略在类路径, 原文件路径中有不存在的路径的警告 |
@SuppressWarnings("deprecation") | 忽略使用了某些不赞成使用的类和方法的警告 |
@SuppressWarnings("fallthrough") | 忽略 switch 语句执行到底没有break关键字的警告 |
@SuppressWarnings("serial") | 忽略某类实现Serializable , 但是没有定义 serialVersionUID 的警告 |
@SuppressWarnings("rawtypes") | 忽略没有传递带有泛型的参数的警告 |
class RichMan {
public int money = 100000000;
private String wife = "wife";
public void play() {
System.out.println("炒股");
}
}
class Son extends RichMan {
@Override
public void play() {
System.out.println("健身");
}
public static void main(String[] args) {
Son son = new SOn();
son.play();
}
}
super 关键字:
super
在java中, 是一个指针, 类似于 this
关键字
this
关键字指向创建的每一个对象
super
会自动指向父类
super();
// 调用父类的无参构造, 默认在构造函数的第一行, 早于this();
可以不写, 但是写就必须写在构造函数的第一行, 只能写其一
final 关键字:
final
: 最后
-
被他修饰的变量就是常量
static final int a = 1
-
用
final
修饰的方法不能被重写 -
**
final
关键字修饰类则该类不能被继承**
为什么类用 final
修饰:
基于 jvm 的安全性考虑, 防止被继承之后底层代码被篡改从而引起各种安全问题
对象的创建流程及内存结构三:
- javac 命令将源码 (.java) 进行编译, 生成字节码文件 (.class)
- java 命令执行字节码文件
- 将字节码文件加载进虚拟机, 具体方法进入方法区(非静态区和静态区)
- JVM从静态区中寻找main函数, 并将其加载进栈开始执行程序
- main 函数开始执行, 创建对象代码, 如
Son son = new Son();
- 在堆内存中开辟对对象的内存空间, 并分配地址
- 静态代码块执行
- 创建成员变量开始初始化
- 代码块执行
- 子类构造函数从非静态方法区加载进栈开始执行
- 第一句先执行父类的构造函数
- 父类构造函数执行, 为子类继承到的成员变量进行初始化(对象内存空间里的父类空间)
- 父类构造函数弹栈执行完成
- 子类构造函数继续执行
- 再执行子类构造函数的内容, 进行针对性初始化
- 执行完成, 子类构造函数弹栈, 将对象的内存空间地址赋予相应的引用变量
instanceof 关键字 判断对象是否为对应类, 配合继承判断
三. 多态 (Polymorphism):
在继承的基础上才有多态
父类应用指向子类示例
Animal cat = new Cat( );
访问不到子类自身的方法, 但可以重写父类方法来满足子类使用
多态的好处
提高了程序的扩展性.
多态的弊端
通过父类引用操作子类对象时, 只能使用父类中已有的方法, 不能操作子类特有的方法.
多态的前提
-
必须有关系:继承, 实现
-
通常都有重写操作
当父类的引用指向子类对象时, 就发生了向上转型, 即把子类类型对象转成了父类类型. 向上转型的好处是隐藏了子类类型, 提高了代码的扩展性.
public class Test {
public static void main(String[] args) {
Animal cat = new Cat("小黄", "猫");
Animal dog = new Dog("小红", "狗");
PetStore store = new PetStore();
store.wash(cat);
store.wash(dog);
}
}
@Setter
@Getter
class Animal {
String name;
String type;
public Animal() {
}
public Animal(String name, String type) {
this.name = name;
this.type = type;
}
public void beWash() {
// 这段函数体没意义只用来被重写, 可以用抽象方法从而省去写这个函数
}
}
class Dog extends Animal {
public Dog() {
}
public Dog(String name, String type) {
super(name, type);
}
@Override
public void beWash() {
System.out.printf("%s正在被洗, 是一只%s\n", this.name, this.type);
}
}
class Cat extends Animal {
public Cat() {
}
public Cat(String name, String type) {
super(name, type);
}
@Override
public void beWash() {
System.out.printf("%s正在被洗, 是一只%s\n", this.name, this.type);
}
}
class PetStore {
public void wash(Animal pet) {
pet.beWash();
}
}