Giấu tên Java: Con đường khó


96

Tôi có một vấn đề với ẩn tên rất khó giải quyết. Đây là một phiên bản đơn giản giải thích sự cố:

Có một lớp học: org.A

package org;
public class A{
     public class X{...}
     ...
     protected int net;
}

Sau đó, có một lớp học net.foo.X

package net.foo;
public class X{
     public static void doSomething();
}

Và bây giờ, đây là lớp có vấn đề kế thừa Avà muốn gọinet.foo.X.doSomething()

package com.bar;
class B extends A {

    public void doSomething(){
        net.foo.X.doSomething(); // doesn't work; package net is hidden by inherited field
        X.doSomething(); // doesn't work; type net.foo.X is hidden by inherited X
    }
}

Như bạn thấy, điều này là không thể. Tôi không thể sử dụng tên đơn giản Xvì nó bị ẩn bởi một kiểu kế thừa. Tôi không thể sử dụng tên đủ điều kiện net.foo.Xnetbị ẩn bởi trường kế thừa.

Chỉ có lớp Btrong cơ sở mã của tôi; các lớp net.foo.Xorg.Alà các lớp thư viện, vì vậy tôi không thể thay đổi chúng!

Giải pháp duy nhất của tôi trông như thế này: Tôi có thể gọi một lớp khác đến lượt nó gọi X.doSomething(); nhưng lớp này sẽ chỉ tồn tại vì sự đụng độ tên, có vẻ rất lộn xộn! Không có giải pháp nào mà tôi có thể gọi trực tiếp X.doSomething()từ đó B.doSomething()?

Trong một ngôn ngữ cho phép chỉ định không gian tên toàn cục, ví dụ: global::trong C # hoặc ::trong C ++, tôi có thể chỉ cần thêm tiền tố nettoàn cục này, nhưng Java không cho phép điều đó.


Một số QuickFix có thể là thế này: public void help(net.foo.X x) { x.doSomething(); }và gọi vớihelp(null);
Absurd-Tâm

@ Absurd-Mind: Chà, gọi một phương thức tĩnh thông qua một đối tượng không tồn tại có vẻ còn lộn xộn hơn giải pháp của tôi :). Tôi thậm chí sẽ nhận được một cảnh báo trình biên dịch. Nhưng đúng, đây sẽ là một giải pháp hack khác ngoài giải pháp của tôi.
gexicide

@JamesB: Điều này sẽ liên kết sai X! net.foo.Xcó phương pháp, không org.A.X!
gexicide

3
Bạn phải thừa kế từ A? Thừa kế có thể rất tồi tệ, như bạn đã tìm thấy…
Donal Fellows

2
I could call another class that in turn calls X.doSomething(); but this class would only exist because of the name clash, which seems very messy+1 cho thái độ của mã sạch. Nhưng đối với tôi, có vẻ như đây là tình huống bạn nên đánh đổi. Đơn giản chỉ cần làm điều này và ném một bình luận dài về lý do tại sao bạn phải làm điều đó (có thể là với một liên kết đến câu hỏi này).
sampathsris

Câu trả lời:


84

Bạn có thể ép nullkiểu thành kiểu và sau đó gọi phương thức trên đó (phương thức này sẽ hoạt động, vì đối tượng đích không liên quan đến việc gọi phương thức tĩnh).

((net.foo.X) null).doSomething();

Điều này có lợi ích của

  • không có tác dụng phụ (vấn đề với việc khởi tạo net.foo.X),
  • không yêu cầu đổi tên bất kỳ thứ gì (vì vậy bạn có thể Bđặt tên cho phương thức mà bạn muốn nó có; đó là lý do tại sao a import staticsẽ không hoạt động trong trường hợp chính xác của bạn),
  • không yêu cầu giới thiệu lớp đại biểu (mặc dù đó có thể là một ý kiến ​​hay…), và
  • không yêu cầu chi phí cao hoặc phức tạp khi làm việc với API phản chiếu.

Nhược điểm là mã này thực sự rất kinh khủng! Đối với tôi, nó tạo ra một cảnh báo và đó là một điều tốt nói chung. Nhưng vì nó đang giải quyết một vấn đề hoàn toàn không thực tế, việc thêm

@SuppressWarnings("static-access")

tại một điểm bao quanh thích hợp (tối thiểu!) sẽ đóng trình biên dịch.


1
Tại sao không? Bạn chỉ cần làm đúng và cấu trúc lại mã.
Gimby

1
Nhập tĩnh không rõ ràng hơn nhiều so với giải pháp này không hoạt động trong mọi trường hợp (bạn gặp khó khăn nếu có một doSomethingphương pháp ở bất kỳ đâu trong hệ thống phân cấp của bạn), vì vậy, đó là giải pháp tốt nhất.
Voo

@Voo Tôi tự do thừa nhận rằng tôi đã thực sự đi và thử tất cả các tùy chọn mà người khác đã liệt kê là câu trả lời khi lần đầu tiên tái tạo chính xác vấn đề ban đầu là gì, và tôi giật mình rằng việc giải quyết khó khăn như thế nào. Câu hỏi ban đầu kết thúc gọn gàng tất cả các lựa chọn khác, đẹp hơn.
Donal Fellows

1
Bình chọn cho sự sáng tạo, tôi sẽ không bao giờ làm điều này trong mã sản xuất. Tôi tin rằng chuyển hướng là câu trả lời cho vấn đề này (không phải nó dành cho tất cả các vấn đề?).
ethanfar

Cảm ơn Donal cho giải pháp này. Trên thực tế, giải pháp này làm nổi bật những nơi có thể sử dụng "Loại" và không thể sử dụng biến. Vì vậy, trong một "cast", trình biên dịch sẽ chọn "Type" ngay cả khi có một biến có cùng tên. Để biết thêm những trường hợp thú vị như vậy, tôi muốn giới thiệu 2 cuốn sách "Cạm bẫy Java": books.google.co.in/books/about/… books.google.co.in/books/about/…
RRM

38

Có lẽ cách đơn giản nhất (không nhất thiết là dễ nhất) để quản lý điều này là với một lớp đại biểu:

import net.foo.X;
class C {
    static void doSomething() {
         X.doSomething();
    }
}

và sau đó ...

class B extends A {
    void doX(){
        C.doSomething();
    }
}

Điều này hơi dài dòng, nhưng rất linh hoạt - bạn có thể làm cho nó hoạt động theo bất kỳ cách nào bạn muốn; cộng với nó hoạt động theo cùng một cách cả với staticcác phương thức và các đối tượng được khởi tạo

Thông tin thêm về các đối tượng đại biểu tại đây: http://en.wikipedia.org/wiki/Delegation_pattern


Có lẽ là giải pháp sạch nhất được cung cấp. Tôi có thể sẽ sử dụng nó nếu tôi gặp vấn đề đó.
LordOfThePigs,

37

Bạn có thể sử dụng nhập tĩnh:

import static net.foo.X.doSomething;

class B extends A {
    void doX(){
        doSomething();
    }
}

Chú ý rằng BAkhông chứa các phương thức có têndoSomething


Không phải net.foo.X.doSomething chỉ có quyền truy cập gói? Có nghĩa là bạn không thể truy cập nó từ gói com.bar
JamesB

2
@JamesB Có, nhưng sau đó câu hỏi hoàn chỉnh sẽ không có ý nghĩa, vì phương pháp này sẽ không thể truy cập được theo bất kỳ cách nào. Tôi nghĩ rằng đây là một lỗi trong khi đơn giản hóa ví dụ
Absurd-Tâm

1
Nhưng câu hỏi ban đầu dường như chủ động muốn sử dụng doSomethinglàm tên của phương pháp trong B
Donal Fellows

3
@DonalFellows: Bạn nói đúng; giải pháp này không hoạt động nếu mã của tôi phải giữ nguyên như tôi đã đăng. May mắn thay, tôi có thể đổi tên phương thức. Tuy nhiên, hãy nghĩ đến trường hợp phương thức của tôi ghi đè phương thức khác, sau đó tôi không thể đổi tên nó. Trong trường hợp này, câu trả lời này sẽ không thực sự giải quyết được vấn đề. Nhưng điều này còn trùng hợp hơn cả vấn đề của tôi, vì vậy có lẽ sẽ không bao giờ có bất kỳ con người nào gặp phải vấn đề như vậy với cuộc đụng độ ba tên :).
gexicide

4
Để mọi người hiểu rõ hơn: Giải pháp này không hoạt động ngay khi bạn có doSomethingở bất kỳ đâu trong hệ thống phân cấp kế thừa (do cách giải quyết các mục tiêu cuộc gọi phương thức). Vì vậy, Knuth giúp bạn nếu bạn muốn gọi một toStringphương thức. Giải pháp do Donal đề xuất là giải pháp nằm trong cuốn sách Câu đố Java của Bloch (tôi nghĩ, chưa tra cứu nó), vì vậy chúng ta có thể coi đó là câu trả lời có thẩm quyền thực sự :-)
Voo

16

Cách thực hiện thích hợp sẽ là nhập tĩnh, nhưng trong trường hợp xấu nhất tuyệt đối, bạn CÓ THỂ tạo một thể hiện của lớp bằng cách sử dụng phản chiếu nếu bạn biết tên đầy đủ của nó.

Java: newInstance của lớp không có hàm tạo mặc định

Và sau đó gọi phương thức trên phiên bản.

Hoặc, chỉ gọi chính phương thức với phản xạ: Gọi một phương thức tĩnh sử dụng phản xạ

Class<?> clazz = Class.forName("net.foo.X");
Method method = clazz.getMethod("doSomething");
Object o = method.invoke(null);

Tất nhiên, đây rõ ràng là những phương án cuối cùng.


2
Câu trả lời này đến nay là quá mức cần thiết lớn nhất cho đến bây giờ - thumbs up :)
gexicide

3
Bạn sẽ nhận được một huy hiệu hoàn thành cho câu trả lời này; bạn đã cung cấp câu trả lời cho một người nghèo hiếm hoi phải sử dụng phiên bản Java cổ và giải quyết vấn đề chính xác này.
Gimby

Tôi thực sự không nghĩ về thực tế là static importtính năng chỉ được thêm vào Java 1.5. Tôi không ghen tị với những người cần phát triển từ 1.4 trở xuống, tôi đã phải một lần và điều đó thật khủng khiếp!
EpicPandaForce

1
@Gimby: Chà, vẫn có ((net.foo.X)null).doSomething()giải pháp hoạt động trong java cũ. Nhưng nếu loại Athậm chí chứa một loại bên trong net, THÌ đây là câu trả lời duy nhất vẫn còn giá trị :).
gexicide

6

Không thực sự là câu trả lời nhưng bạn có thể tạo một thể hiện của X và gọi phương thức tĩnh trên đó. Đó sẽ là một cách (tôi thừa nhận là bẩn) để gọi phương pháp của bạn.

(new net.foo.X()).doSomething();

3
Truyền nullđến kiểu và sau đó gọi phương thức trên đó sẽ tốt hơn, vì việc tạo một đối tượng có thể có tác dụng phụ mà tôi không muốn.
gexicide

@gexicide Ý tưởng đúc nullđó dường như là một trong những cách tốt hơn để thực hiện. Cần @SuppressWarnings("static-access")không có cảnh báo…
Donal Fellows

Cảm ơn vì sự chính xác của nó null, nhưng việc đúc nullluôn là một điều khiến tôi đau lòng.
Michael Laffargue,

4

Không cần phải thực hiện bất kỳ truyền hoặc ngăn chặn bất kỳ cảnh báo lạ nào hoặc tạo bất kỳ phiên bản thừa nào. Chỉ là một mẹo sử dụng thực tế là bạn có thể gọi các phương thức tĩnh của lớp cha thông qua lớp con. (Tương tự với giải pháp hackish của tôi ở đây .)

Chỉ cần tạo một lớp như thế này

public final class XX extends X {
    private XX(){
    }
}

(Hàm khởi tạo riêng trong lớp cuối cùng này đảm bảo không ai có thể vô tình tạo một thể hiện của lớp này.)

Sau đó, bạn có thể thoải mái gọi X.doSomething()qua nó:

    public class B extends A {

        public void doSomething() {
            XX.doSomething();
        }

Thú vị, nhưng một cách tiếp cận khác. Tuy nhiên, lớp có phương thức tĩnh là một lớp tiện ích cuối cùng.
gexicide

Vâng, không thể sử dụng thủ thuật này trong trường hợp đó. Tuy nhiên, một lý do khác tại sao phương thức tĩnh là một ngôn ngữ không thành công và cách tiếp cận đối tượng của Scala nên được thực hiện.
billc.cn

Nếu bạn vẫn định tạo một lớp khác, thì việc sử dụng một lớp đại biểu như được mô tả trong blgt answer có lẽ là tốt nhất.
LordOfThePigs

@LordOfThePigs Tuy nhiên, điều này tránh được việc gọi thêm phương thức và gánh nặng tái cấu trúc trong tương lai. Lớp mới về cơ bản đóng vai trò như một bí danh.
billc.cn

2

Điều gì sẽ xảy ra nếu bạn thử lấy không gian gobalnames vì ​​tất cả các tệp đều nằm trong cùng một thư mục. ( http://www.beanshell.org/javadoc/bsh/class-use/NameSpace.html )

    package com.bar;
      class B extends A {

       public void doSomething(){
         com.bar.getGlobal().net.foo.X.doSomething(); // drill down from the top...

         }
     }

Cái này hoạt động ra sao? AFAIK getGlobal()không phải là một phương pháp Java tiêu chuẩn đối với các gói ... (Tôi nghĩ rằng gói không thể có bất kỳ phương pháp trong Java ...)
siegi

1

Đây là một trong những lý do mà thành phần được ưu tiên hơn là thừa kế.

package com.bar;
import java.util.concurrent.Callable;
public class C implements Callable<org.A>
{
    private class B extends org.A{
    public void doSomething(){
        C.this.doSomething();
    }
    }

    private void doSomething(){
    net.foo.X.doSomething();
    }

    public org.A call(){
    return new B();
    }
}

0

Tôi sẽ sử dụng mô hình Chiến lược.

public interface SomethingStrategy {

   void doSomething();
}

public class XSomethingStrategy implements SomethingStrategy {

    import net.foo.X;

    @Override
    void doSomething(){
        X.doSomething();
    }
}

class B extends A {

    private final SomethingStrategy strategy;

    public B(final SomethingStrategy strategy){
       this.strategy = strategy;
    }

    public void doSomething(){

        strategy.doSomething();
    }
}

Bây giờ bạn cũng đã tách được sự phụ thuộc của mình, vì vậy các bài kiểm tra đơn vị của bạn sẽ dễ viết hơn.


Bạn đã quên để thêm một implements SomethingStrategytrên XSomethingStrategykhai báo lớp.
Ricardo Souza

@rcdmk Cảm ơn bạn. Nên sửa ngay.
Erik Madsen
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.