一、继承与多态
继承性
class 类A extends 类B {
// 类体
}
类A:子类、派生类
类B:父类、超类、基类
继承性的的体现:类A一旦继承了类B后,子类A中就获得了父类B中声明的所有的、属性、方法
继承性的好处:
- 减少的代码的冗余,提高了代码的复用性
- 便于功能的拓展
- 是多态性实现的前提
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):在子类中可以根据需要对从父类中继承来的方法进行更改。子类对象在调用方法时会覆盖掉父类原有的方法。
约定俗成:子类中叫重写的方法;父类中叫被重写的方法。
重写的要求:
-
方法名
与形参列表
:父类中被重写的方法与子类中重写的方法相同; -
权限修饰符
:子类中重写的方法不小于父类被中被重写的方法;特殊情况:子类不能重写父类中的私有方法。
如果从父类继承过来的方法中调用了私有方法,则子类在没有重写该方法的前提下使用对象调用该方法时,私有方法的调用部分还是父类中的私有方法,非私有方法根据子类中是否进行了重写进行调用。
-
返回值类型
:父类的返回值类型 子类的返回值类型 void void 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方法:
- 私有属性不可以被子类所继承,且在在子类中不可以对父类中的私有属性进行访问;
- 子类父类中的私有属性和方法类似公用的关系;
- 父类中的私有属性只有在父类中才有操作的权限,因此需要在父类中设置get/set方法;
- 若在子类中重写get/set方法时,需要使用
super
调用父类中的get/set方法;- 没有重写的话可以直接调用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()); } }
子类对象实例化过程
多态性
对象的多态性:父类的引用指向子类的对象。
多态的使用:当调用子父类同名同参数的方法时,实际执行的时子类重写父类的方法----虚拟方法的使用。
虚拟方法调用:编译看左边,执行看右边。
//向上转型
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的父类。
面试题:
- 多态是编译时行为还是运行时行为?
运行时行为。证明代码
-
判断输出结果
//考查多态的笔试题目: 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关键字的使用
-
static可用来修饰属性、方法、代码块、内部类
-
使用static修饰属性:静态变量
-
属性:按是否使用static修饰分为:静态属性 & 非静态属性(实例变量)
实例变量:当创建了一个类的多个对象,每个对象都有一套属于自己的非静态属性。当其中的一个对象修改 了其费静态属性后,其他对象中的相同属性值不会发生改变。
静态属性:当创建了一个类的多个对象,多个对象共享一个静态变量。当通过某一个对象修改静态属性时, 会导致其他对象调用该静态属性时发生了改变。
-
说明:
- 静态变量随着类的加载而加载;可以通过
类.静态变量
的方式进行调用。 - 静态变量的加载要早于对象的创建;
- 由于类只会加载一次,则静态变量在内存中只会存在一份:存在方法区的静态域中
- 静态变量随着类的加载而加载;可以通过
-
静态变量的内存解析
-
-
使用static修饰方法:静态方法
- 随着类的加载而加载,可以通过
类.方法
的形式进行调用。 - 静态方法中只能调用静态的方法或静态的属性;非静态方法中都可以调用。
- 随着类的加载而加载,可以通过
static使用注意:
- 在静态的方法内不能使用this关键字、super关键字。
- 关于静态属性和静态方法的使用,可以从生命周期的角度去理解。
static的经验之谈
- 如何确定一个属性是否使用static
- 属性可以被多个对象所共享的,不会随着对象的不同而不同的。
- 需要对所有对象所共享的。
- 类中的常量一般声明为静态的。
- 如何确定一个属性是否使用static
- 操作静态属性的方法为静态方法。
- 工具类中的方法习惯上生命为静态方法。
单例设计模式
-
设计模式:设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。
设计模式的分类:
- 创建型模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
- 结构型模式:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
- 行为型模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式
-
单例模式:通过采取一定的方法保证在整个的软件系统中,对某个类只存在一个对象实例,并且该类中提供一个取得其对象实例的方法。
实现思路:将类的构造器权限设置为private,这样就不能通过new操作符创建外部对象,但是类内部仍然可以产生该类的对象,因此可以调用该类的某个静态方法来返回类内部创建的对象。由于静态方法只可以访问类内的静态属性,所以,指向类内部产生的该类对象的变量也必须定义为静态的。
实现步骤:
- 私有化类的构造器;
- 在类内部创建类的私有对象;
- 提供公共的静态方法返回类的对象;
- 要求创建的类内对象也是静态的;
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 饿汉式:
饿汉式:
好处:是线程安全的。
坏处:对象加载时间过长。
懒汉式:
好处:延迟对象的创建。
坏处:目前写法的线程是不安全的。
-
单例模式的优点:
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
-
应用场景:
举例:java.lang.RunTime
- 网站的计数器,一般也是单例模式实现,否则难以同步。
- 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
- 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
- Application 也是单例的典型应用
- Windows的Task Manager (任务管理器)就是很典型的单例模式
- Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
理解main方法
public static void main(String[] args){}
-
main()方法作为程序的口;
-
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()方法中不可以直接调用非静态的属性和方法,只能通过对象去调用。
-
main()方法作为程序的入口,需要由JVM对其进行调用。因此权限声明为public;
-
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中:
-
运行代码,生成字节码文件;
-
右键选择:Run as ----> Run Configuration
-
在参数框内输入参数(加不加引号都行,默认都是字符串类型)
-
运行
-
-
命令行下
对文件进行编译、运行。在运行时,后面直接加上参数即可。
-
代码块
代码块是类的成员之一,也称为初始化块。
//代码块
{
//代码块内容
}
-
代码块的作用:用来初始化当前的类或对象。
-
代码块只可以使用
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 }
-
非静态代码块会在每创建一个新对象的时候就执行一次;
-
作用:可以在创建对象时对对象的属性进行初始化;
-
非静态代码块也可以定义多个,执行顺序:按照声明的先后顺序执行;
-
非静态代码块中都可以进行调用。
-
-
-
代码块的执行先于构造器的执行
对属性赋值的位置:
- 默认初始化
- 显示初始化
- 构造器初始化
- 使用对象进行初始化
- 对象.属性
- 对象.方法
- 代码块初始化
执行的先后顺序:1 -----> 2 \ 5 -----> 3 -----> 4(2,5谁先写谁先执行)
final关键字
-
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关键字
抽象类:将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
抽象方法:只有方法的声明,没有方法的实现。以分号结束。
-
abstract可以用来修饰类、方法;
-
abstract修饰的类:抽象类
- 抽象类不可以进行实例化;
- 抽象类中一定有构造器,便于子类对象实例化时调用;
- 开发中,会提供抽象类的子类,让子类实例化对象;
-
abstract修饰的方法:抽象方法
- 抽象方法只有方法声明,没有方法体;
- 抽象方法只能定义在抽象类中;
- 子类中必须重写父类中的所有抽象方法;
-
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连接
接口的使用:
-
接口使用
interface
关键字来描述; -
接口和类是并列的两个结构;
-
接口的定义:
-
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.接口方法
调用接口中的方法;
-
-
-
接口中不可以定义构造器,意味着接口不可以进行实例化;
-
java开发中,接口通过让类去**实现(implements)**的方式来使用:
- 如果实现类实现了接口中的所有抽象方法,则该实现类可以实例化;
- 如果实现类没有实现了接口中的所有抽象方法,则该实现类为抽象类;
-
java类可以实现多个接口;
格式:
class A extends B implements C, D, ...
-
接口与接口之间可以实现多继承;
格式:
interface A extends B, C, ...
-
接口的使用体现了多态性
-
接口可以看作是一个规范
面试题:抽象类与接口的异同
代理模式
代理设计就是为其他对象提供一种代理以控制对这个对象的访问。
/*
* 接口的应用:代理模式
*
*/
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();
}
}
内部类
-
在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
-
内部类可以分为:
-
局部内部类(方法内、代码块内、构造器内)
-
成员内部类(静态的、非静态的)
-
一方面作为外部类的成员:
- 调用外部类的结构:
类.this.属性或方法
- 可以使用
static
修饰; - 可以使用四种权限修饰符修饰;
- 调用外部类的结构:
-
另一方面,作为一个类:
- 类内可以定义属性、方法、构造器等;
- 可以使用
final
进行修饰; - 可以使用
abstract
修饰;
-
-
-
思考的问题
-
如何在外部实例化成员内部类
- 静态的成员内部类:使用
外部类.内部类 对象 = 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类主要结构
equals()方法
======符号的回顾:
可以使用在基本数据类型变量或引用数据类型变量之间:
用在基本数据类型变量之间表示比较数值是否相等(两个类型可以不一样,满足自动类型提升的均可);
用在引用数据类型变量之间表示比较地址是否相等(即两个引用是否指向同一个对象实体)。
使用 == 运算符时,两边的对象必须有关系才能进行比较。
equals()方法的使用:
-
是一个方法,而非运算符;
-
只适用于引用数据类型;
-
Object类中equals()源码:
public boolean equals(Object obj) { return (this == obj); }
Object 中定义的 equals() 方法与 == 运算符的作用是相同的。
-
像String、Date、File、包装类等都重写了Object类中的 equals() 方法,重写后比较的是两个对象中的“属性”是否相同。
-
在自定义类中,通常会重写 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; }
- 判断形参对象与该对象是否指向同意内存空间,若是,则为同一个对象,否则跳转2;
- 判断形参对象那个是否是当前类的子类实例,若不是,则为不同对象,否则跳转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”。
-
equals() 方法可以自动生成(推荐)。
面试题:== 和 equals()的区别:
- == 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址
- equals的话,它是属于java.lang.Object类里面的方法,如果该方法没有被重写过默认也是 ==;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中用的比较多,久而久之,形成了equals是比较值的错误观点。
- 具体要看自定义类里有没有重写Object的equals方法来判断。
- 通常情况下,重写equals方法,会比较类中的相应属性是否都相等。
toString()方法
-
输出对象的引用时,默认调用对象的 toString() 方法
-
Object类中 toString() 方法的源码:
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
-
像String、Date、File、包装类等都重写了Object类中的 toString() 方法。使得对象在调用 toString() 方法时显示“实体内容”信息。
-
自定义类也可以重写 toString() 方法,调用此方法时返回对象的“实体内容”。
四、包装类
单元测试
使用单元测试的步骤:
-
选中当前工程 ---- 右键选择:build path ---- 选择:add libraries ---- 选择:JUnit ---- 默认下一步完成;
-
创建java类进行单元测试;
此时的java类要求:
-
此类为公共类
-
提供一个公共的、无参的构造器
-
在此类中声明单元测试方法(命名约束:testXXX)
此时的单元测试方法要求:
- 方法的权限是public
- 返回值类型为void
- 没有形参
-
单元测试方法上需要声明一个
@Test
的注解,并在单元测试类中导入org.junit.Test
-
-
在单元测试方法体内写测试代码;
-
Test双击选中单元测试方法名,右键选中run as ---- JUnit Test
- 测试结果在console中输出。若执行没有异常,则JUnit窗口中为绿条,否则为红条。
![]()
- 在单元测试方法中可以直接调用测试类中的属性,不需要创建对象。
包装类的使用
将基本数据类型封装为类,使其具有类的特征。
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之间的的相互转换
-
基本数据类型、包装类 -----> 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); }
-
String -----> 基本数据类型、包装类:
调用包装类中的parseXxx方法。
@Test public void testStringToAutotype() { String str1 = "123"; Integer in1 = Integer.parseInt(str1); int num = Integer.parseInt(str1);//自动拆箱 }
面试题:
-
下列两段程序的输出结果是否相同。
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类型
-
判断输出结果:
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 }
在Integer类中存在一个内部类IntegerCache,起在运行时提前加载进入内存中。其中cache数组为Integer类型的数组,存放着数值范围从-128~127之间的Integer对象。
当进行自动装箱操作的时候,若范围在-128~127之间,则可以直接取出用(如题:两个1其实取得都是同一个地址里的Integer对象);若不在范围内,则会创建新的对象。
练习
多态性练习
- 判断输出结果:
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
}
}
若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。
对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量
- 编写diamagnetic实现功能
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");
}
}
}
- 根据要求写代码:
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练习
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可以根据需要动态伸缩。
创建Vector对象:Vector v=new Vector();
给向量添加元素:v.addElement(Object obj); //obj必须是对象
取出向量中的元素:Object obj=v.elementAt(0);
注意第一个元素的下标是0,返回值是Object类型的。计算向量的长度:v.size();
若与最高分相差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元。
实验说明:
定义一个Employee类,该类包含:
- private成员变量name,number,birthday,其中birthday 为MyDate类的对象;
- abstract方法earnings();
- toString()方法输出对象的name,number和birthday。
MyDate类包含:
- private成员变量year,month,day ;
- toDateString()方法返回日期对应的字符串:xxxx年xx月xx日
定义SalariedEmployee类继承Employee类,实现按月计算工资的员工处理。该类包括:
- private成员变量monthlySalary;
- 实现父类的抽象方法earnings(),该方法返回monthlySalary值;
- toString()方法输出员工类型信息及员工的name,number,birthday。
参照SalariedEmployee类定义HourlyEmployee类,实现按小时计算工资的员工处理。该类包括:
- private成员变量wage和hour;
- 实现父类的抽象方法earnings(),该方法返回wage*hour值;
- toString()方法输出员工类型信息及员工的name,number,birthday。
定义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;
}
}
接口练习
-
排错
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(); } }
-
排错
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()); } }
-
定义一个接口实现两个对象之间的比较
- 定义一个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 } }