第1章,计算机、程序和Java概述
包括【每个java初学者都应该搞懂的问题】
1,java背景
特点:
一次编译,到处运行(Write Once, Run Anywhere.)。 原理:
Java源程序经编译后生成.class字节码文件,.class文件由Java虚拟机(JVM)解释执行。不同的操作系统下,只要有相应的Java字节码解释程序,.class文件就能够运行,这是Java跨平台性的根本。
比较:
c和java编程与执行的过程如下图所示:
分类:
java application:应用程序是独立的程序,能够在任何有JVM的计算机上运行。
java applet: 是一种特殊的Java程序,可以在web浏览器中直接运行。
java servlet: 是一种特殊的Java程序,可以在web服务器上运行,创建动态的web内容。【Java Server Pages(JSP)则可以在服务器端生成动态网页】
2,创建、编译和运行Java程序
示例:
public class Welcome{
public static void main(String [] args){
System.out.println("welcome");
}
}
创建:
将该文件保存为Welcome.java:注意文件名必须与公用类名完全相同,这里是public class Welcome,所以文件名必须是Welcome.java
编译:
再将文件Welcome.java编译为Welcome.class:
javac Welcome.java
运行:
再执行字节码文件Welcome.class:
java Welcome
注意这里不再需要扩展名".class",JVM把命令的第一个参数当做文件名,如果写作 java Welcome.class则实际上会去查找Welcome.class.class。
3,Java语法特点
大小写:
Java是大小写敏感的。
注释:
除了//和/*这两种注释,Java还支持一种称为Java文档注释的特殊注释,以/**开头,*/结尾,主要用于描述类、数据和方法,它可以通过JDK的javadoc命令转换为HTML文件。
/**Title: XXXX DRIVER 3.0
*Description: XXXX DRIVER 3.0
*Copyright: Copyright (c) 2003
*Company:XXX
*
* @author Java Development Group
* @version 3.0
*/
命名习惯:
1、包名中的字母一律小写。如:xxxyyyzzz。
2、类名、接口名应使用名词,每个单词的首字母大写。如:XxxYyyZzz。
3、方法名,第一个单词小写,后面每个单词的首字母大写。如:xxxYyyZzz。
4、变量名,第一个单词小写,后面每个单词的首字母大写。如:xxxYyyZzz。
5、常量名中的每个字母一律大写。如:XXX_YYY_ZZZ。
JDK常用包:
1、java.lang——包含一些Java语言的核心类,如String、Math、Thread、System、Integer类等。
2、java.awt——包含构成抽象窗口工具集的多个类,用来构建和管理应用程序的图形用户界面(GUI)。
3、java.applet——包含applet运行所需的一些类。
4、java.net——包含执行与网络相关操作的类。
5、java.io——包含能提供多种输入/输出功能的类。
6、java.util——包含一些实用工具类。
Java关键字:
1、用于类和接口的声明:class, extends, implements, interface
2、包引入和包声明:import, package
3、数据类型:byte, boolean, char, double, int, long, float, short
4、某些数据类型的可选值:flase, ture, null
5、流程控制:break, case, continue, default, do, else, for, if, return, switch, while
6、异常处理:catch, finally, throw, throws, try
7、修饰符:abstract, final, native, private, protected, public, static, synchronilzed, transient, volatitle
8、操作符:instanceof
9、创建对象: new
10、引用:this, supper
11、方法返回类型:void
12、保留字:const, goto
4,java语法之基本数据类型和基本操作
基本数据类型分类:
字符类型 | char |
布尔类型 | boolean |
数值类型 | 整数类型: byte short int long 浮点类型: float double |
基本数据类型特点:
Java 不是纯的面向对象的语言,不纯的地方就是这些基本数据类型不是对象。当然初期Java的运行速度很慢,基本数据类型能在一定程度上改善性能。 如果你想编写纯的面向对象的程序,用包装器类是取代基本数据类型就可以了。
Java中的基本数据类型不是对象,但有对应的包装类。
Java中的数组是对象。
Java中的字符串是对象。
Java中也内置实现了一些高级的数据结构,比如堆栈、集合、列表。
1、基本类型的存储空间固定:
byte--8位,short--16位,int--32位,long--64位,float--32位,double--64位。这六种数字类型都是有符号的。 固定的存储空间正是Java可移植性、跨平台的原因之一!
2、Java不是纯面向对象的:
基本类型的存在导致了 Java OOP的不纯粹性。因为基本类型不是对象,一切皆对象是个小小的谎言。这是
出于执行效率的权衡。
3、整数类型范围计算公式:
使用公式(-2)^(位数-1)次幂到2^(位数-1)次幂-1确定整数类型(byte、short、int、long)的范围。
例如,byte范围是:(-2)^7~(2)^7-1即-128~127
4、布尔类型范围:
char是16位Unicode字符或者说是16位无符号整数,范围从0到65535。即便如此,可以强制转换非法的数据,如:char c1 = (char) 10000; char c2 = (char) -200;。可以从二进制存储的角度理解这点。
5、浮点类型的科学表示法:
在数学中e代表自然对数(Math.E给出了double值),而在Java中e代表10的幂次。浮点型的数可以这样表示float f = 1e-27f; 代表1乘以10的负27次幂。
6,浮点数的包装类:
BigInteger支持任意精度的整数。BigDecimal支持任意精度的定点数。
4.1 Java中的常量
1、整数常量表示方法:
十进制
十六进制 ——以0x或0X开头
八进制 ——以0开头
长整形 ——以L(l)结尾
2、浮点常量表示方法:
单精度浮点数——后面加f(F)
双精度浮点数——后面加d(D)
注:
a.小数常量的默认类型是double型,所以float类型常量后一定要加f(F)。
b.浮点数常量可以用指数形式表示,如5.022e+23f
3、布尔常量表示方法:
true或false
4、字符常量表示方法:
由英文字母、数字、转义序列、特殊字符等的字符所表示,如'a'、'\t'等。
Java中的字符占两个字节,是用Unicode码表示的,也可以使用'\u'加Unicode码值来表示对应字符,如'\u0027'。
常用的转义字符有:
\r——表示回车
\n——表示换行
\t——表示制表符,相当于Tab键
\b——表示退格键,相当于Back Space键
\'——表示单引号
\”——表示双引号
\\——表示反斜杠“\”
5、字符串常量表示方法:
字符串常量用双引号括起来。“A”是一个字符串,而‘A’是一个字符。
6、null常量表示方法:
null常量表示对象的引用为空。
7、自定义常量:
final double PI=3.14159;
4.2 Java中的变量:
注意:
1、 不要用字符$命名标示符,习惯上$只在机械地产生源码时使用。
4.3 Java算数运算符:
1、除法操作符(/):
整数除法的结果是整数, 小数部分被舍去。例如,5/2得2而不是2.5;-5/2得-2而不是-2.5。
2、求余操作符(%):
只有被除数是负数时,余数才是负的。例如,-7%3得-1。
3、短路操作符:
&&逻辑与 也叫做短路与 因为只要当前项为假,它就不往后判断了,直接认为表达式为假
||逻辑或 也叫做短路或 因为只要当前项为真,它也不往后判断了,直接认为表达式为真
4.4 数据类型之间的转换
1、自动类型转换(隐式类型转换)条件:
a.两种类型彼此兼容
b.目标类型的取值范围要大于源类型
2、强制类型转换(显示类型转换)格式:
目标类型 常量 = (目标类型)值
注: 字符串可以使用加号“+”同其他的数据类型相连而形成一个新的字符串。
3、字符串与Int型之间的转换:
//将整数值(2)转换为字符串("2")
String s = String.valueOf(2);
String ss = Integer.toString(2);
//将字符串("2")转换为整数值(2)
int i = Integer.parseInt(s);
字符串与double型之间转换类似。
4.5 控制台输入与对话框输入
1、控制台输入:
Scanner scanner = new Scanner(System.in);
int intValue = scanner.nextInt();
double doubleValue = scanner.nextDouble();
String string = scanner.next();
2、对话框输入:
import javax.swing.JOptionPane;
public class TestScanner {
public static void main(String[] args) {
String x="Welcome to Java!";
String y="Display the message!";
/*
*消息框输入
*/
//其中x是用于提示信息的字符串,y是用于对话框标题的字符串。
String string = JOptionPane.showInputDialog(null,x,y,JOptionPane.QUESTION_MESSAGE);
//其中x是用于提示信息的字符串。
String string2= JOptionPane.showInputDialog(x);
/*
*消息框输出
*/
//其中x是所要显示的字符串,y是对话框的标题和字符串。
JOptionPane.showMessageDialog(null,x,y,JOptionPane.INFORMATION_MESSAGE);
//其中x是所要显示的字符串。
JOptionPane.showMessageDialog(null,x);
}
}
4.6 产生随机数0~9
(int)(Math.random()*10)
4.7 Java的格式化输出
举例:
int count = 5;
double amount = 45.65;
String s=String.format("count is %d and amount is %f",count,amount);
System.out.println(s);
常用的格式描述符:
描述符 | 输出 | 举例 |
%b | 布尔值 | true或false |
%c | 字符 | ‘a’ |
%d | 十进制整数 | 200 |
%f | 浮点数 | 45.6400000 |
%e | 标准科学计数法形式的数 | 4.556000e+01 |
%s | 字符串 | “Java is cool” |
指定宽度和精度:
举例 | 输出 |
%5c | 输出字符并在它前面加4个空格 |
%6b | 输出布尔值,在false前面加一个空格,在true前面加两个空格 |
%5d | 输出整数项,宽度至少为5。 如果项目数字的位数少于5个,前面加空格,如果项目的位数多于5个,宽度自动增加。 |
%10.2f | 输出宽度至少为10的浮点数,包括小数点和小数点后面两位。 这样,小数点前有7位,数字前面加空格。如果小数点前的位数大于7,宽度自动增加。 |
%10.2e | 输出宽度至少为10的浮点数,包括小数点和小数点后面两位和指数部分。 如果按科学计数法显示的数字小于10,前面加空格。 |
%12s | 输出宽度至少为12的字符串。 如果字符串少于12个字符,前面加空格。如果字符串项目多于12个字符,宽度自动增加。 |
在string中格式化:
String s=String.format("count is %d and amount is %f",5,45.22);
5,Java语法之程序控制流程
5.1、if条件语句
a.if...
if(test)
{
...
}
b.if...else...
if(test)
{
...
}
else
{
...
}
可简写为:变量 = 布尔表达式? 语句1:语句2;
c.if...else if...else...
if(test1)
{
...
}
else if(test2)
{
...
}
else
{
...
}
5.2、switch选择语句
switch(表达式)
{
case 取值1:
语句块1;
break;
...
case 取值n:
语句块n;
break;
default:
语句块n+1;
break;
}
语句从匹配处开始执行, 直到遇到break语句或是达到switch语句的末端。这种情形称作向下贯通行为。
5.3、条件表达式
y=(x>0)?1:-1;
5.4、while循环语句
while(条件表达式)
{
执行语句
}
5.5、do while循环语句
do{
执行语句
}while(条件表达式);
5.6、for循环语句
for(int i = 0; i<10;i++)
{
执行语句
}
5.7、增强for循环
for (循环变量类型 循环变量名称: 要被遍历的对象)
{
执行语句
}
5.8、break与continue语句
a.无标号的break语句会跳出最内层循环(while,do,for,switch),执行下一条语句。
b.无标号的continue语句的作用是跳过当前循环的剩余语句块,接着执行下一次循环。
c.带标号的break或continue可以跳转到指定的循环层次语句位置。
outer:
for(int i = 1; i<10; i++)
{
inner:
for(int j = 1; j<10; j++)
{
if(i*j>50)
break outer;
//continue outer;
System.out.println(i*j);
}
}
6,Java语法之函数
6.1、Java中函数的格式:
1、定义函数的格式
返回值类型 函数名(参数类型 形式参数1,参数类型形式参数2,...)
{
程序代码
return 返回值;
}
2、函数的重载
函数的重载就是在一个类中可以同时存在一个以上的同名函数,只要它们的参数个数或类型不同即可。
6.2、Java中函数的参数都是传值的
众所周知,java和C、C++中都不能通过值传递的方式实现两个整数的交换。
即下面的函数是不能成功交换两个整数的:
public void swap1(int a,int b)
{
//值参数传递不能实现交换两个整数
int t;
t = a;
a = b;
b = t;
}
在C++,可以通过引用或者指针来实现两个整数的交换,实质上是通过地址传递来实现两个整数的交换的。
//c++代码
void swap2(int &a,int &b)//引用传递
{
int temp;
temp = a;
a = b;
b = temp;
}
还可以通过指针来实现两个整数的交换
//C++代码
void swap2(int *a,int *b)//指针,地址传递
{
int temp;
temp = *a;
*a = *b;
* b = temp;
}
那么java中又是如何实现两个整数的交换呢?
方法1:通过数组方式交换(实际Java中数组也是对象,所以引用传递):
这是因为java中数组的传递是引用传递,如果一定要通过一个 method 来实现,下面的形式也许可以:
void swap(int[] a) //传入数组,相当于引用传递
{
if(a==null||a.length!=2)
throw new IllegalArgumentException();
int temp=a[0];
a[0]=a[1];
a[1]=temp;
}
方法2:构造对象
这也是因为对象是以引用传递方式:
class Num
{
int value;
}
public class TestSwap
{
public static void main(String args[])
{
Num a=new Num();
Num b=new Num();
a.value=12;
b.value=234;
System.out.println("a="+a.value+" b="+b.value);
swap(a,b);
System.out.println("a="+a.value+" b="+b.value);
}
public static void swap(Num a,Num b)
{
Num temp=new Num();
temp.value=a.value;
a.value=b.value;
b.value=temp.value;
}
}
而Integer不行,
1、Integer本身是值对象(value object),不能修改它的内容(初始化后不能改变其值)。实际上,串对象String都不能改变;
2、就算Integer本身可以修改,自动装箱、拆箱也不灵:
void swap(Integer a,Integer b)
{ 交换ao和bo中的实际数据 }
int a,b;
swap(a,b); // 自动装箱机制生成了两个临时对象,不过调用返回时不能传回a和b。
最多只能这样:
Integer a1=a;
Integer b1=b;
swap(a1,b1);
a = a1;
b = b1;
7,Java语法之数组
7.1、数组的定义
数组是对象。
如:
int [ ] x = new int[100];
或 :int x [ ] = new int[100];(这种方式主要是为了适应C/C++程序员)
声明一个数组变量:int [ ] x;并不会在内存中给数组分配任何空间,仅创建一个引用数组的存储地址。
数组创建后,其元素赋予默认值,数值型基本数据类型默认值为0,char类型为‘\u0000’,boolean类型为false。
7.2、数组的静态初始化
如:int [ ] x = new int [ ] {3,4,5};
7.3、多维数据
如:
int [ ][ ] xx = new int [3][ ];
xx[0] = new int[3];
xx[1] = new int[2];
xx[2] = new int[3];
7.4、数组的复制
错误:array1 = array2;
该语句并不能将array2的内容复制给array1,而是将array2的引用传给了array1。使用array1 = array2 这个语句之后,array1,array2指向了同一个数组,如下图所示:
这样,array2之前所引用的数组不能再引用了,变成了垃圾,会被JVM自动回收的。所以使用“=”是不能进行数组的复制,它实际上是将=右边的数组的引用传给了=左边的数组变量,达到两个数组变量指向同样的内存地址。
常用的数组复制的方法有以下3种:
1.使用循环语句逐个复制数组的元素(最简单的方法)
2.使用System类中的静态方法arraycopy
public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length)
参数:
src - 源数组。
srcPos - 源数组中的起始位置。
dest - 目标数组。
destPos - 目标数据中的起始位置。
length - 要复制的数组元素的数量。
另外:arraycopy方法没有给目标数组分配内存空间,复制前需要创建目标数组并给它分配内存。复制完成后,各自占有独立空间。
//使用System中的静态方法arraycopy复制数组
int[] targetArray = new int[sourceArray.length];
System.arraycopy(sourceArray, 0, targetArray, 0, sourceArray.length);
3.使用clone方法复制数组
int[] targetArray = new int[sourceArray.length];
/*使用clone方法将int[]型数组,将sourceArray复制到targetArray
*由于clone方法返回值的类型是对象Object,所以要使用(int[])强制转换为int[]
*/
targetArray = (int[])sourceArray.clone();
7.5、数组作为形参
对于基本数据类型的参数,传递的是实参的值。
对于数组类型的参数,传递的是数组的引用。
7.6、Arrays类
进行排序:
java.util.Arrays.sort(array);
二分查找:
java.util.Arrays.binarySearch(array,key);
数组相当判断:
java.util.Arrays.equals(array1,array2);
8,面向对象的概念
8.1、面向对象举例
//TestCircle.java
class Circle
{
//---------------静态数据-----------------
static int numOfObjects=0; //静态数据
//---------------静态方法-----------------
static int getNumOfObjects() //静态方法,其中只能使用静态数据,不能用实例变量radius
{
return numOfObjects;
}
//---------------数据域-----------------
double radius=1.0;
//---------------构造方法1-----------------
//类如果没有声明构造方法,类隐含声明一个方法体为空的无参构造方法,此称之为默认构造函数
Circle()
{
numOfObjects++;
}
//---------------构造方法2-----------------
//1,构造方法可以重载
//2,构造方法无返回值,与类同名
//3,构造方法的调用是在创建一个对象使用new操作符时进行的
//4,构造方法的作用是初始化对象
Circle(double radius)
{
this.radius=radius;
numOfObjects++;
}
double getArea()
{
return radius*radius*Math.PI;
}
}
//文件中唯一的公共类:包含main方法,与文件名同名,public。
public class Main
{
public static void main(String args[])
{
Circle myCircle = new Circle(10);
System.out.println(myCircle.getArea());
}
}
注意:
1,可以把多个类放在一个文件中,但一个文件只能有一个公共类,且该公共类与文件名同名,包含main方法,如这里的TestCircle类。
2,类的数据域如果没有显式赋值,会有默认值:引用型(如String)会赋值null,数值型(如int)会赋值0,boolean型会赋值false,char型会赋值'\u0000';
而Java中方法的局部变量则不会有默认值,这时会报错。
3,如果认为对象不再需要,可以显示赋值null,这样Java虚拟机将自动回收其所占空间。
8.2 、静态变量、静态方法和常量
1,静态变量、静态方法都是属于类而不是属于对象。
2,静态方法只能调用静态变量、静态方法,不能调用实例变量和实例方法;反之,实例方法却可以调用实例变量、实例方法、静态变量、静态方法。这是因为实例变量是各个对象所独有的,直接调用就不知道是哪个对象了。
3,常量的定义是:final double PI=3.14;它仅仅表示的是不可重新赋值。静态常量则可以被大家直接公用:static final double PI=3.14;
8.3、可见性修饰符(访问控制)
1,访问修饰符是指private,默认修饰符(不用修饰符),protected,public四种。这里按照了访问修饰符所规定的被访问范围从小到大的顺序排列。
2,访问修饰符是用来控制被访问范围的。用不同的访问修饰符修饰一个对象是为了使该对象获得不同的被访问范围。访问修饰符所修饰的对象:可以是类的数据成员、类的方法成员或类本身和接口。访问的方式其实也分为三种:在类内直接访问数据成员和调用方法成员,在类外通过类来访问static成员或实例对象来访问非static成员,和以继承的方式访问(这里,“以继承的方式访问”如果深究下去,还可以包括直接访问基类的数据成员和方法成员与对基类的方法成员进行覆盖以达到“多态”效果等方式)。因此,如果用简单的乘法原理来说,可供陈述的情况就有4*3*3=36种之多。
3,可见性修饰符用于类类的数据成员、类的方法成员或类本身和接口。但不能用于方法中的局部变量,否则引起编译错误。
4,举个特别的例子:
大多数情况下,构造方法应该是公用的。但是,如果想防止用户创建类的实例,可以使用私有的构造方法。比如,因为Math类的所有方法都是静态的,没有必要创建实例。一个解决办法就是在类中定义一个虚设的私有的构造方法,Math类中有一个如下所示的私有构造方法,所以,Math类不能实例化:
private Math(){
}
5,类本身只有两种访问控制:public和默认(定义时无访问控制符)。
1、public修饰的类能被所有的类访问。
2、默认修饰的类只能被同一包中所有的类访问。
8.4、Java的可变类和不可变类
1, 可变类 :当你获得这个类的一个实例引用时,你可以改变这个实例的内容。
不可变类:当你获得这个类的一个实例引用时,你不可以改变这个实例的内容。不可变类的实例一但创建,其内在成员变量的值就不能被修改。
2,如何创建一个自己的不可变类(Mutable and Immutable Objects):
.所有成员都是private
.不提供对成员的改变方法,例如:setXXXX
.确保所有的方法不会被重载。手段有两种:使用final Class(强不可变类),或者将所有类方法加上final(弱不可变类)。
.如果某一个类成员不是基本类型(primitive)或者不可变类,必须通过在成员初始化(in)或者get方法(out)时通过深度clone方法,来确保类的不可变。
3,一个类的所有数据都是私有的,且没有修改器,但它不一定是不可变类。这突出反映在2中的第四点,可以有以下一个例子:
public class Student
{
private int id;
private BirthDate birthDate;
public Student(int id, BirthDate birthDate)
{
this.id = id;
this.birthDate = birthDate;
//深度复制:this.birthDate = new BirthDate(birthDate);
}
//Getters
public int getId()
{
return id;
}
public BirthDate getBirthDate()
{
return birthDate;
//深度复制:return new BirthDate(birthDate);
}
}
public class BirthDate
{
private int year;
private int month;
private int day;
public BirthDate(int year, int month, int day)
{
this.year = year;
this.month = month;
this.day = day;
}
//Setters
public void setYear(int year)
{
this.year = year;
}
}
其中,Student类所有数据均私有,且没有Setters,但是使用getBirthDate()方法返回数据域birthDate,这是BirthDate对象的一个引用,而BirthDate有Setters,通过这个引用,可以用setYear()方法改变year,因此也就改变了Student对象的内容,因此,也就没有达到不可变对象的效果。
要使一个类成为不可变类,必须将所有的数据域说明为私有的,并且不含返回引用“非不可变对象数据域的修改器和访问器”。
解决方法是:深度复制(具体见上代码注释部分)
8.5、关键字this
class Foo
{
int i = 5;
static double k = 0;
void setI(int i)
{
this.i = i;
}
static void setK(int k)
{
Foo.k = k;
}
}
【注意】隐藏的实例变量用this来引用,隐藏的静态变量用类名来引用。
另外,可以使用"this(参数)"形式调用其它构造函数。
8.6、垃圾回收:
a.finalize()方法。finalize()方法的作用类似于C++中的析构方法,其是在对象已变成垃圾即将从内存中释放前调用(不是在对象变成垃圾前调用)。这并不是一个可靠的机制。
b.System.gc()。这个方法可以强制启动垃圾回收器来回收垃圾。
9,String类
9.1、String类概述
1、String是final类,不可继承;
2、String类比较字符串相等时时不能用“ == ”,只能用“equals”;
3、String类不可更改,StringBuilder和StringBuffer类能够创建灵活可变的字符串。
9.2、String的字符串常量池
String s1="abc";
String s2="abc";
System.out.println(s1==s2);//true
String s1="abc";
String s2=new String("abc")
System.out.println(s1==s2);//false
String s1="abc";
String s2=s1.intern();
System.out.println(s1==s2);//true
1,Java有一个初始时为空的 字符串池,它由类 String 私有地维护。
字符串对象的创建方式有两种,如下:
String s1 = new String(""); //第一种
String s2 = ""; //第二种
第一种始终不会入池的.
第二种要看情况而定(等号右边如果是常量则入池,非常量则不入池)
例:
String s3 = "a" + "b"; //"a"是常量,"b"是常量,常量+常量=常量,所以会入池.
String s4 = s1 + "b"; //s1是变量,"b"是常量,变量+常量!=常量,所以不会入池.
一旦入池的话,就会先查找池中有无此对象.如果有此对象,则让对象引用指向此对象;如果无此对象,则先创建此对象,再让对象引用指向此对象.
现在考虑上述代码,为了节省Java虚拟机的效率和内存,在 以String s1=“abc”的方式建立String引用声明时,会首先去Java的字符串池中查找是否有这个对象,如果有了,则直接指向它。所以这里建立s1时,字符串池为空,所以在字符串池中建立对象“abc”,并将s1指向它,后又建立s2,会先去字符串池中查找,有了,所以也指向它,这样,s1与s2指向的其实是同一个对象。
而用String s2=new String("abc")方法声明的,都不会去常量池,这时s2单独会生成一个对象,二者指向不同。
java字符串String的实例方法intern()【也叫字符串扣留】就是为解决这个问题而来的,但使用了intern()字符串扣留方法后能使用“==”操作符比较字符串的结果只是该方法的衍生品,它的主要目的是为了减少java程序中对同一字符串对象的重复引用(reference)从而减少内存的使用提升效能,因为使用字符串扣留intern()实例方法就能够确保不存在封装有完全相同字符串的两个String对象,因此所有的String对象封装的字符串对象都唯一。
当调用 intern 方法时,如果池已经包含一个 等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。
9.3、字符串转换
字符串可以使用加号“+”同其他的数据类型相连而形成一个新的字符串。
字符串与Int型之间的转换:
//将整数值(2)转换为字符串("2")
String s = String.valueOf(2);
String ss = Integer.toString(2);
//将字符串("2")转换为整数值(2)
int i = Integer.parseInt(s);
字符串与double型之间转换类似。
10,基本数据类型的包装类
Java为每一种基本数据类型提供类一个包装类:
基本类型 | 包装类 |
char | Character |
boolean | Boolean |
byte | Byte |
short | Short |
int | Int |
long | Long |
float | Float |
double | Double |
11,文本I/O
示例代码如下,编写名为ReplaceText类,用新的文本字符串代替旧的文本字符串,使用方法:java ReplaceText sourceFile targetFile oldString newString
主要使用Scanner来读数据,用PrintWriter来写数据:
import java.io.*;
import java.util.*;
public class ReplaceText {
public static void main(String[] args) throws Exception {
// Check command line parameter usage
if (args.length != 4) {
System.out.println("Usage: java ReplaceText sourceFile targetFile oldStr newStr");
System.exit(0);
}
// Check if source file exists
File sourceFile = new File(args[0]);
if (!sourceFile.exists()) {
System.out.println("Source file " + args[0] + " does not exist");
System.exit(0);
}
// Check if target file exists
File targetFile = new File(args[1]);
if (targetFile.exists()) {
System.out.println("Target file " + args[1] + " already exists");
System.exit(0);
}
// Create input and output files
Scanner input = new Scanner(sourceFile);
PrintWriter output = new PrintWriter(targetFile);
while (input.hasNext()) {
String s1 = input.nextLine();
String s2 = s1.replaceAll(args[2], args[3]);
output.println(s2);
}
input.close();
output.close();
}
}
12,类的继承和多态
12.1、Java中继承的特点
1、使用extends关键字表示继承,通过继承可以简化类的定义。
2、Java只支持单继承,可以有多重继承。
3、 子类继承父类所有的成员变量和成员方法,但不继承父类的构造方法。在子类的构造方法中可以显示用super( args )调用父类的构造方法,如果子类的构造方法中没有显示的调用父类的构造方法,也没有使用this关键字调用重载的其他构造方法,则在产生子类的实例对象时,系统默认调用父类无参数的构造方法,相当于系统默认调用了一次super()。
【注】:一个类中如果定义了有参构造方法而没有重写无参构造方法,无参构造方法将失效。所以我们在定义类时,只要定义了有参构造方法,通常还要定义一个无参构造方法。
4、子类覆盖父类的方法时,覆盖方法必须和被覆盖方法具有相同的方法名称、参数列表和返回值类型,且子类不能使用比父类中被覆盖方法更严格的访问权限。
//基类Fruit
public class Fruit
{
String id; //1
void show(){}; //2
}
//扩展类Apple
public class Apple extends Fruit
{
String id; //3
void show(){}; //4
}
Apple apple = new Apple();//定义apple,类型:扩展类Apple;实例:扩展类Apple
Fruit fruit = apple; //定义fruit,类型:基类fruit; 实例:扩展类Apple。
fruit.id; //1
apple.id //3
fruit.show(); //4
apple.show(); //4
这说明:
父子同名,属性的调用:看实际类型。因为这是静态匹配的,在编译时就做好了。
父子同名,方法的调用:看声明类型。因为这是动态绑定的(多态性),需要看实际对象。
12.2、Java中的对象转换和instanceof
建议把变量声明为父类类型,这样可以接收任何子类类型的值。
Object object1 = new Circle();
Object object2 = new Rectangle();
display(object1);
display(object2);
public static void display(Object object)
{
if(object instanceof Circle)
{
((Circle)object).display();
}
else if (object instanceof Rectangle)
{
((Rectangle)object).display();
}
}
再比如,覆盖Circle中的equals方法:
public boolean equals(Object o)
{
if(o instanceof Circle)
{
return radius == ((Circle)o).radius;
}
return false;
}
13,Object类
Java中所有类(包括用户自己定义的类)都自动继承Object类,即Object类是所有类的父类。
13.1,Object中常用方法
public boolean equals(Object obj) | 比较当前对象与obj是否为同一对象,返回值:ture/false 由于Object中如下示:
|
public String toString() | 返回当前对象的字符串表达形式:
|
public native int hashCode() | 返回对象的Hash码,Hash码是标志对象的唯一值。 约定:若两个对象equals若返回的是true,则对象的散列码必须一致。 所以需要适当修改。 【注】native表示实现方法的程序设计语言不是Java。 |
protected void finalize()throws Throwable | 对象销毁时被调用。 这是Java虚拟机调用的,在自己的程序中不用书写任何调用该方法的代码。 |
protected native Object clone() throws CloneNotSupportedException; | 创建并返回此对象的一个副本. 所有要进行"克隆"的对象所属的类必须实现java.lang.Cloneable接口. |
public final native Class<?> getClass();; | 返回此时的Java的运行时类,这是Java反射机制的一个应用。 一个类在使用时必须先装入。 Java虚拟机在装入类时,创建一个包含类信息的对象。 这个对象时Java.lang.Class的一个实例,它描述有关类的信息。 |
Reflection是Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说"自审",并能直接操作程序的内部属性。例如,使用它能获得 Java 类中各成员的名称并显示出来。 Java 的这一能力在实际应用中也许用得不是很多,但是在其它的程序设计语言中根本就不存在这一特性。例如,Pascal、C 或者 C++ 中就没有办法在程序中获得函数定义相关的信息。
这里只简略减少Class类。
21,抽象类与接口
抽象类
1、抽象类——包含抽象方法的类就叫做抽象类。一个抽象类中可以有一个或多个抽象方法。
2、抽象方法的写法:abstract 返回值类型 抽象方法名 (参数列表)
3、抽象方法用abstract关键字修饰,只需要声明不需要实现。带有抽象方法的类必须也用abstract声明为抽象类。
4、抽象类不能被实例化,其子类必须覆盖所有的抽象方法后才能被实例化,否则其子类也还是抽象类。
接口
1、接口是一种特殊的抽象类,其中只包含常量和方法的定义,而没有变量和方法的实现。
2、接口中定义的变量是全局静态常量,默认是用public static final标记的。
3、接口可以继承接口。如果一个类只实现了接口中定义的部分方法,那么这个类是抽象类。
4、一个类可以在继承一个父类的同时实现一个或多个接口,此时extends关键字必须位于implements关键字之前。
5、设计接口的目的是为了让类不必受限于单一继承的关系,而可以灵活的同时继承一些共有的特性,从而达到多重继承的目的。
对象的多态性
1、子类能够自动转换为父类类型。如:Bird b = new Bird(); Animal a = b;
2、父类转换为子类需要进行强制类型转换,强制类型转换的前提是需要提前知道要转换的父类引用类型对象的本来面目确实是子类类型的。
3、可以使用instanceof操作符判断一个类是否实现了某个接口,或用来判断一个实例对象是否属于某个类。其语法格式为:对象
instanceof 类(或接口)
4、Object类是Java类层中的最高层类,是所有类的超类。自定义类中必须覆盖Object类的equals方法,否则调用的是Object类的equals方法。
22,异常处理
软件开发中一个古老的说法是:80%的工作使用20%的时间。80%是指检查和处理错误所付出的努力。在许多语言中,编写检查和处理错误的程序代码很乏味,并使应用程序代码变得冗长。原因之一就是它们的错误处理方式不是语言的一部分。尽管如此,错误检测和处理仍然是任何健壮应用程序最重要的组成部分。
Java提供了一种很好的机制,用强制规定的形式来消除错误处理过程中随心所欲的因素:异常处理。它的优秀之处在于不用编写特殊代码检测返回值就能很容易地检测错误。而且它让我们把异常处理代码明确地与异常产生代码分开,代码变得更有条理。异常处理也是Java中唯一正式的错误报告机制。
第一部分 异常
1、抛出异常。所有的标准异常类都有两个构造器:一个是缺省构造器,一个是带参数的构造器,以便把相关信息放入异常对象中。
throw new NullPointerException();
throw new NullPointerException("t = null");
2、如果有一个或者多个catch块,则它们必须紧跟在try块之后,而且这些catch块必须互相紧跟着,不能有其他任何代码。C++没有这样的限制,所以C++的异常处理处理不好就会写得很乱,抛来拋去的。
3、使用try块把可能出现异常的代码包含在其中,这么做的好处是:处理某种指定的异常的代码,只需编写一次。作业没写完的同学到走廊罚站去,这符合我们处理问题的方式,不用挨个地告诉。
4、无论是否抛出异常,finally块封装的代码总能够在try块之后的某点执行。
例子:
try {
return ;
}
finally{
System.out.print("You can't jump out of my hand!");
}
甚至你在try块内用return语句想跳过去都不可以!finally内的输出语句还是执行了!别想逃出我的手掌心!
5、catch块和finally块是可选的,你可以只使用try。但是这么做有意思吗?
6、推卸责任。Java允许你推卸责任,没有必要从相应的try块为每个可能的异常都编写catch子句。Java2类库中很多方法都会抛出异常,就是是把异常处理的权限交给了我们用户。毕竟,Java不知道你的自行车被偷了之后,你会去报案还是会忍气吞声自认倒霉,或者偷别人的自行车。我们需要这种处理异常的自由度。
7、调用栈。调用栈是程序执行以访问当前方法的方法链。被调用的最后一个方法在栈的顶部,它将被最先执行完毕,然后弹出;第一个调用方法位于底部,也就是 main函数。在catch子句中使用printStackTrace()方法打调用栈信息是比较常用的定位异常的方法。
printStackTrace ()继承自Throwable。
8、异常的传播。在一个方法A中,如果一个异常没有得到处理,它就会被自动抛到调用A方法的B方法中。如果B方法也没有处理这个异常,他就会被继续依次向上抛,直到main方法。如果main也没有理会它,那么异常将导致JVM停止,程序就中止了。你被同学揍了,先去告诉老师。老师不理你你就去告诉教导处主任,教导处主任也不管那只能告诉校长,校长还不管!没有比他更大的了,于是你崩溃了,学业中止了……
下面这段程序记录了悲惨的辍学历史:
class ExceptionDemo {
static void student() throws Exception{
teacher();
}
static void teacher() throws Exception{
schoolmaster();
}
static void schoolmaster() throws Exception{
throw new Exception();
}
public static void main(String[] args) {
try {
student();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
输出结果是:
java.lang.Exception
at ExceptionDemo.schoolmaster(ExceptionDemo.java:9)
at ExceptionDemo.teacher(ExceptionDemo.java:6)
at ExceptionDemo.student(ExceptionDemo.java:3)
at ExceptionDemo.main(ExceptionDemo.java:13)
可以看出函数的调用栈,一级一级地哭诉……
9、异常的层次结构及Error。
Object
Throwable
Error Exception
Throwable继承自Object,Error和Exception继承自Throwable。Error比较特殊,它对应于我们常说的不可抗拒的外力,房屋中介的合同上总有一条,如遇不可抗拒的外力本合同中止,返还乙方押金。我不安地问:不可抗拒的外力指什么?中介回答:比如战争、彗星撞击地球等。
对Java来说Error是指JVM内存耗尽等这类不是程序错误或者其他事情引起的特殊情况。一般地,程序不能从Error中恢复,因此你可以能眼睁睁地看着程序崩溃而不必责怪自己。严格来讲,Error不是异常,因为它不是继承自Exception。
10、谁之错?一般地,异常不是我们程序员的错,不是程序设计上的缺陷。比如读取一个重要文件,这个文件被用户误删了;正上着网呢,网线被用户的宠物咬断了。为了程序的健壮性,我们尽量考虑出现可能性大的异常,并处理,但我们不能穷尽。
11、异常的捕获之一。catch子句的参数是某种类型异常的对象,如果抛出的异常是该参数的子类,那么这个异常将被它捕获。也就是说被抛出的异常不会精确地寻找最匹配的捕获者(catch子句),只要是它的继承结构的直系上层就可以捕获它。 按照这个逻辑,catch(Exception e) 不就能捕获所有的异常吗?事实上,确实如此。但是一般地,不建议使用这种一站式的异常处理。因为这样就丢失了具体的异常信息,不能为某个具体的异常编写相应的异常处理代码,失去了异常处理的意义。从哲学角度来讲,具体问题要具体分析,能治百病的万能药一般都是无效的保健品。
Java在此处为什么这么设计呢?因为有另一种机制的存在,请看下条分解。
12、异常的捕获之二。当抛出一个异常时,Java试图寻找一个能捕获它的catch子句,如果没找到就会沿着栈向下传播。这个过程就是异常匹配。 Java规定:最具体的异常处理程序必须总是放在更普通异常处理程序的前面。这条规定再合理不过了,试想如果把catch(Exception e)放在最上面,那么下面的catch子句岂不是永远不能执行了?如果你非要把更普遍的异常处理放在前面,对不起,通不过编译!虽然编译器不会这样报错: “It is so stupid to do like that!”……
13、捕获或声明规则。如果在一个方法中抛出异常,你有两个选择:要么用catch子句捕获所有的异常,要么在方法中声明将要抛出的异常,否则编译器不会让你得逞的。
方案一:处理异常
void ex(){
try{
throw new Exception();
} catch (Exception e) {
e.printStackTrace();
}
}
方案二:抛出去
void ex() throws Exception{
throw new Exception();
}
比较一下行数就知道了,在代码的世界里推卸责任也是那么简单,一个throws关键字包含了多少人生哲理啊……现实生活中我们有很多角色,儿女、父母、学生、老师、老板、员工……每个人都占了几条。可是你能尽到所有责任吗?按照古代的孝道,父母尚在人世就不能远行。
各种责任是有矛盾的,顾此失彼啊。
但是这条规则有个特例。一个继承自Exception名为RuntimeException的子类,也就是运行时异常,不受上述规则的限制。下面的代码完全能编译,只不过调用之后在运行时会抛出异常。
void ex(){
throw new RuntimeException();
}
14、throw和thrwos关键字。throw用在方法体中抛出异常,后面是一个具体的异常对象。throws用在方法参数列表括号的后面,用来声明此方法会抛出的异常种类,后面跟着一个异常类。
15、非检查异常。RuntimeException、Error以及它们的子类都是非检查异常,不要求定义或处理非检查异常。Java2类库中有很多方法抛出检查异常,因此会常常编写异常处理程序来处理不是你编写的方法产生的异常。这种机制强制开发人员处理错误,使得Java程序更加健壮,安全。
16、自定义异常类型。觉得现有的异常无法描述你想抛出的异常,ok!Java允许你自定义异常类型,只需要继承Exception或者它的子类,然后换上有个性的名字。
class NotEnoughMoney extends Exception {
public NotEnoughMoney() {}
public NotEnoughMoney(String msg) { super(msg); }
}
希望大家在生活里不要抛出类似的异常。
17、重新抛出异常。一个很无聊的话题,纯粹的语法研究,实际意义不大。当catch子句捕获到异常之后可以重新抛出,那么它所在的方法必须声明该异常。
void ex() throws Exception{
try {
throw new Exception();
}
catch (Exception mex) {
throw me;
}
}
18、异常处理机制的效率。待补充……
19、终止与恢复模型。异常处理理论上有两种模型:
一、终止模型。错误很关键且无法挽回,再执行下去也没意义,只能中止。“罗密欧,我们分手吧!”“好吧,朱丽叶!”
二、恢复模型。经过错误修正重新尝试调用原来出问题的方法。“罗密欧,我们分手吧!”“朱丽叶,我错了!请再原谅我一次吧!”“好的,再原谅你最后一次!”
显然我们更喜欢恢复模型,但在实际中,这种模式是不易实现和维护的。
例子:用户输入了非法的字符,分别按照两种模式处理
一、终止模型。输出出错信息而已,一旦用户手一抖眼一花你的代码就崩溃了
double number;
String sNumber = "";
try {
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
sNumber = bf.readLine();
number = Double.parseDouble(sNumber);
} catch (IOException ioe) {
System.err.println("some IOException");
} catch (NumberFormatException nfe) {
System.err.println(sNumber + " is Not a legal number!");
}
//System.out.println(number);
二、恢复模型。小样!不输入正确的数据类型就别想离开!
double number = 0;
String sNumber = "";
while(true){
try {
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
sNumber = bf.readLine();
number = Double.parseDouble(sNumber);
break; //如果代码能执行到这一行,就说明没有抛出异常
} catch (IOException ioe) {
System.err.println("some IOException");
} catch (NumberFormatException nfe) {
System.err.println(sNumber + " is Not a legal number!");
}
}
System.out.println(number);
直到用户输入正确的信息才会被该代码放过。这是一种简单的恢复模型的实现,挺耐看的,我很喜欢!
20、try、catch、finally内变量的作用域和可见性。
在try块内定义的变量,它在catch或者finally块内都是无法访问到的,并且在整个异常处理语句之外也是不可见的。 补充一点初始化:第一个例中最后一句被注释掉了。number是在运行时由用户输入而初始化的,但是在编译时刻并没有初始化,编译器会抱怨的。
21、输出异常信息。捕捉到异常之后,通常我们会输出相关的信息,以便更加明确异常。
catch (Exception mex) {
System.err.println("caught a exception!");
}
用标准错误流System.err比System.out要好。因为System.out也许会被重定向,System.err则不会。
22、更高级的话题我会补充上的,但是我的肚子抛出了Hungry异常,我必须catch然后调用eat()方法补充能量。昨晚的鱿鱼盖浇饭很好吃…
读取配置文件:
package myapp.src;
import java.io.IOException;
import java.util.Properties;
/**
* @author xing.gexing E-mail:xing.gexing@aliyun-inc.com
* @version 创建时间:Oct 30, 2012 9:46:53 PM 类说明
*/
public class ReadConf {
private String filename;
private Properties properties;
/**
* @param args
*/
public ReadConf() {
this.filename = "config.property";
this.properties = new Properties();
try {
properties.load(ReadConf.class.getResourceAsStream(filename));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* @return the filename
*/
public String getFilename() {
return filename;
}
/**
* @param filename
* the filename to set
*/
public void setFilename(String filename) {
this.filename = filename;
}
/**
* @return the properties
*/
public Properties getProperties() {
return properties;
}
/**
* @param properties
* the properties to set
*/
public void setProperties(Properties properties) {
this.properties = properties;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
ReadConf readConf = new ReadConf();
System.out.println(readConf.getProperties().getProperty("endpoint"));
}
}
配置文件:
endpoint=http://10.230.205.88:61085