封装、继承和多态是Java三大特性
封装:包含2个方面的意思,一是隐藏即隐藏内部实现细节,二是暴露,将一些操作界面暴露出来。如果通过暴露的界面来操作对象,该对象的内部状态就不会被破坏。
简而言之,封装就是要求合理地隐藏和合理地暴露。
Java的封装是通过访问控制符来实现的,常用的访问控制符有private、不写、protected和public
private(类访问权限):该修饰符修饰的成员,只能在该类中被访问。彻底地隐藏。
不写(包访问权限):该修饰符修饰的成员,只能在该类和该类所在的包中被访问。
protecte(子类访问权限):该修饰符修饰的成员,只能在该类和该类所在的包中、该类的子类中被访问。
public(公共):该修饰符修饰的成员可以在任意的地方被访问
private 不写 protected public
当前类 Y Y Y Y
同一个包 N Y Y Y
子类 N N Y Y
任意 N N N Y
封装的指导原则:
(1)成员变量使用private修饰,为了隐藏实现细节。其他的地方想要修改或获取该成员变量只能由其提供的get和set方法来操作,使用get和set方法这样就能保护该成员变量,阻止恶意操作。
package com.sf.javalearing0823;
public class User {
//将成员变量隐藏了起来,使用private修饰的变量只能在该类内部使用
private int age;
private String name;
public int getAge() {
return age;
}
//可以自己定义set方法,对变量提供保护,规避一些风险
public void setAge(int age) {
if(age > 100){
return ;
}
this.age = age;
}
public String getName() {
return name;
}
/可以自己定义set方法,对变量提供保护,规避一些风险
public void setName(String name) {
if(name.length() > 5){
return;
}
this.name = name;
}
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
package com.sf.javaleaning0824;
import com.sf.javalearing0823.User;
public class TestUser {
public static void main(String[] args) {
User u = new User();
u.setAge(1);
u.setName("gous");
System.out.println(u.getAge() + " " + u.getName());
}
}
(2)需要暴露的方法,通常用public修饰。
(3)如果一个方法希望被子类重写,用protected修饰
包:不同的程序员完全可以定义同名的类,为了解决不同公司、不同项目的类名重复的问题。Java就引入了“包”的机制。
Java程序如何为类定义包?
(1)在源代码中在package后面加上包名;
(2)将类放在对应的位置;
包的命名规范:最好用倒着写的公司的域名再加上项目名
注意:一旦为类加上包名之后,使用该类就应该使用完整的类名即包名加上类名。
导包:import的作用是为了省略包名,如果不用import,每次使用类的时候都需要加上包名。
import有两种使用方式:(1)import 包名.类名-----每次导入一个包。(2)import 包名.* ----导入指定包的所有类,此处的*只能代表类,不能代表包。Java程序默认导入了java.lang下面的所有类,如String类等,我们使用String的时候其实它已经被导入了。
静态导入:import static 包名.类名.静态成员名(包括属性和方法) 这样写了之后以后每次使用该静态成员就不需要使用类名.静态成员了,直接使用该静态成员。import static 包名.类名.*:代表导入该类下面的所有静态成员属性和方法。
Java源程序的结构:
一条package语句
多条import语句
多个class的定义,最多只有一个public类
构造器详解:
构造器规则:(1)构造器用于初始化对象(2)构造器必须使用new来调用构造器,这样可以返回一个初始化完成的对象。(3)如果不为一个类提供构造器,系统会自动为该类提供一个无参数的构造器。
构造器重载:一个类中可以定义多个构造器(因此构造器名字必然相同),必须要求形参列表不同这就是构造器重载。
继承:
理解继承的概念:苹果类继承了水果类;老虎类继承了动物类;东北虎类继承了老虎类
Java里面的继承是一种类与类之间的关系,是一种由一般到特殊的关系。子类可以对应于小类,父类可以对应于大类。子类的实例可以完全当成父类实例来使用!
继承的语法:
【修饰符】class 类名 extends 父类{
}
说明:
(1)Java是单继承,只能有一个直接父类
(2)如果没有显式地继承父类,Java默认是继承了Object类(JDK系统提供的类)
子类继承父类就可以得到父类所有的成员变量和方法。
继承最大的好处就是代码的复用,父类已经声明的方法和成员变量,子类可以直接拿来使用。
方法重写(override):当子类发现父类的方法不适合自己时,就可以重写父类方法了。
方法重写的口诀:两同两小1大
两同:子类的方法名和形参列表需要和父类相同
两小:子类的返回值类型与父类相同或更小(如父类是返回值类型是Object,子类的返回值可以是Object也可以是String),子类声明抛出的异常与父类相同或更小
一大:子类的访问权限和父类相同或比父类大(如如果父类的某个方法修饰符是public,子类在重新该方法时只能是Public不能使用比Public访问权限小的)
@override 在子类需要重写的方法上面加上该注解,如果该方法在重写过程中出现错误,编译器会帮我们报出。
super:与前面的this引用十分相似,super用于限定访问父类定义的实例变量或实例方法。
super.父类定义的实例变量
super.父类定义的实例方法(参数)
子类构造器一定会调用父类构造器一次,有且只有一次。
A:如果子类构造器没有显式地调用父类地构造器,系统会在子类构造器的第一行调用父类的无参构造器,如果此时父类里面没有无参构造器就会报错。
B:子类的构造器中显式地使用了super调用了父类的构造器,此时也应该注意super()后面的形参个数。
super调用的一定式父类的构造器,且只能出现在构造器的第一行。
this调用的一定是当前类的构造器,且只能出现在构造器的第一行。
super调用和this调用不能同时出现。
如果父类中没有无参构造器,子类的构造器只能显式地使用super去调用父类指定的构造器。
多态:
变态:同一个类型的实例在执行同一个方法的时候,个别对象表现出变异的行为特征,这就叫变态。
多态:同一个类型的实例在执行同一个方法的时候,可以呈现出多种行为特征这就叫多态。
public class TestDuoTai {
public static void main(String[] args) {
Niao n1 = new MaQue();
Niao n2 = new TuoNiao();
//这个时候就出现多态了,同一个对象的不同实例在调用同一个方法的时候,出现了不同的表现形式。
n1.fly();
n2.fly();
}
}
为什么会出现多态?
JAVA程序在执行时总是动态绑定的,方法总是执行该变量实际所指向的对象的方法。
变量的类型又分为编译时类型和运行时类型。
编译时类型;声明该变量时指定的类型,在JAVA程序编译阶段,java编译器只认编译时类型。
举例如下:
package com.sf.javalearning0828;
class Shape{
public void draw(){
System.out.println("这是形状");
}
}
class Circle extends Shape{
public void info(){
System.out.println("这是信息");
}
}
class JuXing extends Shape{
public void draw(){
System.out.println("这是矩形");
}
}
public class TestDemo1 {
public static void main(String[] args) {
Shape s1 = new Circle();
Shape s2 = new JuXing();
//直接使用s1调用info()方法会编译不通过,因为程序在编译时编译器会把s1当成Shape类型,
// 而Shape类型没有info()方法
((Circle) s1).info();//这样的强制转换才能使s1变成circle类型,这样才能调用Info方法
}
}
运行时类型:该变量实际运行时所引用对象的类型。
向上转型:子类对象可以直接赋值给父类变量,可以自动转换。 Dog d = new ErHa(); ErHa是Dog的子类。
向下转型:父类变量赋值给子类变量,需要强制转换了。(类型)变量名。
强转运算符的注意点:
A:强转运算符只能在编译类型具有继承关系的变量之间进行强转,否则编译会报错。
public class TestDemo1 {
public static void main(String[] args) {
Shape s1 = new Circle();
Shape s2 = new JuXing();
//直接使用s1调用info()方法会编译不通过,因为程序在编译时编译器会把s1当成Shape类型,
// 而Shape类型没有info()方法
((Circle) s1).info();//这样的强制转换才能使s1变成circle类型,这样才能调用Info方法
// Circle c = (Circle) s2; 这样就会报类型无法转换的错误,因为s2无法转换为circle类型,
}
}
B:强转运算如果在编译类型具有继承关系的变量之间进行强转,但是被转变量的实际类型与需要的类型不对应也会报错。如将String类型转换为Integer类型,
instanceof运算符:该运算符只能在编译类型具有继承关系的变量之间进行判断。为了避免在强制类型转换时出现错误,Java里面可以使用instanceof运算符在进行强制转换之前进行类型的判断,如instanceof运算符判断的结果为空就不再转换了。
public class TestQiangZhuan {
public static void main(String[] args) {
//TuoNiao和MaQue都继承了Niao
Niao n1 = new MaQue();
Niao n2 = new TuoNiao();
if(n1 instanceof TuoNiao){
TuoNiao t1 = (TuoNiao)n1;
}
}
}
instanceof运算符,当变量所引用的对象是后面类或子类的实例时,返回true。上面代码中,n1是Niao的实例,TuoNiao是Niao的子类,符合只能在编译类型具有继承关系的变量之间进行判断这一原则,但是n1和Tuoniao并没有子类的关系所以返回false。
String s1 = "qwe";
//使用instanceof进行判断时,必须保证instanceof前后的变量具有继承关系的,否则会报错
// System.out.println(s1 instanceof Integer);
这里使用instanceof运算符进行判断会编译不通过,因为不满足“该运算符只能在编译类型具有继承关系的变量之间进行判断”这一条件。
初始化块:
语法:
[修饰符] {
各种语句;
}
初始化块是没有名字的,修饰符只可以加上static;有static是静态初始化块,没有static的是实例初始化块;
实例初始化块:
实例初始化块是假象,一个类在编译之后,实例初始化块就会消失,实例初始化块就会被还原到这个类的每一个构造器所有代码之前。
实例代码块的作用:将构造器里面相同的代码就可以写在实例代码块里面,这样会减少重复代码。
package com.sf.javalearning0829;
public class Dog {
private int age = 0;
private String color;
{
age = 8;
System.out.println("这是初始实例化块");
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Dog() {
age = 0;
//实例代码块里面的代码就会自动填充到构造器所有代码之前,
//而且实例变量的赋值也是在这里的
age = 8;
System.out.println("这是初始实例化块");
}
public Dog(int age, String color) {
age = 0;
//实例代码块里面的代码就会自动填充到构造器所有代码之前,
//而且实例变量的赋值也是在这里的
age = 8;
System.out.println("这是初始实例化块");
this.age = age;
this.color = color;
}
}
public class ChuShiHuaKuaiTest01 {
public static void main(String[] args) {
Dog d1 = new Dog();
System.out.println(d1.getAge());
}
}
Q: 实例代码块何时执行?
A: 只要程序调用构造器新建实例,就会先执行实例代码块里面的代码,因为实例代码块里面的代码总会放在构造方法所有代码的最前面执行。
类里面定义实例变量指定的初始值也是假象,该变量的初始值的赋值会在构造器里面完成,也是放在构造器所有代码的前面,而该赋值语句与实例代码块里面代码的执行顺序与原本该两个的顺序对应,即如果该类里面实例代码块在实例变量之前那么转换后在构造器里面的顺序也是这样的。
类初始化块:有static的代码块,负责对类进行初始化。当程序第一次主动使用该类时,系统会为该类分配空间,并执行初始化(调用类初始化块)。只要你使用该类就算主动使用该类,除了使用类变量声明外如(Dog dog;这个语句就是使用类变量声明,这样不算主动使用该类)。
Q:类初始化何时执行?
A:程序第一次主动使用该类时,会执行该类的类初始化块。程序运行时,类初始化块只会运行一次,不像实例初始化块,只要新建一个实例就会运行一次。
执行次数 执行先后 何时执行
类初始化块: 1次 先 第一次主动使用该类时
实例初始化块: N次 后 新建一个实例变量时
定义类变量时指定的初始值也是假象,该类变量的赋值语句会在类代码块里面执行,赋值语句与类代码块原本语句的执行顺序与原码的中的顺序一致。
初始化任何类之前,一定要从Object类开始初始化,依次初始化它的所有祖先类,最后才到它自己。
创建任何对象时,一定是从Object类的构造器开始,执行它所有祖先类的构造器,到最后再执行它自己的构造器。