Java 编程问题:一、字符串、数字和数学5

简介: Java 编程问题:一、字符串、数字和数学

28 通过无符号转换转换数字


这个问题要求我们通过无符号转换将给定的有符号的int转换成long。那么,让我们考虑签名的Integer.MIN_VALUE,即 -2147483648。


在 JDK8 中,使用Integer.toUnsignedLong()方法,转换如下(结果为 2147483648):

long result = Integer.toUnsignedLong(Integer.MIN_VALUE);


下面是另一个将有符号的Short.MIN_VALUEShort.MAX_VALUE转换为无符号整数的示例:

int result1 = Short.toUnsignedInt(Short.MIN_VALUE);
int result2 = Short.toUnsignedInt(Short.MAX_VALUE);

其他同类方法有Integer.toUnsignedString()、Long.toUnsignedString()、Byte.toUnsignedInt()、Byte.toUnsignedLong()、Short.toUnsignedInt()、Short.toUnsignedLong()。



29 比较两个无符号数


让我们考虑两个有符号整数,Integer.MIN_VALUE(-2147483648)和Integer.MAX_VALUE(2147483647)。比较这些整数(有符号值)将导致 -2147483648 小于 2147483647:

// resultSigned is equal to -1 indicating that
// MIN_VALUE is smaller than MAX_VALUE
int resultSigned = Integer.compare(Integer.MIN_VALUE, 
  Integer.MAX_VALUE);


在 JDK8 中,这两个整数可以通过Integer.compareUnsigned()方法作为无符号值进行比较(这相当于无符号值的Integer.compare())。该方法主要忽略了符号位的概念,最左边的被认为是最重要的位。在无符号值保护伞下,如果比较的数字相等,则此方法返回 0;如果第一个无符号值小于第二个无符号值,则此方法返回小于 0 的值;如果第一个无符号值大于第二个无符号值,则此方法返回大于 0 的值。


下面的比较返回 1,表示Integer.MIN_VALUE的无符号值大于Integer.MAX_VALUE的无符号值:

// resultSigned is equal to 1 indicating that
// MIN_VALUE is greater than MAX_VALUE
int resultUnsigned 
  = Integer.compareUnsigned(Integer.MIN_VALUE, Integer.MAX_VALUE);

compareUnsigned()方法在以 JDK8 开始的IntegerLong类中可用,在以 JDK9 开始的ByteShort类中可用。



30 无符号值的除法和模


JDK8 无符号算术 API 通过divideUnsigned()remainderUnsigned()方法支持计算两个无符号值的除法所得的无符号商和余数。


让我们考虑一下Interger.MIN_VALUEInteger.MAX_VALUE有符号数,然后应用除法和模。这里没有什么新鲜事:

// signed division
// -1
int divisionSignedMinMax = Integer.MIN_VALUE / Integer.MAX_VALUE; 
// 0
int divisionSignedMaxMin = Integer.MAX_VALUE / Integer.MIN_VALUE;
// signed modulo
// -1
int moduloSignedMinMax = Integer.MIN_VALUE % Integer.MAX_VALUE; 
// 2147483647
int moduloSignedMaxMin = Integer.MAX_VALUE % Integer.MIN_VALUE; 


现在,我们将Integer.MIN_VALUEInteger.MAX_VALUE视为无符号值,并应用divideUnsigned()remainderUnsigned()

// division unsigned
int divisionUnsignedMinMax = Integer.divideUnsigned(
  Integer.MIN_VALUE, Integer.MAX_VALUE); // 1
int divisionUnsignedMaxMin = Integer.divideUnsigned(
  Integer.MAX_VALUE, Integer.MIN_VALUE); // 0
// modulo unsigned
int moduloUnsignedMinMax = Integer.remainderUnsigned(
  Integer.MIN_VALUE, Integer.MAX_VALUE); // 1
int moduloUnsignedMaxMin = Integer.remainderUnsigned(
  Integer.MAX_VALUE, Integer.MIN_VALUE); // 2147483647


注意它们与比较操作的相似性。这两种操作,即无符号除法和无符号模运算,都将所有位解释为值位,并忽略符号位。


divideUnsigned() and remainderUnsigned() are present in the Integer and Long classes, respectively.


31 double/float是一个有限的浮点值


这个问题产生于这样一个事实:一些浮点方法和操作产生InfinityNaN作为结果,而不是抛出异常。


检查给定的float/double是否为有限浮点值的解决方案取决于以下条件:给定的float/double值的绝对值不得超过float/double类型的最大正有限值:


// for float
Math.abs(f) <= Float.MAX_VALUE;
// for double
Math.abs(d) <= Double.MAX_VALUE


从 Java8 开始,前面的条件通过两个专用的标志方法Float.isFinite()Double.isFinite()公开。因此,以下示例是有限浮点值的有效测试用例:

Float f1 = 4.5f;
boolean f1f = Float.isFinite(f1); // f1 = 4.5, is finite
Float f2 = f1 / 0;
boolean f2f = Float.isFinite(f2); // f2 = Infinity, is not finite
Float f3 = 0f / 0f;
boolean f3f = Float.isFinite(f3); // f3 = NaN, is not finite
Double d1 = 0.000333411333d;
boolean d1f = Double.isFinite(d1); // d1 = 3.33411333E-4,is finite
Double d2 = d1 / 0;
boolean d2f = Double.isFinite(d2); // d2 = Infinity, is not finite
Double d3 = Double.POSITIVE_INFINITY * 0;
boolean d3f = Double.isFinite(d3); // d3 = NaN, is not finite


这些方法在以下情况下非常方便:

if (Float.isFinite(d1)) {
  // do a computation with d1 finite floating-point value
} else {
  // d1 cannot enter in further computations
}



32 对两个布尔表达式应用逻辑与/或/异或


基本逻辑运算的真值表(异或)如下:


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OectRBUa-1657077108927)(https://github.com/apachecn/apachecn-java-zh/raw/master/docs/java-coding-prob/img/482c6d19-72f9-41a3-9590-52cde694d0a0.png)]


在 Java 中,逻辑和运算符表示为&&,逻辑或运算符表示为||,逻辑异或运算符表示为^。从 JDK8 开始,这些运算符被应用于两个布尔值,并被包装在三个static方法中—Boolean.logicalAnd()、Boolean.logicalOr()和Boolean.logicalXor():


int s = 10;
int m = 21;
// if (s > m && m < 50) { } else { }
if (Boolean.logicalAnd(s > m, m < 50)) {} else {}
// if (s > m || m < 50) { } else { }
if (Boolean.logicalOr(s > m, m < 50)) {} else {}
// if (s > m ^ m < 50) { } else { }
if (Boolean.logicalXor(s > m, m < 50)) {} else {}


也可以结合使用这些方法:

if (Boolean.logicalAnd(
    Boolean.logicalOr(s > m, m < 50),
    Boolean.logicalOr(s <= m, m > 50))) {} else {}





33 将BigInteger转换为原始类型


BigInteger类是表示不可变的任意精度整数的非常方便的工具。


此类还包含用于将BigInteger转换为bytelongdouble等原始类型的方法(源于java.lang.Number)。然而,这些方法会产生意想不到的结果和混乱。例如,假设有BigInteger包裹Long.MAX_VALUE

BigInteger nr = BigInteger.valueOf(Long.MAX_VALUE);


让我们通过BigInteger.longValue()方法将这个BigInteger转换成一个原始long

long nrLong = nr.longValue();


到目前为止,由于Long.MAX_VALUE是 9223372036854775807,nrLong原始类型变量正好有这个值,所以一切都按预期工作。


现在,让我们尝试通过BigInteger.intValue()方法将这个BigInteger类转换成一个原始的int值:

int nrInt = nr.intValue();



此时,nrInt原始类型变量的值将为 -1(相同的结果将产生shortValue()和byteValue()。根据文档,如果BigInteger的值太大,无法容纳指定的原始类型,则只返回低位n位(n取决于指定的原始类型)。


但是如果代码没有意识到这个语句,那么它将在进一步的计算中把值推为 -1,这将导致混淆。


但是,从 JDK8 开始,添加了一组新的方法。这些方法专门用于识别从BigInteger转换为指定的原始类型过程中丢失的信息。如果检测到丢失的信息,则抛出ArithmeticException。这样,代码表示转换遇到了一些问题,并防止了这种不愉快的情况。


这些方法是longValueExact()、intValueExact()、shortValueExact()、byteValueExact():

long nrExactLong = nr.longValueExact(); // works as expected
int nrExactInt = nr.intValueExact();    // throws ArithmeticException



注意,intValueExact()没有返回 -1 作为intValue()。这一次,由于试图将最大的long值转换为int而导致的信息丢失通过ArithmeticException类型的异常发出信号。




34 将long转换为int


long值转换为int值似乎是一件容易的工作。例如,潜在的解决方案可以依赖于以下条件:

long nr = Integer.MAX_VALUE;
int intNrCast = (int) nr;


或者,它可以依赖于Long.intValue(),如下所示:

int intNrValue = Long.valueOf(nrLong).intValue();


两种方法都很有效。现在,假设我们有以下long值:

long nrMaxLong = Long.MAX_VALUE;


这一次,两种方法都将返回 -1。为了避免这种结果,建议使用 JDK8,即Math.toIntExact()。此方法获取一个long类型的参数,并尝试将其转换为int。如果得到的值溢出了int,则该方法抛出

ArithmeticException:
// throws ArithmeticException
int intNrMaxExact = Math.toIntExact(nrMaxLong);

在幕后,toIntExact()依赖于((int)value != value)条件。




35 除法的下限与模的计算


假设我们有以下划分:

double z = (double)222/14;


这将用这个除法的结果初始化z,即 15.85,但是我们的问题要求这个除法的下限是 15(这是小于或等于代数商的最大整数值)。获得该期望结果的解决方案将包括应用Math.floor(15.85),即 15。


但是,222 和 14 是整数,因此前面的除法如下:

int z = 222/14;


这一次,z将等于 15,这正是预期的结果(/运算符返回最接近零的整数)。无需申请Math.floor(z)。此外,如果除数为 0,则222/0将抛出ArithmeticException。


到目前为止的结论是,两个符号相同的整数(都是正的或负的)的除法底可以通过/运算符得到。


好的,到目前为止,很好,但是假设我们有以下两个整数(相反的符号;被除数是负数,除数是正数,反之亦然):

double z = (double) -222/14;


此时,z将等于 -15.85。同样,通过应用Math.floor(z),结果将是 -16,这是正确的(这是小于或等于代数商的最大整数值)。

让我们用int再次讨论同样的问题:

int z = -222/14;


这次,z将等于 -15。这是不正确的,Math.floor(z)在这种情况下对我们没有帮助,因为Math.floor(-15)是 -15。所以,这是一个应该考虑的问题。


从 JDK8 开始,所有这些病例都通过Math.floorDiv()方法被覆盖和暴露。此方法以表示被除数和除数的两个整数为参数,返回小于或等于代数商的最大值(最接近正无穷大)int值:


int x = -222;
int y = 14;
// x is the dividend, y is the divisor
int z = Math.floorDiv(x, y); // -16


Math.floorDiv()方法有三种口味:floorDiv(int x, int y)、floorDiv(long x, int y)和floorDiv(long x, long y)。

在Math.floorDiv()之后,JDK8 附带了Math.floorMod(),它返回给定参数的地板模量。这是作为x - (floorDiv(x, y) * y)的结果计算的,因此对于符号相同的参数,它将返回与%运算符相同的结果;对于符号不相同的参数,它将返回不同的结果。

将两个正整数(a/b)的除法结果四舍五入可以快速完成,如下所示:


long result = (a + b - 1) / b;


下面是一个例子(我们有4/3=1.33,我们想要 2):

long result = (4 + 3 - 1) / 3; // 2


下面是另一个例子(我们有17/7=2.42,我们想要 3):

long result = (17 + 7 - 1) / 7; // 3


如果整数不是正的,那么我们可以依赖于Math.ceil()

long result = (long) Math.ceil((double) a/b);





36 下一个浮点值


有一个整数值,比如 10,使得我们很容易得到下一个整数值,比如10+1(在正无穷方向)或者10-1(在负无穷方向)。尝试为floatdouble实现同样的目标并不像对整数那么容易。


从 JDK6 开始,Math类通过nextAfter()方法得到了丰富。此方法采用两个参数,即初始数字(float或double)和方向(Float/Double.NEGATIVE/POSITIVE_INFINITY)——并返回下一个浮点值。在这里,返回 0.1 附近的下一个负无穷方向的浮点值是这种方法的一个特色:


float f = 0.1f;
// 0.099999994
float nextf = Math.nextAfter(f, Float.NEGATIVE_INFINITY);


从 JDK8 开始,Math类通过两个方法进行了丰富,这两个方法充当了nextAfter()的快捷方式,而且速度更快。这些方法是nextDown()nextUp()

float f = 0.1f;
float nextdownf = Math.nextDown(f); // 0.099999994
float nextupf = Math.nextUp(f); // 0.10000001
double d = 0.1d;
double nextdownd = Math.nextDown(d); // 0.09999999999999999
double nextupd = Math.nextUp(d); // 0.10000000000000002

因此,在负无穷大方向上的nextAfter()可通过Math.nextDown()nextAfter()获得,而在正无穷大方向上的Math.nextUp()可通过Math.nextUp()获得。



37 将两个大int/long值相乘并溢出


让我们从*操作符开始深入研究解决方案,如下例所示:

int x = 10;
int y = 5;
int z = x * y; // 50


这是一种非常简单的方法,对于大多数涉及int、long、float和double的计算都很好。


现在,让我们将此运算符应用于以下两个大数(将 2147483647 与自身相乘):


int x = Integer.MAX_VALUE;
int y = Integer.MAX_VALUE;
int z = x * y; // 1


此时,z将等于 1,这不是预期的结果,即 4611686014132420609。仅将z类型从int更改为long将无济于事。但是,将x和y的类型从int改为long将:

long x = Integer.MAX_VALUE;
long y = Integer.MAX_VALUE;
long z = x * y; // 4611686014132420609


但是如果我们用Long.MAX_VALUE代替Integer.MAX_VALUE,问题会再次出现:

long x = Long.MAX_VALUE;
long y = Long.MAX_VALUE;
long z = x * y; // 1



因此,溢出域并依赖于*运算符的计算将最终导致误导性结果。


与其在进一步的计算中使用这些结果,不如在发生溢出操作时及时得到通知。JDK8 附带了Math.multiplyExact()方法。此方法尝试将两个整数相乘。如果结果溢出,int只抛出ArithmeticException:

int x = Integer.MAX_VALUE;
int y = Integer.MAX_VALUE;
int z = Math.multiplyExact(x, y); // throw ArithmeticException


在 JDK8 中,Math.muliplyExact(int x, int y)返回int,Math.muliplyExact(long x, long y)返回long。在 JDK9 中,还添加了Math.muliplyExact(long, int y)返回long。


JDK9 带有返回值为long的Math.multiplyFull(int x, int y)。此方法对于获得两个整数的精确数学积long非常有用,如下所示:

int x = Integer.MAX_VALUE;
int y = Integer.MAX_VALUE;
long z = Math.multiplyFull(x, y); // 4611686014132420609


为了记录在案,JDK9 还附带了一个名为Math.muliptlyHigh(long x, long y)的方法,返回一个long。此方法返回的long值表示两个 64 位因子的 128 位乘积的最高有效 64 位:


long x = Long.MAX_VALUE;
long y = Long.MAX_VALUE;
// 9223372036854775807 * 9223372036854775807 = 4611686018427387903
long z = Math.multiplyHigh(x, y);

在函数式上下文中,潜在的解决方案将依赖于BinaryOperator函数式接口,如下所示(只需定义相同类型的两个操作数的操作):

int x = Integer.MAX_VALUE;
int y = Integer.MAX_VALUE;
BinaryOperator<Integer> operator = Math::multiplyExact;
int z = operator.apply(x, y); // throw ArithmeticException

对于处理大数,还应关注BigInteger(不可变任意精度整数)和BigDecimal(不可变任意精度带符号十进制数)类。





38 融合乘法加法


数学计算a * b + c在矩阵乘法中被大量利用,在高性能计算、人工智能应用、机器学习、深度学习、神经网络等领域有着广泛的应用。


实现此计算的最简单方法直接依赖于*+运算符,如下所示:

double x = 49.29d;
double y = -28.58d;
double z = 33.63d;
double q = (x * y) + z;



这种实现的主要问题是由两个舍入误差(一个用于乘法运算,一个用于加法运算)引起的精度和性能低下。


不过,多亏了 Intel AVX 执行 SIMD 操作的指令和 JDK9,JDK9 添加了Math.fma()方法,这种计算才得以提高。依靠Math.fma(),使用“四舍五入到最接近的偶数四舍五入”模式只进行一次四舍五入:


double fma = Math.fma(x, y, z);

请注意,这种改进适用于现代 Intel 处理器,因此仅使用 JDK9 是不够的。



39 紧凑数字格式


从 JDK12 开始,添加了一个用于紧凑数字格式的新类。这个类被命名为java.text.CompactNumberFormat。这个类的主要目标是扩展现有的 Java 数字格式化 API,支持区域设置和压缩。


数字可以格式化为短样式(例如,1000变成1K),也可以格式化为长样式(例如,1000变成1000)。这两种风格在Style枚举中分为SHORT和LONG。


除了CompactNumberFormat构造器外,还可以通过两个static方法创建CompactNumberFormat,这两个方法被添加到NumberFormat类中:


  • 第一种是默认语言环境的紧凑数字格式,带有NumberFormat.Style.SHORT
public static NumberFormat getCompactNumberInstance()


   第二种是指定区域设置的紧凑数字格式,带有NumberFormat.Style:

public static NumberFormat getCompactNumberInstance(
    Locale locale, NumberFormat.Style formatStyle)

让我们仔细看看格式化和解析。



格式化


默认情况下,使用RoundingMode.HALF_EVEN格式化数字。但是,我们可以通过NumberFormat.setRoundingMode()显式设置舍入模式。

尝试将这些信息压缩成一个名为NumberFormatters的工具类可以实现如下:


public static String forLocale(Locale locale, double number) {
  return format(locale, Style.SHORT, null, number);
}
public static String forLocaleStyle(
  Locale locale, Style style, double number) {
  return format(locale, style, null, number);
}
public static String forLocaleStyleRound(
  Locale locale, Style style, RoundingMode mode, double number) {
  return format(locale, style, mode, number);
}
private static String format(
  Locale locale, Style style, RoundingMode mode, double number) {
  if (locale == null || style == null) {
    return String.valueOf(number); // or use a default format
  }
  NumberFormat nf = NumberFormat.getCompactNumberInstance(locale,
     style);
  if (mode != null) {
    nf.setRoundingMode(mode);
  }
  return nf.format(number);
}


现在,我们将数字100010000001000000000格式化为US语言环境、SHORT样式和默认舍入模式:

// 1K
NumberFormatters.forLocaleStyle(Locale.US, Style.SHORT, 1_000);
// 1M
NumberFormatters.forLocaleStyle(Locale.US, Style.SHORT, 1_000_000);


// 1B
NumberFormatters.forLocaleStyle(Locale.US, Style.SHORT, 
  1_000_000_000);


我们可以对LONG样式做同样的处理:

// 1thousand
NumberFormatters.forLocaleStyle(Locale.US, Style.LONG, 1_000);
// 1million
NumberFormatters.forLocaleStyle(Locale.US, Style.LONG, 1_000_000);
// 1billion
NumberFormatters.forLocaleStyle(Locale.US, Style.LONG, 1_000_000_000);


我们也可以使用ITALIAN区域设置和SHORT样式:

// 1.000
NumberFormatters.forLocaleStyle(Locale.ITALIAN, Style.SHORT, 
  1_000);
// 1 Mln
NumberFormatters.forLocaleStyle(Locale.ITALIAN, Style.SHORT, 
  1_000_000);
// 1 Mld
NumberFormatters.forLocaleStyle(Locale.ITALIAN, Style.SHORT, 
  1_000_000_000);


最后,我们还可以使用ITALIAN语言环境和LONG样式:

// 1 mille
NumberFormatters.forLocaleStyle(Locale.ITALIAN, Style.LONG, 
  1_000);
// 1 milione
NumberFormatters.forLocaleStyle(Locale.ITALIAN, Style.LONG, 
  1_000_000);
// 1 miliardo
NumberFormatters.forLocaleStyle(Locale.ITALIAN, Style.LONG, 
  1_000_000_000);


现在,假设我们有两个数字:12001600

从取整方式来看,分别取整为10002000。默认取整模式HALF_EVEN1200取整为1000,将1600取整为2000。但是如果我们想让1200变成20001600变成1000,那么我们需要明确设置取整模式如下:

// 2000 (2 thousand)
NumberFormatters.forLocaleStyleRound(
  Locale.US, Style.LONG, RoundingMode.UP, 1_200);
// 1000 (1 thousand)
NumberFormatters.forLocaleStyleRound(
  Locale.US, Style.LONG, RoundingMode.DOWN, 1_600);



解析


解析是格式化的相反过程。我们有一个给定的字符串,并尝试将其解析为一个数字。这可以通过NumberFormat.parse()方法来实现。默认情况下,解析不利用分组(例如,不分组时,5,50K解析为5;分组时,5,50K解析为550000)。


如果我们将此信息压缩为一组助手方法,则获得以下输出:

public static Number parseLocale(Locale locale, String number) 
    throws ParseException {
  return parse(locale, Style.SHORT, false, number);
}
public static Number parseLocaleStyle(
  Locale locale, Style style, String number) throws ParseException {
  return parse(locale, style, false, number);
}
public static Number parseLocaleStyleRound(
  Locale locale, Style style, boolean grouping, String number)
    throws ParseException {
  return parse(locale, style, grouping, number);
}
private static Number parse(
  Locale locale, Style style, boolean grouping, String number)
    throws ParseException {
  if (locale == null || style == null || number == null) {
    throw new IllegalArgumentException(
      "Locale/style/number cannot be null");
  }
  NumberFormat nf = NumberFormat.getCompactNumberInstance(locale, 
    style);
  nf.setGroupingUsed(grouping);
  return nf.parse(number);
}


让我们将5K5 thousand解析为5000,而不显式分组:

// 5000
NumberFormatters.parseLocaleStyle(Locale.US, Style.SHORT, "5K");
// 5000
NumberFormatters.parseLocaleStyle(Locale.US, Style.LONG, "5 thousand");


现在,我们用显式分组解析5K5 thousand5000

// 550000
NumberFormatters.parseLocaleStyleRound(
  Locale.US, Style.SHORT, true, "5,50K");
// 550000
NumberFormatters.parseLocaleStyleRound(
  Locale.US, Style.LONG, true, "5,50 thousand");


通过setCurrency()、setParseIntegerOnly()、setMaximumIntegerDigits()、setMinimumIntegerDigits()、setMinimumFractionDigits()和setMaximumFractionDigits()方法可以获得更多的调优。



总结


本章收集了一系列涉及字符串和数字的最常见问题。显然,这样的问题有很多,试图涵盖所有这些问题远远超出了任何一本书的范围。然而,了解如何解决本章中提出的问题,为您自己解决许多其他相关问题提供了坚实的基础



相关文章
|
19小时前
|
Java Shell API
Java 模块化编程:概念、优势与实战指南
【4月更文挑战第27天】Java 模块化编程是 Java 9 中引入的一项重大特性,通过 Java Platform Module System (JPMS) 实现。模块化旨在解决 Java 应用的封装性、可维护性和性能问题
8 0
|
2天前
|
缓存 Java
Java并发编程:深入理解线程池
【4月更文挑战第26天】在Java中,线程池是一种重要的并发工具,它可以有效地管理和控制线程的执行。本文将深入探讨线程池的工作原理,以及如何使用Java的Executor框架来创建和管理线程池。我们将看到线程池如何提高性能,减少资源消耗,并提供更好的线程管理。
|
3天前
|
存储 安全 Java
Java并发编程中的高效数据结构:ConcurrentHashMap解析
【4月更文挑战第25天】在多线程环境下,高效的数据访问和管理是至关重要的。Java提供了多种并发集合来处理这种情境,其中ConcurrentHashMap是最广泛使用的一个。本文将深入分析ConcurrentHashMap的内部工作原理、性能特点以及它如何在保证线程安全的同时提供高并发性,最后将展示其在实际开发中的应用示例。
|
4天前
|
Java API 调度
[Java并发基础]多进程编程
[Java并发基础]多进程编程
|
4天前
|
Java API 调度
[AIGC] 深入理解Java并发编程:从入门到进阶
[AIGC] 深入理解Java并发编程:从入门到进阶
|
4天前
|
前端开发 Java 测试技术
Java从入门到精通:4.1.1参与实际项目,锻炼编程与问题解决能力
Java从入门到精通:4.1.1参与实际项目,锻炼编程与问题解决能力
|
4天前
|
SQL Java 数据库连接
Java从入门到精通:2.3.2数据库编程——了解SQL语言,编写基本查询语句
Java从入门到精通:2.3.2数据库编程——了解SQL语言,编写基本查询语句
|
4天前
|
SQL Java 数据库连接
Java从入门到精通:2.3.1数据库编程——学习JDBC技术,掌握Java与数据库的交互
ava从入门到精通:2.3.1数据库编程——学习JDBC技术,掌握Java与数据库的交互
|
4天前
|
IDE Java 开发工具
Java从入门到精通:1.3.1实践编程巩固基础知识
Java从入门到精通:1.3.1实践编程巩固基础知识
|
8天前
|
IDE Java 物联网
《Java 简易速速上手小册》第1章:Java 编程基础(2024 最新版)
《Java 简易速速上手小册》第1章:Java 编程基础(2024 最新版)
13 0