初学Java,按照天码营教程学习。
一、基本概念
对象:
一辆汽车、一篇博客、一个人,对应到软件系统中都是一个对象;而对象具有自己的状态和行为。以汽车为例,汽车就是一个对象,一辆汽车可以拥有油耗、颜色、轴距、速度等状态属性,一辆汽车可以拥有启动、行驶、加减速、刹车等方法。
类:
类描述一类对象的状态和行为的模板。可以把类想象成一个汽车设计图,根据这个汽车设计图生产出来的每一辆汽车就是类的一个实例,这些实例就称之为对象。
成员变量:
一个对象的状态是通过成员变量的值决定的,通常我们也称之为属性。
成员方法:
方法定义了类的行为,一个类可以有很多方法,在方法中可以编写逻辑,操纵数据,执行特定动作。我们有时也称方法为函数。
创建和使用对象:
我们回到HelloWorld
类中,定义好Post
类(即制造出模板)之后,我们可以在main
方法中来创建和使用Post
对象(即按照模板生产产品)了。
局部变量:
局部变量与成员变量不同,它不属于某个对象,是一个临时变量,当方法执行结束,变量就不再起作用了。一个方法中声明的变量都属于局部变量。
包:
在开发过程中,类的数量会越来越多,我们可以通过包(Package)来组织类。包的命名一般以一个组织的域名的反写开头。比如天码营的域名是tianmaying.com
,那么天码营开发的代码,包名一般就会以com.tianmaying
开头。
二、基本数据类型
变量与数据类型
byte :数据类型是8位、有符号整数
short:数据类型是16位、有符号整数
int :数据类型是32位、有符号整数
long :数据类型是64位、有符号整数
float :数据类型是单精度、32位的浮点数
double:数据类型是单精度、64位的浮点数
boolean:表示一位的信息
char:一个单一的16位Unicode字符
进制:
前缀0b
表示二进制,0
表示八进制,而前缀0x
代表十六进制。
类型转换:
一个浮点数字面量默认是double
类型,如果要定义float
类型则要在字面量最后添加f
或者F,例:float a=1.23//编译出错
占用内存空间小的类型可以自动转换为占用空间大的类型,强制转换如c中做法。
包装类:
包装类可以认为是将基本类型转换成的一个引用类型。
访问变量类型信息
System.out.println("int类型的二进制位数:" + Integer.SIZE);
System.out.println("int类型的最小值:Integer.MIN_VALUE=" + Integer.MIN_VALUE);
System.out.println("int类型的最大值:Integer.MAX_VALUE=" + Integer.MAX_VALUE);
注:将一个整数和字符串通过+
号拼接起来,整数可以自动转换为字符串。
数组:
int[] anArray;
anArray = new int[10];//初始化一个长度为10的整形数组
anArray[0] = 100;//初始化第一个变量
字符串:
String str = "abc";
字符串可以通过+
和+=
操作符进行拼接,例:String str2 = str1 + "def"; str1 += "def";
字符串和基本数据类型也能通过+
进行拼接操作,例:String str = "a=" + a;
获取控制台输入:
Scanner scanner = new Scanner(System.in);
Scanner
对象可以读取用户在命令行输入的各种数据类型,比如:
通过nextInt()
方法读取整数
通过nextFloat()
方法读取浮点数
通过next()
方法读取一个字符串
通过nextLine()
读取完整的一行,即用户输入回车键之前的所有输入信息(不包括回车键),以String
返回
例:
import:
任何一个package
中的类,如果需要访问另外一个package
中的类,就需要import
。
import java.util.*; //表示将java.util
包下的所有类都引入进来。
三、运算符
str.equals("abcd");//比较str和"abcd"是否相同
>>> 二进制右移补零操作符
>> 右移
运算符,
若操作的值为正,则在高位插入0;若值为负,则在高位插入1;
其余与c相同
四、程序控制流
与c用法一样
五、定义类
关于类和Java文件
一般情况下,都是一个类一个.ja
va
文件。如果一个.java
文件里有多个类,只可能有一个public
的类,而且文件名必须和public
类同名。如果文件里所有类都不是public
的,那么文件名和任意一个类同名即可
成员变量的可见性:
定义成员变量时,可以用private
、protected
或者public
进行修饰,可以控制外部的可见性
private
:表示任何其他类不能直接访问该成员变量,只有该类自身可以访问
protected
:表示只有该类自身及其子类,以及与该类同一个包的类,可以访问该成员变量
public
:表示任何类都可以直接访问该成员变量
没有修饰:表示同一个包的类可以访问该成员变量
类似于成员变量,方法也可以控制可见性,规则相同
方法重载:
方法的返回值和参数构成了方法的签名。
方法名相同,但是参数不同的现象,称之为方法重载。
例:
定义构造器
构造器(一种方法/函数)用于创建对象。Post post = new Post();Post()即为默认构造器。
构造器的规则:
方法名必须和类名相同、不允许定义返回类型。
没有显式定义构造器的情况下,编译器会生成默认构造器。
构造器也可以重载:
当一个类有多个构造器时,一个构造器调用另外一个构造器,可以使用this。
this(title);表示调用第一个构造器。
六、创建和使用类
创建对象:
例:Car myCar = new Car();
Car myCar表示声明了一个Car
类型的变量myCar
,即myCar
是一个引用类型变量。
new关键字表示创建一个对象
Car()是构造器的调用,对创建的对象进行初始化
堆和栈的区别:
在方法中定义的基本类型变量和引用类型变量,其内存分配在栈上,变量出了作用域(即定义变量的代码块)就会自动释放
堆内存主要作用是存放运行时通过new
操作创建的对象
图中0x6E34
是我们假设的内存地址。myCar
作为一个引用类型变量保存在栈中,你可以直观地认为myCar
变量保存的就是所创建对象在堆中的地址0x6E34
,即myCar
引用了一个对象,这正是引用类型变量这个叫法的原因;而堆中则保存着的对象本身,包含了其成员变量,如speed
、color
和engine
。
一个对象的成员变量,如果是引用类型的变量的话,比如engine
,则该成员变量可以引用到堆中的其它对象。
堆中的对象如果没有任何变量引用它们时,Java就会适时地通过垃圾回收机制释放这些对象占据的内存。你可以认为没有任何引用的对象(即没有任何引用类型的变量指向它),这个对象就成为"垃圾",Java虚拟机就会清理它们,为将来要创建的对象腾出空间。
引用类型和基本类型的区别:
引用类型存储在栈中,基本类型存储在堆中。
访问对象属性:
在类的内部可以访问自身的属性,在类的内部也可以通过this
来访问自身的属性。
在外部(即其它类中)也可以访问一个类的非private
属性,通过对象名.
属性名的方式进行访问。
访问对象方法:
方法可以在一个类内部进行调用,在类的内部也可以通过this
来访问自身的方法。
在外部(即其它类中)也可以访问一个类的非private
方法,通过对象名.
方法名的方式进行访问。
方法的返回和参数:
与c相同
方法的调用过程:
基本类型参数:
传参即是实参的值赋给形参。对于基本类型的形参,在方法内部对形参的修改只会局限在方法内部,不会影响实参。(和c一样)
引用类型参数:
引用类型的实参传入方法中时,是将对象的引用传入,而非对象本身。因此,在方法执行时,实参和形参会引用到同一个对象。
在方法结束时,形参占据的内存虽然会被释放,但是通过形参对对象进行的修改则不会丢失,因为对象依然保存在堆中。(类似c中指针作为参数)
虽然实参指向的对象可以在方法调用时被修改,但是实参本身的值不会发生改变。
初始化成员变量
初始化成员变量,一般通过构造器完成。
通过final方法赋值:
通过构造块初始化:
编译器会将初始化构造块的代码会自动插入到在每个构造器中。
七、字符串操作
空字符串,String str="";
String的format方法:
String formatString = "我的名字是%s,我今年%d岁,我的爱好是%s";
String output = String.format(formatString, name, age, hobby);
System.out.println(output);
String formatString = "我的名字是%s,我今年%d岁,我的爱好是%s";
System.out.format(formatString, name, age, hobby);
常见的格式化字符:
String的常用操作:
比较:
查找:
替换:
截取:
StringBuffer:
表示可变长的和可修改的字符序列,可以进行插入或者追加字符序列、翻转字符序列等操作。
StringBuffer
必须通过构造函数进行初始化,它有三个构造方法:
StringBuffer()
:默认的构造方法预留16个字符的空间
StringBuffer(int size)
:第二种形式接收一个整数参数,显示的设置缓冲区的大小
StringBuffer(String str)
:第三种形式接收一个String
参数,设置StringBuffer
对象的初始内容,同时多预留16个字符的空间
String和StringBuffer的区别:
String
类型和StringBuffer
类型的主要区别其实在于String
是不可变的对象, 因此在每次对String
对象进行改变的时候其实都会生成一个新的String
对象。所以经常改变内容的字符串最好不要用String
,因为每次生成对象都会对系统性能产生影响。
使用StringBuffer
类则会对StringBuffer
对象本身进行操作,而不是生成新的对象。所以在一般情况下我们推荐使用StringBuffer
,特别是字符串对象经常改变的情况下。StringBuffer
类的操作性能要优于String
。
八、静态变量和静态方法
介绍:当为一个类创建实例时,每个不同的实例的成员变量都有自己特定的值。有时我们希望定义一个类成员,使其作为该类的公共成员,所有实例都共享该成员变量,此时需要使用static
关键字。
访问:由于静态变量属于类,与类的实例无关,因而可以直接通过类名访问这类变量(类名.变量或类名.方法)。
声明成static
的方法有几条限制:仅能调用其他的static
方法。只能访问static
数据。不能以任何方式引用this
或super。
static修饰代码块:有static
修饰的代码块称为静态代码块。它独立于类成员,可以有多个,JVM加载类的时候会执行这些静态代码块,如果static
代码块有多个,JVM则会按照它们在类中出现的顺序依次执行它们,且每个代码块只能执行一次。我们可以利用静态代码块可以对一些static
变量进行赋值。
九、泛型
T为类型参数,具体类型取决于参数的类型,注:传入的参数类型必须是引用类型。
多个泛型参数:
public class Triple<A, B> {
private A a;
private B b;
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
}
Triple<String, Integer, Float> triple = new Triple<String, Integer, Float>();
triple.setA("something");
triple.setB(1);
泛型方法:
与泛型类不同的是泛型方法需要在方法返回值前用尖括号声明泛型类型名,这样才能在方法中使用这个标记作为返回值类型或参数类型。
十、Java集合
Java的容器:
Java的集合(容器),可以帮助我们方便的组织和管理一组对象。
ArrayList是Java提供的一个集合类,用以保存一个元素序列,并且可以进行元素的访问、插入和删除操作。(类似c语言数组)
ArrayList可以用add方法添加元素,remove方法删除元素,遍历可以用for的特殊用法或者Iterator实现。
Map:
每一组映射作为一个<键,值>对保存在Map
容器中。Map
和List
一样是一种接口,它的实现HashMap
类,是我们最常使用的一种容器。
Map具有两个泛型参数,第一个是键的类型,第二个是值的类型。类型不能是原生类型,必须是引用类型。
put方法可以增加一个键值对;get方法可以根据键得到值;remove方法可以删除键对应的元素。
十一、封装与继承
封装:
封装是一种隐藏信息的技术,是将一个系统中的结构和行为通过类来划分的过程。即通过定义一组类,将特定的数据组合到某一个类中,形成一个整体,将该隐藏的数据进行保护,只对外暴露这些数据的访问的方法。
封装代码有两个好处:
代码使用者无需考虑实现细节就能直接使用它,同时不用担心不可预料的副作用,别人不能随便修改内部结构
在外部接口保持不变的情况下,自己可以修改内部的实现
想要让外部访问该成员变量的话,可以给这些私有成员变量添加public
的访问方法:
public long getId() {
return id;
}
在Eclipses中我们可以直接通过菜单栏的【Source】->【Generate Getters and Setters】来生成。
继承:
继承是一种类和类之间的关系,是面向对象系统的基石。继承表明为一个"是一种"(is-a)的关系,为在现实中有很多这样的例子:学生是一种人;树是一种植物,矩形是一种图案。
我们可以把共性的结构和行为放到父类中,子类可以通过继承复用父类中的代码,并且根据自己的需要进行扩展。
在Java中,使用extends
关键字表示继承关系。
Java中的继承是单继承的,也就是说一个子类只能继承一个父类。子类会继承父类中的除构造函数以外的所有非private
成员方法,以及所有非private
成员变量。
父类中所有非private属性和非private方法,子类都可以直接使用。
this表示对当前对象的引用,super表示对父类对象的引用。
在子类的构造函数中,一般第一条语句是super();
,表示调用父类构造函数。也可以调用父类有参数的构造函数,比如super(name);
。
如果一个类的构造函数的第一语句既不是this()
也不是super()
时,就会隐含的调用super()
。生成子类对象或者实例时,Java默认地首先调用父类的不带参数的构造方法,接下来再调用子类的构造方法,生成子类对象。
方法覆盖:
如果子类中有和父类中非private的同名方法,且返回类型和参数表也完全相同,就会覆盖从父类继承来的方法。当两个方法形成重写关系时,可以在子类中通过super
关键字调用父类被重写的方法。
final关键字:
一个变量可以声明为final
,这样做的目的是阻止它的内容被修改。这意味着在声明final
变量的时候,必须初始化它(在这种用法上,final
类似于C/C++中的const
)。
final变量的所有字符选择大写是一个普遍的编码约定,用final修饰的变量在实例中不占用内存,它实质上是一个常数。
被final修饰的方法可以被子类继承,不能被子类的方法覆盖,因此,如果一个类不想让它的子类覆盖它的某个成员方法,就可以在该成员方法前面加上final
关键字
final不能修饰构造方法。由于父类中的private
成员方法是不能被子类覆盖的,所有有private
限制的成员方法默认也是final
的。
使用final
修饰方法除了不想让子类覆盖之外,还有一个原因就是高效,Java编译器在遇到final
关键字修饰的方法时会使用内联机制,省去函数调用的开销,大大提高执行效率。
由final
修饰的类是不能继承的,因此,如果设计类的时候不想让该类被继承,就在该类的前面加上final
关键字。
十二、抽象类与接口
抽象类:
在类定义的前面增加abstract
关键字,就表明一个类是抽象类。由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。
abstract
关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。声明抽象方法会带来以下两个结果:
如果一个类包含抽象方法,那么该类必须是抽象类。
任何子类必须重写父类的抽象方法,否则就必须声明自身为抽象类
一般情况下,我们将一个类声明为abstract
的,是因为它包含了没有具体实现的抽象方法,只能交给特定的子类去实现。
接口:
接口(Interface)是一组抽象方法的集合。接口中定义的方法没有方法体,它们以分号结束。
接口也和抽象类一样,无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。编写接口和编写类的方式是大体上是类似的,一个接口可以有多个方法,代码保存在以接口命名且以.java
结尾的文件中。接口使用interface
关键字进行定义。
接口中的方法都是外部可访问的,因此我们可以不需要用public
修饰。
接口中也可以声明变量,一般是final和static类型的,要以常量来初始化,实现接口的类不能改变接口中的变量。
接口访问权限有两种:public权限和默认权限,如果接口的访问权限是public的话,所有的方法和变量都是public。默认权限则同一个包内的类可以访问。
一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。
接口实现:
类使用implements
关键字实现接口。在类声明中,implements
关键字放在class
声明后面。口支持多重继承,即一个类可以同时实现多个接口。
我们可以使用接口类型来声明一个变量,那么这个变量可以引用到一个实现该接口的对象。
抽象类和接口的比较:
相同点:
- 都不能被实例化
- 都包含抽象方法,这些抽象方法用于描述系统能提供哪些服务,而这些服务是由子类来提供实现的
- 在系统设计上,两者都代表系统的抽象层,当一个系统使用一棵继承树上的类时,应该尽量把引用变量声明为继承树的上层抽象类型,这样可以提高两个系统之间的松耦合
不同点:
- 在抽象类中可以为部分方法提供默认的实现,从而避免在子类中重复实现它们;但是抽象类不支持多继承。接口不能提供任何方法的实现,但是支持多继承。
- 接口代表了接口定义者和接口实现者的一种契约;而抽象类和具体类一般而言是一种is-a的关系,即两者在概念本质上是不同的。
十三、异常处理
异常:
异常定义了程序中遇到的非致命的错误,比如如程序要打开一个不存的文件、网络连接中断、除零操作、操作数越界、装载一个不存在的类等情况。
try/catch语句:
运行结果:
java.lang.ArithmeticException: / by zero
at com.tianmaying.HelloWorld.main(HelloWorld.java:6)
program is still running here!
当try
代码块中的语句发生了异常,程序就会跳转到catch
代码块中执行,执行完catch代码块中的程序代码后,系统会继续执行catch
代码块之后的代码,try代码块中发生异常语句后的代码则不会再执行。
异常发生时,系统会将代码行号,异常类别等信息封装到一个对象中,并将这个对象传递给catch
代码块,catch
代码块是以下面的格式出现的。
catch(Exception e) {
e.printStackTrace();
}
每个try语句必须有一个或多个catch语句对应,try代码块与catch代码块及finally代码块之间不能有其他语句。
throws关键字:
如果一个方法中的语句执行时可能生成某种异常,但是并不能确定如何处理,则此方法应声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。也就是如果程序中的异常没有用try/catch
捕捉异常以及处理异常的代码,我们可以在程序代码所在的函数(方法)声明后用throws
声明该函数要抛出异常,将该异常抛出到该函数的调用函数中。
Exception类是java.lang.Throwable
类的子类。Exception类是所有异常类的父类。
使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类即可。
catch多个异常:
Java中可以使用一个try
后面跟着多个catch
来捕捉多个异常,每一个catch
可以处理一个不同的异常类型。程序会按照catch的顺序依次判断异常是否匹配。
关于异常,在继承中还要注意两点:
- 一个方法被覆盖时,覆盖它的方法必须扔出相同的异常或异常的子类。
- 如果父类抛出多个异常,那么重写(覆盖)方法必须扔出那些异常的一个子集,也就是说,不能扔出新的异常。
finally关键字:
在try/catch
语句后,我们还可以有个finally
语句,finally
语句中的代码块不管异常是否被捕获总是要被执行的。finally还有一个特殊之处在于,即使try
代码块和catch
代码块中使用了return
语句退出当前方法或break
跳出某个循环 ,相关的finally
代码块都要执行。finally
中的代码块不能被执行的唯一情况是:在被保护代码块中执行了System.exit(0)
。
十三、Java IO
字节流:
字节流的最顶层是两个抽象类:InputStream
和OutputStream
,其他关于处理字节的类都是它们的子类,这些子类对不同的外设进行处理,例如磁盘文件,网络连接,甚至是内存缓冲区。
以下为常见字节流处理类
抽象类InputStream
和 OutputStream
中定义两个关键的抽象方法read()
和write()
,它们分别对数据的字节进行读写,其子类重载完成特定输入输出方式的对应实现。
字符流:
字符流的两个顶层抽象类是Reader
和Writer
,分别定义了关键方法read()
和write()
,表示对字符的读写。
标准流:
Java提供了以下的三种标准流:
- Standard Input: 用以将数据输入给用户的程序,通常键盘作为标准输入流,表示为
System.in
,其类型是InputStream
- Standard Output:用以输出用户程序产生的数据,通常控制台屏幕作为标准输出流,表示为
System.out
,其类型是PrintStream
- Standard Error: 这是用来输出用户产生的错误数据,通常控制台屏幕作为标准错误流,表示为
System.err
,类型和System.out
相同是PrintStream
控制台输入输出:
文件的输入输出:
输入的两种方式:
InputStream f = new FileInputStream("D:/java");
File f = new File("D:/java"); InputStream f = new FileInputStream(f);
输出:
byte bWrite [] = {10,20,30,40,50}; OutputStream os = new FileOutputStream("test.txt"); for(int x = 0; x < bWrite.length ; x++){ os.write( bWrite[x] ); // writes the bytes } os.close(); InputStream is = new FileInputStream("test.txt"); int size = is.available(); for(int i = 0; i< size; i++){ System.out.print((char)is.read() + " "); } is.close();
File:
File 类用于进行文件(以及目录)的相关操作,位于java.io
包中。对一个文件进行操作,可以创建一个File
对象。
File类的方法: