文章目录
3. 面向对象
3.1. 什么是面向对象
什么是面向对象编程?
两种编程方式,面向过程
和面向对象
面向过程
遛狗:抓狗->栓狗绳->拿纸->开门->关门->下楼->捡狗屎->回家->开门->上楼
面向对象
遛狗:人,狗,绳,门,纸。人.栓狗(狗,绳)->人.出门(门,狗)->人.遛狗(狗)->人.回家(狗)
面向对象的优缺点
- 优点
- 增强代码可读性
- 增强代码可扩展性
- 缺点
- 性能没有面向过程好
面向对象的特征
- 封装:使代码模块化,通过访问修饰符限制
- 继承:扩展已经存在的代码,提高重用性
- 多态:为了接口重用
3.2. 类和对象
3.2.1. 类
- 是什么:类用来表示具有同样特征的一类事物抽象,比如猫,狗,动物,人,车都是类
- 怎么用:
定义类
[修饰符] class 类名 {
属性;//描述对象的状态信息
方法;//描述对象的动作信息
构造方法;//用来构造对象的特殊方法
}
示例
public class Dog{
String name;
int age;
void eat(){
System.out.println("吃饭")
}
}
3.2.2. 对象
- 是什么:对象是类的实例化体现,即这只猫,汤圆,这个猴子,周杰伦,我的车。这些都是对象。对象也被称作实例
- 怎么用:
创建对象
类名 自定义对象名 = new 类名();
//如果只用一次,可以不指定对象名,匿名对象
new 类名().方法();
示例
//通过new创建对象
Dog 汤圆 = new Dog();
//通过“对象.方法”访问属性或者方法
汤圆.eat();
3.2.3. 构造方法
- 是什么:用来创建对象的方法
- 怎么用:
public 类名([参数列表...]){
表达式
}
-
特点:与类名完全相同,没有返回值
-
分类:
- 有参构造:一般用来对属性初始化
- 无参构造(java为所有类默认创建一个隐身无参数构造函数,但是如果有有参构造,则java不会再创建无参构造)
3.3. Java内存结构
暂时只关注:方法区,堆,栈
- 堆:对象的实体
- 栈:对象的引用,class信息
- 方法区:常量和静态变量,字符串。
图解对象创建过程:略
3.4. 面向对象的特征
3.4.1. 封装
- 是什么:隐藏对象属性和实现细节,仅提供公共访问方式
- 怎么用:
//封装之前
public class Dog{
int age;
}
public class TestDog{
public static void main(String[] args){
Dog tangYuan = new Dog();
tangYuan.age = 3;
System.out.println(tangYuan.age);
}
}
//封装之后
public class Dog{
private int age;
public void setAge(int age){
this.age = age
}
public int getAge(){
return age;
}
}
public class TestDog{
public static void main(String[] args){
Dog tangYuan = new Dog();
tangYuan.setAge(3);
System.out.println(tangYuan.getAge());
}
}
- 注意:
- 封装的好处:将变化隔离;便于使用;提高重用性;安全性
3.4.1.1. 包和import
3.4.1.1.1. 包
- 是什么:定义包用
package
关键字 - 怎么用:package cn.yubo.demo;(文件最开始,class外使用)
- 注意:包名规范:所有字母小写
3.4.1.1.2. import
- 是什么:用来导入其他类到本类
- 怎么用:import 包名(文件最开始,class外使用)
3.4.1.2. 权限修饰符
public
可修饰类,接口,变量,方法protected
可修饰变量,方法default
可修饰类,接口,变量,方法private
可修饰变量,方法
3.4.1.3. this
- 是什么:代表本类对象(定义方法的时候,如果内部需要用到成员变量,如果要用到当前对象,就用this表示),区分同名的情况
- 怎么用:
this.成员变量
或者this.成员函数
- 注意:this有个特殊用法:this()用来表示本类的构造函数(this()调用构造函数,必须定义在构造函数第一行,因为构造函数用来初始化,一定要先执行)
3.4.1.4. static
- 是什么:修饰符,用来修饰成员变量和成员函数,还可以修饰静态代码块
- 修饰成员变量的时候,所有对象共享数据,即数据相同
- 被静态修饰的成员可以直接用类名调用
- 修饰静态代码块,用来对类属性初始化,不管创建多少对象,只运行一次
- 随类加载而加载,优先于对象存在
- 注意:
- 静态方法中只能访问静态成员
- 静态方法中不能使用this,super等关键字
- 什么时候用:
- 成员变量:成员变量是否是所有对象都一样。如果都一样就用static
- 成员函数:成员函数是否调用了对象的特有数据,即不同对象调用这方法,执行是否会有区别。如果没有调用特有数据就用static
//成员变量
//不用static
public class Man {
//姓名
int name;
//性别(所有男人的性别都是“男”)
//true表示男,false表示女
boolean sex = true;
}
//用static
public class Man {
//姓名
int name;
//性别(所有男人的性别都是“男”)
//true表示男,false表示女
static boolean sex = true;
}
//成员方法
//不用static
public class Man {
//姓名
int name;
//性别(所有男人的性别都是“男”)
//true表示男,false表示女
static boolean sex = true;
public void printInfo(){
System.out.print("我是男人");
}
}
//用static
public class ManStatic {
//姓名
int name;
//性别(所有男人的性别都是“男”)
//true表示男,false表示女
static boolean sex = true;
public static void printInfo(){
System.out.print(sex?"我是男人":"我是女人");
}
}
public class testMan(){
public static void main(String[] args){
Man man = new Man();
man.printInfo();
ManStatic.printInfo();
}
}
3.4.1.5. 单例设计模式
Java有23种设计模式
是什么:保证一个类在内存中的对象只有一个
怎么用:
/*
思想:
1,不让其他程序创建该类对象。
2,在本类中创建一个本类对象。
3,对外提供方法,让其他程序获取这个对象。
步骤:
1,因为创建对象都需要构造函数初始化,只要将本类中的构造函数私有化,其他程序就无法再创建该类对象;
2,就在类中创建一个本类的对象;
3,定义一个方法,返回该对象,让其他程序可以通过方法就得到本类对象。(作用:可控)
代码体现:
1,私有化构造函数;
2,创建私有并静态的本类对象;
3,定义公有并静态的方法,返回该对象。
*/
//饿汉式:不管你用不用我都先加载一遍
class Single{
private Single(){} //私有化构造函数。
private static Single s = new Single(); //创建私有并静态的本类对象。
public static Single getInstance(){ //定义公有并静态的方法,返回该对象。
return s;
}
}
//懒汉式:用到了在加载
class Single2{
private Single2(){}
private static Single2 s = null;
public static Single2 getInstance(){
if(s==null)
s = new Single2();
return s;
}
}
3.4.2. 继承
- 是什么:让子类具有父类的属性和方法,提高代码复用性
- 怎么用:
//继承之前
public class Dog{
private int age;
private String name;
public void eat(){
System.out.println("吃东西");
}
public void setAge(int age){
this.age = age
}
public int getAge(){
return age;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
}
public class Bird{
private int age;
private String name;
private int wingLength;
public void eat(){
System.out.println("吃东西");
}
public void setAge(int age){
this.age = age
}
public int getAge(){
return age;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setWingLength(int wingLength){
this.wingLength = wingLength
}
public int getWingLength(){
return wingLength;
}
}
//继承之后
public class Animal{
private int age;
private String name;
public void eat(){
System.out.println("吃东西");
}
public void setAge(int age){
this.age = age
}
public int getAge(){
return age;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
}
public class Dog extends Animal{
}
public class Bird extends Animal{
private int wingLength;
public void setWingLength(int wingLength){
this.wingLength = wingLength
}
public int getWingLength(){
return wingLength;
}
}
- 注意:
- Java类只支持单继承,不支持多继承
- 特点
- 成员变量:直接继承父类使用,但是如果出现同名的成员变量,则需要用到
super
(super.成员变量
) - 成员方法:直接继承父类使用,但是当父子类中出现了一模一样的方法,那么需要用到方法的复写(也叫重写,覆盖),当子类的内容需要修改的情况下进行复写
- 构造方法:子类构造函数运行时,先运行了父类的无参构造函数(因为子类的所有构造函数第一行都有一个隐身的语句
super()
)
- 成员变量:直接继承父类使用,但是如果出现同名的成员变量,则需要用到
- 继承的弊端
- 打破了封装性,对于一些类或者功能是需要复写的,如何解决(通过final关键字)
- 类的实例化顺序
- 父类静态数据,构造函数,属性,子类静态数据,构造函数,属性。当new子类的时候,执行顺序是什么?
- 父类(静态变量、静态语句块)
- 子类(静态变量、静态语句块)
- 父类(实例变量、普通语句块)
- 父类(构造函数)
- 子类(实例变量、普通语句块)
- 子类(构造函数)
- 父类静态数据,构造函数,属性,子类静态数据,构造函数,属性。当new子类的时候,执行顺序是什么?
3.4.2.1. 方法的复写
举例:比如动物是父类,狗是子类,动物们都会吃东西,但是狗吃的东西是狗粮,猫吃的是猫粮,那么吃东西的这个方法就需要被重新个性化定义
public class Animal{
private int age;
public void eat(){
System.out.println("吃东西");
}
}
public class Dog extends Animal{
public void eat(){
System.out.println("吃狗粮");
}
}
- 注意
- 重写的特点(两同两小一大):
- “两同”即方法名相同、形参列表相同;
- “两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
- 如果方法的返回类型是void和基本数据类型,则返回值重写时不可修改。但是如果方法的返回值是引用类型,重写时是可以返回该引用类型的子类的。
- “一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。
- 重写与重载的区别:
- 重载:同一个类中,名称相同,但参数列表不同
- 重写:子类或者实现类中,名称、参数列表、返回值都必须相同(异常可减少或删除,不能扩展)
- 构造器能否被重写
- 不能,因为构造器不能被继承。子类调用父类构造器用super关键字
- 重写的特点(两同两小一大):
3.4.2.2. super
- 是什么:代表父类对象(定义方法的时候,如果内部需要用到父类成员变量,如果要用到当前对象,就用this表示),用来区分同名的情况
- 怎么用:
super.父类成员变量
或者super.父类成员函数
(方法中使用) - 注意:
- super有个特殊用法:super()用来表示父类的构造函数(super()调用构造函数,必须定义在构造函数第一行,因为构造函数用来初始化,一定要先执行)
- 子类继承父类后,会在子类构造函数中的第一行调用父类无参构造函数(隐藏掉了),如果父类写了有参构造函数,想编译通过有两种方式
- 在父类添加无参构造函数
- 在子类构造函数第一行调用父类有参构造函数
- super()和this()是否可以同时出现在构造函数中? 不可以,因为都需要运行在第一行,所以不能同时存在。
- super()或者this()为什么要定义在第一行? 构造函数用于初始化,所以初始化动作要先完成
3.4.2.3. final
- 是什么:用来修饰类,方法,变量。给一些固定的数据起一个阅读性较强的名称。
- 特点:
- 修饰类:类不可以被继承
- 修饰方法:方法不可以被复写
- 修饰变量:变量只可以被赋值一次
3.4.2.4. 抽象类
- 是什么:包含抽象方法的类就是抽象类。通过
abstract方法
定义规范,然后要求子类必须定义具体实现。通过抽象类,我们就可以做到严格限制子类的设计,使子类之间更加通用。- 抽象方法:使用
abstract
修饰的方法,没有方法体,只有声明。定义的是一种“规范”,就是告诉子类必须要给抽象方法提供具体的实现。
- 抽象方法:使用
- 怎么用:
public class Animal{
private int age;
public abstract void eat();
}
public class Dog extends Animal{
public void eat(){
System.out.println("吃狗粮");
}
}
- 注意:
- 有抽象方法的只能定义成抽象类,抽象类可以没有抽象方法,可以有普通方法
- 抽象类不能实例化,即不能用new来实例化抽象类。
- 抽象类可以包含属性、方法、构造方法。但是构造方法不能用来new实例,只能用来被子类调用。
- 抽象方法必须被子类实现。
- abstract不可以和以下关键字共存
- final:final修饰不可以被复写,abstract一定要复写,冲突!
- private:private只能本类访问,abstract一定要复写,都访问不到怎么复写,冲突!
- static:static表明这个方法在不生成类实例就可以被调用,但是abstract没有方法体,不能被直接调用,冲突!
3.4.2.5. 接口
是什么:接口是一种协议,规范传入对象必须具备的某些特征。保证在调用时既不会发生错误,也不需要提前检查。主要用来进行功能集合的声明
举例:
接口是一种协议,规范传入对象必须具备的某些特征。保证在调用时既不会发生错误,也不需要提前检查。继承可以有同样的效果,但是很多场景使用继承会逻辑混乱。
比如我需要一个鸭子对象,于是我定义了一个鸭子类,规定鸭子会游泳会叫;现在有一个绿头鸭类和一个山麻鸭类分别继承自鸭子类,显然它们都可以成为我需要的那个鸭子对象。
然而,现在有一个橡皮鸭子类,它也会游泳(指你用手推它)会叫(指你捏它),它也有资格成为我需要的鸭子对象,那么应该让它继承自鸭子类吗?不应该,因为它和鸭子是有区别的。假设鸭子类继承自动物类,那么让橡皮鸭子类继承自鸭子类甚至是错误的,因为橡皮鸭子不是动物。
所以更好的做法是使用接口。我们可以定义一个鸭子接口,然后让鸭子类实现鸭子接口。在鸭子接口中,我们定义“会游泳会叫”就算是鸭子。这样一来,绿头鸭、山麻鸭和橡皮鸭,甚至AMX-40都可以成为这个接口的对象,而且也不会有抽象上的问题。
- 怎么用:
//接口定义:
[修饰符] interface 接口名 {
//接口中的字段默认使用 public static final修饰
//接口中的方法默认使用public abstract修饰
/*
jdk1.8接口新特性:
1. 接口中可以定义public static 静态方法,可以通过接口名直接调用
2. 接口中可以通过default指定默认实现方法,实现类不复写也可以直接调用
*/
}
//类实现接口
[修饰符] class 类名 implements 接口{
//重写接口的抽象方法
}
public interface Duck{
//接口中的字段默认使用 public static final修饰
public static final int legNuber =2;
//接口中的方法默认使用public abstract修饰
public abstract void swim();
/*
jdk1.8接口新特性:
1. 接口中可以定义public static 静态方法,可以通过接口名直接调用
2. 接口中可以通过default指定默认实现方法,实现类不复写也可以直接调用
*/
public static void introduceSelf(){
System.out.println("鸭子都有"+legNumber+"条腿");
}
default void sound(){
System.out.println("嘎嘎嘎");
}
}
public class LvTouDuck implements Duck{
public void swim(){
System.out.println("绿头鸭游泳");
}
}
public class XiangpiDuck implements Duck{
public void swim(){
System.out.println("橡皮鸭游泳");
}
}
public class Test(){
public static void main(String[] args){
LvTouDuck duck = new LvTouDuck();
duck.swim();
//新特性
Duck.introduceSelf();
duck.sound();
}
}
- 注意:
- 接口可以多实现,这也是继承改良后的结果
- 接口可以多继承接口
- 接口和抽象类有什么异同
- 同:都不能实例化,但可以定义引用
- 异:
- 接口不可定义构造方法,具体方法(1.8之后可以定义用default修饰符定义) 。抽象类可以有,并且可以没有抽象方法
- 接口只能public修饰(1.9之后允许private),抽象类修饰符可以自定义
接口练习
3.4.2.6. 模板设计模式
是什么:当功能内部一部分实现时确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
怎么用:
abstract class GetTime{
public final void getTime(){ //此功能如果不需要复写,可加final限定
long start = System.currentTimeMillis();
code(); //不确定的功能部分,提取出来,通过抽象方法实现
long end = System.currentTimeMillis();
System.out.println("毫秒是:"+(end—start));
}
public abstract void code(); //抽象不确定的功能,让子类复写实现
}
class SubDemo extends GetTime{
public void code(){ //子类复写功能方法
for(int y=0; y<1000; y++){
System.out.println("y");
}
}
}
3.4.3. 多态
- 是什么:父类引用指向子类对象。用来提高扩展性,缺点是只能访问父类的方法,不能访问子类独有的方法
- 怎么用:
父类 自定义名 = new 子类()
- 注意:
- 多态的前提
- 要有继承关系
- 子类重写父类方法
- 父类对象指向子类引用
- 多态的特点
- 成员变量:编译运行都看左边
- 成员函数:编译看左边,运行看右边
- 静态函数:编译运行都看左边
- 多态的前提
public class 花弧 {
public int age = 55;
public String name = "花弧";
public static void intruduce(){
System.out.println("我叫"+this.name +" 我今年" + age +"岁");
}
public void mountBlade(){
System.out.println("骑马砍杀");
}
public void goWC(){
System.out.println("站着尿尿");
}
}
public class 花木兰 {
public int age = 20;
public String name = "花木兰";
public void goWC(){
System.out.println("不站着尿尿");
}
public void makeUp(){
System.out.println("化妆");
}
}
public class Test{
public static void main(String[] args){
//花木兰被提升为花弧,也叫向上转型
花弧 huahu = new 花木兰();
//成员变量编译运行看左边
System.out.println(huahu.name);//花弧
//成员函数编译看左边,运行看右边
huahu.mountBlade();//骑马砍杀
huahu.goWC(); //站着尿尿
// huahu.makeUp(),花弧不会,所以不能调用
//静态函数编译运行看左边
花弧.intruduce(); //我叫花弧 我今年55岁
//后来从军回来了,花木兰要做回自己,得结婚啊。(向下转型)
花木兰 huamulan = ( 花木兰)huahu;
//做回自己就可以调用本身的方法了(化妆)
huamulan.makeUp();
//向下转型一定是在多态前提下,不然直接把父亲强制转型成花木兰,不成变态了。
}
}