Java学习笔记系列

Java基础知识扫盲(一)——编译、基本类型、final、包装器

2018-07-16  本文已影响0人  SonyaBaby

编译和运行

Microsoft_Program_Maintenance_Utility,外号NMAKE,顾名思义,是用来管理程序的工具。其实说白了,就是一个解释程序。它处理一种叫做makefile的文件(以mak为后缀),解释里面的语句并执行相应的指令。我们编写makefile文件,按照规定的语法描述文件之间的依赖关系,以及与该依赖关系相关联的一系列操作。然后在调用NMAKE时,它会检查所有相关的文件,如果相关文件的time_stamp(文件最后一次被修改的时间,一个32位数)小于依赖文件(dependent_file)的times_tamp,NMAKE就执行依赖关系相关联的操作。

打包文件

applet相关
appletviewer可以快速测试applet


数据类型

8基本类型 4整型,2浮点型,字符类型char,和真值boolean型。

整型
byte 1字节,short 2字节,int 4字节,long 8字节

浮点类型
float 4字节,double 8字节

if(x == Double.NaN) //is never true
if(Double.isNaN(x)) // check whether x is "not a number"
System.out.print(2.0 - 1.1);
0.8999999999999999
// 浮点数值采用二进制系统表示, 而在二进制系统中无法精确地表示分数 1/10
// 就好像十进制无法精确地表示出分数1/3一样
// 不允许有任何舍入误差,应该使用BigDecimal类

char类型

boolean类型


变量

常量
const是Java保留字,但并未使用。必须使用final定义常量。


运算符

public static strictfp void main(String[] args)

数学函数
import static java.1ang.Math.*;

自增与自减

int m = 7;
int n = 7;
int a = 2 * ++m; // now a is 16, m is 8
int b = 2 * n++; // now b is 14, n is 8

不建议在表达式中使用++

位运算符

int m= (n & 0b1000)/0b1000;

返回1,否则返回0。利用&并结合使用适当的2的幂,可以把其他位掩掉只保留其中的某一位。

运算级别

字符串是否相等

空串和null

遍历一个字符串,并且依次査看每一个码点

int[] codePoints = str.codePoints().toArray();

把一个码点数组转换为一个字符串:

String str = new String(codePoints, 0, codePoints.length) ;

输出格式
沿用了C库函数的printf方法,可以进行格式化

double x = 333.333333333334f;
System.out.printf("%8.2f", x);
String name = "Jack";
int age = 12;
System.out.printf("Hello,%s,you'll be %d year-old", name, age);
printf转换符.png
System.out.printf("%,.2f", 10000.0 / 3.0);
3,333.33
标志.png

switch语句

有可能触发多个case分支,编译代码时可以考虑加上-Xlint:fallthrough选项,例如:

javac -Xlint:fallthrough Test.java

如果确实是想用这种“直通式(fallthrough)行为”,可以在外围方法添加注解

@SuppressWarnings("fallthrough")

就不会对这个方法生成警告了


带标签的break

read_data:
while(...){
...
  for(...){
    ....
    break read_data;
  }
}

标签需要紧跟一个":"冒号。


带标签的continue

跳至与标签匹配的循环首部


大数值

BigInteger和BigDecimal
这两个类可以处理包含任意长度数字序列的数值。BigInteger类实现了任意精度的整数运算,BigDecimal类实现了任意精度的浮点数运算。
常用Biglnteger API:

Biglnteger add(Biglnteger other) // +
Biglnteger subtract(Biglnteger other) // -
Biglnteger multipiy(Biginteger other) // * 
Biglnteger divide(Biglnteger other) // /
Biglnteger mod(Biglnteger other) // 取余

int compareTo(Biglnteger other) // 相等为0 小于为-1,大于为1
static Biglnteger valueOf(long x) // 返回一个值为x的Biglnteger 类型数据

常用BigDecimal API:

BigDecimal add(BigDecimal other) // +
BigDecimal subtract(BigDecimal other) // -
BigDecimal multipiy(BigDecimal other) // * 
BigDecimal divide(BigDecimal other, RoundingMode mode) // / 给出舍入方式,RoundingMode.HALF_UP 为四舍五入
Biglnteger mod(Biglnteger other) // 取余
int compareTo(Biglnteger other) // 相等为0 小于为-1,大于为1
static BigDecimal valueOf(long x); // 返回值为x
static BigDecimal valueOf(long x ,int scale);// 返回值为x/(10的scale次方)的实数

数组

声明:int[] a=new int[100];
简化: int[] b={1,2,3,4} → b = new int{1,2,3,4}

一旦创建了数组,就不能改变它的大小。如果需要经常在运行过程中扩展数组的大小,使用数组列表 array list有关数组列表

数组拷贝

int[] b = {1,2,3,4,5};
int[] a = b;
a[2] = 10; // b[2] == 10 为 true

上述代码,两个变量引用同一个数组。

int[] copied = Arrays.copyOf(b, b.length);

上述代码,可以将一个数组的值拷贝到一个新数组中。第二个参数为新数组的长度。如果长度小于原数组的长度,则只拷贝前面的数据元素。

多维数组
即 数组的数组

多维数组.png
balances[i]表示引用第i个子数组,即第i行
balances[i][j]表示引用第i个子数组的第j项
double[][] balances = new double[10] [6]; // Java

等价于(分配了一个包含10个指针的数组)

double** balances = new double*[10] ; // C++
for (i = 0; i < 10; i++)
    balances[i] = new double[6];

多维数组的拷贝

int[][] a = {{1,2,3,4},{1,2,3,4},{1,2,3,4}};
int[][] b = a.clone();
System.out.println(a == b); // false

b[2][2] = 100;
System.out.println(a[2][2]);// 100

上述为浅拷贝,a,b为两个不同的对象,但指向的是同一地址。因为是包含对象的对象。看下jdk描述:

clone官方解释.png

尤其注意红框部分:

clone中文解释.png

即用clone()时,除了基础数据和String类型的不受影响外,其他复杂类型(如集合、对象等)还是会受到影响的,除非你对每个对象里的复杂类型又进行了clone(),但是如果一个对象的层次非常深,那么clone()起来非常复杂,还有可能出现遗漏。

所以需要深克隆:实现Cloneable接口、重写clone方法

值得一提的是,在Java中,任何对象变量的值都是对存储在另外一个地方的一个对象的引用

Date d = harry.getHireDay();

返回可变数据域的引用.png

对d调用更改器方法同样会修改这个雇员对象的私有状态


日期

标准Java类库包含了两个类:

Date birthday = new Date();
Date deadline = birthday;
System.out.println(deadline); //  Wed Jul 11 11:22:22 CST 2018  // 
System.out.println(LocalDate.now()); // 2018-07-11

LocalDate date1999_12_31 = LocalDate.of(1999,12,31); // 1999-12-31
LocalDate localDate = date1999_12_31.plusDays(1000); // 返回一个新对象 不改变对象date1999_12_31
System.out.println(localDate);// 2002-09-26

GregorianCalendar someDay = new GregorianCalendar(1999,12,31);
someDay.add(Calendar.DAY_OF_MONTH, 1000);
System.out.println(someDay.get(Calendar.YEAR) + "-" + someDay.get(Calendar.MONTH) + "-" +
 someDay.get(Calendar.DAY_OF_MONTH));// someDay对象状态会改变

只访问对象而不修改对象的方法称为访问器方法(access method),更改器方法(mutator method)会改变对象状态。

GregorianCalendar.add方法为更改器方法
LocalDate.getYear 和 GregorianCalendar.get 均为访问器方法

打印当前月份日历:

// 打印当月日历
LocalDate date = LocalDate.now();
System.out.println("\n\nMON TUE WED THU FRI SAT SUN");
// 获取月、日
int month = date.getMonthValue();
int day = date.getDayOfMonth();
// 获取本月第一天
date = date.minusDays(day - 1);
// 本月第一天第一天为周几?
int dayOfWeek = date.getDayOfWeek().getValue();
// 打印日历第一行的缩进
for (int i = 1; i < dayOfWeek; i++) {
  System.out.print("    ");
}
// 打印主体
while (date.getMonthValue() == month) {
  System.out.printf("%3d", date.getDayOfMonth());
  if (date.getDayOfMonth() == day)
    System.out.print("*");
  else
    System.out.print(" ");
  date = date.plusDays(1);
  if (date.getDayOfWeek().getValue() == 1)
    System.out.println();
}

输出如下:

MON TUE WED THU FRI SAT SUN
                          1 
  2   3   4   5   6   7   8 
  9  10  11* 12  13  14  15 
 16  17  18  19  20  21  22 
 23  24  25  26  27  28  29 
 30  31 

JavaSE8引入另外一些类来处理日期和时间

// 时间线 Instant 实现接口 Temporal
// 查看算法的运行时间
Instant start = Instant.now();
printCalendar();
Instant end = Instant.now();
Duration timeElapsed = Duration.between(start, end);
long millis = timeElapsed.toMillis();
System.out.println(millis); // 9

// 比较两个算法运行时间
Instant start1 = Instant.now();
printCalendar1();
Instant end1 = Instant.now();
Duration timeElapsed1 = Duration.between(start1, end1);
boolean faster = timeElapsed1.minus(timeElapsed).isNegative();

Instant为时间线上的某个点
Duration.between获取时间差。

日期调整器
TemporalAdjusters类提供了大量常见调整的静态方法。

// 日期调整器
LocalDate nextTuesday = LocalDate.of(2018, 7, 1).with(TemporalAdjusters.nextOrSame(
  DayOfWeek.TUESDAY)); // 从2018.7.1号开始的下一个周二

// 实现自己的日期调整器
TemporalAdjuster NEXT_SUNDAY = w -> {
  LocalDate result = (LocalDate) w; //需要强制转换  TemporalAdjuster任何一个实现类都可
  do {
    result = result.plusDays(1);
  }while (result.getDayOfWeek().getValue()!=7);
  return result;
};
System.out.println(LocalDate.now().with(NEXT_SUNDAY)); // 2018-7-15

//  TemporalAdjusters.ofDateAdjuster(UnaryOperator<LocalDate> dateBasedAdjuster) w默认为LocalDate类型则无须强制转换
TemporalAdjuster NEXT_SATURDAY = TemporalAdjusters.ofDateAdjuster(w -> {
  LocalDate date = w;
  do {
    date = date.plusDays(1);
  } while (date.getDayOfWeek().getValue() != 6);
  return date;
});

与遗留util.Date类的转换

Date birthday = new Date();
System.out.println(birthday);  // Wed Jul 11 15:47:36 CST 2018
birthday.toInstant(); // 2018-07-11T07:47:36.172Z
Date.from(Instant.now()); // Wed Jul 11 15:50:40 CST 2018

隐式参数与显式参数

例如:

public void raiseSalary(double byPercent){
  double raise = salary * byPercent / 100;
  salary += raise;
}

其中byPercent为显式参数,salary为隐式参数(方法的调用目标的参数),我们也可使用关键字this表示隐式参数,这样便于将实例域与局部变量明显区分开。

final实例域

对象包含final实例域,构建对象时则必须初始化这样的域。即确保在每一个构造器执行之后。这个域的值被设置,并在后面的操作中,不能够再对其修改。

final修饰符大多应用于基本(primitive)类型域,或不可变(immutable)类的域(如果类中的每个方法都不会改变其对象,这种类就是不可变的类)

对于可变对象,使用final容易造成混乱:

  private final StringBuiIder evaluations;

构造器构造:

  evaluations = new StringBuilder();

final的修饰只是表示存储在evaluations变量中的对象引用不会指向其他StringBuilder对象。不过这个对象内容可以更改:

public void giveGoldStarO
{
evaluations.append(LocalDate.now() + ":Gold star!\n");
}

而String是个不可变的类。String中最核心的就是value[]值了,这个值不会被类中任何方法修改到。

private final char value[];

静态域与静态方法

将域定义为static,每个类中只有一个这样的域。
尔每一个对象对于所有的实例域都有自己的一份拷贝。
静态域可以理解为类域,属于类所有。

class Employee{
    private static int nextId = 1;
    private int id;
}

每一个雇员都有一个自己的id域,但是Employee所有实例都共享一个nextId域。即,1000个Employee对象,有1000个实例域id,但是只有一个静态域nextId。
即使没有一个雇员对象,静态域nextId也存在。它属于类,不属于任何独立的对象。

public void setld()
{
id = nextld;
nextld++;
}

静态变量使用较少,静态常量使用较多。

public static final double PI = 3.14159265358979323846;

Math.PI就可以取到值。
如果没有static这个字段,每个Math对象都将会有一份PI拷贝。

public final static PrintStream out = null;

System.out也是静态常量。
每个类对象都可以对公有域进行修改,最好不要将域声明为public。然而公有常量(final域)是没问题的。因为被声明为final,out不会被更改为其他打印流。

总结:类中声明static域可以看作是公有量,static+final修饰的为公有常量。

/**
 * Reassigns the "standard" output stream.
 *
 * <p>First, if there is a security manager, its <code>checkPermission</code>
 * method is called with a <code>RuntimePermission("setIO")</code> permission
 *  to see if it's ok to reassign the "standard" output stream.
 *
 * @param out the new standard output stream
 *
 * @throws SecurityException
 *        if a security manager exists and its
 *        <code>checkPermission</code> method doesn't allow
 *        reassigning of the standard output stream.
 *
 * @see SecurityManager#checkPermission
 * @see java.lang.RuntimePermission
 *
 * @since   JDK1.1
 */
public static void setOut(PrintStream out) {
    checkIO();
    setOut0(out);
}

private static native void setOut0(PrintStream out);

setOut方法可以将System.out设置为不同的流,即修改final变量的值。
因为setOut0方法是native,本地方法不是用Java语言实现的,可以绕过Java的存取控制机制。

静态方法
静态方法是一种不能向对象实施操作的方法。可以认为静态方法是没有隐式的参数,没有this参数的方法。

例如Emloyee类中静态方法getNextld,

public static int getNextld()
{
return nextld; // returns static field
}

这里不能访问实例域Id,因为静态方法不可以操作对象。但是可以访问类域。

建议用类名访问静态方法,不建议使用对象的引用调用静态方法,防止混淆。

下面两种情况才使用静态方法

静态方法main方法不对任何对象进行操作。启动程序时,还没有任何一个对象,静态的main方法将执行并创建程序所需要的对象。

方法参数

1.对于基本类型参数:

public static void tripieValue(double x){ // 并不能修改x的值
    x = 3 * x;
 }

double percent = 10;
tripieValue(percent); // percent = 10;
对基本类型参数的修改没有保存.png

方法不可能修改一个基本数据类型的参数

2.对于对象参数:

public static void tripleSalary(Employee x){ // works
    x.raiseSalary(200) ;
}

harry = new Employee(. . .) ;
tripleSalary(harry) ; // harry.salary+200
对对象参数的修改保留了下来.png

3.Java对对象采用的就是引用调用吗?答案是No
看个例子,swap为交换x,y

public void swap(Employee x, Employee y){
      Employee tmp = x;
      x = y;
      y = tmp;
 }
...
Employee a = new Employee("Alice", . . .);
Employee b = new Employee("Bob", . . .);
swap(a, b);
交换结果没有保存下来.png

Java 程序设计语言对对象采用的不是引用调用,实际上,对象引用是按值传递的

总结:Java中方法参数的使用情况:

重载(overloading)

多个方法具有相同的名字、不同的参数即重载。签名(方法名+参数类型)。

finalize方法

可以为任何一个类添加 finalize 方法,它将在垃圾回收器清楚对象之前调用。
但在实际应用中,不要依赖于使用finalize 方法回收任何短缺的资源,因为很难知道这个方法什么时候才能调用。

import java.util.;
import static java.lang.Math.
;//可以直接使用sqrt(pow(x, 2) + pow(y, 2));

文档注释

JDK有个很有用的工具: javadoc,可以由源文件生成一个HTML文档

为以下几部分编写注释:

以/** ... */的格式,自由格式文本第一局应该是一个概要性的句子。javadoc自动将这些句子抽取出来形成概要页。
<em>用于强调
<strong>着重强调
<img>图片
不要使用<hl>或<hr> 会与文档格式产生冲突。
{@code ...}等宽代码
可以参考源码中的标签

类注释
必须放在import语句之后,类定义之前。需要注意的是@see 分隔类名与方法名用的是#,多个see标签要放在一起

/**
 * The {@code String} class represents   character strings. All
 * string literals in Java programs, such as {@code "abc"}, are
 * implemented as instances of this class.
 * @author  Lee Boynton
 * @see     java.lang.Object#toString()
 */

方法注释
@param 变量描述
@return 描述
@throw 类描述

/**
 * Allocates a new {@code String} that contains characters from a subarray
 * of the character array argument. The {@code offset} argument is the
 * index of the first character of the subarray and the {@code count}
 * argument specifies the length of the subarray. The contents of the
 * subarray are copied; subsequent modification of the character array does
 * not affect the newly created string.
 *
 * @param  value
 *         Array that is the source of characters
 *
 * @param  offset
 *         The initial offset
 *
 * @param  count
 *         The length
 *
 * @throws  IndexOutOfBoundsException
 *          If the {@code offset} and {@code count} arguments index
 *          characters outside the bounds of the {@code value} array
 */
public String(char value[], int offset, int count)

域注释
只需要对公有域(通常指的是静态常量)建立文档

/**
 * The {@code double} value that is closer than any other to
 * <i>pi</i>, the ratio of the circumference of a circle to its
 * diameter.
 */
public static final double PI = 3.14159265358979323846;

包与概述注释
想要产生包注释,需要在每一个包目录中添加一个单独的文件。两种方式

为所有的源文件提供一个概述性的注释。这个注释将被放置在一个名为 overview.html 的文件中,这个文件位于包含所有源文件的父目录中。标记 <body>... </body> 之间的所有文本将被抽取出来。当用户从导航栏中选择“Overview” 时,就会显示出这些注释内容。

注释抽取

可参考javadoc相关文档


继承

例如Manager和Employee,Manager除了享有Employee待遇和属性之外,还会有其他权利等。根本上讲,Manager is a Employee,is-a 关系是继承的一个明显特征

阻止继承:final类和方法
不允许扩展的类被称为final类,被final修饰的方法不能被子类覆盖。

域也可以被声明为 final。对于 final 域来说,构造对象之后就不允许改变它们的值了。 不过, 如果将一个类声明为 final, 只有其中的方法自动地成为 final,而不包括域。

将方法或类声明为 final 主要目的是: 确保它们不会在子类中改变语义。

多态

一个对象变量可以指示多种实际类型的现象被称为多态,在运行时能够自动选择调用哪个方法的现象称为动态绑定。如下e,即可引用Employee对象,又可引用Manager对象:

Manager boss - new Manager("Carl Cracker", 80000,1987, 12 , 15) ;
boss.setBonus(5000) ;

Employee[] staff = new Employee[3];
staff[0] = boss;
staff[1] = new Employee("Harry Hacker",  50000, 1989, 10, 1);
staff[2] = new Employee("Tony Tester", 40000, 1990, 3, 15);

for (Employee e : staff)
   System.out.println(e.getName0 + " " + e.getSalary());

对象变量是多态的,一个Employee变量既可以引用Employee对象也可引用Employee类的任何一个子类对象。

养成这样一个良好的程序设计习惯: 在进行类型转换之前, 先查看一下是否能够成功地转换。instanceof

if (staff[1] instanceof Manager){
  boss = (Manager) staff[1]:
}

在一般情况下,应该尽量少用类型转换和 instanceof 运算符。并检查一下超类的设计是否合理。

抽象类

  1. 子类中只定义部分抽象类方法或不定义抽象类方法,子类必须被标记为抽象类。
  2. 子类定义了全部的抽象方法,子类就不是抽象的了。

Object

equals方法
关于重写equals,需要注意遵循自反、对称、传递、一致性,equals(null)=false;

完美equals建议:

public boolean equals(Object otherObject){
  if (this = otherObject) return true;
  if (otherObject = null) return false;
  if (getClass() != otherObject.getClass()) return false; // equals 的语义在每个子类中有所改变
  if (!(otherObject instanceof ClassName)) return false; //所有的子类都拥有统一的语义 
  ClassName other = (ClassName) otherObject;
  return field1 == other.field1 && Objects.equa1s(field2, other.field2) && ... ;
}

String中的equals

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

hashCode方法
如果重新定义 equals方法, 就必须重新定义hashCode 方法,以便用户可以将对象插人到散列表中
String中的hashCode()

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        ————for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

比较好的做好是组合多个散列值,调用 Objects.hash 并提供多个参数:

public int hashCode(){
  return Objects,hash(name, salary, hireDay);
}

如果存在数组类型的域, 那么可以使用静态的 Arrays.hashCode 方法计算一个散列码,这个散列码由数组元素的散列码组成

toString方法

强烈建议为自定义的每一个类增加 toString 方法。

Employee:

public boolean equals(Object otherObject){
  // a quick test to see if the objects are identical
  if (this == otherObject) return true;
  // must return false if the explicit parameter is null
  if (otherObject == null) return false;
  // if the classes don't match, they can 't be equal
  if (getClass() != otherObject.getClass() ) return false;
  // now we know otherObject is a non-null Employee
  Employee other = (Employee) otherObject;
  // test whether the fields have identical values
  return Objects.equals(name, other.name) && salary == other.salary&& Objects.equals(hireDay, other.hireDay) ;

public String toString(){
    return getClass().getName() + "[name:" + name +",salary:" + salary + ",hireDay=" + hireDay + "]";
}

Manager:

public boolean equals(Object otherObject){
  if (!super.equals(otherObject)) return false;
  Manager other = (Manager) otherObject;
  // super.equals checked that this and other belong to the same class
  return bonus == other.bonus;
}

public String toString(){
  return super.toString() + "[bonus=" + bonus + "]";
}

泛型数组列表

ArrayList<Employee> staff = new ArrayList<Employee>();

如果调用 add 且内部数组已经满了,数组列表就将自动地创建一个更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中。

如果已经清楚或能够估计出数组可能存储的元素数量, 就可以在填充数组之前调用
ensureCapacity方法:

staff.ensureCapacity(100);
ArrayList<Employee> staff = new ArrayList<>(100) ;

注意区分 数组列表和数组 分配空间的区别:

确认数组列表的大小不再发生变化,可以调用trimToSize方法。这个方法将存储区域的大小调整为当前元素数量所需要的存储空间数目。垃圾回收器将回收多余的存储空间。

list.add(0,"hello"); //添加新元素
list.set(0,"world"); //替换已有元素内容
list.add(n,e); // 位于n之后所有元素向后移动一个位置
list.remove(n); // 移除n下标元素,且之后的元素都前移一个位置

转数组:

ArrayList<X> list = new ArrayList();
while (. . .){
  x = . .
  list.add(x);
}

X[] a = new X[list.size()];
list.toArray(a);

对象包装器和自动装箱

包装器(wrapper):Integer类对应基本类型int
Integer、Long、Float、Double、Short、Byte(前6个类派生于公共的超类Number)、Character 、 Void 和 Boolean

public final class Integer extends Number implements Comparable<Integer> 

自动装箱(autoboxing)

ArrayList<Integer> list = new ArrayList<>();
list.add(3);
// 自动变成
list.add(Integer.value0f(3));

list.add(3)的调用即为自动装箱

自动拆箱

int n = list.get(i);

即编译器会翻译为int n = list.get(i).intValue();

Integer n = 3;
n++;

编译器自动插入一条对象拆箱指令,然后自增,然后再将结果装箱

Integer a = 1000;
Integer b = 1000;
System.out.println(a == b); // false

Integer c = 100;
Integer d = 100;
System.out.println(c == d); // true

== 检测的是对象是否指向同一个存储区域。自动装箱将经常出现的值包装到同一个对象中,即:
自动装箱规范要求 boolean、byte、char 127,介于-128 ~ 127之间的 short 和 int 被包装到固定的对象中。

所以经过自动装箱,a,b所指向的100是同一个对象,而c,d所指向的1000是两个不同的对象。

为了避免这种不确定,两个包装器对象比较时,调用equals方法

在一个表达式中混用Integer和Double,Integer会拆箱,提升为double,再装箱为Double

Integer n = 1;
Double x = 2.0;
System.out.println(true ? n : x); // 1.0

敲黑板~ 装箱和拆箱是编译器认可的, 而不是虚拟机。编译器在生成类的字节码时, 会插入必要的方法调用。虚拟机只是执行这些字节码。

有些人认为包装器类可以用来实现修改数值参数的方法, 然而这是错误的。

public static void triple(int x) {// won't work
  x = 3 * x; // modifies local variable
}

public static void triple(Integer x) // won't work

因为Java是值传递:int基本类型参数不会变、Integer对象不可变:包含在包装器中的内容不会变。

如果需要一个修改数值参数值的方法,需要使用在 org.omg.CORBA包中定义的持有者(holder)类型,包含IntHolder、BooleanHolder等。每个持有者类型都包含一个共有域值,通过它可以访问存储在其中的值。

public final class IntHolder implements Streamable {

/**
 * The <code>int</code> value held by this <code>IntHolder</code>
 * object in its <code>value</code> field.
 */
public int value;

如下:

{
...
IntHolder y = new IntHolder();
y.value = 3;
changeInt(y);
System.out.println(y.value); // 9
...
public static void changeInt(IntHolder x){
  x.value = x.value * 3;
}}

参数数量可变的方法

eg printf:

public PrintStream printf(String format, Object ... args) {
    return format(format, args);
}

这里的 ... 省略号 是Java代码的一部分,表明这个位置可以接收任意数量的对象。
即fmt的第i个格式说明符与arg[i]的值匹配

public static double max(double ... values){
  double largest = Double.NEGATIVE_INFINITY;
  for (double val : values)
    if (val > largest) largest = val;
  return largest;
}
...
// 调用
double[] values = {1.2, 123123.12, -0.999, 12312};
System.out.println(max(values)); // 123123.12

System.out.println(max(1.2, 123123.12, -0.999, 12312)); // 123123.12

枚举类

public enum Size {
  SMALL("S"), MEDIUM("M"), LARGR("L"), EXTRA_LARGE("XL");
  
  private String abbr; // 缩写
  
  private Size(String abbr) {
    this.abbr = abbr;
  }
  
  public String getAbbr() {
    return abbr;
  }
  
  public static void main(String[] args) {
    SMALL.getAbbr(); // S
    SMALL.toString(); // SMALL

    Size s = Enum.valueOf(Size.class, "SMALL");// toString的逆方法 valueOf

    Size[] sizes = Size.values();
    System.out.println(sizes);

    Size.EXTRA_LARGE.ordinal(); // 声明中枚举常量的位置 3
  }
}
sizes数组.png

反射(reflective)5.7

能够分析类能力的程序称为反射。反射机制可以用来:

继承的设计技巧

上一篇 下一篇

猜你喜欢

热点阅读