频道栏目
IT货架 > > 正文
c++ 等等的前置声明 java学识 JPA-第一个简略实例与主键生成策略
网友分享于:Jun 12, 2018 10:42:06 PM    来源: IT货架   
c++ 之类的前置声明

 

转自:http://blog.csdn.net/fjb2080/archive/2010/04/27/5533514.aspx 作者:清林,博客名:飞空静渡

刚开始学习c++的人都会遇到这样的问题:

定义一个类 class A,这个类里面使用了类B的对象b,然后定义了一个类B,里面也包含了一个类A的对象a,就成了这样:

 

  1. //a.h  
  2. #include "b.h"  
  3. class A  
  4. {  
  5. ....  
  6. private:  
  7.     B b;  
  8. };  
  9. //b.h  
  10. #include "a.h"  
  11. class B  
  12. {  
  13. ....  
  14. private:  
  15.     A a;  
  16. };  

 

一编译,就出现了一个互包含的问题了,这时就有人跳出来说,这个问题的解决办法可以这样,在a.h文件中声明类B,然后使用B的指针。

 

  1. //a.h   
  2. //#include "b.h"  
  3. class B;   
  4. class A   
  5. {  
  6.  ....   
  7. private:  
  8.  B b;   
  9. };   
  10. //b.h   
  11. #include "a.h"   
  12. class B  
  13. {  
  14.  ....   
  15. private:  
  16.  A a;   
  17. };  

 

然后,问题就解决了。

但是,有人知道问题是为什么就被解决的吗,也就是说,加了个前置声明为什么就解决了这样的问题。下面,让我来探讨一下这个前置声明。

类的前置声明是有许多的好处的。

我们使用前置声明的一个好处是,从上面看到,当我们在类A使用类B的前置声明时,我们修改类B时,只需要重新编译类B,而不需要重新编译a.h的(当然,在真正使用类B时,必须包含b.h)。

另外一个好处是减小类A的大小,上面的代码没有体现,那么我们来看下:

 

  1. //a.h  
  2. class B;  
  3. class A  
  4. {  
  5.     ....  
  6. private:  
  7.     B *b;  
  8. ....  
  9. };  
  10. //b.h  
  11. class B  
  12. {  
  13. ....  
  14. private:  
  15.     int a;  
  16.     int b;  
  17.     int c;  
  18. };  

 

我们看上面的代码,类B的大小是12(在32位机子上)。

如果我们在类A中包含的是B的对象,那么类A的大小就是12(假设没有其它成员变量和虚函数)。如果包含的是类B的指针*b变量,那么类A的大小就是4,所以这样是可以减少类A的大小的,特别是对于在STL的容器里包含的是类的对象而不是指针的时候,这个就特别有用了。

在前置声明时,我们只能使用的就是类的指针和引用(因为引用也是居于指针的实现的)。

那么,我问你一个问题,为什么我们前置声明时,只能使用类型的指针和引用呢?

如果你回答到:那是因为指针是固定大小,并且可以表示任意的类型,那么可以给你80分了。为什么只有80分,因为还没有完全回答到。

想要更详细的答案,我们看下下面这个类:

 

  1. class A  
  2. {  
  3. public:  
  4.     A(int a):_a(a),_b(_a){} // _b is new add  
  5.       
  6.     int get_a() const {return _a;}  
  7.     int get_b() const {return _b;} // new add  
  8. private:  
  9.     int _b; // new add  
  10.     int _a;  
  11. };  

 

我们看下上面定义的这个类A,其中_b变量和get_b()函数是新增加进这个类的。

那么我问你,在增加进_b变量和get_b()成员函数后这个类发生了什么改变,思考一下再回答。

好了,我们来列举这些改变:

第一个改变当然是增加了_b变量和get_b()成员函数;

第二个改变是这个类的大小改变了,原来是4,现在是8。

第三个改变是成员_a的偏移地址改变了,原来相对于类的偏移是0,现在是4了。

上面的改变都是我们显式的、看得到的改变。还有一个隐藏的改变,想想是什么。。。

这个隐藏的改变是类A的默认构造函数和默认拷贝构造函数发生了改变。

由上面的改变可以看到,任何调用类A的成员变量或成员函数的行为都需要改变,因此,我们的a.h需要重新编译。

如果我们的b.h是这样的:

 

  1. //b.h  
  2. #include "a.h"  
  3. class B  
  4. {  
  5. ...  
  6. private:  
  7.     A a;  
  8. };  

 

那么我们的b.h也需要重新编译。

如果是这样的:

 

  1. //b.h  
  2. class A;  
  3. class B  
  4. {  
  5. ...  
  6. private:  
  7.     A *a;  
  8. };  

 

那么我们的b.h就不需要重新编译。

像我们这样前置声明类A:

class A;

是一种不完整的声明,只要类B中没有执行需要了解类A的大小或者成员的操作,则这样的不完整声明允许声明指向A的指针和引用。

而在前一个代码中的语句

A a;

是需要了解A的大小的,不然是不可能知道如果给类B分配内存大小的,因此不完整的前置声明就不行,必须要包含a.h来获得类A的大小,同时也要重新编译类B。

再回到前面的问题,使用前置声明只允许的声明是指针或引用的一个原因是只要这个声明没有执行需要了解类A的大小或者成员的操作就可以了,所以声明成指针或引用是没有执行需要了解类A的大小或者成员的操作的。

 

 

转自:http://blog.csdn.net/rogeryi/archive/2006/12/12/1439597.aspx

这篇文章很大程度是受到Exceptional C++ (Hurb99)书中第四章 Compiler  Firewalls and the Pimpl Idiom  (编译器防火墙和Pimpl惯用法) 的启发,这一章讲述了减少编译时依赖的意义和一些惯用法,其实最为常用又无任何副作用的是使用前置声明来取代包括头文件。

Item 26 的Guideline - "Never #include a header when a forward declaration will suffice"

在这里,我自己总结了可以使用前置声明来取代包括头文件的各种情况和给出一些示例代码。

首先,我们为什么要包括头文件?问题的回答很简单,通常是我们需要获得某个类型的定义(definition)。那么接下来的问题就是,在什么情况下我们才需要类型的定义,在什么情况下我们只需要声明就足够了?问题的回答是当我们需要知道这个类型的大小或者需要知道它的函数签名的时候,我们就需要获得它的定义。

假设我们有类型A和类型C,在哪些情况下在A需要C的定义:

  1. A继承至C
  2. A有一个类型为C的成员变量
  3. A有一个类型为C的指针的成员变量
  4. A有一个类型为C的引用的成员变量
  5. A有一个类型为std::list<C>的成员变量
  6. A有一个函数,它的签名中参数和返回值都是类型C
  7. A有一个函数,它的签名中参数和返回值都是类型C,它调用了C的某个函数,代码在头文件中
  8. A有一个函数,它的签名中参数和返回值都是类型C(包括类型C本身,C的引用类型和C的指针类型),并且它会调用另外一个使用C的函数,代码直接写在A的头文件中
  9. C和A在同一个名字空间里面
  10. C和A在不同的名字空间里面


1,没有任何办法,必须要获得C的定义,因为我们必须要知道C的成员变量,成员函数。

2,需要C的定义,因为我们要知道C的大小来确定A的大小,但是可以使用Pimpl惯用法来改善这一点,详情请
看Hurb的Exceptional C++。

3,4,不需要,前置声明就可以了,其实3和4是一样的,引用在物理上也是一个指针,它的大小根据平台不同,可能是32位也可能是64位,反正我们不需要知道C的定义就可以确定这个成员变量的大小。

5,不需要,有可能老式的编译器需要。标准库里面的容器像list, vector,map,
在包括一个list<C>,vector<C>,map<C, C>类型的成员变量的时候,都不需要C的定义。因为它们内部其实也是使用C的指针作为成员变量,它们的大小一开始就是固定的了,不会根据模版参数的不同而改变。

6,不需要,只要我们没有使用到C。

7,需要,我们需要知道调用函数的签名。

8,8的情况比较复杂,直接看代码会比较清楚一些。

            C& doToC(C&);
            C& doToC2(C& c)  {return doToC(c);};


从上面的代码来看,A的一个成员函数doToC2调用了另外一个成员函数doToC,但是无论是doToC2,还是doToC,它们的的参数和返回类型其实都是C的引用(换成指针,情况也一样),引用的赋值跟指针的赋值都是一样,无非就是整形的赋值,所以这里即不需要知道C的大小也没有调用C的任何函数,实际上这里并不需要C的定义。

但是,我们随便把其中一个C&换成C,比如像下面的几种示例:

            1.
                C& doToC(C&);
            C& doToC2(C c)  {return doToC(c);};
                
                2.
                C& doToC(C);
                C& doToC2(C& c) {return doToC(c);};

                3.
                C doToC(C&);
                C& doToC2(C& c) {return doToC(c);};

                4.
                C& doToC(C&);
                C doToC2(C& c) {return doToC(c);};


无论哪一种,其实都隐式包含了一个拷贝构造函数的调用,比如1中参数c由拷贝构造函数生成,3中doToC的返回值是一个由拷贝构造函数生成的匿名对象。因为我们调用了C的拷贝构造函数,所以以上无论那种情形都需要知道C的定义。

9和10都一样,我们都不需要知道C的定义,只是10的情况下,前置声明的语法会稍微复杂一些。
最后给出一个完整的例子,我们可以看到在两个不同名字空间的类型A和C,A是如何使用前置声明来取代直接包括C的头文件的:
A.h

#pragma once

#include <list>
#include <vector>
#include <map>
#include <utility>

    //不同名字空间的前置声明方式
namespace test1
{
          class C;
}



namespace test2
{   
       //用using避免使用完全限定名
    using test1::C;
    
    class A 
    {
    public:
                C   useC(C);
            C& doToC(C&);
            C& doToC2(C& c) {return doToC(c);};
                         
    private:
            std::list<C>    _list;
            std::vector<C>  _vector;
            std::map<C, C>  _map;
            C*              _pc;
            C&              _rc;
    
    };
}
 

java学识

java常识
http://wenku.baidu.com/view/0d4f563c0912a21614792928.html

Java基础复习笔记03我们不太注意的陷阱  刘岩  Email:suhuanzheng7784877@163.com  1. 虚拟机对字符串的处理  虚拟机是将字符串直接量(不用new声明的)对象放到一个对象池中去缓存的,第一次使用的时候会将其放入池中,如果下次有其他变量访问一摸一样的直接量的话直接从对象池中去取该直接量,而不是又再生成一个对象。一般该池中的对象都不会被垃圾回收器回收。 比如:  String str1 = "1"; String str2 = "1";  实际上这段代码仅仅创建了一个字符串直接对象,保存在对象池中,池中对象就是”1”。str1和str2指向了该对象池的对象位置。 2. 编译时能确定的值  在编译时就能确定的值,在编译的时候就能够确定是否创建新的对象池对象。比如:  String str3 = "suhuanzhen123";  String str4 = "suhuanzhen" + 1 + "2" + "3"; System.out.println(str3 == str4);  实际上str3和str4在内存池中都是指向了"suhuanzhen123"这个字符串对象,所以输出时true。因为str4在编译期间就能确定值是suhuanzhen123,所以JVM不会再创建新的对象。直接从对象池中去取。 3. 在编译期间不能确定的值  如果字符串的拼接包含了函数、变量等等编译时不确定因素,那么此时就不能指向对象池中的变量了。比如  String str5 = "suhuanzhen123";  String str6 = "suhuanzhen" + "1".length() + "2" + "3"; System.out.println(str5 == str6);    4. 在编译时就能确定值的字符串变量创建值  比如代码  String str6 = "suhuanzhen" + "1" + "2" + "3";  实际上仅仅创建了一个对象——suhuanzhen123,缓存到对象池。 5. StringBuilder和StringBuffer的区别  一般在单线程的系统环境下优先使用StringBuilder,因为它是非线程安全的。而在多线程的环境下,比如Web系统,优先使用StringBuffer,因为它当中绝大部分方法都使用了synchronized进行修饰,保证了多线程环境下的线程安全问题。 6. 字符串之间的比较  用一个线程池的常量对象和一个变量、函数返回值比较直接使用==其实就可以了比如:  String jqgridparams = request.getParameter("jqgridparams"); if (jqgridparams != null) {   if ("getparams" == jqgridparams) {  文档冲亿季,好礼乐相随mini ipad移动硬盘拍立得百度书包 2 / 14      //„„„„„„„„  }  }  更直观一点就是   static String getStr(){   return "suhuanzhen123";   }    public static void main(String[] args) {   String str7 = getStr();    System.out.println("suhuanzhen123" == str7);       }  输出的是true;  如果是2个字符串变量之间比较就不能用==了  String str7 = getStr();  String str8 = new String("suhuanzhen123"); System.out.println(str8 == str7);  输出false。 应该用  System.out.println(str8.equals(str7));   7. 赋值语句的类型提升  上图程序实际上就是类型自动提升发生了错误,num本身是int型,而num = num+33.567;表达式中使用了double类型的数值,那么此表达式会自动向最高级别的基本类型转型,但是呢num自身是int,会提示不能向上转换的编译错误。如果程序这么写   public static void main(String[] args) {   int num = 1;    double numDoub = num+33.567;    }  就不会报错了。  赋值运算中基本数据转型的级别从高到低排序是String->double->float->long->int->chart、short->byte。  如果运算中含有字符串变量,那么二话不说,此表达式所有的运算最后的返回值就是一个字符串了,比如    int num = 1;    double numDoub = num+33.567;     String str = num+numDoub+"";  所以最高级别还是String。  8. 符合运算符中的隐式类型转换   3 / 14    在符合运算符中实际上存在着一个隐藏的类型转换    int sum =1;   sum += 30.44;    System.out.println(sum); 实际上会输出31,如果是之前的例子    int sum =1;     sum = sum+30.44;  编译器是会报错的。那么+=实质上是什么意思呢!就是 sum += 30.44; 等价于  sum = (int)(sum+30.44);  也就是说符合运算符自身还隐式地包含了一个强制转型的动作。这样做有时候会失去精度以及高位段位。  short sum =9999;  sum += 999999999.9999999; System.out.println(sum);  输出-3826  9. 泛型引起的陷阱  List list = new ArrayList(); list.add("1"); list.add(2); list.add("3q");   for (int i = 0; i < list.size(); i++) {  System.out.println(list.get(i));  }   List<String> listNew = list; for (String strNew : listNew) {  System.out.println(strNew); }  第一个循环输出无任何问题,第二个循环报出转型异常。换几话说,集合本身可以存储不同类型元素的对象的,只不过自从JDK1.5之后出现了泛型,才使我们很多人觉得集合变量仅仅能存一种类型的对象元素。泛型是为了在编译时更好的排除类型转换带来的繁琐检查。而在大多数应用中确实集合中就是同类型的元素。而泛型配合上加强的新循环,它会强制进行单一类型的转型操作。所以第二个循环的的结果就是 java.lang.Integer cannot be cast to java.lang.String 泛型还有可能引起擦除,如下:  class MyT<PK extends Number> {   private PK id;   public PK getId() {     return id;   4 / 14     }    public void setId(PK id) {   this.id = id;   }   }   public class TTrap {  /**    * @param args   */   public static void main(String[] args) {   MyT<Integer> my = new MyT<Integer>();   my.setId(100);    System.out.println(my.getId() + ":" + my.getId());   MyT mynew = my;    System.out.println(mynew.getId() + ":" + mynew.getId());   }   }  在mynew.getId()这一句中实际上编译器这时候仅仅将它识别为Number了,直接当做Integer使用会报错,除非强制转型方可当做Integer来使用。这里就相当于将泛型信息擦除了,程序只有到了真正运行时环境下才能把其当做Integer来用,编译期间他不认识其实质面目。Java擦除泛型信息是将该对象所有的泛型信息全部擦除掉,不仅仅是那个传入的泛型,如果此类本身也定义了其他泛型相关的方法、变量,统统没有了!  class MyTT<PK extends Number> {   private PK id;    private List<String> list;   public PK getId() {   return id;   }    public void setId(PK id) {   this.id = id;   }    public List<String> findRows() {   list = new ArrayList<String>();   list.add("步惊云");     list.add("聂风");   5 / 14      return list;   }   }   public static void main(String[] args) {    MyTT<BigInteger> myTT = new MyTT<BigInteger>();   for (String str : myTT.findRows()) {    System.out.println(str);    }       MyTT my2 = myTT;    List listNoString = my2.findRows();     }  执行MyTT my2 = myTT;实际上是擦除了泛型的所有信息,就连之前定义的public List<String> findRows()也被无辜的擦除了泛型信息。编译时只能认识findRows方法返回的是List而不是List<String>。 10. 遇到含有.的字符串分割成字符串数组   public static void main(String[] args) {   String str = "www.google.cn";   String[] array = str.split("\\.");   System.out.println(array.length);    }  直接用String[] array = str.split(".");不起作用,因为”.”代表可以匹配任意字符,必须转义。 11. 什么样的对象存在线程安全问题  当我们刚学Java的时候不会考虑多线程的问题,在自己的IDE环境下运行成功了就行了,不会考虑并发使用此程序的时候会出现什么情况。等做程序员一段时间后发现自己编写的程序确实存在,多线程安全问题。之后走火入魔似地给自己写的方法加上synchronized。其实我们有时候没搞懂什么情况下会出现线程安全的问题。如果我们开发的是单机版的C/S应用系统,客户的计算机就是软件产品的服务器,那么会不会有并发现象呢?只要不是恶意恶搞你的软件,一般一个客户就对应着一个服务,这个时候不存在并发访问的问题,所以一般情况下不会考虑多线程的问题。如果在基于B/S的Web系统中,可能服务器是一台机器,世界各地的客户都来访问你开发的系统,为这些世界各地的客户提供服务,这个时候可能会出现线程安全的问题。为什么说可能,而不是一定呢?如果你的所有的类和类之间的调用都是通过new一个新的对象为之服务(极端情况下还真有),那么new实际上是在服务器内存中新开辟一块内存空间承载对象,那么这种极端情况是不存在线程安全问题的,只要您的服务器内存极大——1TB内存,每日客户量在2万人左右,估计运行1个月应该差不多没什么问题。不过考虑成本,一般没这样高级的服务器让这种极端的程序运行吧。那么最多的就是类之间的调用不是永远的new一个新的出来,而是为了节省资源,使用对象缓存池或者干脆整个应用软件调用比较频繁的类就使用一个单例对象就得了,每次使用这个对象都是原来已有的,不必在内存新建一个,这样垃圾回收器的工作量也不会那么大。而恰恰是这个时候,这个复用的
java常识
http://wenku.baidu.com/view/0d4f563c0912a21614792928.html

Java基础复习笔记03我们不太注意的陷阱  刘岩  Email:suhuanzheng7784877@163.com  1. 虚拟机对字符串的处理  虚拟机是将字符串直接量(不用new声明的)对象放到一个对象池中去缓存的,第一次使用的时候会将其放入池中,如果下次有其他变量访问一摸一样的直接量的话直接从对象池中去取该直接量,而不是又再生成一个对象。一般该池中的对象都不会被垃圾回收器回收。 比如:  String str1 = "1"; String str2 = "1";  实际上这段代码仅仅创建了一个字符串直接对象,保存在对象池中,池中对象就是”1”。str1和str2指向了该对象池的对象位置。 2. 编译时能确定的值  在编译时就能确定的值,在编译的时候就能够确定是否创建新的对象池对象。比如:  String str3 = "suhuanzhen123";  String str4 = "suhuanzhen" + 1 + "2" + "3"; System.out.println(str3 == str4);  实际上str3和str4在内存池中都是指向了"suhuanzhen123"这个字符串对象,所以输出时true。因为str4在编译期间就能确定值是suhuanzhen123,所以JVM不会再创建新的对象。直接从对象池中去取。 3. 在编译期间不能确定的值  如果字符串的拼接包含了函数、变量等等编译时不确定因素,那么此时就不能指向对象池中的变量了。比如  String str5 = "suhuanzhen123";  String str6 = "suhuanzhen" + "1".length() + "2" + "3"; System.out.println(str5 == str6);    4. 在编译时就能确定值的字符串变量创建值  比如代码  String str6 = "suhuanzhen" + "1" + "2" + "3";  实际上仅仅创建了一个对象——suhuanzhen123,缓存到对象池。 5. StringBuilder和StringBuffer的区别  一般在单线程的系统环境下优先使用StringBuilder,因为它是非线程安全的。而在多线程的环境下,比如Web系统,优先使用StringBuffer,因为它当中绝大部分方法都使用了synchronized进行修饰,保证了多线程环境下的线程安全问题。 6. 字符串之间的比较  用一个线程池的常量对象和一个变量、函数返回值比较直接使用==其实就可以了比如:  String jqgridparams = request.getParameter("jqgridparams"); if (jqgridparams != null) {   if ("getparams" == jqgridparams) {  文档冲亿季,好礼乐相随mini ipad移动硬盘拍立得百度书包 2 / 14      //„„„„„„„„  }  }  更直观一点就是   static String getStr(){   return "suhuanzhen123";   }    public static void main(String[] args) {   String str7 = getStr();    System.out.println("suhuanzhen123" == str7);       }  输出的是true;  如果是2个字符串变量之间比较就不能用==了  String str7 = getStr();  String str8 = new String("suhuanzhen123"); System.out.println(str8 == str7);  输出false。 应该用  System.out.println(str8.equals(str7));   7. 赋值语句的类型提升  上图程序实际上就是类型自动提升发生了错误,num本身是int型,而num = num+33.567;表达式中使用了double类型的数值,那么此表达式会自动向最高级别的基本类型转型,但是呢num自身是int,会提示不能向上转换的编译错误。如果程序这么写   public static void main(String[] args) {   int num = 1;    double numDoub = num+33.567;    }  就不会报错了。  赋值运算中基本数据转型的级别从高到低排序是String->double->float->long->int->chart、short->byte。  如果运算中含有字符串变量,那么二话不说,此表达式所有的运算最后的返回值就是一个字符串了,比如    int num = 1;    double numDoub = num+33.567;     String str = num+numDoub+"";  所以最高级别还是String。  8. 符合运算符中的隐式类型转换   3 / 14    在符合运算符中实际上存在着一个隐藏的类型转换    int sum =1;   sum += 30.44;    System.out.println(sum); 实际上会输出31,如果是之前的例子    int sum =1;     sum = sum+30.44;  编译器是会报错的。那么+=实质上是什么意思呢!就是 sum += 30.44; 等价于  sum = (int)(sum+30.44);  也就是说符合运算符自身还隐式地包含了一个强制转型的动作。这样做有时候会失去精度以及高位段位。  short sum =9999;  sum += 999999999.9999999; System.out.println(sum);  输出-3826  9. 泛型引起的陷阱  List list = new ArrayList(); list.add("1"); list.add(2); list.add("3q");   for (int i = 0; i < list.size(); i++) {  System.out.println(list.get(i));  }   List<String> listNew = list; for (String strNew : listNew) {  System.out.println(strNew); }  第一个循环输出无任何问题,第二个循环报出转型异常。换几话说,集合本身可以存储不同类型元素的对象的,只不过自从JDK1.5之后出现了泛型,才使我们很多人觉得集合变量仅仅能存一种类型的对象元素。泛型是为了在编译时更好的排除类型转换带来的繁琐检查。而在大多数应用中确实集合中就是同类型的元素。而泛型配合上加强的新循环,它会强制进行单一类型的转型操作。所以第二个循环的的结果就是 java.lang.Integer cannot be cast to java.lang.String 泛型还有可能引起擦除,如下:  class MyT<PK extends Number> {   private PK id;   public PK getId() {     return id;   4 / 14     }    public void setId(PK id) {   this.id = id;   }   }   public class TTrap {  /**    * @param args   */   public static void main(String[] args) {   MyT<Integer> my = new MyT<Integer>();   my.setId(100);    System.out.println(my.getId() + ":" + my.getId());   MyT mynew = my;    System.out.println(mynew.getId() + ":" + mynew.getId());   }   }  在mynew.getId()这一句中实际上编译器这时候仅仅将它识别为Number了,直接当做Integer使用会报错,除非强制转型方可当做Integer来使用。这里就相当于将泛型信息擦除了,程序只有到了真正运行时环境下才能把其当做Integer来用,编译期间他不认识其实质面目。Java擦除泛型信息是将该对象所有的泛型信息全部擦除掉,不仅仅是那个传入的泛型,如果此类本身也定义了其他泛型相关的方法、变量,统统没有了!  class MyTT<PK extends Number> {   private PK id;    private List<String> list;   public PK getId() {   return id;   }    public void setId(PK id) {   this.id = id;   }    public List<String> findRows() {   list = new ArrayList<String>();   list.add("步惊云");     list.add("聂风");   5 / 14      return list;   }   }   public static void main(String[] args) {    MyTT<BigInteger> myTT = new MyTT<BigInteger>();   for (String str : myTT.findRows()) {    System.out.println(str);    }       MyTT my2 = myTT;    List listNoString = my2.findRows();     }  执行MyTT my2 = myTT;实际上是擦除了泛型的所有信息,就连之前定义的public List<String> findRows()也被无辜的擦除了泛型信息。编译时只能认识findRows方法返回的是List而不是List<String>。 10. 遇到含有.的字符串分割成字符串数组   public static void main(String[] args) {   String str = "www.google.cn";   String[] array = str.split("\\.");   System.out.println(array.length);    }  直接用String[] array = str.split(".");不起作用,因为”.”代表可以匹配任意字符,必须转义。 11. 什么样的对象存在线程安全问题  当我们刚学Java的时候不会考虑多线程的问题,在自己的IDE环境下运行成功了就行了,不会考虑并发使用此程序的时候会出现什么情况。等做程序员一段时间后发现自己编写的程序确实存在,多线程安全问题。之后走火入魔似地给自己写的方法加上synchronized。其实我们有时候没搞懂什么情况下会出现线程安全的问题。如果我们开发的是单机版的C/S应用系统,客户的计算机就是软件产品的服务器,那么会不会有并发现象呢?只要不是恶意恶搞你的软件,一般一个客户就对应着一个服务,这个时候不存在并发访问的问题,所以一般情况下不会考虑多线程的问题。如果在基于B/S的Web系统中,可能服务器是一台机器,世界各地的客户都来访问你开发的系统,为这些世界各地的客户提供服务,这个时候可能会出现线程安全的问题。为什么说可能,而不是一定呢?如果你的所有的类和类之间的调用都是通过new一个新的对象为之服务(极端情况下还真有),那么new实际上是在服务器内存中新开辟一块内存空间承载对象,那么这种极端情况是不存在线程安全问题的,只要您的服务器内存极大——1TB内存,每日客户量在2万人左右,估计运行1个月应该差不多没什么问题。不过考虑成本,一般没这样高级的服务器让这种极端的程序运行吧。那么最多的就是类之间的调用不是永远的new一个新的出来,而是为了节省资源,使用对象缓存池或者干脆整个应用软件调用比较频繁的类就使用一个单例对象就得了,每次使用这个对象都是原来已有的,不必在内存新建一个,这样垃圾回收器的工作量也不会那么大。而恰恰是这个时候,这个复用的 JPA------第一个简单实例与主键生成策略

     写实体bean,映射的数据可以采用XML配置方式,也可以采用注解方式,在JPA中推荐大家用注解的方式,因为注解的方式开发应用效率是挺高的。

     每个实体bean都要有个实体标识属性,这个实体标识属性主要用于在内存里面判断对象。通过@Id就可以定义实体标识。可以标识在属性的get方法前面,也可以标识在字段上面,通常我们更倾向于标识在属性的get方面上面。
     如果我们希望采用数据库的id自增长的方式来生成主键值的话,这时候我们要用到一个注解@GeneratedValue,这注解里面有一些属性,其中一个是策略strategy,生成主键值的方案,JPA里没有Hibernate提供的那么多方案,它提供的方案有如下图:



 

 

 

 

  1.  
    • AUTO: JPA自动选择合适的策略,是默认选项;
    • IDENTITY: 采用数据库ID自增长的方式来生成主键值,Oracle不支持这种方式;
    • SEQUENCE: 通过序列产生主键,通过@SequenceGenerator注解指定序列名,MySql不支持这种方式;
    • TABLE: 采用表生成方式来生成主键值,那怎么样生成呢?很简单,表里面通常有两个字段,第一个字段是给它一个名称(就是个列名而已),第二个字段专门用来累加用的,就是说每访问一次这个表,第二个字段就会累加1,不断累加。就是说你们要得到这个主键值的话,访问这个表,然后update这个表的这个字段,把它累加1之后,然后再把这个值取出来作为主键,再给他赋进去,表生成就是这样。
    • Oracle数据库默认情况下,不能支持用id自增长方式来生成主键值;
    • mysql在默认情况下不能支持SEQUENCE序列的方式来生成主键值,所以我们一定要注意我们使用的数据库。
    • TABLE表生成方式才是通用的,但是这种方式效率并不高。
    • 如果我们开发的应用,我们不可以预测用户到底使用哪种数据库,那么这个时候应该设为哪个值呢?答案是AUTO,就是说由持久化实现产品,来根据你使用的方言来决定它采用的主键值的生成方式,到底是IDENTITY?还是SEQUENCE?还是TABLE? 如果用的是Hibernate,那么它会用IDENTITY这种生成方式来生成主键值。
         

    IDENTITY和SEQUENCE这两种生成方案通不通用啊?对所有数据库:
              注意:如果我们把策略strategy设置成@GeneratedValue(strategy=GenerationType.AUTO)的话,AUTO本身就是策略的默认值,我们可以省略掉,就是说简单写成这样@GeneratedValue

    摘自CSDN:
         @GeneratedValue:主键的产生策略,通过strategy属性指定。默认情况下,JPA自动选择一个最适合底层数据库的主键生成策略,如SqlServer对应identity,MySql对应auto increment。在javax.persistence.GenerationType中定义了以下几种可供选择的策略:

    1) IDENTITY:采用数据库ID自增长的方式来自增主键字段,Oracle不支持这种方式;
    2) AUTO: JPA自动选择合适的策略,是默认选项;
    3) SEQUENCE:通过序列产生主键,通过@SequenceGenerator注解指定序列名,MySql不支持这种方式; 4) TABLE:通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。不同的JPA实现商生成的表名是不同的,如 OpenJPA生成openjpa_sequence_table表Hibernate生成一个hibernate_sequences表,而 TopLink则生成sequence表。这些表都具有一个序列名和对应值两个字段,如SEQ_NAME和SEQ_COUNT。

    可参考http://blog.csdn.net/lzxvip/archive/2009/06/19/4282484.aspx



        实体bean开发完后,就要用持久化API对实体bean进行添删改查的操作,我们学习持久化API的时候,可以对照Hibernate来学习,接下来建立个单元测试,在开发的过程中,建议大家一定要用单元测试(junit可以用来进行单元测试)。

    第一步写:persistence.xml(要求放在类路径的META-INF目录下)
Xml代码 复制代码
  1. <persistence xmlns="http://java.sun.com/xml/ns/persistence"     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  2.     xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">  
  3.   
  4. <persistence-unit name="itcast" transaction-type="RESOURCE_LOCAL">  
  5.   
  6.    <properties>  
  7.     <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect" />  
  8.     <property name="hibernate.hbm2ddl.auto" value="update"/>  
  9.     <property name="hibernate.connection.driver_class" value="org.gjt.mm.mysql.Driver" />  
  10.     <property name="hibernate.connection.username" value="root" />  
  11.     <property name="hibernate.connection.password" value="root" />  
  12.     <property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8" />  
  13.    </properties>  
  14.   
  15. </persistence-unit>  
  16. </persistence>   

第二步写:Person.java (实体bean)

 

Java代码 复制代码
  1. package cn.itcast.bean;   
  2.   
  3. import javax.persistence.Entity;   
  4. import javax.persistence.GeneratedValue;   
  5. import javax.persistence.GenerationType;   
  6. import javax.persistence.Id;   
  7.   
  8.   
  9. @Entity          //以javax开头的包,都是Sun公司制定的一些规范   
  10. public class Person {   
  11.     private Integer id;   
  12.     private String name;   
  13.   
  14.     public Person() {   
  15.     /*对象是由Hibernate为我们创建的,当我们通过ID来获取某个实体的时候,这个实体给我们返回了这个对象的创建是由Hibernate内部通过反射技术来创建的,反射的时候用到了默认的构造函数,所以这时候必须给它提供一个public的无参构造函数。*/  
  16.     }   
  17.   
  18.     public Person(String name) {   
  19.         this.name = name;   
  20.     }   
  21.   
  22.     @Id  
  23.     @GeneratedValue(strategy=GenerationType.AUTO) //auto默认,可不写,直接写@GeneratedValue   
  24.     public Integer getId() {   
  25.         return id;   
  26.     }   
  27.   
  28.     public void setId(Integer id) {   
  29.         this.id = id;   
  30.     }   
  31.   
  32.     public String getName() {   
  33.         return name;   
  34.     }   
  35.   
  36.     public void setName(String name) {   
  37.         this.name = name;   
  38.     }   
  39. }   

第三步:PersonTest.java (junit单元测试)

 

 

Java代码 复制代码
  1. package junit.test;   
  2.   
  3. import javax.persistence.EntityManager;   
  4. import javax.persistence.EntityManagerFactory;   
  5. import javax.persistence.Persistence;   
  6.   
  7. import org.junit.BeforeClass;   
  8. import org.junit.Test;   
  9.   
  10. import cn.itcast.bean.Person;   
  11.   
  12. public class PersonTest {   
  13.   
  14.     @BeforeClass  
  15.     public static void setUpBeforeClass() throws Exception {   
  16.     }   
  17.   
  18.     @Test public void save(){   
  19.         //对实体bean进行操作,第一步应该获取什么对象啊?     SessionFactory对象   
  20.         //这里用获取的EntityManagerFactory对象,这可以把它看成跟Hibernate的SessionFactory对象差不多的东西   
  21.         EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast");   
  22.         //参数"itcast"是persistence.xml文件中<persistence-unit&nbsp;name="itcast">name的属性值。   
  23.         EntityManager em = factory.createEntityManager();   
  24.         em.getTransaction().begin();    //开启事务   
  25.         em.persist(new Person("中信软件")); //持久化对象   
  26.         em.getTransaction().commit();   //提交事务   
  27.         em.close();   
  28.         factory.close();   
  29.   
  30.         //SessionFactory  -->  Session  -->  begin事务   
  31.     }   
  32. }  

 
/*
session.save(obj);
    persist这方法在Hibernate里也存在,Hibernate的作者已经不太推荐大家用save方法,而是推荐大家用persist方法 。
    why? 首先并不是代码上的问题,主要是这个名字上的问题,因为我们把这个ORM技术叫做持久化产品,那么我们对某个对象持久化,应该叫持久化,而不应该叫保存,所以后来Hibernate的作者推荐用persist方法,这并不是功能的问题, 主要是取名的问题,所以用persist方法也可以 。
*/ 


    目前数据库表是不存在的,我们采取实体(领域)建模的思想,让它根据实体bean来生成数据库表,在persistence.xml里,<property name="hibernate.hbm2ddl.auto" value="update"/>,生成策略是update,就是说表不存在的时候,它会创建数据库表。 
    问题,它什么时候创建表啊?创建表的时机是在什么时候创建的啊?答案是得到SessionFactory的时候,在JPA里也一样,是我们得到EntityManagerFactory的时候创建表,也就是说我们只要执行下面的那段代码就生成表了。

 

Java代码 复制代码
  1. public class PersonTest {   
  2.   
  3.     @BeforeClass  
  4.     public static void setUpBeforeClass() throws Exception {   
  5.     }   
  6.   
  7.     @Test public void save(){          
  8.         EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast");           
  9.         factory.close();   
  10.     }   
  11.   
  12. }  

 

        通过这个特性,可以在开发的时候,用来验证我们编写的实体映射元数据是否是正确的,通过这个就可以判断。如果生成不了表,就说明是编写的实体映射出问题了(比如实体bean),以后要学会怎样排错。

广告服务联系QQ:1134687142 | 网站地图

版权所有: IT货架- 内容来自互联网,仅供用于技术学习,请遵循相关法律法规. 京ICP备11030978号-1