java全局变量和局部变量_Java 10中的局部变量类型推断

java全局变量和局部变量

Java is often criticized as being too verbose. One aspect contributing to this characterization is the requirement to specify every type explicitly, which leads to a lot of additional noise.

Java通常被批评为过于冗长。 促成此特性的一个方面是要求明确指定每种类型,这会导致很多额外的噪音

A new way of declaring local variables with less clutter was given to us with JDK 10 and JEP 286: local variable type inference.

JDK 10和JEP 286为我们提供了一种使杂乱的地方声明得更少的新方法: 局部变量类型推断

TABLE OF CONTENTSGeneral ConceptReading Vs. Writing CodeExplicit ContextImplicit ContextCaveatsConclusionResources

一般概念 (General Concept)

The name itself perfectly describes the feature’s core and its limitations:

该名称本身完美地描述了功能的核心及其局限性:

局部变量 (Local variable)

  • Only local variables are supported

    仅支持局部变量
  • No methods parameters

    无方法参数
  • No return types

    没有退货类型
  • No fields

    没有领域
  • No lambdas

    没有lambdas

类型推断 (Type inference)

The Java compiler will automatically detect the correct type for us.

Java编译器将自动为我们检测正确的类型。

Actually, this isn’t a completely new feature for the JDK. Inferred types were already supported by the diamond operator <> (JDK 7), and lambdas can infer their argument types (JDK 8):

实际上,这不是JDK的全新功能。 钻石运算符<> (JDK 7)已经支持推断的类型,lambda可以推断其参数类型(JDK 8):

List<String> data = new ArrayList<>();


BinaryOperator<Integer> sumFn = (a, b) -> a + b;

This doesn’t mean Java suddenly became dynamically typed. All types will be inferred at compile time, not runtime, providing us with the same safety as before.

这并不意味着Java突然变成了动态类型 。 所有类型都将在编译时而不是运行时进行推断,从而为我们提供与以前相同的安全性。

如何使用' var' (How to use ‘var')

The basic idea is simple: When we initialize a local variable, we can replace the explicitly stated left-hand type with the newly introduced reversed-type name var instead. All the information for the inferred type must be provided by the initializer (e.g., constructor, literal, method return value):

基本思想很简单:初始化局部变量时,可以用新引入的反向类型名称var代替显式声明的左侧类型。 推断类型的所有信息必须由初始化程序提供(例如,构造函数,文字,方法返回值):

// WITHOUT INFERENCE
// {type} {variableName} = {initializer};
Customer customer = new Customer();


// WITH INFERENCE
// var {variableName} = {initializer};
var customer = new Customer();

With var not being a keyword, we can also use final to make the variable not reassignable.

如果var不是关键字,我们也可以使用final来使变量不可重新分配。

可能会有一些限制 (Some restrictions may apply)

The type must be inferable at the initialization of a variable, so null isn’t allowed:

该类型必须在变量初始化时是可推断的,因此不允许null

var customer = null; // ERROR


customer = new Customer();

Lambdas can also not be represented with var, at least not without explicit casting. Due to lambdas being represented by concrete functional interfaces behind the scenes, the type can't be inferred without additional context:

Lambda也不能用var表示,至少在没有显式转换的情况下也是如此。 由于lambda由幕后的具体功能接口表示,因此如果没有其他上下文,就无法推断出类型:

// NO EXPLICIT TYPE CAN BE INFERRED
var sumFn = (Integer a, Integer b) -> a + b; // ERROR


// COMPILES, BUT ISN'T AN IMPROVEMENT
var sumFn = (BinaryOperator<Integer>) (a, b) -> a + b;

By casting it to an explicit interface, we might be able to make the compiler happy. But this abomination isn’t in the intended spirit of var.

通过将其强制转换为显式接口,我们也许可以使编译器满意。 但是这种可憎性并不是var的本意。

阅读与 编写代码 (Reading Vs. Writing Code)

Our code will be read way more often than it’ll be written.

我们的代码将比被编写的更多地被阅读。

While writing code, all the context is still right there. Whenever someone reads our code, even ourselves, we need to be able to reason with it.

在编写代码时,所有上下文仍然在那里。 每当有人甚至是我们自己阅读我们的代码时,我们都需要能够对此进行推理。

“Programs are meant to be read by humans and only incidentally for computers to execute.” — Donald Knuth

“程序只能被人类读取,并且只能由计算机执行。” —唐纳德·努斯

The verbosity of Java can be a mental burden by confronting us with more code than is actually needed to comprehend it. var can help to reduce this additional, often redundant information. But not all code removed by it might be redundant. It might remove any indicators of the bigger context.

Java的冗长性可能会给我们带来比实际理解更多的代码,这可能是一个精神负担。 var可以帮助减少这些额外的,通常是冗余的信息。 但是并非所有删除的代码都可能是多余的。 它可能会删除任何有关大背景的指标。

To reason with code means understanding its context and impact. If we remove the explicit type information, we need to make sure the context can be deducted in other ways. Just because the compiler might infer the correct types doesn’t automatically mean we, as humans, can do it.

用代码推理意味着理解其上下文和影响。 如果删除显式类型信息,则需要确保可以以其他方式推论上下文。 仅仅因为编译器可以推断出正确的类型并不意味着我们作为人类就可以做到。

We should always be able to comprehend code in its local scope without knowing the complete bigger picture surrounding it. This is why not every local variable declaration should be using var.

我们应该始终能够在本地范围内理解代码,而无需了解围绕它的完整大图。 这就是为什么不是每个局部变量声明都应使用var

显式上下文 (Explicit Context)

Excellent use cases for var are constructs already containing explicit context: constructors, literals, and static factory methods.

var绝佳用例是已经包含显式上下文的构造:构造函数,文字和静态工厂方法。

建设者 (Constructors)

Constructors are made up of their type so not much more information is possible:

构造函数由其类型组成,因此不可能提供更多信息:

var customer = new Customer("John Doe");


var vatTax = new BigDecimal("0.19");


var joiner = new StringJoiner(" ");

No additional context needed.

无需其他上下文。

文字 (Literals)

Literals can provide all the context needed to infer the correct type if we comply with their special notations:

如果我们遵守字面量的特殊表示法,则字面量可以提供推断正确类型所需的所有上下文:

// String => double-quoted
var message = "Hello, World!";


// char => single-quoted
var bullet = '\u2022';


// int => whole number
var dozen = 12;


// long => ends with "L/l"
var kb = 1_024L;


// double => contains decimal point, optional "d/D"
var tax = 0.19; // double
var reducedTax = 0.07d; // double


// float => contains decimal point and ends with "f/F"
var threeQuarter = 0.75f

If we don’t comply, literals could be inferred as another type. In practice, they still might work due to implicit casting. But the actual type would be wrong:

如果我们不遵守,字面量可能会被推断为另一种类型。 实际上,由于隐式强制转换,它们仍然可以工作。 但是实际类型是错误的:

long kb = 1_024;
// vs.
var kb = 1_024; // Inferred as int


float threeQuarter = 0.75;
// vs.
var threeQuarter = 0.75; // Inferred as double

The literals byte and short don’t have special indicators, so they always will be inferred to int instead.

字面量byteshort没有特殊的指示符,因此它们总是会被推断为int

静态工厂方法 (Static factory methods)

Many types contain static factory methods, providing just as much information as a constructor, either by the class name or the factory-method name:

许多类型包含静态工厂方法,它们通过类名或工厂方法名提供与构造函数一样多的信息:

// List<String>
var names = List.of("Albattani",
                    "Bell",
                    "Boyd",
                    "Gauss");


// Long
var kb = Long.valueOf("1024");


// BufferedReader
var reader = Files.newBufferedReader(...);

内隐语境 (Implicit Context)

Another aspect of var is the ability to replace unnecessary information.

var另一个方面是能够替换不必要的信息。

中间值 (Intermediate values)

Local variables are an easy and cheap way of storing intermediate values in a narrow scope. For understanding the context, the actual type these variables are might not be as crucial as its name and its surroundings:

局部变量是在狭窄范围内存储中间值的简便且廉价的方法。 为了理解上下文,这些变量的实际类型可能不如其名称和周围环境那么关键:

Customer customer = new Customer("John Doe");


Map<String, List<Order>> result = loadOrdersByCategory(customer);


for (Map.Entry<String, List<Order>> entry : result.entrySet()) {


    List<Order> orders = entry.getValue();
    
    // ...
}

By using better variable names, we can rely on var and still grasp the context:

通过使用更好的变量名,我们可以依靠var并仍然掌握上下文:

var customer = new Customer("John Doe");


var ordersByCategoryMap = loadOrdersByCategory(customer);


for (var entry : ordersByCategoryMap.entrySet()) {


    var orders = entry.getValue();


    // ...
}

循环 (Loops)

As seen before, loops can be simplified with var. Usually the surrounding context provides enough information, so we don't need explicit types anymore:

如前所述,可以使用var简化循环。 通常,周围的上下文会提供足够的信息,因此我们不再需要显式类型:

// List<String>
var names = List.of("Albattani",
                    "Bell",
                    "Boyd",
                    "Gauss");


for (var name : names) {
    // ...
}

“尝试资源” (‘try-with-resources’)

A try-with-resources block can be very verbose. But thanks to var, we can make them more concise:

try-with-resources块可能非常冗长。 但是感谢var ,我们可以使它们更简洁:

// WITHOUT TYPE INFERENCE
try (FileReader fileReader = new FileReader(...);
     BufferedReader bufferedReader = new BufferedReader(fileReader)) {


    // ...
}


// WITH TYPE INFERENCE
try (var fileReader = new FileReader(...);
     var bufferedReader = new BufferedReader(fileReader)) {


    // ...
}

泛型 (Generics)

Generic type declarations, especially, can be a mouthful. A simple Iterator can be as complicated as:

特别是泛型类型声明可能会很麻烦。 一个简单的Iterator可能非常复杂:

void removeIfLonger(Map<? extends String, ? extends String> map, int maxLength) {


    for (Iterator<? extends Map.Entry<? extends String, ? extends String>> iter = map.entrySet().iterator(); iter.hasNext();) {
  
        Map.Entry<? extends String, ? extends String> entry = iter.next();
        if (entry.getValue().length() > maxLength) {
            iter.remove();
        }
    }
}

By knowing what type the Map is, we don’t need to bother with the explicit types of the iterator or the entry:

通过知道Map是什么类型,我们无需理会迭代器或条目的显式类型:

void removeIfLonger(Map<? extends String, ? extends String> map, int maxLength) {


    for (var iter = map.entrySet().iterator(); iter.hasNext();) {
        var entry = iter.next();
        if (entry.getValue().length() > maxLength) {
            iter.remove();
        }
    }
}

This is mucheasier on the eyes and still as understandable as before.

这在眼睛上更加轻松,并且仍然像以前一样容易理解。

注意事项 (Caveats)

Besides overusing var and destroying valuable bits of information, there are multiple caveats to be aware of.

除了过度使用var并破坏有价值的信息外,还有许多注意事项。

钻石算子 (Diamond operator)

As mentioned before, the diamond operator <> already provides us with type inference. The compiler infers the right-hand type based on the left-hand declaration:

如前所述,菱形运算符<>已经为我们提供了类型推断。 编译器根据左侧声明来推断右侧类型:

// No additional type required for ArrayList
List<String> values = new ArrayList<>();

With var, we no longer have this information and must provide it ourselves in the initializer:

使用var ,我们不再有此信息,必须在初始化程序中自行提供:

// ArrayList<String>
var values = new ArrayList<String>();

That doesn’t mean we can’t use the diamond operator at all, though. If the initializer provides enough information due to other circumstances, the compiler can infer the correct type:

但这并不意味着我们根本无法使用菱形运算符。 如果初始化程序由于其他情况而提供了足够的信息,则编译器可以推断出正确的类型:

Comparator<String> comparator = (lhs, rhs) -> ...;


// PriorityQueue<String>
var queue = new PriorityQueue<>(comparator);

界面与具体类型 (Interface vs. concrete types)

Usually we code against interfaces and not concrete implementations:

通常我们针对接口而不是具体的实现进行编码:

List<String> values = new ArrayList<>();

This abstraction provides us with a lot of flexibility for future changes. But with type inference we only get the type of the initializer, not its interface:

这种抽象为我们提供了很大的灵活性,可以应对未来的变化。 但是通过类型推断,我们只能得到初始化器的类型,而不是其接口:

// ArrayList<String>
var values = new ArrayList<String>();

By being only available for local variables, this shouldn’t impose much of a problem. We still should code against abstractions for public interfaces. But in a local scope, it doesn’t matter as much.

通过仅可用于局部变量,这不会造成太大的问题。 我们仍然应该针对公共接口的抽象进行编码。 但是在本地范围内,这并不重要。

文字 (Literals)

As mentioned before, literals need additional context via type-specific indicators to be correctly inferred:

如前所述,文字需要通过特定于类型的指示符来提供其他上下文,以便正确推断:

 TYPE   | INDICATOR
------- | ------------------------------------
String | double-quoted
char | single-quoted
int | whole number
long | whole number ending with "L/l"
float | decimal number ending with "f/F"
double | decimal number, optional "d/D"
byte | no indicator, always inferred to int
short | no indicator, always inferred to int

结论 (Conclusion)

More type inference is a great addition to Java. Static compile-time type safety paired with less typing and more concise code is a win-win. Omitting the explicit type reduces clutter, as long as we have other bits of information to deduce the context.

更多类型推断是Java的重要补充。 静态编译时类型安全性与更少的键入和更简洁的代码相结合是双赢的。 只要我们还有其他信息来推断上下文,省略显式类型可以减少混乱。

But there’s more to it than just replacing an existing type declaration with var indiscriminately. Code must be reasoned with thanks to its surrounding context.

但是,它还有很多功能,而不仅仅是用var随意替换现有的类型声明。 由于其周围的上下文,必须对代码进行推理。

We can improve this context by choosing better variable names and narrowing the scope of intermediate values. If understandability is still impaired by using type inference, it can be an indicator for deeper structural problems, and var might not be the best way to go.

我们可以通过选择更好的变量名并缩小中间值的范围来改善这种情况。 如果使用类型推断仍然无法理解,则它可以指示更深层次的结构问题,而var可能不是最佳选择。

翻译自: https://medium.com/better-programming/local-variable-type-inference-in-java-10-cb4967dd6eb0

java全局变量和局部变量

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值