¿Hay alguna manera de simular el concepto de 'amigo' de C++ en Java?

Resuelto Matthew Murdoch asked hace 16 años • 18 respuestas

Me gustaría poder escribir una clase Java en un paquete que pueda acceder a métodos no públicos de una clase en otro paquete sin tener que convertirla en una subclase de la otra clase. es posible?

Matthew Murdoch avatar Oct 08 '08 18:10 Matthew Murdoch
Aceptado

Aquí hay un pequeño truco que uso en JAVA para replicar el mecanismo amigo de C++.

Digamos que tengo una clase Romeoy otra clase Juliet. Están en paquetes diferentes (familiares) por motivos de odio.

Romeoquiere cuddle Juliety Julietsólo quiere dejarla Romeo cuddle.

En C++, Julietse declararía Romeocomo (amante) friendpero no existen tales cosas en Java.

Aquí están las clases y el truco:

Mujeres primero:

package capulet;

import montague.Romeo;

public class Juliet {

    public static void cuddle(Romeo.Love love) {
        Objects.requireNonNull(love);
        System.out.println("O Romeo, Romeo, wherefore art thou Romeo?");
    }
    
}

Entonces el método Juliet.cuddlees publicpero necesitas un Romeo.Lovepara llamarlo. Utiliza esto Romeo.Lovecomo una "firma de seguridad" para garantizar que solo Romeose pueda llamar a este método y verifica que el amor sea real para que el tiempo de ejecución arroje un mensaje NullPointerExceptionsi lo es null.

Ahora chicos:

package montague;

import capulet.Juliet;

public class Romeo {
    public static final class Love { private Love() {} }
    private static final Love love = new Love();
    
    public static void cuddleJuliet() {
        Juliet.cuddle(love);
    }
}

La clase Romeo.Lovees pública, pero su constructor lo es private. Por lo tanto cualquiera puede verlo, pero sólo Romeopuede construirlo. Utilizo una referencia estática para que la Romeo.Loveque nunca se usa solo se construya una vez y no afecte la optimización.

Por lo tanto, Romeopuede cuddle Juliety solo él puede porque solo él puede construir y acceder a una Romeo.Loveinstancia, que es requerida por Julietella cuddle(de lo contrario, ella te abofeteará con un NullPointerException).

Salomon BRYS avatar Sep 05 '2013 10:09 Salomon BRYS

Los diseñadores de Java rechazaron explícitamente la idea de amigo tal como funciona en C++. Pones a tus "amigos" en el mismo paquete. La seguridad privada, protegida y empaquetada se aplica como parte del diseño del lenguaje.

James Gosling quería que Java fuera C++ sin errores. Creo que sintió que ese amigo era un error porque viola los principios de programación orientada a objetos. Los paquetes proporcionan una forma razonable de organizar componentes sin ser demasiado puristas con respecto a la programación orientada a objetos.

NR señaló que se podía hacer trampa usando la reflexión, pero incluso eso solo funciona si no estás usando SecurityManager. Si activa la seguridad estándar de Java, no podrá hacer trampa con la reflexión a menos que escriba una política de seguridad que lo permita específicamente.

David G avatar Oct 08 '2008 12:10 David G

El concepto de "amigo" es útil en Java, por ejemplo, para separar una API de su implementación. Es común que las clases de implementación necesiten acceso a los componentes internos de la clase API, pero estos no deben estar expuestos a los clientes API. Esto se puede lograr utilizando el patrón 'Friend Accessor' como se detalla a continuación:

La clase expuesta a través de la API:

package api;

public final class Exposed {
    static {
        // Declare classes in the implementation package as 'friends'
        Accessor.setInstance(new AccessorImpl());
    }

    // Only accessible by 'friend' classes.
    Exposed() {

    }

    // Only accessible by 'friend' classes.
    void sayHello() {
        System.out.println("Hello");
    }

    static final class AccessorImpl extends Accessor {
        protected Exposed createExposed() {
            return new Exposed();
        }

        protected void sayHello(Exposed exposed) {
            exposed.sayHello();
        }
    }
}

La clase que proporciona la funcionalidad 'amigo':

package impl;

public abstract class Accessor {

    private static Accessor instance;

    static Accessor getInstance() {
        Accessor a = instance;
        if (a != null) {
            return a;
        }

        return createInstance();
    }

    private static Accessor createInstance() {
        try {
            Class.forName(Exposed.class.getName(), true, 
                Exposed.class.getClassLoader());
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e);
        }

        return instance;
    }

    public static void setInstance(Accessor accessor) {
        if (instance != null) {
            throw new IllegalStateException(
                "Accessor instance already set");
        }

        instance = accessor;
    }

    protected abstract Exposed createExposed();

    protected abstract void sayHello(Exposed exposed);
}

Ejemplo de acceso desde una clase en el paquete de implementación 'amigo':

package impl;

public final class FriendlyAccessExample {
    public static void main(String[] args) {
        Accessor accessor = Accessor.getInstance();
        Exposed exposed = accessor.createExposed();
        accessor.sayHello(exposed);
    }
}
Matthew Murdoch avatar Nov 25 '2008 09:11 Matthew Murdoch