6

I'm trying to understand scope in Java. In Perl, I can do the following:

my $x = 1;
{
    my $x = 2;
    say $x; # prints 2
}
say $x; # prints 1

In other words, since I declared the variable $x with my within the scope, the $x variable in that scope is local to that scope (i.e., not the same variable as the global $x variable). Now, in Java I am trying to do something similar, but I am getting the error

Variable rawURL is already defined in method run()

Here's the code:

// Global rawURL
URI rawURl;
try {
  rawURL = new URI("http://www.google.com");
} catch (Exception e) {
  // Handle later
}

// Some time later
for (Element link : links) {
  // rawURL in this scope
  URI rawURL;
  try {
    rawURL = new URI(link.attr("abs:href"));
  } catch (Exception e) {
    // Handle later
  }
}

Now, the thing is, I don't want to have to get creative for all of my variable names, ensuring each one is different. I am simply using rawURL to build a normalized URL, so it's essentially a temporary variable. I could just do this:

for (Element link : links) {
  rawURL = new URL(..);
}

But then that will change the contents of my global variable, and I really don't want that. Is there any way to use the same name for the local variable as I am using for the global variable?

Franz Kafka
  • 780
  • 2
  • 13
  • 34
  • There is no such concept of global variable in java. You may have to reuse or define your own new variable for `URI`. – SMA Jan 02 '16 at 14:04
  • If your method is so long that you fall short of good names for your variables, it's a really strong indication that your method should be split into shorter methods. Each method has its own scope. Catching Exception is also very bad practice, BTW. – JB Nizet Jan 02 '16 at 14:04
  • My method is not that long. Take a URL, normalize it, get the page using jsoup, parse DOM, grab links from page (normalize them) and add to Redis. That is the entirety of it, about 40 lines total. – Franz Kafka Jan 02 '16 at 14:06
  • That's already quite long. But you can reuse the variable, or choose better names: googleUri, linkUri. – JB Nizet Jan 02 '16 at 14:09
  • 40 lines of code in a method is quite long? What's best practices here? 5? 10? – Franz Kafka Jan 02 '16 at 14:11
  • 2
    Yes, that's what you should strive for. I also have the feeling that your problems are caused by the way you handle exceptions. Why don't you post the real, complete code, so that we can suggest better alternatives. – JB Nizet Jan 02 '16 at 14:12
  • 3
    ^You are sarcastic here, but yes, your code should look like what you have described: `Page page = getPageUsingJsoup(normalizeUrl(url)); for (Link link: getLinksFromPage(page)) { storeLinkInRedis(link); }`. See, that's quite readable code, it doesn't need many variables, and it does what your description says it should do.But I understand that, for a Perl programmer, so much clarity might look scary :) – JB Nizet Jan 02 '16 at 14:21
  • The best way to get questions like this answered is to put together a little code snippet, and run it. Of you did that, it would took you way less time to figure out that Java scoping rules are exactly the same as in perl, than you have already spent arguing about the length of you methods, and reading incorrect answers. – Dima Jan 02 '16 at 14:26
  • @Dima, it may tell me whether it will work or not, it certainly does not tell me why it won't work, which is what the answers here have explained. – Franz Kafka Jan 02 '16 at 14:27
  • Sure. But if you tried it, you'd see that it would work, and that would render the second question moot. – Dima Jan 02 '16 at 14:28
  • @Dima you should probably follow your own advice. – JB Nizet Jan 02 '16 at 14:33
  • @Dima I don't see how the scoping in the Perl example and in Java are the same. – user1803551 Jan 02 '16 at 14:36
  • @JBNizet, Your comment gave me quite a good laugh, thanks for that. For my mind, that Java way here (where I cannot override a variable within a certain scope) does not make a lot of sense. Would you say Perl's way of dealing with these variables (same name, different scope) is also bad? I'm genuinely curious, because I don't see why Java does not support this. – Franz Kafka Jan 02 '16 at 14:36
  • I guess it was designed that way because it would be the cause of many bugs. Shadowing is often undesired: you want to assign a new value to an existing variable, and instead, you mess up and redeclare it. I have answered countless questions here where the problem was shadowing between a local variable and an instance variable (which is allowed in Java). – JB Nizet Jan 02 '16 at 14:40
  • @JBNizet, Alright, well thanks for the education. I'm new to Java, coming from a Perl background obviously, so all of this is great stuff to know. – Franz Kafka Jan 02 '16 at 14:42
  • "*I cannot override a variable within a certain scope*" Careful with the terminology. You cannot declare a local variable which has already been declared within the scope. See my answer for extra explanations and "ideas". – user1803551 Jan 02 '16 at 14:43
  • And you really would do well to write short methods for various reasons like modularity (or "reusabilty") and ease of debugging. – user1803551 Jan 02 '16 at 14:47

3 Answers3

4

You must define a new local variable with a new name because scope of variable is like: current block + subblocks. So in your case, rawURL is defined for what you called "global" and it is visible to subblocks, ie into the for block.

In java, you can do :

{
   String myvar = "";
}

{
   String myvar = "";
}

because here, there are two different blocks at same level

but you can't do :

String myvar = "";
{
    String myvar = "";
}

because there is conflict with same name for two variables in same block (the subblock).

Prim
  • 2,880
  • 2
  • 15
  • 29
  • This is incorrect. You can certainly define a variable with the same name in inner scope. It will give you warning that it shadows an outer variable, but you can still do it, and it will behave as one would expect (same way as in perl). – Dima Jan 02 '16 at 14:25
  • 3
    @Dima The warning is only between a variable which hides a instance field but a variable can't hide other variable, it produces not only a warning but an error – Prim Jan 02 '16 at 14:31
  • Instance field, yes. That's the closest thing to what the OP characterized as a "global variable", isn't it? – Dima Jan 02 '16 at 14:40
1

In java you have public and private access modifiers to control the accessibility of variables however if you declare a variable in the class it would be a global variable and the one with in the class method would be local compared to the one that is outside the method. There are no absolute global and local variables you have to declare and define new variables names for new variables.

public class abc{
    public int a; //global and accessible by every class outside this class too 
    private int b; //global and accessible only within this class
    private void m(){
        int x; //local and accessible only within this method 
    }
}

Plus you can do this

public void m(){
    for(;;){
        int a;
    }
    for(;;){
        int a;
    }
}

But you cannot do this

public void m(){
    int a;
    for(;;){
        int a;
    }
}

Hope this helps you out.

Bilal Rafique
  • 304
  • 3
  • 18
1

in Java I am trying to do something similar, but I am getting the error

Variable rawURL is already defined in method run()

The JLS explains why you get the error:

6.4. Shadowing and Obscuring

It is a compile-time error if the name of a local variable v is used to declare a new variable within the scope of v, unless the new variable is declared within a class whose declaration is within the scope of v.

and to explain what the scope of v is look at an answer where I explain it.

Basically, if you compare it to Perl, the scope is "one way" and not "two way".

void method1() {

    int x;
    {
        int y = x; // O.K. - x is in scope.
        int x = y; // Not O.K. - x is in scope and was already declared.
    }
    x = y; // Not O.K. - y is not in scope.
}

What you can do, it this:

void method2() {

    int x;            
    class InsideClass {                             
        {             
            int y = x;
            int x = y;
        }             
    }
}      

but it's probably an overkill for your needs.

Community
  • 1
  • 1
user1803551
  • 12,965
  • 5
  • 47
  • 74