Skip to content

Instantly share code, notes, and snippets.

@cardil
Last active December 6, 2024 06:22
Show Gist options
  • Save cardil/b29a81efd64a09585076fe00e3d34de7 to your computer and use it in GitHub Desktop.
Save cardil/b29a81efd64a09585076fe00e3d34de7 to your computer and use it in GitHub Desktop.
[Draft] JEP - Change name of imported type (aliasing)

Summary

Change name of imported type (aliasing)

Goals

The goal is to make code more productive and clean in cases where multiple classes with same name must be used in code. It should relief a developers frustration in that situations.

Non-Goals

None.

Success Metrics

Only metric to be measured here will be developers satisfaction rate on using Java after introducing this feature.

Motivation

Situation that same class exists in code base is common. In most enterprise application there are multiple object models and there is also the code that maps values from one model to the other. Names of those models shouldn't be forged by developers, but business domain names that are used should be utilized. It's directly advised to do that be Domain-driven design (DDD).

Without such a feature developers are pushed to consider renaming a model classes to make them unique, to evade using fully qualified class names.

This feature is present in multiple languages. Most notable in Python, but also strongly present in JVM languages. Scala, Groovy and Kotlin all have this feature implemented. C# is also equipped with this feature.

Groovy

import org.example.view.model.Employee as EmployeeView

Scala

import org.example.view.model.{Employee => EmployeeView};

Kotlin

import org.example.view.model.Employee as EmployeeView

C Sharp

using EmployeeView = org.example.view.model.Employee;

Implementing this feature is asked by multiple developers and it will make Java on par with it's competitors and relief a developers frustration.

There are multiple threads with discussion on this topic. Naming a few:

It's also a topic on various conferences, must notably by Kevlin Henney.

I'm aware that this feature was proposed multiple times in the past (about 20 years ago , JDK-4194542, JDK-4214789) and it was rejected:

This is not an unreasonable request, though hardly essential. The occasional use of fully qualified names is not an undue burden (unless the library really reuses the same simple names right and left, which is bad style).

In any event, it doesn't pass the bar of price/performance for a language change.

I find this reasoning incorrect. As already mentioned, it is desired to have meaningful domain names, without need to suffix them with *Dto, *Entity, or *Data etc. A little bit different models of the same object are also desirable and directly advised by Clean (Hexagonal) architecture:

Typically the data that crosses the boundaries is simple data structures. You can use basic structs or simple Data Transfer objects if you like. Or the data can simply be arguments in function calls. Or you can pack it into a hashmap, or construct it into an object. The important thing is that isolated, simple, data structures are passed across the boundaries. We don’t want to cheat and pass Entities or Database rows. We don’t want the data structures to have any kind of dependency that violates The Dependency Rule.

Additionally as mentioned, situation is different then was in 1999 and Java's competitors, both JVM based and not, has this feature implemented.

Description

This feature should be implemented solely in Java compiler. Compiled code shouldn't be aware that aliasing was used.

package org.example.view;

import org.example.domain.model.Employee;
import org.example.view.model.Employee as EmployeeView;

interface EmployeeMapper {
  EmployeeView map(Employee employee);
}

To implement this changes to compiler should be made in the way that when compiler reads a java class file with lexer tokens should be renamed on the fly. In that way impact of this change will be minimized as further processed will not have access to this alias information as it will be gone at that point.

Some changes are probably required also to javadoc.

TODO: Further detailed description should be written

Alternatives

There are multiple workarounds on this problem, but all of them have their drawbacks:

Giving a unique name using prefix

With this alternative developer should give a unique simple name to all classed that might be in contact with each other, utilizing prefixes or suffixes.

class EmployeeView {
}
class EmployeeModel {
}

This approach is undesirable as names are forced to be concatenated with additional noisy additions. It directly violate naming principle of DDD and defeats usage on packages. If we need unique names, why we need a packages?!

It's also impossible if we don't control both classes. That might be the case when using some kind of RPC mechanism like SOAP, REST, or RMI.

Extend a class

With this approach developers might extend one of the classes, essentially making a wrapper that delegates operations to base class.

package org.example.view;

import org.example.view.model.Employee;

class EmployeeView extends Employee {
  private final Employee delegate;

  @Override
  String fullName() {
    return delegate.fullName();
  }
}

It also requires additional mapping from org.example.view.model.Employee object to EmployeeView wrapper, and extreme amount of error prone boilerplate code in wrapper class. It's also impossible to use when base class uses a final word, as it should . It's also might be impossible to use when using some kind of RPC mechanism.

Testing

Standard unit tests should be enough to properly test this feature.

Risks and Assumptions

There are usual risks of introducing new syntax to the language as exiting tooling must adjust to be able to work well. The risk is mitigated by choosing to limit this change to be solely compile time syntactic sugar.

Dependencies

None.

@manojbaishya
Copy link

manojbaishya commented Oct 21, 2022

Can this feature: "Import Aliasing" be incorporated as a part of the Project Amber? An Oracle blog post describes this project as:

Project Amber describes its goal as “explore and incubate smaller, productivity-oriented Java language features that have been accepted as candidate JEPs.” Amber has been the springboard from which several of the recent language improvements have been launched, starting with the addition of the var keyword for local variables in Java 10.

More recently, Amber has been responsible for text blocks, which came out in Java 13; records in Java 16; and pattern matching in instanceof comparisons, which was finalized in Java 16.

Several Amber subprojects are still in progress.

  • Sealed classes, which have been previewed in the last few Java releases and are scheduled to be finalized in Java 17. Sealed classes (and interfaces) can limit which other classes or interfaces can extend or implement them.
  • Pattern matching in switches is a feature that will be previewed in Java 17. While this capability is conceptually straightforward, the myriad possible syntax options make its implementation complex. The JEP I linked to presents a detailed examination of the options and the various difficulties they pose, especially with regard to maintaining rigorous compatibility with existing switch statements.
  • Pattern matching for records and arrays, which, like pattern matching in switches, is an extension of pattern matching with instanceof. This language feature facilitates identification of the types of fields in records and of entries in arrays. A preview of this feature is projected for Java 18.

To me, "Import Aliasing" seems like a natural extension of Local Variable Type Inference, whose goals section prominently start with the paragraph:

We seek to improve the developer experience by reducing the ceremony associated with writing Java code, while maintaining Java's commitment to static type safety, by allowing developers to elide the often-unnecessary manifest declaration of local variable types.

Is there a Java Mailing List where we can communicate this request to the core JVM developers?

Edit 2:

Found the github repo!

https://github.com/openjdk/amber

Edit 1:

There is one more convenience feature called "Static Imports" that is related to this JEP, whose footnote reads:

So when should you use static import? Very sparingly! Only use it when you'd otherwise be tempted to declare local copies of constants, or to abuse inheritance (the Constant Interface Antipattern). In other words, use it when you require frequent access to static members from one or two classes. If you overuse the static import feature, it can make your program unreadable and unmaintainable, polluting its namespace with all the static members you import. Readers of your code (including you, a few months after you wrote it) will not know which class a static member comes from. Importing all of the static members from a class can be particularly harmful to readability; if you need only one or two members, import them individually. Used appropriately, static import can make your program more readable, by removing the boilerplate of repetition of class names.

@ayanamists
Copy link

+1

@zeidoo
Copy link

zeidoo commented Dec 7, 2022

+1

This is such a silly hill to die on from the Oracle folks that I bet their coding style forces them to not use import statements at all in their source code. Every variable declaration must be using fully qualified type names just to be completely sure there's absolutely no confusion on the type used. God forbid the compiler doing a little bit of extra work. :P

More seriously, for those wanting a workaround to this madness, if you have some control over the code:

package com.some.super.long.ducking.package.name.services.dtos;

import java.time.LocalDate;

public class ServiceDtos {
    public record Tax(String name, Double percentage, LocalDate startDate, LocalDate endDate) {
    }
}

And then

package com.some.super.long.ducking.package.name.mappers;

import com.some.super.long.ducking.package.name.services.dtos.ServiceDtos;
import com.some.other.super.long.ducking.package.name.entities.dtos.Tax

public class TaxMapper implements Mapper<Tax, ServiceDtos.Tax>{
...
}

It sure beats the below:

package com.some.super.long.ducking.package.name.mappers;

import com.some.other.super.long.ducking.package.name.entities.dtos.Tax

public class TaxMapper implements Mapper<Tax, com.some.super.long.ducking.package.name.services.dtos.Tax>{
...
}

@ramilS
Copy link

ramilS commented Dec 14, 2022

+1

@mavek87
Copy link

mavek87 commented Jan 24, 2023

+1 Someone please open a request if he knows how to do it. Pretty much every programming language in the world has this basic feature except Java. It's crazy. Please implement this or at least give us a REAL alternative with something similar to avoid to write horrible workarounds

@niabot
Copy link

niabot commented Jan 28, 2023

+1
With each iteration of libraries, the package names get longer (deeper nesting) and the number of repeated names increases. I wouldn't have seen this as a problem ten to twenty years ago, but it's becoming more and more of a nuisance. I've often noticed this problem when combining multiple libraries and converting types. Of course, both libraries would use the same class name for basically the same thing, which isn't the same thing.

@vvtri
Copy link

vvtri commented Jan 29, 2023

+1

@elfoxus
Copy link

elfoxus commented Feb 15, 2023

+1

@Herm2s
Copy link

Herm2s commented Feb 24, 2023

+1

@Microtribute
Copy link

+10000

@micrommer
Copy link

+1

@longNQ79
Copy link

+1

@greening
Copy link

+1

@GW-Kang
Copy link

GW-Kang commented Mar 23, 2023

+1

@klxfeiyang
Copy link

+1

@andirady
Copy link

Would be nice if this can be extended to support generics. Example:

import java.util.List;
import java.util.Map<String, List<Person>> as PeopleMap;

import com.example.myapp.Person;

public class PeopleService {
    public PeopleMap groupByDepartmentName() { // exported as Map<String, List<Person>> groupByDepartmentName()
        ...
    }

    public PeopleMap groupByPositionName() {
        ...
    }
}

@vlad8x8
Copy link

vlad8x8 commented May 11, 2023

So, @cardil is this JEP posted to JDK Bug System?

@rhinoarmy
Copy link

I can confirm this request is not currently in the JDK Bug System, so the developers who actually work on this are probably not aware of this discussion.

@Ramesh-babu-RF
Copy link

+1

@mavek87
Copy link

mavek87 commented Jul 25, 2023

How can we post it there? If someone knows please do it or tell us how to propose a request

@ModProg
Copy link

ModProg commented Oct 18, 2023

Something similar would be partial path imports, as they could also elevate some of the similar pain points, and could be even more explicit when read:

package com.example.mapper;

import com.example.dto;
import com.example.entity;

public class Example {
   public dto.Example toDto(entity.Example example) {
      // ...
   }
}

The same already works for inner classes, so I don't see a reason why it wouldn't work here.

@CC007
Copy link

CC007 commented Oct 18, 2023

The same already works for inner classes, so I don't see a reason why it wouldn't work here.

The only issue would be collisions with those inner classes, but if you follow the conventions for class names and package names, that shouldn't be an issue. Also, such collisions could already happen today anyway, so that shouldn't be a showstopper.

@rupe120
Copy link

rupe120 commented Dec 18, 2023

+1

@skybluethis
Copy link

+1

@CYWong-Nick
Copy link

CYWong-Nick commented Dec 30, 2023

+1
This makes me feel Java is lagging behind. Java did not have string template until Java 21, and should we expect import renaming to come in Java 69? Other popular languages (JavaScript, Python, C#) already have this feature, even JVM-based languages like Kotlin, Scala, and Groovy can do it.

Some may say this feature is unnecessary as it is rarely used. However, when "3 billion devices run Java", there is a reasonable chance that 2 or more libraries will use the same class name. You may want to call it a "nice to have" feature since it does not bring new functionality but only makes life easier, in that case please remember why we did not code in C or ASM is because Java is (was?) a nice language to use.

@mouhsineelachbi
Copy link

mouhsineelachbi commented Jan 24, 2024

+1

@DanielLiu1123
Copy link

+1

@gdonoso94
Copy link

+1

@jorgevilaca82
Copy link

+1

@abuepam
Copy link

abuepam commented May 16, 2024

+1 - I am studying JAVa now, and I already think that it would be way better to have than not having :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment