No.1
class
简介:
在Java中,创建一个类,就是定义一个class.。
一个class可以包含多个字段(field),字段用来描述一个类的特征。
补充:
在OOP中,class
和instance
是“模版”和“实例”的关系。
定义class
就是定义了一种数据类型,对应的instance
是这种数据类型的实例。
通过new
操作符创建新的instance
,然后用变量指向它,即可通过变量来引用这个instance。
指向instance
的变量都是引用变量。
this变量:
在方法内部,可以使用一个隐含的变量this
,它始终指向当前实例。
因此,通过this.field
就可以访问当前实例的字段。
如果没有命名冲突,可以省略this
。
如:
class Person {
private String name;
public String getName() {
return name; // 相当于this.name
}
}
No.2
关于参数:
可变参数:
可变参数用类型...定义,可变参数相当于数组类型
如:
private String[] names;
public void setNames(String... names) {...}
与String[] names 方式 相比,可变参数 :
无法传入null:
String[] names可以传入null作为参数,而String... names 无法传入null。
因为传入0个参数时,接收到的实际值是一个空数组而不是null
需写在最后一列:
参数列表中有可变参数时,可变参数必须列在最后。
参数绑定:
基本类型:
基本类型参数的传递,是调用方值的复制传入参数后,双方的后续修改互不影响。
如:"int n = 0; p.setAge(n); "执行后,n和p.age各自的修改互不影响。
引用类型:
引用类型参数的传递,调用方的变量,和接收方的参数变量,指向的是同一个对象。
双方任意一方对这个对象进行的修改,都会影响到对方(因为指向同一个对象)。
如:"int[] n = {"Tom"}; p.setnums(n);" 执行后,后继对n[0]和nums[0]的修改都会影响到对方。
No.3
关于方法:
构造方法:
简介:
构造方法没有返回值(也没有void),调用构造方法必须用new操作符。
没有定义构造方法时,编译器会自动创建一个默认的无参数构造方法。
构造方法可以调用其他构造方法,调用语法是this(参数列表):
初始化对象:
在Java中,创建对象实例的时候,按照如下顺序进行初始化:
1.先初始化字段;例如,int age = 10;表示字段初始化为10,
2.执行构造方法的代码进行初始化。
多个构造方法:
可以定义多个构造方法。
可以在一个构造方法内部调用另一个构造方法,便于代码复用。
在通过new操作符调用的时候,编译器通过构造方法的参数数量、位置和类型自动区分。
方法重载(Overload):
简介:
在一个类中,我们可以定义多个方法。
如果有一系列方法,它们的功能都是类似的,只有参数有所不同,那么,可以把这一组方法名做成同名的方法。
补充:重载的方法返回值类型通常都是相同的。
如:
String类提供了多个重载方法indexOf(),可以查找子串:
int indexOf(int ch) :根据字符的Unicode码查找;
int indexOf(String str) :根据字符串查找;
int indexOf(int ch, int fromIndex) :根据字符查找,但指定起始位置;
int indexOf(String str, int fromIndex) :根据字符串查找,但指定起始位置。
No.4
关于继承
简介:
继承是面向对象编程中非常强大的一种机制,它可以复用代码。
如:
当我们让Student从Person继承时,Student就获得了Person的所有功能,我们只需要为Student编写新增的功能。
注意:子类自动获得了父类的所有字段,不能定义与父类重名的字段!
Java使用extends关键字来实现继承:
class Person {...}
class Student extends Person {...}
父类和子类的称呼:
在OOP的术语中,我们把Person
称为超类(super class),父类(parent class),基类(base class),
把Student称为子类(subclass),扩展类(extended class)。
自动继承Object类:
在Java中,没有明确写extends的类,编译器会自动加上extends Object。
所以,任何类,除了Object,都会继承自某个类。
Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。
protect:
继承有个特点,就是子类无法访问父类的private字段或者private方法。
super:
super关键字表示父类(超类)。
子类引用父类的字段时,可以用super.fieldName,效果等同于this.name。
子类会自动调用父类的构造方法:
在Java中,任何class的构造方法,第一行语句必须是调用父类的构造方法。
如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super()。
如果父类没有默认的构造方法,子类自动调用super()时就会报错。
这种调用意味着子类不会继承父类的任何构造函数。
向上转型:
简介:
子类可以向上转型。
如: Person p = new Student(); // 向父类转型
这种把一个子类类型安全地变为父类类型的赋值,被称为向上转型(upcasting)。
注意到继承树是Student > Person > Object。
向上转型实际上是把一个子类型安全地变为更加抽象的父类型。
向下转型:
和向上转型相反,如果把一个父类类型强制转型为子类类型,就是向下转型(downcasting)。
如: Object p1 = new Person();
Person p2 = (Person) p1; //向下转型
但是,向下转型时,父类对象不能转化为子类:
Person p2 = new Person();
Student s2 = (Student) p2; // ClassCastException,p2是父类对象,不能转为子类
为了避免向下转型出错,Java提供了instanceof操作符,可以先判断一个实例究竟是不是某种类型:
如: Object p1 = new Person();
System.out.println( p1 instanceof Person ); // instanceof 判断,输出true
instanceof实际上判断一个变量所指向的实例是否是指定类型,或是这个类型的子类。
instanceof variable:
Java 14开始,判断instanceof后,可以直接转型为指定变量:
如: if (obj instanceof String s) { ...//可以直接使用s }
使用instanceof variable这种判断并转型为指定类型变量的语法时,必须打开编译器开关--source 14和--enable-preview。
使用组合:
如果两个类之间是拥有和包含关系,而不是同一类别内的继承关系,那么应该使用组合。
如: Student和Book之间应该是组合关系:class Student extends Person { protected Book book;...},既Student包含Book。
No.5
多态 Polymorphic
覆写父类方法:
可以在子类中覆写父类的方法。
与重载的区别:
如果方法签名如果不同,就是Overload,Overload的方法是一个新方法;
如果方法签名相同,并且返回值也相同,就是Override。
加上@Override可以让编译器帮助检查是否进行了正确的覆写。
多态的特点:
Java的实例方法调用是基于运行时的实际类型(即对象的类型 )的动态调用,而非变量的声明类型。
由于这个特点,对于类似runTwice(Person p){ p.run();p.run();},只有在运行期才能动态决定调用的子类方法。
对某个类型调用某个方法,执行的实际方法可能是某个子类的覆写方法。
使用: 对于 Person[] person = { new Person(), new Student(), new PrimaryStudent() }
可以定义函数 testrun(Person[] person){ for( Person p : person ) { p.run();} }
然后调用:testrun(person);
这样做的好处是testrun函数只需要和person数组建立联系,不需要为数组中每个对象单独编写方法。
覆写Object方法:
所有的class最终都继承自Object,而Object定义了几个重要的方法:
toString():把instance输出为String;
equals():判断两个instance是否逻辑相等;
hashCode():计算一个instance的哈希值。
在必要的情况下,我们可以覆写这几个方法。
调用super
在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过super来调用。
如: class Person {
protected String name;
public String hello() { return "Hello, " + name; }
}
Student extends Person {
@Override
public String hello() {
// 调用父类的hello()方法:
return super.hello() + "!";
}
}
final:
如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final。
另外,用final修饰的方法不能被Override。
对于final字段,可以在构造方法中进行初始化:
class Person {
public final String name;
public Person(String name) {
this.name = name;
}
}
通过构造方法创建Person对象后,name字段将不可修改。
No.6
抽象类
抽象方法:
简介:
如果父类的方法本身不需要实现任何功能,仅仅是为了定义方法签名,目的是让子类去覆写它,
那么,可以把父类的方法声明为抽象方法
如:
abstract class Person {
public abstract void run();
}
抽象类:
简介:
如果一个class定义了方法,但没有具体执行代码,这个方法就是抽象方法。
抽象方法用abstract修饰,抽象方法无法实例化。
作用:
抽象类本身被设计成只能用于被继承,
因此,抽象类可以强迫子类实现其定义的抽象方法,否则编译会报错。
所以,抽象方法实际上相当于定义了“规范”。
例如,Person类定义了抽象方法run(),那么,在实现子类Student的时候,就必须覆写run()方法。
面向抽象编程
当我们定义了抽象类Person,以及具体的Student、Teacher子类的时候,我们可以通过抽象类Person类型去引用具体的子类的实例:
如: Person s = new Student();
Person t = new Teacher();
这种引用抽象类的好处在于,我们对其进行方法调用,并不关心Person类型变量的具体子类型:
如: // 不关心Person变量的具体子类型:
s.run();
t.run();
同样的代码,如果引用的是一个新的子类,我们仍然不关心具体类型:
如: Person e = new Employee();
e.run();
这种尽量引用高层类型,避免引用实际子类型的方式,称之为面向抽象编程。
面向抽象编程的本质:
上层代码只定义规范(例如:abstract class Person);
不需要使用子类就可以正常编译;
具体的业务逻辑由不同的子类实现,调用者并不关心。
No.7
接口
简介:
抽象方法本质上是定义接口规范,
即规定高层类的接口,从而保证所有子类都有相同的接口实现,实现多态。
如果一个抽象类没有字段,所有方法全部都是抽象方法,就可以把该抽象类改写为接口。
声明:
在Java中,使用interface可以声明一个接口: interface Person { void run(); }
因为接口定义的所有方法默认都是public abstract的,所以这两个修饰符不需要写出来。
当一个具体的class去实现一个interface时,需要使用implements关键字。
在Java中,一个类只能继承自另一个类,不能从多个类继承。但是,一个类可以实现多个interface
如: class Student implements Person, Hello { // 实现了两个interface }
接口之间只能是继承(extends)关系。接口的继承相当于扩展了接口的方法。
注意区分术语:
Java的接口特指interface的定义,表示一个接口类型和一组方法签名,
而编程接口泛指接口规范,如方法签名,数据格式,网络协议等。
设计:
合理设计interface和abstract class的继承关系,可以充分复用代码。
一般来说,公共逻辑适合放在abstract class中,
具体逻辑放到各个子类,而接口层次代表抽象程度。
在使用的时候,实例化的对象永远只能是某个具体的子类,但总是通过接口去引用它,
因为接口比抽象类更抽象。
定义接口的default方法:
在接口中,可以定义default方法:
interface Person {
String getName();
default void run() {
System.out.println(getName() + " run");
}
}
default相当于接口中提前实现的方法,default方法不必覆写。
default方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。
如果新增的是default方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。
interface没有定义字段,所以default方法无法直接访问字段,需要通过函数才能获取。
相比同名的default方法,子类会优先继承父类中的同名方法。
如果实现类继承的多个接口有同名同参数的default方法,必须手动实现default方法,
实现类可以用“接口名.super.方法名(参数)”来调用指定的default方法。
No.8
静态字段和静态方法
静态字段:
简介:
在一个class中定义的字段,我们称之为实例字段。
实例字段的特点是,每个实例都有独立的字段,各个实例的同名字段互不影响。
还有一种字段,是用static修饰的字段,称为静态字段:static field。
实例字段在每个实例中都有自己的一个独立“空间”,但是静态字段只有一个共享“空间”,所有实例都会共享该字段。
静态字段属于class而不属于实例。
静态字段的访问:
虽然实例可以访问静态字段,但是它们指向的其实都是同一个静态字段,而实例本身没有静态字段。
因此,不推荐用实例变量名.静态字段名去访问静态字段,而是直接使用类名.静态字段名访问。
编译器会自动把变量名.静态字段名修改为类名.静态字段名。
接口的静态字段:
因为interface是一个纯抽象类,所以它不能定义实例字段。
但是,interface是可以有静态字段的,并且静态字段必须为final类型。
因为interface的字段只能是public static final类型,所以可以把字段前面的修饰符都省略掉。
如: public interface Person {
// 编译器会自动在字段前加上public statc final
int MALE = 1;
int FEMALE = 2;
}
静态方法:
简介:
有静态字段,就有静态方法。用static修饰的方法称为静态方法。
调用实例方法必须通过一个实例变量,而调用静态方法则不需要实例变量,通过类名就可以调用。
No.9
包
简介:
在Java中,我们使用package来解决名字冲突。
package是一种名字空间,一个类总是属于某个包。
“类名”只是一个简写,真正的完整类名是“包名.类名”。
例如: 小明的Person类存放在包ming下面,因此,完整类名是ming.Person;
小红的Person类存放在包hong下面,因此,完整类名是hong.Person;
在Java虚拟机执行的时候,JVM只看完整类名,因此,只要包名不同,类就不同。
import:
在一个class中,我们总会引用其他包的class。
引用其他包中的类有三种写法:
1,直接写出完整类名
2,用import语句导入类
可以使用*把这个包下面的所有class都导入进来,但不包括子包的class
在导入了多个包后,类名可能会难以区分
3,import static的语法可以导入可以导入一个类的静态字段和静态方法:
import static java.lang.System.*; //import static使用很少
编译器查找包的顺序:
Java编译器最终编译出的.class文件只使用完整类名。
当编译器遇到一个class名称时,如果是简单类名,按下面的顺序依次查找:
1,查找当前package是否存在这个class;
2,查找import的包是否包含这个class;
3,查找java.lang包是否包含这个class。
例: java.util.List list; // 使用完整类名 -> java.util.List
Format format = null; // 使用已经import的类 -> java.text.Format
String s = "hi"; // 使用java.lang包的String类 -> java.lang.String
System.out.println(s); // 使用java.lang包的System -> java.lang.System
自动导入:
编写class的时候,编译器会自动帮我们做两个import动作:
1,默认自动import当前package的其他class;
2,默认自动import java.lang.*。
如果有两个class名称相同,例如,test.jun.Arrays和java.util.Arrays,
那么只能import其中一个,另一个必须写完整类名。
No.10
作用域
简介:
在Java中,我们经常看到public、protected、private这些修饰符。
在Java中,这些修饰符可以用来限定访问作用域。
public:
定义为public的class、interface可以被其他任何类访问.
定义为public的field、method可以被其他类访问,前提是首先有访问class的权限
private:
定义为private的field、method无法被其他类访问:
private访问权限被限定在class的内部。
推荐顺序:推荐把private方法放到public方法后面,因为public方法定义了类对外提供的功能,阅读代码的时候,应该先关注public方法
嵌套类的访问权限:如果一个类内部还定义了嵌套类,那么,嵌套类拥有访问private的权限。
protected:
protected作用于继承关系。
定义为protected的字段和方法可以被子类访问,以及子类的子类
package:
package的作用域是指:一个类允许访问同一个package的没有public、private修饰的class,
以及没有public、protected、private修饰的字段和方法
只要在同一个包(完整包名相同),就可以访问package权限的class、field和method。
作用域的使用:
如果不确定是否需要public,就不声明为public,尽可能少地暴露对外的字段和方法。
把方法定义为package权限有助于测试,因为测试类和被测试类只要位于同一个package,测试代码就可以访问被测试类的package权限方法。
一个.java文件只能包含一个public类,但可以包含多个非public类。如果有public类,文件名必须和public类的名字相同。
No.11
classpath和jar
classpath简介:
classpath是JVM用到的一个环境变量,它用来指示JVM如何搜索class。
因为Java是编译型语言,源码文件是.java,而编译后的.class文件才是真正可以被JVM执行的字节码。
因此,JVM需要知道,要加载指定的类,应该去哪搜索对应的.class文件
所以,classpath就是一组目录的集合。
classpath设置的搜索路径与操作系统相关。
例如: 在Windows系统上,用 ; 分隔,带空格的目录用 "" 括起来:
.;C:\work\project1\bin;C:\shared;"D:\My Documents\project1\bin"
在Linux系统上,用:
分隔。
/usr/shared:/usr/local/bin:/home/liaoxuefeng/bin
假设classpath是.;C:\work\project1\bin;C:\shared,( .
代表当前目录。)
当JVM在加载abc.xyz.Hello这个类时,会依次查找:
<当前目录>\abc\xyz\Hello.class
C:\work\project1\bin\abc\xyz\Hello.class
C:\shared\abc\xyz\Hello.class
classpath:
设置:
在系统环境变量中设置classpath环境变量,不推荐;
推荐在启动JVM时设置classpath变量。
启动时设置:
启动JVM时设置classpath,实际上就是给java命令传入-classpath + 路径:
如: java -classpath .;C:\work\project1\bin;C:\shared abc.xyz.Hello
(-classpath 可以简写为 -cp)
未设置:
如果没有设置系统环境变量,也没有传入-cp参数,如java abc.xyz.Hello
那么JVM默认的classpath为.,即当前目录
在IDE中运行Java程序,IDE自动传入的-cp参数是当前工程的bin目录和引入的jar包
JVM自带的核心类库(rt.jar)不需要设置。
jar包:
简介:
如果有很多.class文件,散落在各层目录中。
把目录打一个包,变成一个文件,更方便管理。
jar包就这样的文件。它可以把处于package组织下的目录层级,以及各级目录下的所有文件都打成一个jar文件。
这样一来,无论是备份,还是发给客户,就简单多了。
jar包的的结构:
jar包实际上就是一个zip格式的压缩文件,而jar包相当于目录。
如果我们要执行一个jar包内的class,就可以把jar包放到classpath中:
java -cp ./hello.jar abc.xyz.Hello // 当前目录下的hello.jar文件
这样JVM会自动在hello.jar文件里去搜索这个类(Hello.class)。
jar包的创建:
将文件以zip格式压缩,然后,把后缀从.zip改为.jar,一个jar包就创建成功。
需要特别注意的是,jar包里的第一层目录,不能是bin 。
MANIFEST.MF:
jar包还可以包含一个特殊的/META-INF/MANIFEST.MF文件,
MANIFEST.MF是纯文本,可以指定Main-Class和其它信息。
JVM会自动读取这个MANIFEST.MF文件,
如果配置了Main-Class,就可以用更方便的命令执行:
java -jar hello.jar
jar包还可以包含其它jar包,这个时候,就需要在MANIFEST.MF文件里配置classpath了。
Java社区提供了大量的开源构建工具,例如Maven,使用它们可以非常方便地创建jar包。
总结:
JVM通过环境变量classpath决定搜索class的路径和顺序;
不推荐设置系统环境变量classpath,始终建议通过-cp命令传入;
jar包相当于目录,可以包含很多.class文件,方便下载和使用;
MANIFEST.MF文件可以提供jar包的信息,如Main-Class,这样可以直接运行jar包。