PHP is easier in a sense, but that's just because it doesn't concern itself with some of these issues. With PHP you deal with the issues later.
Casting is basically in one of two directions (up or down). An up-cast is stating that you would prefer to work with the object only through the interface of a class that it inherits from (or inherits the interface of).
For example, if I have a FileInputStream, I could change it to "just" an InputStream
FileInputStream fileInput = ...
InputStream input = (InputStream)fileInput;
Note that this have exactly zero effect on implementation of the reachable methods; but is does sometimes hide methods that were previously available.
fileInput.getFileName(); // let's pretend this works
input.getFileName(); // this shouldn't work, as any InputStream doesn't have a file name.
Downcasting is different (and dangerous). You basically state that even though you have an InputStream, somehow you know that it should be treated as a FileInputStream
InputStream input2 = ...;
FileInputStream fileInput2 = (FileInputStream)input2;
If the input2 in this scenario doesn't have the appropriate type of a FileInputStream, then you will get a class cast exception at runtime. If it does have the appropriate type, then fileInput2 will be assigned.
The main reason for such a typing system is so you can easily write reusable components. The "higher" types (cast up types) specify general contracts, while the "lower" types (cast down types) specify specific details that vary within the general contracts specified by their super-types.
Generics; however, are a different ball of wax. Basically when dealing with Collections, and things that relate to Collections, the rules are similar (but must be different due to a number of reasons).
One of the reasons generics act differently is backwards compatibility. The design goal of adding "extra" type information to an already existing type (like a java.util.List) meant that for mixed generics and non-generics use, the type effectively cannot exist at runtime. This property of the generic type not being considered present at runtime is called "erasure". In short, when you write
List<Student> students = new ArrayList<Student>();
you are effectively compiling
List students = new ArrayList();
But your compiler will do "extra" work to make sure that in any bit of source code you write, you can only add a "Student" object. Likewise, when you read across the List, you don't need to cast to a "Student" object, as the compiler will assume only "Student" objects can be in the list.
Note that for such a system to work, it is compile-time only. This means that they details of type hierarchy and run time casting don't apply. This reflects itself in the new type constraints added by generics.
T extends Student
means that T can be cast to a Student (up cast)
T super Student
means that T is a super class of Student This latter one is tricky, but useful in certain scenarios.
If CollegeStudent, HighSchoolStudent, and GradeSchoolStudent all extend Student, then all three types of Student can be stored in a List<? extends Student> at the same time.
If something needs to assure that a Collection will at least provide Students, you can then give is a List<? super Student>.
public void studentProcessor(Collection<? super Student> students);
could process a List<Student>, a List<CollegeStudent>, a List<GradeSchoolStudent>, etc.