48

java.lang.String is declared as final, however are there any mechanisms available legitimate or otherwise to extend it and replace the equals(String other) method?

9
  • 4
    What are you hoping to achieve with this? Commented Feb 23, 2012 at 18:14
  • 1
    I was wondering about the same some time ago, and the only answer I got was to wrap a String to other class and use this other class instead. (un)fortunately, final class is a final class :) Commented Feb 23, 2012 at 18:18
  • 20
    From SCJP Book: If programmers were free to extend the String class, civilization - as we know it - could collapse. Commented Aug 26, 2013 at 21:44
  • 1
    But there are good reasons to want to extend String. Many things are a String with extra restrictions/conditions. Your password is a string, but not every string is suitable as a password (legal characters etc). I think Scala allows you to subtype String? Commented Aug 24, 2015 at 21:53
  • 9
    The SCJP book sucks. Either give us a real reason or just don't mention it. I for one have had many use cases for extending String... Or at least being able to create a MyClass and then be able to do MyClass = 'My String'. Also there are languages that do allow you to override / create your own String classes... C++ springs to mind. I'm not saying I like C++ string handling (au contraire), but it's not like civilization has collapsed due to it. Commented Jan 25, 2016 at 20:18

6 Answers 6

39

No, absolutely not. If you want some "other" kind of string, create another type which might contain a string:

public final class OtherString {
    private final String underlyingString;

    public OtherString(String underlyingString) {
        this.underlyingString = underlyingString;
    }        

    // Override equals however you want here
}
Sign up to request clarification or add additional context in comments.

14 Comments

But String s="hi"; is doable, but not OtherString s="hi";
@everlasto: Sure. You'd have to write OtherString s = new OtherString("hi");
@marlonpya: No more or less than with any other class you create.
@JonSkeet it seems to me that making string immutable ("private final byte[] value;" java.lang.String:140) is not the same thing as making String non-extensible ("public final class String" java.lang.String:125). Just because someone figured out one way does not mean it's the only way.
@DavidUrry: Again, it's not something I'd want - because I want guarantees of actual immutability, not just "immutability of the bits exposed by string". It may be useful to you, but I'm personally really glad that String is final.
|
37

I guess the closest you can come is making some class that implements CharSequence. Most JDK string manipulation methods accept a CharSequence. StringBuilder for example. Combined with a good implementation of toString(), the various methods of String and valueOf(), you can come pretty close to a natural substitute.

2 Comments

This is a very good comment... possibly the best solution to OP's problem.
This is the correct answer. I've successfully used this to implement non-empty Strings, and do away with .length() == 0 checks in my code!
7

Now, there is a way. With manifold it's possible to extend every Java Class. Here is an example for String:

package extensions.java.lang.String;

import manifold.ext.api.*;

@Extension
public class MyStringExtension {

  public static void print(@This String thiz) {
    System.out.println(thiz);
  }

  @Extension
  public static String lineSeparator() {
    return System.lineSeparator();
  }
}

Can than be used as follow:

String name = "Manifold";
name.print();
String.lineSeparator();

Another example can be found here: https://jaxenter.com/manifold-code-generator-part-2-151762.html

Notice, that manifold is still alpha.

1 Comment

Very interesting, indeed!
4

You cannot extend a class that is marked as final. You can use composition to either put a String object inside or you can hand roll your own version. This can be accomplished via character arrays and the other magic that goes into creating String classes.

Comments

4

It is not possible to directly inherit String class as it is final. Also wrapper classes java.lang.Integer, java.lang.Float, etc... are final.

1 Comment

This answer only covers standard inheritance - how about alternative approaches to replacing the equals method?
0

I wrote a simple Strings class that mimics java.lang.String and can be extended. This is just to demonstrate the core implementation requirements behind String are not "mysterious" or "complicated". And, you can use it to do things like create a password extension and manage strings more effectively.

Needs:

  • native call to big-endian. (see private static native boolean isBigEndian();)
  • other String methods.
    package com.paintedintel.util;
    
    import java.nio.charset.StandardCharsets;
    
/**
 * 
 * @author David Urry
 * @date 2/8/2020
 * 
 * Strings is a light weight string implementation based on StringUTF16.
 * It's sole purpose is to create a system where strings can be extended
 * so that the type of string can be extended without the weight of carrying
 * extra object references.  
 * 
 * Strings extension is important for 2 reasons:
 * 1) The extra object reference slows the code down by a factor of 50% making 
 *    a 10X speed improvement only 5X.  As the object of looking at/comparing and
 *    otherwise managing strings is expensive.
 * 2) The code understanding benefits greatly from understanding the type of string
 *    you are working with (Name, Value, Field, InitValue, Comment...).  The constant
 *    evaluation of List<String> for example is greatly simplified when observing
 *    List<Field> instead.
 *    
 * This problem was also greatly simplified by working with Type, Domain, Datum,
 * StreamDomain, StreamCase and other objects as complex objects.
 */

public class Strings {
  final byte[] value;
    /** Cache the hash code for the string */
    private int hash; // Default to 0
 
  protected Strings(String value){
      if (value != null) {
          this.value = value.getBytes();
          this.hash = value.hashCode();
      } else {
          this.value = new byte[0];
          this.hash = 0;
      }
  }
  
  Strings(byte[] value){
      this.value = value;
      this.hash = Strings.hashCode(value);
  }
  
  @Override
  public String toString() {
      return new String(value, StandardCharsets.UTF_8);
  }
  
  public String str() {
      return toString();
  }
  
  public boolean equals(String str) {
      return (str == null)?((value == null || this.length() == 0)?true:false):str.hashCode() == value.hashCode();
  }
  
  public boolean eq(String str) {
      return equals(str);
  }
  
  byte[] getBytes() {
      return value;
  }
  
  int getHash() {
      return hash;
  }
  
  public int length() {
      return value.length >> 1;
  }
  
  /**
   * this is based on StringUTF16 
   * @param value
   * @return
   */
  synchronized public static int hashCode(byte[] value) {
        int h = 0;
        int length = value.length >> 1;
        for (int i = 0; i < length; i++) {
            h = 31 * h + getChar(value, i);
        }
        return h;
    }
    
    // intrinsic performs no bounds checks
  synchronized static char getChar(byte[] val, int index) {
        assert index >= 0 && index < length(val) : "Trusted caller missed bounds check";
        index <<= 1;
        return (char)(((val[index++] & 0xff) << HI_BYTE_SHIFT) |
                      ((val[index]   & 0xff) << LO_BYTE_SHIFT));
    }

    //private static native boolean isBigEndian();

//    private static boolean isBigEndian() {
//        //as of 2018 there are no major BigEndian systems left.
//        // This is because it's less processing to convert & work with
//        // Little-Endian.
//        return false;
//    }
    
    static final int HI_BYTE_SHIFT = 0;
    static final int LO_BYTE_SHIFT = 8;
//    static {
//        if (isBigEndian()) {
//            HI_BYTE_SHIFT = 8;
//            LO_BYTE_SHIFT = 0;
//        } else {
//            HI_BYTE_SHIFT = 0;
//            LO_BYTE_SHIFT = 8;
//        }
//    }
    
    synchronized public static int length(byte[] value) {
        return value.length >> 1;
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.