*/
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
color: #333;
background: #f8f8f8;
}
.hljs-comment,
.hljs-template_comment,
.diff .hljs-header,
.hljs-javadoc {
color: #998;
font-style: italic;
}
.hljs-keyword,
.css .rule .hljs-keyword,
.hljs-winutils,
.javascript .hljs-title,
.nginx .hljs-title,
.hljs-subst,
.hljs-request,
.hljs-status {
color: #333;
font-weight: bold;
}
.hljs-number,
.hljs-hexcolor,
.ruby .hljs-constant {
color: #099;
}
.hljs-string,
.hljs-tag .hljs-value,
.hljs-phpdoc,
.tex .hljs-formula {
color: #d14;
}
.hljs-title,
.hljs-id,
.coffeescript .hljs-params,
.scss .hljs-preprocessor {
color: #900;
font-weight: bold;
}
.javascript .hljs-title,
.lisp .hljs-title,
.clojure .hljs-title,
.hljs-subst {
font-weight: normal;
}
.hljs-class .hljs-title,
.haskell .hljs-type,
.vhdl .hljs-literal,
.tex .hljs-command {
color: #458;
font-weight: bold;
}
.hljs-tag,
.hljs-tag .hljs-title,
.hljs-rules .hljs-property,
.django .hljs-tag .hljs-keyword {
color: #000080;
font-weight: normal;
}
.hljs-attribute,
.hljs-variable,
.lisp .hljs-body {
color: #008080;
}
.hljs-regexp {
color: #009926;
}
.hljs-symbol,
.ruby .hljs-symbol .hljs-string,
.lisp .hljs-keyword,
.tex .hljs-special,
.hljs-prompt {
color: #990073;
}
.hljs-built_in,
.lisp .hljs-title,
.clojure .hljs-built_in {
color: #0086b3;
}
.hljs-preprocessor,
.hljs-pragma,
.hljs-pi,
.hljs-doctype,
.hljs-shebang,
.hljs-cdata {
color: #999;
font-weight: bold;
}
.hljs-deletion {
background: #fdd;
}
.hljs-addition {
background: #dfd;
}
.diff .hljs-change {
background: #0086b3;
}
.hljs-chunk {
color: #aaa;
}
#container {
padding: 15px;
}
pre {
border: 1px solid #ccc;
border-radius: 4px;
display: block;
background-color: #f8f8f8;
}
pre code {
white-space: pre-wrap;
}
.hljs,
code {
font-family: Monaco, Menlo, Consolas, 'Courier New', monospace;
}
:not(pre) > code {
padding: 2px 4px;
font-size: 90%;
color: #c7254e;
background-color: #f9f2f4;
white-space: nowrap;
border-radius: 4px;
}
-->
权限修饰符
权限修饰符包括public、private、protected和不加任何修饰符的default,它们都可以修饰方法和变量。其中public和默认的default(不加任何修饰符)这两个还可以修饰class。private和protected修饰类的情况只能在使用内部类时修饰,正常情况下不能使用这两个修饰符修饰类。
(1).public
:用public修饰的变量及方法,包内及包外的任何类(包括子类和普通类)均可以访问;
(2).protected
:用protected修饰的变量及方法,包内的任何类及包外那些继承了该类的子类才能访问,protected重点突出继承;
(3).default
:没有用public、protected及private中任何一种修饰,其访问权限为default默认权限。默认访问权限的类、类属变量及方法,包内的任何类(包括继承了此类的子类)都可以访问它,而对于包外的任何类都不能访问它(包括包外继承了此类的子类)。default重点突出包;
(4)private
: 用private修饰的变量及方法,只有本类可以访问,而包内包外的任何类均不能访问它。
就一句话:protected修饰符所修饰的变量和方法,只可以被子类访问,而不管子类是不是和父类位于同一个包中。default修饰符所修饰的变量和方法,只可被同一个包中的其他类访问,而不管其他类是不是该类的子类。protected属于包修饰符,还是子类修饰符,而default属于包修饰符。
从权限严格角度来说,private < default < protected < public
在考虑default修饰的权限时,它是包修饰符,其中没有加入到包中的"裸体类"属于同一个隐式的包中,因此可以互相访问。
例如,前面的person和student的继承关系中,将父类成员变量name加上private修饰符,于是下面的代码将编译出错。因为new子类对象时,构造方法中赋值给this.name,而这个name是继承自父类的,它是private的。因此对于子类来说,这个成员变量属于能看到,不能引用、不能操作的摆设属性。
class Person {
private String name;
int age;
}
class Student extends Person {
int studentID;
Student(int id,String name,int age) {
this.name = name;
this.age = age;
this.studentID = id;
}
}
public class Inherit {
public static void main(String[] args) {
Student s1 = new Student(1,"Malongshuai",23);
}
}
方法的重写(overwrite/override)
父类定义的成员相对来说都比较粗糙,当子类继承时,难免无法适当地描述子类。因此当子类对从父类继承的方法不满意时,可以重写方法。
例如Person类能eat(),但girl类吃饭是淑女的吃,boy类吃饭是粗鲁的吃。girl类很不满意,因为父类的eat()只能描述吃,不能描述怎么吃。于是girl类就重写eat()方法,让吃这个方法符合自身的淑女形象。
重写方法必须和被重写的方法具有相同的方法名称、参数列表和返回类型。重写的方法不能比被重写的方法权限更严格。从方法访问的角度来说,父类的方法都能被访问,子类重写后的方法却不能被访问,这显然是不合理的,且即使这是能访问父类方法,但重写的意义就丢失了。
重写方法时,最佳实践方式是copy整个被重写的方法的定义语句。因为即使重写方法的名称改变了,编译也不会出错。例如重写eat()结果写成了Eat(),编译是不会有任何错误出现的,此时它没有重写,而是新定义了一个Eat()方法。
class Person {
String name;
int age;
void eat() { System.out.println("eating...");}
}
class Student extends Person {
int studentID;
Student(int id,String name,int age) {
this.name = name;
this.age = age;
this.studentID = id;
}
void eat() { System.out.println("graceful eating");} //重写
void study() {System.out.println("studing...");}
}
public class Inherit {
public static void main(String[] args) {
Student s1 = new Student(1,"Malongshuai",23);
System.out.println(s1.studentID+","+s1.name+","+s1.age);
s1.eat(); //调用重写后的eat方法
}
}
super关键字
this关键字指向对象自身,而super关键字则指向对象中的父对象。如下图:
super既可以用来引用父对象的成员变量,也可以用来调用父对象的方法。例如下面的代码:
class FatherClass {
public int value;
public void f(){
value = 100;
System.out.println("FatherClass.value="+value);
}
}
class ChildClass extends FatherClass {
public int value;
public void f() {
super.f(); //虽然f()要重写,但父对象的f()函数还有一些用武之地来发挥余热
value = 200;
System.out.println("ChildClass.value="+value);
System.out.println(value);
System.out.println(super.value);
}
}
public class TestInherit {
public static void main(String[] args) {
ChildClass cc = new ChildClass();
cc.f();
}
}
new出子对象时,父类和子类中都有value属性,它们都采用的初始化值0。当执行cc.f()时,调用子类的f()方法,该方法首先调用父对象的f()方法,父f()方法先将value赋值为100,这个value是父对象的属性,然后回到子f()中赋值value为200,这个value是子对象自身的value,随后输出的两个value都是子对象中的value属性,最后的super.value是父对象中的value属性。
虽然在图中看上去super和this的地位是相同的,但实际上它们之间很不公平,不公平之处在于有引用变量(上图中的cc)指向子对象,所以能够使用"return this"代码来返回一个子对象,但却不能使用"return super"来返回子对象中的父对象,因为没有引用变量指向父对象。
关于super调用的成员变量,需要区分清楚是子对象中的属性还是父对象中的属性。如果子对象和父对象中有同名属性var,在没有指定"this.var"和"super.var"时,仅模糊地指定var时将优先取子对象的属性,如果子对象中没有某属性,则var表示的是父对象中的属性。例如:
class Student extends Person {
int studentID;
int age = 33;
Student(int id) {
this.name = super.name + "x";
this.age = age + 2; //右边的age是子对象的属性,但如果将"int age = 33;"注释,则age是父对象的属性
this.studentID = id;
}
}
继承时构造方法的重写super()
子对象中总是包含父对象,这个父对象是怎么来的?对象都是通过构造方法构造出来的,因此在new子类对象的时候,会调用对应的子类构造方法构造子对象,正是这个时候使用super()方法表示调用父类构造方法将父对象构造出来的。
在写构造父对象的代码时有以下几个规则:
- 使用子类构造方法构造子对象时,必须要构造父对象。
- 子类可以在自己的构造方法中使用super(args)来调用父类的构造方法。同理,可以使用this(args)来调用本类其他的构造方法。
- super(args)必须写在子类构造方法中的第一行,因为要先构造出父对象,再慢慢填补子对象自身。如果没有显式书写super(args),则默认在第一行处调用父类无参数的构造方法,等价于super()。
- 如果子类构造方法中调用的super(args)在父类中不存在对应参数列表的构造方法,则编译错处。这包括没有显式指定super()时,且父类又重载了构造方法使得父类中没有了无参数的构造方法时。
class Person {
String name;
int age;
Person() {
System.out.println("Person()");
}
Person(String name,int age) {
this.name = name;
this.age = age;
System.out.println("Person(arg1,arg2)");
}
Person(String name) {
this.name = name;
age = 20;
System.out.println("Person(arg1)");
}
}
class Student extends Person {
int studentID;
Student(int id) {
super("Malongshuai",23); //第一行调用父类构造方法构造父对象,且是含有两个参数的Person(arg1,arg2)
this.name = super.name + "X"; //调用父对象中的name属性
this.age = age + 2; //也是调用父对象中的属性age
this.studentID = id;
}
}
public class TestSuper {
public static void main(String[] args) {
Student s1 = new Student(1);
System.out.println(s1.studentID+", "+s1.name+", "+s1.age);
}
}
如果将"super("Malongshuai",23);"修改为super("Malongshuai"),则表示调用父类的Person(arg1)构造方法。如果改为super(),则表示调用父类的Person()构造方法。如果省略不写super,则等价于super()。
Object类
除了明确定义了从某个父类继承的子类,java中的所有类都是从java.lang包中的Object类继承来的。也就是说,Object类是所有类继承的根,也就是它们的祖宗。一级继承一级,最终的根总是Object类。
这个类里提供了几个方法,但基本上所有方法都建议重写,因为它的级别太高,抽象化的太严重,它的提供的那些方法也就太大众化。
toString()
在和对象做数据连接时,将自动调用该类的toString()方法。例如System.out.println("Hello" + Person)
时,等价于System.out.println("Hello" + Person.toString())
例如:
public class TTString {
public static void main(String [] args) {
Person p = new Person();
System.out.println(p);
System.out.println(p.toString());
}
}
class Person {}
编译并运行,查看toString()的运行结果。
D:\myjava
λ javac TTString.java
D:\myjava
λ java TTString
Person@15db9742
Person@15db9742
toString()的结果是"类名@hex(hashcode)"。官方建议,任何子类都应该重写该方法。例如:
public class TTString {
public static void main(String [] args) {
Person p = new Person();
System.out.println(p);
}
}
class Person {
public String toString() { //重写toString()
return "Hello World";
}
}
对象的比较"=="和equals()
对象与对象之间是否有相等关系?一般可以认为,如果两个对象的对象内容完全相同,将认为是相等的对象。
在进行对象比较时,"=="比较的是两个对象的引用地址,因此两个对象使用"=="比较时是绝对不会相等的。Object类中的equals(),基本等价于"==",因此也无法正确比较对象是否相等。所以官方手册建议重写equals()方法。在String类中已经重写好了。
例如,使用String类中的equals()。
public class TTequals {
public static void main(String [] args) {
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
}
final关键字
final表示最终的意思,它可以修饰变量、方法和类。
- final变量的值不能被改变。
- (1).final的成员变量不可改变。
- (2).final的局部变量和形参不可改变。
- final的方法不能被重写。
- final的类不能被继承。
也就是说,要想让变量只读、方法不可改变或类的继承到此结束,就用final进行修饰。
注:若您觉得这篇文章还不错请点击右下角推荐,您的支持能激发作者更大的写作热情,非常感谢!