知识点
1.类:通过对一类事务的共有特征和行为进行抽取,将特征抽取为属性,将行为抽取为方法,类是对象的概括。
2.方法:权限修饰符 返回值类型 方法名 参数列表 方法体
3.构造方法:在类中与类同名而没有返回值类型的方法。
①构造方法可以用public、protected、default、private修饰,一般使用public修饰是为了限制外部使用方便。使用private是为了私有化防止类外创建对象。
②构造方法中可以写return语句,用来回避掉一些不符合逻辑的内容。
构造方法可以有返回值????
构造方法没有返回值类型、void也不行,但是在创建对象的时候隐式地返回了该类对象。
4.this关键字
1)this调用本类中的属性--成员变量。this.name
2)this调用本类中的其他方法。this.eat()
3)this调用本类中的其他构造方法,需要放在首行。
this存在的意义????
①this是一个对象,在不会产生混淆的地方,可以省略this,代码如下。
public class test {
public static void main(String[] args) {
Student s1 = new Student();
s1.name = "zhangsan";
Student s2 = new Student();
s2.name = "lisi";
s1.talk();
s2.talk();
}
}
class Student{
String name;
public void talk(){
System.out.println("my name is "+ name);
}
}
②当方法中有参数时,如果方法参数和成员变量的名字一样,这个时候不加this的话,会根据“就近原则”调用最近的值,代码如下。
public class test {
public static void main(String[] args) {
Student s1 = new Student();
s1.name = "zhangsan";
Student s2 = new Student();
s2.name = "lisi";
s1.talk("1");
s2.talk("2");
}
}
class Student{
String name;
public void talk(String name){
System.out.println("my name is "+ name);
}
}
对于上述代码而言,可以使用this关键字调用name属性也可以不使用,但是如果不使用的话会造成一定混淆,此时虽然s1和s2分别有自己的name为:zhangsan和lisi,但是调用talk方法时输出的是my name is 1和my name is 2,说明输出结果和对象本身的值没有关系,而是与参数一致。
③如果需要输出对象本身的name值,需要加上this,代码如下。
public class test {
public static void main(String[] args) {
Student s1 = new Student();
s1.name = "zhangsan";
Student s2 = new Student();
s2.name = "lisi";
s1.talk("1");
s2.talk("2");
}
}
class Student{
String name;
public void talk(String name){
System.out.println("my name is "+ this.name);
}
}
5.super关键字
1)子类中访问父类属性、父类方法、父类构造函数。
2)注意:
①子类的构造过程中必须调用其父类的构造方法。
②如果子类构造没有显示调用父类构造,那么默认调用父类无参构造。
③如果显示的调用构造方法,那么构造方法必须在子类的第一行。
④如果子类构造方法中既没有显示调用父类的构造方法,而父类中也没有无参构造,就编译报错。
2)super和this比较:
①this.变量调用的是当前对象;super.变量/方法名调用的是父类中的变量/方法。
②this(参数)调用当前类中的构造方法;super(参数)调用的是父类中的构造方法。
注意:
I.初始化子类时,父类的构造先执行,因为每一个子类的构造的第一行都有一条隐式super;
II.this()和super()都只能写在构造的第一行;
III.this()和super()不能存在于同一个构造中:第一行只有一个;this()调用当前类的另一个构造,那么另一个构造中肯定有一个父类构造,再添加super就又会调用一次父类构造,编译器不会通过。
IV.this和super不能用于static修饰了的变量、方法、代码块,因为this和super都指对象。
6.static:静态先于对象而存在。
1)静态变量
类加载时在方法区中的静态区中初始化,可以通过对象/类名调用,静态变量被所有对象共享。
2)静态方法
静态方法不可以被重写(静态属于类),可以被重载、隐藏。
3)静态代码块
随着类的加载而加载到方法区,只是加载并不执行,在创建类对象或者执行方法时才执行,并且只执行一次。
4)静态内部类
7.final
1)修饰变量:如果是基本类型表示值不可以更改;如果是引用类型表示引用不可以更改。
2)修饰方法:该方法不能被重写。
3)修饰类:该类不能被继承。
8.重载
1)方法名一致、参数列表不一致,通过个数和类型的不同来区分不同的函数;
2)方法的重载跟返回值类型和修饰符无关,重载是发生在本类中的。
9.重写
1)方法签名一致。
2)父类方法的返回值类型是void、基本数据类型、最终类时,子类重写该方法的返回值类型必须一致。
3)父类方法的返回值类型是引用类型时,子类返回值类型要么一致要么是父类的子类。
4)子类方法的权限修饰符范围大于等于父类方法权限修饰符的范围。
注意:八种数据类型是平等的,没有继承关系。
10.重写和重载的区别
1)Overload重载;Override重写(覆盖).
2)final修饰的方法不能被重写,可以被重载;static修饰的方法不能被重写,可以被重载。注意:final重写直接报错;static不报错,子类中的方法会被隐藏,会调用父类中的方法。
3)重载Overload表示一个类中可以有多个名称相同的方法,但这些方法的参数列表不相同(参数个数或者参数类型不同)。
①只能通过不同的参数类型、个数、顺序(当然同一个方法内的参数类型必须不一样,可以是fun(int,float),但不可以是fun(int,int))。
②不能通过权限修饰符、返回类型、抛出的异常不同来进行方法的重载。
③方法的异常类型和数目不会对重载有影响。
④对于继承而言,如果父类中的某个方法用private修饰,那么就不能在子类中对其进行重载,如果子类定义了该方法只是一个全新的方法不是重载。
4)重写Override表示子类中的方法可以与父类中的某个方法的名称、参数完全相同,通过子类创建的实例对象调用这个方法时,调用的是子类中重写过的方法,相当于把父类中定义的相同的方法给覆盖掉了,是多态的一种表现。
11.面向对象之多态:同一个名字在不同时期代表不同功能
1)编译时多态:重载
2)运行时多态:向上造型、重写
12.面向对象之封装
1)方法
2)属性私有化
3)内部类
13.抽象
1)抽象方法
①没有方法体用abstract修饰。
②抽象方法只能在抽象类中。
2)抽象类
①可以有属性和方法(包括构造方法、抽象方法和普通方法)。
②不能创建对象。
14.内部类:原因是平时编写代码时可能用到的场景不多,用得最多的是在有事件监听的情况下.在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。
1)内部类存在的意义???
①内部类可以很好的实现隐藏
②内部类拥有外围类的所有元素的访问权限
③可以实现多重继承
④可以避免接口中的方法和同一个类中的方法同名的问题
2)如何区分内部类和普通类???
看编译后的文件是否有$符号。
3)分类
成员内部类:它的定义为位于另一个类的内部.
方法内部类:定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
匿名内部类:匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。
静态内部类:静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。
15.接口--单继承多实现
1)用interface声明一个接口,接口中的属性默认用public static final修饰,可以使用接口名.属性来调用因为是static的。
2)接口中的方法默认用public修饰。如下代码是否正确????不正确,接口中方法默认用public修饰
interface a{
void m();
}
class b implements a{
void m(){}
}
有趣的题:
1.规定八种基本数据类型的目的?
①限定数据范围;
②占用内存的实际大小;
③申请的空间中只能存放指定类型的数据;
2.数组有没有length()这个方法?
数组没有length()这个方法,有length的属性。String有length()这个方法。
3.java中只有值传递?
①基本类型而言:传递的是值的拷贝;代码如下
public class callByValue2 {
public static void main(String[] args) {
int x = 100;
addFun(x);
System.out.println(x);
}
private static void addFun(int x) {
x ++;
}
}
②引用类型而言:传递的是地址的拷贝,改变的是地址指向的值而不是地址本身;代码如下
import org.omg.Messaging.SYNC_WITH_TRANSPORT;
/**
* 总结:java中没有按引用传递,只有按值传递
*/
public class callByValue {
public static void main(String[] args) {
Person p1 = new Person("jack",20);
Person p2 = new Person("tom",15);
System.out.println("before add p1.age="+p1.getAge());
addAge(p1);
// 发现p1的age并没有交换,说明只是把值传递了进去
System.out.println("after add p1.age="+p1.getAge());
System.out.println("before swap p1.name="+p1.getName()+",p1.age="+p1.getAge()+"\tp2.name="+p2.getName()+",p2.age="+p2.getAge());
swap(p1,p2);
// 经过交换发现p1和p2并没有交换,说明只是把地址指向的值传递了进去,地址本身没有改变
System.out.println("after swap p1.name="+p1.getName()+",p1.age="+p1.getAge()+"\tp2.name="+p2.getName()+",p2.age="+p2.getAge());
}
public static void addAge(Person x){
// x作为p1的一个拷贝,x的age增加
x.add(200);
System.out.println("x.age="+x.getAge());
}
public static void swap(Person a,Person b){
// 如果是callByReference按引用传递,那么可以实现交换值
// 创建中间对象,用于交换a和b
Person t = a;
a = b;
b = t;
// 但是这里交换的是两个对象的拷贝
System.out.println("a.name="+a.getName()+",a.age="+a.getAge());
System.out.println("b.name="+b.getName()+",b.age="+b.getAge());
}
}
class Person{
private String name;
private int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
public void add(int a){
age += a;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
4.携程面试题?
/**
* 携程面试题:输出结果为?
*/
public class testXC {
public static void main(String[] args) {
Base base = new Sub();
}
}
class Base{
private String baseName = "base";
public Base(){
callName();
}
public void callName() {
System.out.println(baseName);
}
}
class Sub extends Base{
private String baseName = "sub";
public void callName(){
System.out.println(baseName);
}
}
考察知识点
(1)类加载机制和程序运行顺序
①首先:main方法中初始化base对象(父类声明子类创建),调用子类Sub中的无参构造,默认有一行super();语句去调用父类Base的父类构造。
②然后父类的无参构造又调用callName方法,但是由于子类重写了callName方法,所以调用的是子类中的callName方法。
③在子类Sub中调用callName方法的时候,需要使用成员变量baseName,但此时baseName还没有初始化完成所以输出结果为null。
(2)继承
子类继承父类的时候,对于同名属性不会覆盖,而是将父类中的属性隐藏掉。
Java保证了一个对象初始化之前其父类必须被初始化完成。
如何保证?
Java强制要求任何类的构造函数的第一句必须是调用父类的构造函数super语句或者是类中定义的其他构造函数this语句。
如果没有构造函数,系统会添加默认的无参构造函数,如果我们的构造函数中没有显示调用父类构造函数,那么编译器会自动生成一个父类的无参构造函数。
(3)多态
父类中的构造函数调用了callName方法,因为父类声明对象子类创建所以真正调用的是子类Sub中的callName方法,因此当前的this指Sub类的对象。
构造器的初始化顺序大概是:父类静态块、子类静态块、父类初始化语句、父类构造方法、子类初始化语句、子类构造器
具体解释:
1)Base b = new Sub();// 在main方法中声明父类对象b对子类Sub的引用,Java类加载器将Base类和Sub类加载到JVM中,完成类的初始化。
2)JVM对Base和Sub的成员变量开辟内存空间且值均为null。在初始化Sub对象前,首先JVM就在堆中开辟内存并将子类Sub和父类Base(注意:父类中的baseName已被隐藏)中的baseName赋值为null。
3)调用父类的无参构造
调用Sub中的无参构造,因为子类没有构造,会使用默认提供的无参构造,调用super使用父类中的无参构造。
4)callName被重写,调用子类Sub中的callName方法。
5)调用子类的callName方法,实际过程为
public Sub(){
super();
baseName = "Sub";
}// 所以baseName赋值在打印之前,结果为null
5.练习题
class ClassA {
public ClassA() {
System.out.println("A");
}
}
class ClassB {
public ClassB() {
System.out.println("B");
}
}
class ClassC {
ClassA a = new ClassA();
ClassB b;
public ClassC() {
System.out.println("C");
b = new ClassB();
}
}
public class Test {
public static void main(String[] args){
ClassC c = new ClassC();
}
}
调用构造器的具体步骤:
1)数据域被初始化为默认值(0或者null)。
2)调用父类构造。
3)按照类中的声明次序,有静态先走静态,依次执行初始化语句和初始化代码块。
4)执行构造方法体。
静态优先,父类优先。
6.观察以下代码,是否有误???
interface Playable {
void play();
}
interface Bounceable {
void play();
}
interface Rollable extends Playable, Bounceable {
Ball ball = new Ball("PingPang");
}
class Ball implements Rollable {
private String name;
public String getName() {
return name;
}
public Ball(String name) {
this.name = name;
}
public void play() {
ball = new Ball("Football");
System.out.println(ball.getName());
}
}
有误,首先原因不是interface Rollable extends Playable, Bounceable这一句,interface可以继承多个interfaces,问题是在Ball ball = new Ball("PingPang")这一句,接口中的属性默认用public static final修饰,然后在Ball类中 ball = new Ball("Football")这句话而言改变了ball的引用,但是final修饰的变量的引用是不能被改变的,所以编译报错。