Static and Dynamic Typing
Types can be static or dynamic. Languages like Lisp or Python have many different types, but when you look at a piece of code there is nothing that forces a variables to "be" (or to "point to") a piece of data of a particular type. In other languages, like ML or Eiffel, a certain variable will always be connected with a value of a certain, fixed type. The first group of languages (where the type of everything is unknown when the program is compiled) has dynamic types, the second group has static types.
Dynamic types are good because the program source can be more flexible and compact (which might be particularly useful for prototyping a system, for example). Static types are good because they allow certain errors in programs to be detected earlier (a compiler for a statically typed language may also be able to make extra optimisations using the extra information available, but this depends on details of particular languages and compilers).
My own view is that at computing projects become larger, static typing becomes more important. I would not like to work on a project with many other programmers using a dynamically typed language, and I choose to use dynamically typed languages, usually, when doing projects of my own.
In some languages (e.g. ML) the interpreter or compiler can often work out the type associated with a variable by itself, which saves the programmer a lot of effort.
Strong and Weak Typing
Types can be weak or strong. The languages mentioned above are all strongly typed, which means that at any point in the program, when it is running, the type of a particular chunk of data is known.
Since a dynamically typed language does not have complete type information at compile time it must, if it strongly typed, keep track of the type of different values as it runs. Typically values are boxed together with information about their type - value and type are then passed around the program together.
It might seem that a strong, statically typed language would not need to do this and so could save some memory (as type information is available when the program is compiled). In practice, however, I believe that they still do so - possibly because of polymorphism (see below).
Unlike the languages mentioned so far, C has weak typing - some variables can point to different types of data, or even random areas of memory, and the program cannot tell what type of object is being referred to. Depending on the context within the program, the variable is assumed to point to some particular type, but it is quite possible - and a common source of confusing bugs - for this assumption to be incorrect (some type checking is done by a C compiler, but not as much as in a language designed to have rigorous compile time checking, like those described as statically typed above).
Java is strongly, but not statically, typed - classes can be converted (cast) and, if the types are not compatible (related through inheritance - see below), a run time error will occur. Apart from this (significant) exception the Java type system can be considered static - one description is "compromised strong static typing".
When strong static typing is enforced (even if only partially, as in Java) it can be difficult to write generic algorithms - functions that can act on a range of different types. Polymorphism allows "any" to be included in the type system. For example, the types of a list of items are unimportant if we only want to know the length of the list, so in ML a function can have a type that indicates that it takes lists of "any" type and returns an integer.
Another solution to the problem of over-restrictive types is to use inheritance from OOP (see below) to group data together. Yet another approach, used in C++, is templates - a way of describing generic routines which are then automatically specialised for particular data types (generic programming and parameterised classes).