proper implementation for non-statics

This commit is contained in:
Sander Hautvast 2023-10-13 17:00:44 +02:00
parent 7313d24777
commit 6cf0365dd0
10 changed files with 353 additions and 115 deletions

7
Cargo.lock generated
View file

@ -196,6 +196,7 @@ name = "java_rs"
version = "0.1.0"
dependencies = [
"anyhow",
"once_cell",
"zip",
]
@ -223,6 +224,12 @@ dependencies = [
"adler",
]
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "password-hash"
version = "0.4.2"

View file

@ -5,4 +5,5 @@ edition = "2021"
[dependencies]
anyhow = { version = "1.0", features = ["default"] }
once_cell = { version = "1.18.0", features = [] }
zip = { version = "0.6", features = ["zstd"] }

View file

@ -4,27 +4,97 @@ use crate::heap::{Object, ObjectRef};
use anyhow::{anyhow, Error};
use std::collections::HashMap;
use std::fmt;
use std::hash::Hash;
use std::rc::Rc;
use std::sync::Arc;
use crate::io::read_u16;
// the class definition as read from the class file + derived values
#[derive(Debug)]
//TODO create factory function
pub struct Class {
pub minor_version: u16,
pub major_version: u16,
pub constant_pool: Rc<HashMap<u16, CpEntry>>,
pub access_flags: u16,
pub this_class: u16,
pub super_class: u16,
pub interfaces: Vec<u16>,
pub fields: Vec<Field>,
pub name: String,
pub super_class_name: Option<String>,
pub super_class: Option<Rc<Class>>,
pub interface_indices: Vec<u16>,
pub interfaces: Vec<Class>,
pub fields: HashMap<String, Field>,
pub methods: HashMap<String, Method>,
pub attributes: HashMap<String, AttributeType>,
pub(crate) field_mapping: Option<HashMap<String, HashMap<String, (String, usize)>>>, // first key: this/super/supersuper-name(etc), second key: fieldname, value (type, index)
}
impl Class {
pub fn new(minor_version: u16,
major_version: u16,
constant_pool: Rc<HashMap<u16, CpEntry>>,
access_flags: u16,
this_class: u16,
super_class_index: u16,
interface_indices: Vec<u16>,
fields: HashMap<String, Field>,
methods: HashMap<String, Method>,
attributes: HashMap<String, AttributeType>) -> Self {
let name = Class::class_name(this_class, constant_pool.clone()).unwrap();
let super_class_name = Class::class_name(super_class_index, constant_pool.clone());
Self {
major_version,
minor_version,
constant_pool,
access_flags,
name,
super_class_name,
super_class: None, // has to be instantiated later, because it involves classloading. maybe not store it here
interface_indices,
interfaces: vec![], // same
fields,
methods,
attributes,
field_mapping: None,
}
}
pub(crate) fn n_fields(&self) -> usize {
self.field_mapping.as_ref().map_or(0, |m| m.len())
}
// Create a mapping per field(name) to an index in the storage vector that contains the instance data.
// When a field is stored, first the index will be looked up, using the qualified name (from the FieldRef)
// The qualified name is the combination of class name and field name.
// The class name is needed as part of the key to separate class from superclass fields
// (duplicates in the singular field name are allowed).
// This way `this.a` can be differentiated from `super.a`.
//
// this method looks up this and super classes and calls map_fields for each.
pub fn initialize(&mut self) {
let mut field_mapping = HashMap::new();
let mut field_map_index: usize = 0;
Class::map_fields(&mut field_mapping, self, &mut field_map_index);
let mut sooper = &self.super_class;
while let Some(super_class) = sooper {
Class::map_fields(&mut field_mapping, super_class, &mut field_map_index);
sooper = &super_class.super_class;
}
self.field_mapping = Some(field_mapping);
}
// part of the initialize procedure
fn map_fields(field_mapping: &mut HashMap<String, HashMap<String, (String, usize)>>, class: &Class, field_map_index: &mut usize) {
let mut this_fields = HashMap::new(); //fields in class are stored per class and every superclass.
for field in &class.fields {
this_fields.insert(field.0.to_owned(), (field.1.type_of().to_owned(), *field_map_index)); //name => (type,index)
*field_map_index += 1;
}
let this_name = class.name.to_owned();
field_mapping.insert(this_name, this_fields);
}
pub fn get_version(&self) -> (u16, u16) {
(self.major_version, self.minor_version)
}
@ -35,16 +105,58 @@ impl Class {
.ok_or(anyhow!("Method {} not found", name))
}
pub fn get_name(&self) -> &str {
if let CpEntry::ClassRef(name_index ) = self.constant_pool.get(&self.this_class).unwrap(){
if let CpEntry::Utf8(name) = self.constant_pool.get(name_index).unwrap(){
return name;
fn class_name(super_class_index: u16, constant_pool: Rc<HashMap<u16, CpEntry>>) -> Option<String> {
if super_class_index == 0 {
None
} else if let CpEntry::ClassRef(name_index) = constant_pool.get(&super_class_index).unwrap() {
if let CpEntry::Utf8(name) = constant_pool.get(name_index).unwrap() {
Some(name.to_owned())
} else {
None
}
} else {
None
}
}
panic!();
// convienence methods for data from the constantpool
pub fn get_field_ref(&self, index: &u16) -> Option<(&u16, &u16)> {
if let CpEntry::Fieldref(class_index, name_and_type_index) = self.constant_pool.get(index).unwrap() {
Some((class_index, name_and_type_index))
} else {
None
}
}
pub fn get_class_ref(&self, index: &u16) -> Option<&u16> {
if let CpEntry::ClassRef(name_index) = self.constant_pool.get(index).unwrap() {
Some(name_index)
} else {
None
}
}
pub fn get_utf8(&self, index: &u16) -> Option<&String> {
if let CpEntry::Utf8(utf8) = self.constant_pool.get(index).unwrap() {
Some(utf8)
} else {
None
}
}
pub fn get_name_and_type(&self, index: &u16) -> Option<(&u16, &u16)> {
if let CpEntry::NameAndType(name_index, type_index) = self.constant_pool.get(index).unwrap(){
Some((name_index, type_index))
} else {
None
}
}
}
unsafe impl Send for Class {}
unsafe impl Sync for Class {}
pub struct Method {
@ -101,6 +213,7 @@ pub struct Field {
pub(crate) name_index: u16,
descriptor_index: u16,
attributes: HashMap<String, AttributeType>,
index: u16,
}
impl fmt::Debug for Field {
@ -120,6 +233,7 @@ impl Field {
name_index: u16,
descriptor_index: u16,
attributes: HashMap<String, AttributeType>,
field_index: u16,
) -> Self {
Field {
constant_pool,
@ -127,18 +241,15 @@ impl Field {
name_index,
descriptor_index,
attributes,
index: field_index,
}
}
pub fn name(&self) -> String {
let mut name = String::new();
name.push(' ');
if let CpEntry::Utf8(s) = &self.constant_pool.get(&self.name_index).unwrap() {
name.push_str(s);
pub fn name(&self) -> &String {
if let CpEntry::Utf8(utf8) = &self.constant_pool.get(&self.name_index).unwrap() {
return utf8;
}
name
unreachable!()
}
pub fn type_of(&self) -> &String {
@ -174,10 +285,11 @@ pub fn get_modifier(modifier: u16) -> String {
output
}
//TODO implement more types
#[derive(Debug)]
pub enum AttributeType {
ConstantValue(u16),
Code(MethodCode),
Code(Box<MethodCode>),
StackMapTable,
BootstrapMethods,
NestHost,
@ -256,8 +368,10 @@ impl MethodCode {
#[derive(Debug)]
pub enum Value {
Void, // variant returned for void methods
Null, // 'pointer' to nothing
Void,
// variant returned for void methods
Null,
// 'pointer' to nothing
I32(i32),
I64(i64),
F32(f32),
@ -268,4 +382,5 @@ pub enum Value {
}
unsafe impl Send for Value {}
unsafe impl Sync for Value {}

View file

@ -4,6 +4,8 @@ use anyhow::Error;
use std::collections::HashMap;
use std::rc::Rc;
// The native classoader
pub fn load_class(bytecode: Vec<u8>) -> Result<Class, Error> {
let pos = &mut 0;
check_magic(&bytecode, pos);
@ -37,9 +39,10 @@ pub fn load_class(bytecode: Vec<u8>) -> Result<Class, Error> {
}
let fields_count = read_u16(&bytecode, pos);
let mut fields = vec![];
for _ in 0..fields_count {
fields.push(read_field(constant_pool.clone(), pos, &bytecode));
let mut fields = HashMap::new();
for i in 0..fields_count {
let field = read_field(constant_pool.clone(), pos, &bytecode, i);
fields.insert(field.name().to_owned(), field);
}
let methods_count = read_u16(&bytecode, pos);
@ -56,11 +59,11 @@ pub fn load_class(bytecode: Vec<u8>) -> Result<Class, Error> {
if let Some(att) = some {
attributes.insert(att.0, att.1);
} else {
panic!(); // bug/not-implemented
panic!("attribute not found"); // bug/not-implemented
}
}
Ok(Class {
Ok(Class::new(
minor_version,
major_version,
constant_pool,
@ -71,7 +74,7 @@ pub fn load_class(bytecode: Vec<u8>) -> Result<Class, Error> {
fields,
methods,
attributes,
})
))
}
fn check_magic(bytecode: &[u8], pos: &mut usize) {
@ -83,6 +86,7 @@ fn check_magic(bytecode: &[u8], pos: &mut usize) {
fn read_constant_pool_entry(cp_index: &mut u16, index: &mut usize, bytecode: &[u8]) -> CpEntry {
let tag = read_u8(bytecode, index);
// println!("tag {}", tag);
match tag {
1 => {
let len = read_u16(bytecode, index) as usize;
@ -137,10 +141,21 @@ fn read_constant_pool_entry(cp_index: &mut u16, index: &mut usize, bytecode: &[u
let descriptor_index = read_u16(bytecode, index);
CpEntry::NameAndType(name_index, descriptor_index)
}
// 15 MethodHandle,
// 16 MethodType,
15 =>{
let reference_kind = read_u8(bytecode, index);
let reference_index = read_u16(bytecode, index);
CpEntry::MethodHandle(reference_kind, reference_index)
}
16 => {
let descriptor_index = read_u16(bytecode, index);
CpEntry::MethodType(descriptor_index)
}
// 17 Dynamic,
// 18 InvokeDynamic,
18 => {
let bootstrap_method_attr_index = read_u16(bytecode, index);
let name_and_type_index = read_u16(bytecode, index);
CpEntry::InvokeDynamic(bootstrap_method_attr_index, name_and_type_index)
}
// 19 Module,
// 20 Package,
_ => panic!("cp entry type not recognized"),
@ -151,6 +166,7 @@ fn read_field(
constant_pool: Rc<HashMap<u16, CpEntry>>,
index: &mut usize,
bytecode: &[u8],
field_index: u16,
) -> Field {
let access_flags = read_u16(bytecode, index);
let name_index = read_u16(bytecode, index);
@ -170,6 +186,7 @@ fn read_field(
name_index,
descriptor_index,
attributes,
field_index,
)
}
@ -210,7 +227,7 @@ fn read_attribute(
*index += attribute_length;
if let CpEntry::Utf8(s) = &constant_pool.get(&attribute_name_index).unwrap() {
// println!("Att [{}]", s);
println!("Att [{}]", s);
return match s.as_str() {
"ConstantValue" => {
assert_eq!(info.len(), 2);
@ -241,17 +258,22 @@ fn read_attribute(
}
Some((
"Code".into(),
AttributeType::Code(MethodCode::new(
AttributeType::Code(Box::new(MethodCode::new(
max_stack,
max_locals,
code,
exception_table,
code_attributes,
)),
))),
))
}
"SourceFile" => Some(("SourceFile".into(), AttributeType::SourceFile)),
"LineNumberTable" => Some(("SourceFile".into(), AttributeType::LineNumberTable)),
"RuntimeVisibleAnnotations" => Some(("".into(), AttributeType::RuntimeInvisibleAnnotations)), //stub
"NestMembers" => Some(("".into(), AttributeType::NestMembers)),//stub
"BootstrapMethods" => Some(("".into(), AttributeType::BootstrapMethods)),//stub
"InnerClasses" => Some(("".into(), AttributeType::InnerClasses)),//stub
//TODO more actual attribute implementations
_ => None,
};
}
@ -271,4 +293,7 @@ pub enum CpEntry {
MethodRef(u16, u16),
InterfaceMethodref(u16, u16),
NameAndType(u16, u16),
MethodHandle(u8, u16),
MethodType(u16),
InvokeDynamic(u16, u16),
}

View file

@ -7,13 +7,15 @@ use std::sync::Arc;
use crate::class::{Class, Value};
use crate::classloader::CpEntry;
// trying to implement efficient object instance storage
pub struct Object {
// locked: bool,
// hashcode: i32,
pub class: Rc<Class>,
pub data: HashMap<u16, Arc<UnsafeCell<Value>>>, //TODO optimize
pub data: Option<Vec<Arc<UnsafeCell<Value>>>>,
}//arrays
// can contain object or array
#[derive(Debug)]
pub enum ObjectRef {
ByteArray(Vec<i8>),
@ -28,45 +30,78 @@ pub enum ObjectRef{
Object(Box<Object>),
}
unsafe impl Send for Object {}
unsafe impl Sync for Object {}
// object, not array
impl Object {
pub fn new(class: Rc<Class>, data: HashMap<u16, Arc<UnsafeCell<Value>>>) -> Self {
Self { class, data }
pub fn new(class: Rc<Class>) -> Self {
Self { class, data: None }
}
fn get_field(&self, cp_index: &u16) -> &str {
// initializes all non-static fields to their default values
pub(crate) fn init_fields(&mut self) {
let mut field_data = Vec::with_capacity(self.class.n_fields());
for (_, fields) in self.class.field_mapping.as_ref().unwrap() {
for (_, (fieldtype, _)) in fields {
let value = match fieldtype.as_str() {
"Z" => Value::BOOL(false),
"B" => Value::I32(0),
"S" => Value::I32(0),
"I" => Value::I32(0),
"J" => Value::I64(0),
"F" => Value::F32(0.0),
"D" => Value::F64(0.0),
"L" => Value::Null,
_ => Value::Void,
};
field_data.push(Arc::new(UnsafeCell::new(value)));
}
}
self.data = Some(field_data);
}
pub fn set(&mut self, class_name: &String, field_name: &String, value: Arc<UnsafeCell<Value>>) {
let p = self.class.field_mapping.as_ref().unwrap();
let p2 = p.get(class_name).unwrap();
let (_type, index) = p2.get(field_name).unwrap();
self.data.as_mut().unwrap()[*index] = value;
}
pub fn get(&mut self, class_name: &String, field_name: &String) -> &Arc<UnsafeCell<Value>> {
let (_type, index) = &self.class.field_mapping.as_ref().unwrap().get(class_name).unwrap().get(field_name).unwrap(); // (index, type)
&self.data.as_ref().unwrap()[*index]
}
fn get_field_name(&self, cp_index: &u16) -> &str {
if let CpEntry::Utf8(name) = self.class.constant_pool.get(cp_index).unwrap() {
return name;
}
panic!()
}
unsafe fn get_mut<T>(ptr: &UnsafeCell<T>) -> &mut T {
unsafe { &mut *ptr.get() }
}
}
impl fmt::Debug for Object {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let fields: Vec<String> = self.data.iter().map(|(k, v)| {
let mut r: String = self.get_field(k).into();
r.push(':');
r.push_str(format!("{:?}", v).as_str());
r
}
).collect();
// let fields: Vec<String> = self.data.unwrap().iter().map(|(k)| {
// // let mut r: String = self.get_field_name(k).into();
// // r.push(':');
// // r.push_str(format!("{:?}").as_str());
// // r
// }
// ).collect();
write!(
f,
"{} {{ {:?} }}",
self.class.get_name(), fields
"{}",
self.class.name
)
}
}
// will using Arc's enable a GC-less heap????
pub(crate) struct Heap {
objects: Vec<Arc<UnsafeCell<ObjectRef>>>,
}

View file

@ -121,7 +121,7 @@ pub const FRETURN: u8 = 174; // (0xae) Return float from method
pub const DRETURN: u8 = 175; // (0xaf) Return double from method
// pub const areturn: u8 = 176; //(0xb0) return reference
pub const RETURN_VOID: u8 = 177; // (0xb1) Return void from method (actually 'return' but that's a keyword)
// pub const getstatic: u8 = 178; // (0xb2) Get static field from class
pub const GETSTATIC: u8 = 178; // (0xb2) Get static field from class
pub const GETFIELD: u8 = 180; // (0xb4) Fetch field from object3
pub const PUTFIELD: u8 = 181; // (0xb5) Set field in object
pub const INVOKEVIRTUAL: u8 = 182; // (0xb6) Invoke instance method; dispatch based on class

111
src/vm.rs
View file

@ -4,6 +4,7 @@ use std::rc::Rc;
use std::sync::Arc;
use anyhow::{anyhow, Error};
use once_cell::unsync::Lazy;
use crate::class::{AttributeType, Class, Value};
use crate::class::Value::Void;
@ -18,6 +19,7 @@ struct StackFrame {
data: Vec<Arc<UnsafeCell<Value>>>,
}
// maybe just call frame
impl StackFrame {
fn new(at_class: &str, at_method: &str) -> Self {
let mut at: String = at_class.into();
@ -39,9 +41,11 @@ impl StackFrame {
}
}
//trying to be ready for multithreaded as much as possible, using Arc's and all, but it will still require (a lot of) extra work
static mut CLASSDEFS: Lazy<HashMap<String, Rc<Class>>> = Lazy::new(|| HashMap::new()); //TODO add mutex...and Arc most likely
pub struct Vm {
classpath: Vec<String>,
classes: HashMap<String, Rc<Class>>,
heap: Heap,
stack: Vec<StackFrame>,
}
@ -52,7 +56,7 @@ const PATH_SEPARATOR: char = ':';
#[cfg(target_family = "windows")]
const PATH_SEPARATOR: char = ';';
// The singlethreaded VM (maybe a future Thread)
impl Vm {
fn local_stack(&mut self) -> &mut StackFrame {
let i = self.stack.len() - 1;
@ -62,49 +66,51 @@ impl Vm {
pub fn new(classpath: &'static str) -> Self {
Self {
classpath: classpath.split(PATH_SEPARATOR).map(|s| s.to_owned()).collect(),
classes: HashMap::new(),
heap: Heap::new(),
stack: vec![],
}
}
/// parse the binary data into a Class struct
/// gets the file from cache, or reads it from classpath
/// Vm keeps ownership of the class and hands out Arc references to it
pub fn get_class(&mut self, class_name: &str) -> Result<Rc<Class>, Error> {
// parse the binary data into a Class struct
// gets the file from cache, or reads it from classpath
// Vm keeps ownership of the class and hands out Arc references to it
pub fn get_class(&self, class_name: &str) -> Result<Rc<Class>, Error> {
println!("get_class {}", class_name);
let entry = self.classes.entry(class_name.into());
unsafe {
let entry = CLASSDEFS.entry(class_name.into());
let entry = entry.or_insert_with(|| {
// print!("read class {} ", class_name);
let resolved_path = find_class(&self.classpath, class_name).expect("Class not found");
let resolved_path = find_class(&self.classpath, class_name).unwrap();
// println!("full path {}", resolved_path);
let bytecode = read_bytecode(resolved_path).unwrap();
Rc::new(load_class(bytecode).unwrap())
let mut class = load_class(bytecode).unwrap();
let super_class_name = class.super_class_name.as_ref();
if let Some(super_class_name) = super_class_name {
if let Ok(super_class) = self.get_class(&super_class_name) {
class.super_class = Some(super_class.clone());
}
}
class.initialize();
Rc::new(class)
});
Ok(entry.clone())
}
}
pub fn new_instance(&self, class: Rc<Class>) -> Object {
//TODO add fields from superclasses
let mut data = HashMap::new();
for f in &class.fields {
let value = match f.type_of().as_str() {
"Z" => Value::BOOL(false),
"B" => Value::I32(0),
"S" => Value::I32(0),
"I" => Value::I32(0),
"J" => Value::I64(0),
"F" => Value::F32(0.0),
"D" => Value::F64(0.0),
"L" => Value::Null,
_ => Value::Void,
};
data.insert(f.name_index, Arc::new(UnsafeCell::new(value)));
}
Object::new(class.clone(), data)
pub fn new_instance(class: Rc<Class>) -> Object {
let mut class = class;
// let mut outer = HashMap::new();
//TODO
let mut instance = Object::new(class.clone());
instance.init_fields();
instance
}
/// execute the bytecode
/// contains unsafe, as I think that mimics not-synchronized memory access in the original JVM
pub fn execute(
&mut self,
class_name: &str,
@ -269,41 +275,45 @@ impl Vm {
self.stack.pop(); // Void is also returned as a value
return Ok(Arc::new(UnsafeCell::new(Void)));
}
GETSTATIC => {
let cp_index = read_u16(&code.opcodes, pc);
let (class_index, _field_name_and_type_index) = class.get_field_ref(&cp_index).unwrap();
let class_name_index = class.get_class_ref(class_index).unwrap();
let class_name = class.get_utf8(class_name_index).unwrap();
let class = self.get_class(class_name.as_str())?;
println!("{:?}", class); //TODO
}
GETFIELD => {
unsafe {
let cp_index = read_u16(&code.opcodes, pc);
if let CpEntry::Fieldref(_class_index, name_and_type_index) =
method.constant_pool.get(&cp_index).unwrap()
{
if let Value::Ref(instance) = &*self.local_stack().pop()?.get() {
if let CpEntry::NameAndType(name, _) =
method.constant_pool.get(name_and_type_index).unwrap()
{
let objectref = &(*instance.get());
if let ObjectRef::Object(object) = objectref {
let value = object.data.get(name).unwrap();
let (class_index, field_name_and_type_index) = class.get_field_ref(&cp_index).unwrap();
let (field_name_index, _) = class.get_name_and_type(field_name_and_type_index).unwrap();
let class_name_index = class.get_class_ref(class_index).unwrap();
let class_name = class.get_utf8(class_name_index).unwrap();
let field_name = class.get_utf8(field_name_index).unwrap();
let mut objectref = self.local_stack().pop()?;
if let Value::Ref(instance) = &mut *objectref.get() {
if let ObjectRef::Object(ref mut object) = &mut *instance.get() {
let value = object.get(class_name, field_name);
self.local_stack().push_arc(Arc::clone(value));
}
}
}
}
}
}
PUTFIELD => unsafe {
let cp_index = read_u16(&code.opcodes, pc);
if let CpEntry::Fieldref(_class_index, name_and_type_index) =
method.constant_pool.get(&cp_index).unwrap()
{
if let CpEntry::NameAndType(name_index, _) = method.constant_pool.get(name_and_type_index).unwrap() {
let (class_index, field_name_and_type_index) = class.get_field_ref(&cp_index).unwrap();
let (field_name_index, _) = class.get_name_and_type(field_name_and_type_index).unwrap();
let class_name_index = class.get_class_ref(class_index).unwrap();
let class_name = class.get_utf8(class_name_index).unwrap();
let field_name = class.get_utf8(field_name_index).unwrap();
let value = self.local_stack().pop()?;
let mut objectref = self.local_stack().pop()?;
if let Value::Ref(instance) = &mut *objectref.get() {
if let ObjectRef::Object(ref mut object) = &mut *instance.get() {
object.data.insert(*name_index, value);
} else {
panic!("not an object, maybe array");
}
} // else?
object.set(class_name, field_name, value);
}
}
}
@ -348,7 +358,7 @@ impl Vm {
{
println!("new {}", new_class);
let class = self.get_class(new_class)?;
let object = Arc::new(UnsafeCell::new(ObjectRef::Object(Box::new(self.new_instance(class)))));
let object = Arc::new(UnsafeCell::new(ObjectRef::Object(Box::new(Vm::new_instance(class)))));
self.local_stack().push(Value::Ref(Arc::clone(&object)));
self.heap.new_object(object);
}
@ -523,6 +533,7 @@ fn get_name_and_type(cp: Rc<HashMap<u16, CpEntry>>, index: u16) -> Option<Method
None
}
fn get_hum_args(signature: &str) -> usize {
let mut num = 0;
let mut i = 1;

44
tests/Inheritance.java Normal file
View file

@ -0,0 +1,44 @@
public class Inheritance {
public static void main(String[] args) {
Father father = new Son();
System.out.println(father.i); //why 1?
System.out.println(father.getI()); //2
System.out.println(father.j); //why 10?
System.out.println(father.getJ()); //why 10?
System.out.println();
Son son = new Son();
System.out.println(son.i); //2
System.out.println(son.getI()); //2
System.out.println(son.j); //20
System.out.println(son.getJ()); //why 10?
}
}
class Son extends Father {
int i = 2;
int j = 20;
@Override
public int getI() {
return i;
}
}
class Father {
int i = 1;
int j = 10;
public int getI() {
return i;
}
public int getJ() {
return j;
}
}

Binary file not shown.

View file

@ -3,6 +3,6 @@ public class Main {
public static void main(String[] args){
FloatBean f = new FloatBean();
f.setValue(42F);
System.out.println(f.getValue());
// System.out.println(f.getValue());
}
}