当前位置: 首页 > article >正文

[Java基础]面向对象

快速入门

计算机的核心作用就是处理数据, 变量用来存储单个数据, 数组用来储存一批数据, 对象用来存储一类数据

什么是对象?

对象就是一种特殊的数据结构, 用户储存一类数据, 在java中万物皆对象

面相对象编程的好处?

更加符合人类思维习惯

类和实例对象

在java中必须先设计类, 才能根据类创建对象

  1. 类是创建对象的模板, 对象是类的具体实例
  2. java的核心是面向对象, 面向对象的核心是设计类
  3. 只要把类设计好, 就可以创建对象, 使用对象的数据和方法, 就可以完成任何需求

基础语法

  1. 成员变量
  • 类中的变量称为成员变量, 类中的方法称为成员方法
  • 完整定义格式:

  • 一般无需指定初始值, 存在默认值

  1. 注意事项
  • 命名规范: 类名称首字母大写, 满足驼峰模式
  • 一个代码文件中, 可以写多个class类
  • 但只能有一个用public修饰, public修饰的类名必须是java代码的文件名
  • 实际开发中, 建议一个文件只定义一个class类
// 一个代码文件中, 可以写多个class类
class Teacher {
}

// 建议一个文件只定义一个class类
class Student {
}

// 一个代码文件中, 只能有一个类被public修饰
// public修饰的类必须与文件名一致
public class People {
}

执行原理

执行流程

  1. java程序运行在jvm虚拟机中
  2. jvm虚拟机把内存划分为3块, 方法区, 栈内存, 堆内存
  3. jvm虚拟机执行代码时, 首先把Test启动类提取到方法区中执行
  4. 然后把Test启动类的main方法放到栈内存中运行
  5. main方法中使用了Cart类, 虚拟机把Cart类提取到方法区
  6. new Cart时,会在堆内存中开辟空间, 存放汽车对象, 汽车象会有一个类的地址. 指向方法区的汽车类
  7. 存放汽车对象的变量地址会被赋值给左侧的c1变量, 对象创建完成
  8. 对象变量c1通过变量地址, 找到堆内存中的汽车对象, 再找到对象的name属性, 完成属性赋值

注意事项:

  1. 对象与对象之间的数据不会相互影响, 除非多个变量指向同一个对象
  2. 如果某个对象没有变量引用它, 该对象无法被操作,会被视为垃圾对象, 垃圾对象会被垃圾回收机制清除

类和对象是什么

  1. 在内存的角度, 类就是一个文件, 对象是一种数据结构, 类的作用是约束对象, 对象的作用是存储数据
  2. 从设计的角度, 类是一张设计图, 对象是具体的产品
  3. 在运行的角度, 类会被虚拟机加载到方法区, 对象是堆内存中的一块空间

构造器

什么是构造器?

构造器是类的五大成分之一, 构造器是一个特殊的方法, 构造器的名字必须与类名一致, 且没有返回值

public class Car {
    //无参构造器 
    public Car() {
    }
    //有参构造器
    public Car(String n) {
    }
}

构造器的作用?

构造器的作用就是创建对象并返回对象的地址

public class Car {
    //无参构造器 
    public Car() {
        System.out.println("无参构造器执行了");
    }
}

// 每次创建对象都会调用构造器
Car c1 = new Car()  // 参构造器执行了
  1. 使用new关键字创建对象时, 对象就会调用构造器, 完成对象的创建, 并返回该对象的地址

构造器的分类

构造器可以分为 有参构造器 和 无参构造器

public class Car {
    //成员变量
    private String  name;
    //无参构造器 
    public Car() {
    }
    //有参构造器
    public Car(String n) {
         this.name = n;
    }
}

// 调用无参构造器创建对象
Car c1 = new Car()
c2.name;  // null

// 调用有参构造器创建对象并赋值
Car c2 = new Car("奔驰")
c2.name;  // "奔驰"
  1. 无参构造器: 创建对象, 里面的数据都是默认值
  2. 有参构造器: 创建对象, 同时为对象的属性赋值

构造器的特点

类在定义时, java会为类自动生成无参构造器

// 类默认就自带无参构造器
public class Car {

}

Car c1 = new Car()
// 一旦声明了有参构造器, 就要主动声明无参构造器
public class Car {
    //主动声明无参构造器 
    public Car() {
    }
    
    //一旦声明了有参构造器
    public Car(String n) {
    }
}

Car c1 = new Car()
Car c1 = new Car(100)
  1. 如果类中定义了有参构造器, java就不会自动生成无参构造器了
  2. 技巧: 如果手动声明了有参构造器, 一定要主动声明无参构造器

this关键字

this就是一个变量, 指向当前对象的地址, 可以用在构造器和成员方法中

public class Car {
    // this在构造器中使用
    public Car() {
        // c1调用构造器, 所以this指向c1
        sout("this1:" + this) // 打印: com.itheima.thisdemo.Studentb4c966a
    }
    // this在成员方法中使用
    public void run() {
        // c1调用成员方法, 所以this指向c1
        sout("this2:" + this) // 打印: com.itheima.thisdemo.Studentb4c966a
    }
}

// 调用无参构造器
Car c1 = new Car()
// 调用成员方法
c1.run()
// 打印c1的地址
sout("c1:" + c1) // 打印: com.itheima.thisdemo.Studentb4c966a
  1. this关键字指向当前类的调用者, 用来访问该对象的成员变量和成员方法
  2. 技巧: 哪个对象调用方法, this就指向哪个对象

this的作用

可以避免对象的成员变量与方法内部变量名的冲突

// 说明: 功能可以实现, 但是 n 并不是合适的变量名

public class Car {
    // 成员变量
    String name;
    // 成员方法
    public void goWith(String n) {
         sout(name + "正在和" n + "比赛加速!")
    }
}

// 调用无参构造器
Car c1 = new Car()
c1.name = "宝马"
// 调用成员方法
c1.rungoWith("奔驰"); // 宝马正在和奔驰比赛加速!
// 说明: 使用name变量接收参数, 使用this.name访问对象属性, 代码很优雅

public class Car {
    // 成员变量
    String name;
    // 成员方法
    public void goWith(String name) {
         sout(this.name + "正在和" name + "比赛加速!")
    }
}

// 调用无参构造器
Car c1 = new Car()
c1.name = "宝马"
// 调用成员方法
c1.rungoWith("奔驰"); // 宝马正在和奔驰比赛加速!

封装性

  1. 三大特性

面向对象的三大特性: 封装, 继承, 多态

  1. 封装的概念

封装是设计对象的原则, 设计对象时, 要把数据以及数据的相关方法设计在一起

  1. 封装的好处

让编程变得简单, 有什么事情, 找对象调方法就行了

  1. 封装的原则

合理隐藏: 成员变量全部隐藏, 提供get/set方法访问数据

合理暴露: 成员方法根据是否有用对外暴露

  1. 修饰符

公开成员: public修饰符, 公开成员可以通过对象直接访问

私有成员: private修饰符, 私有成员无法通过对象访问, 只能在类的内部访问

public class Student {
    // 成员变量私有
    private double score;
    // 成员方法按需暴漏
    public void setScore(double score) {
        this.score = score;
    }
    // 通过方法操作数据
    public double getScore(double score) {
        return score;
    }
}

实体类

javaBean也称为实体类, 是一种专门保存数据的java类

要求

  1. 成员变量必须私有(private), 提供getXX/setXX方法
  2. 必须提供无参构造器, 有参构造器可选

好处

  1. 实际开发中数据和数据的处理都会很多, 如果都混在一起, 代码的可读性和可维护性都很差
  2. 所以流行的开发方式就是数据和数据处理相分离, 所以要用实体类保存数据

补充

  1. 选中代码后, 右键快捷生成gte/set方法

  1. 快速生成有参构造器

  1. 快速生成无参构造器

static关键字

static是静态的意思, 也就是静态修饰符, 可以用来修饰成员变量, 成员方法

静态变量

使用static修饰的成员变量就是静态变量

// 定义
public class User {
    // 静态变量
    static String name;
    // 实例变量
    int age;
    
}
// 访问方式1(推荐)
// 通过类名访问: 类名.静态成员变量
User.name;

// 访问方式2(不推荐)
// 通过对象访问: 对象.静态成员变量
User u = new User();
// 访问静态变量
u.name;
// 访问实例变量
u.age;
  1. static修饰的成员变量就是静态变量(类变量)
  2. 静态变量属于类, 可以被类和类的所有对象访问
  3. 推荐通过类名访问静态变量, 因为用过对象访问实例变量, 并不直观
  4. 无static修饰的成员变量就是实例变量
  5. 实例变量是属于每个对象的
  6. 只能通过实例对象访问, 且每个对象的信息不同

执行原理

  1. 静态变量只会跟随类加载一次, 内存中只有一份, 可以被类和类的所有对象共享
  2. 实例变量属于对象, 每个对象中都有一份, 只能用对象访问

应用场景

如果某个数据只需要一份, 且希望能够被共享, 该数据就应该定义为静态变量

// 系统启动后, 需要用户类记住自己创建了多少个对象
public class User {
    // 类变量
    public static int number;
    // 构造器
    public User() {
        // 访问自己类中的静态变量, 可以省略类名
        // User.number++;
        number++;
    }
}

// 创建对象
new User(); // 每次创建对象, 都会调用构造器
new User();
sout(User.number); // 2
  1. 访问自己类中的静态变量, 类名可以省略不写
  2. 在类中访问其他类的静态变量, 必须带类名访问

静态方法

使用static修饰的成员方法就是静态方法, 静态方法归属于类

// 定义
public class User {
    // 实例方法
    public void run() {
        sout("好好学习.天天向上")
    }
    // 静态方法
    public static int getMax(int a, int b) {
        return a > b ? a : b
    }
    
}
// 访问方式1(推荐)
// 通过类名访问: 类名.静态成员方法
User.getMax();

// 访问方式2(不推荐)
// 通过对象访问: 对象.静态成员方法
User u = new User();
// 访问静态方法
u.getMax();
// 访问实例方法
u.run();
  1. 静态成员方法(static修饰, 属于类), 建议使用类名触发, 也可以用对象触发(不直观)
  2. 实例成员方法(无static修饰, 属于对象),只能用对象触发

最佳实践

  1. 如果该方法是对象的行为, 且需要访问对象的数据, 则该方法必须声明成实例方法
  2. 如果该方法只是执行一个功能, 且不需要访问对象的数据, 则可以声明成静态方法

注意事项

public class Test {
    public static String name;
    public int age;
    
    public static void main(String[] args) {
  
    }

    // 1,静态方法中可以访问静态成员, 不可以直接访问实例成员
    public static void printHello() {
        sout(name); // 本质是Test.name
        sout(age); // 本质是Test.age, 所以报错, 实例成员必须通过对象访问
        sout(this); // 报错, this只能代表对象, 静态方法通过类名掉用,找不到对象
    }

    // 2,实例方法中可以访问静态成员, 也可以访问实例成员
    public static void printHello() {
        sout(name); // 本质是Test.name
        sout(age); // 本质是this.age, 通过对象访问实例成员没毛病
        sout(this); // 哪个对象调用方法, this就指向哪个对象
    }
}
  1. 静态方法中可以访问静态成员, 不可以直接访问实例成员
  2. 实例方法中可以访问静态成员, 也可以访问实例成员
  3. 实例方法中可以使用this关键字, 静态方法中不可以使用this关键字
  4. 上面的语法能看懂就可以了, 不要思考有什么作用, 想多了会乱, 认识语法就行

搞懂main方法

main方法就是一个静态方法

public class Test2 {
    public static void main(String[] args) {
        Student s1 = new Student();
        s1.name = "林青霞";
        s1.math = 89;
        s1.chinese = 76;
        s1.printAllScore();
        s1.printAllScore();
    }
}
  1. 执行原理: 使用java命令运行程序时, 虚拟机是通过Test(类名)访问main方法, 最终启动程序

工具类

工具类是静态方法最常见的应用

public class XxxUtil {
    // 方法1
    public static void xxx() {
        ...
    }
    // 方法2
    public static boolean xxx(String email) {
        ...
    }
    // 方法3
    public static String xxx(int n) {
        ...
    }
    // 构造器私有
    private XxxUtil() { }
}

// 通过类名调用静态方法
XxxUtil.xxx()
  1. 工具类就是一个类中只定义静态方法, 每个方法完成一个功能, 这个类就是工具类
  2. 调用方便: 如果使用实例方法, 还要new出实例, 麻烦而且浪费内存
  3. 代码复用: 一次编写, 处处可用
  4. 建议: 工具类不需要创建对象, 建议把工具类的构造器进行私有

继承性

继承就是用extends关键字, 让一个类和另一个建立父子关系

// Student称为子类
// People称为父类
public class Student extends People {
}

示例

子类继承父类后, 就得到了父类的非私有成员(成员变量/成员方法)

public class People {
    private String name;

    public String getName() {
        retuen name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public class Teacher extends People {
    private String skill; //技能

    public String getSkill() {
        retuen skill;
    }

    public void setSkill(String skill) {
        this.skill = skill;
    }
}
public class Stuted extends People {
    private String exam; // 作业

    public String getExam() {
        retuen exam;
    }

    public void setName(String exam) {
        this.exam = exam;
    }
}
public class Test {
   public static void main(String[] age) {
       // 子类可以继承父类的非私有方法和属性
       // 说明: People类中的name属性是私有的, 所以子类并不能继承这个属性
       // 但是getName()和setName()方法是公开的, 所以就可以读取和设置该属性
       Teacher t = new Teacher();
       t.setName("王六");
       t.setSkill("java, 大数据");
       t.getName(); // 王六
       t.getSkill(); // java, 大数据
   }
}
  1. 好处: 继承的好处就是提高代码的复用性

  1. 规范
  • 子类相同的特性(公共属性/公共方法)放在父类中定义
  • 子类独有的属性和方法放在子类中定义

继承的原理

子类的对象是由子类和父类共同决定的

// 父类
public class Student { 
    String name;
}

// 子类
class Student  { 
    String age;
}

// 子类继承父类后
class Student extends People { 
    String name;
    String age;
}

权限修饰符

权限修饰符的作用就是 限制类中的成员 (成员方法/成员变量/构造器), 能够被访问的范围

  1. 访问权限从小到大是: private(私有)< 缺省(不写) < protected(受限制) < public(公开)
  2. 一般建议:
  • 需要私有的成员使用private修饰
  • 需要公开的成员使用public修饰
  • 需要在子孙类中访问使用protected修饰符, 较少使用

继承的特点

  1. java是单继承模式: 一个类只能继承一个直接父类

class A { 
    public void method() {
        sout("复习数学!")
    }
}

class B { 
    public void method() {
        sout("复习语文!")
    }
}

// 语法错误, 不支持多继承
public class c extends A,B { 
}

// 原因: 是复习数学还是复习语文?
// 如果支持多继承, 那么一旦出现冲突, 以谁为准是个很大的问题
C c = new C();
c.method();

  1. java不支持多继承, 但是支持多层继承

public class People extends Zoon  {
}

class Student extends People { 
}

  1. java中所有的类都是Object类的子类, 要么直接继承, 要么间接继承
// 默认继承Object
public class Movie extends Object {
};

Moive m = new Moive();
m.hashCode(); // 所有类都能使用Object类中的方法
  • 原因1: Java的继承体系模仿现实世界, 人类共同的祖先是非洲直立猿, java中所有的类祖先就是Object
  • 原因2: 所有对象的通用方法可以定义在Object中, 子类可以轻松的使用

  1. 子类不能继承父类的构造器, 因为子类有自己的构造器
  1. 子类可以直接使用父类的静态成员(共享), 但是共享不等于继承

继承后的变化

1.子类访问成员的原则

在子类方法中访问成员(成员变量/成员方法)遵循就进原则

class A {
 String name = "国强";
}

class B extends A {
    String name = "军军";
    
    public void run() {
        String name = "小红";
        sout(name); // 小红, 就近原则
        sout(this.name); // 军军, this指向实例对象
        sout(super.name); // 国强, super指向父类的成员
    }
}

B b = new B();
b.run();
  • 先在子类局部范围找, 然后在子类成员范围找, 然后在父类范围找, 找不到就报错
  • 如果子父类中出现了重名的成员, 会优先使用子类的(更近)
  • 如果一定要在子类中使用父类的成员, 可以通过super关键字,指定访问父类的成员
  • 格式: super.父类成员变量/父类成员方法
  • 也可以通过this关键字, 指定访问子类的成员 (没用, 因为默认就访问子类的成员)
  • 格式: this.成员变量/成员方法

2.子类的方法重写

当子类觉得父类中的某个方法不好用, 子类可以重写一个名称和参数列表相同的方法, 去覆盖父类的方法

class Animal  {
    public void cry() {
       sout("动物都会叫");
    }
}

class Cat extends Animal  {
    //方法重写: 方法的名称和形参列表必须与被重写方法保持一致
    @Override // 方法重写的标志, 可以检查重写的格式, 不符合重写要求会报错
    public void cry() {
       sout("喵喵喵");
    }
}

public class Test {
    public static void main(String[] ages) {
        Cat cat = new Cat();
        // 重写后, 方法的访问就会遵循就近原则
        cat.crt(); // 喵喵喵
    }
}

方法重写的注意事项

  1. 私有方法不能被继承, 所以不能被重写
  2. 静态方法归父类持有, 所以不能被重写
  3. 子类重写父类方法, 访问权限必须等于或大于父类该方法的权限

  1. 重写的方法的返回值, 必须与被重写方法的返回值一致 或者 范围更小
  2. 以上了解即可, 实际开中遵循原则: 声明不变, 重新实现

方法重写的应用

子类重写Object类的toString方法, 使其返回对象内容, 而不是地址

public class Movie {
    private int id;
    private String name;

     @Override
    public String toString() {
        return "Movie{" +
                "id=" + id +
                ", name='" + name + '\'' +
            '}';
    }

    public Movie(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

Movie m = new Movie(4,"西红柿首富");
// 直接输出对象, 默认会调用Object的toString()方法, 返回对象的地址信息
// 完整写法: sout(m.toString());
sout(m); // com.itheima.dome@b4c77a
// 方法重写后返回对象内容, 便于我们观察对象
sout(m); // { id=4, name="西红柿首富" }
  1. 重写toString方法经常使用, 可以快捷生成
  2. 快捷生成: 右键->Generate->toString 快速生成
  3. 快速生成: toString + 回车 + 回车
3.子类构造器的特点

子类中所有的构造器, 默认都会先调用父类的无参构造器, 再执行自己

class Fu {
    private String name;
    
    public Fu() {
        sout("父类无参构造器执行了")
    }

    public Fu(String name) {
        sout("父类无参构造器执行了")
        this.name = name
    }
}

class Zi extends Fu {
    public Zi() {
        // 3. 原因: 子类默认会调用父类的无参构造器, 写不写都存在
        // super()
        
        // 4. 如果父类没有无参构造器, 必须手动调用父类的有参构造器, 否则代码报错
        // super("张三")
        
        sout("子类无参构造器执行了")
    }
}

public class Test {
    public static void main(String[] aegs) {
        Zi z = new Zi();

        // 1. 控制台: 
        // 先打印 -> 父类无参构造器执行了
        // 再打印 -> 子类无参构造器执行了
        // 2. 现象: 子类的构造器一定先调用父类的构造器, 再执行自己的构造器
    }
}
  1. 子类构造器必须先调用父类构造器的原因:
  • 在继承模式下, 子类对象是由子类和父类共同决定的
  • 子类对象在初始化的时候, 会用到父类中的数据, 如果父类没有初始化, 子类就无法使用父类的数据
  • 所以, 子类初始化之前, 一定会调用父类的构造器, 完成父类的初始化
  • 然后再调用自己的构造器, 最终完成子类对象的初始化
  1. 子类如何调用父类的构造器
  • 默认情况下, 子类构造器的第一行代码都是super() , 写不写都有, 他会调用父类的无参数构造器
  • 如果父类没有无参构造器, 则会报错, 我们需要在子类调用父类的有参构造器, 语法 super(参数)
  1. 子类构造器和父类构造器的作用
  • 在继承模式下, 子类对象是由子类数据和父类数据共同组成的,
  • 父类的构造器为父类这部分的数据赋值
  • 子类的构造器为子类这部分的数据赋值
  • 最终由子类构造器, 把完整的子类对象构建出来
  1. 这样设计的目的:
  • 先运行父类构造器, 再运行子类构造器, 从代码逻辑上确保子类对象的初始化过程就绝对可靠,
  • 就能保证子类对象数据的完整性

运行原理

  1. 子类构造器可以通过调用父类构造器,把对象中包含父类这部分的数据先初始化赋值
  2. 再回来把对象里包含子类这部分的数据也进行初始化赋值。
4.调用兄弟构造器

任意构造器中, 都可以通过 this(...) 调用该类的其他构造器

public class Student {
    private String name;
    private char sex;
    private int age;
    private String schoolName;
    
    // 有参构造器1
   public Student(String name, char sex, int age) {
     // 调用兄弟构造器 
     this(name, sex, 22, '清华大学');
   }
    
   // 有参构造器2
   public Student(String name, char sex, int age, String schoolName) {
      this.name = name;
      this.sex = sex;
      this.age = age;
      this.schoolName = schoolName;
   }
}

// 初始化学生对象
Student s = new Student("张三",'男')
  • 执行顺序: 子类通过 this(...) 调用本类的其他构造器, 其他构造器通过 super(...) 去调用父类的构造器
  • 注意事项: super(...) 和 this(...) 都只能放在构造器的第一行, 所以不能同时使用

this和super

this代表本类对象的引用, super代表父类存储空间的标识

多态性

认识多态

多态就是对象可以有多种形态, 是继承/实现情况下的一种现象, 表现为: 对象多态和行为多态

// 人类
public calss People {
    public void run() {
        sout("人会跑~")
    }
}

// 老师类
calss Teacher extedns People {
    public void run() {
        sout("老师跑的慢~")
    }
}

// 学生类
calss Student extedns People  {
    public void run() {
        sout("学生跑的快~")
    }
}

// 认识多态
People p2 = new Teacher();
p2.run(); // 老师跑的慢~

People p1 = new Student();
p1.run(); // 学生跑的快~
  1. 多态是面向对象的一种设计思想, 源于对现实世界的模仿:
  • 老师类和学生类都继承人类,
  • 人类会跑, 老师和学生就会跑(因为继承),
  • 但是老师和学生跑起来是有区别的(方法重写)
  1. 对象多态: 就是同一个对象可以有不同的形态
  • 学生和老师都继承人类这个对象
  • 人类就包含了学生和老师这两种不同的形态
  1. 行为多态: 一个对象的行为可以有不同的表现
  • 学生和老师都继承人类这个对象
  • 老师跑起来和学生跑起来有不同的表现
  1. 多态的前提
  • 有继承/实现关系;
  • 存在父类引用子类对象;
  • 存在方法重写 (多态侧重行为多态)

常见形式

多态的常见形式就是 父类类型接收子类对象, 语法: 父类类型 对象名称 = new 子类构造器

public class Prople {
    public String name = "父类的名称"
    public void run() {
        sout("人可以跑")
    }
}
public class Teacher extends Prople {
    public String name = "老师的名称"
    public void run() {
        sout("老师跑的气喘吁吁")
    }
}
public class Student extends Prople {
    public String name = "学生的名称"
    public void run() {
        sout("学生跑的飞快")
    }
}
public class Test {
    public static void main(String[] ages) {
        // 对象多态: 人对象可以指向学生对象,也可以指向老师对象
        People p1 = new Student();
        People p2 = new Teacher();
        
        // 行为多态: 同样调用run方法,执行结果不同
        p1.run(); // 学生跑的飞快
        p2.run(); // 老师跑的气喘吁吁
        
        // 成员变量不存在多态
        p1.name;  // 父类的名称
        p1.name;  // 父类的名称

    }
}

运行特点

  1. 方法调用: 编译看左边(People), 运行看右边(Student();)

  1. 变量调用: 编译看左边(People), 运行也看左边(People)

  1. 重要结论: 多态是指对象或行为的多态, java中的属性(成员变量)不谈多态

多态的好处

1:在多态形势下, 右边对象是解耦合的, 更便于扩展和维护

// 可以方便的切换对象
// prople p1 = new Teacher(); 
prople p1 = new Student();
p1.run()

2:定义方法时, 使用父类类型的形参, 可以接受一切子类对象, 扩展性更强

Student s = new Student();
go(s);

Teacher t = new Teacher();
go(t);

publci static void go(People p) {
    
}

多态的问题

多态下不能使用子类的独有功能

public class Prople {
    public void run() {
        sout("人可以跑")
    }
}
public class Teacher extends Prople {
    public void run() {
        sout("老师跑的气喘吁吁")
    }
    public void sayHi() {
        sout("老师需要讲课")
    }
}
public class Test {
    public static void main(String[] ages) {
        People p1 = new Teacher();
        p1.run();   // 老师跑的气喘吁吁
        p1.sayHi(); // 报错, 因为编译看左边, 左边的父类中没有sayHi这个方法
    }
}

解决方法

把父类类型强转成子类类型, 解决多态下无法使用子类独有功能的问题

public class Test {
    public static void main(String[] ages) {
        People p1 = new Teacher();
        // 多态下,无法调用子类的独有功能
        p1.sayHi(); // 报错
        
        // 强制类型转换
        Teacher s1 = (Teacher) p1;
        // 类型强转后, 就可以调用子类的独有方法了
         p1.sayHi();  //"老师需要讲课"

        // 隐患: 只要由继承关系就可以强制转换, 编译阶段不会报错
        // 把老师s1 强转为 学生类型, 明显是不对的, 但是编译通过
        Student sd1 = (Student) s1;

        // 类型判断, 防止类型转换错误
        if(p1 instanceof Teacher) {
             Teacher s1 = (Teacher) p1;
             p1.sayHi();
        }
        
    }
}
  1. 只要存在继承/实现关系, 就可以在编译阶段进行强制类型转换, 编译阶段不会报错
  2. 运行时, 如果发现强转的类型和对象的真实类型不符, 就会报类型转换异常(ClassCastException)的错误
  3. 官方建议, 强制类型转换前先 使用 instanceof 关键字, 判断当前对象的真实类型

final关键字

final 关键字是最终的意思, 可以修饰 类/方法/变量

  1. 修饰类: 该类就是最终类, 特点是不能被继承(类似绝育)
// 工具类建议使用final关键字修饰, 
// 因为工具类不需要创建对象, 更不需要被继承
public final class CodeUtil {
    // 工具类不需要创建对象, 建议私有化构造器
    private CodeUtil() {}

    // 工具类中全是静态方法
    public static String getCode(int n) {
        ... ...
    }
}
  1. 修饰方法: 该方法就是最终方法, 特点是不能被重写
public class Dome {
    // 工具类中全是静态方法
    public final void show() {
        sout("show方法执行了")
    }
}

class Dome2 extends Dome {
    // 报错, 最终方法不能被重写
    @Override
    public void show() {
        sout("最终方法不能重写吗?")
    }
}
  1. 修饰变量: 该变量必须赋值且不能二次赋值
/**
   java中的变量有几类?
       a. 成员变量
          ->静态成员变量
          ->实例成员变量
       b. 局部变量
*/
public class Dome {
    // 2.final修饰静态成员变量, 就被称为常量
    public static final String SCHOOL_NAME = "清华大学";
    
    // 3.final修饰实例成员变量, 没有意义,
    //   所有的对象年龄都是18, 且不能改
    private final int age = 18;

    public static void main(String[] args) {
        // 1.final修饰局部变量, 可以防止数据被意外修改
        final double rate = 3.1415926;
        rate = 3.14; // 报错,不能被二次赋值     
    }

}
  • final修饰基本类型的变量: 该变量存储的数据不能被改变
  • final修饰引用类型的变量: 该变量存储的地址不能被改变, 但是对象里面的值可以改变
常量

使用 public static final 修饰的成员变量就是常量, 必须要有初始值, 常用于记录系统的配置信息

public class Constant {
    public static final String SCHOOL_NAME = "清华大学";
    public static final String SYSTEM_VERSION = "v1.0"
}
  1. 命名规范: 全大写英文, 多个单词使用下划线连接
  2. 优势:
  • 实现软编码, 代码可读性更好,可维护性更好
  • 程序编译后,常量会被"宏替换", 出现常量的地方全部会被替换为字面量,所以性能不受影响

枚举类

枚举是java中一种特殊的类, 专门用来做信息分类, 可读性好, 入参约束谨慎, 代码优雅

语法

public enum Season {
    SPRING, SUMMER, AUTUMN, WINTER;
}
  1. 枚举类中的第一行,只能写枚举类的对象名称,且要用逗号隔开。
  2. 这些名称,本质是常量,每个常量都记住了枚举类的一个对象。

反编译后

  1. 手动反编译: javap .\A.class

  1. 枚举类都是继承了枚举类型: java.lang.Enum
  2. 枚举都是最终类, 不可以被继承
  3. 枚举类的构造器都是私有的, 所以对外不能创建对象
  4. 枚举类的第一行只能罗列一些名称, 这些名称都是常量, 并且每个常量记住的都是枚举类的一个对象
  5. 枚举类中, 从第二行开始, 可以定义类的其他各种成员
  6. 枚举类相当于是多例模式
  7. 枚举会从 java.lang.Enum 类中继承一些方法
public enum A {
    X, Y, Z;
}

A a1 = A.X;
sout(a1); // X, 默认返回枚举的名字, 而不是枚举对象的地址, 因为枚举类默认重写了toString()方法
sout(a1.name); // X,拿到枚举对象的名字
sout(a1.ordinal()); // 0,拿到枚举对象的索引

// 可能会用到的方法
A[] aa1 = A.values(); // 拿到枚举类中的全部枚举对象
A aa2 = A.valueOf("X");  // 拿到指定名称的枚举对象

应用场景

可以使用枚举实现单例模式

public enum C {
     x;  //单例
}

信息分类: 枚举和常量都是用来表示一组信息, 然后作为参数进行传输

class Constan {
    public static final int UP = 0; // 上
    public static final int DOWN = 1; // 下
    public static final int LEFT = 2; // 左
    public static final int RIGHT = 3; // 右
}

public class Test {
    public static void main(String[] args) {
        // 使用常量做信息标识和分类挺好, 只是入参不受约束
        move(Constan.UP);
        move(10086);
    }

    public static void move(int direction) {
        switch(direction) {
            case Constan.UP: 
                sout("向上移动");
                break;
            case Constan.DOWN: 
                sout("向下移动");
                break;
            case Constan.LEFT: 
                sout("向左移动");
                break;
            case Constan.RIGHT: 
                sout("向右移动");
                break;
            default: 
                sout("参数有误");
        }
    }
}
enum Direction {
    UP, DOWN, LEFT, RIGHT;
}

public class Test {
    public static void main(String[] args) {
        // 使用常量做信息标识和分类, 代码提示和约束更好
        move(Direction.UP);
    }

    public static void move(Direction direction) {
        switch(direction) {
            case Direction.UP: 
                sout("向上移动");
                break;
            case Direction.DOWN: 
                sout("向下移动");
                break;
            case Direction.LEFT: 
                sout("向左移动");
                break;
            case Direction.RIGHT: 
                sout("向右移动");
                break;
        }
    }
}
  1. 枚举相对于常量, 限制性更好,
  2. 常量相对于枚举, 灵活性更好,
  3. 两者的使用场景相似

抽象类

abstract关键字是抽象的意思, 可以修饰类, 成员方法

// 1.抽象类
// abstract修饰的类称为抽象类
public abstract class A {  
    // 抽象方法
    // 1.1.abstract修饰的方法称为抽象方法
    // 1.2.抽象方法只有方法签名, 不能声明方法体
    public abstract void show();
}

// 2.抽象类不能创建对象
// A a = new A; // 报错

// 3.一个类继承抽象类, 必须重写完抽象类的全部抽象方法, 否则这个类也要定义成抽象类
public class B extends A {  
    @Override
    public void show() {
        sout("子类重写父类方法")
    }
}

B b = new B();
b.show(); // "子类重写父类方法"
  1. 一个类中如果定义了抽象方法,这个类必须声明为抽象类, 否则报错
  2. 类该有的成员(成员变量/方法/构造器), 抽象类都可以有
  3. 特点: 抽象类可以定义抽象方法, 但是不能创建对象
  4. 作用: 抽象类的使命就是被子类继承并实现抽象方法
  5. abstract不能修饰变量, 代码块, 构造器

应用场景

父类知道每个子类都要做某个行为, 但每个子类要做的情况不一样

  1. 父类就可以定义抽象方法, 交给子类去重写实现,
  2. 设计这样的抽象类, 就是为了更好的支持多态
// 动物类
public class abstract Animal {
    // 每个动物都会叫
    public abstract void cry();
}

// 猫类
// 猫类继承动物类
class Cat extends Animal {
    // 动物类是抽象类, 所以必须重写父类全部抽象方法
    @Override
    public void cry() {
        sout("猫喵喵的叫");
    }
}

// 狗类
// 狗类继承动物类
class Dog extends Animal {
    // 动物类是抽象类, 所以必须重写父类全部抽象方法
    @Override
    public void cry() {
        sout("狗汪汪的叫");
    }
}

// 不使用抽象类, 也可以完成需求
// 但是使用抽象类, 代码就会非常优雅, 符合最佳实践
Animal a1 = new Dog();
a.cry(); // 狗汪汪的叫
Animal a2 = new Cat();
a.cry(); // 猫喵喵的叫

扩展

  1. final和abstract是互斥关系
  2. 抽象类的目的就是让子类继承, final定义的类不能被继承
  3. 抽象方法的目的就是让子类重写, final定义的方法子类不能重写
  4. 使用抽象类实现 [模版方法] 设计模式是也能最佳实践
  5. 模板设计方法移步 [设计模式] 章节查看

接口类

传统接口

接口是一种规范, 在java中, 使用 interface 关键字定义接口, 可以理解为一种特殊的类

public interface A {
     // 成员变量(默认就是常量)
     String SCHOOL_NAME = "程序员";
     // 完整形式
     //public static final String SCHOOL_NAME = "程序员";
    
     // 成员方法(默认就是抽象方法)
     void test();
     // 完整形式
     // public abstract void test();
}
  1. JDK8之前的接口中只能定义成员变量(常量) 和 成员方法(抽象方法)
  2. 接口中的成员都是public修饰的, 写不写都是
  3. 接口方法默认被public修饰的原因: 接口是需要被类实现的, 所以接口方法都是要对外暴漏的

接口不能创建对象, 接口是用来被 类 实现的, 实现接口的类称为实现类

// 实现接口的关键字: implements
public class B implements A  {
    @Override
    void test() {
        sout("test...")
    }
}

B b = new B;
b.test(); // test...
sout(A.SCHOOL_NAME); // 程序员
  1. 一个类可以实现多个接口(干爹), 称为接口的多实现
  2. 一个类实现接口, 必须重写完 全部接口的 全部抽象方法, 否则实现类需要定义为抽象类
接口的好处

弥补了类单继承的不足,一个类可以同时实现多个接口

// 司机接口
interface Driver {}
// 男朋友接口
interface BoyFriend {}
// 人类
class People {}
// 学生类
class Student extends People implements Driver, BoyFriend {}

// 同一个类可以拥有更多角色, 类的功能更强大. 更符合现实
People p = new Student();
Driver d = new Student();
BoyFriend bf = new Student();

面向接口编程, 可以灵活方便的切换各种业务实现

// 司机接口
interface Driver {}
// 男朋友接口
interface BoyFriend {}
// 人类
class People {}
// 学生类
class Student extends People implements Driver, BoyFriend {}
// 老师类
class Teacher extends People implements Driver, BoyFriend {}

// 面向接口编程, 灵活的切换业务实现对象
BoyFriend bf = new Student();
BoyFriend bf = new Teacher();
// 假设业务中已经使用对象, 完成很多功能
bf.go();
bf.say();
bf.look();
  • 更详细的案例可查看代码day05-oop/dome3
增强接口

jdk8开始, 接口新增了三种方法, 目的是增强接口的能力, 更便于项目的扩展和维护

1.默认方法

public interface A {
    // 默认方法必须使用default修饰
    default void run() {
        sout("默认方法执行了")
    }
}

// 默认方法, 使用实现类的对象调用
class AImpl implements A {}
AImpl a = new AImpl();
a.run();

2.静态方法

public interface A {
  // 静态方法必须使用static修饰
  static void run() {
     sout("静态方法执行了")
   }
}

// 静态方法, 只能使用当前接口名来调用
A.run();

3.私有方法

public interface A {
  // 私有方法必须使用private修饰
  static void run() {
     sout("私有方法执行了")
  }
    
  default void show() {
      // jdk9开始支持, 只能在本类中被其他的默认方法或私有方法调用
      run();
  }
}
  • 这些方法了解即可, java源码中会看到, 但是我们很少会写
  • 这写方法默认都会被public修饰
  • 使用场景: 实际工资中一个接口可能被几十个类实现, 如果给接口增加一个成员方法, 那么这几十个类都要重写这个新增的方法, 修改成本较高, 此时可以定义静态方法或者默认方法, 减少对其他实现类的影响

接口的多继承

一个接口可以同时继承多个接口, 便于实现类实现多个接口

interface A {
    void show1();
} 
interface B {
    void show2();
} 

interface C extends A, B{
    void show3();
} 

// 相当于D实现了A+B+C
class D implements C {
    @Override
    public void show1(){ }

    @Override
    public void show2(){ }

    @Override
    public void show3(){ }
} 
  1. 类与类: 单维承, 一个类只能继承一个直接父类
  2. 类与接口: 多实现,一个类可以同时实现多个接口
  3. 接口与接口: 多维承,一个接口可以同时维承多个接口
使用细节

一个接口继承多个接口, 如果多个接口中存在方法签名冲突, 则此时不支持多继承, 也不支持多实现

interface A {
    void show();
} 
interface B {
    String show();
} 

// 原因: 方法冲突时, 实现或继承时,就不知道听谁的了
// 方法冲突,不支持多继承
interface C extends A, B{
} 

// 方法冲突,不支持多实现
class D implements A,B {
} 

一个类继承了父类, 又同时实现了接口, 父类中和接口中存在同名的默认方法, 实现类会优先使用父类的

interface A {
    default void show() {
        sout("接口的show方法执行了")
    }
} 

class B {
   public void show() {
        sout("父类的show方法执行了")
   }
} 

class C extends B implements A {
    // 如果就要执行干爹的方法, 只能使用方法中转调用[了解]
    public void go() {
        A.super.show();
    }
}


// 就近原则: 亲爹和干爹打架,听亲爹的
C c = new C();
c.show(); // 父类的show方法执行了

// 就要听干爹的[了解]
c.go(); // 接口的show方法执行了

一个类实现了多个接口, 多个接口中存在同名的默认方法, 会冲突, 解决方法就是在实现类中重写该方法

interface A {
    default void show() {
        sout("A接口的show方法")
    }
} 
interface B {
    default void show() {
        sout("B接口的show方法")
    }
} 

class D implements A,B {
    // 方法冲突了, 只能重写[了解]
    @Override
    public void show() {
        // 1. 谁的我也不听了,我自己决定
        sout("略略略")
        // 2. 听干爹A的
        A.super.show()
        // 1. 听干爹B的
        B.super.show()
    }
} 
对比一下

抽象类和接口的相同点和不同点

相同点:

1、都是抽象形式,都可以有抽象方法,都不能创建对象。

2、都是派生子类形式: 抽象类是被子类继承使用,接口是被实现类实现。

3、一个类继承抽象类,或者实现接口,都必须重写完他们的抽象方法,否则自己要成为抽象类或者报错!

4、都能支持多态,都能够实现解耦合。

不同点:

1、抽象类中可以定义类的全部普通成员,接口只能定义常量,抽象方法(JDK8新增的三种方式)

2、抽象类只能被类单继承,接口可以被类多实现。

3、一个类继承抽象类就不能再继承其他类,一个类实现了接口(还可以继承其他类或者实现其他接口)。

4、抽象类体现模板思想: 更利于做父类,实现代码的复用性。[最佳实践]

5、接口更适合做功能的解耦合: 解合性更强更灵活。[最佳实践]

代码块

代码块是类的5大成分之一: 成员变量/构造器/方法/代码块/内部类

在java中, 使用 {} 括起来的代码被称为代码块

  1. 静态代码块
  • 格式: static { }
  • 特点: 类加载时自动执行, 由于类只会加载一次, 所以静态代码块也只会执行一次
  • 应用: 对类的静态资源进行初始化
public class CodeDemo {
    public static String schoolName;
    public static String[] cards = new String[3];
    
    // 静态代码块: 对类的静态变量初始化赋值
    // 优势: 静态代码块只会执行一次, 可以保证静态资源只初始化一次
    static {
        sout("静态代码块执行了")
        schoolName = "清华大学";
        cards[0] = "A";
        cards[1] = "B";
        cards[2] = "C";
    }

    public static void main(String[] args) {
        sout(schoolName); // 清华大学
        sout(Arrays.toString(cards));  ["A","B","C"]
    }
}

  1. 实例代码块(了解)
  • 格式: { }
  • 特点: 每次创建对象, 都会执行实例代码块, 并在构造器之前执行
  • 使用: 和构造器一样, 用来完成对象的初始化
public class CodeDemo {
    public String schoolName;
    public String[] direction = new String[4];
    
    // 实例代码块: 对实例变量进行初始化赋值
    {
        sout("实例代码块执行了")
        schoolName = "清华大学";
        direction[0] = "N";
        direction[1] = "S";
        direction[2] = "E";
        direction[3] = "W";
    }

    public static void main(String[] args) {
        // 每次创建对象, 都会执行实例代码块, 为当前对象的实例变量赋值
       CodeDemo c1 =  new CodeDemo();
       sout(Arrays.toString(c1.direction)); // ["N","S","E","W"]
       CodeDemo c2 =  new CodeDemo();
       sout(Arrays.toString(c2.direction)); // ["N","S","E","W"]
    }
}

内部类

如果一个类中定义在另一个类的内部, 这个类就是内部类

  1. 内部类是类的五大成分之一(成员变量/方法/构造器/内部类/代码块)
  2. 场景: 当一个类的内部, 包含了一个完成的事物, 且这个事物没必要单独设计时, 就可以设计成内部类
  3. 内部类通常可以方便访问外部类的成员, 包括私有成员
  4. 内部类提供了更好的封装性, 可以使用private, protectecd修饰内部类
成员内部类

属于类的一个普通成员, 类似普通的成员变量, 成员方法

// 定义
public class Car {
    public static String schoolName = "清华大学";
    public int age;
    
    // 1.成员内部类
    // 无static修饰, 属于外部类的对象持有
    public class Inner {
        // 成员内部类可以定义类的所有结构
        private String name;
        public Inner() {
            sout("无参构造器")
        }
        public Inner(String name) {
            this.name = name;
            sout("有参构造器")
        }
        public void show() {
            sout("内部类的成员方法")
            //3.1 成员内部类中可以直接访问外部类的静态成员和实例成员
            sout(schoolName); // 清华大学
            sout(age); // 0
            //3.2 通过this拿到自己的对象,通过 外部类名.this 拿到外部类的对象
            sout(this); //ci对象
            sout(Car.this) // car对象
        }
    }
}

// 2. 成员内部类创建对象的语法
// 外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称(); 
Car.Inner  ci = new Car().new Inner();
ci.show(); // 内部类的成员方法

// 3.访问成员
// 3.1可以直接访问外部类的实例成员和静态成员
// 3.2可以拿到当前外部类对象, 格式是: 外部类名.this
// 扩展面试题
class People {
    private int heartBeat = 100;

    public class Heart {
        private int heartBeat = 80;

        public void show() {
            int int heartBeat = 20;
            sout(heartBeat); // 20
            sout(this.heartBeat); // 80
            sout(People.this.heartBeat) // 100
        }
    }
}

People.Heart heart = new People().new Heart();
heart.show();
静态内部类

用static修饰的内部类就是静态内部类, 属于外部类自己持有, 特点与普通类一致

// 1.定义
public class Car {
    public static String schoolName;
    private int age; // 实例成员
    
   // 静态内部类: 属于外部类持有
   public static class Inner {
       // 静态内部类中可以定义所有的类成员
       private String name;
       public void show() {
           //2.1 可以直接访问外部类的静态成员
           sout(schoolName); // null
           //2.2.不可以直接访问外部类的实例成员
           sout(age); // 报错
       }
   }
}

// 创建对象
Car.Inner ci = new Car.Inner();
ci.show();

// 访问成员
// 2.1.可以直接访问外部类的静态成员
// 2.2.不可以直接访问外部类的实例成员
局部内部类

局部内部类是定义在方法中、代码块中、构造器等执行体中

public class Test {
    public static void main(String[] args) {
        
    }
     // 成员方法
    public static void go() {
        // 1.局部内部类
        class A { }
        // 2.局部内部类
        abstract class B { }
        // 3.局部内部类
        interface C { }
    }
}
匿名内部类(重点)
认识

匿名内部类是一种特殊的局部内部类, 匿名是指这个这个类不需要声明名字, 默认有个隐藏的名字

// 1.定义一个抽象类
abstract class Animal {
    public abstract void cry();
}

// 2.使用传统方式得到子类对象
class Cat extends Animal {
    @Override
    public void cry() {
        sout("猫喵喵喵")
    }
}
Animal a = new Cat();
a.crt(); // 猫喵喵喵

// 3.使用匿名内部类语法
// 把匿名内部类编译成一个子类, 然后立即创建一个子类对象
Animal a = new Animal() {
    @Override
    public void cry() {
        sout("喵喵喵")
    }
};
// 直接使用子类 
a.cry(); // 猫喵喵喵
  1. 特点: 匿名内部类本质是一个子类, 并且会立即创建一个子类对象

  1. 作用: 自动创建一个子类, 并且立即创建子类对象
  2. 优势: 便捷的创建子类对象
  3. 匿名内部类实际上是有名字的, 自动生成, 格式是: 外部类名$编号.class
  4. 匿名内部类的对象类型, 相当于是new的那个类型子类类型
常见使用形式

通常把匿名内部类作为对象参数传递给方法使用

// 需求: 学生,老师要参加游泳比赛。
// 传统编码形式
public class Test {
    public static void main(String[] args) {
        // 5.调用方法,开始比赛
        Swim s1 = new Teacher();
        start(s1);
        
        Swim s2 = new Student();
        start(s2);
    }

    // 4.设计一个方法,接收老师和学生,开始比赛
    public static void start(Swim s) {
        sout("比赛开始...")
        s.swimming();
        sout("比赛结束...")
    }
}

// 1.设计接口, 供子类实现
interface Swim {
    void swimming(); // 游泳
}

// 2.设计老师类
class Teacher implements Swim {
    @Override
    public void swimming() {
        sout("老师狗刨式游泳")
    }
}

// 3.设计学生类
class Student implements Swim {
    @Override
    public void swimming() {
        sout("学生蛙跳式游泳")
    }
}
  1. 那么单独创建对象传进去就比较麻烦, 此时可以使用匿名内部类简化代码

// 需求: 学生,老师要参加游泳比赛。
// 匿名内部类形式
public class Test {
    public static void main(String[] args) {
        // 3.调用方法,开始比赛
        Swim s1 = new Swim() {
             @Override
             public void swimming() {
               sout("老师狗刨式游泳")
             }
        };
        start(s1);
        
        Swim s2 = new Swim() {
             @Override
             public void swimming() {
               sout("学生蛙跳式游泳")
             }
        };
        start(s2);
    }

    // 2.设计一个方法,接收老师和学生,开始比赛
    public static void start(Swim s) {
        sout("比赛开始...")
        s.swimming();
        sout("比赛结束...")
    }
}

// 1.设计接口, 供子类实现
interface Swim {
    void swimming(); // 游泳
}
  1. 匿名内部类都是在需要的时候用, 比如一个方法需要一个对象作为参数
  2. 使用匿名内部类快速的创建子类对象, 可以极大的简化代码
应用场景

调用别人提供的方法时, 这个方法需要对象作为参数, 就可以使用匿名内部类, 快速创建对象给其使用。

public class Test {
    public static void main(String[] args) {
        // 需求: 创建一个登录窗口,窗口上有一个登录按钮
        JFrame win = new JFrame("登录窗口");
        win.setSize(300, 200);
        win.setLocationRelativeTo(null); // 居中展示
        win.setDefaultCloseOperaion(JFrame.EXIT_ON_CLOSE);

        JPanel penel new JPanel();
        win.add(penel);

        JButton btn = new JButton("登录");

        // java要求必须给按钮添加点击事件监听器对象,这样就可以监听用户的点击操作。
        // 1.传统写法: 自己定义对象, 实现监听器接口
        btn.addActionListener(new ClickListener);

        // 2.匿名内部类: 快速创建对象, 传给方法
        btn.addActionListener(new ActionListener{
              public void actionPerfirmed(ActionEvent e) {
                 sout("按钮点击了")
              }
        });

        win.setVisible(true);
    }
}

class ClickListener implements ActionListener {
    public void actionPerfirmed(ActionEvent e) {
        sout("按钮点击了")
    }
}

使用comparator接口的匿名内部类实现对数组进行排序

public class Student {
    private String name;
    private int age;
    private double height;
    private String sex;
}

public class Test {
    public static void main(String[] args) {
        // 需求: 完成数组排序, 加深匿名内部类的使用
        // 1. 准备数组,存放学生对象
        student[] students = new student[4];
        students[0] = new Student( name:"殷素素”,age:35,height:171.5,sex:'女');
        students[1] = new Student( name:"杨幂",age:28,height: 168.5,sex:'女');
        students[2] = new student( name:"张无忌"age:25,height:181.5,sex:'男');
        students[3] = new student( name:"刘亦菲",age: 36,height: 168,sex:'女');

        // 2.使用数组的API, 对数组按照对象的年龄升序排序
        // sort()语法说明:
        // public static void sort(T[] a, Comparator<T> c)
        // 参数一:需要排序的数组
        // 参数二:需要给sort声明一个Comparator比较器对象(指定排序的规则)
        // sort()语法执行:
        // sort方法会调用匿名内部类对象的compare方法,对数组中的学生对象进行两两比较,从而实现排序。
        Arrays.sort(students, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                // 指定排厅规则:
                // 1 如果你认为左边对象 大于 右边对象 那么返回正整数,
                // 2.如果你认为左边对象 小于 石边对象 那么返回负整数。
                // 3.如果两边相等那么返回0
                // if(o1.getAge() > o2.getAge()) {
                //     return 1;
                // } else if(o1.getAge() < o2.getAge()) {
                //     return -1;
                // }
                // return 0;

                // 按对象年龄升序排序
                return o1.getAge() - o2.getAge();
                // 按对象年龄降序排序
                return o2.getAge() - o1.getAge();
            }
        });
        
        // 3.遍历数组中的对象并输出
        for (int i = 0; i<students.length; i++) {
            student s = students[i];
            sout(s);
        }
        
    }
}

泛型

认识泛型

泛型提供了在编译阶段的类型约束, 并自动进行检查, 可以避免强制类型转换和可能出现的转换异常

// 该集合只能添加字符串类型的数据
ArrayList<String> list1 = new ArrayList<>();
list1.add("java1");
  1. 定义类,接口,方法时, 同时声明一个或多个类型变量,用于限制成员类型,
  2. 使用泛型的类称为泛型类, 泛型接口或泛型方法, 统称为泛型
  3. 原理: 把具体的数据类型作为参数传给类型变量, 通过类型变量限制成员的类型
自定义泛型类

// 模拟ArrayList集合
public class MyArrayList<E> {
    private ArrayList list = new ArrayList();
    
    public boolean add(E e) {
        list.add(e);
        return true;
    }
    
    public E remove(E e) {
        return list.remove(e);
    }

    @Override
    public String toString() {
        return list.toString();
    }
}

// 使用泛型
MyArrayList<String> mlist = new MyArrayList<>();
mlist.add("java");
mlist.add("PHP");
mlist.add("UI");
mlist.remove("java"); // true
sout(mlist); // ["PHP","UI"]
  1. 如果需要, 可以进一步使用extends限制泛型的范围

public class MyArrayList<E extends Animal> { }

  1. 表示类型E必须是Animal类型或者是Animal的子类
  2. 类型变量建议用大写的英文字母,常用的有:E、T、K、V等
自定义泛型接口

// 学生类
public class Student {
    
} 
// 老师类
public class Teacher {
    
} 
// 自定义泛型接口
// 数据操作接口
public interface Data<T> {
    void add(T t);
    void delete(T t);
    void update(T t);
    T query(int id);
}
// 学生操作实现类
public class StudentData implements Data<Student> {
    @Override
    public void add(Student student) {
        
    }

    @Override
    public void delete(Student student) {
        
    }

    @Override
    public void update(Student student) {
        
    }

    @Override
    public Student query(int id) {
        retuen null;
    }
}
// 老师操作实现类
public class TeacherData implements Data<Teacher> {
    @Override
    public void add(Teacher teacher) {
        
    }

    @Override
    public void delete(Teacher teacher) {
        
    }

    @Override
    public void update(Teacher teacher) {
        
    }

    @Override
    public Teacher query(int id) {
        retuen null;
    }
}
public class dome {
    public static void main(String[] args) {
        // 需求: 项目需要对学生/老师数据进行增删改查操作
        TeacherData teacherData = new TeacherData();
        teacherData.add(new Teacher());
        Teacher t = teacherData.query(1);

        StudentData studentData = new StudentData();
        studentData.add(new Student());
        Student s = studentData.query(1);
    }
}
  1. 类型变量建议使用大写英文字母, E T K V 等
自定义泛型方法

定义方法时声明泛型变量, 才是泛型方法, 使用泛型的方法不属于泛型方法

public static void main(String[] aegs) {
    String[] names = {"赵敏","张无忌","周芷若"};
    Student[] stus = new Students[3];

    String max = getMax(names);
    Student max2 = getMax(stus);
}


public static <T> T getMax(T[] names) {
     return null;
}
通配符

在使用泛型的时候可以用 ? 表示一切类型

// 汽车类
public class Car {}
// 小米汽车
public class Mi extends Car {}
// 理想汽车
public class Li  extends Car {}
public static void main(String[] args) {
    ArrayList<Mi> mis = new ArrayList<>();
    mis.add(new Mi());
    mis.add(new Mi());
    mis.add(new Mi());
    go(mis);

    ArrayList<Li> lis = new ArrayList<>();
    lis.add(new Li());
    lis.add(new Li());
    lis.add(new Li());
    go(lis);
}

// 需求: 让汽车赛跑
// 报错: 虽然Mi和Li都是Car的子类, 但是Arraylist<Mi> ArrayList<Li>和 Arraylist<Car> 是没有关系的
public static void go(ArrayList<Car> cars) { }
// 使用通配符, 表示一切类型, 这个方法Mi汽车和Li汽车都能用
public static void go(ArrayList<?> cars) { }

泛型上下限
// 狗
public class Dog {}
public static void main(String[] args) {
    ArrayList<Li> lis = new ArrayList<>();
    lis.add(new Li());
    lis.add(new Li());
    lis.add(new Li());
    go(lis);

    ArrayList<Dog> dogs = new ArrayList<>();
    dogs.add(new Dog());
    dogs.add(new Dog());
    dogs.add(new Dog());
    go(dogs);
}

// 需求: 让汽车赛跑
// 缺点: 狗也跑进来了, 约束性太弱了
public static void go(ArrayList<?> cars) { }

// 使用泛型上限: 能接受的类型必须是Car或其子类
// 狗类不是汽车类或其子类, 就进不来了
public static void go(ArrayList<? extedns Car> cars) { }
  1. 泛型上限: ? extends Cart
  2. 表示能接受的类型必须是Car或其子类
  3. 泛型下限: ? super Cart
  4. 表示能接受的必须是Cart或其父类
泛型擦除

泛型不支持基本数据类型, 只支持对象类型, 如Integer Double

public static void main(String[] ages) {
    // 报错: 泛型不支持基本数据类型
    ArrayList<int> list = new ArrayList<>()
    // 原因: 泛型擦除导致的
    // 1.泛型是在编译阶段工作的. 就是帮助程序进行类型校验的
    // 2.程序编译成class文件后, 泛型变量全部恢复为Object类型
    // 3.假设往集合中添加了基本类型的数据
    list.add(12);
    // 4.字节码文件中就会使用Object接收数据, 基本类型的数据不能赋值给对象类型, 造成报错
    Object e = 12; // 所以, 泛型不支持基本类型数据的
}

包装类

包装类就是8种基本类型数据对应的引用类型

  • 从理念上说., Java为了实现一切皆对象, 为8种基本类型提供了对应的引用类型
  • 从技术上说, 泛型和集合只支持包装类型, 所以需要包装类

相互转换

// 手动包装: 把基本类型数据转为引用类型
Integer a =  Integer.valueOf(12); // 得到对象12

// 缓存优化:  -128到127之间的数据经常用, java就封装了共享对象
Integer a1 =  Integer.valueOf(100);
Integer a2 =  Integer.valueOf(100);
sout(a1 == a2); // true, 因为指向同一个对象

Integer a1 =  Integer.valueOf(130);
Integer a2 =  Integer.valueOf(130);
sout(a1 == a2); // false, 超过了边界,创建新对象

//自动装箱: 基本类型的数据可以直接赋值给包装类型的变量
Integer i = 130;

//自动拆箱: 包装类型的数据可以直接赋值给基本类型的变量
int ii = i;

//自动装箱和拆箱的优势: 完全自动,不增加我们的负担
ArrayList<Integer> list = new ArratList<>();
list.add(120);  // 自动装箱
list.add(110);  // 自动装箱
int res1 = list.get(0);  // 自动拆箱
int res2 = list.get(1);  // 自动拆箱

包装类提供的方法

1.把基本类型的数据转成字符串

Integer a = 23;
String s1 = Integer.toString(a); // "23"
String s2 = a.toString(); // "23"
String s3 = a + ""; // "23"

2.把字符串类型的数值转成真实的类型数据

String ageStr = "29";
int age = Integer.parseInt(ageStr); //29
int age = Integer.valueOf(ageStr); // 29(推荐)(容易记)

String scoreStr = "99.5";
double score = Double.parseDouble(scoreStr); // 99.5
double score = Double.valueOf(scoreStr); // 99.5(推荐)(容易记)

// 注意
// 要转换的数据一定要和类型对应
String str1 = "29.5";
int age = Integer.parseInt(str1); //报错, 29.5不能转成29
String str2 = "29a";
int age = Integer.parseInt(str2); //报错, 29a不能转成整数

软件包

包就是分门别类的管理类的, 类似于文件夹

建包

// 语句
// package 公司域名倒写.技术名称

// 必须在第一行, 一般IEDA工具会帮助创建
package com.itheima.javabean;
public class Student {
    
}

导包

相同包下的类可以直接访问

  1. 不同包下的类必须导包, 语句: import 包名.类名;
  2. 使用java官方的程序, 也需要导包, lang包下的程序可以直接用
  3. 一个类中使用多个不同包下的类, 如果类的名字一样, 默认只能导入一个, 另一个必须带包名访问
// 访问多个其他包下的程序,这些程序名又一样的情况下,默认只能导入一个程序,
// 另一个程序必须带包名和类名来访问。
Demo2 d2 = new Demo2();
d2.myPrint();

com.itheima.pkg.itcast.Demo2 d3 = new com.itheima.pkg.itcast.Demo2();
d3.myPrint();

自动导包


http://www.kler.cn/a/531228.html

相关文章:

  • 北京门头沟区房屋轮廓shp的arcgis数据建筑物轮廓无偏移坐标测评
  • MySQL(高级特性篇) 13 章——事务基础知识
  • 在K8S中,pending状态一般由什么原因导致的?
  • python学opencv|读取图像(五十三)原理探索:使用cv.matchTemplate()函数实现最佳图像匹配
  • UE编辑器工具
  • Linux环境下的Java项目部署技巧:环境安装
  • Shell $0
  • git基础使用--4---git分支和使用
  • 数据结构(2)——线性表与顺序表实现
  • AMD模块
  • 25.2.3 【洛谷】作为栈的复习不错(学习记录)
  • opencv图像处理框架
  • 【Rust自学】19.4. 宏(macro)
  • Javascript代码库-jQuery入门
  • 读算法简史:从美索不达米亚到人工智能时代05天气预报
  • Apache Iceberg数据湖技术在海量实时数据处理、实时特征工程和模型训练的应用技术方案和具体实施步骤及代码
  • 爱普生L3153打印机无线连接配置流程
  • 【C++】B2120 单词的长度
  • C++11 多线程 锁与条件变量:mutex、lock_guard、unique_lock 和 condition_variable
  • 电控三周速成计划参考
  • 51c嵌入式~电路~合集25
  • GRE阅读双线阅读 --青山学堂GRE全程班 包括 阅读、数学、写作、填空、背单词
  • Math数字类
  • CH340G上传程序到ESP8266-01(S)模块
  • 大模型领域的Scaling Law的含义及作用
  • 7-4 西安距离