接口
接口是一种与类相似的结构,只包含常量和抽象方法。它的目的是指明相关或者不相关的多个对象的共同行为。例如,使用正确的接口,可以指明这些对象是可比较的、可食用的以及可克隆的。接口是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。
可以使用Edible接口来明确一个对象是否是可食用的。这需要使用implements关键字让对象的类实现这个接口来完成
package edu.uestc.avatar; /** * 可食用接口 * 在jdk1.8以前,接口中只能包含常量和抽象方法。 * 定义的方法默认就是public abstract * */ public interface Ediable { /** * 所有可食用的都具有怎么吃共同的行为 * 接口中的所有方法自动地属于public。因此public可以省略 */ String howToEat(); }
Animal类定义了sound方法。这是个抽象方法,将被具体的动物类所实现
package edu.uestc.avatar; public abstract class Animal { public abstract String sound(); }
Chicken类实现了Edible接口表明小鸡是可食用的。当一个类实现一个接口时,该类用同样的签名和返回值类型实现定义在接口中的所有方法。小鸡类也继承Animal类并实现sound方法。
package edu.uestc.avatar; public class Chicken extends Animal implements Ediable{ @Override public String sound() { return "老鸡骂小鸡,你是个坏东西,教你咯咯咯,你偏叽叽叽。。。。"; } @Override public String howToEat() { return "用啤酒炸鸡......"; } }
Tiger是动物但不可食用。继承自Animal类。
package edu.uestc.avatar; public class Tiger extends Animal{ @Override public String sound() { return "两只老虎跑的快,跑得快"; } }
Fruit类实现Edible。因为它不实现howToEat方法,所以Fruit类必须为abstract的,Fruit的子类必须实现howToEat方法(Apple类和Orange类)
package edu.uestc.avatar; /** * 实现接口:implements,某个类实现了某个接口,就代表这个是满足这个接口规范的这一类事物 * 要求实现该接口所有的抽象方法 */ public abstract class Fruit implements Ediable{ } package edu.uestc.avatar; public class Orange extends Fruit{ @Override public String howToEat() { return "将orange榨汁"; } } package edu.uestc.avatar; //这儿将Apple换为Banana,道理一样 public class Banana extends Fruit{ @Override public String howToEat() { return "apple..."; } }
测试类
package edu.uestc.avatar; public class EidableTest { public static void main(String[] args) { Object[] instances = {new Tiger(),new Chicken(),new Orange(),new Banana()}; for(Object obj : instances) { if(obj instanceof Ediable) System.out.println(((Ediable)obj).howToEat()); if(obj instanceof Animal) System.out.println(((Animal)obj).sound()); } } }
Comparable接口
现在假设希望使用Arrays类的sort方法对Circle对象数组进行排序,Circle类就必须实现Comparable接口,需要实现里面的compareTo方法。假设希望根据圆的面积进行比较。如果第一个圆的面积小于第二个圆的面积就返回-1,如果相等就返回0,否则返回-1.
package edu.uestc.avatar.demo; public class Circle implements Comparable<Circle>,Cloneable{ private double radius; public Circle(double radius) { this.radius = radius; } public double getRadius() { return radius; } public void setRadius(double radius) { this.radius = radius; } public double getArea() { return Math.PI * radius * radius; } @Override public String toString() { return "Circle [radius=" + radius + ",area: " +getArea()+ "]"; } /** * 比较规则:如果当前对象比circle小,返回小于0的整数,如果相等,返回0,如果大于circle,返回一个大于0的整数 */ @Override public int compareTo(Circle circle) { return this.radius == circle.radius ? 0 : this.radius < circle.radius ? -1 : 1; } /** * 覆盖父类的clone方法,以便子类实例可供调用 */ @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } }
Cloneable接口
当拷贝一个变量时,原始变量和拷贝变量引用同一个对象,也就是说,改变一个变量所引用的对象将会对另一个变量产生影响;如果创建一个对象的新copy,它的初始状态和原始对象一样,但以后将可以各自改变各自的状态,那就需要使用clone方法
Circle copy = circle.clone(); copy.getArea();
不过,事情并没有这么简单。clone方法是Object类的一个protected方法,也就是说,在用户编写的代码中不能直接调用它。只有Employee类才能够克隆Employee对象。这种限制有一定的道理。这里査看一下Object类实现的clone方法。由于这个类对具体的类对象一无所知,所以只能将各个域进行对应的拷贝。如果对象中的所有数据域都属于数值或基本类型,这样拷贝域没有任何问题。但是,如果在对象中包含了子对象的引用,拷贝的结果会使得两个域引用同一个子对象,因此原始对象与克隆对象共享这部分信息。
默认的克隆操作是浅拷贝,它并没有克隆包含在对象中的内部对象。
如果进行浅拷贝会发生什么呢?这要根据具体情况而定。如果原始对象与浅克隆对象共享的子对象是不可变的,将不会产生任何问题。也确实存在这种情形。例如,子对象属于像String类这样的不允许改变的类,也有可能子对象在其生命周期内不会发生变化,既没有更改它们的方法,也没有创建对它引用的方法。
然而,更常见的情况是子对象可变,因此必须重新定义clone方法,以便实现克隆子对象的深拷贝。在列举的示例中,hireDay域属于Date类,这就是一个可变的子对象,
对于每一个类,都需要做出下列判断:
-
默认的clone方法是否满足要求。
-
默认的clone方法是否能够通过调用可变子对象的clone得到修补。
-
是否不应该使用clone。
实际上,选项3是默认的。如果要选择1或2,类必须:
-
实现Cloneable接口。
-
使用public访问修饰符重新定义clone方法。
必须谨慎地实现子类的克隆。
接口与抽象类
接口 | 抽象类 |
不考虑java8中default方法的情况下,接口中是没有实现代码的实现 | 抽象类中可以有普通成员方法 ,并且可以定义变量 |
接口中的方法修饰符号 只能是public
|
抽象类中的抽象方法可以有public ,protected ,default
|
接口中没有构造方法 | 可以有构造方法 |
选择:
1、当我们需要一组规范的方法的时候,我们就可以用接口,在具体的业务中,来对接口进行实现,能达到以不变应对万变,多变的需求的情况我们只需要改变对应的实现类 。
2、如果多个实现类中有者相同可以复用的代码 这个时候就可以在实现类和接口之间,添加一个抽象类,把公共的代码抽出在抽象类中。然后要求不同实现过程的 子类可以重写抽象类中的方法,来完成各自的业务。
接口与回调
回调(callback)是一种常见的程序设计模式。在这种模式中,可以指出某个特定事件发生时应该采取的动作。如在java.swing包中有一个Timer类,可以使用它在给定的事件间隔时发出通告。
如程序中有一个时钟,请求每秒钟获得一个通告,以便更新时钟的画面。定时器需要直到调用哪一个方法,并要求传递的对象实现了java.awt.ActionListner接口.
public interface ActionListener extends EventListener { //回调方法,ActionEvent提供了事件的相关信息 public void actionPerformed(ActionEvent e); }
当到达指定时间间隔时,定时器就调用actionPerformed方法。
案例:每10秒钟打印一条信息”At the tone,the time is ...“
package edu.uestc.avatar.beep; import javax.swing.JOptionPane; import javax.swing.Timer; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.time.LocalDateTime; public class BeepDemo { public static void main(String[] args) { /** * 创建一个定时器 * 每10000毫秒触发定时器,定时器就会调用该事件里的回调方法 */ Timer timer = new Timer(1000, new BeepActionListner()); //启动定时器 timer.start(); JOptionPane.showMessageDialog(null, "退出定时器"); System.exit(0); } } class BeepActionListner implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { System.out.println("At the tone,the time is " + LocalDateTime.now()); Toolkit.getDefaultToolkit().beep(); } }
内部类
内部类就是在类的内部定义的类,为什么需要使用内部类:
1.一个内部类的对象能够访问创建它的对象的实现,包括私有数据。
2. 对于同一个包中的其他类来说,内部类能够隐藏起来。
3.匿名内部类可以很方便的定义回调。
4.使用内部类可以非常方便的编写事件驱动程序
package edu.uestc.avatar; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.time.LocalDateTime; import javax.swing.Timer; public class Talking { private boolean beep; private int interval = 1000; public Talking(boolean beep, int interval) { this.beep = beep; this.interval = interval; } public void start() { TimerPrintActionListener listener = new TimerPrintActionListener(); Timer timer = new Timer(interval, listener); timer.start(); } /** * TimerPrintActionListner位于Talking内部----内部类 */ public class TimerPrintActionListener implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { System.out.println("注意,当前时间:" + LocalDateTime.now()); //内部类可以直接访问外部类的数据 if(beep) Toolkit.getDefaultToolkit().beep(); } } }
内部类的特殊语法规则
内部类有一个外围类的引用,使用外围类的引用语法:OuterClass.this
public void actionPerformed(ActionEvent e) { System.out.println("注意,当前时间:" + LocalDateTime.now()); if(Talking.this.beep) Toolkit.getDefaultToolkit().beep(); }
反过来,可以采用下列语法更加明确地编写内部类对象的构造器:outerObject.new InnerClass(costruction params)
1)成员内部类:定义在类的内部,方法的外部,
A.特点:a.作为类的一个成员,有4个权限修饰符:public (default) protected private
b.作为一个类,可以用abstract、final修饰,也有构造器,也可以在类里定义属性、方法
B.成员内部类的注意事项:
a.非静态成员内部类:
1)创建对象的方式:先有外部类的对象,再通过外部类对象调用内部类的构造器,格式:外部类对象.new 内部类()
2)调内部类的属性,可以用"this."来指明;
调外部类的不同名属性,直接调用即可;
调外部类的同名属性:外部类的类名.this.同名属性:表示外部类的当前对象的属性
3)不能有静态的属性和方法
b.静态成员内部类:
1)创建对象的方式:调用构造器的方式:外部类类名.内部类()
2)可以有非静态的属性和方法
3)静态内部类只能调用外部类的静态属性、方法,不能调用外部类的非静态属性、方法
package edu.uestc.avatar; /** * 只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围对象,可以把内部类声明为static的,以便取消产生的引用 * 查找数字中的最大值和最小值 * @author Adan * */ public class ArrayAlg { public static Pair getMinAndMax(int[] list) { int min = list[0], max = list[0]; for(int i = 1; i < list.length; i++) { if(min > list[i]) min = list[i]; if(max < list[i])max = list[i]; } return new Pair(min, max); } public static class Pair{ private int min; private int max; public Pair(int min,int max) { this.min = min; this.max = max; } public int getMin() { return min; } public int getMax() { return max; } } }
C.成员内部类的优势:成员内部类作为外部类的成员,可以直接访问外部类的私有属性。
2)局部内部类:定义在方法的内部,对于局部内部类我们常常使用一个方法,得到一个接口实现类的对象。局部内部类的优势:通过方法非常方便的得到一个接口实现类的对象。
package edu.uestc.avatar; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.time.LocalDateTime; import javax.swing.Timer; import edu.uestc.avatar.Talking.TimerPrintActionListener; /** * 局部内部类 * 发现:前面的TimerPrintActionListener类只是在Talking类的start()方法内部使用,可以使用局部内部类 * */ public class LocalInnerClassTalking { private boolean beep; private int interval = 1000; public LocalInnerClassTalking(boolean beep, int interval) { this.beep = beep; this.interval = interval; } public void start() { class TimerPrintActionListener implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { System.out.println("注意,当前时间:" + LocalDateTime.now()); //内部类可以直接访问外部类的数据 if(beep) Toolkit.getDefaultToolkit().beep(); } } TimerPrintActionListener listener = new TimerPrintActionListener(); Timer timer = new Timer(interval, listener); timer.start(); } }
注意:匿名内部类通过使用"new 接口(){}"的方式用其隐含实现一个接口或抽象类,实现的部分写在大括号内。
package edu.uestc.avatar; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.time.LocalDateTime; import javax.swing.Timer; public class AnonymousInnerClassTalking { private int interval; private boolean beep; public AnonymousInnerClassTalking(int interval, boolean beep) { this.interval = interval; this.beep = beep; } public void start() { //匿名内部类:new的是实现了ActionListener接口类的实例,该类没有名字 Timer timer = new Timer(interval, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("注意,当前时间:" + LocalDateTime.now()); //内部类可以直接访问外部类的数据 if(beep) Toolkit.getDefaultToolkit().beep(); } }); timer.start(); } }
Java8改进的接口
在jdk1.8以前,接口里只能定义常量(public static final)和抽象方法(public abstract),试想,假设1万个类实现了一个接口,这时候对接口进行了升级,按照jdk1.7的规则,加方法的话只能加
抽象方法,当加完抽象方法之后1万个类瞬间编译报错。因为必须要重写抽象方法,在jdk1.8提出接口更新。
有的时候我们希望1万个类如果有类想升级那么重写,有类的不想升级就别重写了。这时候默认方法方法就来了,用default修饰,默认方法可提供方法实现,而实现该接口的类可以不用实现默认方法
接口中可以定义静态方法,让接口具备了功能, 让接口来调用(通过接口名.方法名调用)
函数式接口
接口中有且仅有一个抽象方法的接口即为函数式接口,可以使用@FunctionalInterface检查定义的接口是否是一个函数式接口。函数式接口可以采用lambda表达式。
示例:Rational类
package edu.uestc.avatar.demo; /** * 有理数:a/b,a表示为分子,b表示为分母,分母不能为0 * @author Adan * */ public class Rational extends Number implements Comparable<Rational>{ private static final long serialVersionUID = 1L; /* * 分子 */ private long numerator = 0; /** * 分母 */ private long denominator = 1; public Rational() { this(0,1); } public Rational(long numerator, long denominator) { this.numerator = numerator; this.denominator = denominator; } public long getNumerator() { return numerator; } public void setNumerator(long numerator) { this.numerator = numerator; } public long getDenominator() { return denominator; } public void setDenominator(long denominator) { this.denominator = denominator; } /** * 两个有理数相加 * @param rational 另一个有理数 * @return 有理数 */ public Rational add(Rational rational) { long n = numerator * rational.denominator + denominator * rational.numerator; long d = denominator * rational.denominator; return new Rational(n, d); } /** * 两个有理数相减 */ public Rational substract(Rational rational) { long n = numerator * rational.denominator - denominator * rational.numerator; long d = denominator * rational.denominator; return new Rational(n, d); } /** * 两个有理数相乘 */ public Rational multiply(Rational rational) { long n = numerator * rational.numerator; long d = denominator * rational.denominator; return new Rational(n, d); } /** * 两个有理数相除 */ public Rational divide(Rational rational) { return multiply(new Rational(denominator,rational.numerator)); } @Override public int compareTo(Rational o) { if(substract(o).numerator == 0) return 0; else if(substract(o).numerator < 0) return -1; else return 1; } @Override public int intValue() { return (int)doubleValue(); } @Override public long longValue() { return (long)doubleValue(); } @Override public float floatValue() { return (float)doubleValue(); } @Override public double doubleValue() { return this.numerator * 1.0 / this.denominator; } @Override public String toString() { if(denominator == 1) return numerator + ""; else if(numerator == 0) return 0 + ""; else if(numerator == denominator) return 1 + ""; else return numerator + "/" + denominator; } @Override public boolean equals(Object obj) { return substract((Rational)obj).getNumerator() == 0; } }
类的设计原则
- 内聚性
- 一致性
- 封装性
- 清晰性
- 完整性
- 实例和静态
- 继承与聚合
- 接口和抽象类
练习:使用接口组装电脑。