文章目录
可变参数
-
可变参数用
类型...
定义,可变参数相当于数组类型,可以传入一个数组给方法中的可变参数。 -
完全可以把可变参数改写为
String[]
类型,但是,调用方需要自己先构造String[]
,比较麻烦。
class Group {
private String[] names;
public void setNames1(String... names){
this.names = names;
}
public void setNames2(String [] names){
this.names = names;
}
}
//调用
Group g = new Group();
g.setNames1("Xiao Ming", "Xiao Hong", "Xiao Jun");
g.setNames1(); //为空,传入0个String,不为null
g.setNames2(new String[] {"Xiao Ming", "Xiao Hong", "Xiao Jun"}); //需要构造数组
g.setNames2(null); //接收到一个null值
可变参数可以保证无法传入null
,因为传入0个参数时,接收到的实际值是一个空数组而不是null
。
方法重载
方法名相同,但各自的参数不同,称为方法重载(Overload
)。
继承
-
继承树:在Java中,没有明确写
extends
的类,编译器会自动加上extends Object
。所以,任何类,除了Object
,都会继承自某个类。 -
Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有
Object
特殊,它没有父类。 -
继承有个特点,就是子类无法访问父类的
private
字段或者private
方法,但可以通过父类的set、get方法访问父类的私有属性。 -
为了让子类可以访问父类的字段,我们可以把
private
改为protected
。用protected
修饰的字段可以被子类访问。
向上转型
-
一个引用类型为一个类的父类的变量,可以指向子类的实例。
-
这种把一个子类类型变为父类类型的赋值,被称为向上转型(upcasting),向上转型实际上是把一个子类型安全地变为更抽象的父类型。
向下转型
-
和向上转型相反,如果把一个父类类型强制转型为子类类型,就是向下转型(downcasting)。
-
如果一个类的实际类型是父类,不能把父类变成子类,因为子类功能比父类多,多的功能无法凭空变出来。
-
因此,向下转型很可能会失败,JVM报错
ClassCastException
。 -
为了避免向下转型出错,java提供了
instanceof
操作符,可以先判断一个实例究竟是不是某种类型:注:如果一个类是子类,那么这个类也拥有了父类的非私有字段和方法,所以这个类也算一个父类。
Person p = new Person(); System.out.println(p instanceof Person); // true System.out.println(p instanceof Student); // false Student s = new Student(); System.out.println(s instanceof Person); // true System.out.println(s instanceof Student); // true
-
Java14开始,判断
instanceof
后,可以直接转型为指定变量,避免再次强制转型。Object obj = "hello"; if (obj instanceof String) { String s = (String) obj; System.out.println(s.toUpperCase()); } 可以改写为以下代码: Object obj = "hello"; if (obj instanceof String s) { // 可以直接使用变量s: System.out.println(s.toUpperCase()); }
-
子类可以直接通过
super(Object o,...)
直接调用父类的构造方法(只能在子类的构造方法中调用父类的构造方法)。public Student(String name, int age, int score) { super(name, age); this.score = score; }
super
在子类的方法中,如果要调用父类的非私有方法,可以通过super
来调用。
super.hello()
即是调用父类的hello()方法。
重写
-
在继承关系中,如果子类定义了一个与父类方法签名完全相同的方法,被称为覆写/重写(Override)。
方法签名:方法名,参数列表类型和顺序。(不包括返回类型、形参名)
返回类型不同则代表不是同一个方法,不同方法就不能有相同命名。
在子类中重写父类的私有方法的时候不能加上重写的注解,因为无法访问到父类的私有方法,重写父类的私有方法相当于在子类中添加子类特有的方法,所以不能称之为重写。
-
加上
@Override
可以让编译器帮助检查是否进行了正确的覆写,不是覆写的话会报compile error。 -
Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。
解释:如果一个子类重写了父类的方法,该子类的一个实例的引用类型是父类,如果调用该实例的重写方法,实际上会调用子类重写后的方法。(即实际类型的方法)
-
子类重写父类的方法不能设为私有,因为需要通过父类进行访问,否则没有意义。
final
用final
修饰的方法不能被Override
。
用final
修饰的类不能被继承。
用final
修饰的字段在初始化后不能被修改。
对final
字段重新赋值会报错。
可以在构造方法中初始化final字段,
class Person {
public final String name; //实例一旦创建,final字段就不可修改。
public Person(String name) {
this.name = name;
}
}
多态
多态是指针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法。
例子:普通收入、工资收入、国务院津贴。
public class Main {
public static void main(String[] args) {
// 给一个有普通收入、工资收入和享受国务院特殊津贴的小伙伴算税:
Income[] incomes = new Income[] {
new Income(3000),
new Salary(7500),
new StateCouncilSpecialAllowance(15000)
};
System.out.println(totalTax(incomes));
}
// 该方法只需要接收Income对象,就可以根据传入的对象做不同的处理。
public static double totalTax(Income... incomes) {
double total = 0;
for (Income income: incomes) {
total = total + income.getTax();
}
return total;
}
}
class Income {
protected double income;
public Income(double income) {
this.income = income;
}
public double getTax() {
return income * 0.1; // 税率10%
}
}
class Salary extends Income {
public Salary(double income) {
super(income);
}
@Override
public double getTax() {
if (income <= 5000) {
return 0;
}
return (income - 5000) * 0.2; //减去基数再×税率
}
}
class StateCouncilSpecialAllowance extends Income {
public StateCouncilSpecialAllowance(double income) {
super(income);
}
@Override
public double getTax() {
return 0; //不用交税。
}
}
覆写Object方法
因为所有的class
最终都继承自Object
,而Object
定义了几个重要的方法:
toString()
:把instance输出为String
;equals()
:判断两个instance是否逻辑相等;hashCode()
:计算一个instance的哈希值。
class Person {
...
// 显示更有意义的字符串:
@Override
public String toString() {
return "Person:name=" + name;
}
// 比较是否相等:
@Override
public boolean equals(Object o) {
// 当且仅当o为Person类型:
if (o instanceof Person) {
Person p = (Person) o;
// 并且name字段相同时,返回true:
return this.name.equals(p.name);
}
return false;
}
// 计算hash:
@Override
public int hashCode() {
return this.name.hashCode();
}
}
抽象类
-
如果父类的方法本身不需要实现任何功能,仅仅是为了定义方法签名,目的是让子类去覆写它,那么,可以把父类的方法声明为抽象方法。
-
把一个方法声明为
abstract
,表示它是一个抽象方法,本身没有实现任何方法语句。 -
带有抽象方法的类也无法被实例化。
面向抽象编程
引用高层类型(父类),避免引用实际子类型的方式,称之为面向抽象编程。
面向抽象编程的本质就是:
- 上层代码只定义规范(例如:
abstract class Person
); - 不需要子类就可以实现业务逻辑(正常编译);
- 具体的业务逻辑由不同的子类实现,调用者并不关心。
接口
-
如果一个抽象类没有字段,所有方法全部都是抽象方法,就可以把该抽象类改写为接口:
interface
。 -
接口定义的所有方法默认都是
public abstract
的。 -
接口定义的成员变量默认都是
public static final
。 -
在Java中,一个类只能继承自另一个类,不能从多个类继承。但是,一个类可以实现多个
interface
。 -
如果一个类实现了多个接口,其中两个接口具有相同的方法签名但返回类型不同,此时需要重写的方法返回类型不确定,不能这样实现。(即一个类不能实现一个具有不同返回类型但具有相同方法签名方法的接口),但是如果返回类型的一个是另一个的子类型,那么可以重写返回类型为子类型的方法。
抽象类和接口的对比如下:
abstract class | interface | |
---|---|---|
继承 | 只能extends一个class | 可以implements多个interface |
字段 | 可以定义实例字段 | 不能定义实例字段 |
抽象方法 | 可以定义抽象方法 | 可以定义抽象方法 |
非抽象方法 | 可以定义非抽象方法 | 可以定义default方法 |
接口继承
- 一个
interface
可以继承自另一个或多个interface
。interface
继承自interface
使用extends
,它相当于扩展了接口的方法。
继承关系
- 在使用的时候,实例化的对象永远只能是某个具体的子类,但总是通过接口去引用它,因为接口比抽象类更抽象。
default方法
-
在接口中,可以定义
default
方法。 -
实现类可以不必覆写
default
方法。default
方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default
方法,那么子类就不必全部修改。 -
default
方法和抽象类的普通方法是有所不同的。因为interface
没有字段,default
方法无法访问字段,而抽象类的普通方法可以访问实例字段。 -
如果一个类实现了多个接口,其中两个接口具有相同的方法签名的
default
方法,那么该类必须重写该方法。
静态字段和静态方法
静态字段
-
用
static
修饰的字段,称为静态字段:static field
。 -
实例字段在每个实例中都有自己的一个独立“空间”,但是静态字段只有一个共享“空间”,所有实例都会共享该字段。
不推荐用
实例变量.静态字段
去访问静态字段。因为在Java程序中,实例对象并没有静态字段。在代码中,实例对象能访问静态字段只是因为编译器可以根据实例类型自动转换为类名.静态字段
来访问静态对象。
静态方法
-
用
static
修饰的方法称为静态方法。 -
调用实例方法必须通过一个实例变量,而调用静态方法则不需要实例变量,通过类名就可以调用。
-
静态方法属于
class
而不属于实例,因此,静态方法内部,无法访问this
变量,也无法访问实例字段,它只能访问静态字段。
包
-
在Java中,我们使用
package
来解决名字冲突。 -
在Java虚拟机执行的时候,JVM只看完整类名,因此,只要包名不同,类就不同。
-
import static
的语法,它可以导入可以导入一个类的静态字段和静态方法(很少用)。 -
注意包不能重名。
要特别注意:包没有父子关系。java.util和java.util.zip是不同的包,两者没有任何继承关系。
包作用域
- 位于同一个包的类,可以访问包作用域的字段和方法。不用
public
、protected
、private
修饰的字段和方法就是包作用域。
作用域
public
定义为public
的class
、interface
可以被其他任何类访问。
定义为public
的field
、method
可以被其他类访问,前提是首先有访问class
的权限。
private
定义为private
的field
、method
无法被其他类访问。
阅读代码的时候,应该先关注
public
方法。
嵌套类
定义在一个class
内部的class
称为嵌套类(nested class
),嵌套类拥有访问其外层的private
的权限。
protected
protected
作用于继承关系。定义为protected
的字段和方法可以被子类访问,以及子类的子类。
final
用final
修饰class
可以阻止被继承。
用final
修饰method
可以阻止被子类覆写。
用final
修饰field
可以阻止被重新赋值。
用final
修饰 局部变量/方法参数 可以阻止被重新赋值。
如果不确定是否需要
public
,就不声明为public
,即尽可能少地暴露对外的字段和方法。一个
.java
文件只能包含一个public
类,但可以包含多个非public
类。如果有public
类,文件名必须和public
类的名字相同。
classpath和jar
-
classpath
是JVM用到的一个环境变量,它用来指示JVM如何搜索class
。 -
Java是编译型语言,源码文件是
.java
,而编译后的.class
文件才是真正可以被JVM执行的字节码。 -
jar包相当于目录,可以包含很多
.class
文件,方便下载和使用;
模块
从Java 9开始,JDK又引入了模块(Module)。
-
.class
文件是JVM看到的最小可执行文件,而一个大型程序需要编写很多Class,并生成一堆.class
文件,很不便于管理,所以,jar
文件就是class
文件的容器。 -
如果是自己开发的程序,除了一个自己的
app.jar
以外,还需要一堆第三方的jar包,运行一个Java程序,一般来说,命令行写这样:
java -cp app.jar:a.jar:b.jar:c.jar com.liaoxuefeng.sample.Main
- 如果漏写了某个运行时需要用到的jar,那么在运行期极有可能抛出
ClassNotFoundException
。所以,jar只是用于存放class的容器,它并不关心class之间的依赖。
`文件,方便下载和使用;
模块
从Java 9开始,JDK又引入了模块(Module)。
-
.class
文件是JVM看到的最小可执行文件,而一个大型程序需要编写很多Class,并生成一堆.class
文件,很不便于管理,所以,jar
文件就是class
文件的容器。 -
如果是自己开发的程序,除了一个自己的
app.jar
以外,还需要一堆第三方的jar包,运行一个Java程序,一般来说,命令行写这样:
java -cp app.jar:a.jar:b.jar:c.jar com.liaoxuefeng.sample.Main
-
如果漏写了某个运行时需要用到的jar,那么在运行期极有可能抛出
ClassNotFoundException
。所以,jar只是用于存放class的容器,它并不关心class之间的依赖。 -
从Java 9开始引入的模块主要就是为了解决“依赖”这个问题。如果
a.jar
必须依赖另一个b.jar
才能运行,那我们应该给a.jar
加点说明,让程序在编译和运行的时候能自动定位到b.jar
,这种自带“依赖关系”的class容器就是模块。