COERCE is fine, you can even specify the element-type if you want:
USER> (coerce '(1 2 3) '(vector fixnum))
#(1 2 3)
USER> (describe *)
#(1 2 3)
[simple specialized vector]
Element-type: FIXNUM
Length: 3
You can do the same with make-array, but you need to give the right dimension. The dimension parameter indicates how many rows, columns, etc. the possibly multi-dimensional array has. It should be a list, but when you only have one dimension, it can just be a number. Both forms here are equivalent:
USER> (make-array 3 :initial-contents '(1 2 3))
#(1 2 3)
USER> (make-array '(3) :initial-contents '(1 2 3))
#(1 2 3)
Usually you would call (length list). The added value compared to coerce is that you can specify the :fill-pointer or :adjustable arguments, something that the type argument of coerce cannot convey.
The printed representation of an array is #nA(), where n is the number of dimensions of the array, where the number and A is omitted if its a vector (n = 1). For example:
USER> (make-array '(10 2) :initial-element 0)
#2A((0 0) (0 0) (0 0) (0 0) (0 0) (0 0) (0 0) (0 0) (0 0) (0 0))
For a given array of dimension n, you access the elements with aref and as many arguments as there are dimensions, in row-major order (the same order you specified the dimensions in make-array).
USER> (aref * 5 1)
0 (0 bits, #x0, #o0, #b0)
In your example, you defined an array of dimension 0, because you wrote '() (a.k.a. just () or nil) as the dimension. This can be used if you need a box to store a single element:
USER> (defparameter *box* (make-array nil :element-type '(mod 8)))
*BOX*
USER> (describe *box*)
#0A0
[simple specialized array]
Element-type: (UNSIGNED-BYTE 4)
Dimensions: NIL
Storage vector: #<(SIMPLE-ARRAY (UNSIGNED-BYTE 4) (1)) {101EA0C59F}>
; No values
USER> (setf (aref *box*) 7)
7 (3 bits, #x7, #o7, #b111)
USER> (incf (aref *box*))
8 (4 bits, #x8, #o10, #b1000)
USER> (aref *box*)
8 (4 bits, #x8, #o10, #b1000)
(as you can see, the values that can be stored in the array are the ones corresponding to the upgraded-element-type, here (unsigned-byte 4); for example, (setf (aref *box*) (expt 2 16)) signals an error)
It looks like :initial-contents had the same result as :initial-element, because with your example the content of the zero-dimensional array was the list (1 2 3)
Is one of them faster or more elegant than the other?
I try to use coerce when possible because it's shorter to write and it looks more descriptive of what I am doing. I don't think it is faster, unless you know in advance the length of the list.