什么是 OOP 面向对象编程 (Object-Orien Programming,OOP)
面向过程:编写对数据执行的过程或方法
面向对象:创建同时包含数据和方法的对象
类和对象:
类是对象的模板,而对象是类的实例
创建各个对象时,它们会从类中继承所有变量和方法
类 类的构成:
成员变量(属性)
构造方法
成员方法
代码块
内部类
**类的分类: **
Javabean类:用来描述一类事务的类,不写main方法
测试类:程序的主入口,编写main方法,在测试类中创建Javabean类的对象并进行赋值调用
工具类:不描述事物,帮我们做一些事情
创建类: class
(大驼峰,java 文件的名称应与类名匹配)
1 2 3 4 pulic class Person { public String name; public int age; }
一个文件只能一个类是 public 修饰,public 类名必须是文件名
JavaBean
类名需要见名知意
成员变量使用private 修饰
提供至少两个构造方法
成员方法:
提供每一个成员变量对应的get和set方法
如果还有其他行为,也需要写上
工具类
类名见名知意
私有化构造方法
方法都用 public static
修饰
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class ArrayUtil { private ArrayUtil () {} public static String printArr (int [] arr) { StringBuilder sb = new StringBuilder (); sb.append("[" ); for (int i = 0 ; i < arr.length; i++) { if (i == arr.length - 1 ) { sb.append(arr[i]); } else { sb.append(arr[i]).append(", " ); } } sb.append("]" ); return sb.toString(); } }
测试类 public static void main(String[] args) {}
1 2 3 4 5 6 7 public class TestDemo { public static void main (String[] args) { int [] arr = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 }; String str = ArrayUtil.printArr(arr); System.out.println(str); } }
对象 创建对象: new
1 Person ming = new Person ();
**对象的属性:**访问、修改:.
方法 方法(method)是程序中最小的执行单元
定义 1 2 3 4 修饰符 返回类型 方法名(参数列表) { 方法体; return 方法返回值; }
修饰符:
private
:只能在类内部调用
static
:静态方法,属于类本身,不需要创建对象也能调用
public
:任何类可访问(常用的还是 public static ...
)
**this:**指向当前实例
可变参数: 类型...
1 2 3 4 5 6 class Group { private String[] names; public void setNames (String... names) { this .names = names; } }
按值传递 vs 引用传递:
类型
传递
基本数据类型
按值传递(传递真实数据)
引用类型(数组、对象)
引用传递(传递地址值)
构造方法 快捷键:alt+insert
作用:创建对象时,虚拟机调用构造方法,初始化成员变量
创建对象时由虚拟机 调用,不能手动调用
每创建一次对象,调用一次
要求:
方法的名称是类名
没有返回值(没有 void
,不能写 return
)
调用:new
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Person { private String name; private int age; public Person () { System.out.println("success" ); } public Person (String name, int age) { this .name = name; this .age = age; } public String getName () { return this .name; } public int getAge () { return this .age; } }
如果没有定义构造方法,系统将给出一个默认的无参数构造方法
如果定义了构造方法,系统将不再提供默认的构造方法(不管用不用,都写上 )
重载 重载 overload:
同一个类,方法名相同(小驼峰)
参数列表不同:数量、类型、顺序
返回值可不同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Hello { public void hello () { System.out.println("Hello, world!" ); } public void hello (String name) { System.out.println("Hello, " + name + "!" ); } public void hello (String name, int age) { if (age < 18 ) { System.out.println("Hi, " + name + "!" ); } else { System.out.println("Hello, " + name + "!" ); } } }
方法链 要求:
前一个方法返回类本身 this
或者是构造函数
修饰符
访问控制修饰符 - 控制访问级别
非访问控制修饰符 - 不控制访问级别,但提供其他功能
访问修饰符
对于类 :
修饰符
作用
public
所有类都能访问
默认不写
同包 内可访问
对于属性、方法和构造函数 :从小到大
修饰符
作用
private
仅限当前类可访问
默认不写
同包 内可访问
protected
同包 或 子类 可访问
public
所有类 都能访问
实际开发中,一般只用private和public
特例:如果方法中的代码是抽取其他方法中共性代码,这个方法一般也私有
非访问修饰符
对于类 :
修饰符
作用
final
表示类不可继承
abstract
表示类是抽象类,不能实例化
对于属性和方法 :
修饰符
作用
final
属性和方法不能被重写、修改(常量 )
static
属性方法属于类,而不是实例
abstract
只能在抽象类的抽象方法中使用,方法没有主体
transient
属性和方法不会被序列化
synchronized
方法一次只能由一个线程访问
volatile
属性的值不在线程本地缓存,而是始终从“主内存”读取
static
重新认识main方法
final
包、代码块、作用域 包
导入类: import java.util.Scanner;
导入整个包: import java.util.*;
(不包括子包的 class
)
代码块
局部代码块
构造代码块
静态代码块
局部代码块:
构造代码块:调用构造函数时执行
静态代码块:
格式:static{}
特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发、只执行一次
使用场景:在类加载的时候,做一些数据初始化的时候使用
作用域 **类作用域:**在整个类中可访问
**方法作用域:**只能在该方法中访问
**块作用域:**变量只能在块内访问
**循环作用域:**变量只能在循环体内访问
**包作用域:**同包内可访问(没用任何修饰符 public
、private
、protected
)
封装 对象代表什么,就得封装对应的数据,并提供数据对应的行为
封装:确保对用户敏感的数据进行隐藏
private
:声明变量/属性(只能在同一个类中访问,外部类无权访问)
Get
、Set
方法:访问、更新 private
变量
Get Set get
方法返回变量值,set
方法设置值
两者的语法是它们都以 get
或 set
开头,后跟变量的名称,第一个字母为大写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Student { private String name; private int age; public void setName (String name) { this .name = name; } public String getName () { return name; } public void setAge (int age) { this .age = age; } public int getAge () { return age; } }
this this 修饰的变量用于指代成员变量,主要作用是(区分局部变量和成员变量的重名问题)
重名问题:就近原则 -> 局部变量 -> 成员变量
意义
更好地控制类属性和方法
类属性可以是只读 的(如果只使用 get
方法),也可以是只写 的(如果只使用 set
方法)
灵活:程序员可以更改代码的一部分,而不会影响其他部分
提高数据安全性
继承 特点:
Java只支持单继承发,不支持多继承,但支持多层继承
每一个类都直接或者间接的继承于 Object
格式 extends: class 子类 extends 父类 {}
1 2 3 4 5 6 7 class 父类 { ... } class 子类 extends 父类 { ... }
修饰符 修饰符:
private
:子类无法访问父类的属性、方法
protected
:子类可访问父类的属性、方法
final
:其他类无法继承
注意:能否继承 不等于 能否访问
子类不能继承父类的构造方法
子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已,可以通过getter/setter方法访问父类的private成员变量
内存分析 构造方法:无法继承
成员变量:(方法区省略 Object.class)
成员方法:
super super表示父类对象的引用
继承中成员变量的访问特点:
继承中构造方法的访问特点:
重写 **要求:**建议写注解 @override
方法名、参数列表(建议:返回类型相同)
访问权限子类必须大于等于父类(空着不写 < protected < public)
返回值类型子类必须小于等于父类
只有被添加到虚方法表中的方法才能被重写
重写的本质:
重写 vs 重载:
重写:
1 2 3 4 5 6 7 public class Processor { public void process (int i, int j) { } } class MathProcessor extends Processor { @Override public void process (int i, int j) { } }
重载:
1 2 3 4 5 6 public class Processor { public void process (int i, int j) { } public void process (int [] ints) { } public void process (Object[] objs) { } }
多态 同类型的对象,表现出不同的形态
形式 表现形式:父类类型 对象名称 = 子类对象;
前提:
有继承关系
有父类引用指向子类对象
有方法重写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public class Person { private String name; private int age; public void show () { System.out.println(name + ", " + age); } } public class Administrator extends Person { @Override public void show () { System.out.println("管理员的信息为:" + getName() + ", " + getAge()); } } public class Student extends Person { @Override public void show () { System.out.println("学生的信息为:" + getName() + ", " + getAge()); } } public class Teacher extends Person { @Override public void show () { System.out.println("老师的信息为:" + getName() + ", " + getAge()); } } public class Test { public static void main (String[] args) { Student s = new Student (); s.setName("张三" ); s.setAge(18 ); Teacher t = new Teacher (); t.setName("王建国" ); t.setAge(30 ); Administrator admin = new Administrator (); admin.setName("管理员" ); admin.setAge(35 ); register(s); register(t); register(admin); } public static void register (Person p) { p.show(); } }
调用特点 调用成员的特点:
变量调用:编译看左边,运行看左边
方法调用:编译看左边,运行看右边
1 2 3 4 5 6 7 Fu f = new Zi ();System.out.println(f.name); f.show();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class Test { public static void main (String[] args) { Animal a = new Dog (); System.out.println(a.name); a.show(); } } class Animal { String name = "动物" ; public void show () { System.out.println("Animal --- show 方法" ); } } class Dog extends Animal { String name = "狗" ; @Override public void show () { System.out.println("Dog --- show 方法" ); } } class DCat extends Animal { String name = "猫" ; @Override public void show () { System.out.println("Cat --- show 方法" ); } }
内存分析
优势和弊端
弊端的解决方案:强制转型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 package com.itheima;public class Test { public static void main (String[] args) { Animal a = new Dog (); a.eat(); Dog dog = (Dog) a; dog.loonHome(); if (a instanceof Dog d) { d.loonHome(); } else if (a instanceof Cat c) { c.catchMouse(); } else { System.out.println("没有这个类型,无法转换" ); } } } class Animal { public void eat () { System.out.println("动物在吃东西" ); } } class Dog extends Animal { @Override public void eat () { System.out.println("狗吃骨头" ); } public void loonHome () { System.out.println("狗看家" ); } } class Cat extends Animal { @Override public void eat () { System.out.println("猫在吃小鱼干" ); } public void catchMouse () { System.out.println("猫抓老鼠" ); } }
转型 向上转型: Person p = new Student();
p
是 Person
类型的引用变量,指向 Student
对象(实例)
编译时是 Person
,运行时是 Student
向下转型:
1 2 3 4 Person p1 = new Student (); Person p2 = new Person ();Student s1 = (Student) p1; Student s2 = (Student) p2;
**instanceof:**判断变量指向的实例是否是指定类型,或者这个类型的子类,若是可直接转型
1 2 3 4 5 6 7 8 9 public class Main { public static void main (String[] args) { Object obj = "hello" ; if (obj instanceof String s) { System.out.println(s.toUpperCase()); } } }
**多态:**真正执行的方法取决于运行时期实际类型的方法
抽象 抽象方法:将共性的方法抽取到父类后,由于每个子类执行的内容不一样,所以在父类中不能确定具体的方法体 。该方法就可以定义为抽象方法
抽象类:如果一个类中存在抽象方法,那么该类就必须声明为抽象类
定义
抽象方法:public abstract 返回值类型 方法名(参数列表);
抽象类:public abstract class 类名{}
特点
抽象类不能实例化(不能创建对象)
抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
可以有构造方法
抽象类的子类:
要么重写抽象类中的所有抽象方法
要么是抽象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public class Main { Student student = new Student ("ZhangSan" , 2 ); } abstract class Person { private String name; private int age; public Person () { } public Person (String name, int age) { this .name = name; this .age = age; } public abstract void work () ; public void sleep () { System.out.println("Sleeping..." ); } } class Student extends Person { public Student () { } public Student (String name, int age) { super (name, age); } @Override public void work () { System.out.println("Student working..." ); } }
意义 同一代码规范
接口 抽象:定义一类事物
接口:定义一类行为(接口就是一种规则 ,是对行为的抽象)
定义和使用
定义接口:public interface 接口名{}
接口不能实例化
类实现接口:
单实现:public class 类名 implements 接口名 {}
多实现:public class 类名 extends 父类 implements 接口名1, 接口名2 {}
接口的子类(实现类):
要么重写接口中的所有抽象方法
要么是抽象类
成员特点
成员变量:
只能是常量
默认修饰符:public static final(即使不写,Java也会帮你带上)
构造方法:没有
成员方法:
只能是抽象方法
默认修饰符:public abstract
JDK7以前:接口中只能定义抽象方法(主)
JDK8的新特性:接口中可以定义有方法体的方法
JDK9的新特性:接口中可以定义私有方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public interface InterF { void run () ; String getName () ; int add (int a , int b) ; int AGE = 12 ; String SCHOOL_NAME = "DUT" ; }
接口和类之间的关系
新增方法 默认方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class Main { public static void main (String[] args) { InterImpl ii = new InterImpl (); ii.method(); ii.show(); } } interface InterA { public abstract void method () ; public default void show () { System.out.println("InterA接口中的默认方法 --- show" ); } } interface InterB { public abstract void method () ; public default void show () { System.out.println("InterB接口中的默认方法 --- show" ); } } class InterImpl implements InterA , InterB { @Override public void method () { System.out.println("实现类重写的接口方法" ); } @Override public void show () { System.out.println("重写接口中的默认方法" ); } }
静态方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class Main { public static void main (String[] args) { Inter.show(); InterImpl.show(); } } interface Inter { public abstract void method () ; public static void show () { System.out.println("Inter接口中的静态方法" ); } } class InterImpl implements Inter { @Override public void method () { System.out.println("Inter接口中的静态方法" ); } public static void show () { System.out.println("...." ); } }
私有方法 为了解决默认方法、静态方法中重复的代码的问题
接口中私有方法的定义格式:
格式1:private 返回值类型 方法名(参数列表) {}
范例1:private void show() {}
格式2:private static 返回值类型 方法名(参数列表) {}
范例2:private static void method() {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public interface InterA { public default void show1 () { System.out.println("show1方法开始执行了" ); show3(); } public static void show2 () { System.out.println("show2方法开始执行了" ); show4(); } private void show3 () { System.out.println("记录程序在运行过程中的各种细节,这里有100行代码" ); } private static void show4 () { System.out.println("记录程序在运行过程中的各种细节,这里有100行代码" ); } }
应用
适配器设计模式
内部类 内部类:在一个类的里面,再定义一个类
使用场合:B类表示的事物是A类的一部分,且B单独存在没有意义
分类:
成员内部类
静态内部类
局部内部类
匿名内部类(重要)
成员内部类
**获取成员内部类对象: **
方式一:在外部类中编写方法,对外提供内部类的对象(private)
直接创建格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Main { public static void main (String[] args) { Outer.Inner1 in1 = new Outer ().new Inner1 (); Outer o = new Outer (); Object in2 = o.getInner2(); } } class Outer { String name; class Inner1 { } private class Inner2 { static int a = 10 ; } public Inner2 getInner2 () { return new Inner2 (); } }
内部类获取外部类的成员变量:
静态内部类 静态内部类是一种特殊的成员内部类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class Main { public static void main (String[] args) { Outer.Inner oi = new Outer .Inner(); oi.show1(); Outer.Inner.show2(); } } class Outer { int a = 10 ; static int b = 20 ; static class Inner { public void show1 () { System.out.println("非静态的方法被调用" ); } public static void show2 () { System.out.println("静态的方法被调用" ); } } }
局部内部类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class Main { public static void main (String[] args) { Outer o = new Outer (); o.show(); } } class Outer { int b = 20 ; public void show () { int a = 10 ; class Inner { String name; int age; public void method1 () { System.out.println(a); System.out.println(b); System.out.println("局部内部类中的method1方法" ); } public static void method2 () { System.out.println("局部内部类中的method2方法" ); } } Inner i = new Inner (); System.out.println(i.name); System.out.println(i.age); i.method1(); Inner.method2(); } }
匿名内部类 本质:不是类 ,而是一个类的子类对象 /接口的实现类对象 ,只不过隐藏了名字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 public class Main { public static void main (String[] args) { new Animal () { @Override public void eat () { System.out.println("重写了eat的方法" ); } }; method( new Animal () { @Override public void eat () { System.out.println("狗吃骨头" ); } } ); Swim s = new Swim () { @Override public void swim () { System.out.println("重写了Swim的方法" ); } }; s.swim(); new Swim () { @Override public void swim () { System.out.println("重写了Swim的方法" ); } }.swim(); } public static void method (Animal a) { a.eat(); } } interface Swim { public abstract void swim () ; } abstract class Animal { public abstract void eat () ; } class Dog extends Animal { @Override public void eat () { System.out.println("狗吃骨头" ); } }
核心类 枚举类 enum: enum
类型就是 class
(引用类型)
继承自java.lang.Enum
,且无法被继承
只能定义 enum
的实例,无法通过 new
创建
定义的每个实例都是引用类型的唯一实例
可用于 switch
语句
创建:
1 2 3 public enum Color { RED, GREEN, BLUE; }
编译出的 class
文件:
1 2 3 4 5 6 7 8 public final class Color extends Enum { public static final Color RED = new Color (); public static final Color GREEN = new Color (); public static final Color BLUE = new Color (); private Color () {} }
比较: ==
(不像其他引用类型只能用 equals()
)
常量名: String s = Weekday.SUN.name(); // "SUN"
常量顺序: int n = Weekday.MON.ordinal(); // 1
(从0开始)
添加字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 enum Weekday { MON(1 , "星期一" ), TUE(2 , "星期二" ), WED(3 , "星期三" ), THU(4 , "星期四" ), FRI(5 , "星期五" ), SAT(6 , "星期六" ), SUN(0 , "星期日" ); public final int dayValue; private final String chinese; private Weekday (int dayValue, String chinese) { this .dayValue = dayValue; this .chinese = chinese; } @Override public String toString () { return this .chinese; } }
记录类 record:
用 final
修饰class以及每个字段(实现不变类)
自动创建构造方法,重写 toString()
、equals()
、hashCode()
不能直接从 Record
派生,只能通过关键词由编译器实现继承
1 record Point (int x, int y) {...}
相当于:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 final class Point extends Record { private final int x; private final int y; public Point (int x, int y) { this .x = x; this .y = y; } public int x () { return this .x; } public int y () { return this .y; } public String toString () { return String.format("Point[x=%s, y=%s]" , x, y); } public boolean equals (Object o) { ... } public int hashCode () { ... } }