Reassigned local variable java что такое
Перейти к содержимому

Reassigned local variable java что такое

  • автор:

Reassigned local variable java что такое

Why are method parameters reassigned to local variables?

While looking through the Java API source code I often see method parameters reassigned to local variables. Why is this ever done?

This is in java.util.HashMap

2 Answers 2

This is rule of thread safety/better performance. values in HashMap is volatile. If you are assigning variable to local variable it becomes local stack variable which is automatically thread safe. And more, modifying local stack variable doesn’t force ‘happens-before’ so there is no synchronization penalty when using it(as opposed to volatile when each read/write will cost you acquiring/releasing a lock)

Java 10: Changing variables as you know them by introducing Local Variable Type Inference

Java 10 is just around the corner with a GA set to March.

One of the biggest news is the Local-Variable Type Inference, which brings improvements to how you’re declaring your variables.

Looking at other languages you often see variable declarations without the type specified. In JavaScript you use the keywords let , const and var , in Scala you use var and val and so on.

Looking at Java you’ve always had to specify the type when creating a variable — even when the type is obvious from the initialization.

This can feel a bit verbose, because we know that this is a String since we’re assigning «Luke» to the variable straight away.

Introducing the var

This is where Java 10 brings in the game changer — we can now skip specifying the type for local variables and instead simply use the keyword var .

Now with that said, it’s important to realise that even if var is introduced, Java remains a static typed language — meaning that the usage of var only is possible where the type is obvious.

In other words — the type must be possible to infer.

To figure out what that means, let’s look at a few do’s and dont’s.

Only for local variables

First of all, var is only possible for local variables.

Meaning that you can you use it in methods.

But you can not use it in classes.

Variables using var must be initialized straight away

When using var you need to make sure to initialize it straight away.

The reason for this is that even if we use var , the compiler needs to determine the type under the hood.

This means that first declaring a variable, then initializing it at a later stage won’t be accepted when using var .

Cannot initialize with lambdas or other poly expressions

This also means that initializing a var variable with a lambda or other poly expressions like method reference will fail, because how would you determine the type?

Cannot use var for method parameters

We also need to keep the classic type declaration for method parameters as well as for constructors.

Typical variables to change to var

Now that we got the limitations mapped out, let’s look at what you can do.

As long as the target type of the initialized value is clear, we can go ahead and change it to use var s.

You can reassign var s

You can reassigning a var variable.

One thing to be aware of is that even if you can re-assign a variable like in the example above, you cannot change the type. The type is determined by the first initialization, so an attempt to re-assign to another type would lead to an error.

Cleaning up loops

Loops are a typical place where you need to declare a local variable.

Let’s now clean up a few loops using the var syntax.

That’s it! Hopefully you now have a clear picture of how you can use the new var declaration type. If you want more information, checkout the proposal JEP-286.

Passionate developer located in Oslo. Writing about software development here at Dead Code Rising.

Washing your code: avoid reassigning variables

Reassigning variables is like changing the past. When you see:

You can’t be sure that your pizza will always have salami and jalapeños on it, because:

  • the variable can be reassigned with a new value, even a value of another type;
  • the value, if it’s an array or an object, can be mutated.

Knowing that both things are possible makes you think, every time you see pizza in the code, which value it has now. That’s a huge and unnecessary cognitive load that we should avoid.

And most of the time you can avoid both. Let’s start with reassigning and come back to mutation in the next chapter.

Don’t reuse variables

Sometimes a variable is reused to store different values:

Here the category variable is used to store a category ID, a list of products in a category, and a list of filtered products. Even types of theses values are different. This function isn’t completely hopeless because it’s short, but imagine more code between reassignments.

Also a new value is reassigned to a function parameter, known as function parameter shadowing. I think it’s no different from regular reassignment, so I’ll treat it the same way.

This case is the easiest to fix: we need to use separate variables for each value:

By doing this we’re making the lifespan of each variable shorter and choosing clearer names, so code is easier to understand and we’ll need to read less code to find out the current (and now the only) value of each variable.

Incremental computations

Probably the most common use case for reassignment is incremental computations. Consider this example:

true, validateVideoFileAndUrl = () => true, validateVideoURL = () => true —>

I’ve shortened the comments a bit, the original code had lines longer than 200 characters. If you have a very big screen, it looks like a pretty table, otherwise like an unreadable mess. Any autoformatting tool, like Prettier, will make an unreadable mess out of it too, so you shouldn’t rely on manual code formatting. It’s also really hard to maintain: if any “column” becomes longer than all existing “columns” after your changes, you have to adjust whitespace for all other “columns”.

Anyway, this code appends an error message to the errors string variable for every failed validation. But now it’s hard to see because the message formatting code is mangled with the validation code. This makes it hard to read and modify. To add another validation, you have to understand and copy the formatting code. Or to print errors as an HTML list, you have to change each line of this function.

Let’s separate validation and formatting:

true, validateVideoFileAndUrl = () => true, validateVideoURL = () => true —>

We’ve separated validations, validation logic and formatting. Flies separately, cutlets separately, as we say in Russia. Each piece of code has a single responsibility and a single reason to change. Validations now are defined declaratively and read like a table, not mixed with conditions and string concatenation. We’ve also changed negative conditions (is invalid?) to positive (is valid?). All this improves readability and maintainability of the code: it’s easier to see all validations and add new ones, because you don’t need to know implementation details of running validations or formatting.

And now it’s clear that the original code had a bug: there were no space between error messages.

Also now we can swap the formatting function and render errors as an HTML list, for example:

children, FileUpload = () => null, validateVideo = () => [‘Invalid video’] —>

We can also test each validation separately. Have you noticed that I’ve changed false to null in the last validation? That’s because match() returns null when there’s no match, not false . The original validation always returns true .

I would even inline ERROR_MESSAGES constants unless they are reused somewhere else. They don’t really make code easier to read but they make it harder to change, because you have to make changes in two places.

Now all the code you need to touch to add, remove or change validations is contained in the VIDEO_VALIDATIONS array. Keep the code, that’s likely to be changed at the same time, in the same place.

Building complex objects

Another common reason to reassign variables is to build a complex object:

new Intl.DateTimeFormat().format(x) const SORT_DESCENDING = ‘desc’, DATE_FORMAT = ‘YYYY-MM-DD’ const dateRangeFrom = new Date(2023, 1, 4), dateRangeTo = new Date(2023, 1, 14), sortField = ‘id’ const sortDirection = SORT_DESCENDING, query = » —>

Here we’re adding from and to properties only when they aren’t empty.

The code would be clearer if we teach our backend to ignore empty values and build the whole object at once:

new Intl.DateTimeFormat().format(x) const SORT_DESCENDING = ‘desc’, DATE_FORMAT = ‘YYYY-MM-DD’ const dateRangeFrom = new Date(2023, 1, 4), dateRangeTo = new Date(2023, 1, 14), sortField = ‘id’ const sortDirection = SORT_DESCENDING, query = » —>

Now the query object always have the same shape, but some properties can be undefined . The code feels more declarative and it’s easier to understand what it’s building an object, and see the final shape of this object.

Avoid Pascal style variables

Some people like to define all variables at the beginning of a function. I call this Pascal style, because in Pascal you have to declare all variables at the beginning of a program or a function:

Some people use this style in languages where they don’t have to do it:

Long variable lifespan makes you scroll a lot to understand the current value of a variable. Possible reassignments make it even worse. If there are 50 lines between a variable declaration and its usage, then it can be reassigned in any of these 50 lines.

We can make code more readable by moving variable declarations as close to their usage as possible and by avoiding reassignments:

We’ve shortened isFreeDelivery variable lifespan from 100 lines to just 10. Now it’s also clear that its value is the one we assign at the first line.

Don’t mix it with PascalCase though, this naming convention is still in use.

Avoid temporary variables for function return values

When variable is used to keep a function result, often you can get rid of that variable:

Here we’re checking that every event is valid, which would be more clear with the .every() array method:

We’ve also removed a temporary variable, avoided reassignment and made a condition positive (is valid?), instead of a negative (is invalid?). Positive conditions are usually easier to understand.

For local variables you can either use a ternary operator:

Or you can extract code to a function, for example:

This is less important. You may argue that moving code to a new function just because of a reassignment isn’t a great idea, and you may be right, so use your own judgement here.

Indeterminate loops

Sometimes having a reassignment is quite okay. Indeterminate loops, the ones where we don’t know the number of iterations in advance, are a good case for reassignments.

Consider this example:

Here we’re finding the start of the current week by moving one day back in a while loop and checking if it’s already Monday or not.

Even if it’s possible to avoid a reassignment here, it will likely make code less readable. Feel free to try and let me know how it goes though.

Reassignments aren’t pure evil and exterminating them all won’t make your code better. They are more like signs: if you see a reassignment, ask yourself if rewriting the code without it would make it more readable. There’s no right or wrong answer, but if you do use a reassignment, isolate it in a small function, where it’s clear what the current value of a variable is.

Help your brain with conventions

In all examples above I’m replacing let with const in variable declarations. This immediately tells the reader that the variable won’t be reassigned. And you can be sure, it won’t: the compiler will yell at you if you try. Every time you see let in the code, you know that this code is likely more complex and needs more brain power to understand.

Another useful convention is using UPPER_CASE names for constants. This tells the reader that this is more of a configuration value, than a result of some computation. Lifespan of such constants are usually large: often the whole module or even the whole codebase, so when you read the code you usually don’t see the constant definition, but you still can be sure that the value never changes. And using such a constant in a function doesn’t make the function not pure.

There’s an important difference between a variable defined with the const keyword and a true constant in JavaScript. The first only tells the compiler and the reader that the variable won’t be reassigned. The second describe the nature of the value as something global and static that never changes at runtime.

Both conventions reduce cognitive load a little bit and make code easier to understand.

Unfortunately JavaScript has no true constants, and mutation is still possible even when you define a variable with the const keyword. We’ll talk about mutations in the next chapter.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *