Java编程思想(第四版)学习笔记 第六章---第十章
第六章:访问权限控制6.2Java访问权限修饰词
-
包访问权限
:默认的访问权限(有时也表示成为friendly)
意味着当前包中的所有的其他类对那个成员都有访问权限,但是对于这个包之外的所有类,这个成员却是private。 -
public
:接口访问权限
使用关键字public,意味着public之后紧跟着的成员声明自己对每个人都是可用的。 -
private
:你无法访问
除了包含该成员的类之外,其他任何类都无法访问这个成员。 -
protected
:继承访问权限(extends)
基类的创建者希望有一个特定的成员,把
对它的访问权限赋予派生类而不是所有的类,这就需要protected来完成这一工作,并且protected也提供了宝访问权限,也就是说,相同包内的其他类可以访问protected元素。
7.1 组合语法
初始化:类中域为基本类型时能够自动被初始化为零,但是对象的引用会被初始化为null。
惰性初始化:创建时,不初始化,在使用之前判空,若为null则初始化。
public class Bath{
private String s;
public String toString(){
if(s == null){
s = "Joy";
}
return s;
}
}
7.2 继承语法(extends)
所有的类都是隐式地从Java的标准根类Object进行继承。导出的类会自动获取基类中所有的域和方法。
为了继承,一般的规则是将所有的数据成员都指定为private,将所有的方法都指定为public。(protected成员也可以借助导出类来访问)
无参构造器:基类的构造器总是会被调用,并且在导出类的构造器之前被调用
有参构造器:导出类的有参构造器必须在构造器中的第一句调用基类构造器,否则编译器报错
。
class Game{
Game(int i){
print("Game constructor");
}
}
class BoardGame extends Game{
BoardGame(int i){
super(i);
print("BoardGame constructor");
}
}
public class Chess extends BoardGame{
Chess(int i){
super(i);
print("Chess constructor");
}
public static void main(){
Chess x = new Chess();
}
}
/*
output:
Game constructor
BoardGame constructor
Chess constructor
*/
7.4.2名称屏蔽(重载)
在导出类中重新定义基类中的方法名称,并不会屏蔽基类中的方法。
class Homer{
char doh(char c){
System.out.println("doh(char)");
return 'd';
}
float doh(float f){
System.out.println("doh(float)");
return 1.0f;
}
}
class Milhouse{ }
class Bart extends Homer{
//@Override 编译器提示基类中不包含此类方法 不能覆盖
void doh(Milhouse m){
System.out.println("doh(Milhouse)");
}
}
public class Hide {
public static void main(String[] args) {
Bart b = new Bart();
b.doh(1);
b.doh('x');
b.doh(1.0f);
b.doh(new Milhouse());
}
}
/*Output:
doh(float)
doh(char)
doh(float)
doh(Milhouse)
*/
Java SE5新增加了@Override注解
,它并不是关键字
,但是可以把它当做关键字使用。当你想要覆写某个方法时,可以选择添加这个注解,在你不留心重载
而并非覆写
了该方法时,编译器就会生成一条错误消息。
7.6 protected关键字
可以将某些事物尽可能对这个世界隐藏起来,但仍然允许导出类的成员访问他们。
对于任何继承于此类的导出类
或者位于同一个包内的类
来说,它确实可以访问的。(Protected提佛那个了包内访问权限)
7.7向上转型
由于继承可以确保基类中所有的方法在导出类中也同样有效,所以能够向基类发送的所有信息同样也可以向导出类发送。
如果Instrument类具有一个paly() 方法,那么Wind乐器也将同样具备。这意味着我们可以准确的说Wind对象也是一种类型的INstrument。
class Instrument{
public void play(){};
static void tune(Instrument i){
i.play();
}
}
public class Wind extends Instrument{
public static void main(String[] args) {
Wind flute = new Wind();
Instrument.tune(flute); //Upcasting
}
}
一个方法是在基类中定义的,参数类型是基类,但是可以在子类中使用基类对象调用这个方法,参数类型可以是子类。这时便把子类参数向上转型为基类。(小往大转,总是安全的)
练习16:创建一个名为Amphibian的类。由此继承产生一个称为Frog的类。在基类中设置适当的方法。在main()中创建一个Frog并向上转型至Amphibian
,然后说明所有的方法都可工作。
class Amphibian {
public void moveInWater() {
System.out.println("Moving in Water");
}
public void moveOnLand() {
System.out.println("Moving on Land");
}
}
class Frog extends Amphibian {
}
public class E16_Frog {
public static void main(String args[]) {
Amphibian a = new Frog();
a.moveInWater();
a.moveOnLand();
}
} /* Output: Moving in Water Moving on Land */
到底该用组合还是继承,一个最清晰的判断方法就是是否需要从新类向基类进行向上转型,如果必须向上转型,则继承是必要的,如果不需要,则应当考虑是否需要继承。
7.8 final关键字
7.8.1 final数据
- 一个永不改变的
编译时常量
。 - 一个在运行时被初始化的值,而你
不希望它被改变
。
对于编译器常量这种情况,编译器可以将常量值带入到任何可能用到的计算式中,也就是说可以在编译时执行计算式。这类常量必须是基本数据类型,并且以关键字final表示。在对这个变量进行定义的时候,必须对其进行赋值
。
典型的对常量进行定义的方式:
public static final int VALUE_THREE = 39;
定义为public,则可以用于包之外;定义为static,则强调只有一份;定义为final,则说明它是一个常量。
带有恒定初始值(即编译期常量)的final static 基本类型全用大写字母命名,并且字与字之间用下划线隔开。
当final作用于一个对象时,代表这个变量用于对象的引用,并且只能引用这一个对象,而不能再次进行引用设置。
比如当定义一个final变量a数组,就不能再后面将a指向别的数组。
private final int[] a = {1, 2, 3, 4, 5, 6};
//a = new int[3]; //编译器会报错
练习18:创建一个含有static final域和final域的类,并说明二者之间的区别。
class SelfCounter {
private static int count;
private int id = count++;
public String toString() {
return "SelfCounter " + id;
}
}
class WithFinalFields {
final SelfCounter scf = new SelfCounter();
static final SelfCounter scsf = new SelfCounter();
public String toString() {
return "scf = " + scf + "\nscsf = " + scsf;
}
}
public class E18_FinalFields {
public static void main(String args[]) {
System.out.println("First object:");
System.out.println(new WithFinalFields());
System.out.println("Second object:");
System.out.println(new WithFinalFields());
}
}
/*
Output:
First object:
scf = SelfCounter 1
scsf = SelfCounter 0
Second object:
scf = SelfCounter 2
scsf = SelfCounter 0
*///:~
Because class loading initializes the static final, it has the same value in both instances of WithFinalFields, whereas the regular final’s values are different for each instance.
因为类装入初始化了静态final,所以它在WithFinalFields的两个实例中具有相同的值,而常规final的值对于每个实例是不同的。
空白final
Java允许生成空白的final,所谓空白final是指被声明为final但又为给定初始值的域。无论什么情况下,编译器都确保空白final在使用前必须被初始化。
必须在域的定义处或者每个构造器中用表达式对final进行赋值,这正是final域在使用前总是被初始化的原因所在。
final参数
Java允许在参数列表中以声明的方式将参数指明为final,这意味着你无法在方法中更改参数引用所指向的对象:
class Gizmo{
public void spin(){}
}
public class FinalArguments {
void with(final Gizmo g){
//g = new Gizmo(); //不能给final类型的引用对象指定新的引用
}
void without(Gizmo g){
g = new Gizmo();
g.spin();
}
void f(final int i){
//i++; //final的基本类型变量,值不能被更改
}
int g(final int i){return i + 1;} // 可以被读取到
public static void main(String[] args) {
FinalArguments bf = new FinalArguments();
bf.without(null);
bf.with(null);
}
}
final和private关键字
类中所有的private方法都隐式地指定为final的。由于无法取用private方法,所以也就无法覆盖它。可以对private方法添加final修饰词,但是并不能给该方法增加任何额外的意义。
如果你视图覆盖一个private方法(隐含是final的),似乎是凑效的,而且编译器也不会给出错误信息,但是调用的时候会发现,private方法无法调用。
class WithFinals{
private final void f(){
System.out.println("WithFinals.f()");
}
private void g(){
System.out.println("WithFinals.g()");
}
}
class OverridingPrivate extends WithFinals{
//@Override
private final void f(){
System.out.println("Overriding.f()");
}
private void g(){
System.out.println("Overriding.g()");
}
}
class OverridingPrivate2 extends OverridingPrivate{
public final void f(){
System.out.println("OverridingPrivate2.f()");
}
public void g(){
System.out.println("OverridingPrivate2.g()");
}
}
public class FinalOverridingIllusion {
public static void main(String[] args) {
OverridingPrivate2 op2 = new OverridingPrivate2();
op2.f();
op2.g();
//OverridingPrivate op = op2;
OverridingPrivate op = new OverridingPrivate();
//op.f(); // 提示该方法在类中定义为private
//op.g();
WithFinals wf = op2;
//wf.f();
//wf.g();
}
}
“覆盖”只有在某方法是基类的接口的一部分时才会出现。即,必须能将一个对象向上转型为它的基本类型并调用相同的方法。如果某方法是private,它就不是基类的接口的一部分
。它仅是一些隐藏于类中的程序代码,只不过是具有相同的名称而已。但如果再导出类中以相同的名称生成一个public、protected或包访问权限方法的话,该方法就不会产生在基类中出现的“仅具有相同名称”的情况。此时你并没有覆盖该方法,仅是生成了一个新的方法。由于private方法无法触及而且能有效隐藏
,所以除了把它看成是因为它所归属的类的组织结构的原因而存在外,其他任何事物都不需要考虑到它。
**问题???**
:在练习20中,说使用@Overriding注解可以解决上面的问题。 但是我加了注解之后仍然不能调用,不知道哪里的问题。
7.8.3 final类
当将某个类的整体定义为final时(通过将关键字final置于它的定义之前),就表明了你不打算继承该类,而且也不允许别人这样做。
final类的域可以根据个人的意愿选择
是或不是final类型,如果不是final类型,则仍然可以修改其值。
由于final类禁止继承,所以final类中的所有方法都隐式指定为是final
的,因为无法覆盖它们。
class SmallBrain{
}
final class Dinosaur{
final int i = 7;
int j = 1;
SmallBrain x = new SmallBrain();
void f(){}
}
//class Further extends Dinosaur{} // 提示不可继承
public class Jurassic {
public static void main(String[] args) {
Dinosaur n = new Dinosaur();
n.f();
//n.i = 40; //final类型的域不可更改
System.out.println(n.i);
n.j++;
System.out.println(n.j);
}
}
7.9 初始化及类的加载
一般来说,类的代码在初次使用时才会加载,这通常是指加载发生于创建类的第一个对象之时,但是当访问static域或者static方法时,也会发生加载。
初次使用之处也是static初始化发生之处。所有的static对象和static代码段都会在加载时依程序中的顺序(即,定义类时的书写顺序)而一次初始化。当然定义为static的东西只会被初始化一次。
7.9.1 类的继承与初始化
了解包括继承在内的初始化全过程,以对所有发生的一切有个全局性的把握,是很有益的。
class Insert {
private int i = 9;
protected int j;
Insert(){
System.out.println("i = " + i + ", j = " + j);
j = 39;
}
private static int x1 = printInit("static Insert.x1 initialized");
static int printInit(String s){
System.out.println(s);
return 47;
}
}
public class Beetle extends Insert{
private int k = printInit("Beetle.k initialized");
public Beetle(){
System.out.println("k = " + k);
System.out.println("j = " + j);
}
private static int x2 = printInit("static Beetle.x2 initialized");
public static void main(String[] args) {
System.out.println("Beetle constructor");
Beetle b = new Beetle();
}
}
/*
static Insert.x1 initialized
static Beetle.x2 initialized
Beetle constructor
i = 9, j = 0
Beetle.k initialized
k = 47
j = 39
*/
在Beetle上运行java时,所发生的第一件事情就是试图访问Beetle.main()
(一个static方法),于是加载器开始启动并找出Beetle类的编译代码(在名为Beetle.class的文件中)。对它进行加载的过程中,编译器注意到它有一个基类
(这是由关键字extends得知的),于是它继续进行加载。不管你是否打算产生一个该基类的对象,这都要发生(请尝试将对象创建代码注释掉,以证明这一点)。
如果该基类还有其自身的基类,那么第二个基类就会被加载
,以此类推。接下来基类中的static初始化
(此例中为Insect)即会被执行,然后是一个导出类
,以此类推。这种方式很重要,因为导出类的static初始化可能会依赖基类成员能否正确初始化。
至此为止,必要的类已加载完毕,对象的创建就可以进行了。首先
,对象中所有的基本类型都会被设为默认值
,对象引用被设为null
—这是通过将对象对象内存设为二进制零值而一举生成的。然后
,基类的构造器会被调用
。在本例中,它是被自动调用的。但也可以用super
来指定对基类构造器的调用(正如在Beetle()构造器中的第一步操作)。基类构造器和导出类构造器一样,以相同的顺序来经历相同的过程。在基类构造器完成后,实例变量按其次序被初始化。自后构造器的其余部分被执行
(???构造器和实例变量谁先执行)。
个人总结:首先是执行main方法,加载器开始加载编译代码class文件,由extends发现有基类,先初始化基类的static域,static域初始化之后,开始对象创建,先将对象中所有的基本类型设为默认值(这里的基本类型理解为int i = 0这种的比较好理解,上面例子中的k初始化在构造器之前了),然后是构造器的执行(基类构造器先执行),构造器执行结束,实例变量再按顺序初始化。
第八章:多态多态的作用则是消除类型之间的耦合关系
。
8.1 再论向上转型
演奏乐符:Note(枚举类型)
public enum Note {
MIDDLE_C, C_SHARP, B_FLAT;
}
Wind是一种Instrument,因此可以从Instrument类继承。
class Instrument{
public void play(Note n){
System.out.println("Instrument.play()");
}
}
public class Wind extends Instrument {
public void play(Note n){
System.out.println("Wind.play() " + n);
}
}
public class Music {
public static void tune(Instrument i){
i.play(Note.MIDDLE_C);
}
public static void main(String[] args) {
Wind flute = new Wind();
tune(flute);
}
}
/*OutPut:
Wind.play() MIDDLE_C
*/
Music.tunr()方法接受一个Instrument引用,同时也接受任何导出自Instrument的类。在main() 方法中,当一个Wind引用传递到tune()方法时,就会出现这种情况,而不需要任何类型转换。这样做是允许的----因为Wind从Instrument继承而来,所以Instrument的接口必定存在于wind中。从Wind向上转型到Instrument可能会缩小接口,但不会比Instrument的全部接口更窄。
练习10: 创建一个包含两个方法的基类,在第一个方法中可以调用第二个方法。然后产生一个继承自该基类的导出类,且覆盖基类中的第二个方法。为该导出类创建一个对象,将它向上转型到基类型并调用第一个方法,解释发生的情况。
class TwoMethods {
public void m1() {
System.out.println("Inside m1, calling m2");
m2();
}
public void m2() {
System.out.println("Inside m2");
}
}
class Inherited extends TwoMethods {
public void m2() {
System.out.println("Inside Inherited.m2");
}
}
public class E10_MethodCalls {
public static void main(String args[]) {
TwoMethods x = new Inherited();
x.m1();
}
}
/* Output:
Inside m1, calling m2
Inside Inherited.m2
*///:~
可以看出导出类只覆盖了基类的第二种方法,在向上转型之后,基类对象x执行方法1时,由于导出类为覆盖方法一,则直接执行基类中的方法一,而方法一又调用了方法二,这时导出类中又覆盖了方法二,因此会执行导出类的方法二,而不会执行基类的方法二。对于导出类而言,基类中的方法二已经被覆盖掉了,而方法一作为直接继承过来的方法,可以直接使用。
8.2.4 缺陷:“覆盖”私有方法(private)
public class PrivateOverriding {
private void f(){
System.out.println("private f()");
}
public static void main(String[] args) {
PrivateOverriding po = new Derived();
po.f();
}
}
class Derived extends PrivateOverriding{
public void f(){
System.out.println("public f()");
}
}
/* Output:
private f()
*///:~
这里会发现一个很神奇的东西,输出的结果竟然是基类的方法,而不是导出类的方法。
这是由于private方法被自动认为是final方法,而且对导出类是屏蔽的。因此,在这种情况下,Derived类中的f()方法就是一个全新的方法;既然基类中的方法f() 方法在子类Derived中不可见,因此也不能被重载。
结论就是:只有非private方法才可以被覆盖;但是还需要密切注意覆盖private方法的现象,这时编译器不会报错,但是也不会按照我们所期望的来执行。确切的说,在导出类中,对于基类中的private方法,最好采用不同的名字。
8.2.5 缺陷:域与静态方法
class Super{
public int field = 0;
public int getField(){
return field;
}
}
class Sub extends Super{
public int field = 1;
public int getField(){
return field;
}
public int getSuperField(){
return super.field;
}
}
public class FieldAccess {
public static void main(String[] args) {
Super sup = new Sub();
System.out.println("sup.field = " + sup.field + ", sup.getField = " + sup.getField());
Sub sub = new Sub();
System.out.println("sub.field = " + sub.field + ", sub.getField = " + sub.getField() + ", sub.getSuperField = " + sub.getSuperField());
}
}
/* Output:
sup.field = 0, sup.getField = 1
sub.field = 1, sub.getField = 1, sub.getSuperField = 0
*///:~
当Sub对象转型为Super引用时,任何域访问操作都将由编译器解析,因此不是多态的。
在本例中,为Super.field和Sub.field分配了不同的存储空间。这样,Sub实际上包含两个称为field的域:它自己的域和它从Super处得到的。然而,在引用Sub中的field时所产生的默认域并非Super版本的field域。因此,为了得到Super.field,必须显式地指明super.field.
如果某个方法是静态的,它的行为就不具有多态性
。
class StaticSuper{
public static String staticGet(){
return "Base staticGet()";
}
public String dynamicGet(){
return "Base dynamicGet()";
}
}
class StaticSub extends StaticSuper{
public static String staticGet(){
return "Derived staticGet()";
}
public String dynamicGet(){
return "Derived dynamicGet()";
}
}
public class StaticPolymorphism {
public static void main(String[] args) {
StaticSuper sup = new StaticSub();
System.out.println(sup.staticGet());
System.out.println(sup.dynamicGet());
}
}
/* Output:
Base staticGet()
Derived dynamicGet()
*///:~
8.3 构造器和多态
8.3.1构造器的调用顺序
class Meal{
Meal(){
System.out.println("Meal()");
}
}
class Bread{
Bread(){
System.out.println("Bread()");
}
}
class Chess{
Chess(){
System.out.println("Chess()");
}
}
class Lettuce{
Lettuce(){
System.out.println("Lettuce()");
}
}
class Lunch extends Meal{
Lunch(){
System.out.println("Lunch()");
}
}
class PortableLunch extends Lunch{
PortableLunch(){
System.out.println("PortableLunch()");
}
}
public class Sandwich extends PortableLunch {
private Bread b = new Bread();
private Chess c = new Chess();
private Lettuce l = new Lettuce();
public Sandwich(){
System.out.println("Sandwitch()");
}
public static void main(String[] args) {
new Sandwich();
}
}
/* Output:
Meal()
Lunch()
PortableLunch()
Bread()
Chess()
Lettuce()
Sandwitch()
*///:~
复杂对象调用构造器要遵照下面的顺序:
- 调用基类构造器。这个步骤会不断地反复递归下去,首先是构造这种层次的跟,然后是下一层导出类,等等,直到最底层的导出类。
- 按照声明顺序调用成员的初始化方法。
- 调用导出类构造器的主体。
8.5.2 向下转型和运行时类型识别
向上转型会丢失具体的类型信息,是安全的。
向下转型失败时,会跑出一个类转型异常(ClassCastException)
class Useful{
public void f(){
System.out.println("Useful.f()");
}
public void g(){
System.out.println("Useful.g()");
}
}
class MoreUseful extends Useful{
public void f(){
System.out.println("MoreUseful.f()");
}
public void g(){
System.out.println("MoreUseful.g()");
}
public void u(){
System.out.println("MoreUseful.u()");
}
}
public class RTTI {
public static void main(String[] args) {
Useful[] x = {new Useful(), new MoreUseful()};
x[0].f();
x[1].g();
((MoreUseful)x[1]).u(); // Downcast /RTTI
//((MoreUseful)x[0]).u(); //ClassCastException: char8.Useful cannot be cast to char8.MoreUseful
}
}
/*
Output:
Useful.f()
MoreUseful.g()
MoreUseful.u()*/
第九章:接口
9.1抽象类和抽象方法
抽象类的目的是为它的所有的导出类创建一个通用接口。不同的子类可以用不同的方法会表示此接口。
抽象方法:仅有声明而没有方法体。 abstract void f();
包含抽象方法的类叫做抽象类
,如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的(abstract),否则编译器会报错。
如果一个抽象类继承,并不想创建该新类的对象,那么就必须为基类中的所有抽象方法提供方法定义。如果不那么做(可以选择不做),那么该导出类便也是抽象类,并且编译器会强制我们使用abstract关键字来限定该类。
抽象类中的方法并不需要所有的方法都是抽象的
,仅需将我们需要的方法声明为抽象即可。其他的非抽象方法需要有方法体。
练习2:创建一个抽象类,并验证不能被实例化。
abstract class NoAbstractMethod{
void f(){
System.out.println("f()");
}
}
public class E02_Abstract {
public static void main(String[] args) {
//new NoAbstractMethod(); //编译器会报错,抽象类,不能被实例化
}
}
练习3:创建一个基类,让他包含抽象方法print(),并在导出类中覆盖改方法。覆盖后的方法版本可以打印出导出类中定义的某个整形变量的值。在定义该变量之处,赋予它非零值。在基类的构造方法中调用这个方法。现在,在main() 方法中创建一个导出类对象,然后调用它的print方法。解释这种情况。
abstract class BaseWithPrint {
public BaseWithPrint() {
print();
}
public abstract void print();
}
class DerivedWithPrint extends BaseWithPrint {
int i = 47;
public void print() {
System.out.println("i = " + i);
}
}
public class E03_Initialization {
public static void main(String args[]) {
DerivedWithPrint dp = new DerivedWithPrint();
dp.print();
}
} /* Output:
i = 0
i = 47
*///
The java virtual machine zeroes the bits of the object after it allocates storage, producing a default value for i before any other initialization occurs. The code calls the base-class constructor before running the derived-class initialization, so we see the zeroed value of i as the initial output.
The danger of calling a method inside a constructor is when that method depends on a derived initialization. Before the derived-class constructor is called, the object may be in an unexpected state (in Java, at least that state is defined; this is not true with all languages – C++, for example). The safest approach is to set the object into a known good state as simply as possible, and then perform any other operations outside the constructor.
翻译:java虚拟机在分配存储之后对对象的位进行归零,在任何其他初始化发生之前为i生成一个默认值。代码在运行派生类初始化之前调用基类构造函数,因此我们将i的零值作为初始输出。
在构造函数中调用方法的危险在于该方法依赖于派生的初始化。在调用派生类构造函数之前,对象可能处于意外状态(在Java中,至少定义了该状态;并不是所有的语言都是这样,例如c++)。最安全的方法是尽可能简单地将对象设置为已知的良好状态,然后在构造函数之外执行任何其他操作。
这是第八章中的关于初始化的知识:
初始化的实际过程:
- 在其他任何事物发生之前,将分配给对象的存储空间初始化为二进制的零。
- 如前述那样调用基类构造器。此时,调用被覆盖后的print()方法,由于步骤一的缘故,我们此时会发现i的值为0.
- 按照声明的顺序调用成员的初始化方法。
- 调用导出类的构造器主体。
编写构造器时有一条有效的准则:“用尽可能简单的方法使对象进入正常状态;如果可以的话避免调用其他方法”。在构造器内唯一安全调用的那些方法是基类中的final方法(也适用于private方法,他们自动属于finale方法)。这些方法不能被覆盖,因此也就不会出现上述的问题。
9.4 Java中的多重继承(区分extends和implements的使用)
关键字解释:extends
:继承;指的是同一类型的,接口与接口之间或者基类与子类之间使用,为扩展性。implements
:实现;类实现接口时使用。
使用接口的核心原因:为了能够向上转型为多个基类型(以及由此带来的灵活性)。第二个原因却是与抽象基类相同:防止客户端程序员创建该类的对象,并确保这仅仅建立一个接口。
这就带来了一个问题:我们应该使用接口还是抽象类?
如果要创建不带任何方法定义和成员变量的基类,那么就应该选择接口而不是抽象类。事实上,如果知道某事物应该成为一个基类,那么第一选择应该是使它成为一个接口。
练习13:创建一个接口,并从该接口继承两个接口,然后从后面两个接口多重继承第三个接口。
interface BaseInterface {
void f();
}
interface IntermediateInterface1 extends BaseInterface {
void f();
}
interface IntermediateInterface2 extends BaseInterface {
void f();
}
interface CombinedInterface extends IntermediateInterface1, IntermediateInterface2 {
void f();
}
class CombinedImpl implements CombinedInterface {
public void f() {
System.out.println("CombinedImpl.f()");
}
}
public class E13_Diamond {
public static void main(String[] args) {
new CombinedImpl().f();
}
}
/* Output: CombinedImpl.f() *///:~
一般情况下,只可以将extends用于单一类,但是可以引用多个基类接口。就像上面一样,只需用逗号将接口名一一分开即可。
第十章 内部类看的有点麻木了,暂时先跳过了。。。。。。。。。
标签:Java,final,读书笔记,void,public,---,基类,println,class From: https://blog.51cto.com/linmengmeng/5907343