⬆️ ⬇️

Oracle Certified Professional Java Programmer Exam Preparation - Part 1

Foreword





I want to continue to share the acquired knowledge and my impressions of the preparation for the exam. Many thanks to all those who gave recommendations to the zero part of this series! Today I’ll talk a little more about access modifiers and their relationships with inheritance and packages, consider the varargs and enumerations, as well as arrays and how to initialize them. I hope that habrazhiteli again respond and complement what I forgot to mention or simply did not know.



We continue to prepare for the exam under the cut.

')





Content for the entire series



  1. Identifiers, naming rules, modifiers for classes and interfaces
  2. Modifiers for methods and fields, vararg, enum and arrays




Methods, fields, local variables and their modifiers





As I said before, in Java there are four access modifiers: public, private, protected, and the absence of a modifier (also known as the default modifier). Only two of them are applicable to non-nested classes and interfaces: public and the default modifier. The entire set applies to the methods and fields of the class.



  1. If a method or field has a public modifier, then they are potentially accessible to the entire universe.
  2. If a method or field has a private access modifier, then they are available only within the class. Such class members are not inherited, so they cannot be replaced in subclasses. Remember this.
  3. If the method or field has a default access modifier, then they are available only within the package.
  4. If a method or a field has a protected access modifier, then they are primarily accessible to the class itself and its heirs. In addition, access to these members of the class can get their fellow package.




Checking the code proposed in the exam, you should be careful. Always pay attention to both the access modifier of a method or a field, and the class access modifier. You can often find a situation where the method has a public modifier, while the class containing it is available only from a package. In this situation, the method from outside the package will not be available. You can easily get a minus without paying attention to this detail.



I also want to draw attention to some features that arise when using the default access and the protected modifier. Consider the following example. Suppose there is a base class declared in the test package. This class has two fields. The first is declared with default access, the second is protected.



 package org.kimrgrey.scjp.test; public class BaseClass { int defaultValue; protected int protectedValue; public BaseClass() { this.defaultValue = 1; this.protectedValue = 1; } } 
    

package org.kimrgrey.scjp.test; public class BaseClass { int defaultValue; protected int protectedValue; public BaseClass() { this.defaultValue = 1; this.protectedValue = 1; } }



If you declare a SamePackageAccess class in this package that does not inherit from BaseClass, it will still have access to both the defaultValue field and the protectedValue field. It is worth remembering about this feature of the protected modifier: class members declared as protected within the package are available both through inheritance and through a link. Example:



 package org.kimrgrey.scjp.test; public class SamePackageAccess { public SamePackageAccess() { BaseClass a = new BaseClass(); a.defaultValue = 2; a.protectedValue = 2; } } 
    

package org.kimrgrey.scjp.test; public class SamePackageAccess { public SamePackageAccess() { BaseClass a = new BaseClass(); a.defaultValue = 2; a.protectedValue = 2; } }



In the case of inheritance in this package, access is still stored to both fields, both by reference and by inheritance.



 package org.kimrgrey.scjp.test; public class SamePackageSubclass extends BaseClass { public SamePackageSubclass() { this.defaultValue = 3; this.protectedValue = 3; BaseClass a = new BaseClass(); a.defaultValue = 3; a.protectedValue = 3; } } 
    

package org.kimrgrey.scjp.test; public class SamePackageSubclass extends BaseClass { public SamePackageSubclass() { this.defaultValue = 3; this.protectedValue = 3; BaseClass a = new BaseClass(); a.defaultValue = 3; a.protectedValue = 3; } }



Now let's see what happens if we go beyond the package. The first thing that happens is that we will lose access to the field declared without explicitly specifying the access modifier. Absolutely all classes outside the native package, including the direct heirs of BaseClass, will not see it. The field with the protected modifier will be available through inheritance to all its subclasses. However, even the heir will not be able to use it through the link. In addition, being once an inherited class outside a package, the field becomes closed for any classes, with the exception of further heirs.



 package org.kimrgrey.scjp.main; import org.kimrgrey.scjp.test.BaseClass; public class OtherPackageSubclass extends BaseClass { public OtherPackageSubclass() { this.defaultValue = 10; // Line 8:   ,     this.protectedValue = 10; BaseClass a = new BaseClass(); a.protectedValue = 10; // Line 12:        BaseClass } } 
    

package org.kimrgrey.scjp.main; import org.kimrgrey.scjp.test.BaseClass; public class OtherPackageSubclass extends BaseClass { public OtherPackageSubclass() { this.defaultValue = 10; // Line 8: , this.protectedValue = 10; BaseClass a = new BaseClass(); a.protectedValue = 10; // Line 12: BaseClass } }



This example also contains another important detail. Suppose you are asked what happens if you compile the above code? And give the following answer options:

  1. The code will be successfully compiled
  2. There will be a compilation error on line number 8
  3. There will be a compilation error on line number 12


In this case, unless explicitly stated otherwise, you need to select all the correct answers: 2 and 3, and not stop at the first one that suits you. Carefully read all the answers and check them for correctness. It is this approach that works best. Having read and understood the essence of the question, check and analyze the answers, and not the code given in the wording.



Among the modifiers associated with inheritance, you should also consider final. On methods final acts the same as on classes: it prohibits their redefinition by heirs. At the same time, it is still possible to extend the class itself, in which the final method is located.



It is allowed to apply the final modifier to fields, method arguments and local variables. In the case of primitive types, any change in the value of the variable, beyond its initialization, will be prohibited. Here it should be remembered that the moment of initialization of local variables is considered the first assignment of values ​​to them within the method. Until this variable can not be used: get an error when compiling. The field marked final must also be explicitly initialized. This can be done either directly during the declaration, in the initialization block, or in the constructor of the class in which it is declared. Leaving initialization of final fields on the heirs' conscience is not allowed. In the case of references, the final modifier will not reassign the reference. The object to which the link points can still be modified: call methods changing its state, assign a new value to fields, and so on.



It is important to remember that no modifiers except final are applicable to local variables. Therefore, if you see something like private int a in the declaration of a local variable, then we can safely say that it will not compile. And what about the fields?

  1. As I have already said, all four access levels are applicable to the fields.
  2. A field may be marked as final.
  3. The field may be marked transient.
  4. The field can be marked as static.
  5. A field may be marked as volatile.
  6. The field cannot be marked as abstract.
  7. The field cannot be marked as synchronized.
  8. The field cannot be marked as strictfp.
  9. The field cannot be marked as native.


Some of the modifiers mentioned above, I have not described before. I will try to consider them later, in the relevant topics (transient will be considered as part of serialization, and synchronized and volatile will be considered in multithreading).



Methods with a variable number of arguments



  1. When you specify the vararg parameter, the base type can be any type: primitive or not.
  2. To declare such a parameter, you write a type, then three points, a space, then the name of the array that will be used in the framework of the method: void f (int... a) . You can also separate the type, three points and identifiers by spaces, like this: void f(int ... a) . Watch carefully for the points. The authors of the exam like to transfer them for the ID. This approach does not work.
  3. Other parameters can be passed to the method, but in this case the vararg parameter should be the last: void f (double x, int... a)
  4. In the method there can be one and only one vararg parameter.


For clarity, I will give a good example of a question on this topic. Choose such a declaration of the doSomething () method so that the code below is successfully compiled?



 package org.kimrgrey.scjp.main; public class Application { public static void main(String[] args) { doSomething(1); doSomething(1, 2); } } 
    

package org.kimrgrey.scjp.main; public class Application { public static void main(String[] args) { doSomething(1); doSomething(1, 2); } }



  1. static void doSomething(int... values) {}
  2. static void doSomething(int[] values) {}
  3. static void doSomething(int x, int... values) {}




The first and third options are correct. Both are correct both in terms of call semantics and in terms of syntax. Using the same array as a type to transfer several parameters is not so easy. But the opposite is not true. Example:



 package org.kimrgrey.scjp.main; public class Application { private static void f (int... a) { for (int i = 0; i < a.length; ++i) { System.out.println(a[i]); } } public static void main(String[] args) { f(new int[] {1, 2 ,3}); } } 
    

package org.kimrgrey.scjp.main; public class Application { private static void f (int... a) { for (int i = 0; i < a.length; ++i) { System.out.println(a[i]); } } public static void main(String[] args) { f(new int[] {1, 2 ,3}); } }



Everything is going to work wonderfully. I do not know a formal explanation for this, but I assume that this is due to the fact that the vararg parameter is only syntactic sugar and is perceived by the compiler as a link to an array, so no problems arise.



Transfers



  1. Enums may have constructors.
  2. Enumerations can have fields.
  3. Enums may have methods.
  4. If an enumeration is declared outside the class, it can only get two levels of access: public or default.
  5. Enums have a static values() method, which returns an array containing all possible enumeration values, strictly in the order in which they were declared.


For each of the values ​​in the enumeration, you can declare your own “body” - its specific description. In this case, the value-specific versions of the methods overload the variant that is used for the entire enumeration as a whole. This allows you to change the behavior of enumeration members depending on the needs of the application. Example:



 package org.kimrgrey.scjp.main; import static java.lang.System.*; enum Currency { UNKNOWN, USD { public String getStringCode() { return "USD"; } public int getSomethingElse() { return 10; } }, UAH { public String getStringCode() { return "UAH"; } }, RUR { public String getStringCode() { return "RUR"; } }; public String getStringCode() { return ""; } } public class Application { private static void f (int... a) { for (int i = 0; i < a.length; ++i) { out.println(a[i]); } } public static void main(String[] args) { out.println(Currency.USD.getStringCode()); // out.println(Currency.USD.getSomethingElse()); } } 
    

package org.kimrgrey.scjp.main; import static java.lang.System.*; enum Currency { UNKNOWN, USD { public String getStringCode() { return "USD"; } public int getSomethingElse() { return 10; } }, UAH { public String getStringCode() { return "UAH"; } }, RUR { public String getStringCode() { return "RUR"; } }; public String getStringCode() { return ""; } } public class Application { private static void f (int... a) { for (int i = 0; i < a.length; ++i) { out.println(a[i]); } } public static void main(String[] args) { out.println(Currency.USD.getStringCode()); // out.println(Currency.USD.getSomethingElse()); } }





As a result of executing this code, the string “USD” will be placed in the standard output stream. Notice the getSomethingElse () method. It is declared for the value of USD, but is not mentioned for the entire transfer. Despite the fact that the ad is public, no one from outside can get access to this method. If line number 44 is uncommented, the code will not even compile.



Little bit about arrays





In Java, there are two options for declaring arrays. Square brackets can be placed after a type name, like this: int[] a , - or after an identifier like this: int a[] . It is important to understand that both methods are absolutely equal in terms of syntax, although the first one is recommended. Thus, String[] s[] is nothing but a two-dimensional array of strings. Compiled without question.



When declaring an array, you cannot specify its size, since memory is allocated only at the moment of creating the array: int[] a = new int [4] . Therefore, the int a[4] code will cause a compilation error. In the case of an array of references to objects, it is important to remember that when creating an array, the objects themselves are not created. For example, the code Thread threads = new Thread [20] will create an array of twenty nulls, no constructors will be called.



When constructing multidimensional arrays, they should be thought of as arrays, each element of which refers again to an array. Abstract constructions like matrices and cubes simplify programming, but can complicate the exam. For example, the construct int [][]a = new int [10][] is quite acceptable and will create a two-dimensional array, the elements of which can be initialized later: a[0] = new int [100] - and not necessarily with arrays equal to lengths: a[1] = new int [200] .



In order to initialize an array quickly (not element by element), you can use a syntax like this: int[] x ={1, 2, 3} . In braces can be not only constants, but also variables and even expressions. You can also create anonymous arrays, which is often used when you need to pass a well-defined array to a function: f(new int [] {2, 4, 8}) . If you see such a construction on the exam, then you should take a closer look. There is a possibility that something like this will be written: f(new int[3] {2, 4, 8}) . Such code will not be compiled, since the size of the anonymous array is calculated based on its declaration and should not be explicitly indicated.



On this I will finish for today. In the near future, be sure to talk about the features of some important operations in Java (assignment, comparison, instanceOf, arithmetic), as well as about implicit classes and threads.

Source: https://habr.com/ru/post/132962/



All Articles