java-基础-JNI本地栈

jni全称是Java
Native Interface
是在JAVA和Native层(包括但不限于C/C++)相互调用的接口规范。

JNI在JAVA1.1中正式推出,在JAVA1.2版本中加入了JNI_OnLoadJNI_OnUnload方法,这两个方法还是很有用的,后面再说。

JNI基础篇

Java通过JNI调用本地方法的过程大致是

  1. 写一个Java类,在其中声明对应要调用的native方法,用native关键字修饰。 比如private
    static native int native_newInstance();
  2. 通过javah命令生成java类对应的C/C++头文件。javah
    -encoding utf-8 -cp src com.young.soundtouch.SoundTouch
  3. 在C/C++中实现头文件中声明的函数
  4. 编译C/C++代码为动态库(Windows中的dll,linux(Android)中的so,MAC OSX中的dylib)。
  5. 在java代码中加载动态库,即可像调用Java方法一样,调用到native函数。

其中第三步在Java1.2中增加了JNI_OnLoad方法之后有另一种实现方式(后面说)。

javah生成的头文件大致是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_young_soundtouch_SoundTouch */

#ifndef _Included_com_young_soundtouch_SoundTouch
#define _Included_com_young_soundtouch_SoundTouch
#ifdef __cplusplus
extern "C" {
#endif
#undef com_young_soundtouch_SoundTouch_SETTING_USE_AA_FILTER
#define com_young_soundtouch_SoundTouch_SETTING_USE_AA_FILTER 0L
  /*
 * Class:     com_young_soundtouch_SoundTouch
 * Method:    native_getDefaultSampleElementSize
 * Signature: ()I
 */
  JNIEXPORT jint JNICALL Java_com_young_soundtouch_SoundTouch_native_1getDefaultSampleElementSize
      (JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif

文件开头就是普通的头文件,但是可以发现:

  1. 包含了jni.h头文件(一般位于$JAVA_HOME/jd{jdk-version}/include文目录内)。这是JNI中所有的类型、函数、宏等定义的地方。所以C/C++世界的JNI是由他制定的游戏规则。
  2. 在类中生命的常量(static final)类型会在头文件中以宏的形式出现,这一点还是很方便的。
  3. 函数的注释还是比较全的,包括了:
    1. 对应的class
    2. 对应的java方法名
    3. 对应java方法的签名
  4. 方法的声明显得有点奇怪,由以下及部分组成:
    1. JNIEXPORT这是函数的导出方式
    2. jint 返回值类型(jint由jni.h定义,对应int,下面具体再说吧
    3. JNICALL 函数的调用方式也就是汇编级别参数的传入方式
    4. Java_com_young_soundtouch_SoundTouch_native_1getDefaultSampleElementSize —— 超级长的函数名!!!格式是Java_ + 类全名 + _ +
      JAVA中声明的native方法名。其中会把包名中的点(.)替换成下划线(_),同时为了避免冲突把下划线替换成_1
    5. 方法的参数,上面的这个方法在JAVA的声明中实际上是没有参数的,其中的JNIENV顾名思义是JNI环境,和具体的线程绑定。而第二个参数jclass其实是java中的Class因为上面是一个static方法,因此第二个参数是jclass。如果是一个实例方法则对应第二个参数是jobject,相当于java中的this

下面在C/C++中实现这个方法就行啦。但是在动手前现大致了解以下jni.h制定的游戏规则。

类型转换

javah生成的头文件里面使用的类型都是jni.h定义的,目的是做到平台无关,比如保证在所有平台上jint都是32位的有符号整型。

基本对应关系如下:

jni 类型 JAVA类型 对应本地类型 类型签名
jboolean boolean uint8_t Z
jbyte byte char B
jcahr char uint16_t C
jshort short int16_t S
jint int int32_t I
jlong long int64_t J
jfloat float float F
jdouble double double D
void void void V

引用类型对应关系:

java类型 JNI 类型 java类型 JNI 类型
所有的实例引用 jobject java.lang.Class jclass
java.lang.String jstring Ocject[] jobjectArray
java.lang.Throwable jthrowable 基本类型[] jxxxArray

通过表格发现,除了上面定义的StringClassThrowable,其他的类(除了数组)都是以jobject的形式出现的!事实上jstring,
jclass也都是object的子类。所以这里还是和java层一样,一切皆jobject。(当然,如果jni在C语言中编译的话是没有继承的概念的,此时jstring,jclass等其实就是jobject!用了typedef转换而已!!)

接下来是JNIEnv *这个指针,他提供了JNI中的一系列操作的接口函数。

JNI中操作jobject

其实也就是在native层操作java层的实例。 要操作一个实例无疑是:

  1. 获取/设置 (即 get/set )成员变量(field)的值
  2. 调用成员方法(method)

所以问题来了:(挖掘机技术哪家强?! o(*≧▽≦)ツ┏━┓ )

怎么得到field 和 method?

通过使用jfieldID和jmethodID: 在JNI中使用类似于放射的方式来进行field和method的操作。JNI中使用jfieldID和jmethodID来表示成员变量和成员方法,获取方式是:

1
2
3
4
jfieldID GetFieldID(jclass clazz, const char *name, const char *sig);
jfieldID GetStaticFieldID(jclass clazz, const char *name, const char *sig);
jmethodID GetMethodID(jclass clazz, const char *name, const char *sig);
jmethodID GetStaticMethodID(jclass clazz, const char *name, const char *sig) ;

其中最后一个参数是签名。 获取jclass的方法除了实用上面静态方法的第二个参数外,还可以手动获取。 jclass
FindClass(const char *name)
 需要注意的是name参数,他是一个类包括包名的全称,但是需要把包名中的点.替换成斜杠/。(好吧,事实上我不是太明白为啥要这么做。)

有了jfieldID和jmethodID就知道狗蛋住哪了,现在去狗蛋家找他玩 (^∇^*)

成员变量:

  1. get:

    1. <type> Get<type>Field(jobject , jfieldID);即可获得对应的field,其中field的类型是type,可以是上面类型所叙述的任何一种。
    2. <type> GetStatic<type>Field(jobject , jfieldID);同1,唯一的区别是用来获取静态成员。
  2. set:
    1. void Set<type>Field(jobject obj, jfieldID fieldID, <type> val)
    2. void SetStatic<type>Field(jclass clazz, jfieldID fieldID, <type> value);

成员方法:

调用方法自然要把方法的参数传递进去,JNI中实现了三种参数的传递方式:

  1. Call<type>Method(jobject obj, jmethod jmethodID, ...)其中...是C中的可变长参数,类似于printf那样,可以传递不定长个参数。于是你可以把java方法需要的参数在这里面传递进去。
  2. Call<type>MethodV(jobject obj, jmethodID methodID, va_list args)其中的va_list也是C中可变长参数相关的内容(我不了解,不敢瞎说。。。偷懒粘一下Oracle的文档)Programmers
    place all arguments to the method in an args argument of type va_list that immediately follows the methodID argument. The CallMethodV routine accepts the arguments, and, in turn, passes them to the Java method that the programmer wishes to invoke.
  3. Call<type>MethodA(jobject obj, jmethodID methodID, const jvalue * args)哎!这个我知道可以说两句LOL~~这里的jvalue通过查代码发现就是JNI中各个数据类型的union,所以可以使用任何类型复制!所以参数的传入方式是通过一个jvalue的数组,数组内的元素可以是任何jni类型。

然后问题又来了:(挖掘机技术到底哪家强?!o(*≧▽≦)ツ┏━┓) 如果传进来的参数和java声明的参数的不一致会怎么样!(即不符合方法签名)这里文档中没用明确解释,但是说道:

Exceptions raised during the execution of the Java method.

1
2
3
4
5
6
7
8
9
10
11
typedef union jvalue {
    jboolean z;
    jbyte    b;
    jchar    c;
    jshort   s;
    jint     i;
    jlong    j;
    jfloat   f;
    jdouble  d;
    jobject  l;
} jvalue;
  1. 调用实例方法(instance method):

    1. <type> Call<type>Method(jobject obj, jmethodID methodID,
      ...);
      调用一个具有<type>类型返回值的方法。
    2. <type> Call<type>MethodV(jobject obj, jmethodID methodID,
      va_list args);
    3. Call<type>MethodA(jobject obj, jmethodID methodID, const
      jvalue * args)
  2. 调用静态方法(static method):
    1. <type> CallStatic<type>Method(jobject obj, jmethodID
      methodID, ...);
    2. <type> CallStatic<type>MethodV(jobject obj, jmethodID
      methodID, va_list args);
    3. CallStatic<type>MethodA(jobject obj, jmethodID methodID,
      const jvalue * args)
  3. 调用父类方法(super.method),这个就有点不一样了。多了一个jclass参数,jclass可以使obj的父类,也可以是obj自己的class,但是methodID必须是从jclass获取到的,这样就可以调用到父类的方法。
    1. <type> CallNonvirtual<type>Method(jobject obj, jclass
      clazz, jmethodID methodID, ...)
    2. <type> CallNonvirtual<type>MethodV(JNIEnv *env, jobject
      obj, jclass clazz, jmethodID methodID, va_list args);
    3. <type> CallNonvirtual<type>MethodA(JNIEnv *env, jobject
      obj, jclass clazz, jmethodID methodID, const jvalue *args);

数组的操作

数组是一个很常用的数据类型,在但是在JNI中并不能直接操作jni数组(比如jshortArray,jfloatArray)。使用方法是:

  1. 获取数组长度:jsize GetArrayLength(jarray array)
  2. 创建新数组: ArrayType New<PrimitiveType>Array(jsize length);
  3. 通过JNI数组获取一个C/C++数组:<type>* Get<type>ArrayElements(jshortArray
    array, jboolean *isCopy)
  4. 指定原数组的范围获取一个C/C++数组(该方法只针对于原始数据数组,不包括Object数组):void Get<PrimitiveType>ArrayRegion(JNIEnv
    *env, ArrayType array, jsize start, jsize len, NativeType *buf);
  5. 设置数组元素:void Set<type>ArrayRegion(jshortArray array, jsize
    start, jsize len,const <type> *buf)
    。again,如果是Object数组需要使用:void SetObjectArrayElement(JNIEnv
    *env, jobjectArray array, jsize index, jobject value);
  6. 使用完之后,释放数组:void Release<type>ArrayElements(jshortArray
    array, jshort *elems, jint mode)

有点要说明的:

  1. 上面的3中的isCopy:当你调用getArrayElements时JVM(Runtime)可以直接返回数组的原始指针,或者是copy一份,返回给你,这是由JVM决定的。所以isCopy就是用来记录这个的。他的值是JNI_TURE或者JNI_FALSE
  2. 6释放数组。一定要释放你所获得数组。其中有一个mode参数,其有三个可选值,分别表示:
  3. 0
    • 原始数组:允许原数组被垃圾回收。
    • copy: 数据会从get返回的buffer copy回去,同时buffer也会被释放。
  4. JNI_COMMIT
    • 原始数组:什么也不做
    • copy: 数据会从get返回的buffer copy回去,同时buffer不会被释放。
  5. JNI_ABORT
    • 原始数组:允许原数组被垃圾回收。之前由JNI_COMMIT提交的对数组的修改将得以保留。
    • copy: buffer会被释放,同时buffer中的修改将不会copy回数组!

关于引用与垃圾回收

比如上面有个方法传了一个jobject进来,然后我把她保存下来,方便以后使用。这样做是不行哒!因为他是一个LocalReference,所以不能保证jobject指向的真正的实例不被回收。也就是说有可能你用的时候那个指针已经是个野指针的。然后你的程序就直接Segment Fault了,呵呵。。。

在JNI中提供了三种类型的引用:

  1. Local Reference:即本地引用。在JNI层的函数,所有非全局引用对象都是Local Reference, 它包括函数调用是传入的jobject和JNI成函数创建的jobject。Local Reference的特点是一旦JNI层的函数返回,这些jobject就可能被垃圾回收。
  2. Glocal Reference:全局引用,这些对象不会主动释放,永远不会被垃圾回收。
  3. Weak Glocal Reference:弱全局引用,一种特殊的Global Reference,在运行过程中有可能被垃圾回收。所以使用之前需要使用jboolean
    IsSameObject(jobject obj1, jobject obj2)
    判断它是否已被回收。

Glocal Reference:
1. 创建:jobject NewGlobalRef(jobject lobj);
2. 释放:void DeleteGlobalRef(jobject gref);

Local Reference:
LocalReference也有一个释放的函数:void DeleteLocalRef(jobject obj),他会立即释放Local Reference。 这个方法可能略显多余,其实也是有它的用处的。刚才说Local
Reference会再函数返回后释放掉,但是假如函数返回前就有很多引用占了很多内存,最好函数内就尽早释放不必要的内存。

关于JNI_OnLoad

开头提到JNI_OnLoad是java1.2中新增加的方法,对应的还有一个JNI_OnUnload,分别是动态库被JVM加载、卸载的时候调用的函数。有点类似于WIndows里的DllMain。
前面提到的实现对应native的方法是实现javah生成的头文件中定义的方法,这样有几个弊端:

  1. 函数名太长。很长。。相当长。。。
  2. 函数会被导出,也就谁说可以在动态库的导出函数表里面找到这些函数。这将有利于别人对动态库的逆向工程,因此带来安全问题。

现在有了JNI_OnLoad,情况好多了。你不光能在其中完成动态注册native函数的工作还可以完成一些初始化工作。java对应的有了jint RegisterNatives(jclass
clazz, const JNINativeMethod *methods,jint nMethods)
函数。参数分别是:

  1. jclass clazz,于native层对应的java class
  2. const JNINativeMethod *methods这是一个数组,数组的元素是JNI定义的一个结构体JNINativeMethod
  3. 上面的数组的长度

JNINativeMethod:代码中的定义如下:

1
2
3
4
5
6
7
8
9
10
/*
 * used in RegisterNatives to describe native method name, signature,
 * and function pointer.
 */

typedef struct {
    char *name;
    char *signature;
    void *fnPtr;
} JNINativeMethod;

所以他有三个字段,分别是

字段 含义
char *name java class中的native方法名,只需要方法名即可
char *signature 方法签名
void *fnPtr 对应native方法的函数指针

于是现在你可以不用导出native函数了,而且可以随意给函数命名,唯一要保证的是参数及返回值的统一。然后需要一个const JNINativeMethod *methods数组来完成映射工作。

看起来大概是这样的:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//只需导出JNI_OnLoad和JNI_OnUnload(这个函数不实现也行)
/**
 * These are the exported function in this library.
*/
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved);
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved);

//为了在动态库中不用导出函数,全部声明为static
//native methods registered by JNI_OnLoad
static jint native_newInstance (JNIEnv *env, jclass);

//实现native方法
/*
* Class:     com_young_soundtouch_SoundTouch
* Method:    native_newInstance
* Signature: ()I
*/
static jint native_newInstance
(JNIEnv *env, jclass ) {
  int instanceID = ++sInstanceIdentifer;
  SoundTouchWrapper *instance = new SoundTouchWrapper();
  if (instance != NULL) {
      sInstancePool[instanceID] = instance;
      ++sInstanceCount;
  }
  LOGDBG("create new SouncTouch instance:%d", instanceID);
  return instanceID;
}

//构造JNINativeMethod数组
static JNINativeMethod gsNativeMethods[] = {
      {
          "native_newInstance",
          "()I",
          reinterpret_cast<void *> (native_newInstance)
      }
};
//计算数组大小
static const int gsMethodCount = sizeof(gsNativeMethods) / sizeof(JNINativeMethod);

//JNI_OnLoad,注册native方法。
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
  JNIEnv* env;
  jclass clazz;
  LOGD("JNI_OnLoad called");
  if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
      return -1;
  }
  //FULL_CLASS_NAME是个宏定义,定义了对应java类的全名(要把包名中的点(.)_替换成斜杠(/))
  clazz = env->FindClass(FULL_CLASS_NAME);
  LOGDBG("register method, method count:%d", gsMethodCount);
  //注册JNI函数
  env->RegisterNatives(clazz, gsNativeMethods,
      gsMethodCount);
  //必须返回一个JNI_VERSION_1_1以上(不含)的版本号,否则直接加载失败
  return JNI_VERSION_1_6;
}

实战技巧篇

这里主要是巧用C中的宏来减少重复工作:

迅速生成全名

1
2
3
4
//修改包名时只需要改以下的宏定义即可
#define FULL_CLASS_NAME "com/young/soundtouch/SoundTouch"
#define func(name) Java_ ## com_young_soundtouch_SoundTouch_ ## name
#define constance(cons) com_young_soundtouch_SoundTouch_ ## cons

比如func(native_1newInstance)展开成:Java_com_young_soundtouch_SoundTouch_native_1newInstance即JNI中需要导出的函数名(不过用动态注册方式没太大用了)

constance(AUDIO_FORMAT_PCM16)展开成com_young_soundtouch_SoundTouch_AUDIO_FORMAT_PCM16这个着实有用。

而且如果包名改了也可以很方便的适应之。

安卓的log

1
2
3
4
5
6
7
8
9
10
11
//define __USE_ANDROID_LOG__ in makefile to enable android log
#if defined(__ANDROID__) && defined(__USE_ANDROID_LOG__)
#include <android/log.h>
#define LOGV(...)   __android_log_print((int)ANDROID_LOG_VERBOSE, "ST_jni", __VA_ARGS__)
#define LOGD(msg)  __android_log_print((int)ANDROID_LOG_DEBUG, "ST_jni_dbg", "line:%3d %s", __LINE__, msg)
#define LOGDBG(fmt, ...) __android_log_print((int)ANDROID_LOG_DEBUG, "ST_jni_dbg", "line:%3d " fmt, __LINE__, __VA_ARGS__)
#else
#define LOGV(...)
#define LOGD(fmt)
#define LOGDBG(fmt, ...)
#endif

通过这样的宏定义在打LOGD或者LOGDBG的时候还能自动加上行号!调试起来爽多了!

C++中清理内存的方式

由于C++里面需要手动清楚内存,因此我的解决方案是定义一个map,给每个实例一个id,用id把java中的对象和native中的对象绑定起来。在java层定义一个release方法,用来释放本地的对象。
本地的 KEY-对象 映射 static std::map<int, SoundTouchWrapper*> sInstancePool;

关于NDK

因为安卓的约定是把本地代码放到jni目录下面,但是假如有多个jni lib的时候会比较混乱,所以方案是每一个lib都在jni里面建一个子目录,然后jni里面的Android.mk就可以去构建子目录中的lib了。

jni/Android.mk如下(超级简单):

1
2
LOCAL_PATH := $(call my-dir)
include $(call all-subdir-makefiles)

然后在子目录soundtouch_module中的Android.mk就可以像一般的Android.mk一样书写规则了。

同时记录一下在Andoroid.mk中使用makefile内建函数wildcard的方法。 有时候源文件是一个目录下的所有.cpp/.c文件,这时候wildcard来统配会很方便。但是Android.mk与普通的Makefile的不同在于:

  1. 调用Android.mkmingling的${CWD}并不是Android.ml所在的目录。所以Android.mk中有一个变量LOCAL_PATH
    := $(call my-dir)
    来记录当前 Android.mk所在的目录。
  2. 同时还会把所有的LOCAL_SRC_FILES 前面加上$(LOCAL_PATH)这样写makefile的时候就可以用相对路径了,提供了方便。但是这也导致了坑!

因为1,直接使用相对路径会导致wildcard匹配不到源文件。所以最好这么写FILE_LIST
:= $(wildcard $(LOCAL_PATH)/soundtouch_source/source/SoundTouch/*.cpp)
。然而又因为2,这样还是不行的。所以还需要匹配之后把$(LOCAL_PATH)的部分去掉,因此还得这样$(FILE_LIST:$(LOCAL_PATH)/%=%).

还有个小tip:LOCAL_CFLAGS中最好加上这个定义-fvisibility=hidden这样就不会在动态库中导出不必要的函数了。

附录签名

JAVA中的函数签名包括了函数的参数类型,返回值类型。因此即使是重载了的函数,其函数签名也不一样。java编译器就会根据函数签名来判断你调用的到地址哪个方法。 签名中表示类型是这样的

1.基本类型都对应一个大写字母,如下:

JAVA类型 类型签名
boolean Z
byte B
char C
short S
int I
long J
float F
double D
void V

2.如果是类则是: L + 类全名(报名中的点(.)用(/)代替)+ ; 比如java.lang.String 对应的是 Ljava/lang/String;

3.如果是数组,则在前面加[然后加类型签名,几位数组就加几个[ 比如int[]对应[I,boolean[][]
对应 [[Z,java.lang.Class[]对应[Ljava/lang/Class;

可以通过javap命令来获取签名(javah生成的头文件注释中也有签名):javap -x -p <类全名> 坑爹的是java中并不能通过反射来获取方法签名,需要自己写一个帮助类。
(其实我还写了个小程序可以自动生成签名,和JNI_OnLoad中注册要用到的JNINativeMethod数组,从此再也不用糟心的去写那该死的数组了。LOL~~~)

[全文完]

参考资料

[1] : Oracle java SE documents

[2] : 深入理解Android 卷 1 第二章 ,邓凡平著,机械工业出版社

[3]: Google Android documents – JNI Tips

时间: 2025-01-26 23:53:57

java-基础-JNI本地栈的相关文章

《Android的设计与实现:卷I》——第2章 框架基础JNI

第2章 框架基础JNI JNI(Java Native Interface,Java本地接口)是Java平台上定义的一套标准的本地编程接口.JNI允许Java代码与本地代码互操作,即Java代码可以调用本地代码,本地代码也可以调用Java代码.所谓本地代码指的是用其他编程语言(如C/C++)实现的.依赖于特定硬件和操作系统的代码.通过JNI调用本地代码,可以实现Java语言所不能实现的功能.在Android平台上,Dalvik虚拟机会实现JNI定义的接口. 2.1 JNI在Android系统中所

Java通过JNI调用C语言的方法

JAVA以其跨平台的特性深受人们喜爱,而又正由于它的跨平台的目的,使得它和本地机器的各种内部联系变得很少,约束了它的功能. 解决JAVA对本地操作的一种方法就是JNI. JAVA通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式).通过调用本地的库文件的内部方法,使JAVA可以实现和本地机器的紧密联系,调用系统级的各接口方法. 简单介绍及应用如下: 一.JAVA中所需要做的工作 在JAVA程序中,首先需要在类中声明所调

给Java新手的一些建议——Java知识点归纳(Java基础部分)

写这篇文章的目的是想总结一下自己这么多年来使用java的一些心得体会,主要是和一些java基础知识点相关的,所以也希望能分享给刚刚入门的Java程序员和打算入Java开发这个行当的准新手们,希望可以给大家一些经验,能让大家更好学习和使用Java. 这次介绍的主要内容是和J2SE相关的部分,另外,会在以后再介绍些J2EE相关的.和Java中各个框架相关的内容. 经过这么多年的Java开发,以及结合平时面试Java开发者的一些经验,我觉得对于J2SE方面主要就是要掌握以下的一些内容. 1. JVM相

给Java新手的一些建议——Java知识点归纳(Java基础部分)

写这篇文章的目的是想总结一下自己这么多年来使用java的一些心得体会,主要是和一些java基础知识点相关的,所以也希望能分享给刚刚入门的Java程序员和打算入Java开发这个行当的准新手们,希望可以给大家一些经验,能让大家更好学习和使用Java. 这次介绍的主要内容是和J2SE相关的部分,另外,会在以后再介绍些J2EE相关的.和Java中各个框架相关的内容. 经过这么多年的Java开发,以及结合平时面试Java开发者的一些经验,我觉得对于J2SE方面主要就是要掌握以下的一些内容. 1. JVM相

给Java新手的一些建议----Java知识点归纳(Java基础部分)

学习Java的同学注意了!!!  学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:294919881我们一起学Java! 写这篇文章的目的是想总结一下自己这么多年来使用java的一些心得体会,主要是和一些java基础知识点相关的,所以也希望能分享给刚刚入门的Java程序员和打算入Java开发这个行当的准新手们,希望可以给大家一些经验,能让大家更好学习和使用Java. 这次介绍的主要内容是和J2SE相关的部分,另外,会在以后再介绍些J2EE相关的.和Java中各个

零基础轻松入门——JAVA基础学习

你准备好学习java了吗?了解一下开发java的基本过程以及java的基本特性.与C++做些简单的比较,熟悉两种语言的共性和区别,关于一些java知识体系. Java 的主要特性: 1. Java语言的语法与C语言和C++语言很接近,使得大多数程序员很容易学习和使用.另一方面,Java丢弃了C++中很少使用的.很难理解的.令人迷惑的那些特性,如操作符重载.多继承.自动的强制类型转换.特别地,Java语言不使用指针,而是引用.并提供了自动的废料收集,使得程序员不必为内存管理而担忧. 2.Java是

Java基础之004-面向对象

Java基础之004-面向对象                                        35岁学习Java 1.1 面向对象概念 1.1.1理解面向对象 1)     面向对象是相对面向过程而言 2)     面向对象和面向过程都是一种思想 3)     面向过程 强调的是功能行为 4)     面向对象 将功能封装进对象,强调具备了功能的对象. 5)     面向对象是基于面向过程的. 1.1.2面向对象的特点 1)     是一种符合人们思考习惯的思想 2)     可

Java基础之010-深入理解Java的String类

Java基础之010-深入理解Java的String类                                        老帅        1. 首先String不属于8种基本数据类型,String是一个类.  因为对象的默认值是null,所以String的默认值也是null:但它又是一种特殊的对象,有其它对象没有的一些特性.  2. new String()和new String("")都是申明一个新的空字符串,是空串不是null:  3. String对象实例化   

Java基础总结

问题描述 [size=24px][size=16px][b][b]Java面向对象编程总结Java基础关键字:在JDK1.2之前是48个关键字.在JDK1.5以后加了新增两个关键字.特殊关键字不能做标识符,都是小写abstract---定义抽象类或者抽象方法assert---断言(*)boolean---定义布尔类型的关键字byte---定义字节类型(最小的整型)的关键字break---终止循环catch---捕获异常continue---让循环进入下一趟循环char---定义字符型的关键字cl