Compile a Java class against an interface even if none was specified
I'm teaching an intro to programming course and we're using Java. I want to help the students learn how to translate a written class specification into a working program. I will provide the written specification. It specifies the name of the class and behavior in the form of method signatures. I want the students to translate that into a working Java class.
I could provide them an interface and have them implement the interface, but that defeats part of the purpose: to read and interpret a written functional specification document. I want them to write the class from scratch. Then I want to grade their work.
My idea for checking their work is this: compile their Java class file against my own interface. If it compiles, then at least I'll know they've followed all the method cont开发者_StackOverflow社区racts and I can start testing the functionality. If it doesn't compile, I'll get an error message reporting which methods were not implemented correctly.
How can I force a Java class file to be compiled against an interface even if none was originally specified in the source code?
In other words, let's say I have the following two files:
FooInterface.java
public interface FooInterface
{
...
}
Foo.java
public class Foo
{
...
}
I want to compile Foo as if it implemented FooInterface explicitly. But I don't want to have to manually edit a bunch of source code files in order to do so. How can I do it?
Edit
To address questions about the value of using a written spec vs providing the interface, here's what a hypothetical specification document looks like:
Write a class called Foo with the following methods:
oldest : ages (int[]) -> int
Given an array of ages, return the highest one.anyAdults : ages (int[]) -> boolean
Given an array of ages, return whether any of them are 18 or older.
IMO, this has a great educational benefit. The student has to critically evaluate whether their program obeys the spec. If I provided the interface file, they could unplug their brain and simply have the compiler tell them whether or not they were following the spec. Using the compiler as a cognitive crutch is the same technique the poorer students currently (unsuccessfully) employ to balance their braces and parentheses.
You could maybe do something like:
public class Bar extends Foo implements FooInterface{}
If FooInterface is fully satisfied by Foo then Bar will not have any errors.
You could still have Foo implement FooInterface but just give your students an empty FooInterface. When you compile students' Foo use your actual FooInterface.
In addition to providing a complete Interface when you compile your students code as Murali suggested, I suggest you also implement a unit test suite to make your grading go much more smoothly. In addition to testing that the method contracts from the written spec are all met, you could also be testing that each method works properly.
I don't know if this is the most elegant way to do it, but your grading program could use reflection to load the student class and look for methods with a given name - if it doesn't have such a method, you can deduct whatever points you want. If the method does exist, then you can invoke it using reflection as well, passing in whatever parameters you like.
Sun has a pretty decent tutorial trail here:
http://java.sun.com/docs/books/tutorial/reflect/member/index.html
See the section on obtaining method information and invoking methods.
Marked community - others please feel free to provide corrections, etc.
As per your edit:
You can't make it match an existing interface. You would have to do it by hand, by looking at the method names using reflection.
This code should let you know if the class implement the specification:
String studentClassName = args[0];
Class class = Class.forName( studentClassName );
Methods [] methods = class.getMethods();
for( Method m : methods ) {
if( matchTheSpecs( method ) ){
markAsAccepted( method.getName() );
}
}
The matchTheSpecs
method would verify
method.getName()
method.getParameterTypes()
method.getReturnType()
So, this:
oldest : ages (int[]) -> int
anyAdults : ages (int[]) -> boolean
Should be
public boolean anyAdults( int [] ages );
public int oldest( int [] ages );
You can't.
At least not the way you're describing.
Think about the following situation. One statement of the specification may be:
"... the system saves the user name and ... "
The possible method signatures are just too much.
public void setUserName( String name );
public void setName( String user );
public void userName( String user );
public String saveUserName( String s );
public void save( String userName );
... another 10 more here ...
Etc. All of them are correct, because they will be adding this feature to the code.
What you can do is to add instructions for method generation like.
Notice: all the methods names should follow the next pattern ( describe the pattern here )
Then you can compile the class and using reflection get the methods names and evaluate if they match your specification.
String studentClassName = args[0];
Class class = Class.forName( studentClassName );
Methods [] methods = class.getMethods();
for( Method m : methods ) {
if( matchThePattern( method.getName() ) ){
markAsAccepted( method.getName() );
}
}
if( allTheMethodsAreWritten() ) {
grade("A");
}
To evaluate if they meet the criteria you'll need to verify if the class contains:
1) the number of methods the specification declare ( they could have more ).
2) The method name matches the specification ( the method reflects the intention ) .
For instance for my initial example, all the followings would be valid entries:
[setUserName,setName,userName,saveUserName,save]
Your pattern may be something like:
keyAction + Subject(s) + Other.
Or something that makes sense.
You cannot do what you want to do unless you process their source.
If you REALLY want to do this, then you can write a program that loads their class, and uses reflection to inspect the methods to see if their signatures conform to what you want them to be. A lot of elbow grease is needed.
Personally I would suggest that they collaborate on definitng the interface in class before the project is assigned, and then say that THAT interface must be used.
If the specification contains exact method signatures, you might as well supply the interface as the students will not really be writing the interface but copying it from the spec.
If the specification is not exact (just describing the parameters of each method) then you should probably not use an interface, because there may be many valid interpretations of the same method or parameter description (protected
vs. public
, different argument order, List
vs. array vs. Iterable
, BitSet
vs. Set<Integer>
etc.) You should not penalise your students just for coming up with a working solution that does not exactly match yours.
@weiji's reflection idea sounds really good although it might make things really complicated from your end
Maybe you could write another class which inherits from their class but additionally implements the specified interface, and is filled with dummy methods which simply call the corresponding functions in the parent class
It's hackish, but can't you just use a tool such as sed to insert "implements YourInterface" as appropriate?
I know this would be hard if there were a load of classes with different names, but it seems there is only one class for them to submit so it'd be ok.
If this approach is really abhorent then feel free to critique for the sake of posterity in the comments.
Another thing you could do is have them write the interface and then the class. So you could give them the requirements, and have them write a class w/ methods that meet those requirements, as well as have them write an interface that said class implements. So you would have:
InterfaceFoo{
....
}
Foo implements InterfaceFoo{
....
}
Then you could just have a class w/ the methods defined, and implement InterfaceFoo, if it compiles then you know the students correctly created the correct methods and signatures, if it doesn't then the compiler will let you know what they missed.
One thing that you could do is say that you are going to test the output of their program. In order for them to have the code executed, they need to create an empty interface like IGradeable.
They can define it as they wish. When it comes time for them to hand it in, you could define your IGradeable interface like this:
public interface IGradeable extends FooInterface
{
....
}
Just a suggestion. The only other way that I can think of is to do something like create temporary java files and do a regex replace to have all files implement FooInterface.
You could use reflection to see if the student's class implements all of the methods defined by your interface.
I agree with jonnii's answer. Student does not have to implement the Foo interface. It is the teaches that creates one class only that extends the Student's class and implements the interface.
By the way, be sure to require each student to include his or her full name in the class name. That way you have the option of compiling all these things at the same time, so you'll really save a lot of time.
Also either require a specific package name or, simpler, no package at all.
I don't know if you can do this at compile time, but here's an idea. Why not check at runtime by creating a class of type Foo and checking to see if it is an instanceof
FooInterface? Hope this helps. I tried to think of some sort of solution with annotations, but nothing I can come up with works at compile time.
Using AOP, you can weave the students' classes and synthetically let them implement FooInterface
at runtime and run your test suite against them. This can be done fairly easy and automated using custom class loaders for each student.
However, i'm unsure about how this will behave at runtime when the class does not actually implement all the methods with correct signature, e.g. if it just throws MethodNotFoundErrors
aspect MakeImplementFooAspect {
// makes many classes implement FooInterface
declare parents : edu.teaching.foo.* implements FooInterface;
}
精彩评论