Java基础
序
此文章为JavaSE基础篇学习内容
一、介绍
1、Java白皮书
Java“白皮书”的关键术语:简单性、面向对象、分布式、健壮性、安全性、体系结构中立、可移植性、解释型、高性能、多线程、动态性
2、Java版本
Java版本:1.0、1.1、1.2、1.3、1.4、5.0(泛型类、“for each”、自动装箱、枚举、静态导入 )、6、7、8(“函数式”编程)、 9(模板)
3、JDK
JDK:Java Development Kit(Java开发工具包):编写Java程序的程序员使用的软件。
JRE:Java Runtime Environment(Java运行时环境):运行Java程序的用户使用的软件。
JDK是整个JAVA的核心,包括了Java运行环境JRE(Java Runtime Envirnment)、一堆Java开发工具(javac/java/jdb等)和Java基础的类库(即Java API 包)。
JVM(Java Virtual Machine)就是我们常说的java虚拟机,它是整个java实现跨平台的最核心的部分,所有的java程序会首先被编译为.class的类文件,这种类文件可以在虚拟机上执行。也就是说class并不直接与机器的操作系统相对应,而是经过虚拟机间接与操作系统交互,由虚拟机将程序解释给本地系统执行。
只有JVM还不能成class的执行,因为在解释class的时候JVM需要调用解释所需要的类库lib,而jre包含lib类库。在JDK下面的的jre目录里面有两个文件夹bin和lib,在这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和 lib和起来就称为jre。
总而言之,我们利用JDK(调用JAVA API)开发了属于我们自己的JAVA程序后,通过JDK中的编译程序(javac)将我们的文本java文件编译成JAVA字节码,在JRE上运行这些JAVA字节码,JVM解析这些字节码,映射到CPU指令集或OS的系统调用。
二、基础
1、类结构和main函数
一个.java文件只能有一个public class ClassName,且类名和文件名相同。可以有其他的类,但所有的代码都应该放在类内。
psvm的写法固定,只是一个启动函数,寄存在public class内。一个类文件最多只能有一个main函数,没有的就不能主动执行,可以被别人调用执行。
Java文件必须以.java命名。
2、基本类型和运算符
基本类型:boolean、byte、short/int/long、float/double、char
byte存储8位有符号整数,-128 ~ 127
int默认是32位
float是32位,赋值时必须带f
char是一个单一的16位Unicode字符(\u0000 ~ \uffff),char类型可以存储任何字符 char a = 97 -> ‘a’
3、选择和循环结构
if-else
switch-case
int a = 1;
switch(a){
case 1: System.out.println("1");
break; //满足后跳出case到default执行
case 2: System.out.println("2");
break;
case 3: System.out.println("3");
break;
default: System.out.println("All need"); //不需要break,执行后自动跳出switch
}
//输出结果是:
1
All need
while / do-while
for,int i int num : nums
break:中断循环并退出
continue:跳出本次循环,继续下次循环
4、自定义函数
自定义函数必须放在类的范围内,通常建议方法是public,直接调用的为static。
重载函数:函数名相同,函数参数的个数或者类型必须有所不同,返回值不同不是重载。
三、对象与类
1、面向对象思想
对象:属性+方法,面向对象像盖浇饭和面向过程像蛋炒饭。
对象是一个变量、类就是类型(规范,是定义),从万千对象中抽取共性。类规定了对象应该有的属性内容和方法,对象是类的具体实现(可以有自己的特性)。
2、面向对象四大特性
面向对象的四大特征:抽象、封装、继承、多态
抽象:将一类对象的共同特征总结出来构造类的过程。包括数据抽象和行为抽象两方面,抽象只关注对象的哪些属性和行为,并不关注这此行为的细节是什么。
封装:一个类想要访问另一个类的属性不能直接访问,需要实例化对象。封装给对象提供了隐藏内部特性和行为的能力,对象提供一些能这被其它对象访问的方法来改变它内部的数据。即把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。
继承:继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。继承的过程,就是从一般到特殊的过程。
多态:多态是同一个行为具有多个不同表现形式或形态的能力。允许相同或不同子类型的对象对同一消息作出不同响应。继承、重写、父类引用指向子类对象。
3、类和对象
创建对象即创建引用指向内存空间(堆)中的对象,对象赋值是引用赋值,而基本类型是直接值拷贝。
new出对象后,内部属性值为默认值(0、false、‘\u0000’、0、0.0f、0.0d)。而函数内的局部变量,编译器不会给默认值,需要初始化后才可使用。类的成员变量会被默认初始化。
对象赋值和基本类型赋值。Java中所有的变量传递都是值传递,当变量是对象时则传递对象的地址,基本类型则传递值。当传递的是对象变量时,因为是指向同一个地址,所以修改其内容时就是在修改原来的值。而传递的是基本类型时,修改的值是副本。
在Java中,任何对象变量的值都是对某个对象的引用,new返回值也是一个引用。 对象变量并没有实际包含一个变量,它只是引用一个变量。
所有的Java对象都存储在堆中。
4、构造函数
构造函数:每个Java类都必须有构造函数,构造函数的名称必须和类名一样,且没有返回值(啥也别写)。没有显示定义,Java编译器自动产生一个空的无形参构造函数。一个类可以有多个构造函数,只要形参列表不相同即可。
每个子类的构造函数的第一句话,都默认调用父类的无参数构造函数super()。除非子类的构造函数第一句话是super,而且super必须放在第一条。
Java具有内存自动回收机制,当变量退出其生命周期后,JVM会自动回收所分配的对象的内存。对象回收效率依赖于垃圾回收器GC。
5、信息隐藏和this
信息隐藏原则:类的成员属性private,提供public的get和set方法修改成员属性的值。
this:this负责指向本类中的成员变量、本类中的成员方法、代替本类的构造函数。
四、继承
1、继承
继承:从多个类别(对象)中提取共性,形成了父类。其他类继承父类,成为子类,也拥有这些共性。子类继承父类所有的属性和方法(但不能直接访问private)。
单根继承原则:每个类都只能继承一个类,如果不写extends都默认继承java.lang.Object类,Object类里面默认就有clone,equals,finalize,getClass,hashCode,toString方法。
每个子类的构造函数的第一句话,都默认调用父类的无参数构造函数super(),除非子类的构造函数第一句话是super,而且super语句必须放在第一条,不会出现连续两次super语句。
2、抽象类和接口
类:属性(0或多个)+方法(0或多个)。一个完整的类:所有的方法都有实现(方法体)。类可以没有方法,但是有方法就肯定要实现,这才是一个完整的类。一个完整的类才可以被实例化。
抽象类:若类中存在方法没有方法体,那所在的类就被定义为抽象类。抽象类关键字abstract声明,抽象方法也加abstract关键字。
子类在调用父类时 无论自己有没有构造方法都会先去执行父类无参的函数。哪怕父类是抽象类。虽然抽象类不能被实例化,但是可以在构造方法中初始化一些参数;也可以在子类中调用父类的构造方法。
public abstract class Shape{
int area;
public abstract void calArea();
}
抽象类也是类,更像一个规范/声明。一个类继承于抽象类,就不能继承于其他的(抽象)类。子类可以继承于抽象类,但一定要实现父类全部的抽象方法,否则就也是抽象类。
接口:如果类的所有方法都没有实现,那么这个类就算是接口interface。类只可以继承extends一个类,但是可以实现implements多个接口,继承和实现可以同时。接口设计是为了弥补单根继承的不足。接口里可以定义变量,但是常量,不能有实例字段。
类实现接口,就必须实现所有未实现的方法,否则只能成为一个抽象类。
public interface Sports
{
public void setHomeTeam();
public void setVisitingTeam(String name);
}
抽象类和接口:抽象类有构造函数,接口没有构造函数。抽象类可以有main,也能运行,接口没有main函数,抽象类方法可以有private / protected,接口方法都是public,不用写public默认就是。
Arrays类中的sort方法可以对对象数组进行排序,但要求对象所属的类必须实现Comparable接口,即实现compareTo方法,并返回一个int。
3、转型、多态和契约设计
类转型:子类可以转换成父类,而父类不可以转为子类。因为父类有的子类都有,而子类有的父类不一定有。子类转型为父类后,调用普通方法仍然是子类的方法。
Human obj1 = new Man(); //OK, Man extends Human
HumanMan obj2 = new Human(); //illegal, Man is a derived class Human
//父类可以转为子类有一种情况例外,就是这个父类本身就是从子类转化过来的
Man obj3 = (Man)obj1; //OK, Human obj1 = new Man();即obj1本身就是Man来的。
Man o1 = new Man();
Human o2 = (Human)o1;
Man o3 = (Man)o2;
//o1 == o2 : true, o1 == o3 : true 因为比的是引用是指向的内存地址
public class AnimalTest {
public static void haveLunch(Animal a) {
a.eat();
}
public static void main(String[] args) {
Animal[] as = new Animal[4];
as[0] = new Cat();
as[1] = new Dog();
as[2] = new Cat();
as[3] = new Dog();
for(int i=0;i<as.length;i++) {
as[i].move(); //调用每个元素的自身的move方法
}
for(int i=0;i<as.length;i++) {
haveLunch(as[i]);
}
haveLunch(new Cat()); //Animal a = new Cat(); haveLunch(a);
haveLunch(new Dog());
haveLunch(
new Animal()
{
public void eat() {
System.out.println("I can eat from an anonymous class");
}
public void move() {
System.out.println("I can move from an anonymous class");
}
});
}
}
输出结果:
Cat: I can move
Dog: I can move
Cat: I can move
Dog: I can move
Cat: I can eat
Dog: I can eat
Cat: I can eat
Dog: I can eat
Cat: I can eat
Dog: I can eat
I can eat from an anonymous class
多态:类型转换带来的作用就是多态。子类继承父类的所有方法,但子类可以重新定义一个名字、参数和父类一样的方法即重写(覆盖,不是重载)。
契约设计:类不会直接使用另外一个类,而是采用接口的形式,外部可以空投这个接口下的任意子类对象。
五、static、final和常量设计
1、static
static:变量、方法、类、匿名代码块
static变量:静态变量只依赖于类存在,即通过类名即可方法,不需要实例化对象。类与其他所有的对象实例关于static变量的值都共享在一个共同的空间(栈)。
static方法:静态方法也无需通过对象来引用,而通过类名可以直接引用。静态方法中只能使用静态变量和引用静态方法。
public class Potato {
static int price = 5;
String content = "";
public Potato(int price, String content)
{
this.price = price;
this.content = content;
}
public static void main(String[] a)
{
System.out.println(Potato.price); //Potato.content wrong
System.out.println("----------------------------------");
Potato obj1 = new Potato(10,"青椒土豆丝");
System.out.println(Potato.price);
System.out.println(obj1.price);
System.out.println("----------------------------------");
Potato obj2 = new Potato(20,"酸辣土豆丝");
System.out.println(Potato.price);
System.out.println(obj2.price);
}
}
输出结果是:
5
--------------------------------
10
10
--------------------------------
20
20
public class StaticMethodTest {
int a = 111111;
static int b = 222222;
public static void hello()
{
System.out.println("000000");
System.out.println(b);
//System.out.println(a); //error, cannot call non-static variables
//hi() //error, cannot call non-static method
}
public void hi()
{
System.out.println("333333");
hello(); //ok, call static methods
System.out.println(a); //ok, call non-static variables
System.out.println(b); //ok, call static variables
}
public static void main(String[] a)
{
StaticMethodTest.hello();
//StaticMethodTest.hi(); //error, 不能使用类名来引用非静态方法
StaticMethodTest foo = new StaticMethodTest();
foo.hello(); //warning, but it is ok
foo.hi(); //right
}
}
public class StaticBlockTest {
public static void main(String[] args) {
System.out.println("0000000000000000000");
// TODO Auto-generated method stub
StaticBlock obj1 = new StaticBlock();
StaticBlock obj2 = new StaticBlock();
}
}
class StaticBlock
{
//staticl block > anonymous block > constructor function
static
{
System.out.println("22222222222222222222");
}
{
System.out.println("11111111111111111111");
}
public StaticBlock()
{
System.out.println("33333333333333333333");
}
{
System.out.println("44444444444444444444");
}
}
输出结果是:
00000000000000000000
22222222222222222222
11111111111111111111
44444444444444444444
33333333333333333333
11111111111111111111
44444444444444444444
33333333333333333333
2、单例模式
设计模式:在软件开发过程中,经过验证的,用于解决在特定环境下的、重复出现的、特定问题的解决方案。创建型、结构型和行为型。
单例模式:Singleton,限定某一个类在整个程序运行过程中,只能保留一个实例对象在内存空间。外部不允许new(private 构造函数),内部只允许new一次(static 对象)。
采用static来共享对象实例。采用private构造函数,防止外界new操作。提供public的获得实例方法getInstance()给外部获取统一的内部对象。
public class Singleton {
private static Singleton obj = new Singleton(); //共享同一个对象
private String content;
private Singleton() //确保只能在类内部调用构造函数
{
this.content = "abc";
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public static Singleton getInstance() {
//静态方法使用静态变量
//另外可以使用方法内的临时变量,但是不能引用非静态的成员变量
return obj;
}
public static void main(String[] args) {
Singleton obj1 = Singleton.getInstance();
System.out.println(obj1.getContent()); //abc
Singleton obj2 = Singleton.getInstance();
System.out.println(obj2.getContent()); //abc
obj2.setContent("def");
System.out.println(obj1.getContent());
System.out.println(obj2.getContent());
System.out.println(obj1 == obj2); //true, obj1和obj2指向同一个对象
}
}
输出结果是:
abc
abc
def
def
true
3、final
final:类、方法、字段
final的变量不能再次赋值,如果是基本类型的变量,不能修改其值。如果是对象实例,那么不能修改其指针(但是可以修改对象内部的值)。
final的类不能被继承,父类中如果有final的方法,子类中不能改写此方法。
4、常量设计
常量设计:常量即不会修改的变量,final:不能修改;static:只要一份。
Java中的常量:public static final UPPER_BOUND = 0; 必须初始化。接口内定义的变量默认是常量。
常量池:Java为很多基本类型的包装类 / 字符串都建立常量池。在常量池中,相同的值只存储一份,节省内存,共享访问。
5、常量池
基本类的包装类:Boolean,Byte,Short,Integer,Long,Character,Float,Double
Float和Double没有缓存(常量池),Java为常量字符串都建立常量池缓存机制。
基本类型的包装类和字符串有两种创建方式:常量式、new对象
//常量式赋值创建,放在栈内存(将被常量化)
Integer a = 10;
String b = "abc";
//new对象进行创建,放在堆内存(不会常量化)
Integer c = new Integer(10);
String d = new String("abc");
Integer
public class BoxClassTest {
public static void main(String[] args)
{
int i1 = 10;
Integer i2 = 10; // 自动装箱
System.out.println(i1 == i2); //true
// 自动拆箱 基本类型和包装类进行比较,包装类自动拆箱
Integer i3 = new Integer(10);
System.out.println(i1 == i3); //true
// 自动拆箱 基本类型和包装类进行比较,包装类自动拆箱
System.out.println(i2 == i3); //false
// 两个对象比较,比较其地址。
// i2是常量,放在栈内存常量池中,i3是new出对象,放在堆内存中
Integer i4 = new Integer(5);
Integer i5 = new Integer(5);
System.out.println(i1 == (i4+i5)); //true
System.out.println(i2 == (i4+i5)); //true
System.out.println(i3 == (i4+i5)); //true
// i4+i5 操作将会使得i4,i5自动拆箱为基本类型并运算得到10.
// 基础类型10和对象比较, 将会使对象自动拆箱,做基本类型比较
Integer i6 = i4 + i5; // +操作使得i4,i5自动拆箱,得到10,因此i6 == i2.
System.out.println(i1 == i6); //true
System.out.println(i2 == i6); //true
System.out.println(i3 == i6); //false
}
}
String
public class StringNewTest {
public static void main(String[] args) {
String s0 = "abcdef";
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
String s4 = new String("abc");
System.out.println(s1 == s2); //true 常量池
System.out.println(s1 == s3); //false 一个栈内存,一个堆内存
System.out.println(s3 == s4); //false 两个都是堆内存
System.out.println("=========================");
String s5 = s1 + "def"; //涉及到变量,故编译器不优化
String s6 = "abc" + "def"; //都是常量 编译器会自动优化成abcdef
String s7 = "abc" + new String ("def");//涉及到new对象,编译器不优化
System.out.println(s5 == s6); //false
System.out.println(s5 == s7); //false
System.out.println(s6 == s7); //false
System.out.println(s0 == s6); //true
System.out.println("=========================");
String s8 = s3 + "def";//涉及到new对象,编译器不优化
String s9 = s4 + "def";//涉及到new对象,编译器不优化
String s10 = s3 + new String("def");//涉及到new对象,编译器不优化
System.out.println(s8 == s9); //false
System.out.println(s8 == s10); //false
System.out.println(s9 == s10); //false
}
}
不可变对象:一旦创建,这个对象(状态 / 值)不能被更改了,其内在的成员变量的值就不能修改了。八个基本类型的包装类以及String,BigInteger和BigDecimal等。
//不可变对象是指值对象不再修改,而指针的指向可以修改。
String a = new String("abc");
String b = a;
a = "def"; //此时a又指向了值为"def"的地址中,而b仍然指向"abc"
//不可变对象也是传指针(引用)
public static void change(String b){ //b指向了abc
b = "def"; //b不指向abc而指向def,而a仍然指向abc,因为String是不可变的,只能新创建。
}
String a = new String("abc");
change(a); //
System.out.println(a); //abc
如何创建不可变对象,clone / new一个对象。
所有属性都是final和private的,不提供setter方法,类是final的,或者所有方法都是ifinal,类中包含mutable对象,那么返回拷贝需要深度clone。
不可变对象的优点:只读,线程安全。并发读,提高性能。可以重复使用。缺点:制造垃圾,浪费空间。对不可变对象进行修改时,会新开辟空间,旧对象则被搁置,直到回收。
String:Java字符串是一种典型的不可变对象。由于String不可修改,在拼接字符串或者对字符串做加法时会重新创建一个值,效率很差。使用StringBuffer(同步,线程安全)/ StringBuilder(不同步,线程不安全)类的append方法进行修改。
String a = "abc"; //常量赋值,栈分配内存
String b = new String("abc"); //new对象,堆分配内存
System.out.println(a.equals(b));//字符串内容比较
System.out.println(a==b); //是否指向同一个对象,指针比较
六、package、import和classpath
1、package
package:包名package name尽量唯一,域名是唯一的,因此常用域名逆序做包名。
类的完整名字:包名+类名。com.youth.ClassName 类路径:com\youth\ClassName.java
目录分隔符在Windows上是\,在Linux上是/,在Java中用/均可兼容。
2、import
import:不同包可以通过import类名或包名.*引入类。import必须全部放在package之后,类定义之前。多个import的顺序无关。import尽量精确,不推荐用
*,以免同名引用报错。
3、jar
jar:jar文件实际上是一组class文件的压缩包。Java中所有的类为.java文件格式,.java文件最终会被编译成.class文件(二进制)。
4、命令行执行java文件
命令行执行java文件:java -classpath ,;c:\temp com.youth.ClassName 或者 java -cp “,;c:\test,jar;c:\temp;c:\a bc” com.youth.ClassName
5、Java访问权限
- private:私有的,只能本类访问
- default:同一个包内访问
- protected:同一个包,子类均可访问
- public:公开的,所有类都可以访问
四种都可以用来修饰成员变量、成员方法、构造函数。default和public可以修饰类。
A.java
package test1;
public class A {
private int v1 = 1;
int v2 = 2;
protected int v3 = 3;
public int v4 = 4;
private void showV1()
{
System.out.println(v1);
}
void showV2()
{
System.out.println(v2);
}
protected void showV3()
{
System.out.println(v3);
}
public void showV4()
{
System.out.println(v4);
}
}
B.java:B和A在同一个包下,但没有继承关系,因此B只能通过new方式访问A中private以外的变量和方法
package test1;
//B and A are in the same package
public class B {
public void show()
{
//B is not subclass of A
// System.out.println(v1); //error
// System.out.println(v2); //error
// System.out.println(v3); //error
// System.out.println(v4); //error
// showV1(); //error
// showV2(); //error
// showV3(); //error
// showV4(); //error
A obj = new A();
//System.out.println(obj.v1); error, private
System.out.println(obj.v2);
System.out.println(obj.v3);
System.out.println(obj.v4);
//obj.showV1(); error, private
obj.showV2();
obj.showV3();
obj.showV4();
}
}
C.java:子类C继承父类A,并且C和A在同一个包下,因此可以访问public、default和proteced
package test1;
//C is a subclass of A, and in the same package of A.
public class C extends A {
public void show()
{
//System.out.println(v1); error, private
System.out.println(v2);
System.out.println(v3);
System.out.println(v4);
//showV1(); error, private
showV2();
showV3();
showV4();
A obj = new A();
//System.out.println(obj.v1); error, private
System.out.println(obj.v2);
System.out.println(obj.v3);
System.out.println(obj.v4);
//obj.showV1(); error, private
obj.showV2();
obj.showV3();
obj.showV4();
}
}
D.java:子类D继承父类A,可以以子类方式访问protected和public,不能通过new A()访问proteced
package test2;
import test1.A;
public class D extends A{
public void show()
{
//System.out.println(v1); error, private
//System.out.println(v2); error, default
System.out.println(v3);
System.out.println(v4);
//showV1(); error, private
//showV2(); error, default
showV3();
showV4();
A obj = new A();
//System.out.println(obj.v1); error, private
//System.out.println(obj.v2); error, default
//System.out.println(obj.v3); error, protected 只能作为子类才能访问
System.out.println(obj.v4);
//obj.showV1(); error, private
//obj.showV2(); error, default
//obj.showV3(); error protected 只能作为子类才能访问
obj.showV4();
}
}
E.java:与A.java不在同一个包下,也没有继承关系。只能通过new一个A的对象访问public变量和方法
package test2;
import test1.A;
public class E {
public void show()
{
//E is not a subclass of A. And E is not in the same package of A.
//System.out.println(v1); error, private
//System.out.println(v2); error, default
//System.out.println(v3);
//System.out.println(v4);
//showV1(); error, private
//showV2(); error, default
//showV3();
//showV4();
A obj = new A();
//System.out.println(obj.v1); error, private
//System.out.println(obj.v2); error, default
//System.out.println(obj.v3); error, protected 只能作为子类才能访问
System.out.println(obj.v4);
//obj.showV1(); error, private
//obj.showV2(); error, default
//obj.showV3(); error protected 只能作为子类才能访问
obj.showV4();
}
}
七、异常
1、异常分类
异常:程序不正常的行为或者状态。
UncheckedException:编译器不会辅助检查,需要程序员自己管理的异常,以预防为主。包括Error子类(可以不用处理)和RuntimeException子类。
CheckedException:编译器会辅助检查,程序员必须处理,以发生后处理为主。包括非RuntimeException的Exception的子类。
2、异常处理
异常处理:程序返回到安全状态,允许用户保存结果,抓住异常,分析异常内容,并以适当方式关闭程序。
捕获异常:try、catch、finally
try:正常业务逻辑。
catch:try发生异常,将执行catch代码。若无异常,绕之,执行完不会返回try也不会进入下一个catch,而是直接执行finally。因此多种catch时,小(子)异常放前面。
finally:执行try或catch后都必须要执行finally,即使catch内又发生了异常也会执行finally。
try{
// 程序代码
}catch(异常类型1 异常的变量名1){
// 程序代码
}catch(异常类型2 异常的变量名2){
// 程序代码
}finally{
// 程序代码
}
抛出异常:方法存在可能异常的语句,但不处理,那么可以使用throws来抛出异常。
调用带有throws异常的方法,要么处理这些异常,或者再次向外throws,直到main为止。
一个方法被覆盖,覆盖它的方法必须抛出相同的异常,或者异常的子类。如果父类的方法抛出多个异常,那么重写的子类方法必须抛出异常的子集,也就是不能抛出新(更大)的异常。
3、自定义异常
自定义异常:需要继承Exception类或其子类。
自定义重点在构造函数,调用父类Exception的message构造函数,自定义自己的成员变量,在程序中采用throw主动抛出异常。