Java学习笔记二
Java学习笔记二
面向对象(Object Oriented)
属性(成员变量)跟随对象放在堆里面,局部变量(如 p1)放在栈里面。只有成员变量的前面能添加权限修饰符,且成员变量自带默认值。
在一个类中,一个方法可以调用这个类中的其余方法(包括自身,即递归)以及成员变量,不能在方法中再定义方法。
方法重载(Overload,两同一不同)
在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可。
与权限修饰符、返回值类型、形参名无关,比如
public void add(int m,int n)
和public void add(int n,int m)
就不算重载。
可变形参
方法名(参数的类型名...参数名),例如public static void test(int a ,String...books){};
。
-
可变参数:方法的参数个数:0 个或多个。
-
可变形参的方法与同名的方法之间,彼此构成重载。
-
可变形参与形参是数组是等价的,二者不能同时声明,否则报错。
public static void test(int a ,String[] books){};
与例子写法是等价的。 -
可变形参需要放在形参列表的最后。
-
在一个方法中,最多声明一个可变形参。
参数传递机制-值传递
形参是基本数据类型:将实参的“数据值”传递给形参。
形参是引用数据类型:将实参的“地址值”传递给形参。
前提是实参和形参类型一致。
import
导入其他包下的类。格式:import 包名.类名;
。
import 包名.*
:导入包下面的所有类。- 类所在包的其他类 和 java.lang 无需导入,可以直接使用。
- 导包导入的是当前文件夹里的类,不包括其子文件夹里的类。
比如使用
import com.*;
是指导入 com 目录下的所有类,这里只有 Test 类,并不会导入在 com 的下一级 lc 目录中的 Phone 类。
-
如果在代码中使用不同包下的同名的类,那么就需要使用类的全类名的方式指明调用的是哪个类。
如:
java.sql.Date date = new java.sql.Date(time);
封装性
权限修饰符
Java 规定了 4 种权限修饰符,借助这些权限修饰符修饰类及类的内部成员。当这些成员被调用时,体现了可见性的大小。
权限修饰符 | 本类内部 | 本包内 | 其他包的子类 | 其他包的非子类 |
---|---|---|---|---|
private | √ | |||
缺省 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
外部类只能用 public 和缺省修饰。
- 在其他包的子类中,可以直接使用或通过子类对象间接使用父类中 protected 修饰的属性或方法。
- 在其他包的子类中 ,new 出来的父类对象,无法使用父类中 protected 修饰的属性或方法。
- 其他包的类,通过调用子类对象,无法使用父类中 protected 修饰的属性或方法。
- 在其他包的子类中,通过其他子类的对象,无法使用父类的protected修饰的属性和方法。
这里的“使用”是指:直接使用属性或通过“对象.属性”访问,而非通过如 getter 的方法访问。
构造器(Constructor)
搭配 new 关键字创建对象。
public class Student {
private String name;
private int age;
// 无参构造,当程序员没显式给出构造器时,系统会默认调用无参构造且构造器的修饰符与类相同;当程序员给出有参构造时,则默认无参构造会失效。
public Student() {}
// 有参构造,有参构造可以给私有变量赋初值。
public Student(String n,int a) {
name = n;
age = a;
}
//构造器名与类名相同,构造器没有返回值。构造器可以重载。
public String getName() {
return name;
}
public void setName(String n) {
name = n;
}
public int getAge() {
return age;
}
public void setAge(int a) {
age = a;
}
public String getInfo(){
return "姓名:" + name +",年龄:" + age;
}
}
//调用无参构造创建学生对象
Student s1 = new Student();
//调用有参构造创建学生对象
Student s2 = new Student("张三",23);
Java Bean
一种特殊的类,具有以下属性:
1、具有一个 public 修饰的无参构造函数。
2、所有属性由 private 修饰。
3、通过 public 修饰的 get 方法和 set 方法获得或修改属性。
4、可序列化。
比如,可以实现 Serializable 接口,用于实现bean的持久性。
this 关键字
当形参与成员变量同名时,如果在方法内或构造器内需要使用成员变量,必须添加 this 来表明该变量是类的成员变量。即:我们可以用 this 来区分成员变量和局部变量。
this代表当前对象或即将创建的对象,Base sub = new Sub();
这里的对象是 Sub 对象,而不是看 sub 左边的引用类型,就判断为 Base 对象。
this 可以作为一个类中构造器相互调用的特殊格式。
- this():调用本类的无参构造器。
- this(实参列表):调用本类的有参构造器。
this()和 this(实参列表)只能声明在构造器首行,且不能出现递归调用。
public class Student{
private String name;
private int age;
//无参构造
public Student(){}
//有参构造
public Student(String name){
this.name = name;
}
public Student(String name,int age){
this(name); //此行代表先调用 public Student(String name) 这个构造器,减少了代码量。
this.age = age; //这里的 this 代表即将创建的对象。
}
public void setName(String name){
this.name = name; //this代表当前对象,“this.”修饰的变量代表成员变量。
}
}
继承性
继承的出现让类与类之间产生了 is-a 的关系,比如: A student is a person。
关键字:extends,Java 中的继承是一种扩展。
子类会继承父类所有的实例变量和实例方法。
子类不能直接访问父类中私有的(private)的成员变量和方法,需通过 get/set 方法。
一个父类可以同时拥有多个子类,但一个子类只有一个父类。
类加载的时候,先加载父类,再加载子类,即先加载父类的静态代码块。
public class B extends A{}
重写(overwrite、override)
子类重写从父类继承来的方法,以便适应新需求。
@Override:写在方法上面,用来检测是否满足重写的要求。这个注解就算不写,只要满足要求,也能够重写。
重写的要求:
-
方法名和参数列表不能变。
-
重写的权限修饰符不能小于父类被重写方法的权限修饰符。
① 父类私有方法不能重写。 ② 跨包父类中缺省的方法也不能重写
-
若返回值类型是基本数据类型和 void,则不能改变。
-
若返回值类型是引用数据类型,则返回值类型不能大于父类中被重写的方法的返回值类型。(例如:Student < Person)。
也就是,返回值类型不变或是返回值类型的子类。
-
子类方法抛出的异常不能大于父类被重写方法的异常。
-
子类与父类中同名同参数的方法必须同时声明为非 static 。
static 修饰的方法不能被重写。
super()
super()
使得在子类中可以使用父类的属性、方法、构造器。
一般可以省略,但是当出现重写了方法或子类、父类有重名属性的情况,调用父类的方法或属性需要加上“super.”。
方法前面没有 super.和 this.:先从子类找,如果没有,再从父类找,再没有,继续往上追溯。
方法前面有 this.:先从子类找,如果没有,再从直接父类找,再没有,继续往上追溯。
方法前面有 super.:忽略子类的方法,直接从父类找,如果没有,继续往上追溯。
总结:有
super.
则直接从父类找,没有super.
则从子类开始找。当方法需使用重名的属性时,采用就近的原则。
- 子类中定义过的方法(即子类中重写过的方法和子类中新定义的方法,不包括从父类继承但没重写的方法)优先找子类的属性。
- 父类中拥有的方法找的是父类的属性,无法访问子类的属性。
- 当子类中重写过的方法前加上
super.
后,使用的是父类的方法,所以就近找父类的属性。父类中的
setInfo()
没有在子类中重写,若在代码中调用,则修改的是父类的 Info 属性。
子类调用父类的构造器
super(形参列表)
,必须声明在构造器的首行。
对于每个构造器,"this(形参列表)" 和 "super(形参列表)"只能二选一,没有给出"this(形参列表)",则默认调用"super()",即调用父类中空参的构造器,那么父类空参构造器中已初始化的属性无需在子类构造器中反复初始化,除非初始值不同。
当我们用子类构造器创建对象时,子类构造器会直接或间接调用到其父类的构造器,而其父类的构造器会直接或间接地调用它自己父类的构造器,直到调用了 Object 类中的构造器,所以内存中就有父类声明的属性及方法。
多态性
多态的使用前提:① 类的继承关系 ② 方法的重写
Java 中多态性的体现:父类的引用可以指向子类的对象。比如:Person p = new Student();
一个引用类型的变量可能指向(引用)多种不同类型的对象。
常用多态的地方:
-
方法的形参
-
方法的返回值
使用父类做方法的形参,是多态使用最多的场合。即使增加了新的子类,方法也无需改变,提高了扩展性,符合开闭原则(对扩展开放,对修改关闭)。
public class Person{
private Pet pet;
public void adopt(Pet pet) {//形参是父类类型,实参传入子类对象
this.pet = pet;
}
public void feed(){
pet.eat(); //pet 实际引用的对象类型不同,执行的 eat 方法也不同。
}
}
...
Person person = new Person();
Dog dog = new Dog();
person.adopt(dog);//实参是 dog 子类对象,形参是父类 Pet 类型
弊端:由于声明为父类的引用,导致无法调用子类特有的属性和方法。
使用了多态后,同名属性则会使用父类的,子类独有的属性无法使用,子类新定义的方法无法使用,只有重写过的方法能使用。除非向下转型才能使用子类的属性或新定义的方法。
根据左边的引用类型,决定调用父类的属性还是子类的属性,而方法是调用的子类对象重写过后的方法。
Base b = new Sub(); //引用类型是 Base,调用Base的属性,即 1。 System.out.println(b.a); Sub s = new Sub(); //引用类型是 Sub,调用 Sub 的属性,即 2。 System.out.println(s.a); class Base{ int a = 1; } class Sub extends Base{ int a = 2; }
虚方法调用(Virtual Method Invocation)
编译时,按照左边变量的引用类型处理,只能调用父类中有的属性和方法,不能调用子类特有的变量和方法。但是,运行时,仍然是对象本身的类型,所以执行的方法是子类重写过后的方法体。
Java 中的虚方法是指在编译阶段不能确定方法的调用入口地址,在运行阶段才能确定实际被执行的方法。
向下转型
instanceof
引用类型 a = new 对象X()
;
a instanceof 类A
:对象 a 是否为类 A 或类 A 的子类创建的对象 ,返回值为 boolean 型。
-
对象 a 声明的引用类型要么是类A,要么与类 A 有继承关系,才能过编译。
//Base是父类,sub和sub1是两个子类。 Sub b = new Sub(); //声明的引用类型为Sub System.out.println(b instanceof Sub1); //这里会报错,因为Sub和Sub1没有继承关系。 Base b = new Sub(); //声明的引用类型为Base System.out.println(b instanceof Sub1); //不会报错
向下转型:在方法体中,将接收到的对象从父类引用类型,转为子类引用类型,然后调用子类特有的变量和方法。
if(pets[i] instanceof Dog){ //
Dog dog = (Dog) pets[i];
dog.watchHouse(); //Dog 特有的方法
}
Object
如果一个类没有显式继承父类,那么默认则继承自 Object 类。
Object 类没有属性,只有方法。
clone()
克隆出一个新对象。
Animal a1 = new Animal();
...
Animal a2 = (Animal)a1.clone(); //因为 clone() 的返回值为 Object,这里需要强转,a2 和 a1 是 2 个不同的对象。
finalize()
当 GC(Garbage Collection,垃圾回收器)要回收对象时,可以调用此方法。在子类中重写此方法,可在释放对象前进行某些操作。
在 JDK 9 中此方法已经被标记为过时的。
getClass()
获取对象的运行时类型,而不是编译时类型。
Base sub = new Sub();
System.out.println(sub.getClass());//class com.lc.Sub
equal()[重要]
Object 里的 equal() 实际用了“==”,即对于基本数据类型,比较值是否相等,而对于引用数据类型,比较地址值是否相等。
格式:obj1.equals(obj2)
所有类都继承了 Object,也就获得了 equals()方法。还可以重写。
File、String、Date 及包装类重写了 equals()方法,比较类型及内容而不考虑引用的是否是同一个对象。
toString()[重要]
打印引用数据类型变量默认调用 toString()。
在自定义类,没有重写 toString() 的情况下,打印的是“对象的运行时类型 @ 对象的 hashCode 值
的十六进制形式"
System.out.println(base); //com.lc.Base@1b6d3586
可以根据用户需求重写 toString()方法,如 String 类重写了 toString() 方法,返回字符串的值。
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age +'}';
}
static
成员变量按照是否使用 static 可以分为:
-
使用:静态变量或类变量。
随类加载而加载,内存空间里就一份(因为类只加载一次),被类的对象共享。jdk6 放在方法区,jdk7 及以后放在堆空间。可以通过类或对象调用。
-
不使用:非静态变量或实例变量。
每个对象拥有一份实例变量,随对象的创建而加载,存放在堆空间的对象实体中。只能通过对象调用。
static 修饰的方法:类方法、静态方法。
随类加载而加载,可以通过类或对象调用,静态方法内可以使用静态变量和其他的静态方法,不可以调用非静态的属性和方法。
静态方法中不能使用 this 和 super。
在静态类中使用静态变量和其他静态方法时,可以省略“类名.”。
public class ChildTest {
public static void main(String[] args) {
Base base = null;
System.out.println(base.a); //这里会输出 1,即便 base 指向的是 null
}
}
class Base{
static int a = 1;
}
类的属性可以为自己的实例对象,JVM 把 A 加载进内存后,执行到A a = new A();
时,发现 A 已在内存中,则能过编译。
A aa = new A();
...
class A {
static A a = new A(); //随类加载而加载,只执行一次,静态变量 a 里也有属性 a,只不过指向的是自己。
}
类成员-代码块
如果成员变量的初始值不是一个常量值,而是需要通过复杂的计算或读取文件、或读取运行环境信息等方式才能获取的一些值,可以采用代码块。
public class A{
int a = 10;
public A(){
}
{
... //非静态代码块
}
static{
... //静态代码块
}
}
静态代码块和非静态代码块的相同点:
-
用于初始化类的信息。
-
内部可以声明变量、调用属性或方法、编写输出语句等操作。
-
如果声明多个代码块,则按照声明的先后顺序(即在类中,谁在上面,谁先执行)执行。
静态代码块的执行总先于非静态代码块的执行。
不同点:
-
静态代码块随类的加载而执行,只执行一次;非静态代码块随对象的创建而执行,可执行多次。
理解的时候,可以把非静态代码块当作类中直接调用父类的那些构造器的一部分,放在那些构造器的前几行(但在父类的构造器后),一调用构造器就先执行非静态代码块。
若调用的构造器使用了this(...),非静态代码块不看成这些构造器的一部分,而看成在this(...)里面,直到找到那个直接调用父类的构造器,将代码视为其一部分。
public A(){ //执行的时候可以理解成这样 [super();] //父类构造器,可以不写,默认调用父类空参构造器。 { ... //非静态代码块 } System.out.println("我是直接调用父类的构造器"); ... } public A(int a){ this(); //使用了this(),则没有调用父类的构造器,属于间接调用,因为A()中调用了父类构造器。 System.out.println("我不是直接调用父类的构造器"); } Base base = new Base(1,"lc"); /* new Base(1,"lc")调用的是Base(int b, String name)构造器,其中又调用了this(b),即public Base(int b)。 public Base(int b)又调用this(),即public Base()。 public Base()没有显式给出调用父类构造器,则默认调用父类空参构造器。从以上描述可以看出,public Base()是类中直接调用父类的构造器,非静态代码块可以看为它的一部分,但是在父类的构造器之后。 public A()是类中直接调用其父类Object的构造器,而Object没有任何输出,然后执行类A的非静态代码块,再执行类A构造器的其余代码,执行完再执行Base的非静态代码块,再执行Base()的其余代码以及其他构造器的代码。 */ /* 结果为: 我是代码块A 我是构造器D 我是代码块Base 我是构造器A 我是构造器B 我是构造器C */ class Base extends A{ int b = 1; String name; { System.out.println("我是代码块Base"); } public Base(){ System.out.println("我是构造器A"); } public Base(int b) { this(); this.b = b; System.out.println("我是构造器B"); } public Base(int b, String name) { this(b); this.name = name; System.out.println("我是构造器C"); } } class A{ { System.out.println("我是代码块A"); } public A() { System.out.println("我是构造器D"); } }
-
静态代码块内部只能调用静态的属性或方法,不能调用非静态的属性、方法。非静态代码块中非静态的或静态的属性、方法都可以调用。
非静态代码块的意义:如果多个重载的构造器有公共代码,并且这些代码都是先于构造器其他代码执行的,那么可以将这部分代码抽取到非静态代码块中,减少冗余代码。
静态代码块的意义:当静态变量的初始化很复杂时,可以使用静态代码块。
给实例属性赋值的执行先后次序:
- 默认值
- 显式初始化或代码块初始化(谁放上边,谁先执行)
- 构造器中初始化
- 通过“对象.属性”赋值
final
final 代表最终的,即修饰的内容不能修改。
final 能够修饰的结构:类、方法、变量。
-
final 修饰类:该类无法被继承。
-
final 修饰方法:该方法无法被程序。
-
final 修饰变量(成员变量和局部变量都行),一旦赋值,就无法更改。
-
成员变量赋值
-
显式赋值
-
代码块中赋值
-
构造器中赋值
-
-
局部变量赋值
- 方法内的局部变量,调用之前必须赋值。
- 方法的形参,调用时传给形参的值无法更改。
-
-
final 和 static 一起修饰的成员变量,称为全局常量。
final 修饰引用类型变量时,其指向的对象的地址值不能变,即不能再给这个变量赋另外的对象,但对象里面的属性值能修改。
abstract
abstract 代表抽象的,可以修饰类和方法。
-
修饰类:该类为抽象类,不能实例化,包含构造器。
-
修饰方法:只有方法的声明,没有方法体。无需具体实现,实现是子类的事。
有抽象方法的类一定是抽象类,没有抽象方法的类也可以是抽象类。
子类必须重写完所有抽象方法才能实例化。
public abstract void eat(); //抽象方法声明,没有“{}”。
抽象类也可以继承抽象类。
可以创建引用类型为抽象类的数组,但存放的元素必须是,继承了抽象类并重写完所有抽象方法的类所创建的实例。
接口
接口是一种规范。想要有某种功能,实现对应的接口就行。类和接口是“has-a”的关系,接口和类不是继承关系,而是实现关系。一个类可以实现多个接口。
关键字:interface
属性默认用
public final static
修饰,可以省略不写。声明抽象方法默认用
public abstract
修饰,可以省略不写。不可以声明构造器、代码块。
类必须重写接口中所有的抽象方法,否则必须声明为抽象类。
接口可以继承接口,且可以多继承。
interface cc extends AA,BB{}
接口也有多态,也可以作函数的形参,
接口名 base = new 实现类();
,只能调用接口有的方法,不能调实现类独有的方法。可以创建引用类型为接口的数组,但存放的元素必须是,重写完所有抽象方法的实现类所创建的实例。
Eatable[] eatable = new Eatable[3]; eatable[0] = new Chinese(); eatable[1] = new American();