Java是否有类似于Python的简易字符串切片的功能?

30

好的,我想知道是否有一种使用Java可以像下面Python那样实现的方法...

string_sample = "hello world"

string_sample[:-1]
>>> "hello world"

string_sample[-1]
>>> "d"

string_sample[3]
>>> "l"

因为在我看来,Java让你为了相同的结果而努力工作(我特别是指每次都要使用两个数字以及缺少一个 -1 来指示最后一个字符)

String string_sample = "hello world";

string_sample.substring(0,string_sample.length()-1);
>>> "hello world"

string_sample.substringstring_sample.length()];
>>> "d"

string_sample.(3,4);
>>> "l"

我还没有涉及到 Java 中的数组/列表,所以真的希望 Java 有比这更容易的方法。

编辑:把 string_sample[3] 的 'i' 改成了 'l'。注意到了 Maroun!


2
在你的Python示例中,string_sample[3]代表什么意思? - Maroun
1
你需要实现自己的Java版本 :D - Maroun
Maroun,我现在感觉很傻。你发现得很好,已经编辑过了。 - rory
1
我并不确定,但我怀疑许多Java程序员会认为leftmidright函数比基于负索引的切片函数更像Java(Java中的Pythonic是什么?)。 - abarnert
@abarnert:将那些无法在有缺陷的、略带怪癖的Python 2.5版本中支持C扩展的Python库移植过来,或者应对关于糟糕性能的抱怨。 - millimoose
显示剩余5条评论
8个回答

25

抱歉,Java的substring没有Python的切片符号那么灵活。

具体来说:

  • 你只能给出起始位置,或者起始和结束位置,但不能只给出结束位置。(同时,没有步长,但你不会太在意这一点。)
  • 负数索引是错误的,而不是从末尾计算。

你可以在这里查看文档

然而,自己编写这个函数并不难:

public String slice_start(String s, int startIndex) {
    if (startIndex < 0) startIndex = s.length() + startIndex;
    return s.substring(startIndex);
}

public String slice_end(String s, int endIndex) {
    if (endIndex < 0) endIndex = s.length() + endIndex;
    return s.substring(0, endIndex);
}

public String slice_range(String s, int startIndex, int endIndex) {
    if (startIndex < 0) startIndex = s.length() + startIndex;
    if (endIndex < 0) endIndex = s.length() + endIndex;
    return s.substring(startIndex, endIndex);
}

将它们作为某个实用类的静态方法。

显然这与 Python 不完全相同,但它可能处理您想要的所有情况,并且非常简单。如果您想处理其他边缘情况(包括步长、传递切片等),可以添加任何您想要的其他代码;没有什么特别棘手的。


其他序列基本相同,但是在那里您将需要使用 subSequence 而不是 substring。(您也可以在字符串上使用 subSequence,因为 StringCharSequence。)

数组实际上根本不是序列类型;您需要编写明确创建新数组并复制子数组的代码。但这仍然不太复杂。


请注意,您可能希望寻找已经为您完成此操作的库。至少有三个链接到本页其他答案中,这应该使您的搜索变得容易。 :) (您可能仍然想自己做一次,以了解这些库的工作方式,但对于生产代码,我更愿意使用一个库,其中其他人已经找出并测试了所有边缘情况,而不是重新实现轮子并在单元测试中捕获它们或处理领域中的错误...)


11
Java Boon切片符号允许所有这些,并且适用于字符串、列表、集合、映射等等。
许多语言都有切片符号(例如Ruby、Groovy和Python)。Boon将其添加到了Java中。
Boon有三个slc运算符:slcslc(仅限开始)和slcEnd
使用Boon,您可以对字符串、数组(基本类型和泛型)、列表、集合、树集、树映射等进行切片操作。
切片符号-一个简单的介绍
Boon切片运算符的工作方式类似于Python/Ruby的切片符号: Ruby切片符号:
 arr = [1, 2, 3, 4, 5, 6]
 arr[2]    #=> 3
 arr[-3]   #=> 4
 arr[2, 3] #=> [3, 4, 5]
 arr[1..4] #=> [2, 3, 4, 5]

Python切片符号

string = "foo bar" 
string [0:3]  #'foo'
string [-3:7] #'bar'

以下内容源自一篇关于Python切片符号的优秀文章:
切片符号的基础知识如下:
Python切片符号
     a[ index ]       # index of item
     a[ start : end ] # items start through end-1
     a[ start : ]     # items start through the rest of the array
     a[ : end ]       # items from the beginning through end-1
     a[ : ]           # a copy of the whole array

使用Boon的Java切片表示法:

      idx( index )         // index of item
      slc( a, start, end ) // items start through end-1
      slc( a, start )      // items start through the rest of the array
      slcEnd( a, end )     // items from the beginning through end-1
      copy( a )            // a copy of the whole array

slc 代表切片 idx 代表索引 slcEnd 代表结束切片。 copy 代表复制

需要记住的关键点是,结束值代表未被选择的第一个值。因此,结束和开始之间的差异是所选元素的数量。 另一个特点是,开始或结束可能是负数,这意味着它从数组的末尾开始计算,而不是从开头开始。

因此:

Python 带有负索引的切片符号

         a[ -1 ]    # last item in the array
         a[ -2: ]   # last two items in the array
         a[ :-2 ]   # everything except the last two items

Java负索引

         idx   ( a, -1)     // last item in the array
         slc   ( -2 )       // last two items in the array
         slcEnd( -2 )       // everything except the last two items

Python和Boon对程序员很友好,如果你请求的项数比实际少,Python不允许你越界,如果你这样做,最坏的情况是返回一个空列表。 Boon遵循这个传统,但提供了一个选项来获取越界异常(稍后描述)。在Python和Boon中,如果你走得太远,你会得到长度,如果你试图去0以下,你会得到0(计算后小于0)。相反,Ruby会给你一个空指针(Nil)。 Boon复制了Python的风格,因为Boon的目标之一是避免返回null(你会得到一个异常,Option)。 (Boon有第二个叫做zlc的运算符,它会抛出一个越界索引异常,但大多数人应该使用slc。)
例如,如果你请求slcEnd(a,-2)(a[:-2])而a只包含一个元素,则会返回一个空列表而不是错误。有时你可能更喜欢错误,在Boon中你有这个选项。
更多切片
这里有一些基本的Java类型,列表、数组、蔬菜、原始字符数组和原始字节数组。
声明变量以在Boon中使用
//Boon works with lists, arrays, sets, maps, sorted maps, etc.
List<String> fruitList;
String [] fruitArray;
Set<String> veggiesSet;
char [] letters;
byte [] bytes;
NavigableMap <Integer, String> favoritesMap;
Map<String, Integer> map;

//In Java a TreeMap is a SortedMap and a NavigableMap by the way.

Boon提供的帮助方法可以轻松创建列表、集合、映射、并发映射、排序映射、排序集合等。这些辅助方法包括safeList、list、set、sortedSet、safeSet、safeSortedSet等。其目的是使Java中的列表和映射等数据类型更易于使用。
初始化字符串集、列表、字符数组和字节数组。
veggiesSet  =  set( "salad", "broccoli", "spinach");
fruitList   =  list( "apple", "oranges", "pineapple");
fruitArray  =  array( "apple", "oranges", "pineapple");
letters     =  array( 'a', 'b', 'c');
bytes       =  array( new byte[]{0x1, 0x2, 0x3, 0x4});

有一些方法可以创建地图和排序地图,称为map、sortedMap、safeMap(并发)和sortedSafeMap(并发)。这些主要是因为Java没有列表、地图等字面量而创建的。
Java:使用map运算符生成SortedMap和Map。
 favoritesMap = sortedMap(
      2, "pineapple",
      1, "oranges",
      3, "apple"
 );


 map =    map (
    "pineapple",  2,
    "oranges",    1,
    "apple",      3
 );

你可以使用 idx 运算符来索引地图、列表、数组等。
Java:使用 Boon Java 的 idx 运算符来获取索引处的值。
 //Using idx to access a value.

 assert idx( veggiesSet, "b").equals("broccoli");

 assert idx( fruitList, 1 ).equals("oranges");

 assert idx( fruitArray, 1 ).equals("oranges");

 assert idx( letters, 1 ) == 'b';

 assert idx( bytes, 1 )      == 0x2;

 assert idx( favoritesMap, 2 ).equals("pineapple");

 assert idx( map, "pineapple" )  == 2;

idx运算符也可以使用负索引。
Java:使用idx运算符与负值
         //Negative indexes

          assert idx( fruitList, -2 ).equals("oranges");

          assert idx( fruitArray, -2 ).equals("oranges");

          assert idx( letters, -2 ) == 'b';

          assert idx( bytes, -3 )   == 0x2;

Ruby、Groovy和Python都有这个特性。现在你也可以在Java中使用它了!Java版本(Boon)适用于原始数组,因此您将不会自动装箱。
Ruby和Python没有的是SortedSets和SortedMaps的切片表示法。您可以在Java中使用切片表示法来搜索排序映射和排序集合。
切片表示法适用于排序映射和排序集合。
以下是一个将几个概念结合在一起的示例。
          set = sortedSet("apple", "kiwi", "oranges", "pears", "pineapple")

          slcEnd( set, "o" )      //returns ("oranges", "pears", "pineapple")
          slc( set, "ap", "o" )   //returns ("apple", "kiwi"),
          slc( set, "o" )         //returns ("apple", "kiwi")

你正在处理排序映射和排序集的切片操作,这类似于一个区间查询。 “pi”之后是哪个项目?
          after(set, "pi") //pineapple

还有菠萝之前呢?

          before(set, "pi")

好的,让我们一步一步地来...

  NavigableSet<String> set =
          sortedSet("apple", "kiwi", "oranges", "pears", "pineapple");

  assertEquals(

          "oranges", idx(set, "ora")

  );

记住:TreeSet实现了NavigableSet和SortedSet。
这是从我的博客中得出的......

http://rick-hightower.blogspot.com/2013/10/java-slice-notation-to-split-up-strings.html

还有更多的例子。

我从Python切片讨论中推导出了一些措辞。

解释Python的切片符号

这里是Boon项目链接:

https://github.com/RichardHightower/boon

现在让我们继续进行切片操作!
我们可以使用以下代码查找以'o'开头的第一个水果:
idx(set, "o")

这里是之前我们创建的水果集合(使用TreeSet,其中包含“apple”,“kiwi”,“oranges”,“pears”和“pineapple”)。
      assertEquals(

          "oranges", idx(set, "o")

      );

我们找到了橙子!

这次我们在寻找以“p”开头的水果,即 idx(set, "p")。

      assertEquals(
          "pears",
          idx(set, "p")
      );

是的!我们找到了梨!

那么以“pi”开头的水果如“菠萝”怎么样 - idx(set, "pi")

  assertEquals(
          "pineapple",
          idx(set, "pi")
  );

你也可以查询一个元素的下一个元素。"pi"后面是什么? after(set, "pi")
  assertEquals(

          "pineapple",
          after(set, "pi")

  );

“菠萝”是在“pi”项目之后。顺便说一下,after和idx是相同的。那么为什么我要添加一个after呢?这样我就可以有一个before!!! :) 如果想知道“pi”之前是什么,该怎么办?使用before(set, "pi")函数。
  assertEquals(

          "pears",
          before(set, "pi")

  );

请问所有介于“ap”和“o”之间的水果怎么样?正如我所承诺的那样,这里有切片符号!

slc(set, "ap", "o")

  assertEquals(

          sortedSet("apple", "kiwi"),
          slc(set, "ap", "o")

  );

"o"之后的所有水果怎么样?

slc(set, "o")

  assertEquals(

          sortedSet("apple", "kiwi"),
          slc(set, "o")

  );

所以,字母“o”之后的所有水果是“苹果”和“猕猴桃”。

那么,“o”之前的所有水果呢?(slcEnd将其解读为我正在切掉末尾。)

slcEnd(set, "o")

  assertEquals(

          sortedSet("oranges", "pears", "pineapple"),
          slcEnd(set, "o")
  );

所以所有的水果包括“o”在内都是“橙子”,“梨子”和“菠萝”。
类似列表的安全切片
如果索引超出范围,这些运算符会抛出异常:
使用Boon的Java切片表示法如下:
      ix( index )         // index of item
      zlc( a, start, end ) // items start through end-1
      zlc( a, start )      // items start through the rest of the array
      zlcEnd( a, end )     // items from the beginning through end-1

zlc代表零容忍切片 ix代表零容忍索引 zlcEnd代表零容忍结束切片。 copy代表复制,当然。

男孩和女孩们...记得始终对你不了解的对象执行安全切片。

也适用于基元类型,因此没有自动装箱

索引原语

 byte[] letters =
      array((byte)'a', (byte)'b', (byte)'c', (byte)'d');

 assertEquals(
      'a',
      idx(letters, 0)
 );


 assertEquals(
      'd',
      idx(letters, -1)
 );


 assertEquals(
      'd',
      idx(letters, letters.length - 1)
 );

 idx(letters, 1, (byte)'z');

 assertEquals(
      (byte)'z',
      idx(letters, 1)
 );

方法len和idx是通用运算符,可用于列表、数组、集合、映射等。

len给出类似数组、类似列表、类似映射的东西的长度。 idx给出类似数组、类似列表、类似映射的东西中位置为“索引”的项。

HOME MC字符串切片!

以下是Boon Java字符串切片的一些示例。

  String letters = "abcd";

  boolean worked = true;

  worked &=

          idx(letters, 0)  == 'a'
                  || die("0 index is equal to a");



  worked &=

          idx(letters, -1)  == 'd'
                  || die("-1 index is equal to a");

另一种表达 idx(letters, -1) == 'd' 的方式是 idx(letters, letters.length() - 1) == 'd'!我更喜欢更简短的方式!
  worked &=

          idx(letters, letters.length() - 1) == 'd'
                   || die("another way to express what the -1 means");


  //We can modify too
  letters = idx(letters, 1, 'z');

  worked &=

          idx(letters, 1) == 'z'
                  || die("Set the 1 index of letters to 'z'");


  worked &= (
          in('a', letters) &&
          in('z', letters)
  ) || die("'z' is in letters and 'a' is in letters");

切片,切片,宝贝!

  letters = "abcd";

  worked &=
          slc(letters, 0, 2).equals("ab")
              || die("index 0 through index 2 is equal to 'ab'");



  worked &=
          slc(letters, 1, -1).equals("bc")
                  || die("index 1 through index (length -1) is equal to 'bc'");


  worked &=
          slcEnd(letters, -2).equals("ab")
                  || die("Slice of the end of the string!");


  worked &=
          slcEnd(letters, 2).equals("ab")
                  || die("Vanilla Slice Slice baby!");

我开着我的5.0跑车,敞开车顶让头发飘扬!剁剁宝贝!!!


不用谢。:) 感谢也很好... 点赞也很不错。 :) - RickHigh
这很酷,写得很好 - 但我认为这里有一些错误。例如,您将idx定义为仅使用索引而不是序列和索引。然后,在您的第一个示例中,您确实将序列传递给了idx - 但是没有将其传递给slc - abarnert

8

Apache commons-lang有一些支持StringUtils中的内容

从指定的字符串中获取子字符串,避免异常。

可以使用负的起始位置从字符串末尾开始n个字符。

但是你仍然需要使用显式的起始索引。


非常感谢这个宝贵的资源。如果我有足够的声望,我一定会给你点赞的。 - rory
@rory 我原本以为点赞不会影响声望值,但是我也不清楚低声望账户的限制是什么。 - millimoose
+1。如果你想超越我展示的那个简单版本,使用它可能比自己编写更聪明。(任何不那么明显的边缘情况,Apache可能已经遇到过了...) - abarnert

1
你可以轻松编写这样的方法,记住负索引是从字符串长度中减去以获得正确的索引。
public String slice(String s, int start) {
   if (start < 0) start = s.length() + start; 

   return s.substring(start);
}

1
使用 substring
class Main
{
  public static void main (String[] args) throws java.lang.Exception
  {
     String s = new String("hello world");
     System.out.println(s.substring(0, s.length()));
     System.out.println(s.substring(s.length() - 1, s.length()));
     System.out.println(s.substring(3, 4));
  }
}

或者 charAt:
System.out.println(s.charAt(s.length() - 1));
System.out.println(s.charAt(3));

Java不是Python,因此应避免使用负索引以保持一致性。但是,您可以编写一个简单的转换函数。


0
我创建了一个简单的库,叫做 JavaSlice,它提供了一种统一的方式来访问 Java 中的 String、List 或数组切片,类似于 Python。
因此,您的示例可以简单地编写为:
    String sample = "hello world";

    System.out.println(slice(sample, 0, -1)); // "hello worl"
    System.out.println(slice(sample, -1)); // 'd'
    System.out.println(slice(sample, 3)); // 'l'

0

我认为没有Java字符串库能够提供与Python完全相同的功能。你最好的选择可能是构建一个新类,提供你需要的函数。由于Java中的String是一个final类,你无法从它继承一个类,所以你需要使用组合。例如:

public class PythonString {
  protected String value;
  public PythonString(String str) {
    value = new String(str);
  }

  public char charAt(int index) {
    if (index < 0) {
      return value.charAt(value.length() + index);
    }
    return value.charAt(index);
  }

  ....

}

另一种选择是创建一个静态字符串库。

2
或者,你可以使用“静态”实用函数。 - millimoose
@millimoose:谢谢你指出来。让我完善我的回答,你也可以编辑我的回答:)。 - keelar
不要创建一个新的可实例化的PythonString类。相反,创建一个带有一组静态实用程序方法的最终类 - 比如说PyStrings。创建一个新的字符串类的新风格也会引入各种复杂性,使其不值得。 (例如,当传递一个java.lang.String时,您的新类应该如何实现equals()?答案必须是它们永远不相等 - 否则equals()就变成了非交换的。但是,如果您想要比较它们,那么您必须不断地在两种字符串类型之间进行转换。) - Paul

-1
简单的回答是,没有。在这两种语言中,字符串都是不可变的。字符串在内部以字符数组的形式存储,因此在 Python 中使用 substring 和使用方括号实际上是做同样的事情。Java 不支持操作符重载,因此无法将该功能添加到语言中。使用 substring 并不是那么糟糕。你不应该经常这样做。如果你经常这样做,你可以编写帮助函数来简化你的使用。

3
OP在谈论负数切片索引,而不是你所说的其他内容。根据代码示例,他们显然并没有试图原地修改字符串。 - millimoose

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接