443 lines
17 KiB
Plaintext
443 lines
17 KiB
Plaintext
[[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
|
|
| float | 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 y 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.
|
|
----
|