OpenSearch/docs/painless/painless-types.asciidoc

443 lines
17 KiB
Plaintext
Raw Normal View History

[[types]]
=== Data Types
Painless supports both dynamic and static types. Static types are split into
_primitive types_ and _reference types_.
[[dynamic-types]]
==== Dynamic Types
Painless supports one dynamic type: `def`. The `def` type can represent any
primitive or reference type. When you use the `def` type, it mimics the exact
behavior of whatever type it represents at runtime. The default value for the
def type is `null.`
Internally, if the `def` type represents a primitive type, it is converted to the
corresponding reference type. It still behaves like the primitive type, however,
including within the casting model. The `def` type can be assigned to different
types during the course of script execution.
IMPORTANT: Because a `def` type variable can be assigned to different types
during execution, type conversion errors that occur when using the `def` type
happen at runtime.
Using the `def` type can have a slight impact on performance. If performance is
critical, it's better to declare static types.
*Examples:*
[source,Java]
----
def x = 1; // Declare def variable x and set it to the
// literal int 1
def l = new ArrayList(); // Declare def variable l and set it a newly
// allocated ArrayList
----
[[primitive-types]]
==== Primitive Types
Primitive types are allocated directly onto the stack according to the standard
Java memory model.
Primitive types can behave as their corresponding (<<boxing-unboxing, boxed>>)
reference type. This means any piece of a reference type can be accessed or
called through the primitive type. Operations performed in this manner convert
the primitive type to its corresponding reference type at runtime and perform
the field access or method call without needing to perform any other
operations.
Painless supports the following primitive types.
byte::
An 8-bit, signed, two's complement integer.
Range: [-128, 127].
Default value: 0.
Reference type: Byte.
short::
A 16-bit, signed, two's complement integer.
Range: [-32768, 32767].
Default value: 0.
Reference type: Short.
char::
A 16-bit Unicode character.
Range: [0, 65535].
Default value: 0 or `\u0000`.
Reference type: Character.
int::
A 32-bit, signed, two's complement integer.
Range: [-2^32, 2^32-1].
Default value: 0.
Reference type: Integer.
long::
A 64-bit, signed, two's complement integer.
Range: [-2^64, 2^64-1].
Default value: 0.
Reference type: Long.
float::
A 32-bit, single-precision, IEEE 754 floating point number.
Range: Depends on multiple factors.
Default value: 0.0.
Reference type: Float.
double::
A 64-bit, double-precision, IEEE 754 floating point number.
Range: Depends on multiple factors.
Default value: 0.0.
Reference type: Double.
boolean::
A logical quanity with two possible values: true and false.
Range: true/false.
Default value: false.
Reference type: Boolean.
*Examples:*
[source,Java]
----
int i = 1; // Declare variable i as an int and set it to the
// literal 1
double d; // Declare variable d as a double and set it to the
// default value of 0.0
boolean b = true; // Declare variable b as a boolean and set it to true
----
Using methods from the corresponding reference type on a primitive type.
[source,Java]
----
int i = 1; // Declare variable i as an int and set it to the
// literal 1
i.toString(); // Invokes the Integer method toString on variable i
----
[[reference-types]]
==== Reference Types
Reference types are similar to Java classes and can contain multiple pieces
known as _members_. However, reference types do not support access modifiers.
You allocate reference type instances on the heap using the `new` operator.
Reference types can have both static and non-static members:
* Static members are shared by all instances of the same reference type and
can be accessed without allocating an instance of the reference type. For
example `Integer.MAX_VALUE`.
* Non-static members are specific to an instance of the reference type
and can only be accessed through the allocated instance.
The default value for a reference type is `null`, indicating that no memory has
been allocated for it. When you assign `null` to a reference type, its previous
value is discarded and garbage collected in accordance with the Java memory
model as long as there are no other references to that value.
A reference type can contain:
* Zero to many primitive types. Primitive type members can be static or
non-static and read-only or read-write.
* Zero to many reference types. Reference type members can be static or
non-static and read-only or read-write.
* Methods that call an internal function to return a value and/or manipulate
the primitive or reference type members. Method members can be static or
non-static.
* Constructors that call an internal function to return a newly-allocated
reference type instance. Constructors are non-static methods that can
optionally manipulate the primitive and reference type members.
Reference types support a Java-style inheritance model. Consider types A and B.
Type A is considered to be a parent of B, and B a child of A, if B inherits
(is able to access as its own) all of A's fields and methods. Type B is
considered a descendant of A if there exists a recursive parent-child
relationship from B to A with none to many types in between. In this case, B
inherits all of A's fields and methods along with all of the fields and
methods of the types in between. Type B is also considered to be a type A
in both relationships.
For the complete list of Painless reference types and their supported methods,
see the https://www.elastic.co/guide/en/elasticsearch/reference/current/painless-api-reference.html[Painless API Reference].
For more information about working with reference types, see
<<field-access, Accessing Fields>> and <<method-access, Calling Methods>>.
*Examples:*
[source,Java]
----
ArrayList al = new ArrayList(); // Declare variable al as an ArrayList and
// set it to a newly allocated ArrayList
List l = new ArrayList(); // Declare variable l as a List and set
// it to a newly allocated ArrayList, which is
// allowed because ArrayList inherits from List
Map m; // Declare variable m as a Map and set it
// to the default value of null
----
Directly accessing static pieces of a reference type.
[source,Java]
----
Integer.MAX_VALUE // a static field access
Long.parseLong("123L") // a static function call
----
[[string-type]]
==== String Type
A `String` is a specialized reference type that is immutable and does not have
to be explicitly allocated. You can directly assign to a `String` without first
allocating it with the `new` keyword. (Strings can be allocated with the `new`
keyword, but it's not required.)
When assigning a value to a `String`, you must enclose the text in single or
double quotes. Strings are allocated according to the standard Java Memory Model.
The default value for a `String` is `null.`
*Examples:*
[source,Java]
----
String r = "some text"; // Declare String r and set it to the
// String "some text"
String s = 'some text'; // Declare String s and set it to the
// String 'some text'
String t = new String("some text"); // Declare String t and set it to the
// String "some text"
String u; // Declare String u and set it to the
// default value null
----
[[void-type]]
==== void Type
The `void` type represents the concept of no type. In Painless, `void` declares
that a function has no return value.
[[array-type]]
==== Array Type
Arrays contain a series of elements of the same type that can be allocated
simultaneously. Painless supports both single and multi-dimensional arrays for
all types except void (including `def`).
You declare an array by specifying a type followed by a series of empty brackets,
where each set of brackets represents a dimension. Declared arrays have a default
value of `null` and are themselves a reference type.
To allocate an array, you use the `new` keyword followed by the type and a
set of brackets for each dimension. You can explicitly define the size of each dimension by specifying an expression within the brackets, or initialize each
dimension with the desired number of values. The allocated size of each
dimension is its permanent size.
To initialize an array, specify the values you want to initialize
each dimension with as a comma-separated list of expressions enclosed in braces.
For example, `new int[] {1, 2, 3}` creates a one-dimensional `int` array with a
size of 3 and the values 1, 2, and 3.
When you initialize an array, the order of the expressions is maintained. Each expression used as part of the initialization is converted to the
array's type. An error occurs if the types do not match.
*Grammar:*
[source,ANTLR4]
----
declare_array: TYPE ('[' ']')+;
array_initialization: 'new' TYPE '[' ']' '{' expression (',' expression) '}'
| 'new' TYPE '[' ']' '{' '}';
----
*Examples:*
[source,Java]
----
int[] x = new int[5]; // Declare int array x and assign it a newly
// allocated int array with a size of 5
def[][] y = new def[5][5]; // Declare the 2-dimensional def array y and
// assign it a newly allocated 2-dimensional
// array where both dimensions have a size of 5
int[] x = new int[] {1, 2, 3}; // Declare int array x and set it to an int
// array with values 1, 2, 3 and a size of 3
int i = 1;
long l = 2L;
float f = 3.0F;
double d = 4.0;
String s = "5";
def[] da = new def[] {i, l, f*d, s}; // Declare def array da and set it to
// a def array with a size of 4 and the
// values i, l, f*d, and s
----
[[casting]]
=== Casting
Casting is the conversion of one type to another. Implicit casts are casts that
occur automatically, such as during an assignment operation. Explicit casts are
casts where you use the casting operator to explicitly convert one type to
another. This is necessary during operations where the cast cannot be inferred.
To cast to a new type, precede the expression by the new type enclosed in
parentheses, for example
`(int)x`.
The following sections specify the implicit casts that can be performed and the
explicit casts that are allowed. The only other permitted cast is casting
a single character `String` to a `char`.
*Grammar:*
[source,ANTLR4]
----
cast: '(' TYPE ')' expression
----
[[numeric-casting]]
==== Numeric Casting
The following table shows the allowed implicit and explicit casts between
numeric types. Read the table by row. To find out if you need to explicitly
cast from type A to type B, find the row for type A and scan across to the
column for type B.
IMPORTANT: Explicit casts between numeric types can result in some data loss. A
smaller numeric type cannot necessarily accommodate the value from a larger
numeric type. You might also lose precision when casting from integer types
to floating point types.
|====
| | byte | short | char | int | long | float | double
| byte | | implicit | implicit | implicit | implicit | implicit | implicit
| short | explicit | | explicit | implicit | implicit | implicit | implicit
| char | explicit | explicit | | implicit | implicit | implicit | implicit
| int | explicit | explicit | explicit | | implicit | implicit | implicit
| long | explicit | explicit | explicit | explicit | | implicit | implicit
| float | explicit | explicit | explicit | explicit | explicit | | implicit
| double | explicit | explicit | explicit | explicit | explicit | explicit |
|====
Example(s)
[source,Java]
----
int a = 1; // Declare int variable a and set it to the literal
// value 1
long b = a; // Declare long variable b and set it to int variable
// a with an implicit cast to convert from int to long
short c = (short)b; // Declare short variable c, explicitly cast b to a
// short, and assign b to c
byte d = a; // ERROR: Casting an int to a byte requires an explicit
// cast
double e = (double)a; // Explicitly cast int variable a to a double and assign
// it to the double variable e. The explicit cast is
// allowed, but it is not necessary.
----
[[reference-casting]]
==== Reference Casting
A reference type can be implicitly cast to another reference type as long as
the type being cast _from_ is a descendant of the type being cast _to_. A
reference type can be explicitly cast _to_ if the type being cast to is a
descendant of the type being cast _from_.
*Examples:*
[source,Java]
----
List x; // Declare List variable x
ArrayList y = new ArrayList(); // Declare ArrayList variable y and assign it a
// newly allocated ArrayList [1]
x = y; // Assign Arraylist y to List x using an
// implicit cast
y = (ArrayList)x; // Explicitly cast List x to an ArrayList and
// assign it to ArrayList y
x = (List)y; // Set List x to ArrayList y using an explicit
// cast (the explicit cast is not necessary)
y = x; // ERROR: List x cannot be implicitly cast to
// an ArrayList, an explicit cast is required
Map m = y; // ERROR: Cannot implicitly or explicitly cast [2]
// an ArrayList to a Map, no relationship
// exists between the two types.
----
[1] `ArrayList` is a descendant of the `List` type.
[2] `Map` is unrelated to the `List` and `ArrayList` types.
[[def-type-casting]]
==== def Type Casting
All primitive and reference types can always be implicitly cast to
`def`. While it is possible to explicitly cast to `def`, it is not necessary.
However, it is not always possible to implicitly cast a `def` to other
primitive and reference types. An explicit cast is required if an explicit
cast would normally be required between the non-def types.
*Examples:*
[source,Java]
----
def x; // Declare def variable x and set it to null
x = 3; // Set the def variable x to the literal 3 with an implicit
// cast from int to def
double a = x; // Declare double variable a and set it to def variable x,
// which contains a double
int b = x; // ERROR: Results in a run-time error because an explicit cast is
// required to cast from a double to an int
int c = (int)x; // Declare int variable c, explicitly cast def variable x to an
// int, and assign x to c
----
[[boxing-unboxing]]
==== Boxing and Unboxing
Boxing is where a cast is used to convert a primitive type to its corresponding
reference type. Unboxing is the reverse, converting a reference type to the
corresponding primitive type.
There are two places Painless performs implicit boxing and unboxing:
* When you call methods, Painless automatically boxes and unboxes arguments
so you can specify either primitive types or their corresponding reference
types.
* When you use the `def` type, Painless automatically boxes and unboxes as
needed when converting to and from `def`.
The casting operator does not support any way to explicitly box a primitive
type or unbox a reference type.
If a primitive type needs to be converted to a reference type, the Painless
reference type API supports methods that can do that. However, under normal
circumstances this should not be necessary.
*Examples:*
[source,Java]
----
Integer x = 1; // ERROR: not a legal implicit cast
Integer y = (Integer)1; // ERROR: not a legal explicit cast
int a = new Integer(1); // ERROR: not a legal implicit cast
int b = (int)new Integer(1); // ERROR: not a legal explicit cast
----
[[promotion]]
==== Promotion
Promotion is where certain operations require types to be either a minimum
numerical type or for two (or more) types to be equivalent.
The documentation for each operation that has these requirements
includes promotion tables that describe how this is handled.
When an operation promotes a type or types, the resultant type
of the operation is the promoted type. Types can be promoted to def
at compile-time; however, at run-time, the resultant type will be the
promotion of the types the `def` is representing.
*Examples:*
[source,Java]
----
2 + 2.0 // Add the literal int 2 and the literal double 2.0. The literal
// 2 is promoted to a double and the resulting value is a double.
def x = 1; // Declare def variable x and set it to the literal int 1 through
// an implicit cast
x + 2.0F // Add def variable x and the literal float 2.0.
// At compile-time the types are promoted to def.
// At run-time the types are promoted to float.
----