文章目录
面向对象
面向对象的三大特征:封装、继承、多态
类
public class 类名{
1.成员变量
2.成员方法
3.构造器
4.代码块
5.内部类
}
public class Car{
// 属性(成员变量)
String name;
double price;
//行为(方法)
public void start(){
System.out.println(name + "启动");
}
public void run(){
System.out.println("价格" + price);
}
}
// 调用
// 类 变量名称 = new 构造器;
Car c = new Car();
c.name = "car";
c.start();
注意事项
- 类名要首字母大写+驼峰
- 一个Java文件可以定义多个class类,但是只有一个类是public修饰,且public修饰的类名必须成为文件名。
- 成员变量的完整定义格式为
// 修饰符 数据类型 变量名 = 初始值
// 通常我们不会赋初始值,会自动有默认值
byte short int long 0
double float 0.0
boolean false
String等引用类型 null
内存机制
- 对象放在堆内存中
Car c = new Car();
c变量中存储的是对象地址- 成员变量在堆内存中,方法在方法区中
构造器
定义在类中,可以用于初始化一个类的对象,并返回对象的地址
new Car()
中的Car()
就是一个构造器
this关键字
可以出现在构造器、方法中;代表当前对象的地址(对象或方法中的this都指向对象地址)
封装
对象代表什么,就得封装对应的数据,并提供数据对应的行为
例如人画圆,创建一个人对象、创建一个圆对象、其中画圆这个方法,应该封装到圆对象中
- 一般建议对成员变量使用private关键字修饰
- 为每个成员变量提供public修饰的getter和setter方法暴露其取值和赋值
javabean格式
- 成员变量使用private修饰
- 提供成员变量对应的setter和getter
- 必须提供一个无参构造器;有参构造器可写可不写
public class User {
// 1.私有变量
private String name;
private double height;
// 3.要求提供无参构造器,有参构造器可选
public User() {
}
public User(String name, double height, double salary) {
this.name = name;
this.height = height;
}
// 2.提供getter和setter方法
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 class User {
private String name;
private double height;
}
局部变量
public class User {
public static void main(String[] args) {
double score = 100;
}
}
区别 | 成员变量 | 局部变量 |
---|---|---|
类中位置不同 | 类中,方法外 | 常见于方法中 |
初始化值不同 | 有默认值,无需初始化 | 没有默认值,使用前要先赋值 |
内存位置不同 | 堆内存 | 栈内存 |
生命周期不同 | 随着对象创建而存在,随对象消失而消失 | 随着方法调用而存在,随着方法结束而消失 |
作用域 | 在归属的大括号中 |
static静态关键字
使用
- static是静态,可以修饰成员变量和方法
- static修饰成员变量表示该成员变量只能在内存中存储一份,可以被共享、修改
// 创建
public class User {
public static int number = 111;
// 类中访问
out.println(number);
}
// 类名访问
public class Test {
public static void main(String[] args) {
out.println(User.number);
}
}
// 对象访问
User u = new User();
out.println(u.number);
与普通成员变量在使用方面的区别主要是,普通成员变量只在实例化对象后访问;而static变量推荐用类名访问,并且同一个类中静态成员变量的访问可以省略类名
成员方法使用场景
- 表示对象自己的行为,且方法中需要访问实例成员的,则该方法必须声明成实例方法(无static)。
- 如果该方法以执行一个共用功能为目的,则可以声明静态方法(有static)。
内存机制
实例方法只有在创建实例的时候才会加载到方法区,静态方法属于类,所以会在类产生的一开始就加载到方法区
代码执行流程为,执行静态方法Student.getMax()
和getMax()
之后如果直接执行study()
会报错,因为没有产生实例,不会加载study()
方法,之后创建实例,正常执行。
注意事项
- 静态方法只能访问静态成员,不可以直接访问实例成员(因为实例成员只有再创建实例后才能被访问)
- 实例方法也可以创建静态成员,也可以访问实例成员
- 静态方法中不能出现this关键字(this代表的是当前对象,所以静态方法中不会用)
static应用:工具类
类中都是一些静态方法,每个方法都是以完成一个共用功能为目的,这个类用来给系统开发人员共同使用(定义一个类,只提供方法)
工具类无需创建对象,建议将工具类的构造器进行私有
public class ArrayUtil {
// 私有构造器
private ArrayUtil(){}
public static String toString(int[] arr){
// 内部逻辑
return "1";
}
}
外部使用工具类
ArrayUtil.toString(arr);
static应用:代码块
静态代码块
- 格式:static{}
- 特点:需要通过static关键字修饰,随着类的加载而加载,并自动触发、只执行一次
- 使用场景:在类加载的时候做一些静态数据初始化的操作,方便后续使用
// 打牌程序
public class Demo {
// 定义静态集合,这样的集合只加载一个(当前房间只需要一副牌)
public static ArrayList<String> cards = new ArrayList<>();
static {
// 将牌放入集合
String[] sizes {"3","4"...};
String[] colors = {"♠","♦"...};
for (int i=0;i<sizes.length;i++){
for(int j=0;j<colors.length;j++){
String card = sizes[i]+colors[i];
cards.add(card);
}
}
}
public static void main(String[] args) {
out.println(cards);
}
}
这种写法比都写在main里优雅
实例代码块(用的比较少)
- 格式:{}
- 特点:每次创建对象,调用构造器执行时,都会执行该代码块中的代码,并且在构造器执行前执行
- 使用场景:初始化实例资源
static应用:单例模式
可以保证系统中,应用该模式的这个类永远只有一个实例,即一个类永远只能创建一个对象(例如任务管理器只需要一个就可以解决问题了,这样可以节省内存空间)
饿汉单例设计模式
在类获取对象的时候,对象已经提前创建好了
- 定义一个类,把构造器私有(避免外界创建对象,节省内存)
- 定义一个静态变量存储一个对象
之后在外部创建实例SingleInstance s1 = SingleInstance.instance
,饿汉单例的核心就是,无论在外部创建多少个类,其实都是调用的同一个内部对象
懒汉单例设计模式
在真正需要该对象的时候,才会去创建一个对象(延迟加载对象)
- 定义一个类,把构造器私有
- 定义一个静态变量存储一个对象(但是不要new,到需要时再创建)
- 提供一个返回单例对象的方法
class SingleInstance{
private static SingleInstance instance;
private SingleInstance(){}
public static SingleInstance getInstance(){
if(instance == null){
instance = new SingleInstance();
}
return instance;
}
}
// 外部调用
SingleInstance s1 = SingleInstance.getInstance();
继承
java中提供了一个关键字extends
,这个关键字可以让一个类和另一个类建立父子关系
public class Student extends People{}
作用:当子类继承父类后,就可以直接使用父类公共的属性和方法
内存情况:
继承的特点
- 子类可以继承父类的属性和行为,但是子类不能继承父类的构造器
- 子类可以继承父类的私有成员吗?可以继承,但是不能直接访问(有些资料中直接写的不能继承)
- 子类可以继承父类的静态资源吗?不算继承,但是可以共享使用(即可以访问)
- java是单继承模式,即一个类只能继承一个直接父类
- java不支持多继承、但是支持多层继承
- java中所有的类都是Object类的子类
方法重写
在继承中,子类出现了和父类中一模一样的方法声明
// 子类
// 重写call()方法
public void call(){
// 先继承父类中call()方法的基本功能
super.call()
// 实现自己想实现的功能
// ...
}
注意:
- 重写方法的名称、形参列表必须与被重写方法的名称和参数列表一致
- 私有方法不能被重写
- 子类重写父类方法时,访问权限必须大于或等于父类(缺省<protected<public)
- 子类不能重写父类的静态方法
@Override重写注解
@Override放在重写后的方法上,作为重写是否正确的校验注解
构造器特点
子类中所有的构造器默认都会先访问父类中无参的构造器,再执行自己
// 父类
public class Animal {
public Animal(){
out.println("父类");
}
}
// 子类
public class Dog extends Animal{
public Dog(){
out.println("子类");
}
}
// 执行
public static void main(String[] args) {
Dog d1 = new Dog();
System.out.println(d1);
}
/** 结果输出
父类
子类
*/
// 即先访问父类中的无参构造器,再执行自己的
为什么要这样执行呢?
- 子类在初始化的时候,有可能会用到父类中的数据,如果父类没有完成初始化,子类无法使用父类的数据
- 子类初始化之前,一定要调用父类构造器先完成父类数据空间的初始化
怎么调用父类构造器的?
- 子类构造器的第一行语句默认都是:
super()
,不写也存在。注:这里调用的是无参构造器 - 如果想要调用父类的有参构造器,就在子类使用
super(参数)
,这样可以初始化继承自父类的数据。
this和super
语法
包
用来分门别类的管理各种不同类的,类似于文件夹、建包利于程序的管理和维护
- 同一个包下的类,互相可以直接访问
- 不同包下的类,必须先导入才能访问
- 如果这个类中使用不同包下相同的类名,此时默认只能导入一个类的包,另一个类药使用全名访问
com.mkbird.package.it.Student s1 = new com.mkbird.package.it.Student;
导包
import 包名.类名;
权限修饰符
从小到大
private–>缺省–>protected–>public
final
- 修饰类:表明该类是最终类,不能被继承
- 修饰方法:表明该方法是最终方法,不能被重写
- 修饰变量:表示该变量第一次被赋值后,不能再次被赋值
- 变量是基本类型:变量存储的数据值不能发生改变
- 变量是引用类型:变量存储的地址值不能发生改变,但是地址指向的对象内容可以改变
通常不会使用final
常量
- 常量是使用了public、static、final等修饰的成员变量,必须有初始值,而且执行过程中其值不能被改变
- 常量命名规范:全部大写,多个单词下划线连接
常量的执行原理
- 编译阶段进行“宏替换”,把使用常量的地方全部替换成真实的字面量
- 这样做的好处是让使用常量的程序的执行性能与直接使用字面量是一样的
枚举
为了做信息的标志和信息的分类,就像js中的类一样
相较于常量定义,枚举更加优雅
修饰符 enum 枚举名称{
第一行都是罗列枚举实例的名称
}
// 例
enum Season{
SPRING,SUMMER,AUTUMN,WINTER;
}
枚举特征
- 枚举类都是继承了枚举类型:
java.lang.Enum
- 枚举都是最终类,不可以被继承
- 构造器的构造器都是私有的,枚举对外不能创建对象
- 枚举类的第一行默认都是罗列枚举对象的名称的
抽象类
使用abstract修饰类,就是抽象类;修饰方法,就是抽象方法
修饰符 abstract class 类名{
修饰符 abstract 返回值类型 方法名称(形参列表);
}
// 例
public abstract class Animal{
public abstract void run();
}
- 抽象方法只有方法签名,不能声明方法体
- 一个类中如果定义了抽象方法,这个类必须声明成抽象类
抽象的使用场景
- 抽象类可以理解成不完整的设计图,一般作为父类,让子类来继承
- 当父类知道子类一定要完成某些行为,但是每个子类该行为的实现又不同,于是父类就把该行为定义成抽象方法的形式,具体实现交给子类去完成,此时这个类就可以声明成抽象类
// 例如动物都会跑,但是具体每个动物跑的方式不同
// 此时子类的写法
public class Dog extends Animal{
@Override
public void run() {
out.println("修狗快跑");
}
}
抽象类得到了抽象方法,失去了创建对象的能力
模板方法模式
当系统中出现同一个功能在多处进行开发,而功能中大部分代码是一样的,只有其中部分可能出现不用
- 把功能定义成为一个模板方法,放在抽象类中,模板方法中只定义通用且能确定的代码
- 模板方法中不能决定的功能定义成抽象方法让具体子类去实现
接口
// JDK 8之前只支持常量和抽象方法
public interface 接口名{
// 常量
public static final String A = "aaa";
// 抽象方法
public abstract void run();
}
- 接口是一种规范(接口默认公开,因此
public abstract
,public static final
都可以省略)
接口的基本使用
- 接口是用来被类实现(implements)的,实现接口的类成为实现类(可以理解为子类)
// 实现类
修饰符 class 实现类 implements 接口1,接口2,...{
}
- 一个类实现接口,必须重写完全部接口的全部抽象方法,否则这个类需要定义为抽象类
接口与接口
- 类和类的关系:单继承
- 类和接口的关系:多继承
- 接口和接口的关系:多继承,一个接口可以同时继承多个接口
接口多继承的作用:规范合并,整合多个接口为同一个接口,便于子类实现
JDK 8之后接口的使用方式又有所增加,便于项目的维护
接口的注意事项
- 接口不能创建对象
- 一个类实现多个接口,多个接口中有同样的静态方法不冲突
- 一个类继承了父类,同时又实现了接口,父类中和接口有同名方法,默认用父类的
- 一个类实现了多个接口,多个接口中存在同名的默认方法,不冲突,这个类重写该方法即可
- 一个接口继承多个接口,如果多个接口中存在规范冲突则不能继承
多态
同类型的对象,执行同一个行为,会表现出不同的特征
父类类型 对象名称 = new 子类构造器;
接口 对象名称 = new 实现类构造器;
在多态中,方法调用对象中的(右边),变量调用父类中的(左边)
- 在多态形式下,右边对象可以实现解耦,便于拓展维护
所以多态下无法调用子类独有功能
多态的类型转换(调用子类独有功能)
- 自动类型转换(子->父)
- 强制类型转换(父->子)
Animal a = new Dog();
a.run();
// 强转调用子类独有方法
Dog a1 = (Dog) a;
a1.wangwang();
内部类
定义在一个类里面的类
使用场景
- 当一个事物内部还有一个部分需要一个完整的结构进行描述,而这个完整的结构又只为外部事物提供服务
- 内部类通常可以方便访问外部类的成员,包括私有的成员
- 内部类本身就可以使用private protected等修饰,封装性可以做更多控制
匿名内部类
方便创建子类对象,简化代码编写
new 类|抽象类名|接口名(){
重写方法;
}
Animal a = new Animal(){
@Override
public void run(){
//...
}
}
abstract class Animal{
public abstract void run();
}