美文网首页
Java 编程思想笔记:Learn 6

Java 编程思想笔记:Learn 6

作者: 智勇双全的小六 | 来源:发表于2018-05-03 11:47 被阅读0次

    第9章 接口

    接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法。

    9.1 抽象类和抽象方法

    基类往往只是定义函数签名,并没有方法的具体实现。

    因为基类是为了建立通用接口,不同的子类可以用不同的方法表示此接口。

    那么,这种基类就被称为抽象类。

    在 Java 中提供一个叫做 “抽象方法” 的机制,这种方法是不完整的方法,仅有声明而没有方法体。

    如,abstract void f()

    包含抽象方法的类叫做抽象类,如果一个类包含一个或多个抽象方法,该类必须限定为抽象的。

    抽象类不能实例化,强行实例化会编译出错。

    抽象类可以继承,如果子类不对父类的抽象方法进行重写,那么子类也是抽象类,如果子类对抽象类进行重写,那么子类就是一般的类。

    // 抽象类只需要包含抽象方法,抽象方法只有方法签名;
    // 抽象类也可以像普通类一样,包含属性和普通方法;
    abstract class Instrument4{
        private int i;
        public abstract void play(String n);
        public String what() {return "Instrument";}
        public abstract void adjust();
    }
    
    // 实现抽象类的子类只需要重写父类的抽象方法就好,不必重写所有方法
    class Wind4 extends Instrument4{
        public void play(String n){
            System.out.println("Wind4 play");
        }
        public void adjust(){
            System.out.println("adjust");
        }
    }
    
    class Percussion extends Instrument4 {
        @Override
        public void play(String n) {
            System.out.println("Percussion play");
        }
    
        @Override
        public void adjust(){
            System.out.println("adjust");
        }
    }
    
    
    class Stringed extends Instrument4{
        @Override
        public void play(String n){
            System.out.println("Stringed play");
        }
    
        @Override
        public void adjust(){
            System.out.println("adjust");
        }
    }
    
    public class Music4 {
        private static void tune(Instrument4 i){
            String n = "111";
            i.play(n);
        }
        
        static void tuneAll(Instrument4[] e){
            for(Instrument4 i : e){
                tune(i);
            } 
        }
        
        public static void main(String[] args){
        // 使用基类作为参数类型,就可以调用所有子类。
            Instrument4[] orchestra = {
                    new Wind4(),
                    new Percussion(),
                    new Stringed()
            };
            tuneAll(orchestra);
        }
    }
    

    9.2 接口

    较之于抽象类,接口中只能定义方法签名。所以说接口是完全的抽象类。

    接口不仅提供了一个极度抽象的类,而且它允许人们通过创建一个能够被向上转型为多种基类的类型,来实现多继承。
    也就是说,如果一个方法把接口作为参数,相当于所有实现该接口的类都可以当作此方法的参数来运算,这就有点像动态语言中的多继承。

    把上一节的例子修改为接口,

    interface  Instrument4{
          void play(String n);
          void adjust();
    }
    
    class Wind4 implements Instrument4{
        @Override
        public void play(String n){
            System.out.println("Wind4 play");
        }
    
        @Override
        public void adjust(){
            System.out.println("adjust");
        }
    }
    
    class Percussion implements Instrument4 {
        @Override
        public void play(String n) {
            System.out.println("Percussion play");
        }
    
        @Override
        public void adjust(){
            System.out.println("adjust");
        }
    }
    
    
    class Stringed implements Instrument4{
        @Override
        public void play(String n){
            System.out.println("Stringed play");
        }
    
        @Override
        public void adjust(){
            System.out.println("adjust");
        }
    }
    
    public class Music4 {
        private static void tune(Instrument4 i){
            String n = "111";
            i.play(n);
        }
    
        static void tuneAll(Instrument4[] e){
            for(Instrument4 i : e){
                tune(i);
            }
        }
    
        public static void main(String[] args){
            Instrument4[] orchestra = {
                    new Wind4(),
                    new Percussion(),
                    new Stringed()
            };
            tuneAll(orchestra);
        }
    }
    

    由此可见,接口是彻底的抽象类,把 Instrument4 中的属性和普通方法去掉,Instrument4 就变成一个接口。

    9.3 完全解藕

    9.3.1 . java 策略设计模式

    利用继承实现的策略模式,Upcase / DownCase / Splitter 都是Processor的子类,在 Apply 中 Processor 是参数,通过传递 Processor 不同的子类,从而 Apply.process() 有了不同的行为。

    策略模式就是通过传递不同的参数(实现接口的类),使得调用函数表现出不同的行为。

    import java.util.Arrays;
    
    class Processor{
        public String name(){
            return this.getClass().getName();
        }
        Object process(Object input){
            return input;
        }
    }
    
    class Upcase extends Processor{
        String process(Object input){
            return ((String) input).toUpperCase();
        }
    }
    
    class Downcase extends Processor{
        String process(Object input){
            return ((String) input).toLowerCase();
        }
    }
    
    class Splitter extends Processor{
        String process(Object input){
            return Arrays.toString(((String) input).split(" "));
        }
    }
    
    public class Apply {
        public static void process(Processor p, Object s){
            System.out.println("Using Processor " + p.name());
            System.out.println(p.process(s));
        }
    
        public static String s = "this is a good start";
    
        public static void main(String[] args){
            process(new Upcase(), s);
            process(new Downcase(), s);
            process(new Splitter(), s);
        }
    }
    

    9.3.2. java 适配器模式

    class Processor2{
        public String name(){
            return this.getClass().getName();
        }
        Object process(Object input){
            return input;
        }
    }
    
    class Upcase2 extends Processor2{
        String process(Object input){
            return ((String) input).toUpperCase();
        }
    }
    
    class Downcase2 extends Processor2{
        String process(Object input){
            return ((String) input).toLowerCase();
        }
    }
    
    class Splitter2 extends Processor2{
        String process(Object input){
            return Arrays.toString(((String) input).split(" "));
        }
    }
    

    假如现在有一个其他的类 Process2,Apply.process() 也想调用它。但是这个类 Process2 不是自己的代码,或者我不知道源码,不能修改。这时候就要:

    1. 修改Process 为一个接口,使得任何实现接口的类都能被 Apply.process() 调用。
    2. 做一个适配器,使得 Process2 在不改动代码的情况下,能够适配 Apply.process() 。
    interface Processor{
      String name();
      Object process(Object input);
    }
    

    适配器

    public class Apply2Adapter implements Processor{
    
        private Processor2 processor2;
    
        Apply2Adapter(Processor2 processor2){
            this.processor2 = processor2;
        }
    
        @Override
        public String name(){
            return processor2.getClass().getName();
        }
    
        @Override
        public Object process(Object input){
            return processor2.process(input);
        }
    }
    

    9.4 Java 中的多重继承

    接口不仅仅只是一种更加纯粹形式的抽象类,它的目标比这要高。因为接口是根本没有任何具体实现的——也就是说,没有任何与接口相关的存储; 因此,也就无法阻止多个接口的组合。

    一个类可以继承多个接口,所有的接口名都置于 implements 关键字之后,用逗号将它们一一隔开。可以继承任意多个接口,并可以向上转型为每个接口,每一个接口都是一个独立类型。

    需要注意的是,当一个类又继承又实现接口时,必须将继承类放在前面,实现的接口放到后面。

    package com.zzjack.rdsapi_demo.javathought;
    
    
    interface CanFight{
        void fight();
    }
    
    interface CanSwim{
        void swim();
    }
    
    interface CanFly{
        void fly();
    }
    
    class ActionCharacter{
        public void fight(){}
    }
    
    class Hero extends ActionCharacter implements CanFight,
            CanSwim, CanFly{
        public void swim(){}
        public void fly(){}
    }
    
    public class Adventure {
        public static void t(CanFight x){
            x.fight();
        }
        
        public static void u(CanSwim s){
            s.swim();
        }
        
        public static void v(CanFly f){
            f.fly();
        }
        
        public static void w(ActionCharacter x){
            x.fight();
        }
        
        public static void main(String[] args){
            Hero h = new Hero();
            t(h);
            u(h);
            v(h);
            w(h);
        }
    }
    

    接口和抽象类的使用场景

    1. 为了能够向上转型为多个基类型,这样就非常灵活;
    2. 与使用抽象基类相同,防止客户端程序员创建该类的对象,并确保这仅仅是建立一个端口。
      接口和抽象类的使用场景?
      如果要创建不带任何方法定义和成员变量的积累,那么就该选择成为接口而不是抽象类;

    9.5 通过继承来扩展接口

    接口可以继承,而且可以通过继承在新接口中组合数个接口。

    extends 这个关键词,只能用于单一类,但是可以引用多个基类接口,只需要用逗号一一隔开就好。

    interface Monster{
        void menace();
    }
    
    interface DangerousMonster extends Monster{
        void destory();
    }
    
    interface Lethal{
        void kill();
    }
    
    class DragonZilla implements DangerousMonster{
        public void menace(){}
        public void destory(){}
    }
    
    interface Vampire extends DangerousMonster, Lethal{
        void drinkBlood();
    }
    
    public class VeryBadVampire implements Vampire{
        public void menace(){}
        public void destory(){}
        public void kill(){}
        public void drinkBlood(){}
    }
    

    组合接口时命名冲突

    interface I1 {
        void f();
    }
    
    interface I2{
        int f(int i);
    }
    
    interface I3{
        int f();
    }
    
    class C{
        public int f(){
            return 1;
        }
    }
    
    class C2 implements I1,I2{
        public void f(){}
        
        public int f(int i){
            return i;
        }
    }
    
    class C3 extends C implements I2{
        public int f(int i){
            return 1;
        }
    }
    
    class C4 extends C implements I3{
        public int f(){
            return 1;
        }
    }
    
    
    class C5 extends C implements I1{
        public void f(){} 
    }
    

    打算组合的接口中,如果组合的接口中有的方法签名相同,就会造成错误。

    9.6 适配接口

    接口最吸引人的原因之一就是允许同一个接口具有多个不同的具体实现。在简单的情况中,它的体现形式通常是一个接受接口类型的方法,而该接口的实现和向该方法传递的对象则取决于方法的使用者。

    因此,接口的一种常见用法就是前面提到的策略设计模式,此时你编写一个执行某些操作的方法,而该方法将接受一个同样是你指定的接口。

    public class RandomDoubles {
        private static Random rand = new Random(47);
    
        public double next(){
            return rand.nextDouble();
        }
    
        public static void main(String[] args){
            RandomDoubles randomDouble = new RandomDoubles();
            for(int i=0; i < 7; i++){
                System.out.print(randomDouble.next() + " ");
            }
        }
    }
    

    使用适配器模式,被适配的类可以通过继承和实现 Readable 接口来创建。通过使用 interface 关键字提供的伪多重继承机制,可以生成既是 RanddomDouble 又是 Readable 的新类:

    import java.nio.CharBuffer;
    import java.util.Scanner;
    
    public class AdaptedRandomDoubles extends RandomDoubles implements Readable{
        private int count;
        public AdaptedRandomDoubles(int count){
            this.count = count;
        }
    
        public int read(CharBuffer cb){
            if(count-- == 0){
                return -1;
            }
            String result = Double.toString(next()) + " ";
            cb.append(result);
            return result.length();
        }
    
        public static void main(String[] args){
            Scanner s = new Scanner(new AdaptedRandomDoubles(47));
            while (s.hasNextDouble()){
                System.out.print(s.nextDouble() + " ");
            }
        }
    }
    

    9.7 接口中的域

    因为放入接口的任何域都自动是 static 和 final 的,所以接口就成为了一中很便捷的用来创建常量组的工具。在没有枚举类型之前,经常会看到如下代码:

    public interface Months{
      int JANUARY=1;
      int FEBRUARY = 2;
      ...
    }
    

    在接口中定义的域不是 “空 final”,但是可以被非常量表达式初始化

    public interface RandVals {
        Random RANDOM = new Random(47);
        int RANDOM_INT = RANDOM.nextInt(47);
        long RANDOM_LONG = RANDOM.nextLong() * 10;
        float RANDOM_FLOAT = RANDOM.nextLong() * 10;
        double RANDOM_DOUBLE = RANDOM.nextDouble() * 10;
    }
    

    在接口中,也可以定义一些常量,这些常量会被初始化。

    9.8 嵌套接口

    class A{
        interface B{
            void f();
        }
    
        public class BImp implements B{
            @Override
            public void f() {
    
            }
        }
    
        public class BImp2 implements B{
            @Override
            public void f(){}
        }
    
        public interface C{
            void f();
        }
    
        public class CImp implements C{
            public void f(){}
        }
    
        private interface D{
            void f();
        }
    
        private class DImp implements D{
            @Override
            public void f() {
    
            }
        }
    
        public class DImp2 implements D{
            @Override
            public void f(){
    
            }
        }
    
        public D getD(){
            return new DImp();
        }
    
        private D dRef;
    
        public void  reveive(D d){
            dRef = d;
            dRef.f();
        }
    }
    
    interface E{
        interface G{
            void f();
        }
    
        public interface H{
            void f();
        }
    
        void g();
    
        // can not be private within an interface
    //    private interface i {}
    }
    
    
    public class NestingInterfaces {
        public class BImp implements A.B{
            public void f() {}
        }
    
        class CImp implements A.C{
            public void f() {}
        }
    
        // can not implement a private interface 
    //    class DImp implements A.D {}
    }
    

    从此例可以看出,interface 可以有 public 和 包访问 两种权限。

    9.9 接口与工厂

    接口是实现多种集成的途径,而生成遵循某个接口的对象的典型方法就是工厂方法设计模式。这与直接调用构造器不同,我们在工厂对象上调用的是创建方法,而该工厂对象将生成接口的某个实现的对象。理论上,通过这种方式,我们的代码将完全与接口的实现分离,这就使得我们可以透明地将某个实现替换称为另一个实现。下面的实例展示了工厂方法的结构:

    interface Service{
        void method1();
        void method2();
    }
    
    interface ServiceFactory{
        Service getService();
    }
    
    class Implementation1 implements Service{
        Implementation1() {}
    
        @Override
        public void method1() {
            System.out.print("Implementation1 method1");
        }
    
        @Override
        public void method2(){
            System.out.print("Implementation1 method2");
        }
    }
    
    class Implementation1Factory implements ServiceFactory{
        public Service getService(){
            return new Implementation1();
        }
    }
    
    class Implementation2 implements Service{
        Implementation2() {}
    
        @Override
        public void method1() {
            System.out.print("Implementation2 method1");
        }
    
        @Override
        public void method2() {
            System.out.print("Implementation2 method2");
        }
    }
    
    class Implementation2Factory implements ServiceFactory{
        public Service getService(){
            return new Implementation2();
        }
    }
    
    public class Factories {
        public static void serviceConsumer(ServiceFactory fact){
            Service s = fact.getService();
            s.method1();
            s.method2();
        }
    
        public static void main(String[] args){
            serviceConsumer(new Implementation1Factory());
            serviceConsumer(new Implementation2Factory());
        }
    }
    

    工厂方法:
    先定义一个关于调用方法接口 Service,然后定义一个生成该调用方法接口的接口 ServiceFactory 。

    随后定义类实现 Service, Implementation1。再定义类实现ServiceFactory, Implementation1Factory.

    按照需要还可以定义类实现 Implementation2 、 Implementation2Factory; Implementation3 、 Implementation3Factory ...

    最后定义一个工厂类 Factory,以 serviceFactory 作为参数,实例化 Service ,这样通过最后的 工厂类 Factory, 就可以调用任何实现 serviceFactory 接口的数据了。

    应用场景
    为什么需要这种间接性呢?一个常见的原因就是想创建框架。比如现在需要创建一个棋类游戏,在相同的棋盘上可以下西洋棋和国际象棋

    interface Game{
      boolean move();
    }
    
    interface GameFactory{
      Game getGame();
    }
    
    class Checkers implements Game{
      private int moves = 0;
      private final static int MOVES = 3;
    
      @Override
      public boolean move(){
        System.out.println("Checker move " + moves);
        return  ++moves != MOVES;
      }
    } 
    
    class CheckerFactory implements GameFactory{
      @Override
      public Game getGame(){
        return new Checkers();
      }
    }
    
    class Chess implements Game{
      private int moves = 0;
      private final static int MOVES = 4;
      
      @Override
      public boolean move{
          System.out.println("Chess move " + moves);
          return ++moves != MOVES;
      }
    
    class ChessFactory implements GameFactory{
        
        @Override
        public Game getGame(){
          return new Chess();
      }
    }
    
    public class Games(){
      public void static playGame(GameFactory gameFactory){
        Game game = gameFactory.getGame();
        while(game.move());
      }
    
      public void static main(String[] args){
        playGame(new CheckerFactory);
        playGame(new ChessFactory);  
    }
    }
    

    checker 是棋盘的意思,chess 是国际象棋的意思。这段代码先定义了接口Game 、 GameFactory, 国际象棋和西洋棋分别实现了这两个接口。这样它们都可以被 Game.playGame调用。

    9.10 总结

    “确定接口是理想选择,因此应该总是选择接口而不是具体的类”。这其实是一种引诱。当然,对于创建类,几乎在任何时刻,都可以替代为创建一个接口和一个工厂(这个工厂是生成该接口的)。

    许多人都掉进了这种诱惑的陷阱,只要有可能就去创建接口和工厂。这种逻辑看起来好像是因为需要使用不同的具体实现,因此总是应该添加这种抽象性。这实际上已经变成了一种草率的设计优化。

    任何抽象性都应该是应真正的需求产生的。当必须时,你应该重构接口而不是到处添加额外级别的间接性,并由此带来额外的复杂度。

    恰当的原则是应该是优先选择类而不是接口。从类开始,如果接口的必需性变得非常明确,那么就进行重构。接口是一种重要的工具,但是他们不应该被滥用。(我觉得作者的意思是针对接口容易被滥用的情况)

    内部类

    内部类可以将一个类的定义放在另一个类的定义内部,这就是内部类。

    内部类是非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并且控制内部类的可视性。

    但是内部类与组合是完全不同的概念。

    在最初,内部类看起来就像是一种代码隐藏机制,将类置于其他类的内部。但是,你将了解到,内部类远不止于此,

    它了解外围类,并能与之通信,而且你使用内部类写出的代码更加优雅而清晰,尽管并不总是如此。

    10.1 创建内部类

    创建内部类的方式——把类的定义置于外围类的里面.

    class A{
        class B{
            
        }
        class C{
        
        }
    }
    

    外部有一个方法,该方法返回一个指向内部类的引用。
    使用时,必须具体指明这个对象的类型,OuterClassName.InnerClassName

    package com.zzjack.rdsapi_demo.javathought;
    
    public class Parcel3 {
        class Contents{
            private int i = 11;
            public int value(){
                return i;
            }
        }
    
        class Destination{
            private String label;
            Destination(String whereTo){
                label = whereTo;
            }
            String readLabel(){
                return label;
            }
        }
    
        public Destination to(String s){
            return new Destination(s);
        }
    
        public Contents contents(){
            return new Contents();
        }
    
        public void ship(String dest){
            Contents c = contents();
            Destination d = to(dest);
            System.out.println(d.readLabel());
        }
    
        public static void main(String[] args){
            Parcel3 p = new Parcel3();
            p.ship("Tasmanina");
            Parcel3 q = new Parcel3();
            // 具体指明这个对象的类型,OuterClassName.InnerClassName
            // 外部类的方法中使用内部类
            Parcel3.Contents c = q.contents();
            Parcel3.Destination d = q.to("Borneo");
        }
    }
    

    10.2 链接到外部类

    到目前为止,内部类似乎还只是一种名字隐藏和组织代码的模块。除此之外,外部类还有其他用途。

    当生成一个内部类的对象时,此对象与制造它的外围对象之间就有了一种联系,所以它能访问外围对象的所有成员,而不需要任何特殊条件。

    此外,内部类还拥有其外围类的所有元素的访问权。

    interface Selector{
        boolean end();
        Object current();
        void next();
    }
    
    public class Sequence{
        private Object[] items;
        private int next = 0;
        
        public void add(Object x){
            if(next < items.length){
                items[next++] = x;
            }
        }
        
        private class SequenceSelector iimplements Selector{
            private int i = 0;
            
            // 私有类可以调用外部 item
            public boolean end(){
                return i == items.length;
            }
            
            public Object current(){
                return items[i];
            }
            
            public void next(){
                if(i < items.length){
                    i++;
                }
            }
        }
        
        public Selector selector(){
            return new SequenceSelector();
        }
        
        public static void main(String[] args){
            Sequence sequence = new Sequence(10);
            for(int i = 0; i < 10; i++){
                sequence.add(Integer.toString(i));
            }
            Selector selector =  sequence.selector();
            while(!selector.end()){
                System.out.println(selector.current() + " ");
                selector.next();
            }   
        }
    }
    

    Sequence 类只是一个固定大小的 Object 的数组,以类的形式包装起来了。可以调用add() 在序列末增加新的 Object.

    要获取 Sequence 中的每一个对象,可以使 Selector 接口。这是 “迭代器” 设计模式的一个例子。

    Selector 允许你检查序列是不是到了末尾(end()), 访问当前对象(current()),以及移动到序列中的下一个对象(next())。

    Selector 是一个接口,所以别的类可以按照它们自己的方式来实现这个接口,并且别的方法能以此接口为参数,来生成更加通用的代码。

    这里,SequenceSelector 是提供 Selector 功能的 private 类。可以看到,在 main() 中创建了一个Sequence, 并且向其中添加了一个

    String 对象。然后通过调用 selector() 获取一个 Selector,并用它在 Sequence 中移动和选择每一个元素。

    最初看到 SequenceSelector, 可能觉得它只不过是另外一个内部类。注意方法 end()/ current()/ next() 都用到了 objects,这是

    一个引用,它并不是 SequenceSelector 的一部分,而是外围类中的一个 private 字段。然而内部可以访问其外围类的方法和字段,就

    像自己拥有它们似的,这带来了很大的方便。

    所以内部类自动拥有对其外围类所有成员的访问权,这是如何做到的?当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密地

    捕捉一个指向那个外围类对象的引用。然后,在你访问此外围类的成员时,就是那个引用来选择外围类的成员。幸运的是,编译器会帮你处理所有的

    细节,但你现在可以看到:内部类的对象只能在与其外围类的对象相关联的情况下才能被创建(就像你现在看到的,在内部类是非 static 类时)。

    构建内部类对象时,需要一个指向其外围类对象的引用,如果编译器访问不到这个引用就会报错。

    10.3 使用 .this 与 .new

    如果你需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和 this.这样产生的引用自动地具有正确的类型。

    在内部类引用外围类,需要使用 .this。

    public class DotThis{
        void f(){
            System.out.println("DotThis.f()");
        }
        
        public class Inner{
            public DotThis outer(){
                return DotThis.this;
            }
        }
        
        public Inner inner(){
            return new Inner();   
        }
        
        public static void main(String[] args){
            DotThis dt = new DotThis();
            DotThis.Inner dti = dt.inner();
            dti.outer().f();
        }
    }
    

    有时候你需要告知某些其他对象,去创建其某个内部类的对象。要实现此目的,你必须在 new 表达式

    中提供对其他外部类对象的引用,这是需要使用 .new 语法:

    public class DotNew{
        public class Inner{}
        
        public static void main(String[] args){
            DotNew dn = new DotNew();
            // 外部类实例创建内部类实例
            DotNew.Inner inner = dn.new Inner();
        }
    }
    

    要想直接创建内部类的对象,不能去引用外部类的名字 DotNew,而是必须使用外部类的实例来创建

    内部类的实例,就像上面程序看到的那样,dn.new。这也解决了内部类名字作用域的问题。

    在拥有外部类实例之前是不可能创建内部类实例的。这是因为内部类实例会暗暗地连接到创建它的

    外部类实例上。但是,如果你创建的是嵌套类(静态内部类),那么它不需要对外部类对象的引用。

    10.4 内部类与向上引用

    当将内部类向上转型为其基类,尤其是转型为一个接口时,内部类就有了用武之地。

    (从实现了某个接口的对象,得到对此接口的引用,与向上转型为这个对象的基类,实际上效果是一样的。)

    这是因此内部类——某个接口的实现——能够完全不可见,并且不可用,所得到的只是指向基类或接口的引用。所以能够
    很方便地隐藏细节。

    接口一,Destination

    public interface Destination{
        String readLabel();
    }
    

    接口二,Contents

    public interface Contents{
        int value();
    }
    

    内部类,指向接口的引用

    class Parcel4{
        private class PContents implements Contents{
            private int i = 11;
            public int value(){
                return i;
            }
        }
        
        protected class PDestination implements Destination{
            private String label;
            private PDestination(String whereTo){
                label = whereTo;
            }
            public String readLabel(){
                return label;
            }
        }
        
        public Contents contents(){
            return new PContents();
        }
    }
    
    public class TestParcel{
        public static void main(String[] args){
            Parcel4 p = new Parcel4();
            Contents c = p.contents();
            Destination d = p.destination(Tasmania");
        }
    }
    

    Parcel4 中增加了一些新东西:内部类 PContents 是 private,所以除了 Parcel4,

    没有人能访问它。PDestinantion 是 protected,所以只有 Parcel4 及其子类,还有与

    Parcel4 同一个包中的类(因为 protected 也给予了包访问权) 能访问 PDestination,其他类都不能

    访问 PDestination。这意味着,如果客户端程序员想了解或访问这些成员,那是要受到限制的。实际上,

    甚至不能向下转型成 private 内部类(或 protected 内部类,除非是继承自它的子类),因此不能访问其名字。

    就像在 TestParcel 中看到的那样。于是,private 内部类给类的设计者提供了一种途径,通过这种方式可以完全阻止

    任何依赖于类型的编码,并且完全隐藏了实现的细节。

    10.5 在方法和作用域内的内部类

    如果所读、写的代码包含了内部类,那么它们都是平凡的内部类,比较简单。

    还有一些比较复杂,比如可以在一个方法里面或在任意的作用域内定义内部类。这么做的两个理由:

    • 如前所示,你实现了某类型的接口,于是可以创建并返回对其的引用。

    • 你要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的。

    还有一些其他用途的内部类:

    • 一个定义在方法中的类,这个被称为局部内部类。

    • 一个定义在作用域内的类,此作用域在方法的内部。

    • 一个实现了接口的内部类

    • 一个匿名类,它扩展了有非默认构造器的类。

    • 一个匿名类,它执行字段初始化

    • 一个匿名类,它通过实例初始化实现构造(匿名类不可能有构造器)

    public class Parcel5{
        
        public Destination destination(String s){
            class PDestination implements Destination{
                private String label;
                private PDestination(String whereTo){
                    label = whereTO;
                }
                
                public String readLabel(){
                    return label;
                }
            }
            return new pDestination();
        }
        
        public static void main(String[] args){
            Parcel5 p = new Parcel5();
            Destination d = p.destination("Tasmania");
        }
    }
    

    PDestination 类是 destination() 方法的一部分,而不是 Parcel5 的一部分。

    所以,在 destination() 之外不能访问 PDestination。注意出现在 return 语句中的

    向上转型——返回的是 Destination 的引用,它是 PDestination 的基类。当然,在

    destination() 中定义了内部类 PDestination, 并不意味着一旦 dest() 方法执行完毕,

    PDestination 就不可用了。

    你可以在同一个子目录下的任意类中对某个内部类使用类标识符 PDestination, 这并不会有

    命名冲突。下面的例子展示了如何在任意的作用域内嵌入一个内部类:

    public class Parcel6{
        private void internalTracking(boolean b){
            if(b){
                class TrackingSlip{
                    private String id;
                    TrackingSlip(String s){
                        id = s;
                    }
                    String getSlip(){
                        return id;
                    }
                }
                TrackingSlip ts = new TrackingSlip("slip");
                String s = ts.getSlip();
            }
        }
        public void track(){
            internalTrakcing(true);
        }
        public static void main(String[] args){
            Parcel6 p = new Parcel6();
            p.track();
        }
    }
    

    TrackingSlip 类被嵌入在 if 语句的作用域内,这并不是说该类的创建是有条件的,它其实与别的类

    一起编译过了。然而,在定义 TrackingSlip 的作用域之外,它是不可用的,除此之外,它与普通类一样。

    10.6 匿名内部类

    public class Parcel7{
        public Contents contents(){
            return new Contents(){
                private int i = 11;
                public int value(){
                    return i;
                }
            };
        }
        
        public static void main(String[] args){
            Parcel7 p = new Parcel7();
            Contents c = p.contents();
        }
    }
    

    contents() 方法将返回值的生成与表示这个返回值的定义结合在一起。

    另外这个类是匿名的,它没有名字。更糟的是,看起来似乎是你想要创建一个 Contents 对象。

    但是然后(在到达语句结束的分号之前)你却说,“等一等,我想在这里插入一个类的定义”

    这种奇怪的语法指的是:“创建一个继承自 Contents 的匿名类的对象。”通过 new 表达式返回的

    引用被自动向上转型为对 Contents 的引用。上述匿名内部类的语法是下述形式的简化表达:

    class Parcel7b{
        class MyContents implements Contents{
            private int i = 11;
            public int value(){
                return i;
            }
        }
    
        public Contents contents(){
            return new MyContents();
        }
    
        public static void main(String[] args){
            Parcel7b p = new Parcel7b();
            Contents c = p.contents();
        }
    }
    

    如果匿名类的构造器需要携带参数, 直接传递参数就好了。

    注意此时的 super

    class Wrapping{
        private int i;
    
        public Wrapping(int x){
            i = x;
        }
    
        public int value(){
            return i;
        }
    }
    
    
    public class Parcel8 {
        public Wrapping wrapping(int x){
            return new Wrapping(x){
                public int value(){
                    return super.value() * 47;
                }
            };
        }
        
        public static void main(String[] args){
            Parcel8 p = new Parcel8();
            Wrapping w = p.wrapping(10);
        }
    }
    
    

    相关文章

      网友评论

          本文标题:Java 编程思想笔记:Learn 6

          本文链接:https://www.haomeiwen.com/subject/mxrdlftx.html