0. 引言
在恶补完C/C++的基础知识后,终于开始了Java知识的进阶学习,说来惭愧,因为项目需要在看完郭神的《第一行代码》和一些Android入门书籍后就开始撸安卓了,并且在自己成功写出几个小APP后居然开始沾沾自喜起来,然而在进一步开发过程中发现自己的Java知识实在是太low,很多程序都是在参考CSDN和各大论坛大佬经验基础上搞出来的,大部分只是知道使用方法或者照葫芦画瓢弄出来,当时也是项目赶进度觉得实现功能就好,有些基础原理也是一知半解。现在当静下心来回看做过的大大小小项目和作品发现,不但漏洞百出而且还不知道如何改进,在此真的要感谢任玉刚大神的博文给我的启发,推荐大家有空也可以去看看链接在此:http://blog.csdn.net/singwhatiwanna/article/details/49560409
他的学习路线可以说是很好的规划了从小白到项目工程师的进阶路线,我觉得不管是从事Android还是其他类型的开发工作都适合作为参考。说了这么多希望给自己一个警醒作用吧,技术容不得半点马虎,脚踏实地做的才是真才实干,故从这篇博文开始记录自己温习Java的过程和心得,其中也是参考学习了许多书籍和博文,再次感谢大佬们的分享,废话不多说,就此开始。
1. Java简介
Java是一种解释型语言,相对于C/C++的编译型语言来说,虽然写出来的程序效率低,执行速度略慢,但是它是通过不同平台的解释器对Java代码进行解释,来实现“一次编写,到处执行”的目标。为了这一目标,依靠现代计算机的发展,牺牲那么一点速度并无大碍。
所有的Java程序文件的后缀都用该是.java
,而任何一个.java
程序首先必须经过编译(javac
)后形成.class
的文件(字节码文件),而后在JVM(JAVA虚拟机)上运行。在Java所有程序都是在JVM上运行的,Java虚拟机读取并处理经过编译的与平台无关的字节码.class
文件。Java解释器负责将Java虚拟器的代码在特定的平台运行。你在什么平台(例如linux,Dos,Win等等)用相应平台的JAVA虚拟机就可以运行Java程序了,这种效率虽然没有直接运行操作系统上高,但是随着技术的发展,这些都可以忽略。
2. 数组及变量
2.1 数组创建与简化写法
//完整模式
double[] a; //声明数组
a=new double[N]; //创建数组
for(int i=0;i<N;i++) //初始化数组
a[i]=0.0;
//简化写法
double[] a=new double[N];
执行动态初始化时,只需要指定数组的长度,系统将按照如下规则分配初始值
- 数组类型是整数类型(byte,short,int,long),则数组元素的值是0;
- 数组元素是浮点类型(float,double),则数组元素都是0.0;
- 数组元素是字符类型(char),则数组元素值是’\u0000’;
- 数组元素是布尔类型(boolean),则数组元素是flase;
- 数组元素是引用类型(类,接口,数组),则数组元素都是null.
2.2 典型应用
数组可以使用类似a.length获取数组a[]的长度,而它最后一个元素总是a[a,length-1].
//找数组最大元素
double max=a[0];
for(int i=0;i<a.length;i++)
if(a[i]>max)max=a[i];
//颠倒数组元素的顺序
int N=a.length;
for(int i=0;i<N/2;i++)
{
double temp=a[i];
a[i]=a[N-i-1]
a[N-i-1]=temp;
}
//神奇的矩阵相乘a[][]*b[][]=c[][]
int N=a.length;
double[][] c=new double[N][N];
for(int i=0;i<N;i++)
for(int j=0;j<N;j++)
{
for(int k=0;k<N;k++)
c[i][j]+=a[i][k]*b[k][j];
}
2.3 起别名
注意:数组名表示的是整个数组,如果我们将一个数组变量赋于另一个变量,那么变量将会指向同一个数组。例如:
int[] a=new int [N];
...
a[i]=1234;
...
int b[]=a;
...
b[i]=5678; //此时a[i]的值也会变成5678
这种情况叫做起别名,如果你想要将数组复制一份,你应该重新声明创建并初始化一个新的数组,然后将原数组里面的值一个个复制到新的数组中。
为什么会有起别名的现象呢?归根到底是因为第一句话用关键字new为其开辟了堆内存,而其中的内容1234就在a所指向的堆内存,但是b只是有栈内存它指向了a的堆内存里面的数据,所以可以获取和修改堆内存和a的效果一样。
2.4 数据类型转换
在开发中经常会用到将String数据转换成基本数据类型的方法。下面进行简单总结
类型种类 | 函数名称 |
---|---|
Integer | public static int parseInt(String s) |
Double | public static double parseDouble(String s) |
Boolean | public static boolean parseBoolean(String s); |
那么如何将基本数据类型转换为String型数据呢?
方法一:任何数据类型+字符串都会变成字符串但是这样会产生垃圾不建议使用;
方法二:利用String类中提供的方法:valueOf();
int num=100;
String str=String.valueOf(num); //字符串转换int型
3. 字符串
Java的String是一种重要的抽象数据类型,一个String值是一串可以由索引访问的char值。
3.1 Java的字符串API(部分)
名称 | 说明 |
---|---|
String() | 创建一个空字符串 |
int length() | 字符串长度 |
int charAt(int i) | 第i个字符 |
int compareTo(String t) | 比较字符串 |
3.2 类型转换
名称 | 说明 |
---|---|
int parseInt(String s) | 将字符串s转化为整数 |
String toString(int i); | 将整数i转化为字符串 |
我们一般很少使用toString( )方法,因为Java在连接字符串的时候会自动将数据类型转换成字符串:如果加号+的一个参数是字符串,那么Java会自动将其他参数都转换为字符串。
3.3 字符串比较
由于String实现了Comparable接口,因此程序还可以通过String提供的compareTo()方法来判断两个字符串的大小关系,当两个字符串相等时,会返回0;
String s1="abc";
String s2="aaa";
if(s1.compareTo(s2)==0)
System.out.printIn("S1和S2相等“);
字符串==
比较问题,看起来没有任何问题,但是==
比较的不是内容,而只是地址的数值内容,这样的操作一般判断两个不同名的对象是否指向同一内存空间的操作上。
如果需要比较字符串,还可以使用String类里面定义的方法,内容比较操作(区分大小写)语法如下:
public boolean equals(String str);
String aaa="hello";
String bbb="hello";
System.out.printIn(aaa.equals(bbb));
程序的执行结果是:true
这个方法专门用来判断字符串关系的。
3.4 字符串查找
名称 | 说明 |
---|---|
public boolean contains(String s) | 判断指定的内容是否存在 |
public boolean startsWith(String prefix ) | 判断是否以指定字符串开头 |
punlic boolean startsWith(String prifix,int toffset) | 从指定位置开始判断是否以指定字符串开头 |
3.5 字符串替换
名称 | 说明 |
---|---|
public String replaceAll(String regex,String replacement) | 用新的内容替换全部指定内容 |
public String repalceFirst(String regex,String replacement) | 替换首个满足条件的内容 |
例如:
String str ="hello world";
String resultA=str.replaceAll("l","_"); //将字符串str中的所有l字符换成_
String resultB=str.replaceFirst("l","_"); //将字符串str中的第一个l字符换成_
Sysytem.out.printIn(resultA);
Sysytem.out.printIn(resultB);
输出:he_ _o wor_d
he_lo world
3.6 字符串拆分
名称 | 说明 |
---|---|
public String[] split(String regex) | 按照制定的字符串进行全部拆分 |
public String[] split(String regex,int limit) | 按照指定的字符串进行部分拆分,由limit决定拆分几个 |
例如下面这样:
String str ="hello world java";
String result[]=str.split(" "); //注意引号里面有个空格
for(int i=0;i<result.length;i++)
{
System.out.print(result[x]+"、");
}
运行结果: hello、world、java
注意:如果写成String result[]=str.split(“”);则将拆分成一个个字符
4. 修饰符
4.1关于static
先看下面这段代码
public class error
{
int num1=num2+2;
int num2=2;
}
显然这段代码是错误的,“非法前向引用”,但如果像以下这样
public class right
{
int num1=num2+2;
static int num2=2;
}
为什么这样写就是对的呢?因为num2变量是个类变量,而num1是普通变量,而类变量的初始化总是处于实例变量之前,所以其实num2的初始化是在之前的。
4.2 final修饰符
时刻记住:
- final可以修饰变量,被final修饰的变量被赋初始值后,不能对它重新赋值;
- final可以修饰方法,被final修饰的方法不能被重写;
- final可以修饰类,被final修饰的类不能派生子类
- final一般变量,定义时必须显示指定初始值。
5. 类与对象
5.1 关于构造函数顺序
这方面Java表现的和C++差不多,顺序一般都是:
1.默认要执行父类无参数构造函数
2.可以显示调用父类带参数的构造函数
3.子类的各种构造函数
注意:super用于显示调用父类的构造器,系统将根据super里传递的参数来确定调用父类的那个构造函数。另外super函数只能在构造函数中调用,且必须在第一行,最多只能调用一次。
5.2 封装型
所有在类中定义的属性都最好使用private声明,如果需要被外部使用,那么按照要求定义相应的getter,setter方法。
class Book{
String title;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
double price;
public void getInfo()
{
System.out.println(title+","+price);
}
}
public class Hello {
public static void main(String arg[])
{
Book bookA=new Book();
bookA.setTitle("Java"); //此时可以修改Book里面的私有成员了
bookA.setPrice(20.0);
bookA.getInfo();
}
}
在eclipse中可以右键 —> Source –> Generate Getters and Setters
6. 关于内存
6.1 内存管理
记录一些Java内存管理的小技巧
6.1.1 尽量使用直接量
当时用如字符串,还有Byte,short,integer,long,float,double…包装类实例时,尽量不要采用new的方式创建对象,而应该直接量来创建他们。
String str="Hello";
String str=new String("Hello");
上面的方法就比下面的好,而且JVM的字符缓存池还会缓存这个字符串,而下面的程序不但执行了此操作,还包含一个char[]数组,里面放了相应字符。
6.1.2 避免在经常调用的方法,循环中创建Java对象
public static void mian(String[] args)
{
for(int i=0;i<10;i++)
{
Object obj=new Obj();
}
}
6.2 堆内存和栈内存
堆内存:保存对象的真正数据,都是每一个对象的属性内容。
栈内存:保存的是一块堆内存的空间地址,为了方便理解,可简单理解成保存对象的名称。
如果要开辟堆内存空间,只能依靠关键字new进行开辟,也就是说new出现就开辟堆内存。
class Book
{
String title;
double price;
public void getInfo()
{
System.out.println(title+ "," +price);
}
}
public static void main(String arg[])
{
Book bookA=new Book(); //实例化对象(开辟了堆内存)
Book bookB=bookA; //引用传递 只开辟了栈内存bookB指向了bookA,
//并可以用A里面的元素
bookA.title="Java";
bookA.price=20.0;
bookB.title="Android";
bookB.getInfo();
}
此程序输出Android,20.0
,说明bookB指向bookA的堆内存,规定一块堆内存可以同时被多个栈内存所指向,但是一块栈内存只能指向一块堆内存。加入此时bookB也也被开辟了堆内存仍然指向A的堆内存那么原来B的堆内存就会被Java的垃圾收集器回收掉。
7.陷阱
7.1 表达式的陷阱
Java是强类型语言,就是所有变量必须先声明,然后才能使用,声明的时候还必须指定数据类型,一旦某个变量的数据类型被确定下来,那这个变量将永远只能接收该类型的值。
例如
short value=5;
value=value-2;
这句话居然可能报错,因为value-2的类型将自动提升为int类型,所以将一个int类型的值给short是错的。
short value=5;
value-=2;
这样写是对的,因为使用了-=复合赋值运算符。
对于复合赋值运算符,语句
a += b;
并不等价于
a = a+b;
而等价于这个
a =(int)(a+b);
也就是说符合赋值运算符会自动进行强制类型转换,但同时也会带来一些风险,例如在进行
int,long,float,double等操作会有可能出现”数据截断”的现象。
7.2 Switch的陷阱
default分支会永远执行吗
显然不是,只有在前面分支都没有执行的时候,才会执行default;
switch表达式支持的类型
对于switch表达式来说,他只能是以下五种类型:
byte,short,int,char,enum;
8. 关于异常
Exception用于捕获处理所有异常,可以简化异常处理操作,例如:
try
{
int x=2;
int y=0;
y=x/y; //此处产生异常;
}
catch(Exception e) //用以处理所有异常
{
//输出异常信息
}
9. 声明与命名规范
9.1 关于包、类相关
关于punlic class与class声明类的区别
public class:文件名(.java)必须与类的名称一致,在一个.java文件里面只能有一个public class声明,如果一个类需要被不同的包访问,那么一定要有public class;
class:文件名和类名可以不一样,并且一个java文件里面可以有多个class类定义,如果一个类用class定义则说明可以被本包访问。
9.2 命名规范
种类 | 规则 |
---|---|
类名称 | 每一个单词的开头首字母大写,例如:TestDemo |
变量名称 | 第一个单词的首字母小写,之后每个单词的首字母大写,例如:studentName |
方法名称 | 第一个单词的首字母小写,之后每个单词的首字母大写,例如:printInfo() |
常量名称 | 每个字母大写,例如:FLAG |
包名称 | 所有字母小写,例如:com.aaa.bbb |
其他(待更新)
1.泛型
一种特别的Java机制叫做泛型,也叫做参数化类型。在每份API中,类名后的记号将Item定义为一个类型参数,它是一个象征性的占位符,表示的是某种数据类型。比如可以将Stack理解为某种元素的栈。在实现栈的时候我们并不知道Item的具体类型,但却可以用来处理任意类型的数据,使用泛型的代码很容易调试,后面将经常用来处理算法,这里不再赘述。
2.可迭代集合类型,例如
Queue<Transaction> collection=new Queue<Transaction>();
//如果集合是可以迭代的,则可以一行语句打印出交易的列表
for(Transaction t : collection)
{
StdOut.printIn(t);
}
这种语句叫做foreach语句,刚好复习到C++的STL库里面也有for_each,有兴趣的同学可以去看看我的另一篇博文关于C++算法的。http://blog.csdn.net/kilotwo/article/details/78838422
这种语句可以将for语句看作对于集合中的每个元素执行以下代码段的意思,这段代码不需要知道集合的任何细节,他只想逐个处理集合中的元素,在做网络爬虫中也曾经用到这个按照标签爬取网页上的内容。
注意:不能用foreach循环访问字符串,因为String没有实现Iterable接口。
关于foreach语句进行补充,该循环用于遍历数组,集合的每个元素,使用改循环遍历的时候,不需要获得数组和集合的长度,他会自动遍历每个元素。例如:
List<String> books =new ArrayList<String>();
books.add("我爱Java");
books.add("我爱算法");
books.add("我爱你");
//实用foreach循环遍历数组
for(String book :books)
{
book="asdasd";
System.out.printIn(book);
}
System.out.printIn(books);
可以虽然看到循环内对每次都赋值了,但由于循环计数器book只是个中间变量,所以不会改变集合元素本身,运行后可以看到数组内的元素仍然没变。
虽然obj都是局部变量,但是由于这段循环回避创建10次,因此系统 需要不断为这10个对象分配内存空间,执行初始化操作。在接下来的不断分配回收操作中,程序性能将受到影响。
3.线性表的分析
从某种程度来看,线性表是数组的加强,线性表比数组多了几个功能:
1.线性表的长度可以改变,但Java的数组长度是固定的;
2.线性表可以插入元素,数组无法插入元素;
3.线性表可以删除元素,数组无法删除元素,数组只能将指定元素赋为null,但各种元素依然存在;
4.线性表提供方法来搜索指定元素的位置,但数组一般不提供该方法;
5.线性表提供方法来清空所有元素,但数组一般不提供该方法。
对于大部分Java程序猿来说,经常在使用List,Java的List接口就代表了线性表,线性表的两种实现分别是ArrayList和LinkedList,其中LinkedList还是一个双向链表。