Java和C++在细节上的差异(转)

Java的基本程序结构、关键字、操作符都和C/C++非常相似,以下为主要的几点区别:

一、基本程序设计结构:

       Java的基本程序结构、关键字、操作符都和C/C++非常相似,以下为主要的几点区别:

  1. Java的原始数值型数据类型中不包含无符号类型,如c中的unsigned int。

  2. 在进行移位运算时,当向左边移动时,如1 << 35, 对于int类型,由于其占有4个bytes(32bits), 因此在Java中,大于32的移位将对32取模,即1 << 35的结果等于1 << 3,以此类推,long将会对64取模。对于int类型而言,如果确实需要获取32位以上的移位,需要将返回值的类型提升到long即可。

  3. 在c语言中,可以通过判断一个数值是否为0来代替布尔中的false,其他的数值均表示为true。该写法可应用于if和while等子句中,如 if (i) {....}, 当i的值不为0时,该条件可为真,或者是在判断指针对象是否为NULL时,也可作为if和while的条件,因此很容易出现将if (i == 9) {...}写成if (i = 9) {...}的低级错误,在Java中禁止了该类转换,既if和while中条件必须是布尔类型,如果在Java中写成 if (i = 9) {...}将会直接导致编译错误,从而更好的避免了该类问题的发生。

  4. Java中去除了goto字句,但是仍然视为保留字。然而Java中的break字句,新增了带标签的break [label],可以使break语句直接跳出指定的循环,而不仅仅是缺省的最内层循环。注:标签必须放在希望跳出的最外层循环之前,并且紧跟一个冒号。如:

?
public void test() {
        int n;
         read_data:
         while (...) {
             for (...) {
                 System.out.print("Enter a number >= 0: ");
                 n = in.nextInt(); 8                 if (n < 0)
                     break read_data;
            }
         }
         //下面的代码将会被立即执行,当break跳出最外层的循环之后。
         if (n < 0) {
             ...
         } else {
             ...
         }
     }

  

5. Java中支持0长度的数组定义,如int et = new int[0]; 在C/C++中,该写法将会导致编译错误。

6. 多维数组的两种常用访问方式。

?
public static void main(String[] args) { 2             int[][] magicSquare = 
            {
                {16,3,2,13},
                {5,10,11,8},
                {9,6,7,12},
                {4,15,14,1}
            };
            // 通过普通的for循环访问
            for (int i = 0; i < magicSquare.length; ++i) {
                for (int j = 0; j < magicSquare[i].length; ++j) {
                    System.out.printf("%s ",magicSquare[i][j]);
                }
                 System.out.println();
             }
             // 通过普通的for each循环访问
             for (int[] row : magicSquare) {
                 for (int col : row) {
                     System.out.printf("%s ",col);
                 }
                System.out.println();
            }
         }
         /*两次输出结果均为:       
           16 3 2 13
           5 10 11 8
           9 6 7 12
           4 15 14 1 */

7. Java中的不规则二维数组。

?
public void foo() {
     int[][] odds = new int[NMAX+1][];
     for (int n = 0; n <= NMAX; ++n)
         odds[n] = new int[n + 1];
       
     for (int n = 0; n < odds.length; ++n) {
         for (int k = 0; k < odds[n].length; ++k)
             odds[n][k] = n * k;
    }
 }

C/C++中对应于Java的不规则二维数组的表示方式。

?
void foo() {
            int** odds = new int*[10];
            for (int n = 0; n < 10; ++n) {
               if (n == 0)
                     odds[n] = new int;
                 else
                    odds[n] = new int[n + 1];
             }
                 
             for (int n = 0; n < 10; ++n) {
                 for (int k = 0; k < n + 1; ++k)
                     odds[n][k] = n * k;
             }
             //注:C/C++代码部分需要自行释放分配的内存。           
             for (int n = 0; n < 10; ++n) {
                 if (n == 0)
                     delete odds[n];
                 else
                    delete [] odds[n];
             }
             delete [] odds;
         }

二、对象与类:

  1. Java对象实例的存储方式:

  所有的Java对象实例都是通过new的方式创建的,如Employee employee = new Employee()。而此时创建的employee对象实例,实际是指向Employee对象的一个实例的引用,主要体现为实例之间基于等号的赋值,如:employee = employee2; 赋值后两个变量将指向同一个Employee对象实例。Java处理对象变量的方式和C++中的引用比较类似,但是还是存在一定的差异,首先C++不存在空引用,既引用变量定义时也必须被同时声明其所引用的对象实例,再者就是引用一旦定义时初始化后就不能再被重新赋值了。因此这里可以将Java的对象变量看做C++中的对象指针,如:BirthdayDate d; /*Java*/ 等同于 BirthdayDate* d; /*C++*/。

  与Java对象实例声明的方式相同,C++中的对象指针也是通过new的方式进行初始化的,如BirthdayDate* d = new BirthdayDate. 同样可以将C++中的对象指针赋值为NULL,也可以将其重新赋值指向另外一个对象实例。与Java相同,通过new操作符创建的对象实例是存储在堆中的,不同的是,Java的对象在创建后,无需开发人员在去关注该对象实例需要合适被释放,所有的操作均有Java虚拟机中提供的垃圾回收机制自动完成。而C++中的该类对象,则需要开发人员通过调用delete操作符来自行完成释放,如果忘记释放将会产生内存泄露。在C++中,不仅可以将对象存储在堆上,同样也可以定义并存储的栈上,如BrithdayDate d; 该对象实例不需要手工释放,在栈退出时将自动释放该对象的存储空间,同时也会调用该对象的析构函数。

  2. Java对象方法的显式参数和隐式参数:

?
public class Employee {
    public void raiseSalary(double byPercent) {
        double raise = salary + byPercent / 100;
        salary += raise;
    }
    private double salary;
}

  raiseSalary是Employee类的一个成员方法,该方法是由两个参数构成,一个是显式参数byPercent,另一个则是隐式参数this,既raiseSalary方法是实现体可以改为:

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

  

这里的隐式参数this表示当前调用raiseSalary方法的对象实例的自身,该机制和C++基本相同。

  注:静态方法中不存在该特征。

  3. Java对象中定义的final实例域,如:public class Employee { ... private final String name; }, 该类型的field必须在对象构造函数中进行初始化,之后该变量将不能再被重新赋值。和final字段相似,C++对象中的const成员变量也必须在对象构造函数的初始化列表中完成赋值任务,在之后的使用中该字段将不会再被修改,否则会产生编译错误。对于Java的final域而言,以便应用于基本数据类型,如int,double等,或者不可变类型,如String。对于可变类型而言,final修饰符可能会造成某些预料之外的混乱,如 private final Date hiredate; 当该field作为某个get方法的返回值返回给调用者之后,final的修饰作用只能保证返回后的date对象不能再被重新赋值并指向新的对象实例引用,但是可以通过直接修改返回值对象的自身数据来破坏对象的封装性,从而可能造成数据的非法性,或者状态的不一致性。

  4. 函数参数传递的方式:传值和传引用。

  在Java中调用函数是,参数都是通过传值的方式传递到函数内部,然而根据参数类型的不同,其表现仍然存在一定的差异。主要总结为以下3点:

  被调用方法不能修改一个基本数据类型的参数,如:int,double,boolean等,见如下代码:

?
private static void tripleValue(double x) {
         x *= 3;
         System.out.println("End of method: x = " + x);
     }
       
     public static void testTripleValue() {
         System.out.println("Test tripleValue");
         double percent = 10;
        System.out.println("Before: percent = " + percent);
        tripleValue(percent);
         System.out.println("After: percent = " + percent);
     }
     /*    结果如下:
         Test tripleValue
         Before: percent = 10.0
         End of method: x = 30.0
         After: percent = 10.0  */

  被调用方法可以改变一个对象参数的状态,见如下代码:

?
private static void tripleSalary(Employee x) {
         x.raiseSalary(200);
         System.out.println("End of method: salary = " + x.getSalary());
     }
       
     public static void testTripleSalary() {
         System.out.println("Test tripleSalary");
         Employee harry = new Employee("Harry",50000);
         System.out.println("Before: salary = " + harry.getSalary());
         tripleSalary(harry);
         System.out.println("After: salary = " + harry.getSalary());
     }
     /*    结果如下:
         Test tripleSalary
         Before: salary = 50000.0
         End of method: x = 150000.0
         After: salary = 150000.0  */

  被调用方法不能实现让对象参数引用一个新的对象,见如下代码:

?
private static void swap(Employee a,Employee b) {
        Employee temp = x;
        x = y;
        y = temp;
        System.out.println("End of method: x = " + x.getName());
        System.out.println("End of method: y = " + y.getName());
    
    public static void testSwap() {
        System.out.println("Test Swap");
        Employee a = new Employee("Alice",70000);
        Employee b = new Employee("Bob",60000);
        System.out.println("Before: a = " + a.getName());
        System.out.println("Before: b = " + b.getName());
        swap(a,b);
        System.out.println("After: a = " + a.getName());
        System.out.println("After: b = " + b.getName());
    }
    /*    结果如下:
        Test swap
        Before: a = Alice
        Before: b = Bob
        End of method: x = Bob
        End of method: y = Alice
        After: a = Alice
        After: b = Bob     */

  C++有值调用和引用调用,引用参数标有&符号。如:void tripleValue(double& x)或void swap(Employee& x,Employee& y)方法实现修改他们引用参数的目的,既该方法执行完成后,调用函数的参数变量的值将发生改变。

5. 对象的构造和构造函数:

  在Java中如果一个class没有定义任何构造函数,Java编译器将自动生成一个缺省的构造函数,没有任何参数,其行为只是按照Java默认的方式初始化该类的所有域变量,如数值型为0,布尔为false,对象则为null。但是如果该class定义了自己的构造函数,那么缺省构造函数将不会被自动生成,再试图调用自动生成的缺省构造函数将会导致编译错误。该行为和C++完全一致。但是Java提供了另外一种域变量初始化方式,如下:

?
public class Employee {
        ...
        private String name = "";    //直接赋值
        private int id = assignId();//通过调用域方法完成初始化。
    }

  

在C++中不能直接在类的定义中以任何形式直接初始化成员变量。但是C++提供了在构造函数中以初始化列表的方式完成成员变量对象的初始化,特别是const成员,必须在这里赋值。

  通过一个构造器调用另一个构造器从而完成域变量的初始化和部分代码复用。通过this关键字(或称隐式参数)作为函数名,然后传入参数调用你期望的另一个构造函数,注:this被调用之前不能执行任何其他的code。

?
public Employee(double s) {
        //calls Employee(String,double)
        this("Employee #" + nextId,s);
        ++nextId;
    }

  在Java定义的子类中,如果子类的构造函数不是调用父类的缺省构造函数,则需要在子类构造函数的第一行代码中指定欲调用的父类构造函数,该调用需要通过super关键字来完成。见如下代码:

?
public class MyFirst {
         public static void main(String[] args) {
             BaseClass bc1 = new SonClass();
            BaseClass bc2 = new SonClass(5);
         }
     }
       
     class BaseClass {
        public BaseClass() {
             System.out.println("This is BaseClass");
         }
          
         public BaseClass(int i) {
             System.out.println("This is BaseClass with i.");
         }
     }
     
     class SonClass extends BaseClass {
        public SonClass() {
             System.out.println("This is SonClass");
         }
          
         public SonClass(int i) {
             super(5);
             System.out.println("This is SonClass with i");
         }
     }
     /*    结果如下:
         This is BaseClass
         This is SonClass
         This is BaseClass with i.
         This is SonClass with i */

  在C++中也可以完成该种类型的指定,但是必须在子类构造函数的初始化列表中完成对父类指定构造函数的调用。

?
class BaseClass {
    public:
        BaseClass() {
            printf("This is BaseClass\n");
        }
      
        BaseClass(int i) {
            printf("This is BaseClass with i\n");
        }
    };
     
    class SonClass : public BaseClass {
    public:
        SonClass() {
            printf("This is SonClass\n");
        }
     
        SonClass(int i) : BaseClass(i) {
            printf("This is SonClass with i\n");
        }
    };
     
    int main()
    {
        BaseClass* bc1 = new SonClass;
       BaseClass* bc2 = new SonClass(5);
        delete bc1;
        delete bc2;
        return 0;
    }
    /*    结果如下:
        This is BaseClass
        This is SonClass
        This is BaseClass with i.
        This is SonClass with i */

  在Java的域变量初始化方法中存在初始化块的方式,既除声明即初始化、构造函数初始化之外的第三种域变量初始化方式。在一个类的声明中可以存在多个代码块,只要构造类的对象,这些块就会被执行,然后再运行类的构造函数。静态域变量可以在静态初始化块中完成初始化的工作,但是该初始化块只是在类第一次加载时被执行一次,之后都将不再被执行。见如下代码:

?
class Employee {
      public Employee(String n,double s) {
           name = n;
           salary = s;
       }
         
       ...
         
       private static int nextId;
       private int id;
       private String name;
       private double salary;
        
       //object initialization block.
       {
           id = nextId;
           nextId++;
       }
        
       //static initialization block.
       static
       {
           Random generator = new Random();
           nextId = generator.nextInt();
       }
   }

  

6. C++的对象析构和Java对象的finalize方法:

  C++是有显式的析构方法,其中放置一些当对象不再使用时需要执行的清理代码。在析构函数中,最常见的操作时回收分配给对象的存储空间,系统资源等。有Java有自动的垃圾回收器,不需要人工回收内存,所以Java并不支持析构函数。如果打算在Java的代码中完成类似的工作,可以通过为该类添加finalize方法,该方法将会在垃圾收集器清除对象之前调用,在实际应用中,不要依赖于使用finalize方法回收任何短缺的资源,这是因为很难知道这个方法什么时候才能调用。如果某个资源确实需要在使用完毕后立刻关闭,那么就需要由人工来管理。可以应用一个类似dispose或close的方法完成相应的清理操作。特别需要说明,如果一个类使用了这样的方法,当对象不再被使用时一定要调用它。

  7. Java的包 vs C++的名字空间

  他们具有极为相同的只能,即防止名字污染。当一个应用程序中存在多个第三方组件,那么不同组件中命名了相同名称的类将是极为可能发生的,如Java中的Date类,在java.util和java.sql中均存在该名称的类的声明。为了有效的防止名字污染,C++中采用了namespace和using namespace的指令来明确定义某个类具体所位于的具体位置,Java中则采用了package和import语句。

  Java在Java SE5.0 开始,import语句不仅可以导入类,还增加了导入静态方法和静态域的功能。如import static java.lang.System.*。在完成该静态导入之后,就可以在剩下的代码中直接使用System类的静态方法和静态域了,如out.println();exit(0)。该技巧主要用于带有较长名称的常量,如if (d.get(DAY_OF_WEEK) == MONDAY) ...,看起来比if (d.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY) ...要容易的多。

 

       三、继承:

  1. Java和C++在对象继承方面的主要差异:

  对象的继承性是所有面向对象语言都支持的面向对象特性之一,Java和C++作为两个重要的面向对象开发语言在此方面有着较多的相似性,但是在有些概念的表示方式上还是存在着一定的差异,先列举如下:

  1) 对象继承的关键字,Java中采用extents关键字,如class DeriveClass extends BaseClass, 在C++中则使用(:)冒号表示类之间的继承,如class DeriveClass : public BaseClass。

  2) Java的继承方式中不存在public,protected和private,其表现行为和C++中的public继承完全一致。

  3) 在有些情况下,子类中的方法需要显式的调用超类中的方法实现,特别是当子类中也存在同样方法签名的实现时,如果没有明确的指出需要调用超类的方法,Java的编译器会将子类当前的方法列为本次调用的候选方法,见如下代码:

?
class DeriveClass extends BaseClass {
         public double getSalary() {
             double baseSalary = getSalary();
             return baseSalary + bonus;
         }
     }

  以上代码中的getSalary()方法将会递归的调用其自身,而开发者的实际用意是调用超类中的getSalary方法,由于超类和子类中具有相同签名的该方法,因此编译器在此时选择了子类中的getSalary。其修改方式如下:

?
class DeriveClass extends BaseClass {
       public double getSalary() {
           double baseSalary = super.getSalary();
           return baseSalary + bonus;
       }
   }

  加上关键字super明确的指出要调用超类中的getSalary方法。在C++中的实现方式为BaseClass::getSalary(),既在方法签名的前面加上父类的名字和两个连在一起的冒号(::)。

?
class DeriveClass : public BaseClass {
   public:
       double getSalary() {
           double baseSalary = BaseClass::getSalary();
           return baseSalary + bonus;
       }
   }

  

4) Java中所有未声明为final的方法都视为可以继承的虚方法。在C++中,尽管没有此类限制,但是在实际的应用中还是存在一些潜在的技巧以达到此效果。对于C++类中声明的公有成员方法,如果该方法未声明为virtual,既虚函数,则暗示该类的子类实现者不要在子类中覆盖(override)该方法。

  5) Java中不支持多重继承,不仅有效的避免了C++因多重继承而带来的一些负面影响,与此同时,在Java中可以通过继承(extends)单个父类和实现(implements)多个接口的方式更好表达该类设计意愿。

  6) Java中如果子类和超类同时包含具有相同签名的公有域方法,那么在子类中将覆盖超类中的域方法。这其中的方法签名只是包括方法名和参数列表,既参数的个数和类型,函数的返回值不包含在方法签名中,但是在Java中针对该种方法覆盖的返回值还是存在一定的限制,既子类中的返回值的类型,或者与超类中该方法的返回值类型相同,或者为其返回类型的子类。C++中没有此类返回值类型的限制。但是Java的此类限制也会带来一些潜在的迷惑和危险,见如下代码:

?
class Employee {
         public Employee[] getBuddies() { ... }
     }
       
     class Manager extends Employee {
         public Manager[] getBuddies() { ... }
     }
       
     public static void main(String[] args) {
         Employee[] m = new Manager().getBuddies();
         //在Java中子类的数组在复制给超类的数组时不需要显式的转换,就像
         //子类的实例赋值给超类的实例一样,也不需要任何显式的转换。
         //赋值之后e和m指向相同的内存地址,同样e[0]和m[0]也指向相同的实例。
         Employee[] e = m;
         //本次赋值合法也不会引发任何异常,但是会导致一个潜在的问题,既
         //m[0]的对象已经被悄悄的改变了,指向了Employee的另外一个子类。
         e[0] = new OtherEmployee();
         //此时再调用m[0]中Manager定义的域方法时将会引发Java的运行时异常。
         m[0].setBonus(1000);
     }

  

7) Java中的final类,如果某个自定义类型被加入final关键字,则表示该类将不能被继承,否则会直接产生编译错误。在C++中没有特殊的关键字类完成此类限制,然而在实际的应用中也同样存在一些潜在的技巧协助开发者来进行此类限制的甄别。如将父类中的析构函数不设置为虚函数,此方法则间接的暗示子类的实现者要留意,如果仍然继承该父类,那么在实现多态时,如BaseClass* c = new DeriveClass,如果之后需要释放c变量的内存资源时 delete c, 此时由于父类中的析构函数并不是虚函数,因此此次调用将只会执行父类的析构函数,而不会调用子类的析构函数,最终导致类分割所带来的一些潜在错误或资源泄漏。

  8) 内联方法,在C++中有特殊的关键字inline用于帮助编译器来推断是否需要将该方法编译成内联方法,以提高运行时的效率。在Java中没有此类关键字,而是通过编译器的一连串推演,最终决定该域方法是否可以编译成内联方法,主要候选方法为简短、被频繁调用且没有真正被子类覆盖的域方法。

  9) 超类到子类的强制类型转换。在Java中可以通过直接强转的方式来转换,如Manager m = (Manager)e。如果装换失败将会引发运行时异常ClassCastException,因此很多情况下为了避免此类异常的发生,需要在强转之前先进行判断,如if (e instanceof Manager) { ... }, 如果条件为真,装换将顺利完成。在C++中也可以采用这样的直接强转方法,但是即使类型不匹配程序也不会在强转是引发任何异常,而是在后面针对该变量的使用时才会导致错误的发生。在C++中存在dynamic_cast关键字,如dynamic_cast和dynamic_cast,前者为基于指针的转换,如果转换失败返回变量为NULL,而后者则会引发异常。

  10) 抽象类:在Java中如果class被定义为abstract class,该类将不能被实例化,如果子类未能完全实现超类中所有的抽象方法,那么子类也将会被视为抽象类。C++中没有特殊的关键字来表示抽象类,而且通过将类中的一个或多个方法定义为纯虚方法来间接实现的,见如下C++代码,其中的first和second均为纯虚方法,既在方法的尾部添加" = 0 "。

?
class AbstractClass {
    public:
        virtual void first() = 0;
        virtual void second() = 0;
        virtual void third();
    }

  11) protected关键字在Java和C++中针对域方法和域字段的访问方式存在着不同的限制级别,相同之处是protected的方法和字段都可以被子类直接访问,不同之处是Java中相同包中的类也可以直接他们。C++自身并不存在包的概念,然而即便是相同名字空间内的对象也不能直接访问。

 2. Object:

  Java是单根结构的框架,所有的对象都是Object的子类,即使在对象声明时没有进行直接的指定,Java的编译器将会自行搞定这些。C++中没有适当的类作为所有对象的根类,然而在有些类库中可以自行定义,如MFC的CObject等。Java的Object中有3个非常重要的方法equals、hashCode和toString。如果子类中重载了他们中的任意一个方法,同时也建议重载另外两个域方法。

  1) equals: 主要用于判定两个对象是否相等。类的实现者可以根据自己的真实逻辑来重新实现该方法,通用实现规则见下例:

?
public class Employee {
        //1. 显式参数命名为otherObject,稍后需要将它转换成另一个叫做other的变量。
        public boolean equals(Object otherObject) {
            //2. 检测this与otherObject是否引用同一个对象(一种优化)
            if (this == otherObject)
                return true;
            //3. 检测otherObject是否为null,如果null,则返回false。
            if (otherObject == null)
                return false;
            //4. 比较this与otherObject是否属于同一个类。
            //如果子类中的equals语义各不相同,使用下面的getClass方式,精确定义类类型。
            if (getClass() != otherObject.getClass())
                return false;
            //如果子类中的equal语义和超类完全相同,可以使用instanceof检测即可。
            //5. 将otherObject转换为相应的类类型变量
            Employee other = (Employee)otherObject;
            //6. 现在开始对所有需要比较的域进行比较了。其中使用==比较基本类型,         //使用equals比较对象类型。
            return name.equals(other.name) && salary == other.salary;
        }
    }

注:数组元素的比较可以调用Arrays.equals方法检测。如果子类中重新定义了equals方法,就要在其中包含调用super.equals(other).

  Java在语言规范中给出了自定义equals方法需要遵守的规则:

  自反性: 对于任何非空引用x,x.equals(x)应该返回true。

  对称性: 对于任何引用x和y,当且仅当y.equals(x)返回true,x.equals(y)也应该返回true。

  传递性: 对于任何引用x,y和z,如果x.equals(y)返回true,y.equals(z)返回true,x.equals(z)也应该返回true。

  一致性: 如果x和y引用的对象没有发生变化,反复调用x.equals(y)应该返回同样的结果。

  对于任意非空引用x,x.equals(null)应该返回false。

  2) hashCode: 导出一个经过哈希计算后的整型值,Java对hashCode的缺省实现是返回当前对象的存储地址。一下列出String的hashCode实现方式:

?
public int hashCode() {
         int hash = 0;
         for (int i = 0; i < length(); ++i)
             hash = 31 * hash + charAt(i);
         return hash;
     }

  注:自定义类型的equals和hashCode定义必须一致,如果x.equals(y)返回true,那么x.hashCode()就必须与y.hashCode()具有相同的值。如果打算实现自定义的hashCode方法,推荐使用在对象构造初始化后就不会再改变的域字段作为hashCode的计算因子。否则一旦使用可变域资源作为hashCode计算因子的一部分,将会导致一些隐藏的问题。比如当Employee对象实例存入HashMap中,但是使用者在存入集合之后,修改了某个参数hashCode计算的域字段的值,此后再在HashMap中查找原有对象时由于hashCode已经改变,因此即使该对象已经存入HashMap中,结果是仍然无法找到最初存入的对象了。数组类型的hashCode,可以通过Arrays.hashCode方法计算得出。

3) toString: Java中比较推荐的实现方式为:

?
public String toString() {
        return getClass().getName() +
            "field1 = " + field1 +
            "field2 = " + field2;
    }

  注:C#的Framework中也存在一个类似的Object对象,作为C#所有对象(包括自定义对象)的唯一根类,其中也有对应的3个方法equals、hashCode和toString。Effective C#中针对这3个方法提供了一个很好的建议,既如果自定义类重载了这3个方法中任何一个,那么强烈建议该类也重载另外两个域方法。如对equals和toString而言,如果x.equals(y)返回true,那么x.toString.equals(y.toString)也将返回true,反之亦然。针对equals和hashCode域方法还有一种推荐的实现方式,如下:

?
public bool equals(Object other) {
      return toString().equals(other.toString());
  }
   
  public int hashCode() {
      return toString().hashCode();
  }

  

3. 包装类和自动打包:

  1) 包装器对象均为不可变对象,如String,既一旦初始化之后其值将不会再被改变。包装器类是final类,不能为继承。

  2) 自动拆包和打包:Integer n = 3; n++; 在执行n++时,Java编译器将自动插入一条拆包指令,然后进行自增计算,最后再将结果打入对象包内。

  3) 自动打包的规范要求boolean, byte, char <= 127, 和介于-128--127之间的short和int被包装到固定的对象中,见如下代码:

?
public void test() {
         Integer a1 = 1000;
         Ingeger a2 = 1000;
         if (a1 == a2)
             System.out.println(
                 "This won't be printed out because they are greater than 127.");
   
         Integer a3 = 100;
        Ingeger a4 = 100;
         if (a3 == a4)
             System.out.println(
                 "This will be printed out because they are less then 127.");
     }

  

4) 打包和拆包过程是编译器行为,不是虚拟机行为,是编译器在生成字节码的时候自动插入的指令。

  5) 包装类在容器中的应用。对于Java提供的泛型容器类其类型参数不能是primitive type,如int、float等,如果确实需要添加类似的数据,需要将相应的包装类作为容器类型参数,之后在插入原始类型数据,但是在插入过程中Java的编译器将自动插入打包指令,因此实际插入到容器中的仍然是包装类对象,见如下代码:

?
public static void main(String args[]) {
         ArrayList<Integer> l = new ArrayList<Integer>();
         for (int i = 0; i < 10; ++i)
             l.add(i);
           
         for (int i = 0; i < l.size(); ++i) {
             System.out.printf("The value is %d.\t",l.get(i));
             System.out.printf("The class name is %s.\n"
                 , l.get(i).getClass().getName());
         }
     }
     /*    结果如下:
         The value is 0.    The class name is java.lang.Integer.
        The value is 1.    The class name is java.lang.Integer.
         The value is 2.    The class name is java.lang.Integer.
         The value is 3.    The class name is java.lang.Integer.
        The value is 4.    The class name is java.lang.Integer.
        The value is 5.    The class name is java.lang.Integer.
         The value is 6.    The class name is java.lang.Integer.
         The value is 7.    The class name is java.lang.Integer.
         The value is 8.    The class name is java.lang.Integer.
         The value is 9.    The class name is java.lang.Integer.
     */

  

4. Java函数的变参表示方式:

  PrintStream printf(String fmt,Object...args),其效果相当于 PrintStream printf(String fmt,Object[] args)。在C++中变参的表示方式为int printf(const char* fmt, ...); 其后的缺省参数需要通过C语言中提供的宏VA_LIST来协助完成。

 

转自:http://www.cnblogs.com/xufeiyang/articles/2573492.html

http://webservices.ctocio.com.cn/173/12275673.shtml

转载于:https://www.cnblogs.com/sm21312/p/3909350.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/575310.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

文件路径和模块路径、nodemon工具

文件路径和模块路径&#xff1a; //在文件操作相对路径中&#xff0c;前面的 ./ 可以省略&#xff0c;但是 在模块标识路径中 &#xff0c;前面的 ./ 不能省略。// ./-----表示相对于当前目录 /-------当前文件所属磁盘根目录 var fs require(fs);fs.readFile(t…

Express框架简介、express使用模块引擎、模式数据

Express简介&#xff1a; 原生的http不足以应对我们的开发需求&#xff0c;所以我们需要使用框架来加快我们的开发&#xff0c;这里推荐expressjs&#xff0c;其官网&#xff1a;expressjs.com&#xff0c;中文文档推荐&#xff1a;http://javascript.ruanyifeng.com/nodejs/e…

数据库字段关联更新

MS SQL Server 子查询更新&#xff1a; update log set uin b.uin from log a,logs b where a.accountuin b.accountuin mysql 更新&#xff1a; update t_stat_month_user a INNER JOIN t_dept b on a.op_deptb.op_id set a.dept_short_nameb.dept_short_name;转载于:https:/…

浏览器基础知识

Web浏览器的主要功能是展示网页资源&#xff0c;即请求服务器并将结果展示在窗口中。工作原理大概如下&#xff1a; 地址栏输入URL 浏览器根据输入的URL查找域名的IP地址&#xff0c;DNS查找过程如下&#xff1a; 浏览器缓存——浏览器会缓存DNS记录一段时间&#xff0c;不同浏…

MongoDB简介、在node中使用MongoDB

MongoDB数据库简介&#xff1a; 使用MongoDB的好处是不用SQL语句&#xff0c;它提供了对应的API&#xff0c;其功能和MYSQL基本相同&#xff0c;是最像关系型数据库的非关系型数据库&#xff1b;不需要设计表的结构&#xff0c;文档相当于json&#xff0c;如果想要了解更多&am…

喜用神最正确的算法_各种电磁仿真算法的优缺点和适用范围(FDTD, FEM和MOM等)...

从实际工程应用的角度谈一下我对这几种算法的理解。先说结论&#xff0c;FDTD算的快但是不精确&#xff0c;可以用来算电大尺寸的物体&#xff0c;要是一个物体的尺寸大于10个波长&#xff0c;一般的服务站是跑不动FEM的&#xff0c;那必须得用FDTD了。FEM最经典的电磁仿真软件…

Linux下实现自动设置SSH代理

SSH的巨大价值体现在能够配置为代理服务器上。不像在Windows下每次还需要手动登录设置&#xff0c;Linux有很好的工具链能够实现自动设置SSH代理&#xff0c;就是expect和ssh的联合使用&#xff0c;再加上proxychains&#xff0c;任何程序都可以享用代理了&#xff0c;在此我简…

node中操作MySQL

node操作MySQL数据库&#xff1a; 在node中操作MySQL数据库的基本流程如下&#xff1a; // node操作MySQL需要在npm官网下载mysql包并载入node执行代码&#xff1a;// 1.载入MySQL数据库包var mysql require(mysql);// 2.创建连接&#xff1a;var connection mysql.createCo…

百度UEditor编辑器使用(二)

本文摘自&#xff1a;http://www.cnblogs.com/pmpen/archive/2011/09/19/2181811.html 首先感谢分享 百度WEB前端设计部推出一款开源的编辑器UEditor http://ueditor.baidu.com/index.html &#xff0c;这款编辑器相当强大&#xff0c;它提供了百度地图&#xff0c;google地图…

护士资格证延续注册WEB服务调用失败_服务熔断

熔断机制是应对服务雪崩效应的一种微服务链路保护机制&#xff0c;当扇出链路的某个微服务不可用或者响应时间太长时&#xff0c;会进行服务的降级&#xff0c;进而熔断该节点微服务的调用&#xff0c;快速返回”错误”的响应信息。当检测到该节点微服务响应正常后恢复调用链路…

AssetManager asset的使用

Android 系统为每一个新设计的程序提供了/assets文件夹&#xff0c;这个文件夹保存的文件能够打包在程序里。/res 和/assets的不同点是&#xff0c;android不为/assets下的文件生成ID。假设使用/assets下的文件&#xff0c;须要指定文件的路径和文件名称。以下这个样例&#xf…

vmware 快照用关机吗_PS板绘上色的骚操作,打破初学者上色残的瓶颈!|快照|绘画|初学者|配色|色阶...

PS板绘上色的骚操作&#xff0c;打破初学者上色残的瓶颈&#xff01;初学者如何入门绘画&#xff1f;学习板画难吗&#xff1f;怎样才能学习好绘画&#xff1f;想必这些都是绘画初学者们经常在想的问题吧&#xff0c;就是不知道如何才能学习好绘画&#xff0c;然后绘画出自己想…

vue概述、vue文件特点、vue核心思想、双向数据流、单文件、启动一个vue项目、声明式渲染

vue介绍&#xff1a; Vue&#xff1a;当前较火的MVVM框架&#xff0c;轻量、简介、高效、组件化、数据驱动&#xff0c;模块和渲染函数的弹性选择&#xff0c;简单的语法及项目创建&#xff0c;渲染速度极快&#xff0c;基于Virtual Dom&#xff0c;利用虚拟DOM实现快速渲染&a…

jQuery特效手风琴特效 手写手风琴网页特效

今天写一个简单的手风琴效果&#xff0c;不用插件&#xff0c;利用强大的jQuery&#xff0c;写一个手风琴效果。 页面预览&#xff0c;请点击这里预览&#xff1a; 手风琴预览案例分析&#xff1a; html结构 就是一个大盒子里面放着5个li&#xff0c;每个li的小小是200像素&a…

db2 删除索引_MYSQL进阶——索引

索引模型hash索引hash索引主要适用于等值查询的场景&#xff0c;排序&#xff0c;模糊搜索等场景并不适用有序数组有序数组可用于非等值查询&#xff0c;排序等场景&#xff0c;但是由于写数据时需要对数组中的元素进行位移&#xff0c;所以一般用于静态数据的场景二叉树二叉树…

javascript中es6语法

es6语法简介&#xff1a; // 1.历史&#xff1a;// 1995-----JavaScript诞生// 1997-----ECMAScript标准确立// 1999-----ES3出现&#xff0c;与此同时IE5风靡一时// 2009-----ES5出现&#xff0c;现在绝大所数使用的是ES5// 2015-----ES6/ECMAScript2015出现// 2.函数的Rest参…

react 导航条选中颜色_调整安卓手机的颜色以更好地查看一切

并非所有人都有相同的区分颜色的能力。我们对屏幕上色彩配置的需求甚至口味可能会因人而异。幸运的是&#xff0c;Android为我们提供了多种本地工具&#xff0c;能够调整手机的颜色。我们的手机显示数百万种音调&#xff0c;这些音调是由屏幕配置或终端如何解释从某些应用程序接…

vue项目目录结构分析、过滤器、vue文件中基础template、script、style

项目目录结构&#xff1a; 1.在一个项目中一般的目录结构为&#xff1a;my_project------------项目文件夹|____src--------------------------------存放人可以看懂的源代码&#xff0c;具备一定功能划分&#xff0c;mvc思想|____dist-------------------------------存放真实…

linux进程调度之 FIFO 和 RR 调度策略

转载 http://blog.chinaunix.net/uid-24774106-id-3379478.html linux进程调度之 FIFO 和 RR 调度策略 2012-10-19 18:16:43分类&#xff1a; LINUX 作者&#xff1a;manuscola.beangmail.com 博客地址&#xff1a;bean.blog.chinaunix.net 最近花了10几天的时间&#xff0…

echarts 获取点击的y轴数值_有机磷酸催化对醌的不对称直接加成反应合成轴手性芳基醌类化合物...

有机磷酸催化对醌的不对称直接加成反应合成轴手性芳基醌类化合物本文作者&#xff1a;Summer轴手性联芳基二醇骨架广泛存在于天然产物、生物活性分子、有用的手性配体以及催化剂中(Figure 1a)&#xff0c;因此&#xff0c;轴手性联芳基二醇化合物的合成受到广泛关注且已经取得了…