Java: выдать метод открытого класса ко всем пакетам одного и того же проекта, но сделать приватными для других проектов

У меня есть проект библиотеки с двумя пакетами, например package1 и package2 с классом 1 и классом 2 соответственно. class1 имеет некоторые общедоступные методы, доступные для конечного пользователя. Я хочу добавить несколько методов утилиты в class1, доступ к которым может получить только класс2. Я много искал, но не смог найти какой-либо модификатор доступа для способа предоставления доступа только для разных пакетов одного и того же проекта.

Есть ли у него какой-либо шанс достичь этого каким-либо образом?

UPDATE (пример кода):

Класс1 в пакете1:

package com.example.package1;

public class Class1 {

    // This method should only be accessed by Class2, cannot made it public for
    // every class
    void performUtilityOperation() {
        // internal utility
    }

    // other public methods...
}

Класс2 в пакете2:

package com.example.package2;

import com.example.package1.*;

public class Class2 {

    Class1 class1;

    public void setClass1(Class1 class1) {
        this.class1 = class1;
    }

    public void doSomeOperation() {
        this.class1.performUtilityOperation(); // here this method is not
                                                // accessible if not public
        // do some other operations
    }

    // other public methods
}

Ответ 1

Невозможно достичь этого (ничего не похоже на friend в С++, если это произойдет там, где u r). Хотя члены protected доступны из другого пакета расширенным классом, как показано ниже:

package1
public Class1{
    protected method();
}

Class2 extends Class1 и, следовательно, method() отображается в Class1, даже если Class2 находится в другом пакете.

package2
public Class2 extends Class1{
    public otherMethod(){
        method(); // is visible here
    }
}

Class3 не распространяется Class1, поэтому method() не будет виден

 package2
 public Class3{
      public otherMethod(){
          method(); // not visible here
      }
 }

IMO, это самый дальний путь, который вы можете использовать для скрытия методов в Class1

Ответ 2

Вы можете добавить public вложенный интерфейс к Class1 с помощью методов default, которые вызывают их соответствующие методы доступа к пакетам в Class1 и implement, этот интерфейс в Class2, так что только Class2 получает доступ к Class1 методам доступа к пакетам через этот интерфейс (извините!).

Вероятно, лучше в этот момент показать код.

Class1

Я добавил некоторую туманную реализацию печати для метода, чтобы показать, что он правильно называется.

package package1;

public class Class1 {

    int i;

    public Class1(int i) {

        this.i = i;
    }

    // Utility method only for Class2
    void performUtilityOperation() {

        System.out.println(i);
    }

    public interface Mediator {

        default void performUtilityOperation(Class1 c1) {

            c1.performUtilityOperation();
        }
    }

    // other public methods...
}

Интерфейс определяет метод default, который задает экземпляр Class1, вызывает этот экземпляр соответствующего метода. Я использовал те же имена для методов класса и интерфейса, но они могут быть разными.

Обратите внимание, что интерфейс должен быть public сам, поэтому он может быть видимым для Class2 для реализации.

Class2

package package2;

import package1.Class1;

public class Class2 implements Class1.Mediator {

    Class1 class1;

    public void setClass1(Class1 class1) {

        this.class1 = class1;
    }

    public void doSomeOperation() {

        performUtilityOperation(class1);
    }

    // other public methods
}

Реализация интерфейса позволяет получить доступ к его методам default. Так как Class2 содержит экземпляр Class1, он (используется) во всех вызовах интерфейсных методов. Интерфейс делегирует операции экземпляру Class1.

UserClass

Я добавил этот класс в свой собственный пакет как место для создания экземпляров классов и вызова различных методов. Я не уверен, как это делается в вашем случае, но сглаживание деталей не должно быть проблемой.

package user;

import package1.Class1;
import package2.Class2;

class UserClass {

    public static void main(String[] args) {

        Class1 clazz1Int3 = new Class1(3);
        Class1 clazz1Int4 = new Class1(4);

        Class2 c2 = new Class2();
        c2.setClass1(clazz1Int3);
        c2.doSomeOperation();
        c2.setClass1(clazz1Int4);
        c2.doSomeOperation();

//      clazz1Int3.performUtilityOperation(); // The method performUtilityOperation() from the type Class1 is not visible
    }
}

Я создаю экземпляр 2 Class1 с другим int, чтобы просто различить их. Затем я использую данный метод, чтобы установить ссылку Class1 в Class2 и вызвать метод public (для пользователя) в Class2. Этот вызов внутри него вызывает не доступный (невидимый) метод утилиты в Class1 через интерфейс Mediator.

Обратите внимание, что единственный способ получить доступ к Class1 утилите вне своего пакета - реализовать Mediator (вы не можете вызвать его из Mediator, потому что вы не можете создать экземпляр интерфейс). Так как только Class2 делает это (и вы можете контролировать, какие другие классы делают это также, если вообще), только он может получить к нему доступ за пределами пакета Class1.

Вывод для выполнения приведенного выше

3
4

Почему вложенный интерфейс?

На самом деле вам не нужно вводить интерфейс в виде вложенного интерфейса - это зависит от вашей общей структуры. Он может находиться в своем собственном модуле компиляции, но в том же пакете, что и Class1, поэтому он будет иметь видимые методы утилиты (access access). Преимущество этого вложенного интерфейса в том, что теперь методы утилиты могут быть private (или protected) и, следовательно, не доступны даже в их пакете.

Я упоминаю об этом, потому что вы указываете

Я хочу добавить несколько методов утилиты в class1, доступ к которым может получить только класс2.

но неясно, означает ли вы, что "только класс2 может получить доступ за пределами пакета class1" или "только класс2 может получить доступ в целом". Вы сделали доступ к пакету метода утилиты, поэтому он намекает на первый вариант, но я не был уверен.

Существует также рассмотрение дизайна "это подходящее место для размещения этого интерфейса?", но я не могу этого знать. Вложенные интерфейсы, как правило, придерживаются тех же соображений дизайна, что и вложенные классы, поэтому вы должны полагаться на них.

Заключительная записка

Если это не было очевидно, то этот подход предпочтительнее расширять классы, поскольку это ограничивает ваше наследование, тогда как реализация интерфейсов является "свободной" в этом отношении.

Ответ 3

Я применил следующее обходное решение, которое гарантирует, что только определенные пакеты могут получить доступ к определенным методам:

public class Reserved {

    private static Package p = AccesserA.class.getPackage();

    public void doSomething() throws ClassNotFoundException {
        if (p.equals(Class.forName(Thread.currentThread().getStackTrace()[2].getClassName()).getPackage())) {
            System.out.println("Access granted");
        } else {
            System.out.println("Access denied");
        }
    }

    public static void doSometingElse() throws ClassNotFoundException {
        if (p.equals(Class.forName(Thread.currentThread().getStackTrace()[2].getClassName()).getPackage())) {
            System.out.println("Access granted");
        } else {
            System.out.println("Access denied");
        }
    }
} 

Как вы можете видеть, вы указываете Package p как пакет, которому разрешен доступ. После вызова метода doSometing() или doSometingElse() пакет вызывающего класса проверяется на допустимый пакет. Если он равен, печатается Access granted и Access denied в противном случае. Вы можете создать класс abstract, который реализует это, и каждый класс, требующий ограниченного доступа, может просто расширить его и предоставить разрешенные пакеты. Он также работает для статических методов.

Давайте создадим два класса, которые попытаются получить доступ к методам Reserved:

package a.b.c;

public class AccesserA {
    public void tryAccess() throws ClassNotFoundException {
        Reserved res = new Reserved();
        res.doSomething();
    }
}

и

package a.b;

public class AccesserB {
    public void tryAccess() throws ClassNotFoundException {
        Reserved res = new Reserved();
        res.doSomething();
        Legit.Reserved.doSometingElse();
    }
}

Main:

public static void main (String ... args) throws IOException, InterruptedException, ClassNotFoundException {
    AccesserA a = new AccesserA();
    AccesserB b = new AccesserB();
    a.tryAccess();
    b.tryAccess();
}

Это даст результат:

Access granted
Access denied
Access denied

Ответ 4

Если вы даже не хотите, чтобы пользователи видели этот метод, вам нужно создать фасад (открытый интерфейс) и выставить только этот фасад. Не позволяйте пользователям работать непосредственно с классами реализации.

Обычно это делается с использованием фабрик или методов factory + (если хотите), что делает все конфиденциальные конструкторы частными.

public class Class1 implements Interface1 {

    private Class1() {}

    public static Interface1 create() { return new Class1(); }

    // This method is not visible through Interface1 
    public void performUtilityOperation() {
        // internal utility
    }
}

Затем, где бы вы ни захотели использовать метод утилиты, вы должны использовать кастинг:

Interface1 class1_instance = Class1.create();

((Class1) class1_instance).performUtilityOperation();

Если вам нужна какая-то безопасность (обратите внимание, что обычно можно разбить ее с помощью отражения), тогда объедините ее с решениями, предложенными в других ответах/комментариях...