Java基础-面向对象2

  • 子类对象实例化过程图示:
image.png

思考:
1.为什么super(...)和this(...)调用语句不能同时在一个构造器中出现?

答: 因为他俩都只能出现在首行,所以只能写一个(好比古代皇后只能有一个)

2.为什么super(...)或this(...)调用语句只能作为构造器中的第一句出现?

答: 无论通过哪个构造器创建子类对象,需要保证先初始化父类.
目的: 当子类继承父类后,"继承"父类中所有的属性和方法,因此子类有必要知道父类如何为对象进行初始化.

所以加载顺序是首先加载间接父类,其次加载直接父类,然后才是加载到本类

强调说明:

虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象

图示:

image.png

虽然加载了父类构造器了,但不认为创建了多个对象,对外是整个红框,体现的是一个类型的对象,就是new的Dog

5.6.多态性-多态性的使用

面向对象特征之三: 多态性
1.多态性的理解: 可以理解为一个事物的多种形态
2.什么是多态性: 父类的引用指向子类的对象(或子类的对象赋给父类的引用)
Person p = new Man();
Object obj = new 任意();
3.多态的使用: 虚拟方法使用
有了对象的多态性后,在编译期(编译器编译的时候),只能调用父类中声明的方法,但在运行期,实际执行的是子类重写父类的方法
如果子类中没有重写父类的方法,就执行父类的方法(这样使用多态性没有意义)
p.eat();
总结:编译时看等号左边的类型,运行时看右边的对象(编译时是父类,执行时是子类)
4.多态性的使用前提(只说方法的事没有属性的事,多态性方面跟属性没关系): (1)要有类的继承关系 (2)方法的重写
5.对象的多态性: 只适用于重写的方法,不适用于属性(属性的编译和运行都看左边).
如果Java中没有多态性的话,那抽象类和接口就没有存在的意义,因为抽象类和接口都不能造对象,开发中使用抽象类和接口,一定会提供子类的对象或实现类的对象,这里体现都是多态性
多态性的好处: 减少大量方法的重载

public class PersonTest {
    public static void main(String[] args) {
        Person p1 = new Person();
        p1.eat();
        Man man = new Man();
        man.eat();
        man.age = 25;
        man.earnMoney();
        System.out.println("*********************");
        // 对象的多态性: 父类的引用(p2)指向子类的对象(new Man())
        Person p2 = new Man(); // 多态的形式
        Person p3 = new Woman();
        // 多态的使用: 当调用子类同名同参数的方法时,实际执行的是子类重写父类的方法---虚拟方法调用
        // 有了多态的形式后,通过引用调用子父类都声明过的方法,真正执行的是子类重写父类的方法
        p2.eat(); // 编译看左,运行看右
        p2.walk();
        // 多态性的使用不能调父类没有的方法属性, 编译时,p2是Person类型
        // p2.earnMoney();  报错
        // p2.isSmoking = true; 报错
        System.out.println(p2.id); // 1001; 多态性与属性无关,p2编译时的类型就是属性的类型
    }
}
public class Person {
    String name;
    int age;
    int id = 1001;
    public void eat(){
        System.out.println("人吃饭");
    }
    public void walk(){
        System.out.println("人走路");
    }
}
public class Man extends Person{
    boolean isSmoking;
    int id = 1002;
    public void earnMoney(){
        System.out.println("男人负责挣钱");
    }

    /*@Override
    public void eat() {
        System.out.println("男人多吃肉,长肌肉");
    }*/

    @Override
    public void walk() {
        System.out.println("男人霸气的走路");
    }
}
public class Woman extends Person{
    boolean isBeauty;
    public void goShopping(){
        System.out.println("女人喜欢购物");
    }
    @Override
    public void eat() {
        System.out.println("女人少吃为了减肥");
    }
    @Override
    public void walk() {
        System.out.println("女人窈窕的走路");
    }
}
  • 多态性的使用举例:
import java.sql.Connection;

// 多态性的使用举例一:
public class AnimalTest {
    public static void main(String[] args) {
        AnimalTest test = new AnimalTest();
        test.func(new Dog());
        test.func(new Cat());
    }
    // 使用多态性: 凡是new子类的对象
    // 每种动物对一件事表现出来的形态都不一样 就是多态
    public void func(Animal animal){ // Animal animal = new Dog(); 对象多态性的形式
        // 因为形参是Animal类型的,所以只能调Animal类里的方法,但实际new了一个Dog类的对象,真正运行的时候,是Dog重写父类方法的执行
        animal.eat();
        animal.shout();
    }
    // 不用多态性的写法: 声明什么类型只能new这个类型的对象
    public void func(Dog dog){
        dog.eat();
        dog.shout();
    }
    public void func(Cat cat){
        cat.eat();
        cat.shout();
    }
}
// 父类
class Animal{
    public void eat(){
        System.out.println("动物进食");
    }
    public void shout(){
        System.out.println("动物叫");
    }
}
// 子类
class Dog extends Animal{
    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }

    @Override
    public void shout() {
        System.out.println("汪汪汪");
    }
}
class Cat extends Animal{
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }

    @Override
    public void shout() {
        System.out.println("喵喵喵");
    }
}
// 举例二:
class Order{
    public void method(Object obj){

    }
}
// 举例三:
class Driver{
    // 形参里传哪个对象就建立跟哪个数据库的连接,因为子类方法都重写过了,自然就能实现对那个数据库中表的操作
    public void doData(Connection conn){ // conn = new MySQLConnection();/ conn = new OracleConnection();/...
        // 规范步骤去操作数据 都是子类重写过的方法
        /*conn.method1();
        conn.method2();
        conn.method3();*/
    }
}
  • 虚拟方法调用的再理解
image.png
  • 面试题: 多态是编译时行为还是运行时行为?
编译时期和运行时期的区别
image.png

面试题: 多态是编译时行为还是运行时行为?

运行时行为;看代码看不出来调的是哪个,因为有随机数,运行了才知道随机数是多少

/ 面试题: 多态是编译时行为还是运行时行为?
// 证明结论: 运行时行为,main函数中的animal引用指向哪个子类的对象,是由运行时产生的随机数再调用了判断得到的,最后运行结果是返回对象类型中eat()方法而不是编译时Animal类中的eat()方法
import java.util.Random;

public class InterviewTest {
    public static void main(String[] args) {
        int key = new Random().nextInt(3); // 取随机数: 0~2
        System.out.println(key);
        // 通过下面new的对象来看,这实际上就是多态
        Animal animal = getInstance(key);
        animal.eat();
    }
    public static Animal getInstance(int key){
        switch (key){
            case 0:
                return new Cat();
            case 1:
                return new Dog();
            default:
                return new Sheep();
        }
    }
}
class Animal{
    protected void eat(){
        System.out.println("animal eat food");
    }
}
class Dog extends Animal {
    @Override
    protected void eat() {
        System.out.println("dog eat bone");
    }
}
class Cat extends Animal {
    @Override
    protected void eat() {
        System.out.println("cat eat fish");
    }
}
class Sheep extends Animal {
    @Override
    protected void eat() {
        System.out.println("sheep eat grass");
    }
}
  • 小结: 方法的重载和重写
image.png

5.7.向下转型的使用

内存解析的说明

引用类型的变量,只可能储存两类值:

  • null
  • 内存地址值,包含变量(对象)的类型

例如,直接打印一个实例化后对象的值
Phone p = new Phone();
System.out.println(p)
得到的结果为:

image.png
image.png
  • instanceof关键字的使用

向下转型的目的: a instanceof A 判断 对象a 运行时是不是多态中子类的A类类型,如果是,则要向下转型,向下转型的目的是可以让对象a点出运行时实际(真实)类型对象的特有的属性或方法

a instanceof A: 判断对象a是否是类A的实例.如果是,返回true,如果不是,返回false
使用情境,为了避免在向下转型时出现ClassCastException的异常,在向下转型之前,
先进行instanceof的判断,一旦返回true,就进行向下转型.如果返回false,不进行向下转型.
如果 a instanceof A 返回 true,则 a instanceof B 也返回true,
其中类B和类A的不都是同一父类的子类

public class PersonTest {
    public static void main(String[] args) {
        Person p1 = new Person();
        p1.eat(); // 父类调用自己的方法
        Man man = new Man();
        man.eat(); // 子类继承父类调用父类的方法
        man.age = 25;
        man.earnMoney();
        System.out.println("*********************");
        // 对象的多态性: 父类的引用(p2)指向子类的对象(new Man())
        Person p2 = new Man(); // 多态的形式
        Person p3 = new Woman();
        Person p4 = new Person();
        Man m1 = new Man();
        // 多态的使用: 编译时当调用子类同名同参数的方法时,认为是父类的方法,实际执行的是子类重写父类的方法---虚拟方法调用
        // 有了多态的形式后,通过引用调用子父类都声明过的方法,真正执行的是子类重写父类的方法
        p2.eat(); // 编译看左,运行看右; 在编译期认为p2是Person类型,所以只能调用Person中声明的属性和方法
        p2.walk();
        System.out.println("===================");
        p2.name = "tom";
        // 多态性的使用不能调父类没有子类特有的方法,属性, 编译时,p2是Person类型
        // p2.earnMoney(); // 报错
        // p2.isSmoking = true; // 报错
        System.out.println(p2.id); // 1001; 多态性与属性无关,p2编译时的类型就是属性的类型
        // 有了对象的多态性后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型.
        // 导致编译时,只能调用父类中声明的属性和方法,(子类特有的属性和方法相当于被屏蔽了)子类特有的属性和方法不能调用.
        // 如何才能调用子类特有的属性和方法?
        // Man m1 = p2; // java中左右赋值要么左右类型一样,要么是基本数据类型且有自动类型提升; 父类型变量不能赋给子类型变量
        System.out.println(p4);
        System.out.println(m1);
        p4  = m1; // 类类型变量,子类型变量可以赋给父类型变量,表现为多态;
        // 向下转型: 使用强制类型转换符
        System.out.println(p2); // p2运行时是Man类型
        Man m2 = (Man) p2; // 编译时,把Person类型的变量强转成Man类型的一个新变量,相当于m2是Man类型的p2
        m2.earnMoney();
        m2.isSmoking = true;
        // 使用强转时,出现ClassCastException的异常
        // Woman w1 = (Woman) p2; // 编译虽然通过,但运行时p2已经是Man类型了,子类类型之间不能不能互相转换,地址类型不相同,
        // w1.goShopping(); // 执行时出错,出现ClassCastException的异常

        /*
         * instanceof关键字的使用
         * a instanceof A: 判断对象a是否是类A的实例.如果是,返回true,如果不是,返回false
         * 使用情境,为了避免在向下转型时出现ClassCastException的异常,在向下转型之前,
         * 先进行instanceof的判断,一旦返回true,就进行向下转型.如果返回false,不进行向下转型.
         * 如果 a instanceof A 返回 true,则 a instanceof B 也返回true,
         * 其中类B是类A的父类
         */
        // 本质就是把p2的new的类型能否赋值给声明时的类型,是子类对象一定是父类对象,是父类对象不一定是子类对象
        if (p2 instanceof Person){
            System.out.println("person");
        }
        if (p2 instanceof Man){
            Man m3 = (Man) p2;
            m3.earnMoney();
            System.out.println("man");
        }
        if (p2 instanceof Woman){
            Woman w1 = (Woman) p2;
            w1.goShopping();
            System.out.println("woman");
        }
        // 练习:
        // 问题一: 编译时通过,运行时不通过
        /*Person p5 = new Woman();
        Man m4 = (Man) p5;*/
        Person p6 = new Man();
        Man m6 = (Man) p6;
        // 问题二: 编译时通过,运行时也通过
        Object obj = new Woman();
        Person p = (Person) obj;
        // 问题三: 编译时不通过,运行时也不通过
        // Man m5 = new Woman();
        // String s = new Date(); // Date类型和String类没任何关系
    }
}
public class Person {
    String name;
    int age;
    int id = 1001;
    public void eat(){
        System.out.println("人吃饭");
    }
    public void walk(){
        System.out.println("人走路");
    }
}
public class Man extends Person{
    boolean isSmoking;
    int id = 1002;
    public void earnMoney(){
        System.out.println("男人负责挣钱");
    }

    @Override
    public void eat() {
        System.out.println("男人多吃肉,长肌肉");
    }

    @Override
    public void walk() {
        System.out.println("男人霸气的走路");
    }
}
public class Woman extends Person{
    boolean isBeauty;
    public void goShopping(){
        System.out.println("女人喜欢购物");
    }
    @Override
    public void eat() {
        System.out.println("女人少吃为了减肥");
    }
    @Override
    public void walk() {
        System.out.println("女人窈窕的走路");
    }
}

Person p2 = new Man();
Man m1 = (Man) p2;
父类向下转型图示:

image.png

  • 多态练习: 调用属性和方法

子类继承父类,子类和父类同名的变量会继承父类的但不会把父类覆盖,也就是说子类的同名属性是独立的,但是方法会把父类覆盖
方法里想调父类的属性,就要用到super.属性,否则,输出this.属性结果还是子类属性的值
总结:
1.若子类重写了父类方法,就意味着紫烈里定义的方法彻底覆盖了父类里的同名方法,
系统将不可能把父类里的方法转移到子类中: 编译看左边,运行看右边
2.对于实例变量(属性)则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,
这个实例变量依然不可能覆盖父类中定义的实例边浪: 编译运行都看左边

public class FieldMethodTest {
    public static void main(String[] args) {
        Sub s = new Sub();
        System.out.println(s.count); // 20  this关键字就近原则
        s.display(); // 20 this: 当前类就近原则,super: 父类的变量
        /*Base b = new Base();
        System.out.println(b.count);*/
        Base b = s; // 多态性
        // == 对于引用数据类型来讲,比较的是两个引用数据类型变量的地址值是否相同
        // true 指向同一堆空间中的对象
        System.out.println(b == s); // true
        // 多态性不适用于属性,属性值是谁就看声明这个属性的类型,编译运行都看左边
        System.out.println(b.count); // 10
        // 声明了一个多态,通过b的引用调用子父类都有的方法的时候,实际调用子类的方法(虚拟方法调用),运行看右边
        b.display(); // 20
    }
}
class Base{
    int count = 10;
    public void display(){
        System.out.println(this.count);
    }
}
class Sub extends Base {
    int count = 20;
    public void display(){
        System.out.println(this.count);
    }
}
  • 多态练习: 基本操作
public class InstanceTest {
   public static void main(String[] args) {
       // 基类用来造对象的
       InstanceTest test = new InstanceTest();
       test.method(new Graduate());

   }
   public void method(Person e){ // 上面的实参赋给形参,这就构成了多态
       // 虚拟方法调用
       String info = e.getInfo();
       System.out.println(info);
       // 方式一:
       // 输出条件先从小范围的类(子类)输出,如果从大的类(父类)输出,下面子类就无法输出了
       /*if (e instanceof Graduate){
           System.out.println("a graduate");
       }else if (e instanceof Student){
           System.out.println("a student");
           // else 不用再判断了,因为能进来就一定是个Person类
       }else{
           System.out.println("a person");
       }*/
       // 方式二:
       // 如果 a instanceof A 返回 true,则 a instanceof B 也返回true,
       // 其中类B是类A的父类
       if (e instanceof Graduate){
           System.out.println("a graduate");
       }
       if (e instanceof Student){
           System.out.println("a student");
       }
       if (e instanceof Person){
           System.out.println("a person");
       }
   }
}
class Person{
   protected String name = "person";
   protected int age = 50;
   public String getInfo(){
       return "name: " + name + "\n" + "age: " + age;
   }
}
class Student extends Person{
   protected String school = "pku";
   public String getInfo(){
       return "name: " + name + "\n" + "age: " + age + "\nschool: " +school;
   }
}
class Graduate extends Student{
   protected String major = "IT";
   public String getInfo(){
       return "name: " + name + "\n" + "age: " + age + "\nschool: " +school + "\nmajor: " + major;
   }
}
  • 多态练习: 几何图形
public class GeometricTest {
    public static void main(String[] args) {
        // 下面的方法要想被调用,就要创建当前类的对象
        GeometricTest test = new GeometricTest();
        Circle c1 = new Circle(2.4, "white", 1.0);
        test.displayGeomericObject(c1);
        Circle c2 = new Circle(1.2, "green", 2.0);
        test.displayGeomericObject(c2);
        boolean isEquals = test.equalsArea(c1, c2);
        System.out.println("c1和c2面积是否相等: " + isEquals);
        MyReactangle react = new MyReactangle(2.4, 1.2, 1.0, "red");
        System.out.println("矩形面积: " + react.findArea());
    }

    // 利用动态绑定,测试两个对象的面积是否相等
    public boolean equalsArea(GeometricObject o1, GeometricObject o2){
        return o1.findArea() == o2.findArea();
    }
    // 显示对象面积
    // 这一块已经体现了多态性的使用
    // 声明的时候是 GeomericObject(父类)的类型,实际调用放的是new Circle(...)子类类型,所以与抽象类不冲突
    // 只要传的是该类子类的对象,就一定是多态
    public void displayGeomericObject(GeometricObject o){
        System.out.println("面积为: " + o.findArea());
    }
}
// 将几何图形改为抽象类,因此不能再造对象,事实上也不会去造而是造子类对象
public abstract class GeometricObject {
    protected String color;
    protected  double weight;
    /*public GeometricObject(){

    }*/
    public GeometricObject(String color, double weight){
        super();
        this.color = color;
        this.weight = weight;
    }
    public void setColor(String color){
        this.color = color;
    }
    public String getColor(){
        return this.color;
    }
    public void setWeight(double weight){
        this.weight = weight;
    }
    public double getWeight(){
        return this.weight;
    }
// 因为几何图形的面积求法不一,所以将它抽象化,这样子类自然就需要重写求面积方法
    public abstract double findArea(); // 父类被重写的方法
}
public class Circle extends GeometricObject{
    private double radius; // 定义一个私有属性顺便在构造器中初始化
    public Circle(double radius, String color, double weight){
        super(color, weight); // 调父类指定构造器
        this.radius = radius;
    }
    public void setRadius(double radius){
        this.radius = radius;
    }
    public double getRadius(){
        return this.radius;
    }

    @Override
    public double findArea() {
        return Math.PI * radius * radius;
    }
}
public class MyReactangle extends GeometricObject {
    double width; // 矩形宽
    double height; // 矩形长
    public MyReactangle(double width, double height, double weight, String color){
        super(color, weight);
        this.height = height;
        this.width = width;
    }
    public void setWidth(double width){
        this.width = width;
    }
    public double getWidth(){
        return this.width;
    }
    public void setHeight(double height){
        this.height = height;
    }
    public double getHeight(){
        return this.height;
    }

    @Override
    public double findArea() {
        return this.height * this.width;
    }
}
  • 多态练习: 重写方法
public class InterviewTest1 {
    public static void main(String[] args) {
        Base1 base = new Sub1();
        // 编译看左边,运行看右边
        base.add(1,2,3); // sub1
        Sub1 s = (Sub1) base;
        // 调用子类特有的方法
        // 父类add的形参是不确定多个,子类是确定多个,优先选确定多个的
        s.add(1,2,3); // sub2
    }
}
 // 在可变形参时讲到,定义形参时,int...和int[]不能同时出现,意思是编译器认为他俩一样
 // 所以下面两种方法可理解为重写
class Base1{
    public void add(int a, int... arr){
        System.out.println("Base1");
    }
}
class Sub1 extends Base1{

    public void add(int a, int[] arr){
        System.out.println("sub1");
    }
    public void add(int a, int b, int c){
        System.out.println("sub2");
    }
}

5.8.Object类的使用-- == 和 equals()的区别

结论: 凡是基本数据类型之间比较就用 == ,引用数据类型之间比较久用equals()方法,特别地,除String,Date,File等包装类之外自定义的类之间的比较都要重写equals方法

面试题: == 和 equals()的区别

image.png

一.回顾 == 的使用:
==: 运算符
1.可以使用在基本数据类型变量和引用数据类型变量中
2.如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等.(不一定类型要相同),一般不用特别关注类型,因为有自动类型提升
如果比较的是引用数据类型变量:比较两个对象的地址值是否相同(两个引用是否指向同一对象实体/堆空间当中是否同一对象)
补充: == 符号使用时,必须保证符号左右两边的变量类型一致(但不一定要相同),例如:
System.out.println("Hello" == new java.util.Date()); // 不同类类型进行比较,编译不通过
二.equals()方法的使用:
1.是一个方法,而非运算符
2.只能适用于与引用数据类型(只能通过类创建对象来调用)
3.Object类中equals()的定义:
public boolean equals(Object obj) {
return (this == obj);
}
说明: Object类中定义的equals()和 == 的作用是相同的,比较两个对象的地址值是否相同(两个引用是否指向同一对象实体/堆空间当中是否同一对象)
4.像String,Date,File,包装类等都重写了Object类中的equals()方法.重写以后,比较的不是两个引用的地址是否相同,
而是比较两个对象的"实体内容"是否相同. 实体内容实际指类中的属性是否相同
除了以上的特殊类,一般自定义类调用的默认是Object类中的equals()方法,其方法只是比较地址值是否相等
5.通常情况下,自定义的类如果使用equals()方法的话,也通常是比较两个对象的"实体内容"是否相同
那么这时候就需要对Object类中的equals()进行重写
equals方法重写原则: 比较两个对象"实体内容"是否相同

image.png
public class EqualsTest {
    public static void main(String[] args) {
        // 基本数据类型:
        int i = 10;
        int j = 10;
        double d = 10.0;
        // 除Boolean,其他七种都可以用 == 符号比较
        // 不同基本类型也可以比,主要看存储的值是否相等
        System.out.println(i == j); // true
        System.out.println(i == d); // true 自动类型提升
        // 基本数据类型运算不和Boolean类型一起使用
        /*boolean b = true;
        System.out.println(i == b);*/ // 编译报错
        // 除了boolean型之外都可以
        // char本质就是整数,用整数对应字符
        char c = 10; // char型可以赋值数字
        System.out.println(i == c); // true
        char c1 = 'A';
        char c2 = 65;
        System.out.println(c1 == c2); // true
        // 引用类型: 自定义类,jdk里提供的api都一样
        Customer cust1 = new Customer("tom", 20);
        Customer cust2 = new Customer("tom", 20);
        // 每次new的时候堆空间都会创建一个地址,
        System.out.println(cust1 == cust2); // false
        String str1 = new String("java");
        String str2 = new String("java");
        System.out.println(str1 == str2); // false
        // equals方法是Object中定义过的,这时equals方法相当于用了多态
        // equals方法的形参是Object类型的,放了子类Customer类型的对象
        System.out.println("====================");
        System.out.println(cust1.equals(cust2));// false
        // 他是String当中重写的equals方法,所以不会按照Object中来声明了
        System.out.println(str1.equals(str2));// true
        Date date1 = new Date(1615161L); // 构造器带的参数通常就是给类里的属性赋值的
        Date date2 = new Date(1615161L);
        System.out.println(date1.equals(date2));// true
    }
}
public class Customer {
    private String name;
    private int age;
    public Customer(){

    }
    public Customer(String name, int age){
        this.name = name;
        this.age = age;
    }
    public void setAge(int age){
        this.age = age;
    }
    public int getAge(){
        return this.age;
    }
    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return this.name;
    }
    @Override
    // equals方法重写原则: 比较两个对象"实体内容"(即: name和age)是否相同
    public boolean equals(Object obj){ // 形参这里已经用了多态
        // 如果当前调用equals()方法的对象的地址值等于比较那个对象的形参的地址值
        if (this == obj){
            return true; // 地址相同返回true,否则执行下一步
        }
        // 将形参向下转型;形参是Object类型,调用equals的实参是Customer类型
        // 编译的时候,将父类obj对象向下强转型才能调用子类Customer的对象的属性和方法,否则是点不出Customer类的结构的,编译看左边,编译时还是Object类型
        // 虽然运行的时候,形参实际接收到的obj是Customer类,因为多态虚拟方法使用,但是不强转型,写不出Customer类的结构
        if (obj instanceof Customer){
            Customer cust = (Customer) obj;
            // if (this.age == cust.age && this.name.equals(cust.name)) {
            //     return true;
            // }else{
            //     return false;
            // }
            // 或
            // name属性是String类型,比较内容不能用==(地址比较),要用String中重写的equals方法比较其实体内容
            // 而age是基本数据类型只需比较数值是否相等即可
            return this.age == cust.age && this.name.equals(cust.name);
        }else{
            return false;
        }
    }
}
String str1 = new String("java");
String str2 = new String("java");
System.out.println(str1 == str2); // false

这里用 == 判断结果是false是因为判断的不是常量池里的内容,这里用两个String类型的对象进行比较的是对象的地址值,而不是内容,所以结果为false

String s1 = "BB";
String s2 = "BB";
System.out.println(s1 == s2); // true

s1 == s2的原因: String类型的内容存放在方法区的字符串常量池中,再定义一个变量发现内容和已有的相同了,新变量直接复用了旧变量
意味着两变量赋过来的地址是一样的,所以用==判断就是true

String s1 = new String("BB");
String s2 = "BB";
System.out.println(s1 == s2); // false

当通过new来造的String类型对象来比较内容的时候,地址就不一样了,所以用 == 比较结果为false,如果是用equals()方法比较则是true,因为不管怎么写比的都是内容

  • equals()方法练习:
public class OrderTest {
    public static void main(String[] args) {
        Order o1 = new Order(1001, "AA");
        Order o2 = new Order(1001, "BB");
        System.out.println(o1.equals(o2)); // false
        Order o3 = new Order(1001, "BB");
        System.out.println(o2.equals(o3)); // true // equals方法重写
        // s1 == s2的原因: String类型的内容存放在方法区的字符串常量池中,再定义一个变量发现内容和已有的相同了,新变量直接复用了旧变量
        // 意味着两变量赋过来的地址是一样的,所以用==判断就是true
        String s1 = "BB";
        String s2 = "BB";
        String s3 = new String("BB");
        System.out.println(s1 == s2); // true
        System.out.println(s1 == s3); // false
    }
}

class Order {
    private int orderId;
    private String name;

    public Order(int orderId, String name) {
        super();
        this.name = name;
        this.orderId = orderId;
    }

    public void setOrderId(int orderId) {
        this.orderId = orderId;
    }

    public int getOrderId() {
        return this.orderId;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public boolean equals(Object obj) {
        if (this == obj) { // 判断两个对象地址是否相同
            return true;
        }
        // 地址不相同则执行下面类型判断
        if (obj instanceof Order) { // 形参传入的obj是不是个Order
            Order order = (Order) obj;
            // 正确写法: 结论: 只要比较基本属性类型就用== ,只要比较引用数据类型都用equals
            return this.orderId == order.orderId && this.name.equals(order.name);
            // 错误写法:
            // return this.orderId == order.orderId && this.name == order.name;
        } else {
            return false;
        }
    }
}
  • 单元测试方法的使用

5.9.Object类的使用--toString()方法

Object类中toString()的使用:

  • 1.当输出一个对对象的引用时,实际上就是默认调用当前对象的toString()方法
    1. Object类中toString()的定义:
      public String toString() {
      return getClass().getName() + "@" + Integer.toHexString(hashCode());
      }
  • 3.像String,Date,File,包装类等都重写了Object类中toString()方法
    使得在调用欧冠对象的toString()时,返回"实体内容"信息
  • 4.自定义类也可以重写toString()方法,当调用此方法时,返回对象的"实体内容"

5.10.包装类的使用

image.png

1.java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
2.掌握的:基本数据类型,包装类,String三者之间的相互转换

public class WrapperTest {
    // String类型 --> 基本数据类型,包装类:  调用包装类的parseXxx(String s) (主要掌握)
    @Test
    public void test5(){
        String str1 = "123";
        // 错误写法: 强转类型 必须要有子父类关系,无关系的两个类不能强转
        // int num1 = (int) str1;
        // Integer in1 = (Integer) str1;
        // 如果数字里有包含其他字符可能会报NumberFormatException
        int num2 = Integer.parseInt(str1);
        System.out.println(num2 + 1);
        String str2 = "true";
        boolean b = Boolean.parseBoolean(str2);
        System.out.println(b);
    }
    // 基本数据类型,包装类 --> String类型: 调用String重载的ValueOf(XXX xxx) (主要掌握)
    @Test
    public void test4(){
        int num1 = 10;
        // 方式一: 连接运算
        String str1 = num1 + "";
        // 方式二: 不管是基本数据类型还是包装类都可以调用String的ValueOf(哪一个类型的变量)
        float f1 = 12.3f;
        String str2 = String.valueOf(f1); // "12.3"
        Double d1 = new Double(12.4);
        String str3 = String.valueOf(d1);
        System.out.println(str2);
        System.out.println(str3);
    }
    /*
     * jdk5.0新特性: 自动装箱与自动拆箱
     */
    @Test
    public void test3(){
        /*int num1 = 10;
        // 基本数据类型 --> 包装类的对象
        method(num1);*/
        // 自动装箱: 基本数据类型 --> 包装类的对象 (主要掌握)
        int num2 = 10;
        Integer i1 = num2; // 自动装箱
        boolean b1 = true;
        Boolean b2 = b1; // 自动装箱
        // 自动拆箱: 包装类的对象 --> 基本数据类型 (主要掌握)
        System.out.println(i1.toString());
        int num3 = i1;// 自动拆箱

    }

    public void method(Object obj){
        System.out.println(obj);
    }
    // 包装类 --> 基本数据类型: 调用包装类的xxxValue()方法
    // 包装类作为类的对象是不能做加减乘除运算的,但转化为基本数据类型就可以了
    @Test
    public void test2(){
        Integer in1 = new Integer(12);
        int i1 = in1.intValue();
        System.out.println(i1 + 1); // 13
        Float f1 = new Float(12.3);
        float f2 = f1.floatValue();
        System.out.println(f2 + 1);
    }
    // 基本数据类型 --> 包装类: 调用包装类的构造器
    // 例如:有些方法的形参如果是类类型,就必须把基本数据类型转化为包装类才能放到形参里去用
    // 创建一个单元测试
    @Test
    public void test1(){
        int num1 = 10;
        Integer in1 = new Integer(num1);
        // 把整数10转化为了字符串10
        // Integer重写了toString方法,输出的值就是方法里存的实体内容
        System.out.println(in1.toString()); // 10
        // 还能放String类型的值
        Integer in2 = new Integer("123"); // 123
        System.out.println(in2.toString());
        // 报异常 形参要是纯粹的数
        /*Integer in3 = new Integer("123agc");
        System.out.println(in3.toString());*/
        Float f1 = new Float(12.3f);
        Float f2 = new Float("12.3");
        System.out.println(f1);
        System.out.println(f2);
        Boolean b1 = new Boolean(true);
        Boolean b2 = new Boolean("false");
        // Boolean类里有优化,根据Boolean类源码可知,如果传入的字符串不是正常的true值,则返回false
        Boolean b3 = new Boolean("true123");
        System.out.println(b3);
        Order order = new Order();
        System.out.println(order.isMale); // false
        System.out.println(order.isFemale);// null
    }
}
class Order{
    boolean isMale;
    Boolean isFemale;
}
  • 包装类的练习:
import java.util.Scanner;
import java.util.Vector;

public class ScoreTest {
    public static void main(String[] args) {
        // 1.实例化Scanner,用于从键盘获取学生成绩
        Scanner scan = new Scanner(System.in);
        // 2.创建Vector对象: Vector v = new Vector(); 造一个容器,相当于原来的数组
        Vector v = new Vector();
        int maxScore = 0;
        // 3.通过for(;;)或while(true)方式,给Vector中添加数组
        for (; ; ) {
            System.out.println("输出学生成绩(以负数代表输入结束): ");
            int score = scan.nextInt();
            // 3.2.当输入是负数时,跳出循环
            if (score < 0) {
                break;
            }
            if (score > 100) {
                System.out.println("输出非法数据,请重新输入");
                continue; // 输入超出范围,结束本次循环,重新开始
            }
            // 3.1.添加操作: v.addElement(Object obj)
            // jdk5.0之前
            /*Integer inScore = new Integer(score);
            v.addElement(inScore); // 多态形式*/
            // jdk5.0之后
            v.addElement(score); // 自动装箱 --> 向上转型
            // 4.获取学生成绩的最大值: 讲数组的时候,这个操作可以放循环里: 每次从键盘上获取一个学生成绩时候,从现有所谓最高分比较一下
            if (maxScore < score) {
                maxScore = score;
            }
        }
        // 5.遍历Vector,得到每个学生的成绩,并与最大成绩比较,得到每个学生的等级
        char level; // 没赋值不报错,因为在if..else..条件里一定会赋值,他就一定会有这个值
        for (int i = 0; i < v.size(); i++) {
            Object obj = v.elementAt(i); // 取到每一个元素,因为从v.addElement()往Vector里放的Object类型的对象,所以取的时候他自认为也是个Object类型的
            /*Integer inScore = (Integer) obj; // 先从父类Object向下强转型为Integer子类型,
            int score = inScore.intValue();*/ // 再将包装类转化为基本数据类型
            int score = (int) obj; // 直接从Object类型拆箱成int型
            if (maxScore - score <= 10) {
                level = 'A';
            } else if (maxScore - score <= 20) {
                level = 'B';
            } else if (maxScore - score <= 30) {
                level = 'C';
            } else {
                level = 'D';
            }
            System.out.println("student " + i + " score is " + score + " grade is " + level);
        }
    }
}

6.1.static关键字

  • 变量
image.png
  • 类变量 vs 实例变量内存解析
image.png
static关键字的使用

1.static:静态的
2.static可以用来修饰: 属性,方法,代码块,内部类 (变量分成属性和局部变量)
3.使用static修饰属性: 静态变量(或类变量,一定是属性,不归具体对象多有,归类所有)
3.1.属性: 按是否使用static修饰,又分为: 静态属性(静态变量) vs 非静态属性(实例变量,归具体的某一个对象所有的)
实例变量: 创建了多个类的多个对象,每个对象都独立的拥有一套类中的非静态属性.当修改其中一个对象中的
非静态属性时,不会导致其他对象中同样的属性值的修改,可理解为:随着对象的创建而加载的
静态变量: 创建了类的多个对象,多个对象共享同一个静态变量.当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的
3.2.static修饰属性的其他说明:
(1)静态变量随着类的加载而加载(加载指把它放到内存中了).可以通过"类.静态变量"的方式进行调用
(2)静态变量的加载要早于对象的创建.(而实例变量是在有了对象以后或在创建对象当中,才帮你把实例变量加载的
(3)由于类只会加载一次,则静态变量在内存中也只会存在一份,存在JVM的方法区的静态域中. 创建一次后就会存在缓存区中,可以加快调用速度
静态域指的是: 存放类当中声明为static的属性

(4)

类变量 实例变量
yes no
对象 yes yes

3.3.静态属性举例: System.out; Math.PI;
4.使用static修饰方法: 静态方法
(1)凡是静态的,都是随着类的加载而加载,可以通过"类.静态方法"的方式进行调用

(2)

静态方法 非静态方法
yes no
对象 yes yes

(3)静态方法中,只能调用静态的方法或属性,如果要调用非静态的则需要new对象
非静态方法中,既可以调用非静态方法或属性,也可以调用静态的方法或属性
生命周期: 1.类的加载(静态结构加载)放在缓存区 --> 2.对象出生(非静态结构加载) --> 3.对象消亡(非静态结构消失) --> 重复二三的情况 --> 内存中不再使用类:类从内存中消亡(静态结构消亡)
静态结构是完全跟类的生命周期同步的,而非静态结构跟对象的生命周期同步的
晚出生的(非静态结构)可以调用早出生的(静态结构),早出生的不能调用晚出生的,因为可能还没有(例如我可以向父辈借钱,但我不能向我的孩子借钱(还没出生))
5.static注意点:
5.1.在静态的方法内,不能使用this关键字,super关键字,因为static方法是随类加载而加载的,this关键字表示当前对象,static方法调用非静态方法时静态结构还没有产生对象,super则是调当前对象的父类,当前对象还没有就更没父类
5.2.关于静态属性和静态方法的使用,都从生命周期的角度去理解
6.开发中,如何确定一个属性是否要声明为static的?
属性是可以被多个对象所共享的,不会随着对象的不同而不同的
开发中,如何确定一个方法是否要声明为static的
操作静态属性的方法,通常设置为static的
工具类中的方法,习惯上声明为static的,不必造对象,直接用类就可以调.如:Math,Arrays.Collections
static修饰的属性,相较于实例变量,有哪些好处?
随着类的加载而加载;早于对象的创建;只要权限允许,可以通过"对象.static属性"的方式进行调用;存放在方法区中的静态域

public class StaticTest {
    public static void main(String[] args) {
        Chinese.nation = "中国";
        // 每new一个对象,他们的属性都各自有一份,当我们修改其中一个对象的属性时,
        // 不会导致另一个对象同样的属性值的修改
        Chinese c1 = new Chinese();
        c1.name = "姚明";
        c1.age = 40;
        c1.nation = "CHM";
        Chinese c2 = new Chinese();
        c2.name = "玛丽";
        c2.age = 50;
        c2.nation = "CHINA";
        System.out.println(c1.nation);
        // 编译不通过
        //Chinese.name = "张三"; // 类不能调实例变量
        c1.eat();
        Chinese.show();
        // 编译不通过
        // Chinese.eat(); // 类不能调用非静态方法
        // Chinese.info();
    }
}

class Chinese {
    String name;
    int age;
    static String nation;
    public void eat() {
        System.out.println("中国人吃中餐");
        // 调用非静态结构
        this.info(); // 谁调info,谁(指哪个对象)就是this
        System.out.println("name: " + name);// 包括调非静态的属性
        // 调用静态结构
        walk();
        // 凡是看到静态结构前没有明确声明的,都是省略了类
        System.out.println("nation: " + nation); // 实际是 Chinese.nation
    }
    public static void show() {
        System.out.println("我是中国人");
        // 不能调用非静态结构
        // 声明周期不够,静态方法随类加载而加载,但是非静态的结构还没加载出来,所以不能调用this和super
        // eat(); 方法前默认有this关键字,只是省略了 ,编译报错
        // name = "tom";
        // 可以调用静态结构
        System.out.println(nation); // 静态方法可以调用静态属性,属性前默认省略了类,实际是 Chinese.nation
    }
    public void info() {
        System.out.println("name: " + name + ", age: " + age);
    }
    public static void walk() {

    }
}
  • static练习: 账户信息
public class AccountTest {
    public static void main(String[] args) {
        Account acct1 = new Account();
        Account acct2 = new Account("qewwf", 2000);
        System.out.println(acct1.toString());
        System.out.println(acct2.toString());
        Account.setInterestRate(0.012);
        Account.setMinMoney(100);
        System.out.println(acct1.getInterestRate()); // 0.012
        System.out.println(acct2.getInterestRate()); // 0.012
        System.out.println(acct1.getMinMoney()); // 100.0
        System.out.println(acct2.getMinMoney()); // 100.0
    }
}
public class Account {
    private int id;
    private String pwd = "000000";
    private double balance;
    private static double interestRate; // 利率
    private static double minMoney; // 最小存钱数
    private static int init = 1001; // 用于自动生成id使用

    public Account() {
        id = init++;
    }

    public Account(String pwd, double balance) {
        this();
        this.pwd = pwd;
        this.balance = balance;
    }
    public int getId() {
        return this.id;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    public String getPwd() {
        return this.pwd;
    }

    public double getBalance() {
        return this.balance;
    }

    public static void setInterestRate(double interestRate) {
        Account.interestRate = interestRate;
    }

    public static double getInterestRate() {
        return Account.interestRate;
    }

    public static void setMinMoney(double minMoney) {
    Account.minMoney = minMoney;
    }

    public static double getMinMoney() {
        return Account.minMoney;
    }
    // 重写Object类的toString方法
    @Override
    public String toString() {
        return "Account{" + "id=" + id + ", pwd='" + pwd + '\'' + ", balance=" + balance + '}';
    }
}
  • 单例设计模式
image.png

单例设计模式:
1.所谓类的单例设计模式,就是采取一定的方法保证在整个的系统中,对某个类只能存在一个对象实例
2.如何实现?
饿汉式 vs 懒汉式
3.区别饿汉式和懒汉式 (面试优先写饿汉式)
饿汉式: 好处: 饿汉式是线程安全的,使用效率高,坏处: 在一开始就把对象造好了,对象加载时间过长
懒汉式: 好处: 延迟对象的创建; 目前写法的坏处: 线程不安全. --> 到多线程内容时,再修改

  • 饿汉式
public class SingletonTest {
    public static void main(String[] args) {
        // 虚拟机只加载一次class文件,而静态的属性或方法也只加载一次,所以只会有唯一一个new的对象
        Bank bank1 = Bank.getInstance();
        Bank bank2 = Bank.getInstance();
        System.out.println(bank1 == bank2); // true
    }
}
// 饿汉式
class Bank {
    // 单例要确保获取到的对象具有唯一性,声明为static,类加载到内存时,就会自动加载
    // 1.私有化构造器
    private Bank(){

    }
    // 2.内部创建类的对象 封装性
    // 该类对象可理解为类的属性,这属性恰好就是该类对象
    // 4.要求此属性也必须声明为静态的
    private static Bank instance = new Bank();
    // 3.提供公共方法,返回类的对象(实例)
    // 当把方法静态化,返回对象报错,因为静态方法只能调静态的结构,所以创建的对象也只能是静态的
    public static Bank getInstance() {
        return instance;
    }
}
  • 懒汉式
public class SingleTest2 {
    public static void main(String[] args) {
        Order order1 = Order.getInstance();
        Order order2 = Order.getInstance();
        System.out.println(order1 == order2);
    }
}
// 懒汉式
class Order{
    // 1.私有化类的构造器
    private Order() {

    }
    // 2.声明当前类的对象,但没有初始化
    // 4.此对象必须声明为static
    private static Order instance = null;

    // 3.声明public,static的返回当前类对象的方法
    // 因为外部类不能通过Order类造对象来调用Order类的方法,而只能通过类去调,所以要方法加上static
    public static Order getInstance() {
        // instance = new Order(); // 写法错误,如果Order类每调一次该方法就会创建一次类的对象,这样就不是唯一一个类对象了
        // 用判断条件来造对象
        if (instance == null) {
            instance = new Order();
        }
        return instance;
    }
}

6.2.理解main方法的语法

  • main()方法的使用说明:
  1. main()方法作为程序的入口
  2. main()方法也是一个普通的静态方法
  3. main()方法可以作为与控制台交互的方式,也可以运行时候传入数据.(之前,使用Scanner从控制台上获取相应类型的变量)
// 每个源文件中只能声明一个public类
public class MainTest {
    // 静态方法只能调静态结构,造了对象后才能调非静态结构
    // 入口方法不需要返回值
    // 形参是String类型的数组,也可以作为跟控制台交互的一种方式
    public static void main(String[] args) { // 入口,要想他作为入口权限就要大
        // 调哪个类的方法,就造那个类的对象
        Main test = new Main();

        Main.main(new String[100]);
    }

    public void show() {
        // 方法里面不能再写方法
        /*public stvoid eat(){

        }*/
    }
}
// 每个类里面可以写各自的main方法
class Main {
    // 作为普通的静态方法出现
    public static void main(String[] args) {
        for (int i = 0; i < args.length; i++){
            args[i] = "args_" + i;
            System.out.println(args[i]);
        }
    }
}

6.3.类的成员之四: 代码块

  • 类的成员之四: 代码块(或初始化块)

1.代码块的作用,用来初始化类,对象
2.代码块如果有修饰的话,只能用static修饰.
3.分类: 静态代码块 vs 非静态代码块
4.静态代码块
内部可以有输出语句
随着类的加载而执行,而且只执行一次;只要当前的类没有重新加载,代码块就不会重新执行
作用: 初始化类的信息
如果一个类中定义了多个静态代码块,则按照声明的先后顺序(从上到下)执行
静态代码块的执行要优先于非静态代码块的执行
静态代码块内只能调用静态的属性,静态的方法,不能调用非静态的结构
5.非静态代码块
内部可以有输出语句
随着对象的创建而执行,更严谨来说,随着构造器的this或super的调用后执行,且优先于构造器内部语句的执行而执行,因为如果有继承父类的情况下,当先执行到父类的时候,父类可能并没有创建对象
每创建一个对象,就执行一次非静态代码块,且优先于构造器的执行,实际上是先进入构造器,且先执行完this或super方法,但并不执行里面的语句,再是执行非静态代玛块,执行完之后才是构造函数语句,所以看起来是先非静态代玛块后构造函数
作用: 可以在创建对象时,对对象的属性等进行初始化
如果一个类中定义了多个非静态代码块,则按照声明的先后顺序(从上到下)执行
非静态代码块内可以调用静态也可以调用非静态

public class BlockTest {
    public static void main(String[] args) {
        String desc = Person.desc;
        System.out.println(desc);
        Person p1 = new Person();
        Person p2 = new Person();
        System.out.println(p1.age);
    }
}

class Person {
    String name;
    int age;
    static String desc = "我是一个人";

    public Person() {
        super();
        System.out.println("我是构造器");
    }

    public Person(String name, int age) {
        this();
        this.name = name;
        this.age = age;
    }
    // 不能够主动调代码块,通常都是自动执行
    // 非static代码块: 随着对象的调用而执行
    {
        System.out.println("hello,block2");
    }
    {
        System.out.println("hello,block1");
        // 可以调用非静态结构
        age = 1;
        eat();
        // 非静态代码块可以调用静态结构
        desc = "我是一个爱学习的人1";
        info();
    }
    // static代码块,随着类的加载而执行
    // 类只加载一次,所以不管类调用多少次,静态代码块也只执行一次
    static{
        System.out.println("hello,static block2");
    }
    static {
        // 可想象成方法体
        System.out.println("hello,static block1");
        // 静态代码块可以调用静态结构
        desc = "我是个爱学习的人";
        info();
        // 静态代码块不能调非静态结构
        // eat();
        // name = "tom";
    }
    public void eat() {
        System.out.println("吃饭");
    }

    public static void info() {
        System.out.println("我是个快乐的人");
    }
    @Override
    public String toString() {
        return "Person:{name=" + name + ", age=" + age + "]";
    }
}
  • 代码块练习:

总结:由父及子,静态先行
执行顺序: 父级静态代码块 --> 子类静态代码块 --> 父类非静态代码块 --> 父类构造器 --> 子类非静态代码块 --> 子类构造器
只要类先加载,就一定先执行静态代码块
子类加载前要加载父类的所有信息,所以首先先执行父类的静态代码块和子类的静态代码块

class Root {
    static {
        System.out.println("Root的静态初始化块");
    }

    {
        System.out.println("Root的普通初始化块");
    }

    public Root() {
        super();
        System.out.println("Root的无参数的构造器");
    }
}

class Mid extends Root {
    static {
        System.out.println("Mid的静态初始化块");
    }

    {
        System.out.println("Mid的普通初始化块");
    }

    public Mid() {
        super();
        System.out.println("Mid的无参数的构造器");
    }

    public Mid(String msg) {
        //通过this调用同一类中重载的构造器
        this();
        System.out.println("Mid的带参数构造器,其参数值:"
                + msg);
    }
}

class Leaf extends Mid {
    static {
        System.out.println("Leaf的静态初始化块");
    }

    {
        System.out.println("Leaf的普通初始化块");
    }

    public Leaf() {
        //通过super调用父类中有一个字符串参数的构造器
        super("尚硅谷");
        System.out.println("Leaf的构造器");
    }
}

public class LeafTest {
    public static void main(String[] args) {
        new Leaf();
        System.out.println();
        new Leaf();
    }
}
  • 代码块练习二:

虽然main方法是作为入口但也是Son类里的普通静态方法,静态方法也得是通过Son类调的,类调之前类就得先加载
所以在执行main方法前就已经先加载了Son类的信息,又因为Son类继承了Father类,
所以子类加载前要加载父类的所有信息,所以首先先输出父类的静态代码块和子类的静态代码块
lic static void main(String[] args) { // 由父及子 静态先行

class Father {
    static {
        System.out.println("11111111111");
    }

    {
        System.out.println("22222222222");
    }
    public Father() {
        System.out.println("33333333333");

    }
}
public class Son extends Father {
    static {
        System.out.println("44444444444");
    }

    {
        System.out.println("55555555555");
    }
    public Son() {
        System.out.println("66666666666");
    }
    // 虽然main方法是作为入口但也是Son类里的普通静态方法,静态方法也得是通过Son类调的,类调之前类就得先加载
    // 所以在执行main方法前就已经先加载了Son类的信息,又因为Son类继承了Father类,
    // 所以子类加载前要加载父类的所有信息,所以首先先输出父类的静态代码块和子类的静态代码块
    public static void main(String[] args) { // 由父及子 静态先行
        System.out.println("77777777777");
        System.out.println("************************");
        new Son();
        System.out.println("************************");
        new Son();
        System.out.println("************************");
        new Father();
    }
}
  • 对属性赋值的顺序:

对属性可以赋值的位置:
1.默认初始化
2.显示初始化/5.在代码块中赋值(并列,不分先后)
3.构造器中初始化
4.有了对象后,通过"对象.属性"或"对象.方法"的方式,进行赋值
执行先后顺序: 1 - 2 / 5 - 3 - 4 ,从后往前覆盖

6.4.final关键字的使用

final只能对属性赋值一次,不能变化了
final:最终的
1.final可以用来修饰的结构: 类,方法,变量
2.final用来修饰一个类: 此类不能被其他类所继承.比如: String类,System类,StringBuffer类
3.final用来修饰方法: 表明此方法不能被重写.比如:Object类中getClass();
4.final用来修饰变量: 此时的"变量"就称为是一个常量
4.1.final修饰属性:可以考虑赋值的位置有: 显式初始化,代码块中初始化,构造器中初始化
如果每个对象的属性值不一样,就在构造器中赋值
如果大家的值都一样,就可以显示显式初始化
如果不是简单的值,而是调了方法,方法可能抛异常,还得去处理,就放到代码块中赋值
4.2.final修饰局部变量: 尤其是用final修饰形参时,表明此形参时一个常量,这时候的常量不是说马上要给他赋值,
还是遵照规则,当调用此方法时,给常量形参赋一个实参,一旦赋值以后,就只能在方法体内用此形参,但不能进行重新赋值
static final用来修饰属性,明确表示属性是不能变的通常一定加final: 全局变量,全局体现在static,随着类的加载而加载,只有一份,通过类来调,final体现在常量的意思

public class FinalTest {
    // final显式初始化
    final int WIDTH= 10;
    final int RIGHT;
    // final int DOWN; // 可能创建多个对象,然后属性可能多次赋值,也有可能不调方法,也就不会被赋值
    // final代码块中初始化
    final int LEFT;
    {
        LEFT = 1;
    }
    // 构造器是独立的
    public FinalTest() {
        RIGHT = 2;
    }
    // 防止调用第二个构造器的时候没有给RIGHT赋值
    public FinalTest(int n) {
        RIGHT = n;
    }
    // 被final修饰的变量必须要有初始值,如果才在方法里赋值就太晚了
    // 所以这种操作不靠谱
    /*public void setDOWN() {

    }*/
    public void doWidth() {
        // width = 20;
    }

    public void show() {
        // final修饰方法体里普通的局部变量
        final int NUM = 10; // 常量
        // num += 20; // 修饰以后不能修改
    }
    // final修饰形参,在调此方法的时候才给方法的形参赋值实参
    // 一旦赋值以后,在方法内只能对变量进行调用,不能再修改
    public void show(final int num) {
        // num = 20; // 编译不通过,修饰后不能再操作
        System.out.println(num);
    }

    public static void main(String[] args) {
        FinalTest test = new FinalTest();
        test.show(10);
    }
}

final class FinalA {

}
/*class B extends FinalA{

}*/

class AA {
    public final void show() {

    }
}
// final修饰符的方法不能被重写
/*class BB extends AA {
    public void show() {

    }
}*/

6.5.抽象类与抽象方法

abstract关键字的使用
1.abstract: 抽象的,可理解为不确定的
2.abstract可以用来修饰的结构: 类,方法 (不确定的)
3.abstract修饰类: 抽象类
此类不能实例化
抽象类中一定提供有构造器,只要是类就一定有构造器,便于子类实例化时使用(设计: 子类对象实例化的全过程)
开发中,都会提供抽象类的子类,让子类对象实例化,完成相关操作
4.abstract修饰方法: 抽象方法
抽象方法只有方法的声明,没有方法体
包含抽象方法的类,一定是一个抽象类.反之,抽象类中可以没有抽象方法的
若子类重写了所有父类中的所有的抽象方法,此子类放可实例化
若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰

abstract使用上的注意点:
1.abstract不能用来修饰: 属性,构造器(构造器只能重载)等结构
2.abstract不能修饰私有方法,因为abstract修饰过的方法,总会有子类需要重写,否则继承abstract方法类的只能是抽象类,
而private修饰的方法可以被子类继承,但不能被子类直接调用和不能被子类重写,所以abstract和private冲突
3.abstract不能修饰静态方法,因为静态方法不能被子类覆盖,也不能被重写
4.abstract不能修饰final的方法,final方法不能被重写
5.abstract不能修饰final的类,final类不能被继承

public class AbstractTest {
    public static void main(String[] args) {
        // 一旦Person类抽象了,就不可实例化
        /*Person p1 = new Person();
        p1.eat();*/
    }
}

abstract class Ctreature {
    public abstract void breath();
}
// Person类有两个抽象方法,一个是自己的,一个是父类的
abstract class Person extends Ctreature{
    String name;
    int age;
    public Person() {

    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /*public void eat() {
        System.out.println("人吃饭");
    }*/
    // 抽象方法
    public abstract void eat();
    public void walk() {
        System.out.println("人走路");
    }
}
// 因为调了父类构造器了,所以就加载了父类,也就可以用父类的属性和方法了
class Student extends Person {
    public Student(String name, int age) {
        super(name, age);
    }
    // 子类要重写所有父类的所有抽象方法
    //子类要重写父类的抽象方法
    public void eat() {
        System.out.println("学生多吃有营养食物");
    }
    // 间接父类抽象方法的重写
    @Override
    public void breath() {
        System.out.println("学生呼吸新鲜空气");
    }
}
  • 抽象类的练习: 基本操作
// 用继承思想,要求类中提供必要的方法进行属性访问
// 在原有继承基础之上,在需要在父类做修改,涉及到抽象类和抽象方法,子类必要的时候重写父类的方法,没重写就也是抽象类
public class EmployeeTest {
    public static void main(String[] args) {
        // 多态和抽象联系起来用,如果多态里有通用型方法,且形参是父类(抽象类)的类型,抽象类本身不能造对象,还不让用多态,抽象就没有意义了
        // 多态性的意义还可以体现抽象类要用
        // 除了Manager还能用Employee声明,Employee类表现为抽象,new的Manager类表现为多态
        Employee manager = new Manager("库克", 1001, 5000, 50000);
        manager.work();
        CommonEmployee commonEmployee = new CommonEmployee();
        commonEmployee.work();
    }
}
// 员工数量太多,从此只造子类的对象
public abstract class Employee {
    private String name;
    private int id;
    private double salary;

    public Employee() {
        super();
    }

    public Employee(String name, int id, double salary) {
        super();
        this.name = name;
        this.id = id;
        this.salary = salary;
    }
    // 每个员工工作方式不同,将方法抽象化
    public abstract void work();
}
// 对于Manager类,他既是员工,还具有奖金(bonus)属性
public class Manager extends Employee{
    private double bonus; // 奖金

    public Manager(double bonus) {
        super();
        this.bonus = bonus;
    }

    public Manager(String name, int id, double salary, double bonus) {
        super(name, id, salary);
        this.bonus = bonus;
    }

    @Override
    public void work() {
        System.out.println("管理员工,提高公司运行效率");
    }
}
public class CommonEmployee extends Employee{
    @Override
    public void work() {
        System.out.println("员工在一线车间生产");
    }
}
  • 创建抽象类的匿名子类的对象
/*
 * 抽象类的匿名子类
 */
public class PersonTest {
    public static void main(String[] args) {
        PersonTest.method(new Student()); // 匿名对象
        Worker worker = new Worker();
        method1(worker); // 非匿名类的非匿名对象
        method1(new Worker()); // 非匿名类的匿名对象
        // 创建了一个匿名子类的有名对象(对象是有个名的): p;相当于用多态的方式赋给了父类的引用
        // 相当于需要用抽象类的一个对象,但又不想创建新的类,因此重写抽象方法就行了
        Person p = new Person() { // new Person() 实际上是父类的子类,只是借父类的名字充当了一下
            // 只能是子类才能重写方法
            @Override
            public void eat() {
                System.out.println("吃东西");
            }

            @Override
            public void breath() {
                System.out.println("好好呼吸");
            }
        };
        method1(p);
        // 创建匿名子类的匿名对象
        method1(new Person() {
            @Override
            public void eat() {
                System.out.println("吃好吃的");
            }

            @Override
            public void breath() {
                System.out.println("好好呼吸新鲜空气");
            }
        });
    }

    public static void method1(Person p) { // 形参是(父类)抽象类的变量,多态的使用
        // 编译调的是父类的方法,实际运行时子类重写的方法
        p.eat();
        p.breath();
    }
    public static void method(Student s) { // 形参是抽象类

    }
}

class Worker extends Person {

    @Override
    public void breath() {

    }

    @Override
    public void eat() {

    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容