|
From Classic Computer Science Problems in Java by David Kopec Brian Goetz is one of the leading figures in the Java world. As Java Language Architect at Oracle, he helps steer the direction of the language’s evolution and its supporting libraries. He has led the language through several important modernizations, including Project Lambda. Brian has a long career in software engineering and is the author of the best-selling book “Java Concurrency in Practice.” (Addison-Wesley, 2006)
|
Take 40% off Classic Computer Science Problems in Java by entering fcckopec3 into the discount code box at checkout at manning.com.
This interview, taken from Chapter 10, was conducted on August 25th, 2020 at Brian’s home in Williston, Vermont. The transcript has been edited and condensed for clarity.
How did you first get started in computing?
I started as a hobbyist around 1978, when I was 13 or 14. I had access to a timeshare computing system through the local school, and my older brother who had been through the same program ahead of me brought me books and stuff to read, and I was just absolutely hooked. I was completely fascinated by this system that was governed by a complex but understandable set of rules. And so, I spent as much time as I could after school in the computer room, at school, learning everything I could. And at the time, it was a very polyglot time for programming. There wasn’t a dominant language the way there has been for probably the last 25 years. Everybody was expected to know multiple programming languages. I taught myself BASIC, Fortran, COBOL, APL and assembly language. I saw how each of these were different tools for different problems. I was completely self-taught because there wasn’t really any formal education available at the time. My degree is not in computer science, it’s in mathematics, because at the time a lot of schools didn’t even have a computer science department. And I think that mathematical orientation has stood me in very good stead.
Was there one programming language when you were first learning that had a very big influence on you?
When I was first learning, there wasn’t really. I was just swapping it all in. The dominant languages at the time were Fortran, COBOL and BASIC for different categories of problems. But later, when I was a graduate student, I had the opportunity to take the Structure and Interpretation of Computer Programs class at MIT where I learned Scheme, and this was where all the light bulbs came on. At that point, I had already been programming for almost 10 years, and I had already encountered a lot of interesting problems. This was the first time I saw that there was an overarching theory that could connect a lot of the observations I had made. For me, I was very lucky to take that class as a graduate student rather than as a freshman, because the freshmen were just completely overwhelmed by the volume of material being thrown at them. Having more experience allowed me to see the underlying beauty and structure underneath it, and what they were really trying to get at. If I had to pick a moment where the beauty of computing really became obvious to me, it was that class.
How did you develop your software engineering and computer science skill sets after college?
I think the same way almost everyone else does—mostly by doing. In typical professional engineering situations, more often than not you get thrown in the deep end of the pool with a problem to solve, and you have to figure it out on your own. You have an array of tools at your disposal, but it’s not always obvious what the right one to use is, and there’s a process of trial and error where you try things. You see what works—and what stops working when the problem reaches a certain threshold of complexity. Hopefully, there’s some inductive reasoning process that’s going on alongside that by which you can figure out why something worked, and when it might work again, or might not. Earlier in my career, I had a number of fairly typical software engineering jobs; I worked for a research laboratory, I worked for a small software company that made networking software. I learned by doing, and experimenting, like most developers do today.
How did your career lead you to become the Java Language Architect?
By a fairly strange and circuitous path! For the first half of my career, I was mostly an ordinary programmer. At some point, I made this transition to being sort of halfway between programming and education, giving presentations and writing articles, and eventually a book. I always tried to choose topics that had been confusing to me on the theory they might also be confusing to others—and try to present them in an understandable light. I discovered I had a talent for bridging the gap between the technical details and intuitive mental models, and that culminated in writing “Java Concurrency in Practice,” which at this point was almost 15 years ago! And from there I went to work at Sun in a role that was more about technology evangelism than development, explaining to people, “How does the JVM work?” “What does a dynamic compiler do?” “Why is dynamic compilation potentially better than static compilation?” I was trying to demystify the technology and debunk the myths surrounding it. And once I was in there, I got to see how the engineering process worked on the Java team, and at some point, the opportunity to contribute in a more substantial way arose. If I had to give people a road map for how to get there, I don’t know how I would draw it. It was definitely not a straight line.
What does it mean to be the Java Language Architect?
In the simplest terms, I have to decide in what direction the Java programming model moves. And there are, of course, many, many options here—and many people who would happily give me advice! I’m sure each of the nine million Java developers has an idea or two for a language feature. And of course we can’t do all of them, or even many of them. So, we have to pick very, very carefully. My job is to balance the need to keep moving forward, so Java stays relevant, with the need for Java to stay “Java.” And relevance has many dimensions: relevant to the problems that we want to solve, relevant to the hardware we run on, relevant to the interests, orientations, and even fashions of programmers. We have to evolve, but we also can’t move so quickly that we lose people. If we were to make a radical overnight change, people would say, “This isn’t the Java I know,” and they would go do something else. And so, we have to pick both the direction and a rate to move forward, such that we’re able to stay relevant to the problems people want to solve without making people feel uncomfortable.
For me, that means getting into the shoes of Java developers and understanding where their pain points are. Then we try to move the language in a way that works with them and obviates the pain points that they’re experiencing, but not necessarily in the same way they imagined they needed. There’s an old saying attributed to Henry Ford: “If I asked my customers what they wanted, they would tell me ‘faster horses.’” Programmers are very much prone to say “you should add this feature” and the art is in listening to that suggestion and understanding the kind of pain they are in that leads them to think that this would be the right solution. By comparing that to the things we’ve heard from other developers, perhaps we can see what’s really missing that is actually going to address people’s pain, make them more productive, make programs safer, and more efficient.
How does the process of evolving the Java language work? How is it decided that new features should be added to the language?
It’s actually fairly rare that a feature is invented out of whole cloth. The reality is, almost every “new” idea has been kicking around the programming world for decades. Invariably, when someone comes to me with a feature idea I can see a connection to something that was done in some other language long ago. A big part of the process is waiting for the right time to expose a new concept, and fitting it in in a way that is consistent with the rest of the language. There’s no shortage of feature ideas, and in every language, you find lots of features that people in those communities like. And the real game is to get underneath them and ask: “What does having this feature enable you to do that you can’t do otherwise? How does it make your programs safer? How would it permit better type checking? How does it make your programs less error prone, more expressive, etc?”
It’s a fairly subjective process; if we’re looking to alleviate people’s pain, we have to make subjective calls about which kinds of pain are more important to alleviate now. If you look at the big features that have been added to Java in the past: In the mid-2000s, we saw generics, and that was an obvious gap. At that time the language screamed out for parametric polymorphism. They wanted to have it in 1995, but they didn’t know how to do it in a way that made sense for the language. And they didn’t want to graft C++ templates onto Java, which would’ve been terrible. And so, it took almost another 10 years to figure out what the natural way to add parametric polymorphism and data abstraction to Java in a way that feels natural. And I think they did a fantastic job. We did the same thing with behavioral abstraction when we did lambdas more recently. Again, all the hard work there was not in the theory. The theory of lambda expressions has been well understood since the thirties. The hard part was, how do you make it fit into Java so that it doesn’t look nailed on the side? The ultimate measure of success is when you finally deliver something after three or five or seven years, people say, “What took you so long? That’s so obvious.” Well, the version we had in the first year would not look so easy or obvious. And we don’t want to inflict that on people, so we have to take our time.
How do you know when considering a feature that it’s not just fashion and it’s something really important that developers really need?
That is a really good question, because there have been some real near misses. In the early 2000s, there was a big call to add XML literals to the Java language, and I consider that to be a bullet that was dodged. Not all languages dodged that bullet.
I can’t give you an algorithm for it; often, you just have to sit and think about it for a long time and see what the connections to the rest of the language look like. We’ve all seen languages that bolt a feature on the side to solve a particular problem, but that problem may not be a problem for all times. If you’re willing to sit and be patient and think about it over and over again before you make a decision, very often you can get a sense of when something is just the flavor of the week.
What additions to Java are you most proud of during your tenure as Java Language Architect?
I was the specifications lead for adding lambdas to Java. And not only was that an enormous change, but it signaled a change for how the language would be evolved going forward. It was, in some sense, a make or break thing, because in the time leading up to that Sun was fairly distracted by slowly going out of business, and we had not been able to evolve the platform at the rate that we would have liked. It was pretty clear at the time that Java was falling behind, and this was our big chance to prove to the world that it was possible for Java to remain relevant and fun to program in; that we could continue to teach this old dog some fancy new tricks.
The main challenge of adding lambdas to Java was making it not look like it was tacked on the side, but integrated into the whole as if it had always been there. And there was no shortage of suggestions for how to do it—nearly all of which were “do it like this other language does it.” All of these would have been unsatisfying. We might have gotten there a year or two faster, but we would not have gotten a result that worked as well, and we’d be stuck with it for a long time. What I’m really proud of is how we managed to figure out how to integrate it into the language at multiple levels so that it looked like it belonged there. It works very cleanly with the generic type system. We had to overhaul a number of other aspects of the language in order to make it work—we had to overhaul type inference and its interaction with overload selection. If you ask people what were the features that were added in Java 8, that would never be on anybody’s list. But that was a huge part of the work—it was under the waterline, but it was the foundation that was needed to make it so that you could write the code that you wanted to write, and it just naturally worked.
We also had to start to address the problem of compatible API evolution. One of the huge risks that we faced in Java 8 was the way you would write a library with lambdas is very different than you would in a language without lambdas. And we didn’t want to make the language better and then be in the situation where now all of a sudden, all our libraries look 20 years older. We had to address the question of how we are going to evolve the libraries in a compatible way so they could take advantage of these new library design idioms. That led to the ability to compatibly add methods to interfaces, which was an essential part to keeping the libraries we had relevant so that on day one, the language and the libraries were ready for a newer style of programming.
A question I get asked by a lot of students, is how much should they use lambdas? Can they overuse them in their code?
I come at this perhaps from a different perspective than a lot of students, because most of the code that I write are libraries that are intended to be used by a lot of people. The bar for writing a library like that, like the streams API, is very high because you have to get it right the first time. The compatibility bar for changing it is very high. The way I tend to think about abstracting over behavior is when you’re crossing a boundary between user code and library code, the main thing that lambdas allow you to do is to design APIs that could be parameterized not just with data, but with behavior, because lambdas let us treat behavior as data. So, I am focused on the interaction between the client and this library and the natural flow of control. When does it make sense for the client to be pulling all the strings, versus when does it make sense for the client to hand some behavior to the library that it will call at the appropriate time? I’m not sure my experiences translate directly to the experience of your students. But one thing that will surely be a key to success here is being able to recognize where the boundaries are in your code, and where the divisions of responsibility lie. Whether those are strictly demarcated in separately compiled modules or carefully documented APIs, or whether they’re just conventions for how you organize your code, this is something we want to stay aware of.
We use these boundaries in our code for a reason—so that we can manage the complexity through divide-and-conquer. Every time you’re designing one of these boundaries, you’re designing a little protocol interaction, and you should be thinking about the roles of the participants on each side, what information they are exchanging, and what does that interchange look like.
You spoke earlier about a period of stagnation for Java. When was that, and why did it happen?
I would say the Java 6-7 timeframe was the dark ages for Java. Not coincidentally, this was the time at which Scala started to gain some traction, in part because I think the ecosystem was saying, “Well, we may need to find another horse to back if Java doesn’t get up and run.” Thankfully, it did get up and run, and it’s been running ever since.
And now we’re seeing a pretty rapid evolution of the language. How has the philosophy changed?
In the big picture, it hasn’t changed that much, but in the details, it’s changed quite a lot. Starting after Java 9, we moved to a six month time-boxed release cadence rather than a multiyear feature-boxed cadence. And there were all kinds of good reasons for doing that, but one of them was that there were a lot of good, smaller ideas that always got crowded out when you were planning multiyear releases with big release drivers. The shorter release cadence has allowed us to have a better mix of big and small features. In these six month releases, a lot of them have smaller language features, like local variable type inference. They didn’t necessarily only take six months to do; they may still have taken a year or two, but we now have more opportunities to deliver something once it’s ready. In addition to smaller features, you’ll also see bigger feature arcs like pattern matching that may play out in increments over a multiyear period. The earlier parts can give you a sense of the direction of where the language is going.
There are also clusters of related features which may be delivered individually. For example, pattern matching, records and sealed types work together to support a more data-oriented model of programming. And that’s not an accident. That’s based on observing what kind of pain people are having using Java’s static type system to model the data that they’re working with. And how have programs changed in the last 10 years? They’ve gotten smaller. People are writing smaller units of functionality and deploying them as (say) micro-services. So, more of the code is closer to the boundary where it’s going to be getting data in from some partner whether it’s JSON or XML or YAML over a socket connection that it’s then going to turn into some Java data model, operate on, and then do the reverse. We wanted to make it easier to model data as data, since people are doing it more. So, this cluster of features is designed to work together in that way. And you can see similar clusters of features in a lot of other languages, just with different names. In ML, you would call them algebraic data types because records are product types and sealed classes are sum types, and you do polymorphism over algebraic data types with pattern matching. So, these are individual features that maybe Java developers haven’t seen before because they haven’t programmed in Scala or ML or Haskell. They may be new to Java, but they are not new concepts, and they have been proven to work together to enable a style of programming that’s relevant to the problems people are solving today.
I’m wondering if there’s one upcoming feature in Java that you are most excited about.
I’m really excited about the bigger picture for pattern matching because, as I’ve worked on it, I realized that it has been a missing piece of the object model from Java all along, and we just hadn’t noticed. Java offers good tools for encapsulation, but it only goes one way: you invoke a constructor with some data and that gives you an object, which then is very cagey about giving up its state. And the ways in which it gives up its state are generally via some ad hoc API that’s hard to reason about programmatically. But there is a large category of classes that are just modeling plain old data. The notion of a deconstruction pattern is really just the dual of a concept that we’ve had from day one, which is the constructor. The constructor takes state and turns it into an object. What’s the reverse of that? How do you deconstruct an object into the state that you started with (or could restart with)? And that’s exactly what pattern matching lets you do. It turns out that there are an awful lot of problems for which the solution is just much more straightforward, elegant, and most importantly composable, than doing it the ad hoc way.
And I bring that up because despite all we’ve learned about advances in programming language theory in the last 50 years, my one sentence summary of the history of programming languages is, “we have one good trick that works.” And that trick is composition. That’s the only thing that works to manage complexity. And so, as a language designer, you want to be looking for techniques that allow developers to work with composition rather than against it.
Why is it important to know problem solving techniques from the realm of computer science?
To stand on the shoulders of giants! There are so many problems that have already been solved by someone else, often at great effort and expense and with many false starts. And if you don’t know how to recognize that you’re staring at a problem that has probably been solved by somebody before, you’re going to be tempted to reinvent their solution—and you’re probably not going to do it as well.
I saw a funny comic the other day about how mathematics works. When something new is being discovered, at first, no one believes that it’s even true, and it takes years to figure out the details. It may take years more to get the rest of the mathematical community to agree that this actually makes sense. And then at the other end, you spend 45 minutes on it in a lecture and when a student doesn’t understand it, the professor asks “we spent all class on that yesterday, how could you not get it?” A lot of the concepts that we see as lecture-sized units of understanding in class are the result of years of someone bashing their head against the problem. The problems that we solve are hard enough that we need every bit of help we can get. If we can decompose the problem so that some part can be solved by an existing technique, that is hugely freeing. It means you don’t have to reinvent a solution, and especially not a bad solution. You don’t have to rediscover all the ways in which the obvious solution isn’t quite right. You can just lean on an existing solution and focus on the part of your problem that is unique.
Sometimes students have trouble envisioning how the data structure and algorithm problems they learn will actually come up in real world software development. Can you tell us how often computer science problems actually come up in software engineering?
This reminds me of a conversation I had when I went back to visit my thesis advisor some 10-15 years after graduation. He asked me two questions. The first was, “Do you use the math that you learned in your work here?” And I said, “Well, to be honest, not very often.” The second was, “But do you use the thinking and analysis skills that you learned when studying math?” And I said, “Absolutely, every day.” And he smiled with the pride of a job well done.
For example, take red-black trees. How do they work? Most of the time, I shouldn’t have to care. If you need one, every language has an excellent pre-written, well tested, high performance library that you can just use. The important skill is not being able to recreate this library, but knowing how to spot when you can use it profitably to solve a bigger problem, whether it is the right tool, how this will fit into the time or space complexity of your overall solution, etc. These are skills that you use all the time. It can be hard, when you’re in the middle of a data structures class, to see the forest through the trees. You can spend a lot of time in class working through the mechanics of a red-black tree, and this might be important, but it’s something you’ll likely never have to do again. And hopefully they won’t get asked to do it in an interview, because I think that’s a terrible interview question! But you should know what the time complexity of a tree look-up could be, what the conditions on the key distribution would have to be in order to achieve that complexity, etc. That’s the kind of thinking that real world developers are called upon to apply every day.
Can you give us an example of a time in which you or another engineer were able to parlay knowledge from computer science to better attack an engineering problem?
In my own work, it’s kind of funny, because the theory is a very important underpinning to a lot of what we do. But the theory also stops short of being able to solve the problem for you in real-world language design. For example, Java is not a pure language, so in theory, there’s nothing to be learned from monads. But of course, there’s a lot to be learned from monads. So, when I’m looking at a possible feature, there is a lot of theory I can lean on. That gives me an intuition, but I’m going to have to fill in the last mile myself. The same thing with type systems. Most of type theory doesn’t deal with effects like throwing exceptions. Well, Java has exceptions. That doesn’t mean the type theory is useless. There’s a lot of type theory I can lean on in evolving the Java language. But I have to recognize that the theory only is going to get me so far, and I’m going to need to pave the last mile on my own.
Finding that balance is hard. But it’s critical, because it’s all too easy to say, “Oh, the theory won’t help me”, and then you’re reinventing wheels.
What areas of computer science are important in language development?
Type theory is the obvious one. Most languages have a type system, and some of them have more than one. Java, for example, has one type system at static compilation time, and a different type system at runtime. There is even a third type system for verification time. These type systems have to be consistent, of course, but they have different degrees of precision and granularity. So, type theory is, of course, important. There is a lot of formal work on program semantics that is useful to be aware of, but not necessarily something that gets applied in everyday language design. But I don’t think any reasonable project goes by without opening the type theory books and reading dozens of papers.
If somebody is out there and they’re interested in eventually getting involved in language design, is there something you recommend they study or that they do in their career so that they could be in a position like yours someday?
Obviously, in order to be involved in language design, you have to understand the tools that language developers use. You have to understand compilers, and type systems, and all the details from computational theory: finite automata, context free grammars, etc. It is a prerequisite to understand all of that stuff. It’s also really important to have programmed in a number of different languages, and specifically different kinds of languages, to see the different ways in which they approach problems, the different assumptions they make, the different tools that they reserve for the language versus what they put in the user’s hands, etc. I think you have to have a pretty broad perspective on programming in order to be able to succeed in language design. You also need to have a “systems thinking” perspective. When you add a feature to a language, it changes how people will program in that language, and changes the set of directions you can go in the future. You have to be able to see not only how a feature will be used, but also abused, and whether the new equilibrium is actually better than the old, or whether it just moves the problem to a different place.
In fact, I’d give some of that advice — specifically, to go out and learn different kinds of programming languages — to everyone, regardless of whether they are interested in programming languages or not. Learning more than one programming paradigm will make them better programmers; when they approach a problem, they’ll more easily see multiple ways to attack it. I’d especially recommend learning a functional language, because it will give you a different and useful perspective on how to construct programs, and will stretch your brain (in good ways.)
What mistakes do you often see Java programmers make, that they could perhaps avoid by better exploiting the language’s features?
I think the biggest one is not doing the work to understand how generics work. There are a few non-obvious concepts in generics, but not that many, and they’re not all that hard once you set yourself to it. And generics are the underpinnings of other features, such as lambdas, as well as the key to understanding a lot of libraries. But a lot of developers treat it as an exercise in “what do I have to do to make the red squiggles go away”, rather than as leverage.
What do you think is one of the biggest shifts that is going to happen in the next 5 to 10 years for working programmers?
I suspect it will be the integration of traditional computational problem solving with machine learning. Right now, programming and machine learning are completely separate areas. The current techniques for machine learning have been sort of lying dormant for 40 years. All the work on neural networks was done in the sixties and seventies. But we didn’t have the computational power or the data to train them until now. We have those things now, and all of a sudden it’s become relevant. You’re seeing machine learning applied to things like handwriting recognition, speech recognition, fraud detection, and all of these things that we used to try to solve (not very well) with rule-based systems or heuristics. But the problem is the tools that we use for machine learning and the styles of thinking that we apply for machine learning are completely different from the way that we write traditional programs. And I think this is going to be a big challenge for the programmers of the next 20 years. How are they going to bridge these two different types of thinking in these two different tool sets in order to solve problems that will increasingly require both sets of skills?
What do you think some of the largest evolutionary changes in programming languages over the next decade will be?
I think we’re seeing the broad shape of a trend now, which is the convergence between object-oriented and functional languages. Twenty years ago, languages were strictly separated into functional languages, procedural languages, and object-oriented languages, and they each had their own philosophy of how to model the world. But each of those models was deficient in some way because it was only modeling part of the world. What we’ve seen over the last decade or so starting with languages like Scala and F#, and now languages like C# and Java, is many of the concepts that originally took root in functional programming are finding their way into the more broad-spectrum languages, and I think that trend will only continue. Some people like to joke that all the languages are converging to $MY_FAVORITE_LANGUAGE. There’s some truth to that joke, in that functional languages are acquiring more tools for data encapsulation and object-oriented languages are acquiring more tools for functional composition. And there’s an obvious reason, which is these are both useful sets of tools. Each excels at one kind of problem or another—and we are called upon to solve problems that have both aspects. So, I think what we’re going to see over the next 10 years is an increased convergence of concepts that were traditionally considered object-oriented and concepts that were traditionally considered functional.
I think there are many examples of influences from the functional programming world on Java. Can you give us a few of those?
The most obvious one is lambda expressions. And you know, it’s not really fair to call them a functional programming concept because the lambda calculus predates computers by several decades. It is a natural model for describing and composing behavior. It makes just as much sense in a language like Java or C# as it does in Haskell or ML. So, that’s clearly one. Another similar one is pattern matching, which again most people associate with functional languages, because that’s probably the first place they saw it, but actually, pattern matching goes way back to languages like SNOBOL from the seventies, which was a text processing language. Pattern matching actually fits into the object model very cleanly. It’s not a pure functional concept. It just happens to be that the functional languages noticed that it was useful a little bit before we did. A lot of these concepts that we associate with functional languages make perfect sense in object-oriented languages as well.
Java is by many measures one of the most popular programming languages in the world. What do you think has caused it to be so successful, and why do you think it will continue to be successful going forward?
As with any success, there’s a little bit of luck, and I think you should always acknowledge the role that luck has played in your success because to do otherwise is not being honest. I think in many ways Java came around at just about the right time. At that time, the world was on the cusp of deciding whether to leap from C to C++. C was the dominant language at the time, for better or worse, and C++ offered on the one hand better abstractive power than C, and on the other hand ungodly complexity. And so, you can imagine that the world was poised on a cliff, saying, “Do we really want to make this jump?” And Java came along and said, “I can give you most of what C++ is promising you without nearly as much complexity.” And everyone said “yes, please, we want that!” It was the right thing at the right time. It picked up on a number of old ideas that had been kicking around the computing world for years, including garbage collection and building concurrency into the programming model, that had not been used before in serious commercial languages. And all of these things were relevant to the problems people were solving in the nineties.
So, James Gosling has a quote where he describes Java as the “wolf in sheep’s clothing.” People needed garbage collection, they needed an integrated concurrency model that was better than pthreads, but they didn’t want the languages that these things traditionally came with—because they came with all sorts of other things that scared the heck out of them. Java, on the other hand, looked like C. In fact, they went out of their way to make the syntax look like C. It was familiar, and then they could sneak some cool stuff along with it that you only noticed much later. One of the things that Java did was that the entire language runtime was designed with the anticipation that just-in-time compilation was coming but wasn’t quite there. The first version of Java in 1995 was strictly interpreted. It was slow, but every design decision about the language and the class file format and the runtime structure was made with the know-how to make this fast. Eventually it became fast enough, and in some cases, even faster that C (though some people still don’t believe this is possible.) So there was some right-place, right-time luck, and a lot of brilliant vision for where the technology was going and what people really needed, that got Java going. But that’s just what happened at the start—to keep Java #1, with competitors itching to eat Java’s lunch, we needed something more. And I think the thing that has kept us going, even through the dark times we discussed, is the relentless commitment to compatibility.
Making incompatible changes is breaking your promises. It invalidates the investment that your customers have made in their code. Whenever you break someone’s code, you’re almost handing them an opportunity to go rewrite it in some other language, and Java has never done that. The code that you wrote in Java 5, 10, 15, 20, 25 years ago still works. That means that we evolve a little bit more slowly. But it means that the investment you’ve made not only in code but in your understanding of how the language works is preserved. We don’t break our promises and we don’t hurt our users in that way. The challenge is, how to balance moving forward with that kind of commitment to compatibility. And I think that’s our secret weapon. We figured out how to do that in the last 25 years, and we’ve gotten pretty good at it. That’s what enables us to add generics, and lambda expressions, and modules, and pattern matching, and other things that may seem foreign to Java without making them look bolted on—because we figured out how to do this.
Go gets a lot of credit for its integrated concurrency model, but Java already had synchronization primitives, keywords, and a threading model built into the language back in 1995. Why do you think it doesn’t get more credit for this?
I think part of it is that a lot of the real cleverness is under the waterline where people don’t see it. When something just works, it often doesn’t get the credit. So, that might be part of it. I’m not a big fan of Go for a couple of reasons. Everyone thinks that the concurrency model is Go’s secret weapon, but I think their concurrency model is actually quite error prone. That is, you have coroutines with a very basic message-passing mechanism (channels). But in almost all cases, the things on one side or the other of the channel are going to have some shared mutable state guarded with locks. And that means that you have the union of the mistakes you can make with message passing and the mistakes you can make with shared state concurrency, combined with the fact that Go’s concurrency primitives for shared-state concurrency are dramatically weaker than those in Java. (For example, their locks aren’t reentrant, which means that you can’t compose any behaviors that use locks. It means that you often have to write two versions of the same thing, one to be called with lock held, one not to be called with the lock held.) I think what people will discover is that Go’s concurrency model, not unlike Reactive, is going to be a transitional technology that looked attractive for a while, but something better is going to come along, and I think people will desert it quite quickly. (Of course, that might just be my bias showing.)
What does your work look like on a day-to-day basis as the Java Language Architect?
It’s actually all over the map. Any given day, I could be doing pure research on language evolution and how far-off features will connect up. I could be prototyping the implementation of something to see how the moving parts fit together. I could be writing up a direction statement for the team: “Here’s where I think we are in the process of solving this problem, here’s what I think we have figured out, here are the problems that are left.” I might be speaking at conferences, talking to users, trying to understand what their pain points are, and to some degree, sell the message of where we’re going in the future. Any given day could be any of those things. Some of those things are very much in-the-moment, some are forward looking, some are backward looking, some are community facing, some are internal-facing. Every day is different!
Right now, one of the projects that I’m involved in, that we’ve been working on for several years, is an upgrade to the generic type system to support primitives and primitive-like aggregates. This is something that touches the language, the compiler, the translation strategy, the class file format, and the JVM. In order to be able to credibly say that we have a story here, all of those pieces have to line up. So on any given day I might be working at the intersection of two of those things to see if the story is lining up properly or not. This is a process that can take years!
What words of advice do you have for self-taught programmers trying to improve their skill set, students, or experienced developers going back and reviewing material to improve their computer science skills?
One of the most valuable ways to understand a technology is to put it in historical context. Ask: “How does this technology relate to whatever came before it for solving the same problem?” Because most developers don’t always get to pick what technology they’re using to solve a problem. So, if you were a developer in 2000 and you took a job, they would tell you “We use this database, we use this application container, we use this IDE, we use this language. Now go program.” So, all of these choices were already made for you, and you may be overwhelmed by the complexity of how they all fit together. But every one of those pieces that you’re working with exist in a historical context, and it is the product of somebody’s better idea for solving a problem we solved in a different way yesterday. Very often you can get a better understanding of how a given piece of technology works by understanding what didn’t work about the previous iteration of the technology, what made someone say, “Let’s not do it that way. Let’s do it this way.” Because the history of computing is so compressed, most of that material is still available and you can go back and read what was written about in the version 1.0 release. The designers will tell you why they invented it and what problems they were frustrated by that they couldn’t solve with yesterday’s technology. That technique is tremendously useful at understanding both what it’s for and the limitations that you’re going to run into.
Brian can be followed on Twitter @BrianGoetz
If you want to learn more about the book, you can check it out on our browser-based liveBook platform here.