4. 面对对象进阶笔记

本文详细介绍了Java的面向对象特性,包括继承、多态、抽象类与接口、内部类、单例模式、异常处理、反射、注解等核心概念。深入探讨了这些特性在实际开发中的应用和最佳实践,帮助读者更好地理解和运用Java的面向对象编程思想。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、继承与多态

继承性

classA extendsB {
   // 类体
}

类A:子类、派生类

类B:父类、超类、基类

继承性的的体现:类A一旦继承了类B后,子类A中就获得了父类B中声明的所有的、属性、方法

继承性的好处:

  1. 减少的代码的冗余,提高了代码的复用性
  2. 便于功能的拓展
  3. 是多态性实现的前提

Note:

  • 子类可以继承父类中的私有属性和方法,但是在子类中不可以直接访问;

    如何理解子类中继承了父类中的私有结构:

    //父类
    class Person{
        private String name;
         public String setName(String name){
            this.name = name;
        }
        public int getName(){
             return this.name;
        }
       }
    
    class Student extends Person{
    }
    
    class Test{
     public static void main(String[] args){
         Student s = new Student();
            s.setName("zbs");
            sysout(s.getName());   //这里为什么可以行的通?
            /* Student对象调用从父类继承过来的getName()方法,方法返回this.name,但是name在子类中不可以被调用???那么是通过什么机制调用到子类中的name的呢?
             */
        }
       }
    

    代码运行不报错,但是Student类中没有显示的定义name属性,说明Student类中有从父类继承来的name的内存空间,只是不可以在子类中直接调用。

  • java只支持单继承和多层继承,不支持多重继承;

方法的重写

方法的重写(override/overwrite):在子类中可以根据需要对从父类中继承来的方法进行更改。子类对象在调用方法时会覆盖掉父类原有的方法。

约定俗成:子类中叫重写的方法;父类中叫被重写的方法。

重写的要求

  • 方法名形参列表:父类中被重写的方法与子类中重写的方法相同

  • 权限修饰符:子类中重写的方法不小于父类被中被重写的方法;

    特殊情况:子类不能重写父类中的私有方法。

    ​ 如果从父类继承过来的方法中调用了私有方法,则子类在没有重写该方法的前提下使用对象调用该方法时,私有方法的调用部分还是父类中的私有方法,非私有方法根据子类中是否进行了重写进行调用。

  • 返回值类型

    父类的返回值类型子类的返回值类型
    voidvoid
    A类A类及其子类
    基本数据类型相同的基本数据类型
    /* 注意:当父类中的被重写的方法的返回值类型为double时,子类中的重写的方法的返回值类型也必须是double,不          可以是int
     * 区别:自动类型提升指的是低级类型的数值可以高级基类型的变量接收,但不代表低级类型是高级类型的子类。
     * ☆下面的写法是错误的!!!!
     */
    class father{
        public double test(){
            return 0.0;
        }
    }
    
    class son extends father{
        public int test(){
            return 0;
        }
    }
    
  • 异常类型:子类中重写的方法不大于父类被中被重写的方法【异常处理

面试题:区别方法的重载与重写:

从编译和运行的角度看:

重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,
这称为“早绑定”或“静态绑定”; 而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体
方法,这称为“晚绑定”或“动态绑定”。

Note:子类和父类中的同名同参数的方法要么都声明为非static的(才考虑重写),要么都声明为static的(不是 重写,静态方法不可以被重写)。

super关键字

使用super关键字可以调用父类中的属性、方法和构造器。

  • 调用属性和方法:

    • 当子类中出现和父类中重名的属性时,子类对象默认调用子类中的属性。使用super.属性可以调用父类中的同名属性;
    • 当子类中对父类中的方法进行重写后,若想调用父类中被重写的方法,可以使用super.方法调用。
  • 调用父类中的构造器:

    • 使用super(形参列表)的形式调用父类中的构造器;

    • 使用super()时和this()一样,必须放在构造器的首行;

    • 在构造其中super()this()两者只能出现其一;

    • 构造器默认会调用空参的super()构造器。

      在定义类的时候最好定义一个空参的构造器。

如何理解私有属性以及get/set方法:

  1. 私有属性不可以被子类所继承,且在在子类中不可以对父类中的私有属性进行访问;
  2. 子类父类中的私有属性和方法类似公用的关系;
  3. 父类中的私有属性只有在父类中才有操作的权限,因此需要在父类中设置get/set方法;
  4. 若在子类中重写get/set方法时,需要使用super调用父类中的get/set方法;
  5. 没有重写的话可以直接调用get/set方法,相当于直接调用父类中的get/set方法;
class Father{
    private String name;
    public Father(){
        this.name = "zbs";
    }
    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return this.name;
    }
}
class Son extends Father{
    public void setName(String name) {
        if (name == "lhp"){
    	super.setName(name);
        }
    }
    
    public String getName() {
    	return super.getName();
    }
}
class Test{
    public static void main(String[] args){
        Son s = new Son();
        s.setname("lhp");
        sysout(s.getname());
    }
}

子类对象实例化过程

image-20210816095444418

多态性

对象的多态性:父类的引用指向子类的对象。

多态的使用:当调用子父类同名同参数的方法时,实际执行的时子类重写父类的方法----虚拟方法的使用。

虚拟方法调用:编译看左边,执行看右边。

//向上转型
Father test= new Son();
/*
 * test是父类的引用,编译时只可以调用父类中定义过的方法,子类中添加的方法不可以调用(完全是由编译器限制)。
 * 当执行的时候,执行的是子类中重写的方法。
 * 但是在内存中实际上是加载了子类中的特有属性和方法的,只是由于编译的原因不可以调用。
 */

多态性的使用前提:

  • 有类的继承关系;
  • 子类中对父类方法进行重写;

属性不存在覆盖,也不存在多态性。具体的属性值看左边。


向下转型

作用:可以实现调用子类特有的属性和方法

使用强制类型转换符:子类对象 = (子类类型)父类对象;

但是强转有风险:类型转换时若两者之间没有子父类关系,就会出现类型转换异常(ClassCastException)。

在使用向下转型前使用instanceof关键字进行判断,返回true表示可以进行向下转型,否则不可以进行向下转型。


instanceof关键字

  • 使用方法:a instanceof A
  • 返回值类型:boolean
  • 作用:判断对象a是否是A的实例

a instanceof A 为 true 且 a instanceof B 为 true,则B为A的父类。

面试题

  1. 多态是编译时行为还是运行时行为?

运行时行为。证明代码

  1. 判断输出结果

    //考查多态的笔试题目:
    public class InterviewTest1 {
    
    	public static void main(String[] args) {
    		Base base = new Sub();
    		base.add(1, 2, 3);
            /*
             * 29行~31行没有:sub_1
             * 29行~31行有:sub_1
             */
    
    		Sub s = (Sub)base;
    		s.add(1,2,3);//sub2
    	}
    }
    
    class Base {
    	public void add(int a, int... arr) {
    		System.out.println("base");
    	}
    }
    
    class Sub extends Base {
    
    	public void add(int a, int[] arr) {
    		System.out.println("sub_1");
    	}
        
    	public void add(int a, int b, int c) {
    		System.out.println("sub_2");
    	}
    
    }
    

    编译器认为可变个数形参和一维数组形参类型相同。

    形参个数确定的优先调用。

二、面对对象高级用法

static关键字

static关键字的使用

  1. static可用来修饰属性、方法、代码块、内部类

  2. 使用static修饰属性:静态变量

    • 属性:按是否使用static修饰分为:静态属性 & 非静态属性(实例变量)

      实例变量:当创建了一个类的多个对象,每个对象都有一套属于自己的非静态属性。当其中的一个对象修改 了其费静态属性后,其他对象中的相同属性值不会发生改变。

      静态属性:当创建了一个类的多个对象,多个对象共享一个静态变量。当通过某一个对象修改静态属性时, 会导致其他对象调用该静态属性时发生了改变。

    • 说明:

      • 静态变量随着类的加载而加载;可以通过类.静态变量的方式进行调用。
      • 静态变量的加载要早于对象的创建;
      • 由于类只会加载一次,则静态变量在内存中只会存在一份:存在方法区的静态域
    • 静态变量的内存解析

      image-20210817191830249
  3. 使用static修饰方法:静态方法

    • 随着类的加载而加载,可以通过类.方法的形式进行调用。
    • 静态方法中只能调用静态的方法或静态的属性;非静态方法中都可以调用。

static使用注意:

  1. 在静态的方法内不能使用this关键字、super关键字。
  2. 关于静态属性和静态方法的使用,可以从生命周期的角度去理解。

static的经验之谈

  • 如何确定一个属性是否使用static
    • 属性可以被多个对象所共享的,不会随着对象的不同而不同的。
    • 需要对所有对象所共享的。
    • 类中的常量一般声明为静态的。
  • 如何确定一个属性是否使用static
    • 操作静态属性的方法为静态方法。
    • 工具类中的方法习惯上生命为静态方法。

单例设计模式

  1. 设计模式:设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。

    设计模式的分类:

    • 创建型模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
    • 结构型模式:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
    • 行为型模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式
  2. 单例模式:通过采取一定的方法保证在整个的软件系统中,对某个类只存在一个对象实例,并且该类中提供一个取得其对象实例的方法。

    实现思路:将类的构造器权限设置为private,这样就不能通过new操作符创建外部对象,但是类内部仍然可以产生该类的对象,因此可以调用该类的某个静态方法来返回类内部创建的对象。由于静态方法只可以访问类内的静态属性,所以,指向类内部产生的该类对象的变量也必须定义为静态的

    实现步骤:

    1. 私有化类的构造器;
    2. 在类内部创建类的私有对象;
    3. 提供公共静态方法返回类的对象;
    4. 要求创建的类内对象也是静态的;
    package zbs.note.singleton;
    
    /**
     * 饿汉式的实现
     */
    public class Hungry {
    	
    	public static void main(String[] args) {
    		//通过调用类的静态方法获取对象
    		Hungry hungry1 = Hungry.getInstance();
    		Hungry hungry2 = Hungry.getInstance();
    		
    		//虽然获取出来了两个对象,但是他们指向的都是同一个对象
    		System.out.println(hungry1 == hungry2);//true
    	}
    	
    	//1. 私有化类的构造器
    	private Hungry() {
    		
    	}
    	
    	//2. 创建类的内部对象
    	//4. 更改类内部对象为静态属性
    	private static Hungry instance = new Hungry();
    	
    	//3. 提供一个公共的、静态的方法返回类内部对象
    	public static Hungry getInstance() {
    		return instance;
    	}	
    }
    
    package zbs.note.singleton;
    
    /**
     * 懒汉式的实现
     * 这种写法存在线程安全问题,在多线程中可修复
     */
    public class Lazy {
    
        public static void main(String[] args) {
            //通过调用类的静态方法获取对象
            Lazy lazy1 = Lazy.getInstance();
            Lazy lazy2 = Lazy.getInstance();
    
            //虽然获取出来了两个对象,但是他们指向的都是同一个对象
            System.out.println(lazy1 == lazy2);
        }
    
        //1. 私有化类的构造器
        private Lazy() {
    
        }
    
        //2. 声明当前类对象,未进行初始化
        //4. 更改类内部对象为静态属性
        private static Lazy instance = null;
    
        //3. 声明公共的、静态的返回当前类对象的方法
        public static Lazy getInstance() {
            if (instance == null) {
                instance = new Lazy();
            }
            return instance;
        }
    
    }
    

    懒汉式 vs 饿汉式:

    饿汉式:

    ​ 好处:是线程安全的。

    ​ 坏处:对象加载时间过长。

    懒汉式:

    ​ 好处:延迟对象的创建。

    ​ 坏处:目前写法的线程是不安全的。

  3. 单例模式的优点:

    由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。

  4. 应用场景:

    举例:java.lang.RunTime

    饿汉式单例模式举例
    • 网站的计数器,一般也是单例模式实现,否则难以同步。
    • 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
    • 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
    • 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
    • Application 也是单例的典型应用
    • Windows的Task Manager (任务管理器)就是很典型的单例模式
    • Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。

理解main方法

public static void main(String[] args){}

  1. main()方法作为程序的口;

  2. main()方法其实是一个普通的静态方法,可以通过类进行调用;

    package zbs.note.mainTest;
    
    public class MainTest {
    	public static void main(String[] args) {
    		Main.main(new String[20]);
    	}
    }
    
    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]);
    		}
    	}
    }
    

    因为main()方法是静态的方法,所以在main()方法中不可以直接调用非静态的属性和方法,只能通过对象去调用。

  3. main()方法作为程序的入口,需要由JVM对其进行调用。因此权限声明为public

  4. String[] args形参的作用:也可以作为与控制台交互的方式;(之前使用Scanner)

    使用方法:

    注意:通过args接受到的参数全部是String类型的。

    package zbs.note.mainTest;
    
    public class MainArgs {
    	public static void main(String[] args) {
    		for (int i = 0; i < args.length; i++) {
    			System.out.println("**********" + args[i]);
    		}
    	}
    }
    
    • eclipse中:

      1. 运行代码,生成字节码文件;

      2. 右键选择:Run as ----> Run Configuration

        image-20210818212848370

      3. 在参数框内输入参数(加不加引号都行,默认都是字符串类型)

        image-20210818213058280
      4. 运行

        image-20210818213236495

    • 命令行下

      对文件进行编译、运行。在运行时,后面直接加上参数即可。

      image-20210818213956848

代码块

代码块是类的成员之一,也称为初始化块。

//代码块
{
    //代码块内容
}
  1. 代码块的作用:用来初始化当前的类或对象。

  2. 代码块只可以使用static进行修饰;

    class Person{
    	//属性
    	String name;
    	int age;
    	static String desc = "Person";
    	
    	//构造器
    	public Person() {
    		super();
    	}
    	
    	public Person(String name, int age) {
    		super();
    		this.name = name;
    		this.age = age;
    	}
    	
    	//代码块
    	//静态代码块
    	static {
    		System.out.println("static block");
    	}
    	//非静态代码块
    	{
    		System.out.println("block");
    	}
    	
    	
    	//方法
    	public void eat() {
    		System.out.println("Person eat");
    	}
    
    	@Override
    	public String toString() {
    		return "Person [name=" + name + ", age=" + age + "]";
    	}
    	
    	public static void info() {
    		System.out.println("i'm a Person!");
    	}
    }
    
    • 静态代码块

      • 内部可以写执行语句;

      • 随着类的加载而执行;

        public static void main(String[] args){
            String desc = Person.dest;
            //输出:static block
        }
        
      • 静态代码块只会执行一次,只在第一次加载代码块的时候执行;

      • 作用:初始化当前类的信息;

      • 静态代码块可以定义多个,执行顺序:按照声明的先后顺序执行;

      • 静态代码块内只能调用静态属性、静态方法,不可以调用非静态的结构。

    • 非静态代码块

      • 内部可以写执行语句;

      • 随着对象的加载而执行;

        public static void main(String[] args){
            Person p = new Person();
            //输出:block
        }
        
      • 非静态代码块会在每创建一个新对象的时候就执行一次;

      • 作用:可以在创建对象时对对象的属性进行初始化;

      • 非静态代码块也可以定义多个,执行顺序:按照声明的先后顺序执行;

      • 非静态代码块中都可以进行调用。

  3. 代码块的执行先于构造器的执行

对属性赋值的位置:

  1. 默认初始化
  2. 显示初始化
  3. 构造器初始化
  4. 使用对象进行初始化
    • 对象.属性
    • 对象.方法
  5. 代码块初始化

执行的先后顺序:1 -----> 2 \ 5 -----> 3 -----> 4(2,5谁先写谁先执行)

final关键字

  1. final可以用来修饰的结构:

    • 类:被final修饰的类不可以作为其他类的父类。

      ​ 如:String类、System类、StringBuffer类……

    • 方法:被final修饰的方法不可以被子类重写。

      ​ 如:Object类中的getClass()方法

    • 变量:被final修饰的变量称为常量。

      • final修饰属性,可以考虑赋值的位置有:显示初始化、代码块中初始化、、构造其中初始化

        class finalTest{
            final int APPARENT = 0;
            final int BLOCK;
            final int CONSTRUCTOR;
            
            {
                BLOCK = 1;
            }
            
            public finalTest(){
                VONSTRUCTOR = 2;
            }
        }
        

        final修饰的属性必须在对象创建之前进行赋值。

      • final修饰局部变量

        • 修饰方法内的局部变量
        • 修饰方法的形参:给final修饰后的形参一旦赋值后只可以进行调用,不可以进行更改。
    • static final修饰的属性:称为全局常量。

abstract关键字

抽象类:将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。

抽象方法:只有方法的声明,没有方法的实现。以分号结束。

  1. abstract可以用来修饰方法

  2. abstract修饰的类:抽象类

    • 抽象类不可以进行实例化;
    • 抽象类中一定有构造器,便于子类对象实例化时调用;
    • 开发中,会提供抽象类的子类,让子类实例化对象;
  3. abstract修饰的方法:抽象方法

    • 抽象方法只有方法声明,没有方法体;
    • 抽象方法只能定义在抽象类中;
    • 子类中必须重写父类中的所有抽象方法;
  4. abstract不可以修饰私有方法静态方法final方法final类

匿名类

关于抽象类的匿名子类。

/* 
 * 创建抽象类Person
 */
package zbs.note.anoymousClass;

public abstract class Person {

	public abstract void eat();
	
	public abstract void walk();
	
}
/* 
 * 创建抽象类Person的匿名子类的对象
 */
package zbs.note.anoymousClass;

public class AnoymousTest {
	public static void main(String[] args) {
        
        //创建了一个匿名子类的对象
		Person p = new Person() {

			@Override
			public void eat() {
				// TODO Auto-generated method stub
				
			}

			@Override
			public void walk() {
				// TODO Auto-generated method stub
				
			}
		};
        
	}
}

模板方法的设计模式(TemplateMethod)

抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。

/*
 * 举例:计算某段代码执行的时间
 */
abstract class Template {
    public final void getTime() {
        long start = System.currentTimeMillis();
        code();
        long end = System.currentTimeMillis();
        System.out.println("执行时间是:" + (end - start));
	}
    
    public abstract void code();
    }
}

class SubTemplate extends Template {
    
    public static void main(String[] args){
        Template temp = new Template();
        temp.getTime();
    }
    
    public void code() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(i);
        }
    }
}

解决的问题:

  • 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
  • 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。

应用场景:

  • 数据库访问的封装

  • Junit单元测试

  • JavaWeb的Servlet中关于doGet/doPost方法调用

  • Hibernate中模板程序

  • Spring中JDBCTemlate、HibernateTemplate等

interface关键字

接口:接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的关系,而接口实现则是 **“能不能”**的关系。

  • 一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果

  • 另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系(子父类关系),仅仅是具有相同的行为特征而已。

    如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接

接口的使用

  1. 接口使用interface关键字来描述;

  2. 接口和类是并列的两个结构;

  3. 接口的定义:

    • JDK7及以前:在接口中只能够定义全局常量和抽象方法

      • 全局常量:public static final (定义时可省略)
      • 抽象方法:public abstract (定义时可省略)
      interface Flyable{
          
          //全局常量
      	public static final int MAX_SPEED = 7900;
          int MIN_SPEED = 0;
          
          //抽象方法
          public abstract void fly();
          void landing();
      }
      
    • JDK8及以后:除了可以定义全局常量和抽象方法外,还可以定义静态方法和默认方法

      • 静态方法:static

        接口中定义的静态方法只可以通过接口调用;

        这样可以将工具类中的静态方法放到接口中,有替换工具类的趋势。

      • 默认方法:default

        通过实现类的对象可以调用接口中的默认方法;

        默认方法可以在实现类中进行重写;

        类优先原则 :如果父类和接口中有同名同参数的方法时,实现类在没有重写此方法的情况下,规定调用父类中的方法。

        接口冲突 :如果实现类实现了多个接口,而多个接口中定义了同名同参数的默认方法,在实现类在没有重写此方法的情况下,程序报错。解决方法:必须在实现类中重写此方法。

        重写方法后可以使用:

        • super.父类方法调用父类中的方法;
        • 接口.super.接口方法调用接口中的方法;
  4. 接口中不可以定义构造器,意味着接口不可以进行实例化;

  5. java开发中,接口通过让类去**实现(implements)**的方式来使用:

    • 如果实现类实现了接口中的所有抽象方法,则该实现类可以实例化;
    • 如果实现类没有实现了接口中的所有抽象方法,则该实现类为抽象类;
  6. java类可以实现多个接口;

    格式:class A extends B implements C, D, ...

  7. 接口与接口之间可以实现多继承;

    格式:interface A extends B, C, ...


  1. 接口的使用体现了多态性

  2. 接口可以看作是一个规范

面试题:抽象类与接口的异同

image-20210821215326554

代理模式

代理设计就是为其他对象提供一种代理以控制对这个对象的访问。

/*
 * 接口的应用:代理模式
 * 
 */
public class NetWorkTest {
	public static void main(String[] args) {
		Server server = new Server();
//		server.browse();
		ProxyServer proxyServer = new ProxyServer(server);
		
		proxyServer.browse();
		
	}
}

interface NetWork{
	
	public void browse();
	
}

//被代理类
class Server implements NetWork{

	@Override
	public void browse() {
		System.out.println("真实的服务器访问网络");
	}

}

//代理类
class ProxyServer implements NetWork{
	
	private NetWork work;
	
	public ProxyServer(NetWork work){
		this.work = work;
	}
	

	public void check(){
		System.out.println("联网之前的检查工作");
	}
	
	@Override
	public void browse() {
		check();
		
		work.browse();
		
    }
}

内部类

  1. 在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类

  2. 内部类可以分为:

    • 局部内部类(方法内、代码块内、构造器内)

    • 成员内部类(静态的、非静态的)

      • 一方面作为外部类的成员:

        • 调用外部类的结构:类.this.属性或方法
        • 可以使用static修饰;
        • 可以使用四种权限修饰符修饰;
      • 另一方面,作为一个类:

        • 类内可以定义属性、方法、构造器等;
        • 可以使用final进行修饰;
        • 可以使用abstract修饰;
  3. 思考的问题

    • 如何在外部实例化成员内部类

      • 静态的成员内部类:使用外部类.内部类 对象 = new 外部类.内部类();的形式来实例化;
      • 非静态的成员内部类:先造出外部类的对象,使用外部类.内部类 对象 = 外部类对象.new 内部类();
    • 如何在成员内部类中区分调用外部类的结构

      class Person{
          String name;
          
          class Dog{
              String name;
              
              public void display(String name){
                  sysout(name);			 //形参
                  sysout(this.name);		 //内部类中的name属性
                  sysout(Person.this.name); //外部类中的name属性
              }
          }
      }
      

      如果想要在内部类的方法中使用外部类方法中的局部变量,则该局部变量必须声明为final类型的变量。

    • 开发中内部类的使用

      class Test{
          
          //返回一个实现了Comparable接口的实现类的对象
          public Comparable<Integer> getComparable(){
              
              //方法一:
              //创建Comparable的实现类
      		class MyComparable implements Comparable<Integer>{
      
      			@Override
      			public int compareTo(Integer o) {
      				// TODO Auto-generated method stub
      				return 0;
      			}
      			
      		}
              
              //返回实现类对象
      		return new MyComparable();
              
              //方法二:
              //创建了一个匿名的实现类的匿名对象
              return new Comparable(){
                  
                  @Override
      			public int compareTo(Integer o) {
      				// TODO Auto-generated method stub
      				return 0;
      			}
                  
              };
          }
      }
      

三、Object类

若没有显示声明一个类的父类,则该类默认继承自java.lang.Object类。

除Object类外,所有类都直接或间接的继承自java.lang.Object类。

Object类主要结构

image-20210816181116071

equals()方法

======符号的回顾:

  1. 可以使用在基本数据类型变量或引用数据类型变量之间:

    用在基本数据类型变量之间表示比较数值是否相等(两个类型可以不一样,满足自动类型提升的均可);

    用在引用数据类型变量之间表示比较地址是否相等(即两个引用是否指向同一个对象实体)。

  2. 使用 == 运算符时,两边的对象必须有关系才能进行比较。

equals()方法的使用:

  1. 是一个方法,而非运算符;

  2. 只适用于引用数据类型;

  3. Object类中equals()源码:

    public boolean equals(Object obj) {
        return (this == obj);
    }
    

    Object 中定义的 equals() 方法与 == 运算符的作用是相同的。

  4. 像String、Date、File、包装类等都重写了Object类中的 equals() 方法,重写后比较的是两个对象中的“属性”是否相同。

  5. 在自定义类中,通常会重写 equals() 方法,对对象中的属性进行比较。

    参考String类中重写的 equals() 方法:

    public boolean equals(Object anObject) {
     if (this == anObject) {
         return true;
     }
     if (anObject instanceof String) {
         String aString = (String)anObject;
         if (!COMPACT_STRINGS || this.coder == aString.coder) {
             return StringLatin1.equals(value, aString.value);
         }
     }
     return false;
    }
    
    public static boolean equals(byte[] value, byte[] other) {
     if (value.length == other.length) {
         for (int i = 0; i < value.length; i++) {
             if (value[i] != other[i]) {
                 return false;
             }
         }
         return true;
     }
     return false;
    }
    
    1. 判断形参对象与该对象是否指向同意内存空间,若是,则为同一个对象,否则跳转2;
    2. 判断形参对象那个是否是当前类的子类实例,若不是,则为不同对象,否则跳转3;
    3. 判断对象内的属性值是否相等:若值为基本数据类型,则使用======;若值为引用数据类型,需要在对应类中实现 equals() 方法。

    equals()方法书写的原则

    • 对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。

    • 自反性:x.equals(x)必须返回是“true”。

    • 传递性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。

    • 一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。

    • 任何情况下,x.equals(null),永远返回是“false”; x.equals(和x不同类型的对象)永远返回是“false”。

  6. equals() 方法可以自动生成(推荐)。


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

  1. == 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址
  2. equals的话,它是属于java.lang.Object类里面的方法,如果该方法没有被重写过默认也是 ==;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中用的比较多,久而久之,形成了equals是比较值的错误观点。
  3. 具体要看自定义类里有没有重写Object的equals方法来判断。
  4. 通常情况下,重写equals方法,会比较类中的相应属性是否都相等。

toString()方法

  1. 输出对象的引用时,默认调用对象的 toString() 方法

  2. Object类中 toString() 方法的源码:

    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
    
  3. 像String、Date、File、包装类等都重写了Object类中的 toString() 方法。使得对象在调用 toString() 方法时显示“实体内容”信息。

  4. 自定义类也可以重写 toString() 方法,调用此方法时返回对象的“实体内容”。

四、包装类

单元测试

使用单元测试的步骤:

  1. 选中当前工程 ---- 右键选择:build path ---- 选择:add libraries ---- 选择:JUnit ---- 默认下一步完成;

    image-20210817101441129

    image-20210817101631564

  2. 创建java类进行单元测试;

    此时的java类要求:

    • 此类为公共类

    • 提供一个公共的、无参的构造器

    • 在此类中声明单元测试方法(命名约束:testXXX)

      此时的单元测试方法要求:

      • 方法的权限是public
      • 返回值类型为void
      • 没有形参
    • 单元测试方法上需要声明一个@Test的注解,并在单元测试类中导入org.junit.Test

  3. 在单元测试方法体内写测试代码;

  4. Test双击选中单元测试方法名,右键选中run as ---- JUnit Test

image-20210817103153853

  • 测试结果在console中输出。若执行没有异常,则JUnit窗口中为绿条,否则为红条。
image-20210817103634051
  • 在单元测试方法中可以直接调用测试类中的属性,不需要创建对象。

包装类的使用

将基本数据类型封装为类,使其具有类的特征。

image-20210817105259942

1. 基本数据类型与包装类的转换

  • 基本数据类型 -----> 包装类:包装类 对象 = 包装类.valueOf(value)

    调用包装类的静态valueOf方法。

    通装箱后暑促的变量都为字符串类型。

    @Test
    public void testBasicToWapper() {
    
        Integer in1 = Integer.valueOf(10);
        System.out.println(in1.toString());//10
        Integer in2 = Integer.valueOf("10");
        System.out.println(in2);//10
    
        Float f1 = Float.valueOf(1.0f);
        System.out.println(f1.toString());//1.0
        Float f2 = Float.valueOf("10.1");
        System.out.println(f2);//10.1
        /* 注意:
    		  * 传入字符串构造对象时必须为纯数字,除Boolean类以外。
    		  * */
    
        Boolean b1 = Boolean.valueOf(true);
        System.out.println(b1);//true
        //Boolean类中传入的只要不是true的大小写情况都为false
        Boolean b2 = Boolean.valueOf("true123");
        System.out.println(b2);//false
        Boolean b3 = Boolean.valueOf("TrUe");
        System.out.println(b3);//true
    }
    
  • 包装类 -----> 基本数据类型:基本数据类型 变量 = 包装类.xxxValue();

    调用包装类的XXXValue。

    包装类不可以进行算术运算。

    @Test
    public void testWapperToBasic() {
        Integer in1 = Integer.valueOf(10);
        int i1 = in1.intValue();
        System.out.println(i1 + 1);
    }
    
  • JDK15新特性:自动装箱、拆箱

    • 自动装箱:基本数据类型的变量可以直接赋值给类对象

      @Test
      public void testAuto() {
          Integer in1 = 10;
          Float f1 = 3.14f;
          System.out.println("in1 = " + in1);
          System.out.println("f1 = " + f1);
      }
      
    • 自动拆箱:包装类对选对象可以自动赋值给基本数据类型的变量。

      public void testAuto() {
          Integer in1 = 10;
          Float f1 = 3.14f;
          System.out.println("in1 + f1 = " + (in1 + f1));
      
      
          int num = in1;
          float f = f1;
          System.out.println("num + f = " + (num + f));
      }
      

      有了自动拆箱后,包装类之间可以直接进行算术运算。

2. 基本数据类型、包装类与String之间的的相互转换

  1. 基本数据类型、包装类 -----> String

    @Test
    public void testAutotypeToString() {
        int num = 10;
        Double d = Double.valueOf(15.0);
    
        //方式1:与字符串做连接运算
        String str1 = num + "";
        //方式2:调用String重载的valueOf()方法
        String str2 = String.valueOf(num);
        String str3 = String.valueOf(d);
    }
    
  2. String -----> 基本数据类型、包装类:

    调用包装类中的parseXxx方法。

    @Test
    public void testStringToAutotype() {
        String str1 = "123";
        Integer in1 = Integer.parseInt(str1);
        int num = Integer.parseInt(str1);//自动拆箱
    }
    

面试题

  1. 下列两段程序的输出结果是否相同。

    Object o1 = true ? new Integer(1) : new Double(2.0);
    System.out.println(o1);//1.0
    
    Object o2;
    if (true)
    	o2 = new Integer(1);
    else
    	o2 = new Double(2.0);
    System.out.println(o2);//1
    

    原因:在三元运算符中,两个表达式的类型会提升为同一类型,即Integer提升为Double类型

  2. 判断输出结果:

    public void method1() {
        Integer i = new Integer(1);
        Integer j = new Integer(1);
        System.out.println(i == j);//false
        
        Integer m = 1;
        Integer n = 1;
        System.out.println(m == n);//true
        
        Integer x = 128;
        Integer y = 128;
        System.out.println(x == y);//false
    }
    

    image-20210817153931341

    在Integer类中存在一个内部类IntegerCache,起在运行时提前加载进入内存中。其中cache数组为Integer类型的数组,存放着数值范围从-128~127之间的Integer对象。

    当进行自动装箱操作的时候,若范围在-128~127之间,则可以直接取出用(如题:两个1其实取得都是同一个地址里的Integer对象);若不在范围内,则会创建新的对象。

练习

多态性练习

  1. 判断输出结果:
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 FieldMethodTest {
    public static void main(String[] args){
        Sub s = new Sub();
        System.out.println(s.count);//20
        s.display();//20
        
        Base b = s;
        // == 对于引用数据类型来讲,比较的是两个引用数据类型的地址值是否相同。
        System.out.println(b == s);//true
        System.out.println(b.count);//10
        //执行的是Sub中的display
        b.display();//20
	} 
}
  • 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。

  • 对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量

  1. 编写diamagnetic实现功能

image-20210816171200862

public class E1Test {
	public static void main(String[] args) {
		E1Test test = new E1Test();
		test.method(new Person());
		System.out.println("---------------------------------");
		test.method(new Student());
		System.out.println("---------------------------------");
		test.method(new Graduate());
	}
	
	public void method(Person e) {
		
		String string = e.getInfo();
		System.out.println(string);
		
		//把范围小的写上面
		if (e instanceof Graduate) {
			System.out.println("a graduate student");
		}
		if (e instanceof Student) {
			System.out.println("a student");
		}
		if (e instanceof Person) {
			System.out.println("a person");
		}
	}
}
  1. 根据要求写代码:image-20210816171449814
public class GeometricObject {

	protected String color;
	protected double weight;
	
	protected GeometricObject(String color, double weight) {
		super();
		this.color = color;
		this.weight = weight;
	}

	public String getColor() {
		return color;
	}

	public void setColor(String color) {
		this.color = color;
	}

	public double getWeight() {
		return weight;
	}

	public void setWeight(double weight) {
		this.weight = weight;
	}
	
	public double findArea() {
		return 0.0;
	}
}
public class Circle extends GeometricObject {
	private double radius;
	
	public Circle(String color, double weight, double radius) {
		super(color, weight);
		this.radius = radius;
	}

	public double getRadius() {
		return radius;
	}

	public void setRadius(double radius) {
		this.radius = radius;
	}
	
	@Override
	public double findArea() {
		return Math.PI * radius * radius;
	}
}

public class MyRectangle extends GeometricObject {

	private double width;
	private double height;
	
	public MyRectangle(String color, double weight, double width, double height) {
		super(color, weight);
		this.width = width;
		this.height = height;
	}

	public double getWidth() {
		return width;
	}

	public void setWidth(double width) {
		this.width = width;
	}

	public double getHeight() {
		return height;
	}

	public void setHeight(double height) {
		this.height = height;
	}
	
	@Override
	public double findArea() {
		return width * height;
	}
}
public class GeometricTest {

	public static void main(String[] args) {
		GeometricTest gtest = new GeometricTest();
		boolean result = gtest.equalsArea(new Circle("red", 0.0, 10), new MyRectangle("yello", 0.0, 10, 10));
		System.out.println(result ? "相等" : "不等");
         
        gtest.displayGeometricObject(new Circle("red", 0.0, 5));

	}
	
	public boolean equalsArea(GeometricObject g1, GeometricObject g2) {
		return g1.findArea() == g2.findArea();
	}
	
	public void displayGeometricObject(GeometricObject g) {
		System.out.println("面积为:" + g.findArea());
	}
}

Object练习

image-20210816222941795

image-20210816223011706

image-20210816223039166

public class GeometricObject {
	protected String color;
	protected double weight;
	
	protected GeometricObject() {
		this.color = "white";
		this.weight = 1.0;
	}
	
	protected GeometricObject(String color, double weight) {
		this.color = color;
		this.weight = weight;
	}

	public String getColor() {
		return color;
	}

	public void setColor(String color) {
		this.color = color;
	}

	public double getWeight() {
		return weight;
	}

	public void setWeight(double weight) {
		this.weight = weight;
	}
}
public class Circle extends GeometricObject{
	private double radius;
	
	public Circle() {
		super();
		this.radius = 1.0;
	}
	
	public Circle(double radius) {
		super();
		this.radius = radius;
	}
	
	public Circle(double radius, String color, double weight) {
		super(color, weight);
		this.radius = radius;
	}

	public double getRadius() {
		return radius;
	}

	public void setRadius(double radius) {
		this.radius = radius;
	}
	
	public double findArea() {
		return Math.PI * radius * radius;
	}
	
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		
		if (obj instanceof Circle) {
			Circle c = (Circle)obj;
			return this.radius == c.radius;
		}
		
		return false;
	}
	
	@Override
	public String toString() {
		return "Circle [radius = " + radius + "]";
	}
}
public class E3Test {
	public static void main(String[] args) {
		Circle c1 = new Circle();
		Circle c2 = new Circle(5, "white", 2.0);
		
		System.out.println("c1和c2的颜色" + (c1.getColor().equals(c2.getColor()) ? "相等" : "不相等"));
		System.out.println("c1和c2的半径" + (c1.equals(c2) ? "相等" : "不相等"));
		
		System.out.println(c1.toString());
		System.out.println(c2.toString());
	}
}

包装类练习

利用Vector代替数组处理:从键盘读入学生成绩(以负数代表输入结束),找出最高分,并输出学生成绩等级。

  • 提示:数组一旦创建,长度就固定不变,所以在创建数组前就需要知道它的长度。而向量类java.util.Vector可以根据需要动态伸缩。
  1. 创建Vector对象:Vector v=new Vector();

  2. 给向量添加元素:v.addElement(Object obj); //obj必须是对象

  3. 取出向量中的元素:Object obj=v.elementAt(0);
    注意第一个元素的下标是0,返回值是Object类型的。

  4. 计算向量的长度:v.size();

  5. 若与最高分相差10分内:A等;20分内:B等;30分内:C等;其它:D等

import java.util.Scanner;
import java.util.Vector;

public class StudentScore {
    private Vector scores;

    public StudentScore() {
        this.scores = new Vector();
    }

    public boolean addScore() {
        Scanner scan = new Scanner(System.in);
        System.out.print("请输入学生成绩(输入负数代表结束):");
        Double score = scan.nextDouble();
        if (score < 0) {
            return false;
        } else {
            scores.addElement(score);
            return true;
        }
    }

    public Double maxGrade() {
        Double max = (Double)this.scores.elementAt(0);
        for (int i = 1; i < this.scores.size(); i++) {
            Double temp = (Double)this.scores.elementAt(i);
            if (temp > max) {
                max = temp;
            }
        }
        return max;
    }

    public void show(Double maxGrade) {
        char level;
        for (int i = 0; i < this.scores.size(); i++) {
            double myScore = ((Double)this.scores.elementAt(i));
            if (maxGrade - myScore <= 10) {
                level = 'A';
            } else if (maxGrade - myScore <= 20) {
                level = 'B';
            } else if(maxGrade - myScore <= 30) {
                level = 'C';
            } else {
                level = 'D';
            }

            System.out.println("Student--" + i + "score is:" + myScore + ", level is " + level);
        }
    }
}
public class StudentScoreTest {
    public static void main(String[] args) {
        StudentScore sc = new StudentScore();

        boolean flag = true;
        while (flag) {
            flag = sc.addScore();
        }
        sc.show(sc.maxGrade());
    }
}

static练习

编写一个类实现银行账户的概念,包含的属性有“帐号”、“密码”、“存款余额”、“利率”、“最小余额”,定义封装这些
属性的方法。账号要自动生成。
编写主类,使用银行账户类,输入、输出3个储户的上述信息。

考虑:哪些属性可以设计成static属性。

public class BankAccount {

    private int bankId;
    private String password = "00000000";
    private double balance;

    public BankAccount() {
        this.bankId = initId++;
    }

    public BankAccount(String password, double balance) {
        this.bankId = initId++;
        this.password = password;
        this.balance = balance;
    }

    public static double intrestRate = 0.51;
    public static double minBalance = 100000;
    private static int initId = 1001;

    public int getBankId() {
        return bankId;
    }

    public String getPassword() {
        return password;
    }

    public double getBalance() {
        return balance;
    }

    public static double getIntrestRate() {
        return intrestRate;
    }

    public static double getMinBalance() {
        return minBalance;
    }

}

public class BankAccountTest {

    public static void main(String[] args) {
        BankAccount account1 = new BankAccount("zbs", 100000);
        BankAccount account2 = new BankAccount("lhp", 300000);

        System.out.println("account1:");
        System.out.println("id = " + account1.getBankId());
        System.out.println("password = " + account1.getPassword());
        System.out.println("balance = " + account1.getBalance());
        System.out.println("rate = " + account1.getIntrestRate());
        System.out.println("minBalance = " + account1.getMinBalance());

        System.out.println("account2:");
        System.out.println("id = " + account2.getBankId());
        System.out.println("password = " + account2.getPassword());
        System.out.println("balance = " + account2.getBalance());
        System.out.println("rate = " + account2.getIntrestRate());
        System.out.println("minBalance = " + account2.getMinBalance());
    }
}

代码块练习

package com.atguigu.java;
class Root{
	static{
		System.out.println("Root的静态初始化块");
	}
	{
		System.out.println("Root的普通初始化块");
	}
	public Root(){
		System.out.println("Root的无参数的构造器");
	}
}
class Mid extends Root{
	static{
		System.out.println("Mid的静态初始化块");
	}
	{
		System.out.println("Mid的普通初始化块");
	}
	public Mid(){
		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(); 
		//new Leaf();
	}
}

输出信息:

Root的静态初始化块
Mid的静态初始化块
Leaf的静态初始化块
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:尚硅谷
Leaf的普通初始化块
Leaf的构造器

类加载的顺序:递归加载父类(从当前类一直向上寻找父类,直到Object类,然后从Object类开加载静态代码块),然后再进行对象的创建。

对象创建时,先执行非静态代码块,然后执行构造器代码。

package zbs.exercise.block;

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");
	}


	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

4

7

2

3

5

6

2

3

5

6

2

3

综合练习1

编写工资系统,实现不同类型员工(多态)的按月发放工资。如果当月出现某个Employee对象的生日,则将该雇员的工资增加100元。

实验说明:

  1. 定义一个Employee类,该类包含:

    • private成员变量name,number,birthday,其中birthday 为MyDate类的对象;
    • abstract方法earnings();
    • toString()方法输出对象的name,number和birthday。
  2. MyDate类包含:

    • private成员变量year,month,day ;
    • toDateString()方法返回日期对应的字符串:xxxx年xx月xx日
  3. 定义SalariedEmployee类继承Employee类,实现按月计算工资的员工处理。该类包括:

    • private成员变量monthlySalary;
    • 实现父类的抽象方法earnings(),该方法返回monthlySalary值;
    • toString()方法输出员工类型信息及员工的name,number,birthday。
  4. 参照SalariedEmployee类定义HourlyEmployee类,实现按小时计算工资的员工处理。该类包括:

    • private成员变量wage和hour;
    • 实现父类的抽象方法earnings(),该方法返回wage*hour值;
    • toString()方法输出员工类型信息及员工的name,number,birthday。
  5. 定义PayrollSystem类,创建Employee变量数组并初始化,该数组存放各类雇员对象的引用。利用循环结构遍历数组元素,输出各个对象的类型,name,number,birthday。当键盘输入本月月份值时,如果本月是某个Employee对象的生日,还要输出增加工资信息

package zbs.exercise.salary;

public abstract class Employee {
	
	private String name;
	private String number;
	private MyDate birthday;

	public Employee(String name, String number, MyDate birthday) {
		super();
		this.name = name;
		this.number = number;
		this.birthday = birthday;
	}

	public abstract double earings();
	
	public abstract double earings(double salary);
	
	@Override
	public String toString() {
		return "name:" + name + ", number:" + number + ", birthday:" + birthday.toDateString();
	}

	public String getName() {
		return name;
	}

	public String getNumber() {
		return number;
	}

	public MyDate getBirthday() {
		return birthday;
	}
}
package zbs.exercise.salary;

public class MyDate {

	private int year;
	private int mounth;
	private int day;
	
	public MyDate(int year, int mounth, int day) {
		super();
		this.year = year;
		this.mounth = mounth;
		this.day = day;
	}

	public String toDateString() {
		return year + "年" + mounth + "月" + day + "日";
	}

	public int getYear() {
		return year;
	}

	public void setYear(int year) {
		this.year = year;
	}

	public int getMounth() {
		return mounth;
	}

	public int getDay() {
		return day;
	}
}
package zbs.exercise.salary;

public class SalariedEmployee extends Employee{
	
	private double monthlySalary;

	public SalariedEmployee(String name, String number, MyDate birthday, double monthlySalary) {
		super(name, number, birthday);
		this.monthlySalary = monthlySalary;
	}
	
	@Override
	public double earings(double salary) {
		return monthlySalary += salary;
	}

	@Override
	public double earings() {
		return monthlySalary;
	}

	@Override
	public String toString() {
		return "SalariedEmployee [" + super.toString() + "]";
	}
}
package zbs.exercise.salary;

public class HourlyEmployee extends Employee{

	private double wage;
	private double hour;
	
	public HourlyEmployee(String name, String number, MyDate birthday, double wage, double hour) {
		super(name, number, birthday);
		this.wage = wage;
		this.hour = hour;
	}

	@Override
	public double earings() {
		return wage * hour;
	}
	
	@Override
	public String toString() {
		return "HourlyEmployee [" + super.toString() + "]";
	}

	@Override
	public double earings(double salary) {
		return wage * hour + 100;
	}
}
package zbs.exercise.salary;

import java.util.Calendar;
import java.util.Scanner;

public class PayrollSystem {

	public static void main(String[] args) {
		Employee[] emps = new Employee[2];
		
		emps[0] = new SalariedEmployee("赵彬胜", "1001", new MyDate(2001, 7, 24), 10000);
		emps[1] = new HourlyEmployee("李海萍", "1002", new MyDate(2001, 12, 3), 12, 200);
		
		for (int i = 0; i < emps.length; i++) {
			System.out.println(emps[i]);
		}
		
		System.out.println("方法一:****************************");
		Scanner scan = new Scanner(System.in);
		int month1 = getMonth(scan);
		Employee emp1 = findBirthday(month1, emps);
		if (emp1 == null) {
			System.out.println("本月没有人过生日。");
		} else {
			System.out.println("这个月有" + emp1.getName() + "的生日,加工资100元。");
			System.out.println(emp1.getName() + "的工资为" + emp1.earings(100));
		}
		
		System.out.println("方法二:****************************");
		Calendar caladar = Calendar.getInstance();
		int month2 = getMonth(caladar);
		Employee emp2 = findBirthday(++month2, emps);
		if (emp2 == null) {
			System.out.println("本月没有人过生日。");
		} else {
			System.out.println("这个月有" + emp2.getName() + "的生日,加工资100元。");
			System.out.println(emp2.getName() + "的工资为" + emp2.earings(100));
		}
	}
	
	public static int getMonth(Scanner scan) {
		System.out.print("请输入一个月份:");
		while (true) {
			int month = scan.nextInt();
			if (month <= 0 || month > 12) {
				System.out.print("请重新输入1~12之间的数值:");
			} else {
				return month;
			}
		}
	}
	
	public static int getMonth(Calendar calendar) {
		return calendar.get(Calendar.MONTH);//一月份为0
	}
	
	public static Employee findBirthday(int month, Employee[] emps) {
		for (int i = 0; i < emps.length; i++) {
			if (month == emps[i].getBirthday().getMounth()) {
				return emps[i];
			}
		}
		return null;
	}
}

接口练习

  1. 排错

    interface A {
    	int x = 0;
    }
    
    class B {
    	int x = 1;
    }
    
    class C extends B implements A {
    	public void pX() {
    //		System.out.println(x);//x不明确
            System.out.println(super.x);// 1
            System.out.println(A.x);// 0
    	}
    	public static void main(String[] args) {
    		new C().pX();
    	} 
    }
    
  2. 排错

    interface Playable {
    	void play();
    }
    
    interface Bounceable {
    	void play();
    }
    
    interface Rollable extends Playable, Bounceable {
    	Ball ball = new Ball("PingPang");
    }
    
    class Ball implements Rollable {
    	private String name;
        
    	public String getName() {
    		return name; 
        }
        
    	public Ball(String name) {
    		this.name = name; 
        }
        
    	public void play() {//算重写了两个接口中的play()方法
    		ball = new Ball("Football");//接口中的全局变量不可以修改
    		System.out.println(ball.getName());
    	} 
    }
    
  3. 定义一个接口实现两个对象之间的比较

    • 定义一个Circle类,声明redius属性,提供getter和setter方法
    • 定义一个ComparableCircle类,继承Circle类并且实现CompareObject接口。
    • 在ComparableCircle类中给出接口中方法compareTo的实现体,用来比较两个圆的半径大小。
    • 定义一个测试类InterfaceTest,创建两个ComparableCircle对象,调用compareTo
      方法比较两个类的半径大小。
    interface CompareObject{
    	public int compareTo(Object o); //若返回值是 0 , 代表相等; 若为正数,代表当前对象大;负数代表当前对象小
    }
    
    package zbs.exercise.compareable;
    
    public interface CompareObject {
    	public int compareTo(Object o);
    }
    
    package zbs.exercise.compareable;
    
    public class Circle {
    
    	private double radius;
    
    	public Circle() {
    		super();
    	}
    
    	public Circle(double radius) {
    		super();
    		this.radius = radius;
    	}
    
    	public double getRadius() {
    		return radius;
    	}
    
    	public void setRadius(double radius) {
    		this.radius = radius;
    	}
    	
    }
    
    package zbs.exercise.compareable;
    
    public class ComparableCircle extends Circle implements CompareObject{
    	
    	public ComparableCircle() {
    		super();
    	}
    	
    	public ComparableCircle(double radius) {
    		super(radius);
    	}
    
    	@Override
    	public int compareTo(Object o) {
    		if (this == o) {
    			return 0;
    		}
    		if (o instanceof ComparableCircle) {
    			ComparableCircle circle = (ComparableCircle)o;
                //方法一:
    			if (this.getRadius() > circle.getRadius()) {
    				return 1;
    			} else if (this.getRadius() < circle.getRadius()){
    				return -1;
    			} else {
    				return 0;
    			}
                //方法二:
                //当属性声明为包装类时,可以直接调用包装类中的方法
    			return this.getRadius().compareTo(circle.getRadius());
    		} else {
    //			return 0;
    			throw new RuntimeException("传入的数据类型不匹配");
    		}
    	}
    
    }
    
    package zbs.exercise.compareable;
    
    public class ComparableCircleTest {
    
    	public static void main(String[] args) {
    		ComparableCircle circle1 = new ComparableCircle(5);
    		ComparableCircle circle2 = new ComparableCircle(5.1);
    		
    		System.out.println(circle1.compareTo(circle2));// -1
    		System.out.println(circle2.compareTo(circle1));//  1
    	}
    }
    
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值