JAVA学习
作用域
public
定义为public的class interface可以被其他任何类访问:
package abc;
public class Hello {
public void hi() {
}
}
上边的hello是public 因此可以被其他包的类访问:
package xyz;
class Main {
void foo() {
// Main可以访问Hello
Hello h = new Hello();
}
}
定义为public的field method 可以被其他类访问,前提是首先有访问class的权限
private
定义为private的field method无法被其他类访问:
package abc;
public class Hello {
// 不能被其他类调用:
private void hi() {
}
public void hello() {
this.hi();
}
}
private访问权限被限定在class内部,而且与方法声明顺序无关。推荐把private方法放到后面,因为public方法定义了类对外提供的功能,阅读代码的时候,应该先关注public方法:
package abc;
public class Hello {
public void hello() {
this.hi();
}
private void hi() {
}
}
java支持嵌套类,如果一个类内部还定义了嵌套类,嵌套类拥有访问private的权限
public class Main {
public static void main(String[] args) {
Inner i = new Inner();
i.hi();
}
// private方法:
private static void hello() {
System.out.println("private hello!");
}
// 静态内部类:
static class Inner {
public void hi() {
Main.hello();
}
}
}
定义在class内部的class称为嵌套类
protected
protected作用于继承关系,定义为protected的字段和方法可以被子类访问,以及子类的子类
package abc;
public class Hello {
// protected方法:
protected void hi() {
}
}
上面的protected方法可以被继承的类访问
package xyz;
class Main extends Hello {
void foo() {
// 可以访问protected方法:
hi();
}
}
package
包的作用域是指一个类允许访问同一个package的没有public private修饰的class,以及没有public protected private修饰的字段和方法
package abc;
// package权限的类:
class Hello {
// package权限的方法:
void hi() {
}
}
只要在同一个包中,就可以访问package权限的class field和method
package abc;
class Main {
void foo() {
// 可以访问package权限的类:
Hello h = new Hello();
// 可以调用package权限的方法:
h.hi();
}
}
局部变量
方法内部定义的变量称为局部变量,局部变量作用域从变量声明处到对应的块结束,方法参数也是局部变量。
package abc;
public class Hello {
void hi(String name) { // ①
String s = name.toLowerCase(); // ②
int len = s.length(); // ③
if (len < 10) { // ④
int p = 10 - len; // ⑤
for (int i=0; i<10; i++) { // ⑥
System.out.println(); // ⑦
} // ⑧
} // ⑨
} // ⑩
}
我们观察上面的hi()方法代码:
方法参数name是局部变量,它的作用域是整个方法,即①~⑩;
变量s的作用域是定义处到方法结束,即②~⑩;
变量len的作用域是定义处到方法结束,即③~⑩;
变量p的作用域是定义处到if块结束,即⑤~⑨;
变量i的作用域是for循环,即⑥~⑧。
使用局部变量时,应该尽可能把局部变量的作用域缩小,尽可能延后声明局部变量。
final
final不是访问权限,final有很多作用
- 用final修饰的class可以阻止被继承;
- 用final修饰的method可以阻止被子类覆写
- 用final修饰的field可以阻止被重新赋值
package abc;
public class Hello {
private final int n = 0;
protected void hi() {
this.n = 1; // error!
}
}
- 用final修饰的局部变量可以阻止被重新赋值
TIPS
如果不确定是否需要public,就不声明为public,即尽可能少地暴露对外的字段和方法。
把方法定义为package权限有助于测试,因为测试类和被测试类只要位于同一个package,测试代码就可以访问被测试类的package权限方法。
一个.java文件只能包含一个public类,但可以包含多个非public类。如果有public类,文件名必须和public类的名字相同。
内部类
定义在类内部的类,称之为内部类(Nested Class)
Inner Class
如果一个类定义在另一个类的内部,这个类就是Inner Class:
class Outer {
class Inner {
// 定义了一个Inner Class
}
}
上述的Outer是一个普通类,而Inner 是一个Inner class 与普通类有个最大的不同,Inner Class的实例不能单独存在,必须依附于一个Outer Class的实例。
public class Main {
public static void main(String[] args) {
Outer outer = new Outer("Nested"); // 实例化一个Outer
Outer.Inner inner = outer.new Inner(); // 实例化一个Inner
inner.hello();
}
}
class Outer {
private String name;
Outer(String name) {
this.name = name;
}
class Inner {
void hello() {
System.out.println("Hello, " + Outer.this.name);
}
}
}
要实例化一个Inner,必须首先创建一个Outer的实例,然后调用Outer实例的new来创建Inner实例:
Outer.Inner inner = outer.new Inner();
Inner Class 除了有一个this指向他自己,还隐含地持有一个Outer Class实例,可以用Outer。this访问这个实例,所以,实例化一个Inner Class不能脱离Outer实例。
Inner Class和普通地Class相比,除了能引用Outer实例外,还有一个额外地特权,可以修改Outer class的private字段。
观察Java编译器编译后的.class文件可以发现,Outer类被编译为Outer.class,而Inner类被编译为Outer$Inner.class。
Anonymous Class
另外一种定义Inner Class的方法,不需要再Outer Class中明确定义这个class,而是在方法内部,通过匿名类(Anonymous Class)来定义,
public class Main {
public static void main(String[] args) {
Outer outer = new Outer("Nested");
outer.asyncHello();
}
}
class Outer {
private String name;
Outer(String name) {
this.name = name;
}
void asyncHello() {
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello, " + Outer.this.name);
}
};
new Thread(r).start();
}
}
在方法内部实例化了一个Runnable ,Runnable本身是接口,接口不能实例化,这里实际上是定义了一个实现了Runnable接口的匿名类,并且通过new实例化该匿名类,然后转型为Runnable,再定义匿名类的时候就必须实例化它 定义匿名类的写法如下:
Runnable r = new Runnable() {
// 实现必要的抽象方法...
};
观察Java编译器编译后的.class文件可以发现,Outer类被编译为Outer.class,而匿名类被编译为Outer$1.class。如果有多个匿名类,Java编译器会将每个匿名类依次命名为Outer$1、Outer$2、Outer$3……
除接口外,匿名类也完全可以继承自普通类,关系以下代码:
import java.util.HashMap;
public class Main {
public static void main(String[] args) {
HashMap<String, String> map1 = new HashMap<>();
HashMap<String, String> map2 = new HashMap<>() {}; // 匿名类!
HashMap<String, String> map3 = new HashMap<>() {
{
put("A", "1");
put("B", "2");
}
};
System.out.println(map3.get("A"));
}
}
map1是一个普通的HashMap实例,但map2是一个匿名类实例,只是该匿名类继承自HashMap。map3也是一个继承自HashMap的匿名类实例,并且添加了static代码块来初始化数据。观察编译输出可发现Main$1.class和Main$2.class两个匿名类文件。
Static Nested Class
最后一种内部类和Inner Class类似,但是使用stati修饰 称为静态内部类(static nested class)
public class Main {
public static void main(String[] args) {
Outer.StaticNested sn = new Outer.StaticNested();
sn.hello();
}
}
class Outer {
private static String NAME = "OUTER";
private String name;
Outer(String name) {
this.name = name;
}
static class StaticNested {
void hello() {
System.out.println("Hello, " + Outer.NAME);
}
}
}
用static修饰的内部类和Inner Class有很大的不同,它不再依附于Outer的实例,而是一个完全独立的类,因此无法引用Outer.this,但它可以访问Outer的private静态字段和静态方法。如果把StaticNested移到Outer之外,就失去了访问private的权限。
class path和jar
class path
class是JVM用到的一个环境变量,它用来指示JVM如何搜索class
因为Java是编译型语言,源码文件是.java,而编译后的.class文件才是真正可以被JVM执行的字节码。因此,JVM需要知道,如果要加载一个abc.xyz.Hello的类,应该去哪搜索对应的Hello.class文件。
jar
jar包相当于目录,可以包含很多.class文件,方便下载和使用;