Java笔记(二)类
类
一、类的基础
1.类---一种自定义数据类型。
2.与方法内创建局部变量不同,在创建对象的时候,所有的实例变量都会分配
一个默认值,这与创建数组的时候是类似的。
3.在{}对实例变量内赋值:
int x;
int y;
{
x = 1;
y = 2;
}
在新创建一个对象的时候会先调用这个初始化,然后再执行构造函数。
静态变量可使用static{}初始化:
private static int STATE_ONE;
private static int STATE_TOW;
static
{
STATE_ONE = 1;
STATE_TOW = 2;
}
静态初始化代码块在类加载的时候执行,这是任何对象创建之前,且只执行一次。
4.对象和数组一样有两块内存,保存地址的部分分配在栈中,而保存实际内容的部分
分配在堆中。
5.jar包:
打包的是编译后文件,将多个编译后文件打包,方便其他程序调用。
到编译后的class文件根目录,运行:
jar -cvf <包名>.jar <最上层包名>
如何使用jar包:将其加入classpath中即可。
二、类的继承
1.使用继承的好处:一是可以复用代码,二是不同的子类对象可以方便地统一管理。
2.向上转型:转换为父类的类型。
3.多态:一种类型变量可以引用多种实际类型对象。
4.动态绑定:在调用时实际调用的是子类的方法。
5.多态和动态绑定是计算机程序的一种重要的思维方式,使得操作对象的程序
不需要关心对象的实际类型。
6.重载:指方法名称相同但参数签名(参数的个数、类型或者顺序)不同。
7.重写:子类重写父类参数签名相同的方法。
8.注意:当有多个重名函数时,在决定调用哪个函数的过程中,首先是按照参数
类型进行匹配,换句话说,先寻找所有重载中最匹配的,然后再看变量的动态类型
进行动态类型绑定。
9.一个父类的变量是否能转换为子类的变量,取决于这个父类的
动态类型(即引用的对象类型)是不是这个子类或者该子类的子类。
10.重写时子类的方法不能降低父类方法的可见性。
11.用final关键子修饰的类不可被继承。
继承实现的基本原理:
举个例子:
基类:
public class Base {
public static int s;
private int a; static {
System.out.println("基类静态代码块, s = " + s);
s = 1;
} {
System.out.println("基类实例代码块, a = " + a);
a = 1;
} public Base() {
System.out.println("基类构造方法, a = " + a);
a = 2;
} protected void step() {
System.out.println("base s = " + s + " a = " + a);
} public void action() {
System.out.println("start:");
step();
System.out.println("end:");
}
}
子类:
public class Child extends Base{
public static int s;
private int a; static {
System.out.println("子类静态代码块, s = " + s);
s = 10;
} {
System.out.println("子类实例代码块, a = " + a);
a = 10;
} public Child() {
System.out.println("子类构造方法, a = " + a);
a = 20;
} protected void step() {
System.out.println("child s = " + s + " a = " + a);
}
}
调用:
public class Use { public static void main(String[] args) {
System.out.println("-----new Child()");
Child c = new Child();
System.out.println("\n-----c.action()");
c.action(); Base b = c;
System.out.println("\n------b.action()");
b.action();
System.out.println("\n------c.s: " + c.s);
System.out.println("\n------b.s: " + b.s);
}
} /*-----new Child()
基类静态代码块, s = 0
子类静态代码块, s = 0
基类实例代码块, a = 0
基类构造方法, a = 1
子类实例代码块, a = 0
子类构造方法, a = 10 -----c.action()
start:
child s = 10 a = 20
end: ------b.action()
start:
child s = 10 a = 20
end: ------c.s: 10 ------b.s: 1*/
过程详解:
1)类加载过程:
在Java中所谓的类加载是指将类的相关信息加载到内存中。在Java中,类是动态
加载的,当第一次使用这个类的时候才会加载,加载一个类时会查看其父类是否
加载,如果没有则会加载其父类。
存放类信息的内存区域在Java中被称为方法区(不同于堆和栈)。加载后方法区
就有了类的信息:
实例初始化代码包括了实例初始化代码块和构造方法。
本例中,类的加载大概在内存形成了类似上面的布局,然后分别执行了Base和Child的初始化代码。
2)对象的创建过程
类加载过后,new Child()就是创建Child实例。创建过程包括
1.分配内存
2.对所有实例变量赋予默认值
3.执行实例初始化代码
其中,分配的内存包括本类和所有父类的实例变量,但不包括任何类变量。
实例初始化代码从父类开始,再执行子类。
每个对象除了保存有实例变量外,还保存有类信息的引用。
3)方法调用过程
寻找要执行的实例方法的时候,先从对象的实例开始查找,找不到再查找父类。(这也是动态绑定的实现原理)
4)变量的访问过程
变量的访问是静态访问的,无论是类变量还是实际变量。比如例子中,如果变量a不是私有的
b.a访问的是Base中的变量,c.a访问的Child实例中的变量。
继承是一把双刃剑:
继承会破坏封装,而封装可以说是程序设计的基本原则,另外继承可能没有反应出is-a关系。
1.继承会破坏封装
继承可能破坏封装是因为子类和父类可能存在着实现细节的依赖。
子类在继承父类的时候,往往不得不关注父类的实现细节,而父类
如果在修改其内部实现的时候,如果不考虑子类,也往往会影响到子类。
举个例子:
public class Base {
private static final int MAX_NUM = 1000;
private int[] arr = new int[MAX_NUM];
private int count; public void add(int number) {
if (count < MAX_NUM) {
arr[count++] = number;
}
} public void addAll(int[] numbers) {
for (int num: numbers) {
add(num);
}
}
}
public class Child extends Base{
private long sum;
@Override
public void add(int number) {
super.add(number);
sum += number;
}
@Override
public void addAll(int[] numbers) {
super.addAll(numbers);
for (int i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
}
public long getSum() {
return sum;
}
}
public class Use { public static void main(String[] args) {
Child child = new Child();
child.addAll(new int[]{1, 2, 3});
System.out.println("The sum is " + child.getSum());//The sum is 12
}
}
说明:如果子类不知道父类的实现细节就不能正确地扩展。子类和父类之间是细节依赖的。
因此,父类不能随意增加公开的方法,因为给父类增加等于给子类增加,而子类可能需要
重写该方法才能保证其正确性。
2.继承没有反应is-a关系
继承关系是用来反应is-a关系的,子类是父类对象的一种,
父类的属性和行为也适用于子类。在is-a关系中,重写方法
时,子类不应该改变父类的预期行为,但这在Java的继承中
是无法约束的。
3.如何应对继承的双面性
方法一:避免使用继承
1)使用final避免继承:
final方法不能被重写,final类不能被继承。
给方法添加final,父类就保留了随意修改该方法内部实现的自由。类添加final同理。
2)优先使用组合而不是继承:
使用组合可以避免父类变化对子类的影响,从而保护子类。
public class Child extends Base{
private Base base;
private long sum;
public Child(){
base = new Base();
}
public void add(int number) {
base.add(number);
sum+=number;
}
public void addAll(int[] numbers) {
base.addAll(numbers);
for(int i=0;i<numbers.length;i++){
sum+=numbers[i];
}
}
public long getSum() {
return sum;
}
}
3)使用接口
方法二:正确使用继承
三大场景
1)父类是别人写的,我们写子类
我们需要注意:
重写方法不要改变预期行为
阅读文档说明,理解可重写方法的实现机制,尤其是方法间的依赖关系
基类如果修改,阅读其修改说明
2)我们写基类,别人写子类
使用继承反应真正的is-a关系,只将正则公共的部分放入基类。
对不希望被重写的公开方法添加final修饰符
写文档,对子类如何实现提供指导
写修改说明
三、内部类
一个类可以放在另一个类的内部,该类称为内部类,包含它的类称为外部类。
一般而言,内部类与包含它的外部类有密切的关系,而与其他类关系不大,使用内部类,
可以对外部完全隐藏,因此可以有更好的封装性,代码实现上也更为简洁。
不过,内部类知识Java编译器的概念,对于Java虚拟机而言是不知道有内部类这回事的,
每个内部类都被编译为一个独立的类,生成一个独立的字节码文件。
1.静态内部类
public class Outer {
private static int shared = 100;
//静态内部类只能访问外部类的静态变量和方法,实例变量和方法不能访问。
public static class StaticInner {
public void innerMethod() {
System.out.println("I got the outer shared=" + shared);
}
}
public void test() {
//在外部类内部,可以直接使用静态内部类
StaticInner staticInner = new StaticInner();
staticInner.innerMethod();
}
}
//public静态内部类可以被外部使用
Outer.StaticInner inner = new Outer.StaticInner();
inner.innerMethod(); //I got the outer shared=100
静态内部类的实现:
代码实际上会生成两个类,一个是Outer,一个是Outer$StaticInner
public class Outer {
private static int shared = 100;
public void test() {
Outer$StaticInner staticInner = new Outer$StaticInner();
staticInner.innerMethod();
}
static int access$0(){
return shared;
}
} public static class Outer$StaticInner {
public void innerMethod() {
//内部类访问了外部类的静态私有变量,类的私有变量是不能被外部访问到的
//Java的解决办法:自动为Outer生成一个非私有访问方法access$0,它返回shared变量。
System.out.println("I got the outer shared=" + Outer.access$0());
}
}
静态内部类使用场景:与外部类关系密切,且不需要使用外部类实例变量。
2.成员内部类 成员内部类没有static修饰
public class Outer {
private static int a = 100;
public class Inner {
public static final int A = 1; // ok
//public static int b = 2; 报错
//除了外部类的静态变量和方法,成员内部类可以访问实例变量和方法
public void innerMethod() {
System.out.println("I got the outer a = " + a);
action();
//如果内部类有方法与外部类重名可以使用
//Outer.this.action();
}
} private void action() {
System.out.println("action");
} public void test() {
Inner inner = new Inner();
inner.innerMethod();
}
}
Outer outer = new Outer();
//与静态内部类不同,成员内部类总是与一个外部类的实例关联
Outer.Inner inner = outer.new Inner();
//Outer.Inner inner1 = new Outer.Inner(); 编译器报错
与静态内部类不同,成员内部类里面不能定义静态变量和方法,final修饰的除外。(想一想,WHY????)
成员内部类的实现,也会生成两个类:
public class Outer {
private static int a = 100;
public void action() {
System.out.println("action");
}
public void test() {
Outer$Inner inner = new Outer$Inner();
inner.innerMethod();
}
static int access$0(Outer outer) {
return outer.a;
}
static void access$1(Outer outer) {
outer.action();
}
} public static class Outer$Inner {
final Outer outer;
public Outer$Inner(Outer outer) {
this.outer = outer;
}
public void innerMethod() {
System.out.println("outer a " + Outer.access$0(outer));
Outer.access$1(outer);
}
}
成员内部类应用场景:内部类与外部类关系密切,需要访问外部类的实例变量或者方法。
另外,外部类的一些方法的返回值可能是某些接口,为了返回这个接口,外部类方法可能使用
内部实现这些接口,这个内部类可以设置为private,完全对外隐藏。(不需要其他类使用的接口)
3.方法内部类
public class Outer {
private int a =100;
public void test(final int param) {
final String str = "hello";
//方法内部类只能在定义它的方法内使用
//如果该方法是实例方法,则除了静态变量和方法外
//内部类还能直接访问外部类的实例变量和方法
//如果该方法是静态方法,则只能外部类的访问静态变量和方法
class Inner {
public void innerMethod() {
System.out.println("outer a " + a);
System.out.println("param " + param);
System.out.println("local str " + str);
}
}
Inner inner = new Inner();
inner.innerMethod();
}
public static void main(String[] args) {
Outer outer = new Outer();
outer.test(88);
}
}
方法内部类的实现:
public class Outer {
private int a =100;
public void test(final int param) {
final String str = "hello";
Inner inner = new Inner(this, param);
inner.innerMethod();
} static int access$0(Outer outer) {
return outer.a;
} public class Inner {
Outer outer;
int param;
public Inner(Outer outer, int param) {
this.outer = outer;
this.param = param;
}
public void innerMethod() {
System.out.println("outer a " + Outer.access$0(this.outer));
System.out.println("param " + param);
//String str 并没有作为参数传递,这是因为它被定义为了常量,可直接使用
System.out.println("local str " + "hello");
}
}
public static void main(String[] args) {
Outer outer = new Outer();
outer.test(88);
}
}
以上代码也解释了为什么方法内部类访问外部方法的参数和局部变量必须定义为final:
因为内部方法类操作的实际是自己的实例变量,并不是外部变量,只是这些变量和外部
变量有一样的值。所以对这些变量赋值,并不会改变外部方法变量的值,
为了避免混淆干脆将外部方法的局部变量和参数声明为final。
如果确实需要修改外部方法的局部变量和参数,可以把他们声明为数组:
public class Outer {
private int a =100;
public void test(final int param) {
final String[] str = new String[] {"hello"};
class Inner {
public void innerMethod() {
str[0] = "hello world";
}
}
}
}
4.匿名内部类
创建对象的时候定义的类:
四、枚举
枚举可以定义在一个单独的文件中,也可以定义在类的内部。
public enum Size {
SMALL,MEDIUM,LARGE
}
Size size = Size.LARGE;
System.out.println(size); //LARGE
枚举变量可以使用equals和==进行比较。枚举类型都有一个方法
ordinal(),表示枚举时在声明时的顺序,从0开始。另外,枚举类型都实现了Comparable。
在switch内部枚举值不能带枚举类型前缀。
枚举类的实现:枚举类型会被Java编译器转换为一个对应的类,该类继承了java.lang.Enum类
public final class Size extends Enum<Size> {
public static final Size SMALL = new Size("SMALL", 0);
public static final Size MEDIUM = new Size("MEDIUM", 1);
public static final Size LARGE = new Size("LARGE", 2);
private static Size[] VALUES = new Size[] { SMALL, MEDIUM, LARGE }; private Size(String name, int ordinal) {
super(name, ordinal);
} public static Size[] values() {
Size[] values = new Size[VALUES.length];
System.arraycopy(VALUES, 0, values, 0, VALUES.length);
return values;
} public static Size valueOf(String name) {
return Enum.valueOf(Size.class, name);
}
}
应用实例:
public enum Size {
//枚举值的定义必须放在最上面
//枚举值写完后必须以分号结尾
//枚举值定义完成后才能写其他代码
SMALL("S", "小号"),
MEDIUM("M", "中号"),
LARGE("L", "大号");
private String abbr;
private String title; private Size(String abbr, String title) {
this.abbr = abbr;
this.title = title;
}
public String getAbbr() {
return abbr;
}
public void setAbbr(String abbr) {
this.abbr = abbr;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public static Size fromAbbr(String abbr) {
for (Size size : Size.values()) {
if (size.getAbbr().equals(abbr)) {
return size;
}
}
return null;
}
}
自定义枚举id(思考为什么使用自定义id而不是使用ordinal):
public enum Size {
XSMALL(10), SMALL(20), MEDIUM(30), LARGE(40);
private int id;
private Size(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
Java笔记(二)类的更多相关文章
- Java笔记---枚举类和注解
Java笔记---枚举类和注解 一.枚举类 自定义枚举类 方式一:JDK5.0之前自定义枚举类 class Seasons { //1. 声明Seasons对象的属性 private final St ...
- Java笔记 #04# 类的初始化顺序补充
参考java中的类的初始化顺序详解 package org.sample; class Bread { Bread() { System.out.println("Bread()" ...
- Java笔记(二十八)……IO流下 IO包中其他常用类以及编码表问题
PrintWriter打印流 Writer的子类,既可以接收字符流,也可以接收字节流,还可以接收文件名或者文件对象,非常方便 同时,还可以设置自动刷新以及保持原有格式写入各种文本类型的print方法 ...
- Java笔记(二十七)……IO流中 File文件对象与Properties类
File类 用来将文件或目录封装成对象 方便对文件或目录信息进行处理 File对象可以作为参数传递给流进行操作 File类常用方法 创建 booleancreateNewFile():创建新文件,如果 ...
- Java笔记(二十四)……集合工具类Collections&Arrays
Collections 集合框架的工具类,方法全部为静态 Collections与Collection的区别 Collection是集合框架的一个顶层接口,里面定义了单列集合的共性方法 Collect ...
- JAVA笔记【类】
java的概述和编程基础在这里我就不过多的强调了,因为已经有学习C和C++的基础了,我在这里强调一下类和对象. [一]类的定义: Java类的定义包括类声明和类体两个部分,其中类体又包含变量声明,方法 ...
- JAVA笔记11__File类/File类作业/字节输出流、输入流/字符输出流、输入流/文件复制/转换流
/** * File类:文件的创建.删除.重命名.得到路径.创建时间等,是唯一与文件本身有关的操作类 */ public class Main { public static void main(St ...
- java笔记--超级类Object多线程的应用+哲学家进餐算法内部类与多线程结合
关于Object类中的线程方法: Object类是所有Java类的 父类,在该类中定义了三个与线程操作有关的方法,使得所有的Java类在创建之后就支持多线程 这三个方法是:notify(),notif ...
- Java笔记(二十六)……IO流上 字节流与字符流
概述 IO流用来处理设备之间的数据传输 Java对数据的操作时通过流的方式 Java用于操作流的对象都在IO包中 流按操作的数据分为:字节流和字符流 流按流向不同分为:输入流和输出流 IO流常用基类 ...
- Java笔记(二十五)……其他常用API
System类 工具类全部都是静态方法 常用方法 获取系统属性信息 static PropertiesgetProperties() static StringgetProperty(String k ...
随机推荐
- jQuery之CSS选择器的处理机制
<!DOCTYPE html><html lang="en"><head> <meta charset="utf-8" ...
- 如何使用Scrapy框架实现网络爬虫
现在用下面这个案例来演示如果爬取安居客上面深圳的租房信息,我们采取这样策略,首先爬取所有租房信息的链接地址,然后再根据爬取的地址获取我们所需要的页面信息.访问次数多了,会被重定向到输入验证码页面,这个 ...
- 20165206 2017-2018-2 《Java程序设计》第二周学习总结
20165205 2017-2018-2 <Java程序设计>第一周学习总结 教材学习内容总结 java语言共有8种基本数据类型,分别是boolean.byte.short.char.in ...
- tempalte模板
tempalte模板层: 功能:为了更有逻辑的将数据库中的数据渲染到模板中: 模拟数据源: DB = [ {"hostname":"c1.com"," ...
- Leetcode刷题第20天
一.找树左下角的值 题目:513. Find Bottom Left Tree Value C++ Soution 1: /** * Definition for a binary tree node ...
- 第k个互质数(二分 + 容斥)
描述两个数的a,b的gcd为1,即a,b互质,现在给你一个数m,你知道与它互质的第k个数是多少吗?与m互质的数按照升序排列. 输入 输入m ,k (1<=m<=1000000;1<= ...
- codeforces 1037
题解: E-trips 哎哎哎好傻逼啊 没有想到算不能的一直在想怎么算能的 太傻逼了 其实很简单 我们只需要对好友<=k的首先dfs一下给他连接着的朋友-1 然后如果小于了就递归下去 这个正确性 ...
- npm淘宝镜像的设置和删除
设置 npm config set registry https://registry.npm.taobao.org npm config set disturl https://npm.taobao ...
- loadrunner场景报错:Error: CCI compilation error -/tmp/brr_5d65oV/netdir/E/\320\324/Action.c (318): undeclared identifier `LAST'解决思路
在windows下写的脚本编译通过 但是拿到linux agent场景执行中就会提示如下,同样的脚本在windows agent下没有任何问题 agent连的是linux负载机 通过脚本一行一行排查, ...
- loadrunner下的putty和plink
loadrunner中是有集成plink和putty的,难怪可以通过监控机监控linux上的负载情况呢,可以通过这个命令来进行访问:C:\Program Files\Mercury\LoadRunne ...